vlad 86cc3fa541 Update admin theme/layout and refresh README details.
Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
2026-07-14 17:12:28 +03:00

Compton Platform

Monorepo-lite project that follows the docs/TZ.md specification for the Compton platform.

Stack

  • Frontend: React 19, TypeScript, Vite, React Router, TanStack Query, Zustand, RHF + Zod, Ant Design (admin panel)
  • Backend: FastAPI, SQLAlchemy 2, Alembic, Pydantic v2
  • Data/Infra: PostgreSQL, Redis, MinIO (S3-compatible), Docker Compose
  • Quality: Vitest, Testing Library, Playwright, pytest, coverage gates in CI

Repository Layout

  • apps/web — frontend SPA
  • apps/api — backend API (app/core/crypto.py — unified crypto; app/core/install_secrets.py — bootstrap/lock)
  • apps/api/data/secrets/ — per-install secrets (install.env, gitignored)
  • apps/api/data/compton_settings.json — runtime panel settings (superuser-editable via API)
  • packages/shared-types — generated API types contract target
  • packages/eslint-config — shared eslint config package
  • infra — docker/nginx/ci helper files
  • docker-compose.dev-ports.yml — optional override to expose DB/Redis/MinIO on host

Quick Start

Full stack in Docker (API + DB + frontend — no local pnpm install required):

# 1. Bootstrap install secrets — REQUIRED before the first docker compose up
python apps/api/scripts/bootstrap_install.py

# 2. Build and start everything
docker compose --profile docker-web up -d --build

# 3. Verify (Windows PowerShell: use curl.exe, not curl — it is an alias for Invoke-WebRequest)
curl.exe http://localhost:8000/api/v1/health

Open http://localhost:5173 — static landing (index.html). SPA routes (/login, /admin, /profile, …) are served via app.html fallback in Vite dev/preview.

The web container runs Vite with hot-reload; dependencies are installed inside the container automatically.

Log in as admin@compton.example (password Admin1234 by default) and open /admin. See Seed data for all demo accounts.

Optional — copy env files if you also run API or frontend locally (hybrid mode):

cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env

Important: Run bootstrap before the first docker compose up. If Postgres was started without install.env, the API will fail with password authentication failed for user "compton_app". Fix: docker compose --profile docker-web down -v, then bootstrap + up again. See Troubleshooting.

Hybrid setup (Docker API + local frontend):

cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env
pnpm install
python apps/api/scripts/bootstrap_install.py
docker compose up -d
pnpm --filter web dev

Local Setup

Prerequisites

  • Docker + Docker Compose — enough for the full-stack Docker workflow
  • Node.js 22+ and pnpm 9+ — only if you run the frontend locally (pnpm --filter web dev)
  • Python 3.12 — bootstrap script, local API, or tests outside Docker

Start services

Mode Command What runs
Full Docker (recommended) docker compose --profile docker-web up -d --build API, Postgres, Redis, MinIO, Vite on :5173
API + infra only docker compose up -d API, Postgres, Redis, MinIO — frontend locally
Local API + local frontend see Run applications everything on host
Service URL / Port Notes
API http://localhost:8000 migrations + seed on startup
Web (dev) http://localhost:5173 Docker --profile docker-web or pnpm --filter web dev
Postgres internal docker network host access via docker-compose.dev-ports.yml
Redis internal docker network host access via docker-compose.dev-ports.yml
MinIO internal docker network host access via docker-compose.dev-ports.yml

Do not run Docker web and pnpm --filter web dev at the same time — both bind port 5173.

The Docker web container bind-mounts source for live-reload on Windows/macOS (CHOKIDAR_USEPOLLING=true); node_modules stay isolated inside the container.

Install secrets

Each project copy gets unique runtime secrets generated once and locked forever (prevents accidental rotation and DB credential mismatch).

File Purpose
apps/api/data/secrets/install.env PostgreSQL, JWT, S3/MinIO credentials (gitignored)
apps/api/data/secrets/install.meta.json Install ID and lock timestamp (gitignored)

