mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 05:24:49 +09:00
Deleting is the only action in a control panel that repeating cannot undo, so records a person destroys by hand now leave a tombstone and the Recycle Bin hands them back. SoftDeleteMixin adds deleted_at/deleted_by_id plus soft_delete/restore and query_active()/query_deleted(). recycle_bin_service is a REGISTRY: a model registers with a label function and an optional on_restore hook, so the API lists and restores across types without knowing what a domain is. Adding the next soft-deletable model is one register() call. Applied to Domain (a real, destructive case -- deleting one used to lose the record outright) and SavedView. DELETE /domains/<id> now tombstones and still tears the vhost down; restoring a row does NOT re-do side effects unless the type supplies on_restore, which is the honest default. API: GET /recycle-bin (all kinds, newest first), POST /recycle-bin/<kind>/<id>/restore, DELETE /recycle-bin/<kind>/<id> (purge, admin-only -- the one irreversible action), POST /recycle-bin/purge-expired with a 30-day default retention. THE PART THAT NEEDED CARE: both tables had a plain UNIQUE covering what is now a tombstone, so deleting a domain would have permanently burned its name and deleting a view would have blocked re-creating one by the same name. Migration 083 replaces each with a PARTIAL unique index predicated on deleted_at IS NULL -- supported by SQLite 3.8+ and Postgres. Tests pin exactly that: a deleted name can be reused, and the live row stays unique. Also gives SavedView a `slug` (backfilled), the handle the upcoming ?view=<slug> links resolve against. A tombstoned view does not reserve its slug, so deleting a view frees the link name too. Migration is idempotent on both columns and indexes: `deleted_at` is index=True on the mixin, so a database built from the models already has that index, and a bare create_index would abort the upgrade on exactly the installs we most want to keep working. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>