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:
37
app/main.py
37
app/main.py
@@ -8,13 +8,26 @@ via ``app.main:app``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from app import __version__
|
||||
from app.config import get_settings
|
||||
from app.logging_config import configure_logging
|
||||
from app.routes.health import router as health_router
|
||||
from app.routes.public import router as public_router
|
||||
|
||||
|
||||
# Resolve the package root once so template / static paths stay correct
|
||||
# regardless of the current working directory at startup (running under
|
||||
# uvicorn from the repo root vs. pytest from anywhere vs. inside Docker).
|
||||
_PACKAGE_ROOT: Path = Path(__file__).resolve().parent
|
||||
_TEMPLATES_DIR: Path = _PACKAGE_ROOT / "templates"
|
||||
_STATIC_DIR: Path = _PACKAGE_ROOT / "static"
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
@@ -24,7 +37,11 @@ def create_app() -> FastAPI:
|
||||
- Load validated configuration via :func:`get_settings`.
|
||||
- Initialize structured logging *before* any logger is used.
|
||||
- Instantiate FastAPI with canonical title + version.
|
||||
- Register routers (Phase 0: only ``/healthz``).
|
||||
- Mount the ``/static`` directory for CSS, JS, and image assets.
|
||||
- Attach the shared :class:`Jinja2Templates` to ``app.state`` so route
|
||||
dependencies can retrieve it without a circular import on this
|
||||
module.
|
||||
- Register routers (Phase 1: health + public).
|
||||
- Emit a single ``app_started`` structured log event.
|
||||
"""
|
||||
# Parse + validate configuration first so a bad environment fails fast
|
||||
@@ -44,9 +61,27 @@ def create_app() -> FastAPI:
|
||||
redoc_url="/redoc",
|
||||
)
|
||||
|
||||
# Serve CSS, images, and other static assets from app/static. The
|
||||
# `check_dir=False` would let Starlette skip the existence check; we
|
||||
# leave it at its default so a missing directory surfaces loudly in
|
||||
# dev. In prod the directory is baked into the container image.
|
||||
application.mount(
|
||||
"/static",
|
||||
StaticFiles(directory=_STATIC_DIR),
|
||||
name="static",
|
||||
)
|
||||
|
||||
# Single shared Jinja2 environment. Storing it on ``app.state`` keeps
|
||||
# route modules free of an import dependency on this module (which
|
||||
# would be circular once admin/auth routers are added in later
|
||||
# phases). Route handlers pull it via a ``Depends(get_templates)``
|
||||
# function defined next to the routes.
|
||||
application.state.templates = Jinja2Templates(directory=_TEMPLATES_DIR)
|
||||
|
||||
# Register routers. Kept explicit (no dynamic discovery) so the set of
|
||||
# mounted endpoints is trivially auditable.
|
||||
application.include_router(health_router)
|
||||
application.include_router(public_router)
|
||||
|
||||
# Single structured startup event. Do NOT include secret material.
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
Reference in New Issue
Block a user