Files
wekan/models/lib/featureFlags.js
T
Lauri Ojansivu 46924985f8 Soft delete for lists: delete = mark, restorable + undoable (#1023).
Deleting a list no longer destroys it. The list and its cards are marked
(deletedAt/deletedBy/deleteBatchId), hidden from the board, recorded as a
reversible delete in history, and restorable via lists.restore or Ctrl+Z.
This is the first slice of "no permanent delete in ordinary use": physical
removal is limited to GDPR/account erasure and a Global-Admin purge gated by
a new Admin Panel / Features / Delete "Enable permanent delete" toggle
(off by default).

- models/lib/softDelete.js: pure notDeleted/softDeleteSet/restoreModifier/
  canPurge helpers (+ tests/softDelete.test.cjs, wired into test:unit:node).
  softDeleteSet omits falsy actor/batch and restore uses $unset so no null is
  ever written to a Date/String schema field.
- enablePermanentDelete feature flag (models/lib/featureFlags.js) + Settings
  schema field.
- deletedAt/deletedBy/deleteBatchId schema fields on Lists and Cards
  (optional, no null default -> absent means live).
- board/swimlane list render paths filter deletedAt: null.
- server/models/lists.js: shared softRemoveList() + lists.softRemove /
  lists.restore / lists.purge (purge gated on canPurge = isAdmin && flag);
  client delete handler and REST DELETE now soft-remove.
- userPositionHistory undo()/redo() handle actionType delete/restore via a
  batch-consistent _applyDeleted() helper.

Design + planned rollout (other entities, Recycle Bin, admin Delete category
table with GDPR data classes) in docs/Features/Undo/Undo.md.
2026-07-17 20:20:06 +03:00

51 lines
2.1 KiB
JavaScript

import { Meteor } from 'meteor/meteor';
import Settings from '/models/settings';
// Synchronously-readable cache of the Admin Panel / Features / Notifications
// toggles (issue #5820). A collection `before.insert` hook (models/activities.js)
// runs in a synchronous context and cannot await a Settings lookup, so the server
// keeps this small cache up to date with a Settings observer and exposes it via a
// plain getter. On the client the flags stay at their defaults (false); the server
// is authoritative for the actual enforcement.
const flags = {
disableActivities: false,
disableNotifications: false,
disableWatch: false,
// Admin Panel / Features / Delete: when true, a Global Admin may PHYSICALLY
// (permanently) delete soft-deleted content. Off by default — ordinary deletes
// are always soft. GDPR/account erasure is a separate path and ignores this.
enablePermanentDelete: false,
};
export function getFeatureFlags() {
return flags;
}
function applyFields(fields) {
if (!fields) return;
if ('disableActivities' in fields) flags.disableActivities = !!fields.disableActivities;
if ('disableNotifications' in fields) flags.disableNotifications = !!fields.disableNotifications;
if ('disableWatch' in fields) flags.disableWatch = !!fields.disableWatch;
if ('enablePermanentDelete' in fields) flags.enablePermanentDelete = !!fields.enablePermanentDelete;
}
if (Meteor.isServer) {
Meteor.startup(async () => {
// observeChanges' `changed` reports only the changed fields, so applyFields
// merges rather than replaces — correct for the single Settings document.
await Settings.find(
{},
{ fields: { disableActivities: 1, disableNotifications: 1, disableWatch: 1, enablePermanentDelete: 1 } },
).observeChangesAsync({
added(id, fields) { applyFields(fields); },
changed(id, fields) { applyFields(fields); },
removed() {
flags.disableActivities = false;
flags.disableNotifications = false;
flags.disableWatch = false;
flags.enablePermanentDelete = false;
},
});
});
}