curo1305 b8238e03ea Fix prod startup: add start.sh for backend, fix documents proxy base route
- 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>
2026-04-14 05:32:43 +02:00

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 Anthropic / Ollama / LM Studio, per-user categories, file download
  • Admin settings per app at /apps/documents/settings/admin: AI provider (cloud or local), upload limits; config stored in /config/doc_service_config.json on a shared Docker volume
  • /apps launcher hub — one card per installed app with Open + Settings links
  • 4 separate Docker containers: db, backend, 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
doc-service custom (python:3.12-slim) none backend-net 1001:1001 PDF extraction microservice
frontend custom (nginxinc/nginx-unprivileged:alpine) 80 (prod) / 5173 (dev) backend-net + frontend-net 1001:1001 React UI + nginx reverse proxy

Networks:

  • backend-net — db, backend, doc-service, and frontend reverse proxy; 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 files
  • doc_data — uploaded PDF files (mounted into doc-service at /data/documents)
  • app_config — per-service runtime config JSON files (mounted into backend 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. No backend, doc-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
S
Description
No description provided
Readme 1.4 MiB
Languages
Python 53.2%
TypeScript 44.1%
Dockerfile 1%
CSS 0.9%
Shell 0.5%
Other 0.2%