# Contributing to Multithreaded Patterns, conventions, and rules for working on the Multithreaded forum codebase. ## Project Structure ``` multithreaded/ (workspace root) Cargo.toml # Workspace + root crate deps src/ lib.rs # AppState, module declarations main.rs # Entry point, migrations, server bind auth.rs # MNW OAuth (PKCE), session extractors csrf.rs # CSRF middleware (synchronizer token) config.rs # Config from environment internal_auth.rs # HMAC-SHA256 auth for MNW→MT internal API link_preview.rs # OG metadata extraction storage.rs # S3 wrapper around s3-storage crate seed.rs # Seed data for development routes/ mod.rs # Route tree, rate limiting helpers.rs # Reusable route helpers (get_community, render_markdown) forum/ # Forum views, thread, posts, actions moderation.rs # Mod tools flagging.rs # Content flagging settings.rs # Community settings admin.rs # Platform admin search.rs # Full-text search tracking.rs # Read state tracking uploads.rs # Image uploads (S3) templates/ mod.rs # Template structs + IntoResponse macro public.rs # Public page templates admin.rs # Admin templates crates/ mt-core/ # Domain types (enums, time formatting) mt-db/ # Database queries and mutations (sqlx) templates/ # Askama HTML templates static/ # CSS, JS, fonts migrations/ # PostgreSQL migrations (auto-applied on boot) tests/ # Integration tests deploy/ # Deploy scripts, systemd unit ``` ### Crate Boundaries | Crate | Role | May depend on | |-------|------|---------------| | `mt-core` | Domain enums, time formatting | Nothing internal | | `mt-db` | SQL queries and mutations | `mt-core` | | root crate | Routes, templates, auth, config | `mt-core`, `mt-db`, shared libs | Library crates (`mt-core`, `mt-db`) contain no web framework types. Routes and templates live in the root crate only. ## MNW OAuth Integration Multithreaded delegates all authentication to MNW via OAuth 2.0 with PKCE. There are no local passwords or signup forms. See [architecture.md § Authentication](docs/architecture.md#4-authentication) for the full flow (PKCE parameters, state nonce validation, retry behavior, session cycling). **Extractors:** - `MaybeUser(Option)` — optional auth, infallible (never rejects) - `PlatformAdmin(SessionUser)` — admin-only, returns 404 to hide admin routes from non-admins ## Route Handlers ### Signature Pattern ```rust #[tracing::instrument(skip_all)] pub(in crate::routes) async fn handler_name( axum::extract::State(state): axum::extract::State, session: Session, MaybeUser(session_user): MaybeUser, Path((slug, category)): Path<(String, String)>, Query(page_query): Query, ) -> Result { let csrf_token = Some(csrf::get_or_create_token(&session).await); let community = get_community(&state.db, &slug).await?; // ... build template struct ... Ok(MyTemplate { csrf_token, session_user, /* ... */ }) } ``` ### Error Handling Multithreaded uses `Result` directly — no centralized `AppError` type. Errors are converted to `Response` inline via helper functions in `routes/helpers.rs`: ```rust pub(crate) async fn get_community(db: &PgPool, slug: &str) -> Result { mt_db::queries::get_community_by_slug(db, slug) .await .map_err(|e| { tracing::error!(error = ?e, "db error fetching community"); StatusCode::INTERNAL_SERVER_ERROR.into_response() })? .ok_or_else(|| StatusCode::NOT_FOUND.into_response()) } ``` Use these helpers for consistent error responses. All DB errors log with `tracing::error!` and return 500. Missing resources return 404. ### Rate Limiting Write routes (POST) use `tower_governor` rate limiting. Read routes (GET) have no rate limit. The rate limiter is applied via `.route_layer()` on the write routes group. ## Templates Askama templates with Jinja2-like syntax. Template structs live in `src/templates/`, HTML files in `templates/`. ### IntoResponse Macro Template structs get `IntoResponse` via a bulk macro: ```rust impl_into_response!( ForumDirectoryTemplate, CommunityTemplate, ThreadTemplate, // ... ); ``` This handles rendering and returns 500 if template rendering fails. ### Layout Inheritance All full pages extend `templates/base.html`: ```html {% extends "base.html" %} {% block title %}Page Title{% endblock %} {% block header %}{% include "partials/site_header.html" %}{% endblock %} {% block content %} {% endblock %} ``` Every full-page template struct needs: - `csrf_token: Option` — for the CSRF meta tag - `session_user: Option` — for header login state - `mnw_base_url: Arc` — for links back to MNW ## Database Layer Queries and mutations live in `crates/mt-db/`. Same sqlx patterns as MNW server: ```rust pub async fn list_communities(pool: &PgPool, limit: i64, offset: i64) -> Result, sqlx::Error> { sqlx::query_as::<_, CommunityListRow>( "SELECT co.name, co.slug, co.description, ... FROM communities co ... LIMIT $1 OFFSET $2", ) .bind(limit).bind(offset) .fetch_all(pool).await } ``` **Rules:** - Always use positional parameters (`$1`, `$2`). Never interpolate. - Projection structs derive `sqlx::FromRow` and are shaped for templates, not domain models. - Queries in `queries.rs`, mutations in `mutations.rs`. - Use `ON CONFLICT ... DO UPDATE` for upserts (user sync from MNW). Migrations are in `migrations/` and auto-apply on boot via `sqlx::migrate!()`. ## Shared Dependencies MT uses three shared crates from `MNW/shared/`: | Crate | Usage | |-------|-------| | `docengine` | Markdown rendering with `render_strict()`, @mention resolution, quote post-processing | | `tagtree` | Tag name validation (`validate_with(&tag, &MT_TAG_CONFIG)`) | | `s3-storage` | Image uploads via `S3Storage` wrapper | **Deploy note:** `deploy.sh` syncs shared deps to `~/src/shared/` on Astra because Cargo.toml references `../shared/X`. If you add a new shared dep, update the rsync loop in `deploy.sh`. ## CSRF Protection Synchronizer token pattern, same as MNW server. CSRF middleware validates `X-CSRF-Token` header on POST/PUT/PATCH/DELETE. Exempt paths: `/auth/`, `/api/health`, `/_test/`. Client-side: HTMX sends the token automatically via a `htmx:configRequest` listener that reads from ``. ## Internal API (MNW → MT) MNW server can call MT's internal API (e.g., to auto-create communities for new projects). These requests use HMAC-SHA256 authentication: - `X-Internal-Timestamp` — Unix timestamp (must be within 60 seconds of server time) - `X-Internal-Signature` — HMAC-SHA256 of `"timestamp\nbody"` using shared secret The `InternalAuth` extractor validates both before allowing access. ## Testing Integration tests use the same pattern as MNW: each test creates and drops its own PostgreSQL database. ```rust let harness = TestHarness::new().await; // Creates mnw_test_ DB let user_id = harness.login_as("alice").await; // Inserts user + sets session // ... test routes via harness.client ... // Database dropped on harness drop ``` A `/_test/login` route (only available in tests) bypasses OAuth for session setup. Tests are organized by domain in `tests/workflows/`. ## Deployment MT builds natively on Astra (aarch64). Deploy from `multithreaded/`: ```bash ./deploy/deploy.sh # rsync source + shared deps, build on Astra, deploy binary + assets ``` The deploy script: 1. Rsyncs source to `~/src/multithreaded/` on Astra 2. Rsyncs shared deps (docengine, tagtree, s3-storage) to `~/src/shared/` 3. Builds release binary on Astra 4. Copies binary + static + migrations to `/opt/multithreaded/` 5. Restarts the systemd service