# AI Weekly Synth ## Overview AI Weekly Synth is a self-hosted web application that generates AI-powered weekly news syntheses. Users configure their topics, categories, and preferred LLM provider, then the app searches the web, validates sources, and produces structured summaries. ## Architecture - **Backend**: Rust (Axum) — `backend/` - **Frontend**: SolidJS + Tailwind CSS v4 — `frontend/` - **Database**: PostgreSQL (via sqlx with runtime-checked queries) - **Deployment**: Docker only (`docker-compose.yml`) ## Project Structure ``` ai_synth/ ├── backend/ Rust/Axum backend │ ├── src/ │ │ ├── main.rs Entry point, CLI (serve, create-admin) │ │ ├── router.rs All API routes + middleware stack │ │ ├── handlers/ HTTP handlers (auth, settings, sources, syntheses, admin, etc.) │ │ ├── services/ Business logic (auth, email, encryption, scraper, LLM providers, synthesis pipeline) │ │ ├── db/ Database queries (sqlx) │ │ ├── models/ Data types + validation │ │ ├── middleware/ Auth session extraction, CSRF check │ │ └── util/ Token generation, hashing │ ├── migrations/ SQL migrations (9 files) │ ├── tests/ Integration tests (require Postgres) │ ├── Cargo.toml │ └── Dockerfile Multi-stage build ├── frontend/ SolidJS frontend │ ├── src/ │ │ ├── App.tsx Router, layouts, route guards │ │ ├── pages/ Login, Register, Home, Settings, Sources, GenerateSynthesis, SynthesisDetail, admin/* │ │ ├── components/ Navbar, Layout, AdminLayout, Turnstile, ApiKeyManager, ui/* │ │ ├── api/ API clients (auth, settings, sources, syntheses, admin, config, apiKeys) │ │ ├── contexts/ AuthContext (session-based) │ │ ├── i18n/ French translations (i18n-ready for future languages) │ │ └── utils/ SSE client, date formatting, provider info │ ├── package.json │ └── vite.config.ts SolidJS + Tailwind + dev proxy ├── docs/ Analysis reports + implementation plans ├── docker-compose.yml App + Postgres ├── .env.example All required env vars documented └── CLAUDE.md This file ``` ## Key Features - **Authentication**: Email + magic link (passwordless), Cloudflare Turnstile captcha, 30-day session cookies - **LLM Providers**: Google Gemini, OpenAI, Anthropic — users bring their own API keys - **Generation Pipeline**: 2-pass (search with web grounding → scrape/validate URLs → rewrite summaries), adaptive per provider - **Admin Module**: Provider/model curation, rate limit config, user management - **Security**: AES-256-GCM encryption for API keys at rest, SSRF prevention in scraper, CSRF via X-Requested-With, HttpOnly/SameSite cookies - **Export**: Email via Resend, PDF, Markdown - **Real-time**: SSE for generation progress streaming ## Running Locally ### Docker (production-like) ```bash cp .env.example .env # Fill in values docker compose up ``` ### Development ```bash # Backend (requires Postgres running) cd backend && cargo run -- serve # Frontend (proxies /api to backend) cd frontend && npm install && npm run dev ``` ### CLI ```bash # Create first admin user cd backend && cargo run -- create-admin admin@example.com ``` ## Testing ```bash # Backend unit tests (no Postgres needed) cd backend && cargo test --lib # Backend integration tests (requires Postgres) TEST_DATABASE_URL=postgres://user:pass@localhost:5432/postgres cargo test # Frontend unit tests cd frontend && npx vitest run # Frontend type check cd frontend && npx tsc --noEmit ``` ## API Endpoints ### Public - `POST /api/v1/auth/register` — create account + magic link - `POST /api/v1/auth/login` — request magic link - `GET /api/v1/auth/verify` — verify token (email click) - `POST /api/v1/auth/verify` — verify token (frontend API) - `GET /api/v1/health` — health check ### Authenticated - `GET/PUT /api/v1/settings` — user settings - `GET/POST/DELETE /api/v1/sources` — sources CRUD + bulk/CSV import/export - `GET/DELETE /api/v1/syntheses/:id` — syntheses CRUD - `POST /api/v1/syntheses/generate` — trigger async generation - `GET /api/v1/syntheses/generate/:job_id/progress` — SSE progress stream - `POST /api/v1/syntheses/:id/send-email` — email synthesis - `GET /api/v1/syntheses/:id/export/markdown` — Markdown download - `GET /api/v1/syntheses/:id/export/pdf` — PDF download - `GET/POST/DELETE /api/v1/user/api-keys` — LLM API key management - `GET /api/v1/config/providers` — available providers/models ### Admin Only - `GET/POST/PUT/DELETE /api/v1/admin/providers` — provider/model config - `GET/PUT /api/v1/admin/rate-limits` — rate limit config - `GET /api/v1/admin/users` — user list - `PUT /api/v1/admin/users/:id/role` — role management ## Database (11 migrations) Tables: `users`, `sessions`, `magic_link_tokens`, `user_settings`, `sources`, `syntheses`, `admin_providers`, `admin_rate_limits`, `user_api_keys`, `audit_log` ## Environment Variables See `.env.example` for the complete list. Key ones: - `DATABASE_URL` — Postgres connection string - `MASTER_ENCRYPTION_KEY` — 64 hex chars for AES-256-GCM - `SESSION_SECRET` — at least 64 chars - `RESEND_API_KEY` — for email sending - `TURNSTILE_SECRET_KEY` / `TURNSTILE_SITE_KEY` — captcha - `APP_URL` — public URL (for CORS, magic links, cookies) ## Design Decisions - Idiomatic Rust (learning project) — no unwrap() in production code - Users bring their own LLM API keys (encrypted at rest) - Admin curates available providers/models, users select from the list - Single-tenant self-hosted (one instance per deployment) - i18n-ready (French only for now, all strings in `frontend/src/i18n/fr.ts`) - Adaptive generation pipeline: skips scrape+rewrite when native web grounding is sufficient