List, preview, download, and delete files stored in Backblaze B2.
- UI:
/filespage, file browser component - API:
GET /files,GET /files/{key},GET /files/{key}/download,DELETE /files/{key}
apps/web/src/components/files/file-browser.tsx— tree view with expand/collapse folders, type-specific icons, hover action menusapps/web/src/components/files/file-preview.tsx— dialog modal for file previewapps/web/src/components/files/file-metadata-panel.tsx— structured metadata displayapps/web/src/lib/file-tree.ts—buildFileTree()converts flat S3 keys to folder/file hierarchyapps/web/src/lib/api-client.ts—getFiles(),getDownloadUrl(),deleteFile()services/api/app/runtime/files.py— HTTP handlers for list, get, download, deleteservices/api/app/service/files.py— business logic, key validationservices/api/app/repo/b2_client.py—list_files(),get_file_metadata(),get_presigned_url(),delete_file()
- File route handlers:
services/api/app/runtime/files.py - File tree builder:
apps/web/src/lib/file-tree.ts - B2 data access pattern:
services/api/app/repo/b2_client.py
- prefix: string (optional filter for file listing)
- limit: int (max files to return, 1-1000, default 100)
- key: string (file key for get/download/delete — must start with allowed prefix, no traversal)
GET /files→FileMetadata[](sorted most recent first)GET /files/{key}→FileMetadataGET /files/{key}/download→{ url: string }(presigned URL, attachment disposition, 10-min expiry)DELETE /files/{key}→{ deleted: true, key: string }- Side effects: DELETE removes file from B2
- Page loads → fetches file list from
GET /files(sorted most recent first) - Files organized into tree view — folders expand/collapse, files shown with type-specific icons
- Top-level folders auto-expand on load
- User hovers file row → action buttons appear (preview / download / delete)
- Preview: opens dialog with image/PDF preview + metadata panel
- Download: fetches presigned URL (attachment disposition, 10-min expiry), browser downloads file
- Delete: calls
DELETE /files/{key}, removes row from tree, shows toast - All key-based API calls validated against allowed prefixes and path traversal patterns
- File not found (deleted externally) → API returns 404
- Invalid file key (traversal attempt, wrong prefix) → API returns 400
- B2 unreachable → API error, toast notification
- Empty bucket → "No files found" message with upload prompt
- Delete failure → API returns 500, toast error
- Empty: centered message with upload prompt
- Loading: skeleton rows
- Error: toast notification
- Loaded: tree view with expand/collapse folders and hover action menus
- Test files:
services/api/tests/(no dedicated file browser tests yet) - Required cases: list files, empty list, file not found, presigned URL generation, delete success, delete failure
- Quick verify command:
pnpm test:api - Full verify command:
pnpm lint && pnpm lint:api && pnpm test:api && pnpm check:structure - Pass criteria: all pytest tests green, no ruff violations