diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c74e35f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +ROA AUTO is a multi-tenant auto service management PWA. The architecture is **offline-first**: the Vue 3 frontend uses wa-sqlite (WebAssembly SQLite) running entirely in the browser as `:memory:`, with a sync engine that pulls from and pushes to the FastAPI backend. All business data lives locally in the browser's in-memory SQLite DB and is periodically synced. + +## Development Commands + +### Backend (FastAPI) +```bash +cd backend +source .venv/bin/activate +uvicorn app.main:app --port 8000 --reload # start dev server +pytest tests/ -v # run all tests +pytest tests/test_orders.py -v # run single test file +pytest tests/test_orders.py::test_name -v # run single test +``` + +### Frontend (Vue 3 + Vite) +```bash +cd frontend +npm run dev # start dev server (port 5173) +npm run build # production build +``` + +### Docker (preferred for full stack) +```bash +make dev # start all services (docker-compose.dev.yml) +make test # run backend tests in container +make migrate # run alembic migrations +make seed # seed demo data +``` + +## Architecture + +### Sync Model (critical to understand) +- **Frontend writes locally first**, then queues changes in `_sync_queue` (SQLite table) +- `SyncEngine` (`frontend/src/db/sync.js`) handles three operations: + - `fullSync()` — downloads all tenant data from `/api/sync/full` on login + - `incrementalSync()` — downloads changes since `last_sync_at` + - `pushQueue()` — uploads queued local changes to `/api/sync/push` +- The backend sync service (`backend/app/sync/service.py`) defines `SYNCABLE_TABLES` — only these tables are synced +- IDs are `crypto.randomUUID()` generated client-side; backend uses `INSERT OR REPLACE` + +### Frontend (`frontend/src/`) +- **`db/database.js`** — singleton wa-sqlite instance. Uses `:memory:` only (no IndexedDB persistence). `execSQL(sql, params)` is the only query interface. Parameterized queries **must** use `sqlite3.statements()` async generator — `exec()` does not support `?` params. Do NOT use `prepare_v2` (doesn't exist in this library). +- **`db/schema.js`** — full SQLite schema as a string constant, applied on init +- **`db/sync.js`** — `SyncEngine` class, exported as singleton `syncEngine` +- **`stores/`** — Pinia stores (`auth.js`, `orders.js`, `vehicles.js`) wrapping `execSQL` calls +- **`views/`** — one file per route; data loaded via `execSQL` directly or through stores +- **Reactivity** — `notifyTableChanged(table)` / `onTableChange(table, cb)` in `database.js` provide a pub/sub for cross-component updates. Call `notifyTableChanged` after any write. + +### Backend (`backend/app/`) +- **Multi-tenant**: every model has `tenant_id`. All queries filter by `tenant_id` from the JWT. +- **Auth**: JWT in `Authorization: Bearer` header. `get_tenant_id` dep extracts tenant from token. +- **`deps.py`** — `get_current_user` and `get_tenant_id` FastAPI dependencies used across all routers +- **`sync/`** — The most complex module. `apply_push` does `INSERT OR REPLACE` for all ops. +- **`pdf/`** — WeasyPrint + Jinja2 HTML templates to generate PDF deviz/factura +- **`client_portal/`** — Public routes (no auth) for client-facing deviz view via `token_client` +- **DB**: SQLite via SQLAlchemy async (`aiosqlite`). Alembic for migrations (files in `alembic/versions/`). + +### Key Data Flow: Order Lifecycle +`DRAFT` → `VALIDAT` → `FACTURAT` +- Order lines (manopera/material) added only in DRAFT +- PDF deviz available after VALIDAT +- Invoice created locally in `handleFactureaza()` then queued for sync + +## Critical wa-sqlite Notes +- Import: `wa-sqlite.mjs` (sync WASM build), NOT `wa-sqlite-async.mjs` +- All API methods (`open_v2`, `exec`, `step`, `finalize`) return Promises — always `await` +- For parameterized queries: `for await (const stmt of sqlite3.statements(db, sql))` then `sqlite3.bind_collection(stmt, params)` +- `vite.config.js` excludes `@journeyapps/wa-sqlite` from `optimizeDeps` +- COOP/COEP headers (`Cross-Origin-Opener-Policy: same-origin` + `Cross-Origin-Embedder-Policy: require-corp`) are required for SharedArrayBuffer used by wa-sqlite — set in vite dev server and nginx + +## WSL2 Note +Running on WSL2 with code on Windows NTFS (`/mnt/e/`): Vite is configured with `server.watch.usePolling: true, interval: 1000` to work around inotify not firing on NTFS mounts. + +## Testing +- Backend tests use an in-memory SQLite DB (overrides `get_db` via `app.dependency_overrides`) +- `asyncio_mode = auto` set in `pytest.ini` — no need to mark tests with `@pytest.mark.asyncio` +- `auth_headers` fixture registers a user and returns `Authorization` header for authenticated tests +- Demo credentials (after `make seed`): `demo@roaauto.ro` / `demo123`