Translate the whole site to English: English routes with 301 redirects from the Dutch paths, English UI, docs, seeds and captions, one-time live content migration

This commit is contained in:
2026-10-05 11:25:41 +02:00
parent bbdecdc95b
commit ff547e1677
153 changed files with 1095 additions and 774 deletions
+20 -19
View File
@@ -1,27 +1,28 @@
<!-- retoor <retoor@molodetz.nl> -->
# Architectuur
# Architecture
## Lagen
## Layers
- `molodetz/main.py`: FastAPI-object (`/swagger`, `/openapi.json`), lifespan (init-lock, `init_db`, bootstrap, services met service-lock), statische mounts (uploads, versioned `/static/v<STATIC_VERSION>/`, fallback), middleware in templatevolgorde, exception handlers, routers.
- `molodetz/config.py`: enige plek voor `load_dotenv`, paden, env-bindingen met prefix `MOLODETZ_`, `APP_VERSION` via `tomllib`, `BOOT_ID`, `STATIC_VERSION`.
- `molodetz/database/`: synchrone `dataset`-handle (SQLAlchemy NullPool, WAL-PRAGMA's), per-tabel modules, soft delete met registries, `init_db` met indexen en `ANALYZE`. Geen async driver en geen threadpool rond databasecalls: de synchrone laag wordt direct in async handlers aangeroepen (sectie 7.1). Alleen CPU- en bestandswerk (PBKDF2, archiefbouw, DNS) gaat via `asyncio.to_thread`.
- `molodetz/routers/`: een module per oppervlak. Elke HTML-route geeft JSON bij `Accept: application/json` via `responses.respond` en een Pydantic-uitvoerschema uit `schemas/`.
- `molodetz/templating.py`: de enige `Jinja2Templates`. Globals `static_url`, `local_dt`, `avatar_url`, `render_content` en meer.
- `molodetz/rendering.py`: server-side markdown (mistune, escape aan), emoji, URL-allowlist, streepjesnormalisatie. Gepubliceerde inhoud wordt altijd server-side gerenderd.
- `molodetz/stealth.py`: enige fabriek voor uitgaande HTTP-clients (httpx met curl_cffi-transport, `net_guard` SSRF-bescherming). `link_check.py` gebruikt hem voor de repo-linkcontrole bij aanmeldingen.
- `molodetz/services/`: achtergrondservices (presence, housekeeping, backup) onder een `ServiceManager`, zichtbaar en bestuurbaar via `/admin/services`.
- `molodetz/gallery.py`: flyers en memes uit `static/media/`, Pillow-thumbnails (webp) als blobs onder `data/uploads`, perceptuele hash (imagehash) tegen duplicaten.
- `molodetz/cli/`: argparse-CLI `molodetz`.
- `molodetz/static/js/`: ES6-modules zonder bundler. `Application.js` laadt `components/index.js` (custom elements in light DOM) en zet singletons op `window.app`. Vendor: marked, DOMPurify, highlight.js als ES-modules in `static/vendor/`.
- `molodetz/static/css/`: handgeschreven, tokens in `variables.css`, breakpoints exact 360/480/768/1024.
- `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.
## Auth
Sessiecookie (HttpOnly, SameSite=Lax, Secure achter TLS), rijen in `sessions`. Daarnaast `X-API-KEY`, `Authorization: Bearer <api key>` en Basic voor scripts. Wachtwoorden met passlib PBKDF2-SHA256. Geen JWT, geen OAuth. De eerste gebruiker is de beheerder uit `.env`; er is geen publieke registratie.
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.
## Datastromen
## Data flows
- Schrijven: beheerder -> `/admin/posts` -> `content.create_post` / `edit_post` -> SQLite -> server-render bij lezen (lru-cache op tekst).
- Aanmelden: bezoeker -> `POST /binnen` (honeypot, rate limit) -> `join_requests` -> melding aan beheerders -> `/admin/joins`.
- Galerij: start -> `sync_gallery` -> `media`-tabel + thumbnails -> `/flyers`, `/memes` met lightbox.
- 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.