Bootstrap (required before first docker compose up):

python apps/api/scripts/bootstrap_install.py

Generated keys: POSTGRES_USER, POSTGRES_PASSWORD, DATABASE_URL, JWT_ACCESS_SECRET, JWT_REFRESH_PEPPER, S3_SECRET_KEY, MINIO_ROOT_PASSWORD.

Docker Compose passes install.env directly into the api, postgres, and minio services — no root .env or --env-file flag needed.

  • Re-run is safe: existing locked secrets are never overwritten.
  • API entrypoint also calls ensure_install_secrets() on startup (adopts env vars when migrating from an older setup).
  • Do not rotate POSTGRES_PASSWORD / JWT secrets after first bootstrap without a coordinated DB migration — see docs/secrets-recovery.md.

Host access to DB/Redis/MinIO (DBeaver, pgAdmin, MinIO console):

docker compose -f docker-compose.yml -f docker-compose.dev-ports.yml up -d
Exposed port Service
5432 PostgreSQL
6379 Redis
9000 / 9001 MinIO API / console

Reveal credentials: Admin → Security → Install Secrets (superuser only, audited). Supports database_password, jwt_access_secret, jwt_refresh_pepper, s3_secret_key.

Configure environment

For hybrid or fully local dev, copy example env files:

cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env

Key variables:

Variable App Purpose
DATABASE_URL api PostgreSQL connection string
APP_ENV api development / production startup guards
JWT_ACCESS_SECRET, JWT_REFRESH_PEPPER api Token signing (32+ bytes)
VITE_API_URL web Local dev: http://localhost:8000. Docker web: http://api:8000
VITE_USE_API_PROXY web true — proxy /api through Vite (default in dev)
EMAIL_DELIVERY_MODE api memory (Docker dev) or smtp (real mail)
STORAGE_MODE api s3 (MinIO) or memory (tests)
S3_* api MinIO/S3 credentials and bucket
CORS_ORIGINS api Must include http://localhost:5173
ENABLE_TEST_ROUTES api true only for E2E (email token helper)
TRUSTED_PROXY_IPS api Which proxy IPs may set X-Forwarded-For
ADMIN_INITIAL_PASSWORD api Seed password for superuser admin
DEMO_USER_PASSWORD api Seed password for regular demo user
DEMO_OPS_PASSWORD api Seed password for ops admin (no superuser)
ENABLE_RATE_LIMIT api Rate limiting (required true in production)
COOKIE_SECURE api HttpOnly refresh cookie Secure flag
COMPTON_SETTINGS_PATH api Path to runtime settings JSON (default data/compton_settings.json)

In dev the frontend proxies API requests through Vite (/api → backend). Locally the target is localhost:8000; in Docker Compose it is the api service.

Runtime settings (compton_settings.json)

Non-secret runtime toggles live in apps/api/data/compton_settings.json and are applied on API startup. Superusers can read/update them via GET/PATCH /admin/settings.

Env vars with the same keys (e.g. ENABLE_RATE_LIMIT, CORS_ORIGINS) act as locks — when set, the corresponding JSON field cannot be changed from the admin panel.

Typical fields: rate limit, API docs, secure cookie, JWT TTL, CORS origins, SMTP host/port, avatar limits, audit retention.

Install dependencies (hybrid / local dev only)

Skip if you use docker compose --profile docker-web — dependencies are installed inside the web container.

pnpm install
pip install -r apps/api/requirements-dev.txt

Run applications

Option A — Full Docker stack:

python apps/api/scripts/bootstrap_install.py   # first run only
docker compose --profile docker-web up -d --build

Option B — Docker API + local frontend:

python apps/api/scripts/bootstrap_install.py   # first run only
docker compose up -d
pnpm --filter web dev

Option C — Local API + local frontend:

cd apps/api
alembic upgrade head
uvicorn app.main:app --reload --app-dir .

In another terminal:

pnpm --filter web dev

Open http://localhost:5173.

Seed data

