| 04-folders-sharing-quotas-document-ux |
03 |
api |
| fastapi |
| sqlalchemy |
| postgresql |
| sqlite |
| folders |
| audit-log |
| fts |
| quota |
|
| phase |
provides |
| 03-document-migration-multi-user-isolation |
Document ORM model with folder_id FK, Quota model with CASE WHEN decrement pattern, get_regular_user dep, ownership-assertion-404 pattern |
|
| phase |
provides |
| 04-01 |
AuditLog ORM model with metadata_ attribute, Folder ORM model with UniqueConstraint, Share ORM model |
|
|
| write_audit_log() async helper in backend/services/audit.py (flush-not-commit, never-raises) |
| POST /api/folders — create folder with parent_id, IntegrityError → 409 |
| GET /api/folders — list top-level folders (parent_id IS NULL) |
| GET /api/folders/{id} — folder metadata + breadcrumb array (iterative Python walk) |
| PATCH /api/folders/{id} — rename folder |
| DELETE /api/folders/{id} — cascade-delete with WITH RECURSIVE CTE + atomic quota decrement |
| PATCH /api/documents/{id}/folder — move document to folder (or root) |
| GET /api/documents extended with sort, order, folder_id, q params and is_shared field |
|
|
| added |
patterns |
|
|
| write_audit_log: flush-not-commit (session.flush only); never-raises (bare except + warning log) |
| Folder IDOR prevention: all ownership failures return 404 not 403 |
| IntegrityError → 409 for duplicate folder name under same parent |
| WITH RECURSIVE CTE for subtree collection + OperationalError fallback for SQLite tests |
| Atomic quota decrement: CASE WHEN used_bytes > delta THEN ... ELSE 0 END (SQLite compat) |
| MinIO deletion best-effort: per-object try/except, never aborts primary operation |
| Breadcrumb: iterative Python parent-walk (not SQL recursive CTE) for SQLite compat |
| document_move_router: separate APIRouter with /api/documents prefix on folders module |
| FTS via plainto_tsquery wrapped in try/except for SQLite unit test compat |
|
|
| created |
modified |
| backend/services/audit.py |
| backend/api/folders.py |
|
| backend/api/documents.py |
| backend/main.py |
|
|
| write_audit_log uses session.flush() not session.commit() — D-14 architectural constraint: caller owns the transaction |
| Breadcrumb built by iterative Python walk (not WITH RECURSIVE) so unit tests on SQLite pass without special handling |
| document_move_router (PATCH /api/documents/{id}/folder) placed in folders.py not documents.py as a separate APIRouter with /api/documents prefix — logically a folder operation |
| FTS query (plainto_tsquery) wrapped in try/except so SQLite unit tests are not broken — silently degrades to unfiltered results on SQLite |
| Backward-compat fast path in list_documents: when no new params (sort=date, order=desc, no folder_id, no q), delegates to storage.list_metadata() for full topic-filter compat |
|
| FOLD-01 |
| FOLD-02 |
| FOLD-03 |
| FOLD-04 |
| FOLD-05 |
|
5min |
2026-05-25 |