mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 11:02:33 -05:00
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.
66 lines
2.6 KiB
JavaScript
66 lines
2.6 KiB
JavaScript
'use strict';
|
|
|
|
// Soft-delete core (delete = mark, never destroy). Design: docs/Features/Undo/Undo.md.
|
|
//
|
|
// Kept Meteor-free and CommonJS so these decisions are unit-testable
|
|
// (tests/softDelete.test.cjs) AND importable from server model code (Meteor's
|
|
// module system reads the named exports off `module.exports`). Every user-facing
|
|
// delete routes through here so the "no permanent delete in ordinary use" rule is
|
|
// enforced in one place; physical removal is only ever done by the flag-gated
|
|
// admin purge or by GDPR/account erasure.
|
|
|
|
// Add the "not soft-deleted" constraint to a Mongo/Minimongo selector.
|
|
// `{ deletedAt: null }` matches BOTH documents whose field is null and whose field
|
|
// is ABSENT (all pre-feature rows), so no migration/backfill is needed. Merges into
|
|
// an existing `$and` when present so we never clobber a sibling constraint.
|
|
function notDeleted(selector) {
|
|
const sel = selector || {};
|
|
if (sel && typeof sel === 'object' && Array.isArray(sel.$and)) {
|
|
return { ...sel, $and: [...sel.$and, { deletedAt: null }] };
|
|
}
|
|
return { ...sel, deletedAt: null };
|
|
}
|
|
|
|
// The `$set` applied when soft-deleting a document. `at` is passed in (never
|
|
// generated here) so the same batch of cascaded deletes shares one timestamp and
|
|
// the function stays pure/deterministic. Falsy userId/batchId are OMITTED rather
|
|
// than set to null: `deletedBy`/`deleteBatchId` are String schema fields and
|
|
// collection2 would reject a null value on update.
|
|
function softDeleteSet(userId, batchId, at) {
|
|
const set = { deletedAt: at };
|
|
if (userId) set.deletedBy = userId;
|
|
if (batchId) set.deleteBatchId = batchId;
|
|
return set;
|
|
}
|
|
|
|
// The full modifier applied when RESTORING (undo of a delete): drop all three
|
|
// bookkeeping fields, so a restored doc is indistinguishable from one that was
|
|
// never deleted. Pure `$unset` (rather than `$set deletedAt: null`) because setting
|
|
// an optional Date field to null trips SimpleSchema type validation; an absent
|
|
// field reads as "live" via the `{ deletedAt: null }` filter.
|
|
function restoreModifier() {
|
|
return {
|
|
$unset: { deletedAt: '', deletedBy: '', deleteBatchId: '' },
|
|
};
|
|
}
|
|
|
|
// Is a document soft-deleted?
|
|
function isDeleted(doc) {
|
|
return !!(doc && doc.deletedAt);
|
|
}
|
|
|
|
// May this user PHYSICALLY purge user content? Only a global admin, and only when
|
|
// the Admin Panel / Features / Delete toggle ("Enable permanent delete for Global
|
|
// Admin") is on. GDPR/account erasure is a SEPARATE path and does not consult this.
|
|
function canPurge(user, enablePermanentDelete) {
|
|
return !!(user && user.isAdmin) && !!enablePermanentDelete;
|
|
}
|
|
|
|
module.exports = {
|
|
notDeleted,
|
|
softDeleteSet,
|
|
restoreModifier,
|
|
isDeleted,
|
|
canPurge,
|
|
};
|