Files
tutortool/CLAUDE.md
s0wlz (Matthias Puchstein) ff5ad26cfc feat: harden security with httpOnly cookies and modernize frontend with Svelte 5 runes
- Switched to secure httpOnly, SameSite=Strict cookies for JWT authentication.
- Refactored backend to use AppState for shared secrets and database pool caching.
- Modernized frontend with Svelte 5 runes ($state) and removed localStorage reliance.
- Gated destructive test endpoints behind debug_assertions and fixed unsafe test patterns.
- Enhanced CI pipeline with cargo clippy, cargo fmt, and pinned pnpm version.
- Updated documentation and implementation plans to match the hardened architecture.
2026-05-02 03:16:33 +02:00

93 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
# Linting & Quality
make lint # runs cargo fmt, clippy (-D warnings), and svelte-check
# Build
make build # runs lint, then pnpm build and cargo build --release
make compose-up # docker compose build + start
# Testing
make test # runs lint, then cargo test (backend unit tests)
make test-e2e # test-up + pnpm test:e2e in one step
# Demo data
make seed-demo # wipe dev.db and reseed from backend/demo/demo_seed.sql
```
## 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**: Secure JWT-based authentication. The backend sets an `httpOnly`, `SameSite=Strict` cookie named `token`. The `TutorClaims` extractor in `auth.rs` enforces authentication by reading this cookie.
- **Shared State**: Axum handlers use `State<AppState>` (or `State<SqlitePool>` via `FromRef`) which caches the `JWT_SECRET` and DB pool.
- **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`.
Route modules: `auth_routes`, `checkin`, `courses`, `rooms`, `sessions`, `attendance`, `notes`, `export`, `tutors`, and `test_reset` (mounted only when `TT_TEST_MODE=1` AND in debug builds).
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.
- **Auth state**: Managed by the `auth` object in `$lib/auth.svelte.ts`.
- **Adapter**: `adapter-static` → single-page app, `fallback: 'index.html'`
- **API client**: `src/lib/api.ts` — all fetch calls go through here; relies on browser automatic cookie handling.
- **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 (~1050 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 `make lint`, `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`.
- **Zero Warnings Policy**: All code must pass `make lint` (clippy, fmt, svelte-check) without warnings before committing.