media.user_id ON DELETE CASCADE strips attachments from entries that survive user deletion #98
Labels
No labels
bug
duplicate
enhancement
future
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/TeaLeaves#98
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium · Confidence: High · Effort: S · Category: code (data integrity)
Evidence
api/src/db/migrations/026_projects.sql:85-89-entries.user_idFK →ON DELETE SET NULL(entries survive account deletion via attribution snapshot).api/src/db/migrations/004_media.sql:11-media.user_id . ON DELETE CASCADE.Problem
Deleting a user preserves their entries (user_id → NULL) but hard-deletes all their
mediarows, so preserved entries lose their attachments (and files orphan on disk, since the purge worker is the only thing that unlinks files).Impact
Silent data loss / broken media on entries the schema explicitly intends to preserve. Defeats the v4 attribution-snapshot design.
Fix
New migration changing
media.user_idFK toON DELETE SET NULL(media is already CASCADE-tied toentry_id, the correct lifecycle anchor); reconcile file cleanup so orphaned files are unlinked.Acceptance criteria
Filed from the 2026-07-15 codebase audit. Full report:
docs/.internal/report-2026-07-15.md(gitignored).Fixed in
763aa0b(v7.2.0 wave 1).Migration
044_media_user_id_set_null.sqlmirrors what migration 026 did forentries:(
user_idwasNOT NULL, so dropping that was required too — same asentriesin 026.)entry_idstaysON DELETE CASCADEas the lifecycle anchor: media dies with its entry, not with whoever uploaded it.Acceptance criteria:
deleteMediaFileintrashWorker.ts).Verified live on the dev server after deploy — migration applied
✓, and:CI run 191 green (241/241 tests).
⚠️ Important scope note — read before assuming attachments now render: #135.
This stops the data loss, but the preserved media is not yet reachable. Every media route authorizes on
media.user_id = <viewer>(media.ts:153,:179, etc.), souser_id IS NULLnever matches and the surviving owner still can't load it. Filed as #135 (v7.2.0), which re-scopes media access to the entry's project the way entries were in v4.That gap already bites today with no deletion involved: a collaborator's uploads are invisible to the project owner right now. I kept it out of this issue deliberately — #98 is a one-migration data-preservation fix; #135 is a multi-route access-control change with its own authorization decisions (notably whether delete stays uploader-only).