- db.rs: fix fresh-PVC startup crash by using SqliteConnectOptions with create_if_missing(true) and foreign_keys(true); drops after_connect - Dockerfile: switch to Node 22 + pnpm (corepack), run pnpm check before build, copy backend/demo/ for TT_TEST_MODE support, non-root app user, add HEALTHCHECK, remove baked-in JWT_SECRET - .dockerignore: exclude node_modules, build artifacts, data/, logs - deploy/: new Helm chart replacing k8s/ — Deployment, Service, HTTPRoute (Gateway API), PVC (hcloud-volumes), CronJob backup, ServiceAccount, VPA; JWT_SECRET sourced from pre-provisioned K8s Secret - k8s/: removed (superseded by deploy/) - ci.yml: replaces test.yml — Node 20->22, same test steps, adds no-push Docker build; triggers on non-main pushes and PRs - release.yml: new tag-driven workflow (v*.*.*) — runs tests, pushes image to registry.itsh.dev/s0wlz/tutortool, deploys via helm upgrade https://claude.ai/code/session_01N1kWaQJkz1fC7mUippdQR5
99 lines
5.0 KiB
Markdown
99 lines
5.0 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
# Development
|
||
make dev # start backend + frontend in parallel
|
||
make dev-backend # cargo run (port 3000)
|
||
make dev-frontend # pnpm dev (port 5173, /api proxied to :3000)
|
||
|
||
# Build
|
||
make build # pnpm build, then cargo build --release
|
||
make compose-up # docker compose build + start
|
||
|
||
# Backend
|
||
cargo test # run all backend unit tests
|
||
cargo check # fast type check without linking
|
||
|
||
# Frontend
|
||
pnpm check # TypeScript + Svelte type check
|
||
pnpm check:watch # watch mode
|
||
pnpm build # Vite build to dist/
|
||
|
||
# Demo data
|
||
make seed-demo # wipe dev.db and reseed from backend/demo/demo_seed.sql
|
||
|
||
# E2E test pipeline (see docs/testing.md for full detail)
|
||
make test-up # build test DB if missing, start backend on test port, wait for /health
|
||
make test-down # stop the test backend
|
||
make test-reset # fast DB reset via POST /__test__/reset (~10–50 ms)
|
||
make test-rebuild # wipe and rebuild test DB from migrations + seed
|
||
make test-e2e # test-up + pnpm test:e2e in one step
|
||
```
|
||
|
||
## Architecture
|
||
|
||
TutorTool is a **Rust + SvelteKit attendance tracker** for tutoring sessions.
|
||
|
||
### Backend (`backend/`)
|
||
|
||
- **Framework**: Axum (async) on Tokio, port 3000
|
||
- **Database**: SQLite via SQLx — all queries use the runtime `sqlx::query()` / `sqlx::query_as::<_, T>()` (not compile-time macros); no `DATABASE_URL` needed for `cargo build`/`cargo check`
|
||
- **Auth**: JWT (7-day expiry, `jsonwebtoken` crate) + bcrypt passwords; `TutorClaims` extractor in `auth.rs`
|
||
- **Static serving**: `tower_http::ServeDir` serves compiled frontend from `frontend/build/` with SPA fallback to `index.html`
|
||
- **Migrations**: auto-run via `sqlx::migrate!` at startup from `backend/migrations/`
|
||
- **`PRAGMA foreign_keys = ON`** is enforced on every connection in `db.rs`
|
||
|
||
Route handlers live in `backend/src/routes/` and are merged in `routes/mod.rs`. Each handler receives `State<SqlitePool>` and extracts `TutorClaims` from the JWT on protected routes.
|
||
|
||
Route modules: `auth_routes`, `checkin`, `courses`, `rooms`, `sessions`, `attendance`, `notes`, `export`, `tutors`, and `test_reset` (mounted only when `TT_TEST_MODE=1`).
|
||
|
||
The `/health` route always returns `"ok"` and is used by the test pipeline to wait for startup.
|
||
|
||
### Frontend (`frontend/`)
|
||
|
||
- **Framework**: SvelteKit 5 (Svelte runes, `$state`/`$derived`) with TypeScript
|
||
- **Adapter**: `adapter-static` → single-page app, `fallback: 'index.html'`
|
||
- **API client**: `src/lib/api.ts` — all fetch calls go through here; JWT injected from `src/lib/auth.ts` (localStorage-backed store)
|
||
- **Types**: `src/lib/types.ts` mirrors the Rust models exactly — keep them in sync when changing the API
|
||
|
||
Routes:
|
||
- `routes/admin/login/` — public login
|
||
- `routes/admin/` — all tutor-facing pages (guarded by `+layout.svelte` auth check)
|
||
- `attendance/`, `courses/`, `export/`, `live/`, `rooms/`, `sessions/`, `students/`, `tutors/`
|
||
- `routes/s/[code]/` — public student check-in page
|
||
|
||
### Data Model (SQLite, 9 tables)
|
||
|
||
```
|
||
tutors ──< tutor_courses >── courses ──< students
|
||
└──< sessions ──< slots ──< attendances
|
||
└──< notes
|
||
rooms (layout_json) ←── slots.room_id
|
||
```
|
||
|
||
Key slot status transitions: `closed` → `open` → `locked`. The `code` field on slots is the public check-in token students use at `/s/[code]`.
|
||
|
||
### Testing
|
||
|
||
E2E tests run Playwright against a dedicated test backend daemon. The test backend uses a separate DB and is started with `TT_TEST_MODE=1`, which enables `POST /__test__/reset` for fast state resets (~10–50 ms) without restarting the process. Each git worktree gets its own deterministic port and DB path (no collisions when testing across branches).
|
||
|
||
See `docs/testing.md` for the full guide including seed data, MCP-driven verification, and worktree isolation details.
|
||
|
||
### Container / K8s / CI
|
||
|
||
- `Dockerfile`: 3-stage build (Node 22/pnpm frontend → Rust 1.95 backend → Debian slim runtime, non-root)
|
||
- `deploy/`: Helm chart — Deployment, Service, HTTPRoute (Gateway API), PVC, CronJob for nightly vacuum + backup rotation
|
||
- Live at `tutor.puchstein.dev` (tenant-5, ITSH Cloud); image at `registry.itsh.dev/s0wlz/tutortool`
|
||
- CI: Gitea Actions at `.gitea/workflows/ci.yml` — runs `cargo check`, `pnpm check` (tsgo + svelte-check), `cargo test`, `pnpm build`, `make test-e2e`, and a no-push Docker build on every non-main push and PR
|
||
- Release: `.gitea/workflows/release.yml` — triggered by `v*.*.*` tags; builds + pushes image, then `helm upgrade`
|
||
|
||
## Conventions
|
||
|
||
- Rust toolchain is pinned to 1.95.0 via `rust-toolchain.toml`.
|
||
- Frontend indentation: 2 spaces (Svelte/TS files). Backend: standard `rustfmt` defaults.
|
||
- All SQLx queries are runtime (`sqlx::query_as::<_, T>()`); no compile-time macros are used, so `DATABASE_URL` is not required for `cargo build` or `cargo check`.
|