# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview Lunchtime is a self-hosted group lunch ordering app: FastAPI + SQLite backend (Python 3.12), React 18 + TypeScript + Vite + Ant Design frontend, nginx in front. All app code lives under `src/`. There are no tests and no linter configured. ## Commands Full dev stack (backend with `--reload`, Vite HMR, nginx, Mailpit); also what the devcontainer starts: ```bash cd src && docker compose up -d # app at http://localhost:8020, Mailpit at http://localhost:8025 ``` Without Docker: ```bash cd src/backend && pip install -r requirements.txt && DB_PATH=./.data uvicorn app.main:app --reload --port 5000 cd src/frontend && npm install && npm run dev # Vite on :3000 cd src/frontend && npm run build # tsc is not run by build; use `npx tsc --noEmit` for type checking ``` `nginx.conf` (repo root) expects `backend:5000` and `frontend:8000`, so running outside Compose needs your own proxy for `/api`. CI (`.gitea/workflows/build-and-push-images.yaml`) builds and pushes backend/frontend images with Kaniko on changes to `src/**`. ## Architecture **Backend** (`src/backend/app/`): almost every route is in the single `main.py` (~1300 lines), with Pydantic request models at the top. `services/` holds helpers (accounts/tokens, email, per-order file storage, submissions, config loading); `db.py` creates the schema inline in `init_db()` (`CREATE TABLE IF NOT EXISTS` plus ad-hoc `ALTER TABLE` migrations — there is no migration tool) and maps rows to dicts. **Auth model:** there are no passwords or sessions. The client generates a user ID and sends it in the `X-User-Id` header (`get_existing_user_id` dependency). Per-order access is via tokens stored in `user_order_tokens` (`admin_token` = order owner, `submission_token` = participant); routes under `/api/orders/{id}/admin/...` check the caller's admin token. Email confirmation, user-ID change, email change and account migration flows use `account_confirmation_tokens` and send mail through `email_service`. **Storage is split:** SQLite (`burger_orders.db` in `DB_PATH`) holds orders, submissions, user profiles and tokens. Per-order content is stored as files under `data/` next to the app, **keyed by the order's `admin_token`**: `configs/.json` (menu/form definition), `descriptions/.md`, `templates/`, `payments/`, and uploaded `images/` (served at `/api/images`). Deleting an order must also delete these files (see `delete_order` in `main.py`). **App config:** `config.yaml` (mounted read-only at `/app/config.yaml`) controls `public_base_url`, SMTP settings and UI announcements; loaded by `services/config_service.py` and exposed via `GET /api/config`. **Frontend** (`src/frontend/src/`): no router library — `lib/routing.ts` parses `window.location` into `home | create | order | admin` routes and `App.tsx` switches between the four `views/`. `lib/api.ts` wraps `fetch` (adds `X-User-Id`, clears the stored ID on "invalid user id" 401s); `lib/services/*` are typed per-domain API clients; `hooks/useApiRequest.ts` and `components/utils/AsyncContent.tsx` handle loading/error state. `lib/orderTemplating.ts` and `lib/orderFormatting.ts` render submissions into the text that is shared/exported from the form config. `TODO.md` tracks planned admin-view work. Dev note: Compose mounts only `backend/app` and selected frontend source into the containers, so changes to `requirements.txt`/`package.json` need a rebuild. `src/frontend/dist/` is committed build output.