app/FastAPI backend (Python package withmain.py,routers/,services/,schemas/,middleware/).routers/HTTP surfacesservices/business logicschemas/Pydantic modelsmiddleware/cross-cutting policies
alembic/DB migrationsscripts/seeds & utilitiesadmin/Vite + React admin dashboard (src/components,src/pages,src/lib,src/test)app/Vite + React tenant app (Node toolchain lives alongside the backend package; run frontend commands from this folder)landing/Vite marketing site (Next.js variant lives inlanding/nextjs-landing)docs/,docs-site/,samples/,sdks/
- Backend bootstrap:
python -m venv venv && pip install -r requirements.txt && alembic upgrade head - Run API:
uvicorn app.main:app --reload - Backend checks:
coverage run --branch -m pytest -q && coverage report --fail-under=90 - Admin dev:
cd admin && npm install && npm run dev - Tenant dev:
cd app && npm install && npm run dev - Frontend gates:
npm run lint && npm test && npm run build
- Python: PEP8, four spaces,
black,ruff; snake_case functions, PascalCase classes/schemas - TS/React: Components/pages PascalCase; hooks start with
use; shared helpers insrc/lib
- Backend 90% statements / 80% branches; security middleware covered in
tests/test_middleware_security.py - Fixtures in
tests/conftest.py; keepDEMO_MODE=falseand dont setOPENAI_API_KEYin CI - Frontend:
vitestwith coverage (admin/coverage)
- Start from
.env.example; never commit secrets - ENV must be
productionin prod (code readsENV=production) - Keep
DEMO_MODE=false - Dont put secrets in Vercel frontends
- Vercel projects must set Root Directory to the app folder:
rulhublanding(Vite marketing)rulhub-adminadmin(Vite admin dashboard)rulhub-appapp(Vite tenant UI)
- Never point a Vercel project at repo root or use FastAPI preset for frontends
- Backend deploys to Render only (
uvicorn app.main:app)
-
Alembic ops must be idempotent (check
pg_constraintbefore creating constraints). -
Example helper:
def constraint_exists(conn, name): return bool(conn.exec_driver_sql( "SELECT 1 FROM pg_constraint WHERE conname=%s", (name,) ).fetchone())