feat: phase 2 content model + cache — SQLite schema, markdown, TTL

Stand up the full SQLite content layer: all 7 tables from the authoritative
schema with WAL + foreign-keys enforced per-connection, entity dataclasses
plus row mappers, hand-rolled versioned migrations tracked in
schema_migrations, and an idempotent Python seed (system user + welcome
post + About page).

Add a Markdown->HTML service using markdown-it-py with a strict bleach
allowlist (tables intentionally omitted on both sides). Add a typed
in-process TTLCache[K,V] and wire it into real DB-backed PostService and
PageService, both exposing invalidate_all() for Phase 4 admin writes.

Rewire / and /about to read from the DB; homepage renders the seeded
welcome post, About renders page.title + sanitized body_html_cached.
Update the Phase 1 route tests accordingly.

Mark Phase 2 complete in docs/ROADMAP.md.
This commit is contained in:
2026-04-21 15:40:35 -05:00
parent 28168f57b6
commit 0306f71763
21 changed files with 2055 additions and 108 deletions

View File

@@ -1,42 +1,37 @@
{#
About page — static placeholder copy. Head Hen will replace this via
the Phase 4 admin CMS, so the prose below is deliberately generic and
free of lorem ipsum. Per CLAUDE.md, the physical address is not shown
anywhere on the site — only the town name.
About page. Phase 2: body comes from the ``pages`` row with
slug='about', rendered via the Markdown pipeline (markdown-it-py →
bleach allowlist) at write time and cached on the row. The cached
HTML has already been sanitized against an allowlist that forbids
scripts, styles, iframes, etc., so it is safe to emit with the
``| safe`` filter (Jinja autoescape is explicitly disabled for the
body only). Head Hen edits this content through the Phase 4 admin.
Per CLAUDE.md, the physical address is not shown anywhere on the
site — only the town name.
Context:
- page : app.models.entities.Page
- active_nav : str "about"
#}
{% extends "public/base.html" %}
{% block title %}About — Chicken Babies R Us{% endblock %}
{% block title %}{{ page.title }} — Chicken Babies R Us{% endblock %}
{% block meta_description %}About Chicken Babies R Us — a small family farm in Morrison, Tennessee raising chickens, ducks, and geese.{% endblock %}
{% block content %}
<article class="page-article">
<header class="page-article__header">
<h1 class="page-article__title">About the farm</h1>
<h1 class="page-article__title">{{ page.title }}</h1>
</header>
<p>
Chicken Babies R Us is a small family farm tucked into the rolling
hills of Morrison, Tennessee. What started as a handful of chicks
in a backyard brooder has grown into a flock of chickens, ducks, and
geese that keep us busy (and entertained) year round.
</p>
<p>
The operation is run by Head Hen &mdash; the chief wrangler, egg
gatherer, waterfowl-whisperer, and unofficial chicken photographer.
She handles the day-to-day care of the birds and does most of the
writing you'll find on this site. Expect updates on hatching plans,
new arrivals, the occasional coop mishap, and whatever the geese
decided to get into this week.
</p>
<p>
We're a hobby farm at heart, not a commercial one, which means we
can take the time to know our birds and raise them the way we think
they ought to be raised. If you're curious about what we've got
going on &mdash; or just want to say hello &mdash; pop over to the
contact page.
</p>
{#
body_html_cached is the output of the bleach-sanitized
Markdown pipeline. It contains only tags / attributes /
protocols from our allowlist (p, strong, em, a, ul, ol, li,
h1-h4, blockquote, code, pre, img, hr + href/src/etc.), so
rendering with ``| safe`` does not reintroduce XSS risk.
#}
{{ page.body_html_cached | safe }}
</article>
{% endblock %}