5349f21752acc0e26e9b4fabecdd8fee46d9dac8
New FastAPI microservice (port 8020) providing unified blob storage via PUT/GET/DELETE/LIST HTTP API. Local filesystem backend is the default (zero extra deps). S3-compatible and WebDAV backends are built in. Backend is switchable at runtime via POST /migrate, which copies all objects to the new backend, verifies each one, atomically switches, then cleans up the old backend. WebDAV XML parsing uses defusedxml to prevent XXE attacks. Wired into docker-compose (storage_data volume) and registered in the backend service-health poller as 'storage-service'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
destroying_sap
A fullstack SaaS web application built with FastAPI, React, and PostgreSQL.
Stack
| Layer | Tech |
|---|---|
| Backend | FastAPI (async), SQLAlchemy 2, Alembic, PostgreSQL 16 |
| Auth | JWT bearer tokens (RS256), bcrypt password hashing |
| Frontend | React 18, TypeScript, Vite, React Router v6, TanStack Query |
Current State
- User registration and login (JWT RS256 auth, 8-hour expiry)
- Protected dashboard with nav bar
/api/users/me— authenticated user info/api/profile/me— GET/PUT personal profile (position, phone, date of birth, address)- Admin-only user management at
/admin: list, add, delete, toggle active - 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 viaPOST /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.jsonand/config/doc_service_config.json /appslauncher hub — one card per installed app with Open + Settings links- 5 separate Docker containers:
db,backend,ai-service,doc-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!) - Password policy: min 8 chars, upper + lowercase, digit, special character, no common words
- Pre-commit security hook (
scripts/security_check.py) runs inside Docker on every commit
Containers
| Container | Image | Host port | Network | User (UID:GID) | Description |
|---|---|---|---|---|---|
db |
postgres:16-alpine | none | backend-net | 70:70 | PostgreSQL database |
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) |
frontend |
custom (nginxinc/nginx-unprivileged:alpine) | 80 (prod) / 5173 (dev) | backend-net + frontend-net | 1001:1001 | React UI + nginx reverse proxy |
Networks:
backend-net— all backend services; no host ports bound; outbound internet access allowed (needed for cloud AI API calls)frontend-net— frontend only; this is where the single host port (80/5173) is bound
Volumes:
postgres_data— PostgreSQL data filesdoc_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)
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.
Installation
Prerequisites
- Docker + Docker Compose
Production
git clone <repo>
cd destroying_sap
cp .env.example backend/.env
python scripts/generate_jwt_keys.py # paste output into backend/.env
docker compose up --build -d
- Frontend: http://localhost
- API docs: not directly accessible from host (backend port not exposed)
After first start, configure the AI provider at /apps/documents/settings/admin (admin login required).
Development (hot reload)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
- Frontend (Vite): http://localhost:5173
- Backend: reachable by frontend via Docker network only (not exposed to host)
Local (no Docker)
1. Start PostgreSQL
docker compose up db -d
2. Backend
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp ../.env.example .env
alembic upgrade head
uvicorn app.main:app --reload
3. doc-service
cd features/doc-service
python -m venv .venv && source .venv/bin/activate
pip install -e .
alembic upgrade head
uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
4. Frontend
cd frontend && npm install && npm run dev
Environment Variables
Copy .env.example to backend/.env and adjust:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql+asyncpg://postgres:password@localhost:5432/destroying_sap |
Async PostgreSQL URL |
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).
Development
# Backend lint + format
cd backend && ruff check . && ruff format .
# Backend tests
cd backend && pytest
# Frontend type check + lint
cd frontend && npm run typecheck && npm run lint
# New DB migration — main backend
cd backend && alembic revision --autogenerate -m "describe change"
cd backend && alembic upgrade head
# New DB migration — doc-service
cd features/doc-service && alembic revision --autogenerate -m "describe change"
cd features/doc-service && alembic upgrade head
Description
Languages
Python
53.2%
TypeScript
44.1%
Dockerfile
1%
CSS
0.9%
Shell
0.5%
Other
0.2%