feat: phase 1 public site skeleton — layout, routes, CSS, logo pipeline
Ship base Jinja layout (header/nav/main/footer with skip link and aria-current), mobile-first single-file CSS using the ROADMAP palette tokens, and four public routes: /, /about, /contact, /shop. Blog index renders via a stable PostService.list_published() stub returning [] — Phase 2 only swaps the body. About is static placeholder copy, /contact ships an inert form plus a mailto: link driven by ADMIN_CONTACT_EMAIL, /shop shows a "Coming soon" card. Adds a Pillow-based scripts/generate_static_assets.py producing resized logo PNG + WebP, multi-size favicon.ico, and a 180x180 apple-touch-icon on a cream background. Outputs committed for a reproducible build. Also ship docs/MANUAL_TESTING.md with per-route / responsive / a11y / static- asset checklists, and mark Phase 1 complete in docs/ROADMAP.md.
This commit is contained in:
45
app/models/posts.py
Normal file
45
app/models/posts.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Blog post domain models.
|
||||
|
||||
Phase 1 only needs the *list-view* projection of a post — a minimal
|
||||
immutable record sufficient to render a blog card on the home page.
|
||||
Phase 2 will introduce the richer persisted :class:`Post` dataclass that
|
||||
mirrors the SQLite schema; :class:`PostSummary` intentionally stays as a
|
||||
narrower DTO even after the DB arrives because list endpoints shouldn't
|
||||
pay the cost of loading full post bodies.
|
||||
|
||||
The dataclass is frozen: summaries flow one-way from the service layer
|
||||
into templates and must never mutate mid-request.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PostSummary:
|
||||
"""Immutable summary row for the blog index.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
slug:
|
||||
URL-safe identifier used to build the post's canonical URL.
|
||||
title:
|
||||
Human-readable headline shown on the card.
|
||||
published_at:
|
||||
Timezone-aware UTC publish timestamp. Templates format this for
|
||||
display; storing a real :class:`datetime` (rather than a
|
||||
pre-formatted string) keeps locale/formatting concerns in the
|
||||
view layer.
|
||||
excerpt:
|
||||
Short plaintext teaser. The service layer is responsible for
|
||||
producing a sanitized, already-truncated excerpt so the template
|
||||
can render it without additional escaping beyond Jinja's default
|
||||
HTML autoescape.
|
||||
"""
|
||||
|
||||
slug: str
|
||||
title: str
|
||||
published_at: datetime
|
||||
excerpt: str
|
||||
Reference in New Issue
Block a user