Skip to content

fix(admin): purge soft-deleted users after a grace period (#99) - #102

Merged
kipavy merged 1 commit into
mainfrom
fix/purge-soft-deleted-users
Oct 8, 2026
Merged

kipavy merged 1 commit into
mainfrom
fix/purge-soft-deleted-users

Conversation

@kipavy

@kipavy kipavy commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Closes #99.

Problem

Soft-deleted users kept their email reserved forever. Two causes:

  • The nightly purge job promised by migration 028 never existed.
  • admin_audit_log.target_id referenced users(id) with no ON DELETE action. Soft delete writes an audit row targeting the user, so no soft-deleted user could ever be hard-deleted, not even with force-delete.

Changes

  • migrations/055_admin_audit_target_no_fk.sql drops that FK. Nothing joins the audit log to users, and the trail should outlive the user.
  • src/user_purge.rs adds purge_expired, which runs at boot and in the daily retention loop. For each user soft-deleted longer ago than USER_PURGE_GRACE_DAYS (default 30):
    • Hard-delete the user and write a purge_user audit row in the same transaction.
    • If FKs still block the delete (team owner, team vault editor, audit actor), rewrite the email to <id>@purged.invalid and write a purge_user_release_email audit row once. The delete is retried on later runs.
  • restore_user returns 409 once the email has been released.
  • USER_PURGE_GRACE_DAYS is documented in .env.example and passed through compose.yml.

Migration 028's comment is left unchanged because sqlx checksums applied migrations and editing it would make boot fail.

Deploy note

The first boot purges every user already soft-deleted more than 30 days ago.

Tests

  • A user past the grace period is purged and the email can be registered again (case-insensitive).
  • Live users and users within the grace period are kept.
  • An FK-blocked user gets the email released, audited once across two runs.
  • Force-delete works after a soft delete.
  • Restoring a user whose email was released returns 409.

cargo test --all-targets (683 passed) and cargo clippy --all-targets -- -D warnings pass locally.

🤖 Generated with Claude Code

Soft-deleted users kept their email reserved forever: the purge job that
migration 028 promised never existed, and admin_audit_log.target_id's FK
made every soft-deleted user impossible to hard-delete, force-delete
included, because soft delete audits the user as target.

- 055 drops that FK so the audit trail outlives the user.
- user_purge hard-deletes users soft-deleted longer than
  USER_PURGE_GRACE_DAYS (default 30), at boot and daily, auditing each.
- A user other rows still reference (team owner, shared-object author)
  cannot be deleted; their email is rewritten to <id>@purged.invalid so
  the address is free, and restore then refuses with 409.

Migration 028's comment is left as is: sqlx checksums applied migrations.

Closes #99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kipavy
kipavy force-pushed the fix/purge-soft-deleted-users branch from c6c23fd to 63ad4d9 Compare October 8, 2026 11:52
@kipavy
kipavy merged commit 1f952b3 into main Oct 8, 2026
1 check passed
@kipavy
kipavy deleted the fix/purge-soft-deleted-users branch October 8, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Soft-deleted users keep their email reserved forever (no purge job)

1 participant