After migrations the database is seeded on every API startup with demo users and CMS pages. Passwords come from env vars (defaults in .env.example):

Email Env variable Default (dev) Role Superuser Access
admin@compton.example ADMIN_INITIAL_PASSWORD Admin1234 admin yes Full admin + Security/Diagnostics/Install Secrets
ops@compton.example DEMO_OPS_PASSWORD OpsAdmin1234 admin no Users, Content, Activity (no Security/Diagnostics)
user@compton.example DEMO_USER_PASSWORD User1234 user no Profile only

Content pages: about, privacy, terms (published).

Change demo passwords via env before first seed in production. Admin-created users must pass the shared password policy (length, complexity, denylist).

Frontend Routes

The project uses a dual-entry frontend:

Entry Served at Purpose
index.html / Public marketing landing (static HTML/CSS/JS in main/)
app.html /login, /register, /profile, /admin, /pages/:slug, … React SPA (auth, profile, admin, CMS pages)

Vite middleware rewrites SPA paths to app.html on dev/preview (vite.main-static.ts).

Path Page Access
/ Landing (index.html) public
/login Login guest
/register Registration guest
/verify Email verification public
/forgot-password Password reset request guest
/reset-password Set new password public (with token)
/pages/:slug CMS page public (published only)
/profile Profile CRUD + avatar authenticated
/admin Admin panel role admin

Auth model: one login flow for everyone. The is_superuser flag on admin accounts controls access to critical panel sections (Security, Diagnostics, Install Secrets reveal, runtime settings). Regular admins see Users, Content, and Activity only.

  • Access JWT — in memory only (Zustand), not in localStorage / sessionStorage.
  • Refresh token — HttpOnly cookie (Path=/api/v1/auth, SameSite=Lax), rotated on each refresh.
  • Session restore on reloadAuthBootstrap calls a deduplicated bootstrapSessionRefresh() when a session hint exists in sessionStorage or the current path is protected (/admin, /profile). Guards wait for bootstrapped before redirecting.
  • After login — admins go to /admin, regular users to /profile.

Admin users can return to the public landing via the «На сайт» topbar link (full navigation to /, not client-side React routing).

API Overview

Base URL: http://localhost:8000/api/v1

Area Endpoints
Health GET /health
Auth POST /auth/register, /login, /logout, /refresh, /verify-email, /forgot-password, /reset-password
Profile GET/PATCH /users/me, POST /users/me/password, POST /users/me/avatar
Content GET /content/pages, GET /content/pages/{slug}, admin: POST/PATCH/DELETE /content/pages, GET /content/pages/manage/all
Admin GET/PATCH /admin/users, POST /admin/users, PATCH /admin/users/{id}/password, GET /admin/stats, GET/PATCH /admin/settings, GET /admin/diagnostics/report, GET /admin/activity-feed, POST /admin/ui-activity, GET /admin/server-log, GET /admin/secrets, POST /admin/secrets/reveal
Media GET /media/files/{path}

OpenAPI docs (when ENABLE_DOCS=true): http://localhost:8000/api/v1/docs

Database Migrations

cd apps/api
alembic upgrade head    # apply schema
alembic downgrade -1    # rollback one revision

Tables: users, user_profiles, refresh_tokens, password_reset_tokens, email_verification_tokens, content_pages.

Security constraints (PostgreSQL, migration 20260714_0004):

  • CHECK on users.role, users.status, content_pages.status
  • Superuser rule: is_superuser=true only when role=admin
  • Indexes on expires_at for token tables

Expired tokens are cleaned up on API startup (cleanup_expired_tokens, 30-day retention).

Auth Email (SMTP)

  • Verify/reset use one-time opaque tokens (SHA-256 hash + pepper in DB, TTL 1 hour); raw token omitted from production email bodies
  • Env: SMTP_HOST, SMTP_PORT, SMTP_FROM, FRONTEND_URL, EMAIL_DELIVERY_MODE
  • Docker dev: EMAIL_DELIVERY_MODE=memory (no real SMTP required)
  • Local SMTP: Mailpit/Mailhog on localhost:1025 (SMTP_HOST=localhost, SMTP_PORT=1025)
  • Tests: EMAIL_DELIVERY_MODE=memory (in-memory outbox)

