# Development Guidelines ## Getting Started ### Prerequisites - **Rust** (stable, 1.88+) with `cargo` - **Node.js** (22+) with `npm` - **PostgreSQL** (17+) -- or Docker to run it - **Docker** and `docker compose` for containerized development ### Local Development Setup 1. **Start a Postgres instance.** The easiest way is via the test compose file: ```bash cd e2e && docker compose -f docker-compose.test.yml up -d db ``` This starts Postgres on port 5433 with user `ai_synth_test` / password `testpassword`. 2. **Run the backend:** ```bash cd backend export DATABASE_URL=postgres://ai_synth_test:testpassword@127.0.0.1:5433/ai_synth_test cargo run -- serve ``` Migrations run automatically on startup. 3. **Run the frontend (dev server with hot reload):** ```bash cd frontend npm install npm run dev ``` The Vite dev server proxies `/api` requests to the backend on port 8080. 4. **Create an admin user:** ```bash cd backend && cargo run -- create-admin admin@example.com ``` ### Environment Variables Copy `.env.example` to `.env` and fill in the values. The critical ones for local dev are `DATABASE_URL`, `MASTER_ENCRYPTION_KEY` (64 hex chars -- generate with `openssl rand -hex 32`), and `APP_URL`. --- ## Project Structure ``` ai_synth/ backend/ Rust/Axum backend src/ main.rs Entry point, CLI (serve, create-admin) router.rs All API routes + middleware app_state.rs Shared application state (Arc-wrapped) errors.rs Unified AppError enum config.rs Environment config parsing handlers/ HTTP handlers (thin: validate, delegate, respond) services/ Business logic (auth, synthesis pipeline, LLM providers, scraper, etc.) db/ Database queries (sqlx, parameterized) models/ Data types + validation middleware/ Auth session extraction, CSRF check util/ Token generation, hashing migrations/ SQL migrations (30 files, auto-run on startup) tests/ Integration tests (require Postgres) frontend/ SolidJS + Tailwind CSS v4 src/ App.tsx Router, layouts, route guards pages/ Page-level components components/ Reusable components (Button, LoadingSpinner, settings/*) api/ API clients (one file per resource) contexts/ AuthContext (session-based) i18n/ French translations utils/ SSE client, date formatting, provider info types.ts All TypeScript domain types e2e/ Playwright E2E tests tests/ Test specs helpers/ Auth helpers, DB access seed.ts Test data seeder scripts/ Test runner scripts docs/ Architecture reports, plans, specs ``` ### Layer Architecture (Backend) ``` handlers/ (HTTP layer) --> services/ (business logic) --> db/ (data access) | | models/ (shared types) <---------+ | errors.rs ``` - **Handlers** are thin: validate input, call services/db, format responses. - **Services** contain business logic. The `LlmProvider` trait and synthesis pipeline live here. - **DB** modules contain pure SQL queries returning typed results. No business logic. - **Models** define data types and validation. Shared across layers. --- ## Coding Standards ### Rust #### Error Handling All errors flow through the unified `AppError` enum (in `backend/src/errors.rs`): ```rust #[derive(Debug, thiserror::Error)] pub enum AppError { NotFound(String), // 404 Unauthorized(String), // 401 Forbidden(String), // 403 BadRequest(String), // 400 Validation(String), // 422 Internal(anyhow::Error),// 500 -- details logged, not exposed to client RateLimited(String), // 429 } ``` Key rules: - **Never use `unwrap()` in production code.** Use `?`, `ok_or_else`, `map_err`, or `unwrap_or_default` with appropriate logging. `unwrap()` is only acceptable in `#[cfg(test)]` blocks and `LazyLock` static initializers. - **`AppError::Internal` hides details** from the client. The full error is logged via `tracing::error!` but the response body only contains `"An internal error occurred"`. - **`From` and `From`** conversions are implemented, so you can use `?` with both types. - **Validation errors** should use `AppError::Validation(message)` (returns 422). #### Arc Usage `Arc` is used to share data across `tokio::spawn` boundaries. Common patterns: - `Arc` for the LLM provider (shared across classify tasks) - `Arc` for cancellation flags - `Arc>` for SSE progress channels - `Arc`, `Arc>`, `Arc` for data shared with spawned tasks #### Auth Middleware Pattern Authentication uses Axum extractors (in `backend/src/middleware/auth.rs`): - **`AuthUser`**: Reads the session cookie, looks up the session in the DB, checks expiration, loads the user. Any handler that takes `AuthUser` as a parameter automatically rejects unauthenticated requests with 401. - **`AdminUser(AuthUser)`**: Wraps `AuthUser` and additionally checks `UserRole::Admin`. Returns 403 if not admin. To require authentication, simply add the extractor to your handler signature: ```rust async fn my_handler(auth: AuthUser, State(state): State) -> Result<..., AppError> { ... } ``` For admin-only endpoints: ```rust async fn admin_handler(admin: AdminUser, State(state): State) -> Result<..., AppError> { ... } ``` #### Other Rust Conventions - All SQL queries use parameterized bindings (`$1`, `$2`) via sqlx. Never interpolate strings into SQL. - Prefer `tracing::info!`, `tracing::warn!`, `tracing::error!` over `println!`. - Code comments and log messages are in English. User-facing strings are in French (via the i18n system). - Module-level `//!` doc comments on every file; function-level `///` doc comments on public items. ### Frontend (SolidJS) #### Reactive Primitives - Use `createSignal` for local component state. - Use `createResource` for async data that should auto-refetch (preferred over `createEffect` + manual fetch). - Use `createMemo` for derived/computed values. - Use `createEffect` for side effects that need to react to signal changes. - Always use `onCleanup` to clear timers, close connections, and cancel subscriptions. #### Component Patterns - Use the `Button` component (`components/ui/Button.tsx`) with `variant`/`loading`/`icon` props instead of raw `