Files
volumen/docs/architecture.md
petrbalvin 3b8ed31f67
Test / test (push) Successful in 1m7s
Release / release (push) Successful in 1m11s
chore: prepare release v0.4.0
2026-07-26 18:15:29 +02:00

3.6 KiB

Architecture

Status: the core library, the public JSON API, and the server-rendered admin (login, CSRF, post CRUD, Markdown editor + live preview) are implemented.

volumen is a small, file-based Markdown blog engine written in Python. It has no database: every post is a .md file on disk with a TOML frontmatter block. The engine reads those files, exposes them as a JSON API, and ships a server-rendered admin for editing them.

Design goals

  • File-based — posts are plain .md files, safe to commit to Git and edit by hand or through the admin.
  • Self-contained — runs as a single Python process (Uvicorn) under FastAPI; no Node build step is required to operate the engine or its admin.
  • Dependency-light — a small, well-chosen set of PyPI packages (FastAPI, python-markdown, Jinja2, itsdangerous, python-multipart) rather than a full-stack framework. The standard library covers cryptography.
  • Markdown-first — the visual editor round-trips through the same renderer that powers the public API, so stored content is always Markdown.

Components

src/volumen/
  __init__.py        # VERSION + in-memory login-attempt rate limiter
  cli.py             # argparse entry point: serve, hash-password, version
  config.py          # loads config.toml via stdlib tomllib, deep-merges defaults
  frontmatter.py     # parse/serialise the `+++ … +++` TOML block
  post.py            # Post model: metadata + raw body + rendered HTML
  store.py           # reads the content directory into Post objects (mtime-cached)
  markdown.py        # python-markdown + pymdown-extensions wrapper, <figure>/<figcaption>
  feed_helpers.py    # RSS / JSON Feed / sitemap generation
  users.py           # users.toml + admin/author roles
  password.py        # scrypt hashing/verification (hashlib + secrets.compare_digest)
  api_routes.py      # /api/volumen/* (FastAPI APIRouter)
  admin_routes.py    # /admin/* + /media/* (FastAPI APIRouter)
  server.py          # create_app factory, middleware, dependency helpers
  web/
    templates/       # Jinja2 templates for the admin
    static/          # static assets (volumen-logo.svg, volumen-icon.svg)
exe/volumen          # thin shim that calls volumen.cli:main

Request flow

graph TD
  A[HTTP request] --> B{Route}
  B -->|/api/volumen/*| C[api_routes.py]
  B -->|/admin/*| D[admin_routes.py + CSRF + CSP]
  B -->|/media/*| E[admin_routes.py media_router]
  C --> F[Store]
  D --> F
  D --> U[Users]
  C --> H[FeedHelpers]
  C --> G[Markdown renderer]
  F --> I[(content_dir/*.md)]
  F --> J[(content_dir/media/*)]
  U --> K[(users.toml)]
  H --> F
  G --> L[<figure> for titled images]
  D --> M[SessionMiddleware + CSPMiddleware]
  C --> M
  E --> M

Middleware stack

  • SessionMiddleware (Starlette) — signed cookie session, signed with itsdangerous using the secret derived from [admin].session_key. Set to HttpOnly and SameSite=Strict; Secure is added by SecureCookieMiddleware when the request was forwarded over HTTPS.
  • SecureCookieMiddleware (custom) — inspects X-Forwarded-Proto (when [server].trust_proxy = true) and exposes request.state.session_secure for downstream code that needs to know whether the request is HTTPS.
  • CSPMiddleware (custom) — injects the Content-Security-Policy header on every /admin/* response.

Public site integration

The public website (e.g. petrbalvin.org, a Vue 3 + Vite + Tailwind SPA) consumes volumen's JSON API. volumen does not render the public site itself; it is headless for the front-end and only renders its own admin.