User-Owned Module → User Deletion
purge_user_data() in src/fim_one/web/services/user_deletion.py is the
single path both admin deletion and self-serve account deletion funnel
through. A module that stores per-user state and is not wired in there fails
in one of two ways, neither of which any existing test catches:
- Orphaned files. ORM cascade deletes rows. It knows nothing about disk,
so
uploads/anddata/keep the user's content forever after the account is gone. - A hard 500 on delete. A new table with a FK to
usersand noondeletecascade makesdb.delete(user)raise a FK violation. SQLite now runs withPRAGMA foreign_keys=ON, so this fails in dev too — but only if someone actually deletes a user.
What to add
Find the block marked # 3. Clean up file-system resources before the DB delete. in user_deletion.py.
Files
Collect the ids your module keys on, then remove each path. Deletion must be tolerant of missing paths — the account may be deleted before the module ever wrote anything:
shutil.rmtree(some_dir / some_id, ignore_errors=True)
(some_dir / f"{some_id}.json").unlink(missing_ok=True)
For a glob-shaped layout (one file per user, name-prefixed), glob and unlink rather than assuming a directory exists.
Rows
If the new table has a FK to users without ondelete="CASCADE", add an
explicit FK-safe DELETE in the same function, ordered before the parent row
goes. Prefer declaring the cascade on the FK when the relationship genuinely
is ownership; the explicit delete is for the cases where it is not.
Current registry
Keep this table and the one in CLAUDE.md in step with the code.
| Module | Path | Method |
|---|---|---|
| conversations | data/sandbox/{conv_id}/, uploads/conversations/{conv_id}/, data/workspaces/{conv_id}/, data/dag_checkpoints/{conv_id}.json |
shutil.rmtree / unlink |
| knowledge_bases | uploads/kb/{kb_id}/, data/vector_store/user_{user_id}/ |
shutil.rmtree |
| user uploads | uploads/user_{user_id}/ |
shutil.rmtree |
| avatar | uploads/avatars/{user_id}_* |
glob + unlink |
Test it
Deletion is the one path where "it looked fine" is worthless: the failure
shows up only once real user data exists. Add a case to
tests/test_user_deletion.py that creates the module's rows and its
files, deletes the user, and asserts both are gone.