3.6 KiB
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
.mdfiles, 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 withitsdangeroususing the secret derived from[admin].session_key. Set toHttpOnlyandSameSite=Strict;Secureis added bySecureCookieMiddlewarewhen the request was forwarded over HTTPS.SecureCookieMiddleware(custom) — inspectsX-Forwarded-Proto(when[server].trust_proxy = true) and exposesrequest.state.session_securefor downstream code that needs to know whether the request is HTTPS.CSPMiddleware(custom) — injects theContent-Security-Policyheader 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.