Files

29 lines
3.3 KiB
Markdown
Raw Permalink Normal View History

2026-10-05 09:36:20 +02:00
<!-- retoor <retoor@molodetz.nl> -->
# Architecture
2026-10-05 09:36:20 +02:00
## Layers
2026-10-05 09:36:20 +02:00
- `molodetz/main.py`: FastAPI object (`/swagger`, `/openapi.json`), lifespan (init lock, `init_db`, bootstrap, services with service lock), static mounts (uploads, versioned `/static/v<STATIC_VERSION>/`, fallback), middleware in template order, exception handlers, routers.
- `molodetz/config.py`: the only place for `load_dotenv`, paths, env bindings with prefix `MOLODETZ_`, `APP_VERSION` via `tomllib`, `BOOT_ID`, `STATIC_VERSION`.
- `molodetz/database/`: synchronous `dataset` handle (SQLAlchemy NullPool, WAL PRAGMAs), per-table modules, soft delete with registries, `init_db` with indexes and `ANALYZE`. No async driver and no threadpool around database calls: the synchronous layer is called directly from async handlers (section 7.1). Only CPU and file work (PBKDF2, archive building, DNS) goes through `asyncio.to_thread`.
- `molodetz/routers/`: one module per surface. Every HTML route returns JSON on `Accept: application/json` through `responses.respond` and a Pydantic output schema from `schemas/`. `routers/legacy.py` answers the old Dutch paths with 301 (308 for POST), hidden from the schema.
- `molodetz/templating.py`: the only `Jinja2Templates`. Globals `static_url`, `local_dt`, `avatar_url`, `render_content` and more.
- `molodetz/rendering.py`: server-side markdown (mistune, escaping on), emoji, URL allowlist, dash normalisation. Published content is always rendered on the server.
- `molodetz/stealth.py`: the only factory for outbound HTTP clients (httpx with curl_cffi transport, `net_guard` SSRF protection). `link_check.py` uses it for the repo link check on join requests.
- `molodetz/services/`: background services (presence, housekeeping, backup) under a `ServiceManager`, visible and controllable at `/admin/services`.
- `molodetz/gallery.py`: flyers and memes from `static/media/`, Pillow thumbnails (webp) as blobs under `data/uploads`, perceptual hash (imagehash) against duplicates.
- `molodetz/cli/`: argparse CLI `molodetz`.
- `molodetz/static/js/`: ES6 modules without a bundler. `Application.js` loads `components/index.js` (custom elements in light DOM) and puts singletons on `window.app`. Vendor: marked, DOMPurify, highlight.js as ES modules in `static/vendor/`.
- `molodetz/static/css/`: handwritten, tokens in `variables.css`, breakpoints exactly 360/480/768/1024.
2026-10-05 09:36:20 +02:00
## Auth
Session cookie (HttpOnly, SameSite=Lax, Secure behind TLS), rows in `sessions`. Also `X-API-KEY`, `Authorization: Bearer <api key>` and Basic for scripts. Passwords with passlib PBKDF2-SHA256. No JWT, no OAuth. The first user is the administrator from `.env`; there is no public registration.
2026-10-05 09:36:20 +02:00
## Data flows
2026-10-05 09:36:20 +02:00
- Writing: administrator -> `/admin/posts` -> `content.create_post` / `edit_post` -> SQLite -> server render on read (lru cache on text).
- Joining: visitor -> `POST /join` (honeypot, rate limit) -> `join_requests` -> notification to administrators -> `/admin/joins`.
- Gallery: startup -> `sync_gallery` -> `media` table + thumbnails -> `/flyers`, `/memes` with lightbox.
- Language: startup -> `bootstrap.migrate_to_english` (once, guarded by setting `content_language`) -> seeded posts, topics, tagline, maintenance message, notifications and admin bio translated in place. Old post slugs resolve through their uid tail and redirect with 301.