Files
ad/ARCHITECTURE.md

3.3 KiB

Architecture

Layers

  • 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

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.

Data flows

  • 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.