Profile & Avatar (MinIO)

  • GET/PATCH /api/v1/users/me, POST /api/v1/users/me/password, POST /api/v1/users/me/avatar
  • Avatar: jpeg/png/webp, max 2 MB, re-encoded via Pillow (SVG rejected)
  • Storage env: STORAGE_MODE (s3 or memory), S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET
  • Local dev: MinIO on localhost:9000 (console 9001), bucket compton (created on API startup)
  • Tests: STORAGE_MODE=memory (in-memory file store, no MinIO)

Admin Panel

  • Route: /admin (AdminGuard, role admin)
  • UI: WESP-style Ant Design shell with fixed left sidebar, content topbar, and a dedicated admin theme system
  • Topbar actions:
    • Light/Dark — toggles isolated admin theme (preference persisted in localStorage)
    • «На сайт» — opens the static landing at / (index.html) via full navigation
    • Logout — revokes refresh cookie and redirects to /login
  • Reload UX: no blocking «Loading…» screens; admin routes preload the current admin background (#f6f6f4 in Light, #1f2229 in Dark) to avoid flash
  • Sections:
    • Users — list/create/patch role, status, password
    • Content — CMS CRUD
    • Security (superuser only) — runtime toggles + Install Secrets reveal
    • Diagnostics (superuser only) — security health checks (JWT, CORS, DB credentials, SSL mode, token table size, install secrets lock)
    • Activity — admin audit feed + server log tail
  • Business rules: last admin protected, no self-demotion, no self-block; critical endpoints require is_superuser

Auth UI (zootech)

Auth pages (/login, /register, /forgot-password, /reset-password, /verify) use a WESP-inspired zootech card layout with organic theme tokens (#48816d, centered card, icon inputs). AppHeader is hidden on auth routes and in /admin.

Tests

# Frontend unit/component (coverage ≥ 85%)
pnpm --filter web test:ci

# Same, inside Docker web container (when pnpm is not installed on host)
docker compose --profile docker-web exec web sh -c "cd apps/web && pnpm test:ci"

# Backend unit/integration (coverage ≥ 90%)
cd apps/api && python -m pytest --cov=app --cov-fail-under=90

# E2E regression (Playwright §15.7, 16 critical scenarios)
pnpm --filter web e2e

E2E (Playwright)

  • pnpm --filter web e2e starts a dedicated API (:8001) and Vite (:5175) — does not conflict with dev on :5173 or Docker API on :8000
  • Uses SQLite + EMAIL_DELIVERY_MODE=memory + ENABLE_TEST_ROUTES=true
  • Test email tokens: GET /api/v1/test/emails/latest-token (only when test routes enabled)
  • Reuse running API: E2E_START_API=false E2E_API_URL=http://localhost:8000 pnpm --filter web e2e
  • Install browsers once: pnpm --filter web exec playwright install chromium

Troubleshooting

Problem Solution
docker compose up fails: install.env missing Run python apps/api/scripts/bootstrap_install.py before first start
API exits: password authentication failed for user "compton_app" Postgres volume was initialized before bootstrap. Reset local dev data: docker compose --profile docker-web down -v, bootstrap again, then docker compose --profile docker-web up -d --build. See docs/secrets-recovery.md
Login failed with correct credentials Check API: curl http://localhost:8000/api/v1/health. Ensure apps/web/.env exists for local frontend. Restart: docker compose restart web or pnpm --filter web dev
401 on /auth/refresh in browser console (guest) Expected for logged-out users on public pages — refresh is skipped unless a session hint or protected path (/admin, /profile) triggers bootstrap
Logged out after F5 on /admin Usually a failed refresh (expired cookie) or stale Docker web build. Rebuild: docker compose --profile docker-web up -d --build web. Log in again if the refresh cookie expired
«На сайт» in admin does nothing / goes to login Must use full navigation to / (static landing), not React Router. Ensure topbar action is href="/", then rebuild web container if behavior persists
White flash on admin reload app.html preloads admin background before React mount. Verify localStorage.wespAdminTheme and rebuild web if stale assets are served
Sidebar should stay visible while scrolling Admin sidebar is fixed on desktop (position: fixed, height: 100vh) and switches back to normal flow on mobile (<=768px)
Port 5173 already in use Stop Docker web: docker compose --profile docker-web stop web. Or stop local Vite
Frontend in Docker: missing modules / esbuild errors Rebuild: docker compose --profile docker-web up -d --build web. Do not run pnpm install on the host for the Docker workflow
Hot-reload not working in Docker (Windows/macOS) Enabled via CHOKIDAR_USEPOLLING=true. Restart: docker compose --profile docker-web restart web
Admin panel missing Security/Diagnostics tabs Log in as admin@compton.example (superuser), not ops@compton.example
Need DB password for DBeaver Admin → Security → Install Secrets → Reveal DB password (superuser), then docker-compose.dev-ports.yml
Lost install.env / DB auth failed See docs/secrets-recovery.md. Do not regenerate secrets if Postgres volume already exists
CORS errors in browser console Keep VITE_USE_API_PROXY=true (default in dev). Do not call localhost:8000 directly from the browser

Security Highlights

All cryptographic primitives live in a single module (apps/api/app/core/crypto.py); security.py and media_signing.py re-export from it.

Layer Mechanism
Passwords bcrypt cost 12 with per-user salt (embedded in hash); shared denylist
Access JWT HS256, short TTL, in-memory on frontend only
Refresh tokens HttpOnly cookie (Path=/api/v1/auth), rotation + family reuse detection; SHA-256 hash with server-side pepper
Email/reset tokens Opaque tokens, SHA-256 + pepper in DB; raw token never stored
Media URLs HMAC-SHA256 signed paths with TTL
Install secrets secrets.token_* generation; generate-once + lock; superuser reveal (audited)

Auth & API

  • Refresh token rotation and reuse-detection (family revoke)
  • Rate limiting and brute-force lockout (ENABLE_RATE_LIMIT=true in Docker compose)
  • Pending/blocked users rejected on refresh; frontend clears session on 403 (ACCOUNT_BLOCKED, EMAIL_NOT_VERIFIED)
  • Origin/Referer validation on cookie-based auth endpoints
  • TRUSTED_PROXY_IPS — only listed proxies may influence client IP via X-Forwarded-For
  • Email bodies omit raw tokens in production (EMAIL_DELIVERY_MODE=memory or ENABLE_TEST_ROUTES=true only)

Data & infra

  • PostgreSQL/Redis/MinIO on internal Docker network by default (no host ports)
  • DB CHECK constraints, connection pool tuning, startup token cleanup
  • CMS HTML sanitization (bleach) with allowed URL protocols: http, https, mailto
  • Signed avatar URLs; RBAC + superuser guards on admin routes
  • Admin password policy on user create/reset

Production guards (APP_ENV=production — API refuses to start if):

  • ENABLE_TEST_ROUTES=true
  • ENABLE_DOCS=true
  • ENABLE_RATE_LIMIT=false
  • COOKIE_SECURE=false
  • Default DB credentials (user:pass) or placeholder JWT secrets
  • DATABASE_URL without sslmode=require

MVP Status (Phase 1)

# Feature Status
1.1 PostgreSQL + Alembic + seed done
1.2 Auth + SMTP (verify/reset) done
1.3 Profile CRUD + avatar (MinIO) done
1.4 Content pages done
1.5 Admin panel (Ant Design + zootech auth UI) done
1.6 E2E regression §15.7 done
S
Description
No description provided
Readme
28 MiB
Languages
Python 49.9%
HTML 21.5%
JavaScript 19.3%
CSS 7.3%
TypeScript 1.6%
Other 0.4%