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

4.9 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# 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: closedopenlocked. 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.