working dev environment
This commit is contained in:
+3
-2
@@ -31,6 +31,7 @@ See [README.md](README.md) for a full architecture map. Key files:
|
||||
- **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 (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).
|
||||
- **Update content (seed):** Edit [backend/seed.py](backend/seed.py) and restart the API (autoseed runs on empty DB), or hit `curl -X POST http://localhost:8000/api/admin/reset` to re-seed.
|
||||
- **Run the app (dev):** From the `backend/` directory, set `KMTN_DATABASE_URL=sqlite+aiosqlite:///./kmountain.db`, then `uvicorn app.main:app --reload` + `ng serve` in parallel. Autoseed populates data on first run.
|
||||
- **Run the app (Docker):** `docker compose up --build` → [localhost:4200](http://localhost:4200).
|
||||
- **Build:** `ng build` → `dist/`, then `docker compose up --build -d` for full stack.
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"*"
|
||||
"Bash(*)",
|
||||
"Read(*)",
|
||||
"Edit(*)",
|
||||
"Write(*)",
|
||||
"Glob(*)",
|
||||
"Grep(*)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/workspaces/web_app/.vscode"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
FROM ubuntu:latest
|
||||
FROM python:3.12-alpine
|
||||
|
||||
RUN apt update -y && apt install -y git npm python3 bash podman podman-compose fuse-overlayfs
|
||||
RUN apk update && apk add git nodejs npm
|
||||
|
||||
# Install Angular.js via NPM
|
||||
RUN npm install -g @angular/cli@21.2.14
|
||||
|
||||
# ENV SHELL /bin/bash
|
||||
@@ -7,7 +7,8 @@
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"Angular.ng-template",
|
||||
"magnobiet.sass-extension-pack"
|
||||
"magnobiet.sass-extension-pack",
|
||||
"Anthropic.claude-code"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +45,6 @@ Thumbs.db
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
|
||||
# Dev SQLite database
|
||||
backend/kmountain.db
|
||||
@@ -6,27 +6,19 @@ Renders a hero page, about page, broadcast schedule, donation tiers, upcoming ev
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option A — Docker Compose (recommended)
|
||||
### Option A — Dev Container (recommended)
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker compose up --build
|
||||
|
||||
# Seed the database (first run only)
|
||||
docker compose exec api python seed.py
|
||||
```
|
||||
Open the repo in VS Code and click **Reopen in Container** (or run `Dev Containers: Reopen in Container` from the command palette). The dev container starts both the backend (port 8000) and frontend (port 4200) automatically.
|
||||
|
||||
Open [http://localhost:4200](http://localhost:4200).
|
||||
|
||||
### Option B — Manual (frontend + backend separately)
|
||||
|
||||
```bash
|
||||
# Database
|
||||
createdb kmountain
|
||||
export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain
|
||||
|
||||
# Backend
|
||||
cd backend && pip install -r requirements.txt
|
||||
# Backend (uses SQLite for local dev — no database server needed)
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
export KMTN_DATABASE_URL="sqlite+aiosqlite:///./kmountain.db"
|
||||
uvicorn app.main:app --reload
|
||||
|
||||
# Frontend (new terminal)
|
||||
@@ -39,11 +31,10 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
|
||||
| Requirement | Version | Notes |
|
||||
|---|---|---|
|
||||
| Docker + Docker Compose | 24+ | For the Docker Compose path |
|
||||
| Node.js | 20+ | For manual frontend development |
|
||||
| Node.js | 20+ | For frontend development |
|
||||
| npm | 11+ | |
|
||||
| Python | 3.12+ | For manual backend development |
|
||||
| PostgreSQL | 16+ | For manual backend development (Docker handles it) |
|
||||
| Python | 3.12+ | For backend development |
|
||||
| PostgreSQL | 16+ | For production only (dev uses SQLite) |
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -58,29 +49,28 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
┌────▼─────┐ ┌──────▼─────┐
|
||||
│ Nginx │ /api/* │ FastAPI │
|
||||
│ (static) │─ proxy ──▶ │ :8000 │
|
||||
└────┬─────┘ └──────┬─────┘
|
||||
│ │
|
||||
┌────▼─────┐ ┌──────▼─────┐
|
||||
│ Angular 21│ │ PostgreSQL │
|
||||
│ SPA │ │ :5432 │
|
||||
└──────────┘ └────────────┘
|
||||
└──────────┘ └──────┬─────┘
|
||||
│
|
||||
┌──────▼─────┐
|
||||
│ PostgreSQL │
|
||||
│ :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.
|
||||
1. **Frontend** — Angular 21 SPA. In production, served by Nginx. In development, served by the Angular dev server on port 4200.
|
||||
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).
|
||||
3. **Database** — PostgreSQL 16. Schema contains three tables: `programs`, `events`, `donation_tiers`. Initial data is loaded via [seed.py](backend/seed.py). Local development uses SQLite for convenience.
|
||||
|
||||
## 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
|
||||
├── docker-compose.dev.yml # Dev-sidecar compose (dev container)
|
||||
├── .devcontainer/ # VS Code dev container
|
||||
│ ├── Dockerfile # Alpine Python 3.12 + Node.js + Angular CLI
|
||||
│ └── devcontainer.json # Dev container config
|
||||
│
|
||||
├── src/ # Angular 21 frontend
|
||||
│ ├── app/ # Standalone components
|
||||
@@ -95,7 +85,6 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
│ └── _themes.scss # SCSS → CSS custom properties
|
||||
│
|
||||
└── backend/ # FastAPI backend
|
||||
├── Dockerfile # Python gunicorn/uvicorn
|
||||
├── requirements.txt
|
||||
├── seed.py # Initial data for all models
|
||||
└── app/
|
||||
@@ -112,43 +101,40 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
||||
|
||||
## Local Development
|
||||
|
||||
### Docker Compose
|
||||
### Dev Container (recommended)
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
The `pgdata` volume persists the database across container restarts.
|
||||
Open the repo in VS Code → **Reopen in Container**. The dev container starts both services automatically. See [.devcontainer/](.devcontainer/) for the configuration.
|
||||
|
||||
### Manual Development
|
||||
|
||||
Run all three layers in parallel:
|
||||
Run both layers in parallel — development uses SQLite (no database server needed):
|
||||
|
||||
```bash
|
||||
# 1. Database (install PostgreSQL 16 locally)
|
||||
createdb kmountain
|
||||
|
||||
# 2. Backend (new terminal)
|
||||
# 1. Backend (new terminal)
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain
|
||||
export KMTN_DATABASE_URL="sqlite+aiosqlite:///./kmountain.db"
|
||||
export KMTN_CORS_ORIGINS='["http://localhost:4200"]'
|
||||
uvicorn app.main:app --reload
|
||||
|
||||
# 3. Frontend (another terminal)
|
||||
# 2. Frontend (another terminal)
|
||||
npm install
|
||||
ng serve
|
||||
```
|
||||
|
||||
The database auto-seeds on first startup. To reset and re-seed at any time:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/admin/reset
|
||||
```
|
||||
|
||||
## 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_DATABASE_URL` | `postgresql+asyncpg://...` (prod) / `sqlite+aiosqlite:///./kmountain.db` (dev) | [backend/app/config.py](backend/app/config.py) | Database connection string |
|
||||
| `KMTN_CORS_ORIGINS` | `["http://localhost:4200"]` | [backend/app/config.py](backend/app/config.py) | CORS allowlist (JSON array) |
|
||||
| `KMTN_ENV` | `development` | [backend/app/config.py](backend/app/config.py) | Set to `production` to disable admin endpoints |
|
||||
| `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.
|
||||
@@ -158,21 +144,17 @@ Environment variables are prefixed with `KMTN_` in the backend. For production,
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
For production deployment, serve the built static files with Nginx and run the backend behind it.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
ng test # Angular + Vitest
|
||||
```
|
||||
|
||||
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.
|
||||
The backend API is self-documented at [http://localhost:8000/docs](http://localhost:8000/docs) (Swagger UI). The database auto-seeds on first startup — visit that URL to interactively test endpoints.
|
||||
|
||||
## API Reference
|
||||
|
||||
@@ -200,33 +182,41 @@ Full interactive API docs at [http://localhost:8000/docs](http://localhost:8000/
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production (Docker Compose on a VPS)
|
||||
### Production
|
||||
|
||||
For production, you'll need to set up:
|
||||
- A PostgreSQL database (managed or self-hosted)
|
||||
- The FastAPI backend (gunicorn/uvicorn)
|
||||
- Nginx to serve the Angular build and proxy `/api/*` to the backend
|
||||
|
||||
```bash
|
||||
# 1. Clone repo on server
|
||||
git clone <repo-url> kmountain
|
||||
cd kmountain
|
||||
# 1. Build the frontend
|
||||
ng build --configuration production
|
||||
|
||||
# 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
|
||||
# 2. Configure the backend
|
||||
export KMTN_DATABASE_URL="postgresql+asyncpg://user:pass@host:5432/kmountain"
|
||||
export KMTN_CORS_ORIGINS='["https://yourdomain.com"]'
|
||||
export KMTN_ENV="production"
|
||||
|
||||
# 3. Build and start
|
||||
docker compose up --build -d
|
||||
# 3. Start the backend
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# 4. Seed the database
|
||||
docker compose exec api python seed.py
|
||||
# 4. Serve the frontend with Nginx (or your preferred web server),
|
||||
# pointing /api/* requests to http://localhost:8000
|
||||
```
|
||||
|
||||
The database auto-seeds on first startup.
|
||||
|
||||
### Production hardening checklist
|
||||
|
||||
- [ ] Change the default `POSTGRES_PASSWORD` in [docker-compose.yml](docker-compose.yml)
|
||||
- [ ] Use a managed PostgreSQL database (RDS, Cloud SQL, etc.)
|
||||
- [ ] Update `KMTN_CORS_ORIGINS` to your actual domain (not `localhost`)
|
||||
- [ ] Set `KMTN_DATABASE_URL` to a managed PostgreSQL connection (RDS, Cloud SQL, etc.)
|
||||
- [ ] Set `KMTN_ENV="production"` to disable admin endpoints
|
||||
- [ ] 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
|
||||
- [ ] Set up log rotation
|
||||
- [ ] Configure a process manager (systemd, PM2) for the backend
|
||||
|
||||
## Development Tools
|
||||
|
||||
@@ -245,6 +235,6 @@ docker compose exec api python seed.py
|
||||
| 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) |
|
||||
| Containers | Dev container (VS Code Remote - Containers) |
|
||||
| CI/CD | _(not yet configured)_ |
|
||||
| Testing | Vitest (via Angular CLI) |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.config import settings
|
||||
from app.database import get_session
|
||||
from app.models import DonationTier, Event, Program
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
async def reset_database():
|
||||
"""Truncate all tables and re-seed. Dev-only — disabled in production."""
|
||||
if settings.ENV == "production":
|
||||
return {"error": "Admin reset is disabled in production"}
|
||||
|
||||
from app.database import async_session
|
||||
from seed import seed as run_seed
|
||||
|
||||
async with async_session() as session:
|
||||
await session.execute(Program.__table__.delete())
|
||||
await session.execute(Event.__table__.delete())
|
||||
await session.execute(DonationTier.__table__.delete())
|
||||
await session.commit()
|
||||
|
||||
await run_seed()
|
||||
return {"status": "reset complete"}
|
||||
@@ -4,6 +4,7 @@ from pydantic_settings import BaseSettings
|
||||
class Settings(BaseSettings):
|
||||
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain"
|
||||
CORS_ORIGINS: list[str] = ["http://localhost:4200", "http://localhost:3000"]
|
||||
ENV: str = "development"
|
||||
|
||||
model_config = {"env_prefix": "KMTN_"}
|
||||
|
||||
|
||||
+19
-3
@@ -2,18 +2,29 @@ from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.config import settings
|
||||
from app.database import engine
|
||||
from app.models import Base
|
||||
from app.database import async_session, engine
|
||||
from app.models import Base, Program
|
||||
from app.api import programs, events, tiers
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Create tables on startup (seed.py handles data seeding)
|
||||
# Create tables on startup
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
# Auto-seed if database is empty
|
||||
async with async_session() as session:
|
||||
result = await session.execute(select(func.count(Program.id)))
|
||||
count = result.scalar()
|
||||
if count == 0:
|
||||
print(" → Database is empty — running seed...")
|
||||
from seed import seed as run_seed
|
||||
await run_seed()
|
||||
|
||||
yield
|
||||
|
||||
|
||||
@@ -36,3 +47,8 @@ app.add_middleware(
|
||||
app.include_router(programs.router, prefix="/api/programs", tags=["programs"])
|
||||
app.include_router(events.router, prefix="/api/events", tags=["events"])
|
||||
app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"])
|
||||
|
||||
# Dev-only admin router (disabled in production)
|
||||
if settings.ENV != "production":
|
||||
from app.api import admin
|
||||
app.include_router(admin.router, prefix="/api/admin", tags=["admin"])
|
||||
|
||||
@@ -6,7 +6,7 @@ from sqlalchemy import (
|
||||
Numeric,
|
||||
Boolean,
|
||||
Date,
|
||||
ARRAY,
|
||||
JSON,
|
||||
)
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
@@ -47,5 +47,5 @@ class DonationTier(Base):
|
||||
name = Column(String(50), unique=True, nullable=False)
|
||||
amount = Column(Numeric(10, 2), nullable=False)
|
||||
icon = Column(String(10), nullable=False)
|
||||
benefits = Column(ARRAY(Text), nullable=False)
|
||||
benefits = Column(JSON, nullable=False)
|
||||
display_order = Column(Integer, nullable=False)
|
||||
|
||||
@@ -2,6 +2,7 @@ fastapi==0.115.6
|
||||
uvicorn[standard]==0.34.0
|
||||
psycopg2-binary==2.9.10
|
||||
asyncpg==0.30.0
|
||||
aiosqlite==0.20.0
|
||||
sqlalchemy[asyncio]==2.0.36
|
||||
alembic==1.14.1
|
||||
python-multipart==0.0.20
|
||||
|
||||
+68
-10
@@ -1,14 +1,18 @@
|
||||
"""
|
||||
Seed script: populates the database with station data.
|
||||
|
||||
Idempotent — safe to run multiple times. Uses get-or-create (upsert) for each entity.
|
||||
|
||||
Usage:
|
||||
DATABASE_URL=postgresql://... python -m app.seed
|
||||
(or from the repo root: cd backend && python seed.py)
|
||||
KMTN_DATABASE_URL=... python seed.py
|
||||
(run from the backend/ directory)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.database import async_session
|
||||
from app.models import Program, Event, DonationTier
|
||||
|
||||
@@ -90,29 +94,83 @@ TIERS: list[dict] = [
|
||||
]
|
||||
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────
|
||||
|
||||
async def _upsert_program(session, data: dict) -> None:
|
||||
"""Get-or-create a program matched on (day_of_week, time)."""
|
||||
existing = await session.execute(
|
||||
select(Program).where(
|
||||
Program.day_of_week == data["day_of_week"],
|
||||
Program.time == data["time"],
|
||||
)
|
||||
)
|
||||
program = existing.scalar_one_or_none()
|
||||
if program:
|
||||
for key, value in data.items():
|
||||
setattr(program, key, value)
|
||||
else:
|
||||
session.add(Program(**data))
|
||||
|
||||
|
||||
async def _upsert_event(session, data: dict) -> None:
|
||||
"""Get-or-create an event matched on (date, title)."""
|
||||
existing = await session.execute(
|
||||
select(Event).where(
|
||||
Event.date == data["date"],
|
||||
Event.title == data["title"],
|
||||
)
|
||||
)
|
||||
event = existing.scalar_one_or_none()
|
||||
if event:
|
||||
for key, value in data.items():
|
||||
setattr(event, key, value)
|
||||
else:
|
||||
session.add(Event(**data, active=True))
|
||||
|
||||
|
||||
async def _upsert_tier(session, data: dict) -> None:
|
||||
"""Get-or-create a donation tier matched on name."""
|
||||
existing = await session.execute(
|
||||
select(DonationTier).where(DonationTier.name == data["name"])
|
||||
)
|
||||
tier = existing.scalar_one_or_none()
|
||||
if tier:
|
||||
for key, value in data.items():
|
||||
setattr(tier, key, value)
|
||||
else:
|
||||
session.add(DonationTier(**data))
|
||||
|
||||
|
||||
# ── Main ──────────────────────────────────────────────────
|
||||
|
||||
async def seed():
|
||||
async def seed() -> None:
|
||||
"""Populate the database with station data (idempotent)."""
|
||||
async with async_session() as session:
|
||||
# Programs
|
||||
for data in PROGRAMS:
|
||||
session.add(Program(**data))
|
||||
await _upsert_program(session, data)
|
||||
await session.commit()
|
||||
print(f" ✓ Seeded {len(PROGRAMS)} programs")
|
||||
|
||||
# Events
|
||||
for data in EVENTS:
|
||||
event = Event(**data, active=True)
|
||||
session.add(event)
|
||||
await _upsert_event(session, data)
|
||||
await session.commit()
|
||||
print(f" ✓ Seeded {len(EVENTS)} events")
|
||||
|
||||
# Tiers
|
||||
for data in TIERS:
|
||||
session.add(DonationTier(**data))
|
||||
await _upsert_tier(session, data)
|
||||
await session.commit()
|
||||
print(f" ✓ Seeded {len(TIERS)} donation tiers")
|
||||
|
||||
|
||||
async def truncate_all() -> None:
|
||||
"""Remove all seed data from the database."""
|
||||
async with async_session() as session:
|
||||
await session.execute(Program.__table__.delete())
|
||||
await session.execute(Event.__table__.delete())
|
||||
await session.execute(DonationTier.__table__.delete())
|
||||
await session.commit()
|
||||
print(" ✓ Truncated all tables")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed())
|
||||
|
||||
+3
-23
@@ -1,5 +1,6 @@
|
||||
# Dev-sidecar compose: app container + PostgreSQL sidecar.
|
||||
# Dev-sidecar compose: app container only (no database service).
|
||||
# Used by .devcontainer/devcontainer.json via dockerComposeFile.
|
||||
# Development uses SQLite — no Docker-in-Docker database needed.
|
||||
|
||||
services:
|
||||
app:
|
||||
@@ -11,15 +12,12 @@ services:
|
||||
- "8000:8000" # FastAPI
|
||||
- "4200:4200" # Angular dev server
|
||||
environment:
|
||||
KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||
KMTN_DATABASE_URL: sqlite+aiosqlite:///./backend/kmountain.db
|
||||
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 &&
|
||||
@@ -30,24 +28,6 @@ services:
|
||||
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:
|
||||
|
||||
Generated
-90
@@ -2204,9 +2204,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2224,9 +2221,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2244,9 +2238,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2264,9 +2255,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2284,9 +2272,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2304,9 +2289,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2324,9 +2306,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2770,9 +2749,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2794,9 +2770,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2818,9 +2791,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2842,9 +2812,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2866,9 +2833,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2890,9 +2854,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3070,9 +3031,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3090,9 +3048,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3110,9 +3065,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3130,9 +3082,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3309,9 +3258,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3326,9 +3272,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3343,9 +3286,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3360,9 +3300,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3377,9 +3314,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3394,9 +3328,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3411,9 +3342,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3428,9 +3356,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3445,9 +3370,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3462,9 +3384,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3479,9 +3398,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3496,9 +3412,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -3513,9 +3426,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
Reference in New Issue
Block a user