From 6fc00b8ce97714c9749128b6ac5b4205119f3880 Mon Sep 17 00:00:00 2001 From: kfj001 Date: Thu, 18 Jun 2026 04:31:37 +0000 Subject: [PATCH] updated 3 tier structure --- .claude/CLAUDE.md | 24 +-- .devcontainer/Dockerfile | 6 +- .env.example | 9 ++ .gitignore | 3 + README.md | 322 ++++++++++++++++++++++++++------------- docker-compose.dev.yml | 53 +++++++ docker-compose.yml | 4 + trans_to_3_tier.md | 243 ----------------------------- 8 files changed, 307 insertions(+), 357 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.dev.yml delete mode 100644 trans_to_3_tier.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index cbec2f0..0dcf2f0 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -2,29 +2,35 @@ ## Project overview -This is a standalone-component Angular 21 app for a community radio station website. It renders a hero page, about page, broadcast schedule, donation tiers, upcoming events, and a contact form — all styled with a warm mountain/nature color palette (blue primary + orange accent on cream). +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, donation tiers, upcoming events, and a contact form — styled with a warm mountain/nature color palette (blue primary + orange accent on cream). ## Code style -- **Components:** Standalone only (no NgModules). Every component is a single file with `@Component` decorator, `imports` array, and the four standard files (`.ts`, `.html`, `.scss`, component). -- **Data patterns:** Page content lives as `readonly` arrays with interfaces defined in the same file. Never put content in services or APIs — this is a static site. +- **Components:** Standalone only (no NgModules). Every component is a single file with `@Component` decorator, `imports` array, and four standard files (`.ts`, `.html`, `.scss`, component). +- **Data patterns:** Page content (programs, events, tiers) is stored in PostgreSQL and served via the FastAPI backend. The frontend uses HTTP services ([src/app/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](src/styles/_variables.scss). Mixins in [_mixins.scss](src/styles/_mixins.scss). CSS custom properties exposed in [_themes.scss](src/styles/_themes.scss). Components import via `@use 'variables'` (no relative path needed). - **Routes:** Defined in [src/app/app.routes.ts](src/app/app.routes.ts) using `loadComponent()` lazy loading. Add new pages here after creating the component. -- **TypeScript:** Strict mode enabled. Use `readonly` for data arrays. No unnecessary interfaces — keep data shapes close to their components. +- **TypeScript:** Strict mode enabled. Use `readonly` for constants (config, URLs). Keep data shapes close to their components where used only once. ## Architecture -See [README.md](../README.md) for a full architecture map. Key files: +See [README.md](README.md) for a full architecture map. Key files: -- `src/app/app.routes.ts` — all routing +- `src/app/app.routes.ts` — all frontend routing +- `src/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-in - `src/styles/_themes.scss` — SCSS → CSS custom property mapping +- `docker-compose.yml` — 3-service stack (db, api, frontend) +- `backend/app/main.py` — FastAPI entry point (CORS, router registration) +- `backend/app/models.py` — SQLAlchemy ORM (Program, Event, DonationTier) ## Common tasks - **Re-skin:** Edit colors in `_variables.scss` only. - **Add a page:** Create component folder → add route in `app.routes.ts` → add nav link (if needed). -- **Update content:** Edit the `readonly` array in the relevant component file (`schedule.component.ts`, `events.component.ts`, `donate.component.ts`). -- **Run the app:** `ng serve` → [localhost:4200](http://localhost:4200). -- **Build:** `ng build` → `dist/`. +- **Update content (dev):** Run the API, then use Swagger UI at [http://localhost:8000/docs](http://localhost:8000/docs) to create/update content. +- **Update content (seed):** Edit [backend/seed.py](backend/seed.py) and run `docker compose exec api python seed.py`. +- **Run the app:** `docker compose up --build` → [localhost:4200](http://localhost:4200). +- **Build:** `ng build` → `dist/`, then `docker compose up --build -d` for full stack. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index db673a0..43fbde7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,8 @@ -FROM alpine:3.23.0 +FROM ubuntu:latest -RUN apk update && apk add git npm python3 bash podman +RUN apt update -y && apt install -y git npm python3 bash podman podman-compose fuse-overlayfs # Install Angular.js via NPM RUN npm install -g @angular/cli@21.2.14 -ENV SHELL /bin/bash \ No newline at end of file +# ENV SHELL /bin/bash \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..142e3f6 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# KMountain Flower Radio Station — Environment Variables +# Copy this file to .env and fill in the values. Do NOT commit .env. + +# PostgreSQL connection string (backend) +# Format: postgresql+asyncpg://user:password@host:port/database +KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain + +# CORS allowlist for the backend (JSON array of allowed origins) +KMTN_CORS_ORIGINS=["http://localhost:4200"] diff --git a/.gitignore b/.gitignore index 854acd5..882b9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ __screenshots__/ # System files .DS_Store Thumbs.db + +# Environment files +.env diff --git a/README.md b/README.md index 4faadde..b28ff77 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,250 @@ -# RadioStation — KMountain Flower +# KMountain Flower Radio Station -A static Angular boilerplate for a community radio station website. The app showcases a broadcast schedule, upcoming events, donation tiers, an about page, and a contact form — all styled with a warm mountain/nature aesthetic. +A three-tier web application for a community radio station: Angular 21 frontend, FastAPI backend, PostgreSQL database. -## Architecture +Renders a hero page, about page, broadcast schedule, donation tiers, upcoming events, and a contact form — styled with a warm mountain/nature aesthetic. -``` -src/ -├── app/ # Angular application root -│ ├── app.ts # Root component (shell layout) -│ ├── app.config.ts # Application config: providers + router -│ ├── app.html # Shell template: navbar + + footer -│ ├── app.routes.ts # Route definitions (lazy-loaded components) -│ ├── app.scss # Root component styles -│ ├── hero/ # Landing page (home route) -│ ├── about/ # About the station page -│ ├── schedule/ # Program schedule (data-driven table) -│ ├── donate/ # Donation tiers (data-driven cards) -│ ├── events/ # Upcoming events (data-driven cards) -│ ├── contact/ # Contact form (two-way binding via FormsModule) -│ ├── navbar/ # Top navigation bar (responsive hamburger menu) -│ └── footer/ # Site footer -├── styles/ # Global SCSS design tokens -│ ├── _variables.scss # Color palette, spacing, breakpoints, z-index scale -│ ├── _mixins.scss # Reusable SCSS mixins (cards, buttons, responsiveness) -│ ├── _themes.scss # Maps SCSS vars → CSS custom properties on :root -│ └── index.scss # Global entry: reset + utility classes + theme import -├── assets/ -│ └── svg/ # Decorative SVGs (mountain dividers, floral accents) -├── main.ts # App bootstrap entry point -└── index.html # HTML shell ( mount point) -``` +## Quick Start -### Key architectural decisions - -- **Standalone components** — Every component is standalone (no NgModules). This is Angular 21's default and keeps imports explicit. -- **Lazy-loaded routes** — Each page is code-splitted via `loadComponent()` in [app.routes.ts](src/app/app.routes.ts). The router imports each component on first navigation. -- **Data-driven pages** — `ScheduleComponent`, `DonateComponent`, and `EventsComponent` all hold their content as readonly arrays (`readonly programs: Program[]`, etc.). To update content, edit these arrays in the component TypeScript files. -- **Design tokens in SCSS** — All colors, spacing, breakpoints, and typography live in [src/styles/_variables.scss](src/styles/_variables.scss). Change these once and every component inherits the new values via [_themes.scss](src/styles/_themes.scss) (which exposes them as CSS custom properties) and the mixins in [_mixins.scss](src/styles/_mixins.scss). -- **SCSS preprocessor paths** — [angular.json](angular.json) adds `src/styles/` to the include path, so any component can `@use 'variables'` without a relative path. - -## Pages - -| Route | Component | Description | -| -------- | --------------- | -------------------------------------------- | -| `/` | HeroComponent | Hero landing section | -| `/about` | AboutComponent | Station mission & info | -| `/schedule` | ScheduleComponent | Program schedule, rendered from `programs[]` | -| `/donate` | DonateComponent | Donation tiers, rendered from `tiers[]` | -| `/events` | EventsComponent | Upcoming events, rendered from `upcomingEvents[]` | -| `/contact` | ContactComponent | Contact form with submission handler | - -## Customization guide - -### Re-skinning the color palette -1. Edit the color variables in [src/styles/_variables.scss](src/styles/_variables.scss). The primary palette (blue) and accent palette (orange) control the entire site. -2. That's it — [_themes.scss](src/styles/_themes.scss) automatically maps them to CSS custom properties used by every component. - -### Changing typography -Edit `$font-primary`, `$font-heading`, and `$font-mono` in [src/styles/_variables.scss](src/styles/_variables.scss). - -### Adding a new page -1. Create a new folder under `src/app/` with a `*.component.ts`, `*.component.html`, and `*.component.scss`. -2. Export a `@Component` decorated class. -3. Add a route in [app.routes.ts](src/app/app.routes.ts) using `loadComponent()` to lazy-load it. -4. (Optional) Add a `RouterLink` in [navbar.component.html](src/app/navbar/navbar.component.html). - -### Updating schedule / events / donation content -Edit the `readonly` arrays directly in their respective component files: -- [schedule.component.ts](src/app/schedule/schedule.component.ts) — `programs` -- [events.component.ts](src/app/events/events.component.ts) — `upcomingEvents` -- [donate.component.ts](src/app/donate/donate.component.ts) — `tiers` - -Each interface (`Program`, `Event`, `Tier`) is defined in the component file. Modify the interfaces and data together. - -## Getting started - -### Prerequisites -- Node.js 20+ (or use the dev container) -- npm 11+ - -### Quick start +### Option A — Docker Compose (recommended) ```bash -npm install -ng serve +# Start all services +docker compose up --build + +# Seed the database (first run only) +docker compose exec api python seed.py ``` Open [http://localhost:4200](http://localhost:4200). -### Development server +### Option B — Manual (frontend + backend separately) ```bash -ng serve # Start dev server (hot reload enabled) -ng serve --port 8080 # Custom port +# Database +createdb kmountain +export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain + +# Backend +cd backend && pip install -r requirements.txt +uvicorn app.main:app --reload + +# Frontend (new terminal) +npm install && ng serve ``` -### Building +Open [http://localhost:4200](http://localhost:4200) and ensure the backend is running on [http://localhost:8000](http://localhost:8000). + +## Prerequisites + +| Requirement | Version | Notes | +|---|---|---| +| Docker + Docker Compose | 24+ | For the Docker Compose path | +| Node.js | 20+ | For manual frontend development | +| npm | 11+ | | +| Python | 3.12+ | For manual backend development | +| PostgreSQL | 16+ | For manual backend development (Docker handles it) | + +## Architecture + +``` + ┌──────────────┐ + │ Browser │ + │ :4200 / :80│ + └──────┬───────┘ + │ + ┌────────────┴────────────┐ + │ │ + ┌────▼─────┐ ┌──────▼─────┐ + │ Nginx │ /api/* │ FastAPI │ + │ (static) │─ proxy ──▶ │ :8000 │ + └────┬─────┘ └──────┬─────┘ + │ │ + ┌────▼─────┐ ┌──────▼─────┐ + │ Angular 21│ │ PostgreSQL │ + │ SPA │ │ :5432 │ + └──────────┘ └────────────┘ +``` + +**Three tiers:** + +1. **Frontend** — Angular 21 SPA built via multi-stage Dockerfile and served by Nginx. In production, Nginx serves the static files and proxies `/api/*` requests to the backend. In development, both run on separate ports and the browser talks directly to both. +2. **Backend** — Python FastAPI with async SQLAlchemy. Provides CRUD REST endpoints for programs (broadcast schedule), events (upcoming events), and donation tiers. Auto-generates Swagger/OpenAPI docs at `/docs`. +3. **Database** — PostgreSQL 16. Schema contains three tables: `programs`, `events`, `donation_tiers`. Initial data is loaded via [seed.py](backend/seed.py). + +## Project Structure + +``` +. +├── docker-compose.yml # 3-service stack (db, api, frontend) +├── Dockerfile # Multi-stage: Angular build → Nginx serve +├── nginx.conf # Static files + /api/* proxy +├── .env.example # Environment variable template +├── .devcontainer/ # VS Code dev container +│ +├── src/ # Angular 21 frontend +│ ├── app/ # Standalone components +│ │ ├── app.routes.ts # Lazy-loaded route map +│ │ ├── hero/ about/ schedule/ donate/ events/ contact/ navbar/ footer/ +│ │ ├── services/ # HTTP services (program, event, tier) +│ │ └── interfaces/ # TypeScript types (Program, Event, Tier) +│ ├── environments/ # dev/prod config (apiBaseUrl) +│ └── styles/ # SCSS design tokens +│ ├── _variables.scss # Colors, spacing, breakpoints +│ ├── _mixins.scss # Card, button, responsive mixins +│ └── _themes.scss # SCSS → CSS custom properties +│ +└── backend/ # FastAPI backend + ├── Dockerfile # Python gunicorn/uvicorn + ├── requirements.txt + ├── seed.py # Initial data for all models + └── app/ + ├── main.py # FastAPI app, CORS, router registration + ├── config.py # ENV-based settings (KMTN_* prefix) + ├── database.py # async engine + session factory + ├── models.py # SQLAlchemy ORM (Program, Event, DonationTier) + ├── schemas.py # Pydantic schemas (Create/Update/Response) + └── api/ + ├── programs.py # CRUD /api/programs + ├── events.py # CRUD /api/events + └── tiers.py # CRUD /api/tiers +``` + +## Local Development + +### Docker Compose ```bash -ng build # Production build → dist/ -ng build --configuration development # With source maps +docker compose up --build # Start all services +docker compose exec api python seed.py # Seed database (first run) +docker compose logs -f api # Stream backend logs +docker compose down # Stop and remove containers ``` -### Code style +The `pgdata` volume persists the database across container restarts. -This project uses [Prettier](https://prettier.io/) (configured in [.prettierrc](.prettierrc)). Run: +### Manual Development + +Run all three layers in parallel: ```bash -npx prettier --write . +# 1. Database (install PostgreSQL 16 locally) +createdb kmountain + +# 2. Backend (new terminal) +cd backend +pip install -r requirements.txt +export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain +export KMTN_CORS_ORIGINS='["http://localhost:4200"]' +uvicorn app.main:app --reload + +# 3. Frontend (another terminal) +npm install +ng serve ``` -### Testing +## Configuration + +| Variable | Default | Where | Description | +|---|---|---|---| +| `KMTN_DATABASE_URL` | `postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain` | [backend/app/config.py](backend/app/config.py) | Async PostgreSQL connection string | +| `KMTN_CORS_ORIGINS` | `["http://localhost:4200"]` | [backend/app/config.py](backend/app/config.py) | CORS allowlist (JSON array) | +| `apiBaseUrl` | `http://localhost:8000` (dev) / `''` (prod) | [src/environments/](src/environments/) | Where the Angular app finds the API | + +Environment variables are prefixed with `KMTN_` in the backend. For production, set `KMTN_DATABASE_URL` to your managed PostgreSQL connection and update `KMTN_CORS_ORIGINS` to your actual domain. + +## Building ```bash -ng test # Run unit tests (Vitest) +# Frontend (produces dist/radio-station/browser/) +ng build --configuration production + +# Backend Docker image +docker build -t kmountain-api -f backend/Dockerfile backend/ + +# Full production stack +docker compose up --build -d ``` -## Dev container +## Testing -This project ships with a [.devcontainer/](.devcontainer/) for VS Code. Open the repo in VS Code → "Reopen in Container" to get a pre-configured environment with Angular CLI, Node.js, Python 3, and Git. +```bash +ng test # Angular + Vitest +``` -## Project tech stack +The backend API is self-documented at [http://localhost:8000/docs](http://localhost:8000/docs) (Swagger UI). Run `docker compose exec api python seed.py` and visit that URL to interactively test endpoints. -- **Framework:** Angular 21 (standalone components, signals-ready) -- **Language:** TypeScript 5.9 -- **Styling:** SCSS with design tokens (variables → mixins → CSS custom properties) -- **Routing:** Angular Router with lazy loading -- **Form handling:** FormsModule (two-way binding) -- **Linting/formatting:** Prettier -- **Testing:** Vitest (via Angular CLI) -- **Package manager:** npm 11.11 +## API Reference + +The backend exposes CRUD endpoints for three models: + +| Method | Endpoint | Description | +|---|---|---| +| `GET` | `/api/programs` | List all programs (`?day=1..7` to filter by day of week) | +| `GET` | `/api/programs/:id` | Single program | +| `POST` | `/api/programs` | Create program | +| `PUT` | `/api/programs/:id` | Update program | +| `DELETE` | `/api/programs/:id` | Delete program | +| `GET` | `/api/events` | List events (`?active=true` to filter active events) | +| `GET` | `/api/events/:id` | Single event | +| `POST` | `/api/events` | Create event | +| `PUT` | `/api/events/:id` | Update event | +| `DELETE` | `/api/events/:id` | Delete event | +| `GET` | `/api/tiers` | List donation tiers (ordered by `display_order`) | +| `GET` | `/api/tiers/:id` | Single tier | +| `POST` | `/api/tiers` | Create tier | +| `PUT` | `/api/tiers/:id` | Update tier | +| `DELETE` | `/api/tiers/:id` | Delete tier | + +Full interactive API docs at [http://localhost:8000/docs](http://localhost:8000/docs). + +## Deployment + +### Production (Docker Compose on a VPS) + +```bash +# 1. Clone repo on server +git clone kmountain +cd kmountain + +# 2. Edit docker-compose.yml +# - Change POSTGRES_PASSWORD to a strong value +# - Update KMTN_CORS_ORIGINS to your domain +# - Optionally set KMTN_DATABASE_URL to a managed PostgreSQL + +# 3. Build and start +docker compose up --build -d + +# 4. Seed the database +docker compose exec api python seed.py +``` + +### Production hardening checklist + +- [ ] Change the default `POSTGRES_PASSWORD` in [docker-compose.yml](docker-compose.yml) +- [ ] Update `KMTN_CORS_ORIGINS` to your actual domain (not `localhost`) +- [ ] Set `KMTN_DATABASE_URL` to a managed PostgreSQL connection (RDS, Cloud SQL, etc.) +- [ ] Add a reverse proxy (Caddy, Traefik) in front of Nginx for HTTPS +- [ ] Set up log rotation for Docker containers +- [ ] Configure Docker image registry and push/pull workflow + +## Development Tools + +- **Dev container** — Open the repo in VS Code → "Reopen in Container" for a pre-configured environment with Angular CLI, Node.js, Python 3, and Git ([.devcontainer/](.devcontainer/)). +- **VS Code workspace** — [workspace file](web_app.code-workspace) in the repo root configures both Angular and Python extensions. +- **Prettier** — Run `npx prettier --write .` to format the codebase. +- **Prettier config** — [**.prettierrc**](.prettierrc) (100 char width, single quotes, Angular HTML parser). + +## Tech Stack + +| Layer | Technology | +|---|---| +| Frontend | Angular 21 (standalone components, signals-ready) | +| Backend | FastAPI 0.115, Python 3.12 | +| Database | PostgreSQL 16 (async via SQLAlchemy 2.0) | +| Styling | SCSS with design tokens (variables → mixins → CSS custom properties) | +| Routing | Angular Router (lazy loaded) + Nginx SPA fallback | +| HTTP | RxJS observables (frontend → FastAPI) | +| Containers | Docker Compose (3 services: db, api, frontend) | +| CI/CD | _(not yet configured)_ | +| Testing | Vitest (via Angular CLI) | diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..3d6a5f7 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,53 @@ +# Dev-sidecar compose: app container + PostgreSQL sidecar. +# Used by .devcontainer/devcontainer.json via dockerComposeFile. + +services: + app: + build: + context: . + dockerfile: .devcontainer/Dockerfile + restart: "no" + ports: + - "8000:8000" # FastAPI + - "4200:4200" # Angular dev server + environment: + KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain + KMTN_CORS_ORIGINS: '["http://localhost:4200"]' + volumes: + - ..:/workspaces/web_app:cached + - node_modules:/workspaces/web_app/node_modules + - backend_venv:/app/.venv + depends_on: + db: + condition: service_healthy + command: > + sh -c " + pip install -r backend/requirements.txt && + cd backend && + uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload & + cd /workspaces/web_app && + npm install && + npx ng serve --host 0.0.0.0 --port 4200 + " + + db: + image: docker.io/library/postgres:16-alpine + restart: "no" + environment: + POSTGRES_DB: kmountain + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + pgdata: + node_modules: + backend_venv: diff --git a/docker-compose.yml b/docker-compose.yml index cbdb3f5..be4f31d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,8 @@ services: db: image: docker.io/library/postgres:16-alpine restart: unless-stopped + # NOTE: Change POSTGRES_PASSWORD in production. Consider using Docker secrets or + # a managed PostgreSQL service instead of local credentials. environment: POSTGRES_DB: kmountain POSTGRES_USER: postgres @@ -14,6 +16,8 @@ services: timeout: 5s retries: 5 + # NOTE: Update KMTN_CORS_ORIGINS in production to your actual domain(s). + # Set KMTN_DATABASE_URL to your managed PostgreSQL connection if not using the local db service. api: build: context: ./backend diff --git a/trans_to_3_tier.md b/trans_to_3_tier.md deleted file mode 100644 index 36406fb..0000000 --- a/trans_to_3_tier.md +++ /dev/null @@ -1,243 +0,0 @@ -# Backend Infrastructure for KMountain Flower Radio Station - -## Context - -The app currently has a polished frontend with zero backend — all dynamic content (schedule, events, donations) lives as hardcoded `readonly` arrays in components. The contact form just shows an alert. We need to add persistent data, a backend API, and Docker-based deployment while keeping things simple for a volunteer-run community radio station. - -## VS Code Workspace Setup - -Use a **multi-root workspace** so both projects get their own IDE context, linting, and debugging configs: - -``` -web_app/ ← repo root (existing) -├── .vscode/ -│ ├── settings.json ← workspace settings (shared) -│ ├── tasks.json ← common tasks (start backend, start frontend) -│ └── launch.json ← debug configs for both projects -├── .vscode/workspace.code-workspace ← multi-root workspace file (open this in VS Code) -├── src/ ← existing Angular frontend (unchanged structure) -├── backend/ ← new FastAPI backend -│ ├── .vscode/ -│ │ ├── settings.json ← Python/Flake8/Pylance settings -│ │ └── launch.json ← FastAPI debug config -├── docker-compose.yml -├── Dockerfile ← Angular build + nginx serve (at repo root) -└── nginx.conf ← nginx config (at repo root) -``` - -**Steps:** -1. Create `web_app/.vscode/` directory with shared workspace settings -2. Create `web_app/.vscode/workspace.code-workspace` listing both roots: - ```json - { - "folders": [ - { "path": "." }, - { "path": "backend" } - ], - "settings": {} - } - ``` -3. VS Code: File → Open Workspace from File → select `workspace.code-workspace` -4. Backend folder gets Python language server; frontend folder gets Angular TS language server — no conflict - -## Decisions (confirmed) - -- **Admin panel**: Skip for now. Initial data via a seed script. -- **Contact form**: Leave as-is (alert only, unimplemented for now). -- **Rollout**: Phase by phase. -- **Backend**: FastAPI (Python 3 already in devcontainer, auto-docs, async). -- **Database**: PostgreSQL in Docker (production-ready, any managed host compatible). -- **Frontend serve**: nginx in Docker (build Angular → serve static). - -## Target Architecture - -``` -docker-compose.yml -├── db (postgres:16-alpine) -- data tier, persistent volume -├── api (FastAPI + uvicorn) -- middleware tier, CORS + all endpoints -└── frontend (nginx) -- frontend tier, Angular build + API proxy in prod -``` - -## Phase 1: Backend + Schedule Page + Docker Skeleton - -### 1.0 VS Code workspace setup - -1. Create `.vscode/` directory with `workspace.code-workspace`, `settings.json`, `tasks.json`, `launch.json` -2. Create `backend/.vscode/settings.json` (Python language settings) -3. Open `workspace.code-workspace` in VS Code — both roots visible, each with its own language server -4. `tasks.json` provides unified commands: "Start Backend" (uvicorn --reload), "Start Frontend" (ng serve), "Docker Up" - -### 1.1 Backend skeleton - -Create `backend/` directory: - -``` -backend/ -├── requirements.txt -- fastapi, uvicorn, psycopg2-binary, alembic, python-multipart -├── Dockerfile -- python:3.12-slim, copy requirements, copy app, CMD uvicorn -├── seed.py -- seed 10 weekday programs -└── app/ - ├── __init__.py - ├── main.py -- FastAPI app, lifespan (DB init), CORS middleware - ├── config.py -- DATABASE_URL, CORS_ORIGINS from env vars - ├── database.py -- async SessionLocal engine - ├── models.py -- Program SQLAlchemy model - ├── schemas.py -- Program Pydantic schemas (read + create + update) - └── api/ - ├── __init__.py - └── programs.py -- GET /api/programs (with ?day filter), POST, PUT, DELETE -``` - -**Program model**: `id, day_of_week (int), day_label (str), time (str), title (str), host (str), genre (str)` - -**Seed data**: The 10 existing weekday programs from [schedule.component.ts:19-29](src/app/schedule/schedule.component.ts). - -### 1.2 Angular service layer - -Modify: -- [src/app/app.config.ts](src/app/app.config.ts) -- add `provideHttpClient()` to providers -- [src/environments/environment.ts](src/environments/environment.ts) -- **new**: `apiBaseUrl` for dev/prod - -Create: -- [src/app/interfaces/program.ts](src/app/interfaces/program.ts) -- `Program` interface mirroring backend -- [src/app/services/program.service.ts](src/app/services/program.service.ts) -- `getPrograms()`, `getProgram()`, `createProgram()`, etc. - -### 1.3 Convert schedule component - -Modify [src/app/schedule/schedule.component.ts](src/app/schedule/schedule.component.ts): -- Remove `readonly programs: Program[]` array -- `inject(ProgramService)` in class body -- Add `programs: Program[] = []` and `loading = true` -- Load data in `ngOnInit()` via service -- Template: add loading/empty states, keep `*ngFor` structure - -### 1.4 Docker skeleton - -Create: -- [docker-compose.yml](docker-compose.yml) -- db + api services, healthcheck on db, depends_on ordering, named `pgdata` volume -- [backend/Dockerfile](backend/Dockerfile) -- already covered in 1.1 - -### Phase 1 verification - -| Check | How | -|-------|-----| -| Backend runs locally | `cd backend && uvicorn app.main:app --reload` | -| OpenAPI docs at `/docs` | Browse `http://localhost:8000/docs` | -| Seed data present | `curl http://localhost:8000/api/programs` returns 10 items | -| Schedule page fetches from DB | DevTools Network tab shows GET to `/api/programs`, page renders | -| Docker stack | `docker compose up` -- db + api running, schedule renders on frontend | - -## Phase 2: Donate + Events Pages - -### 2.1 Backend additions - -Modify `backend/app/`: -- [models.py](backend/app/models.py) -- add `Event` and `DonationTier` models - - Event: `id, date (DATE), title, description (TEXT), location, icon, rsvp_url (nullable), active (bool)` - - DonationTier: `id, name (UNIQUE), amount (NUMERIC), icon, benefits (TEXT[]), display_order (int)` -- [schemas.py](backend/app/schemas.py) -- add Pydantic schemas for both -- [api/events.py](backend/app/api/events.py) -- GET/POST/PUT/DELETE with `?active` filter -- [api/tiers.py](backend/app/api/tiers.py) -- GET/POST/PUT/DELETE ordered by `display_order` -- [seed.py](backend/seed.py) -- add events and tiers data (from [events.component.ts](src/app/events/events.component.ts) and [donate.component.ts](src/app/donate/donate.component.ts)) - -### 2.2 Angular additions - -Create: -- [src/app/interfaces/event.ts](src/app/interfaces/event.ts) -- [src/app/interfaces/tier.ts](src/app/interfaces/tier.ts) -- [src/app/services/event.service.ts](src/app/services/event.service.ts) -- `getEvents()`, `createEvent()`, etc. -- [src/app/services/tier.service.ts](src/app/services/tier.service.ts) -- `getTiers()` ordered - -Modify: -- [src/app/events/events.component.ts](src/app/events/events.component.ts) -- remove readonly array, inject EventService, load in ngOnInit -- [src/app/donate/donate.component.ts](src/app/donate/donate.component.ts) -- remove readonly array, inject TierService, load in ngOnInit -- Keep template structure identical, just swap the data source - -### Phase 2 verification - -| Check | How | -|-------|-----| -| Events API | `curl http://localhost:8000/api/events?active=true` returns 4 events | -| Tiers API | `curl http://localhost:8000/api/tiers` returns 4 tiers ordered | -| Events page renders from DB | Navigate to /events, verify from DB not hardcoded | -| Donate page renders from DB | Navigate to /donate, verify from DB not hardcoded | - -## Phase 3: Frontend Docker + Full Stack - -### 3.1 Dockerfile + nginx config - -Create at repo root alongside `src/` and `backend/`: - -- `Dockerfile` -- multi-stage: node:20-alpine build Angular → nginx:alpine serve the dist output -- `nginx.conf` -- serve Angular static files; in production mode, proxy `/api/*` to api service (avoids CORS in prod) - -### 3.2 Update docker-compose.yml - -Add frontend service: -```yaml -frontend: - build: - context: . - dockerfile: Dockerfile - ports: - - "4200:80" - depends_on: - - api -``` - -### Phase 3 verification - -| Check | How | -|-------|-----| -| Full stack up | `docker compose up --build` -- 3 containers, all healthy | -| Frontend accessible | Browse `http://localhost:4200` -- all pages load | -| All data pages dynamic | schedule, donate, events all render from DB | -| Data persists | `docker compose down && docker compose up` -- data still present | - -## Files to Create (new) - -| File | Purpose | -|------|---------| -| `.vscode/workspace.code-workspace` | Multi-root workspace (frontend + backend) | -| `.vscode/settings.json` | Shared workspace settings | -| `.vscode/tasks.json` | Common tasks (start backend, start frontend, docker up) | -| `.vscode/launch.json` | Debug configs for both projects | -| `backend/.vscode/settings.json` | Python-specific VS Code settings | -| `backend/.vscode/launch.json` | FastAPI debug config | -| `backend/requirements.txt` | Python dependencies | -| `backend/Dockerfile` | API container image | -| `backend/seed.py` | Initial station data | -| `backend/app/main.py` | FastAPI app entry point | -| `backend/app/config.py` | Env-based config | -| `backend/app/database.py` | async session engine | -| `backend/app/models.py` | SQLAlchemy ORM models | -| `backend/app/schemas.py` | Pydantic schemas | -| `backend/app/api/programs.py` | Programs CRUD endpoints | -| `backend/app/api/events.py` | Events CRUD endpoints | -| `backend/app/api/tiers.py` | Tiers CRUD endpoints | -| `src/app/interfaces/program.ts` | Program TS type | -| `src/app/interfaces/event.ts` | Event TS type | -| `src/app/interfaces/tier.ts` | Tier TS type | -| `src/app/services/program.service.ts` | Program HTTP service | -| `src/app/services/event.service.ts` | Event HTTP service | -| `src/app/services/tier.service.ts` | Tier HTTP service | -| `src/environments/environment.ts` | Dev/prod API URL | -| `docker-compose.yml` | 3-service compose | -| `Dockerfile` | Angular build + nginx serve (at repo root) | -| `nginx.conf` | nginx config with API proxy (at repo root) | - -## Files to Modify - -| File | Change | -|------|--------| -| `src/app/app.config.ts` | Add `provideHttpClient()` | -| `src/app/schedule/schedule.component.ts` | Remove readonly array, inject service | -| `src/app/events/events.component.ts` | Same pattern | -| `src/app/donate/donate.component.ts` | Same pattern | -| `docker-compose.yml` | Created in Phase 1, extended in Phase 3 | - -## Not In Scope (for now) - -- Admin panel (mentioned as Phase 5 in analysis — will be a future addition) -- Contact form implementation (left as alert per user decision) -- Streaming status endpoint (can add later via same pattern)