Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
3.5 KiB
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:
cd src && docker compose up -d # app at http://localhost:8020, Mailpit at http://localhost:8025
Without Docker:
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/<token>.json (menu/form definition), descriptions/<token>.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.