diff --git a/CLAUDE.md b/CLAUDE.md index 73b38b4..11e6c21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,13 +15,23 @@ make build # pnpm build, then cargo build --release make compose-up # docker compose build + start # Backend -cargo test # run all tests (uses sqlx::test macro — no DB setup needed) +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 @@ -37,7 +47,11 @@ TutorTool is a **Rust + SvelteKit attendance tracker** for tutoring sessions. - **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 into the main router in `main.rs`. Each handler receives `State` and extracts `TutorClaims` from the JWT on protected routes. +Route handlers live in `backend/src/routes/` and are merged in `routes/mod.rs`. Each handler receives `State` 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/`) @@ -49,6 +63,7 @@ Route handlers live in `backend/src/routes/` and are merged into the main router 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) @@ -62,14 +77,21 @@ 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]`. -### Container / K8s +### 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 20 frontend → Rust 1.95 backend → Debian slim runtime) - `k8s/`: Deployment, Service, PVC for SQLite, CronJob for nightly vacuum + backup rotation - Live at `tutor.puchstein.dev` (tenant-5, ITSH Cloud) +- CI: Gitea Actions at `.gitea/workflows/test.yml` — runs `cargo check`, `pnpm check`, `cargo test`, `pnpm build`, and `make test-e2e` on every push to `main` and on PRs ## Conventions -- SQLx compile-time queries require `DATABASE_URL` set to a valid SQLite file during `cargo build`/`cargo check`. Use `export DATABASE_URL=sqlite:./dev.db` if running outside `make dev`. - 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`. diff --git a/GEMINI.md b/GEMINI.md index 3d37f5e..8521987 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -11,70 +11,113 @@ TutorTool is a full-stack web application for tracking student attendance in tut - **Tools**: `remember`, `recall`, `list`, `get_status`. -## Project Overview +## 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 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 - **Architecture**: Standalone repo with a Rust backend (Axum) and a SvelteKit 5 frontend. - **Backend**: - - **Framework**: Axum (async) on Tokio. - - **Database**: SQLite via SQLx. Migrations are automatically run at startup. - - **Auth**: JWT-based authentication (`jsonwebtoken` crate) with bcrypt for passwords. - - **Static Serving**: Serves the compiled SvelteKit frontend as a Single-Page App (SPA). + - **Framework**: Axum (async) on Tokio, port 3000. + - **Database**: SQLite via SQLx. All queries use runtime `sqlx::query()` / `sqlx::query_as::<_, T>()` — no compile-time macros, so `DATABASE_URL` is not required for `cargo build` or `cargo check`. Migrations are automatically run at startup. + - **Auth**: JWT-based authentication (`jsonwebtoken` crate, 7-day expiry) with bcrypt for passwords. The `TutorClaims` extractor in `auth.rs` enforces authentication on protected routes. + - **Static Serving**: Serves the compiled SvelteKit frontend as a Single-Page App (SPA) via `tower_http::ServeDir`. + - **`PRAGMA foreign_keys = ON`** is enforced on every connection in `db.rs`. + - 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"` — used by the test pipeline to wait for startup. - **Frontend**: - **Framework**: SvelteKit 5 using Svelte Runes (`$state`, `$derived`, etc.). - - **Build Tool**: Vite. + - **Build Tool**: Vite with `adapter-static` (SPA mode, `fallback: 'index.html'`). - **Package Manager**: pnpm (preferred over npm). - **Styling**: Vanilla CSS (based on design handoff). - - **API**: Centralized fetch wrapper in `src/lib/api.ts`. + - **API**: Centralized fetch wrapper in `src/lib/api.ts`; JWT injected from `src/lib/auth.ts`. + - **Types**: `src/lib/types.ts` mirrors `backend/src/models.rs` — keep in sync when changing the API. -## Build and Run +## Routes -### Prerequisites -- Rust 1.95.0 (pinned via `rust-toolchain.toml`) -- Node.js & pnpm -- SQLite +### Backend -### Demo Credentials +Route handlers live in `backend/src/routes/` and are merged in `routes/mod.rs`. + +### Frontend + +- `src/routes/admin/login/` — public login page +- `src/routes/admin/` — protected tutor-facing pages (guarded by `+layout.svelte` auth check) + - `attendance/`, `courses/`, `export/`, `live/`, `rooms/`, `sessions/`, `students/`, `tutors/` +- `src/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. + +Demo / seed credentials: - **Admin**: `admin@tutortool.com` / `admin` -### Environment-specific Fixes -- **btrfs pnpm fix**: Use `--package-import-method copy` when running pnpm. +## CI -### Key Commands -- `make dev`: Starts both backend and frontend in parallel (requires `JWT_SECRET` and `DATABASE_URL` in environment). -- `make build`: Performs a full production build (frontend first, then backend). -- `cargo test`: Runs all backend unit and integration tests (uses `sqlx::test`). -- `pnpm check`: Performs TypeScript and Svelte type checking for the frontend. - -### Environment Variables -- `DATABASE_URL`: Path to the SQLite database (e.g., `sqlite:./dev.db`). -- `JWT_SECRET`: Secret key for signing and verifying JWT tokens. -- `STATIC_DIR`: Path to the compiled frontend (defaults to `../frontend/build`). - -## Development Conventions - -### Backend (Rust) -- **Error Handling**: Uses a custom `AppError` enum (see `error.rs`) that implements `IntoResponse`. -- **Database**: Queries use runtime `sqlx::query()` / `sqlx::query_as()`. **Note**: While CLAUDE.md mentions compile-time queries might need `DATABASE_URL`, the current implementation primarily uses runtime queries to simplify CI/CD. -- **Routing**: Routes are modularized in `backend/src/routes/` and merged in `routes/mod.rs`. -- **Auth**: Use the `TutorClaims` extractor in route handlers to enforce authentication and access the current user's ID and email. - -### Frontend (SvelteKit) -- **Runes**: Use Svelte 5 Runes for state management. -- **Type Safety**: Ensure `frontend/src/lib/types.ts` is kept in sync with `backend/src/models.rs`. -- **Layouts**: - - `src/routes/admin/login/`: Public login page. - - `src/routes/admin/`: Protected admin routes (tutor dashboard). - - `src/routes/s/[code]/`: Public student check-in page. +Gitea Actions at `.gitea/workflows/test.yml` runs on every push to `main` and on PRs: +1. `cargo check` + `pnpm check` (type checks) +2. `cargo test` (unit tests) +3. `pnpm build` (frontend build) +4. `make test-up` + `pnpm test:e2e` (E2E) ## Key Files -- `backend/src/main.rs`: Entry point and server configuration. -- `backend/src/db.rs`: Database initialization and migration logic. -- `backend/src/auth.rs`: JWT encoding/decoding and Axum extractor. -- `backend/src/models.rs`: Shared data models (DB rows and Request/Response DTOs). -- `frontend/src/lib/api.ts`: API client for frontend-backend communication. -- `frontend/src/lib/RoomCanvas.svelte`: Interactive SVG-based room layout component. -- `conductor/attendance-tool.md`: Detailed implementation plan and project scope. -## Project History & Context -- The project was migrated/continued from a feature branch as documented in `conductor/attendance-tool.md`. -- Deployment is targeted at Kubernetes with a 3-stage Docker build. +- `backend/src/main.rs` — entry point, `TT_TEST_MODE` flag, `/health` route +- `backend/src/db.rs` — database initialization and migration logic +- `backend/src/auth.rs` — JWT encoding/decoding and Axum extractor +- `backend/src/models.rs` — shared data models (DB rows and Request/Response DTOs) +- `backend/src/routes/mod.rs` — router assembly; test_reset mounted conditionally +- `backend/src/routes/test_reset.rs` — `POST /__test__/reset` handler (test-mode only) +- `backend/demo/demo_seed.sql` — demo/test seed data +- `frontend/src/lib/api.ts` — API client for frontend-backend communication +- `frontend/src/lib/RoomCanvas.svelte` — interactive SVG-based room layout component +- `docs/testing.md` — E2E test pipeline guide + +## 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; `DATABASE_URL` is not required for `cargo build`/`cargo check`. +- **btrfs pnpm fix**: Use `--package-import-method copy` when running pnpm on btrfs filesystems (e.g., `pnpm install --package-import-method copy`). diff --git a/README.md b/README.md new file mode 100644 index 0000000..54c4223 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# TutorTool + +Attendance tracker for tutoring sessions. Tutors manage courses, rooms, and slots; students check in via a public QR code link. Live at [tutor.puchstein.dev](https://tutor.puchstein.dev). + +## Quickstart + +```bash +make dev +# Backend: http://localhost:3000 +# Frontend: http://localhost:5173 +``` + +Demo credentials: `admin@tutortool.com` / `admin` + +## Stack + +- **Backend**: Rust + Axum + SQLite (via SQLx), JWT auth +- **Frontend**: SvelteKit 5 (Svelte runes), TypeScript, adapter-static (SPA) +- **Build**: Vite + Cargo; 3-stage Docker build for production + +## Documentation + +| Doc | Contents | +|---|---| +| [`CLAUDE.md`](CLAUDE.md) | Agent guidance: commands, architecture, conventions | +| [`GEMINI.md`](GEMINI.md) | Same, with Gemini-specific context | +| [`docs/testing.md`](docs/testing.md) | E2E test pipeline (Playwright + test daemon) | +| [`docs/specs/`](docs/specs/) | Feature specs | +| [`docs/plans/`](docs/plans/) | Implementation plans | +| [`docs/design_handoff/`](docs/design_handoff/) | UI design mocks | + +## Deployment + +Kubernetes via `k8s/` manifests on ITSH Cloud (tenant-5, Hetzner). CI via Gitea Actions at `.gitea/workflows/test.yml`.