diff --git a/CLAUDE.md b/CLAUDE.md index 2501c82..4a8597c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -292,8 +292,9 @@ Each entry must include: 4. Add router in `backend/app/routers/`, mount it in `main.py` 5. Add API function(s) to `frontend/src/api/client.ts` 6. Add page component in `frontend/src/pages/`, register route in `App.tsx` -7. Update `STATUS.md` for affected services -8. Add changelog entry +7. If the resource involves file or blob data: store it via `PUT /objects/{bucket}/{key}` on `storage-service:8020`. Never write to the local filesystem. See `features/storage-service/CLAUDE.md` for the API. +8. Update `STATUS.md` for affected services +9. Add changelog entry --- diff --git a/README.md b/README.md index 0ee0d64..2d98fab 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,11 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL. - All input sanitized before reaching the DB (null-byte rejection, length caps, format validation) - **PDF Documents app** (`/apps/documents`): upload PDFs, async text extraction (pdfplumber), AI classification via ai-service, per-user categories, file download - **AI Service** (`ai-service:8010`): shared AI intermediary container; routes prompts to Anthropic / Ollama / LM Studio; stateless; all feature containers talk to it via `POST /chat` -- Admin settings: `/apps/ai/settings/admin` (provider, credentials, test connection); `/apps/documents/settings/admin` (upload limits only) -- Config stored in shared Docker volume: `/config/ai_service_config.json` and `/config/doc_service_config.json` +- **Storage Service** (`storage-service:8020`): unified file/blob storage with pluggable backends (local filesystem default; S3-compatible and WebDAV built in); backend switchable via admin UI with zero-data-loss migration +- Admin settings: AI provider, doc upload limits, storage backend switching with live migration progress +- Config stored in storage-service (`config` bucket); PDFs stored in storage-service (`documents` bucket) — no shared filesystem volumes - `/apps` launcher hub — one card per installed app with Open + Settings links -- 5 separate Docker containers: `db`, `backend`, `ai-service`, `doc-service`, `frontend` +- 6 separate Docker containers: `db`, `backend`, `ai-service`, `doc-service`, `storage-service`, `frontend` - All containers run as non-root users (UID 1001 for app containers, UID 70 for db) - Network-isolated: only the frontend exposes a host port (80/5173); all backend services are unreachable from outside Docker - Dev environment seeds a test user automatically on startup (`test@example.com` / `Test123!`) @@ -38,6 +39,7 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL. | `backend` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | FastAPI management API + proxy to doc-service | | `ai-service` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | Shared AI intermediary (routes to LM Studio / Ollama / Anthropic) | | `doc-service` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | PDF extraction microservice (calls ai-service) | +| `storage-service` | custom (python:3.12-slim) | none | backend-net | 1001:1001 | Unified file/blob storage (local / S3-compatible / WebDAV) | | `frontend` | custom (nginxinc/nginx-unprivileged:alpine) | 80 (prod) / 5173 (dev) | backend-net + frontend-net | 1001:1001 | React UI + nginx reverse proxy | **Networks:** @@ -46,8 +48,8 @@ A fullstack SaaS web application built with FastAPI, React, and PostgreSQL. **Volumes:** - `postgres_data` — PostgreSQL data files -- `doc_data` — uploaded PDF files (mounted into doc-service at `/data/documents`) -- `app_config` — per-service runtime config JSON files (mounted into backend, ai-service, and doc-service at `/config`) +- `storage_data` — all file/blob storage: uploaded PDFs (`documents/` bucket) and service config JSON files (`config/` bucket); mounted into storage-service at `/data/storage` +- `watch_data` — file watcher input directory; mounted into doc-service at `/data/watch` The frontend nginx proxies `/api/*` to `backend:8000` via `backend-net`. The backend proxies `/api/documents/*` and `/api/documents/categories/*` to `doc-service:8001`. The backend test-connection endpoint proxies to `ai-service:8010`. No backend service or database port is ever exposed to the host. @@ -126,10 +128,8 @@ Copy `.env.example` to `backend/.env` and adjust: | `JWT_PRIVATE_KEY` | — | RS256 private key PEM (generate with `scripts/generate_jwt_keys.py`) | | `JWT_PUBLIC_KEY` | — | RS256 public key PEM (generate with `scripts/generate_jwt_keys.py`) | | `CORS_ORIGINS` | `["http://localhost:5173"]` | Allowed frontend origins | -| `APP_CONFIG_DIR` | `/config` | Directory for per-service runtime config JSON files | -| `DOC_SERVICE_URL` | `http://doc-service:8001` | Internal URL of the doc-service (set by docker-compose) | - -`doc-service` reads `DATABASE_URL`, `DATA_DIR`, and `CONFIG_PATH` from its own environment (set in `docker-compose.yml`). +| `DOC_SERVICE_URL` | `http://doc-service:8001` | Internal URL of doc-service (set by docker-compose) | +| `STORAGE_SERVICE_URL` | `http://storage-service:8020` | Internal URL of storage-service (set by docker-compose) | ## Development diff --git a/features/ai-service/Dockerfile b/features/ai-service/Dockerfile index 485fb53..d5d97fc 100644 --- a/features/ai-service/Dockerfile +++ b/features/ai-service/Dockerfile @@ -15,8 +15,7 @@ FROM python:3.12-slim RUN groupadd --gid 1001 appuser && \ useradd --uid 1001 --gid 1001 --no-create-home --shell /bin/sh appuser -# Pre-create the config directory with correct ownership -RUN mkdir -p /config && chown -R appuser:appuser /config +# No filesystem directories needed — all config goes through storage-service. WORKDIR /app diff --git a/features/doc-service/Dockerfile b/features/doc-service/Dockerfile index fdd0e9c..c8cca06 100644 --- a/features/doc-service/Dockerfile +++ b/features/doc-service/Dockerfile @@ -15,9 +15,9 @@ FROM python:3.12-slim RUN groupadd --gid 1001 appuser && \ useradd --uid 1001 --gid 1001 --no-create-home --shell /bin/sh appuser -# Pre-create data and config dirs with correct ownership. -# Named volumes mounted over these paths will inherit ownership on first creation. -RUN mkdir -p /data/documents /data/watch /config && chown -R appuser:appuser /data /config +# Pre-create watch dir with correct ownership. +# /data/documents and /config are no longer used — all file/config storage goes through storage-service. +RUN mkdir -p /data/watch && chown -R appuser:appuser /data WORKDIR /app