You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
6.0 KiB
Markdown

# 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 (25 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