feat(phase-4): complete UX redesign — FileManagerView, FolderTreeItem, test suite, and all Phase 4 fixes
Adds the unified file manager view (Windows Explorer-style), collapsible folder tree sidebar item, full vitest test suite (55 tests, 4 files), and commits all Phase 4 backend/frontend fixes that were staged but uncommitted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
---
|
||||
phase: 04-folders-sharing-quotas-document-ux
|
||||
plan: "05"
|
||||
subsystem: backend-api
|
||||
tags: [streaming-proxy, content-delivery, preferences, pdf, range-requests, doc-02, doc-01]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- 04-02 # pdf_open_mode migration 0004 adds users.pdf_open_mode column
|
||||
- 04-04 # Share model with recipient_id for access control
|
||||
provides:
|
||||
- "GET /api/documents/{id}/content — streaming proxy from MinIO"
|
||||
- "GET /api/auth/me/preferences — read pdf_open_mode"
|
||||
- "PATCH /api/auth/me/preferences — update pdf_open_mode"
|
||||
affects:
|
||||
- 04-09 # frontend uses content URL + preferences PATCH for PDF display
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "StreamingResponse with iter([bytes]) for zero-copy streaming"
|
||||
- "_parse_range() module-level helper for RFC 7233 byte range parsing"
|
||||
- "Literal['in_app', 'new_tab'] Pydantic field for allowlist enforcement (T-04-05-05)"
|
||||
- "get_regular_user dep blocks admin access to content proxy (T-04-05-01)"
|
||||
key_files:
|
||||
created: []
|
||||
modified:
|
||||
- path: backend/api/documents.py
|
||||
role: "Added _parse_range() + stream_document_content endpoint"
|
||||
- path: backend/api/auth.py
|
||||
role: "Added PreferencesUpdate model + GET/PATCH /me/preferences endpoints"
|
||||
- path: backend/db/models.py
|
||||
role: "Added pdf_open_mode column to User ORM model"
|
||||
- path: backend/tests/test_documents.py
|
||||
role: "Replaced xfail stubs with 8 real streaming proxy tests"
|
||||
- path: backend/tests/test_auth_api.py
|
||||
role: "Added 7 preferences endpoint tests"
|
||||
decisions:
|
||||
- "Use get_regular_user (not get_current_user) for content proxy: admin role blocked at dep level (T-04-05-01)"
|
||||
- "Fetch bytes via get_object() directly — presigned_get_url() forbidden in proxy handler (T-04-05-02)"
|
||||
- "Access check inline in handler body (not helper function) for test mocking simplicity"
|
||||
- "HTTP_416_RANGE_NOT_SATISFIABLE used instead of deprecated HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE"
|
||||
- "pdf_open_mode added to User ORM model (migration 0004 already added the DB column)"
|
||||
- "GET /me/preferences uses AttributeError guard for env without migration run"
|
||||
metrics:
|
||||
duration: "~15 minutes"
|
||||
completed: "2026-05-25"
|
||||
tasks_completed: 2
|
||||
tasks_total: 2
|
||||
files_modified: 5
|
||||
---
|
||||
|
||||
# Phase 04 Plan 05: Document Streaming Proxy + PDF Preferences Summary
|
||||
|
||||
**One-liner:** MinIO streaming proxy for document content with Range header support (DOC-02) and pdf_open_mode user preference endpoint (DOC-01) backed by Literal validation.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Task 1: GET /api/documents/{id}/content (DOC-02)
|
||||
|
||||
Added a streaming proxy endpoint to `backend/api/documents.py`:
|
||||
|
||||
- **`_parse_range(range_header, file_size)`** module-level helper that parses RFC 7233 `bytes=X-Y` syntax, handles open-ended ranges (`bytes=X-` and `bytes=-Y`), and raises HTTP 416 on any invalid or out-of-bounds range
|
||||
- **`stream_document_content`** endpoint at `GET /api/documents/{id}/content`:
|
||||
- Uses `get_regular_user` dep — admin role → 403 (T-04-05-01, CRITICAL)
|
||||
- Parses doc_id as UUID → 404 on ValueError
|
||||
- Loads Document via session.get → 404 if None
|
||||
- Access: `doc.user_id == current_user.id` OR `Share.recipient_id == current_user.id`; neither → 404 (T-04-05-04)
|
||||
- Fetches bytes via `get_storage_backend().get_object(doc.object_key)` — no presigned URL (T-04-05-02)
|
||||
- Returns `StreamingResponse` with `content-type`, `content-disposition: inline`, `accept-ranges: bytes`, `content-length`
|
||||
- Range header present → 206 with `content-range: bytes {start}-{end}/{total}` (T-04-05-03)
|
||||
- No Range → 200
|
||||
|
||||
Also added `pdf_open_mode` column to `User` ORM model (migration 0004 already added the DB column).
|
||||
|
||||
### Task 2: Preferences Endpoints (DOC-01)
|
||||
|
||||
Added to `backend/api/auth.py`:
|
||||
|
||||
- **`PreferencesUpdate`** Pydantic model with `pdf_open_mode: Literal["in_app", "new_tab"]`
|
||||
- **`GET /api/auth/me/preferences`** — returns `{"pdf_open_mode": ...}` using `get_current_user` (both roles)
|
||||
- **`PATCH /api/auth/me/preferences`** — validates via Literal, updates `current_user.pdf_open_mode`, commits, returns updated value
|
||||
|
||||
## Commits
|
||||
|
||||
| Hash | Message |
|
||||
|------|---------|
|
||||
| `8e6cb6e` | test(phase-4-05): add failing tests for document streaming proxy (DOC-02) — RED phase |
|
||||
| `f868a4e` | feat(phase-4-05): document streaming proxy GET /api/documents/{id}/content (DOC-02) — GREEN phase |
|
||||
| `2a0df32` | feat(phase-4-05): PATCH /api/auth/me/preferences for pdf_open_mode (DOC-01) |
|
||||
|
||||
## Test Results
|
||||
|
||||
**Before this plan:** 85 passed, 10 xfailed (document tests only had xfail stubs)
|
||||
**After this plan:** 137 passed, 35 xfailed — all new tests green
|
||||
|
||||
| Test | Result |
|
||||
|------|--------|
|
||||
| test_content_stream_200 | PASSED |
|
||||
| test_content_stream_206_range | PASSED |
|
||||
| test_content_stream_admin_403 | PASSED |
|
||||
| test_content_stream_no_presigned_url | PASSED |
|
||||
| test_content_stream_share_recipient_200 | PASSED |
|
||||
| test_content_stream_not_found | PASSED |
|
||||
| test_content_stream_invalid_id | PASSED |
|
||||
| test_parse_range_416 | PASSED |
|
||||
| test_get_preferences_default | PASSED |
|
||||
| test_patch_preferences_in_app | PASSED |
|
||||
| test_patch_preferences_new_tab | PASSED |
|
||||
| test_patch_preferences_invalid_value | PASSED |
|
||||
| test_patch_preferences_persists | PASSED |
|
||||
| test_preferences_requires_auth | PASSED |
|
||||
| test_patch_preferences_requires_auth | PASSED |
|
||||
|
||||
Pre-existing failure: `test_extract_docx` (missing `python-docx` module in local env — not introduced by this plan).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Deprecated HTTP_416 status constant**
|
||||
- **Found during:** Task 1 implementation
|
||||
- **Issue:** `HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE` is deprecated in the installed FastAPI version; `HTTP_416_RANGE_NOT_SATISFIABLE` is the current constant
|
||||
- **Fix:** Used `HTTP_416_RANGE_NOT_SATISFIABLE` throughout
|
||||
- **Files modified:** `backend/api/documents.py`
|
||||
- **Commit:** f868a4e
|
||||
|
||||
**2. [Rule 2 - Missing functionality] pdf_open_mode absent from User ORM model**
|
||||
- **Found during:** Task 2 — plan noted "migration 0004 adds column" but the ORM `User` class in `models.py` did not declare `pdf_open_mode`
|
||||
- **Fix:** Added `pdf_open_mode: Mapped[str]` with `server_default="in_app"` to the User class
|
||||
- **Files modified:** `backend/db/models.py`
|
||||
- **Commit:** f868a4e
|
||||
|
||||
## Security Invariants Verified
|
||||
|
||||
| Threat ID | Status |
|
||||
|-----------|--------|
|
||||
| T-04-05-01: Admin blocked at content proxy | VERIFIED — `get_regular_user` dep; `test_content_stream_admin_403` passes |
|
||||
| T-04-05-02: No presigned URL in proxy | VERIFIED — `presigned_mock.assert_not_called()` in `test_content_stream_no_presigned_url` |
|
||||
| T-04-05-03: Range validation bounds | VERIFIED — `test_parse_range_416` confirms 416 on out-of-bounds |
|
||||
| T-04-05-04: Non-recipient 404 | VERIFIED — unshared doc returns 404; share recipient gets 200 |
|
||||
| T-04-05-05: pdf_open_mode Literal | VERIFIED — `test_patch_preferences_invalid_value` confirms 422 on invalid value |
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None — all behavior is fully implemented and wired.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None — no new network endpoints, auth paths, or file access patterns beyond what was planned in the threat model.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- [x] `backend/api/documents.py` — stream_document_content endpoint exists
|
||||
- [x] `backend/api/auth.py` — /me/preferences routes registered (GET + PATCH)
|
||||
- [x] `backend/db/models.py` — pdf_open_mode column on User model
|
||||
- [x] `backend/tests/test_documents.py` — 8 streaming proxy tests pass
|
||||
- [x] `backend/tests/test_auth_api.py` — 7 preferences tests pass
|
||||
- [x] Commits: 8e6cb6e (RED), f868a4e (GREEN Task 1), 2a0df32 (Task 2)
|
||||
- [x] Full suite: 137 passed, 1 pre-existing failure (test_extract_docx — missing docx module)
|
||||
Reference in New Issue
Block a user