5.2 KiB
CLAUDE.md — KMountain Flower Radio Station
Project overview
A three-tier web app for a community radio station: Angular 21 frontend, FastAPI backend, PostgreSQL database. Renders a hero page, about page, broadcast schedule, upcoming events, and a contact form — styled with a warm mountain/nature color palette (blue primary + orange accent on cream). Donations route to an externally configured URL.
Code style
- Components: Standalone only (no NgModules). Every component is a single file with
@Componentdecorator,importsarray, and four standard files (.ts,.html,.scss, component). - Data patterns: Page content (programs, events) is stored in PostgreSQL and served via the FastAPI backend. The frontend uses HTTP services (src/app/services/) to fetch data at runtime. To add or modify content, interact with the API or run
seed.py— never hardcode content in components. - SCSS: All design tokens go in src/styles/_variables.scss. Mixins in _mixins.scss. CSS custom properties exposed in _themes.scss. Components import via
@use 'variables'(no relative path needed). - Routes: Defined in src/app/app.routes.ts using
loadComponent()lazy loading. Add new pages here after creating the component. - TypeScript: Strict mode enabled. Use
readonlyfor constants (config, URLs). Keep data shapes close to their components where used only once.
Architecture
See README.md for a full architecture map. Key files:
src/app/app.routes.ts— all frontend routingsrc/environments/environment.ts— dev API URL (http://localhost:8000)src/environments/environment.prod.ts— prod API URL ('', proxied through Nginx)src/styles/_variables.scss— color palette, spacing, breakpoints (change these to re-skin)src/styles/_mixins.scss— card-style, button-style, responsive, gradient-text, flowery-border, fade-insrc/styles/_themes.scss— SCSS → CSS custom property mappingdocker-compose.dev.yml— dev-sidecar compose for VS Code dev containerbackend/app/main.py— FastAPI entry point (CORS, router registration)backend/app/models.py— SQLAlchemy ORM (Show, Event, StationConfig)
Admin dashboard — visitor stats (implemented)
Nginx access logs → shared volume → FastAPI log parser → PostgreSQL → admin dashboard. No extra services.
How it works:
- Nginx writes structured JSON access logs to daily log files on a shared mount:
/logs/access-YYYY-MM-DD.log. Amapdirective in nginx.conf.template extracts the date from the ISO timestamp, so Nginx rotates to a new file at midnight. - FastAPI (backend/app/log_parser.py) maintains a "last parsed date" (at most yesterday) persisted in PostgreSQL via the
LogParseStatemodel. On each run it scans/logs/for unprocessed daily files, enriches entries with IP geolocation (MaxMind GeoLite2 City), and stores aggregates. - Aggregates are persisted in
visitor_stats_daily(unique visitors per day) andvisitor_stats_geo(country/city breakdown) tables. - Admin dashboard (Angular) reads from stats API endpoints in backend/app/api/stats.py.
Key files:
backend/app/log_parser.py— log file parsing + geo enrichment +process_unparsed_logs()backend/app/api/stats.py— stats API endpoints (summary, timeseries, geo, parse trigger)backend/app/models.py—VisitorStatDaily,VisitorStatGeo,LogParseStateORM modelsbackend/inject_test_logs.py— test data injector (writes test log entries for yesterday)nginx.conf.template— JSON log format (kmtn_json) with daily rotation via$log_datemapbackend/geo/GeoLite2-City.mmdb— MaxMind geolocation database
Why daily files: The currently-being-written file is never read — no partial lines, no race conditions, no file locking. Stats have a minimum one-day lag.
Deployment context: Single-node minikube, hostPath PersistentVolume with ReadWriteMany, or docker-compose named volume mounted in both pods.
Injecting test data: Run backend/inject_test_logs.py (or the inline docker compose command in README.md) to write test log entries for yesterday, then trigger parsing via GET /api/parse.
Common tasks
- Re-skin: Edit colors in
_variables.scssonly. - Add a page: Create component folder → add route in
app.routes.ts→ add nav link (if needed). - Update content (dev): Run the API, then use Swagger UI at http://localhost:8000/docs to create/update content.
- Update content (seed): Edit backend/seed.py and restart the API (autoseed runs on empty DB), or hit
curl -X POST http://localhost:8000/api/admin/resetto re-seed. - Run the app (dev): From the
backend/directory, setKMTN_DATABASE_URL=sqlite+aiosqlite:///./kmountain.db, thenuvicorn app.main:app --reload+ng servein parallel. Autoseed populates data on first run. - Run the app (dev container): Reopen in Container via VS Code.
- Build:
ng build→dist/.