Files
chicken_babies_site/CLAUDE.md
Phillip Tarrant a376207243 chore: move code_guidelines and security under docs/
Keeps repo root lean: CLAUDE.md is the only doc at root. All
reference/architecture material lives under docs/.

Also updates all cross-references in CLAUDE.md, docs/README.md,
and the FastAPI override note in code_guidelines.md so links stay
valid after the move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:24:54 -05:00

5.6 KiB

Chicken Babies R Us — Project Instructions

Small farm website for Chicken Babies R Us (Morrison TN — address is intentionally not displayed publicly). Public brochure site with a blog-style home, About, Contact, and a disabled "Shop (coming soon)" placeholder. The owner's wife, Head Hen, edits all content through an admin area protected by email magic-link authentication.

This file is authoritative for this project. Where it conflicts with docs/code_guidelines.md, this file wins.


Stack (authoritative for this project)

Layer Choice
Language Python 3.12
Web framework FastAPI (overrides the Flask default in docs/code_guidelines.md)
Templates Jinja2
ASGI server Uvicorn, behind Caddy reverse proxy
Database SQLite (WAL mode) — single-writer is fine at this scale
Cache In-process TTL cache + row-level rendered-HTML cache (no Redis)
Email Resend (contact form + magic-link auth)
Anti-spam hCaptcha + honeypot + SlowAPI rate limits
Logging structlog
Container Docker (multi-stage), target = Debian 12 VM on home server
Repo host Gitea (Actions for image build/publish wired later)

Deployment Topology

Internet
  → Cloudflare (proxied DNS)
  → OPNsense firewall (inbound 443 allowed only from Cloudflare IP ranges)
  → Virtual IP
  → Debian 12 VM
  → Caddy (TLS termination)
  → Uvicorn + FastAPI container

The app MUST trust X-Forwarded-For / X-Forwarded-Proto only from Caddy's IP. Run Uvicorn with --proxy-headers --forwarded-allow-ips=<caddy-ip>. Never read X-Forwarded-* in application code directly — let Starlette's ProxyHeadersMiddleware do it.

Target Repository Layout (after Phase 0)

/                       repo root
├── app/                FastAPI application package
│   ├── main.py         app factory + startup
│   ├── config.py       typed config loader (pydantic-settings)
│   ├── models/         dataclasses + SQL schema + migrations
│   ├── routes/         public_router.py, admin_router.py, auth_router.py
│   ├── services/       auth, email, cache, markdown, media, hcaptcha
│   ├── templates/      Jinja2 (public/ + admin/ + emails/)
│   └── static/         CSS, JS, site images, logo
├── data/               runtime (SQLite DB + uploads) — mounted volume in prod
├── docs/               business + architecture docs (NO application code)
│   ├── README.md
│   ├── ROADMAP.md
│   ├── code_guidelines.md
│   ├── security.md
│   └── MANUAL_TESTING.md  (added in Phase 1)
├── tests/              pytest
├── Logo/               brand assets (source)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── .env.example
├── .gitignore
└── CLAUDE.md

Security Must-Haves (in addition to docs/security.md)

  • SQL: parameterized statements only (sqlite3 ? placeholders or SQLAlchemy Core bind params). Never f-string a query.
  • Markdown: hardened pipeline markdown-it-pybleach allowlist. No raw HTML pass-through.
  • Image uploads: validate magic bytes with python-magic, cap at 8 MB, re-encode through Pillow, store under a random filename, discard the client-supplied extension.
  • CSRF: double-submit cookie on every admin POST / PUT / DELETE.
  • Cookies: Secure, HttpOnly, SameSite=Lax; session IDs signed with itsdangerous.
  • Security headers (middleware): strict nonce-based CSP, HSTS, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy.
  • Magic-link tokens: 256-bit random (secrets.token_urlsafe(32)), stored hashed (SHA-256), single-use, 15-minute expiry. IP is logged but not enforced (mobile roaming).
  • Rate limits on auth endpoints: 5 requests / 15 min / IP and / email.
  • Admin allowlist: only addresses in ADMIN_EMAILS env var may request a magic link. Any other address silently succeeds (no user enumeration) but sends no email.
  • Secrets: env / .env only, never committed. .env.example is the public contract.
  • Audit logging: every auth event (link requested, link consumed, session created/revoked, rate-limit hit) at INFO. Never log raw tokens or email bodies.

How to Run (dev)

python3.12 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # fill RESEND_API_KEY, HCAPTCHA_*, ADMIN_EMAILS, SECRET_KEY
uvicorn app.main:app --reload

Docker (parity with prod):

docker compose up --build

Testing

  • pytest for auth, magic-link lifecycle, markdown sanitization, rate limits, contact form.
  • Never mock the DB in auth/magic-link tests — use a temp SQLite file so behavior matches prod.
  • Manual test checklist lives in docs/MANUAL_TESTING.md (added in roadmap Phase 1).

Git Strategy (this project)

  • Branches: master (prod) · dev (integration) · feat/*, chore/*, docs/*, fix/* (work branches).
  • Work branches off dev. Local --no-ff merge into dev, then push dev. Promote devmaster with --no-ff for releases. Tag master with vX.Y.Z.
  • Conventional commits: feat:, fix:, chore:, docs:, refactor:, test:.
  • Do not push directly to master. Do not force-push shared branches.

Pointers

  • Generic Python standards: docs/code_guidelines.md (FastAPI overrides its Flask default)
  • Security baseline: docs/security.md
  • Phased roadmap, dataclasses, SQL schema, visual design: docs/ROADMAP.md
  • Docs folder guide: docs/README.md