- backend/app/routers/storage_config.py: 5 admin-only endpoints proxying storage-service config + migration API (GET/PATCH/POST/DELETE) - backend/app/main.py: register storage_config router - frontend/src/api/client.ts: StorageStatus, MigrationStatus, StorageBackendConfig interfaces + 5 API functions - frontend/src/pages/StorageAdminPage.tsx: full admin UI — backend health dot, driver selector (local/S3/WebDAV), conditional credential fields, Test & Migrate button, live 2s-poll migration progress bar, Cancel - frontend/src/App.tsx: /admin/storage route (AdminRoute guard) - CLAUDE.md: storage enforcement rule, updated Docker tables (6 services, 3 volumes), §20 in merge checklist - backend/CLAUDE.md, frontend/CLAUDE.md, doc-service/CLAUDE.md, ai-service/CLAUDE.md: updated to reflect storage-service integration - tests/ALL_TESTS.md + tests/storage-service_tests.md: §20 (20 tests) - backend/STATUS.md, frontend/STATUS.md: updated with new endpoints/routes - changelog/2026-04-20_storage-service.md: full change log Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.0 KiB
Backend — Status
What it is
Central FastAPI gateway. Handles authentication, user management, admin settings, and proxies feature-service traffic. It is the only container that has host-level port exposure (8000, internal) — all browser traffic arrives via the Vite/nginx frontend proxy.
Port: 8000 (on backend-net, no direct host binding in prod).
Database: PostgreSQL 16 (postgres_data named volume).
Current functionality
Auth (/api/auth)
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/register |
Create account; password policy enforced (uppercase, special char, no "test") |
POST |
/api/auth/login |
OAuth2 password flow; returns RS256 JWT (8-hour expiry) |
JWT signing uses a 4096-bit RSA key pair (RS256). Keys are generated by scripts/generate_jwt_keys.py and stored in backend/.env (gitignored). Token stored in localStorage on the client.
Users (/api/users)
| Method | Path | Description |
|---|---|---|
GET |
/api/users/me |
Current user info |
GET |
/api/users/me/preferences |
User's dashboard preferences (app_ids list) |
PATCH |
/api/users/me/preferences |
Update pinned app IDs (max 50; validated as safe slugs) |
GET |
/api/users/me/groups |
List groups the current user belongs to (for share picker) |
Profile (/api/profile)
| Method | Path | Description |
|---|---|---|
GET |
/api/profile |
Fetch profile (separate profiles table) |
PUT |
/api/profile |
Update profile fields |
Admin (/api/admin)
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/users |
List all users (admin only) |
PATCH |
/api/admin/users/{id} |
Update user (role, active flag) |
Groups (/api/admin/groups)
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/groups |
List all groups with member count |
POST |
/api/admin/groups |
Create a new group |
GET |
/api/admin/groups/{id} |
Get group detail with member list |
PATCH |
/api/admin/groups/{id} |
Update group name / description |
DELETE |
/api/admin/groups/{id} |
Delete group (cascades memberships) |
POST |
/api/admin/groups/{id}/members/{user_id} |
Add user to group |
DELETE |
/api/admin/groups/{id}/members/{user_id} |
Remove user from group |
Services (/api/services)
| Method | Path | Description |
|---|---|---|
GET |
/api/services |
Returns health status of all registered feature services |
A background task (service_health.py) polls each service's /health endpoint every 30 s and stores the result in memory. The first check runs immediately on startup. Any authenticated user may call GET /api/services; the frontend uses it to drive app card visibility.
Settings (/api/settings)
| Method | Path | Description |
|---|---|---|
GET |
/api/settings/ai |
AI service config (masked) — superuser OR ai-service-admin member |
PATCH |
/api/settings/ai |
Update AI provider / credentials — same access |
POST |
/api/settings/ai/test |
Test AI connection — same access |
GET |
/api/settings/documents/limits |
Doc service upload limits — superuser OR doc-service-admin member |
PATCH |
/api/settings/documents/limits |
Update max PDF size — same access |
GET |
/api/settings/system-prompts |
All editable system prompts — superuser OR ai-service-admin member |
PATCH |
/api/settings/system-prompts/{id} |
Update system prompt — same access |
Settings are persisted to the config bucket of storage-service:8020 via core/config_storage.py. All config I/O is async HTTP; no filesystem volumes are used.
Access to service-specific settings endpoints is enforced by get_service_admin(service_id) in deps.py — grants access to superusers OR members of the {service_id}-admin group.
Storage config (/api/admin)
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/storage-config |
Current backend driver + health (proxied from storage-service) |
PATCH |
/api/admin/storage-config |
Reconfigure backend without migration |
POST |
/api/admin/storage-config/migrate |
Start async migration to a new backend |
GET |
/api/admin/storage-config/migrate/status |
Poll migration progress |
DELETE |
/api/admin/storage-config/migrate |
Cancel running migration |
Feature proxies
All /api/documents/* and /api/documents/categories/* requests are transparently proxied to doc-service:8001 via httpx.AsyncClient. The proxy:
- Validates the JWT (
get_current_user) - Injects
x-user-idheader (UUID fromusers.id) - Strips hop-by-hop headers +
content-length,accept-encoding,content-type - Returns
Response(notStreamingResponse) to avoid content-length/chunked conflicts
Plugin system (/api/plugins)
Generic extension/plugin infrastructure — zero feature-specific code in backend. Feature containers self-describe via GET /plugin/manifest.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/plugins |
user | List plugins accessible to current user |
GET |
/api/plugins/{id}/manifest |
user | Cached manifest for a plugin (404 if not accessible) |
GET |
/api/plugins/{id}/settings |
user | Proxy to feature GET /plugin/settings |
PATCH |
/api/plugins/{id}/settings |
user | Proxy to feature PATCH /plugin/settings |
Access is controlled by the manifest: allow_superuser for admins; required_groups for group members. check_plugin_access(plugin_id, user, db) in deps.py enforces this.
During each health poll, service_health.py also fetches GET /plugin/manifest from healthy services and caches it. New feature containers that expose /plugin/manifest automatically appear in the plugin list — no backend code changes required.
Service admin group bootstrap: On every startup, group_bootstrap.py creates a {service-id}-admin group for every registered service (idempotent). Admins add users to these groups via the Admin → Groups UI to delegate service-level administration.
Database models
| Model | Table | Notes |
|---|---|---|
User |
users |
email, hashed_password, role (user|admin), is_active, dashboard_app_ids (JSON) |
Profile |
profiles |
one-to-one with User; full_name, phone, etc. |
Group |
groups |
name (unique), description, created_at |
GroupMembership |
group_memberships |
group_id + user_id (unique pair); joined_at |
Alembic migrations in backend/alembic/versions/ — version table: alembic_version.
Architecture
Browser (port 5173 dev / 80 prod)
│
└── Vite dev proxy / nginx
│
└── /api/* → backend:8000 (FastAPI)
│
┌───────────┼────────────┬──────────────┐
/auth /settings /documents/* /services
/users (JSON │ │
/admin /storage- └── proxy → health-check loop
/profile config doc-service:8001 (30s poll)
(proxy)
│
storage-service:8020
Security notes
- JWT stored in
localStorage— XSS risk. Migration tohttpOnlycookie planned. - No refresh token — after 8h the user must log in again.
- Admin routes use
get_current_admindependency (checksrole == "admin"). - All backend routes require authentication except
/api/auth/*. backend-netis markedinternal: true— containers on it cannot reach the internet directly.
Known limitations / not implemented
- No refresh tokens — 8h hard expiry; adding refresh requires
httpOnlycookie + rotation - No
httpOnlycookie — JWT inlocalStorageis XSS-exposed - App permissions — no per-user, per-app access control. Currently all authenticated users can use all apps. Planned:
user_app_permissionstable, admin UI to grant/revoke - Groups / sharing — groups + memberships exist; app permission hooks not yet wired up
- Email verification — accounts are active immediately after registration
- Password reset — no flow implemented
Future work
- Groups system:
groups,group_membershipstables; admin CRUD; add/remove members - Generic plugin infrastructure: manifest contract,
/api/pluginsproxy router,check_plugin_access - App permissions registry:
group_app_permissionstable; AppsPage filtered by group grants - Doc sharing via group membership
- App permissions registry:
user_app_permissions (user_id, app_key); AppsPage filtered by grants httpOnlycookie migration for JWT- Refresh token flow (paired with cookie migration)
- Email verification on registration
- Password reset flow
- Rate limiting on auth endpoints