- New backend: Group + GroupMembership models, schemas, CRUD router at
/api/admin/groups (list, create, get detail, update, delete, add/remove members)
- New Alembic migration: groups and group_memberships tables
- Frontend: Admin sidebar item is now an expandable accordion with
Users and Groups sub-items; AdminPage redirects to /admin/users;
new AdminUsersPage and AdminGroupsPage with inline member management panel
- API client: 7 new group functions + TypeScript types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap check_all() call inside the loop with try/except so a transient error
cannot exit the while-True and freeze all health statuses. Add transition
logging (HEALTHY / UNHEALTHY) so docker logs show when a service changes
state. Also add refetchIntervalInBackground on the frontend query so the
poll continues even when the browser tab is not focused.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend polls each registered service's /health endpoint every 30 s via a
background asyncio task. GET /api/services exposes the live status snapshot.
The Apps page now renders from this endpoint — showing "Unavailable" (dimmed,
non-clickable) when a service is registered but its container is unreachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each service prompt card now shows:
- A collapsible how-to panel with placeholder docs, required JSON
response keys, and usage notes
- A "Reset to Default" button (with confirmation step) that restores
the built-in prompt without saving, letting the admin review first
- A "Using the built-in default prompt" indicator when unchanged
Backend includes default_system / default_user_template in the
system-prompts API response so the frontend never duplicates defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each feature service owns its system prompt in its config JSON on the
shared volume. The AI Settings page now has General and System Prompts
tabs — admins can view and edit any service's prompts at runtime with
changes taking effect within 30 s (config cache TTL).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
StreamingResponse + forwarded content-length header was causing a
content-length mismatch (chunked vs explicit length), which made axios
reject the response even though doc-service had already saved the file.
Switch to Response, strip content-length/content-type from forwarded
response headers (FastAPI recalculates them correctly), and strip
accept-encoding from forwarded requests to prevent decompression
mismatches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All feature containers now POST messages to ai-service (port 8010) instead
of calling AI providers directly. ai-service routes to LM Studio, Ollama,
or Anthropic based on /config/ai_service_config.json. doc-service AI
providers removed; replaced by httpx ai_client.py. Backend settings
restructured to /api/settings/ai. Frontend gets dedicated AIAdminSettingsPage
and AI Service card in AppsPage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pytest suite for doc-service: 20+ tests covering category CRUD,
document upload/get/delete/patch, ownership isolation, category
assignment, AI processing (mock), and live PDF tests (auto-skipped
when tests/pdfs/ is empty)
- Minimal in-memory PDF builder in conftest so tests run without any
fixture files; real PDFs can be dropped into tests/pdfs/ to activate
live extraction tests
- AI prompt updated to return suggested_categories (2–5 short names)
- Frontend: SuggestionChip component in DocumentRow shows AI-suggested
categories after processing; "Assign" links to an existing category,
"Create & Assign" creates it first, ✕ dismisses locally
- Default AI provider changed to LM Studio at
http://host.docker.internal:1234/v1 (host.docker.internal resolves
to the macOS host from inside Docker Desktop)
- tests/pdfs/ directory tracked via .gitkeep; *.pdf excluded by .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backend/Dockerfile: run migrations via start.sh before uvicorn instead
of launching uvicorn directly (prod was skipping Alembic)
- backend/scripts/start.sh: alembic upgrade head + uvicorn exec
- documents_proxy.py: add explicit "" route so GET /api/documents (no
trailing slash) returns 200 instead of 307 redirect
- README.md: update Containers table, volumes section, and Current State
to reflect the new 4-container architecture with doc-service
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New `features/doc-service` FastAPI microservice: PDF upload, async
text extraction (pdfplumber), AI classification via Anthropic/Ollama/
LM Studio, per-user categories, file download
- Alembic migration isolated with `alembic_version_doc_service` table
- Main backend: httpx proxy routers for /api/documents/* and
/api/documents/categories/*, admin settings API at /api/settings/*
- Runtime config in /config/doc_service_config.json (shared Docker
volume); api_key masking on reads; atomic write with os.replace()
- Frontend: DocumentsPage, DocumentAdminSettingsPage, updated AppsPage
launcher hub, simplified Nav (removed Settings link), new routes
- docker-compose: doc-service service, doc_data + app_config volumes,
removed internal:true from backend-net for outbound AI API calls
- Fix pre-commit hook: probe Docker socket path so git subprocess picks
up Docker Desktop on macOS
- Fix security_check.py: use sys.executable for bandit so venv python
is used instead of system python
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- deps.py: get_current_admin returns 404 Not Found for non-superusers instead
of 403 Forbidden — hides endpoint existence from unauthorised callers
- App.tsx: AdminRoute redirects non-admins to /login instead of /, making
the route indistinguishable from a non-existent page
Layer 3 (network-level IP restriction via Traefik) tracked in TODO.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- schemas/user.py: is_admin (validation_alias=is_superuser) on UserOut and
UserAdminOut; UserAdminCreate extends UserCreate with is_admin flag
- deps.py: get_current_admin dependency — 403 for non-superusers
- routers/admin.py: GET/POST /api/admin/users, DELETE and PATCH /active per
user; self-delete and self-deactivate blocked
- main.py: register /api/admin router
- scripts/seed.py: seed test user with is_superuser=True; promotes existing
user if already created without the flag
Frontend:
- api/client.ts: UserData type with is_admin, admin API functions
- components/Nav.tsx: Admin link visible only when user.is_admin is true
- pages/AdminPage.tsx: user table with add-user form, delete, toggle active
- App.tsx: AdminRoute guard (403-redirects non-admins to /); /admin route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>