updated 3 tier structure

This commit is contained in:
2026-06-18 04:31:37 +00:00
parent 13855bf620
commit 6fc00b8ce9
8 changed files with 307 additions and 357 deletions
+15 -9
View File
@@ -2,29 +2,35 @@
## Project overview ## 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 ## 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). - **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 lives as `readonly` arrays with interfaces defined in the same file. Never put content in services or APIs — this is a static site. - **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). - **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. - **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 ## 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/_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/_mixins.scss` — card-style, button-style, responsive, gradient-text, flowery-border, fade-in
- `src/styles/_themes.scss` — SCSS → CSS custom property mapping - `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 ## Common tasks
- **Re-skin:** Edit colors in `_variables.scss` only. - **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). - **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`). - **Update content (dev):** Run the API, then use Swagger UI at [http://localhost:8000/docs](http://localhost:8000/docs) to create/update content.
- **Run the app:** `ng serve` → [localhost:4200](http://localhost:4200). - **Update content (seed):** Edit [backend/seed.py](backend/seed.py) and run `docker compose exec api python seed.py`.
- **Build:** `ng build``dist/`. - **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.
+3 -3
View File
@@ -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 # Install Angular.js via NPM
RUN npm install -g @angular/cli@21.2.14 RUN npm install -g @angular/cli@21.2.14
ENV SHELL /bin/bash # ENV SHELL /bin/bash
+9
View File
@@ -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"]
+3
View File
@@ -42,3 +42,6 @@ __screenshots__/
# System files # System files
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Environment files
.env
+220 -102
View File
@@ -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.
``` ## Quick Start
src/
├── app/ # Angular application root
│ ├── app.ts # Root <app-root> component (shell layout)
│ ├── app.config.ts # Application config: providers + router
│ ├── app.html # Shell template: navbar + <router-outlet> + 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 (<app-root> mount point)
```
### Key architectural decisions ### Option A — Docker Compose (recommended)
- **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
```bash ```bash
npm install # Start all services
ng serve docker compose up --build
# Seed the database (first run only)
docker compose exec api python seed.py
``` ```
Open [http://localhost:4200](http://localhost:4200). Open [http://localhost:4200](http://localhost:4200).
### Development server ### Option B — Manual (frontend + backend separately)
```bash ```bash
ng serve # Start dev server (hot reload enabled) # Database
ng serve --port 8080 # Custom port 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 ```bash
ng build # Production build → dist/ docker compose up --build # Start all services
ng build --configuration development # With source maps 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 ```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 ```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) ## API Reference
- **Language:** TypeScript 5.9
- **Styling:** SCSS with design tokens (variables → mixins → CSS custom properties) The backend exposes CRUD endpoints for three models:
- **Routing:** Angular Router with lazy loading
- **Form handling:** FormsModule (two-way binding) | Method | Endpoint | Description |
- **Linting/formatting:** Prettier |---|---|---|
- **Testing:** Vitest (via Angular CLI) | `GET` | `/api/programs` | List all programs (`?day=1..7` to filter by day of week) |
- **Package manager:** npm 11.11 | `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 <repo-url> 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) |
+53
View File
@@ -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:
+4
View File
@@ -2,6 +2,8 @@ services:
db: db:
image: docker.io/library/postgres:16-alpine image: docker.io/library/postgres:16-alpine
restart: unless-stopped restart: unless-stopped
# NOTE: Change POSTGRES_PASSWORD in production. Consider using Docker secrets or
# a managed PostgreSQL service instead of local credentials.
environment: environment:
POSTGRES_DB: kmountain POSTGRES_DB: kmountain
POSTGRES_USER: postgres POSTGRES_USER: postgres
@@ -14,6 +16,8 @@ services:
timeout: 5s timeout: 5s
retries: 5 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: api:
build: build:
context: ./backend context: ./backend
-243
View File
@@ -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)