# 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, bcrypt password hashing | | Frontend | React 18, TypeScript, Vite, React Router v6, TanStack Query | ## Current State - User registration and login (JWT auth) - Protected dashboard with nav bar (Dashboard | Profile | Logout) - `/api/users/me` — authenticated user info - `/api/profile/me` — GET/PUT personal profile (position, phone, date of birth, address) - Profile data stored in a dedicated `profiles` table; auto-created on first access - Admin role flag (`is_superuser`) stored in `users` table; exposed as `is_admin` in API (false for regular users, true for admins) - Admin-only user management at `/admin`: list all users, add users, delete users, toggle active status - All input sanitized before reaching the DB (null-byte rejection, length caps, format validation) - 3 separate Docker containers: `db` (PostgreSQL), `backend` (FastAPI), `frontend` (nginx) - All containers run as non-root users (UID 1001 for backend and frontend, UID 70 for db) - 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 | Port | User (UID:GID) | Description | |---|---|---|---|---| | `db` | postgres:16-alpine | 5432 | 70:70 (postgres) | PostgreSQL database | | `backend` | custom (python:3.12-slim) | 8000 | 1001:1001 (appuser) | FastAPI management API | | `frontend` | custom (nginxinc/nginx-unprivileged:alpine) | 80 | 1001:1001 (appuser) | React UI served by nginx (internal port 8080) | The frontend nginx container proxies `/api/*` to the backend container internally — no CORS headers needed in production. ## Installation ### Prerequisites - Docker + Docker Compose ### Production ```bash git clone cd destroying_sap cp .env.example backend/.env # edit SECRET_KEY at minimum docker compose up --build -d ``` - Frontend: http://localhost - API docs: http://localhost:8000/docs ### Development (hot reload) ```bash docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build ``` - Frontend (Vite): http://localhost:5173 - Backend (uvicorn --reload): http://localhost:8000 ### Local (no Docker) **1. Start PostgreSQL** ```bash docker compose up db -d ``` **2. Backend** ```bash cd backend python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -e ".[dev]" cp ../.env.example .env alembic upgrade head uvicorn app.main:app --reload ``` **3. Frontend** ```bash 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 | | `SECRET_KEY` | `change-me-in-production` | JWT signing key | | `CORS_ORIGINS` | `["http://localhost:5173"]` | Allowed frontend origins | ## Development ```bash # 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 (after changing models) cd backend && alembic revision --autogenerate -m "describe change" cd backend && alembic upgrade head ```