working dev environment

This commit is contained in:
2026-06-18 08:29:03 +00:00
parent 6fc00b8ce9
commit 57b882128d
21 changed files with 200 additions and 210 deletions
+3 -2
View File
@@ -31,6 +31,7 @@ See [README.md](README.md) for a full architecture map. Key files:
- **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 (dev):** Run the API, then use Swagger UI at [http://localhost:8000/docs](http://localhost:8000/docs) to create/update content. - **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`. - **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:** `docker compose up --build` → [localhost:4200](http://localhost:4200). - **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. - **Build:** `ng build``dist/`, then `docker compose up --build -d` for full stack.
+6 -1
View File
@@ -1,7 +1,12 @@
{ {
"permissions": { "permissions": {
"allow": [ "allow": [
"*" "Bash(*)",
"Read(*)",
"Edit(*)",
"Write(*)",
"Glob(*)",
"Grep(*)"
], ],
"additionalDirectories": [ "additionalDirectories": [
"/workspaces/web_app/.vscode" "/workspaces/web_app/.vscode"
+2 -4
View File
@@ -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 # 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
+2 -1
View File
@@ -7,7 +7,8 @@
"vscode": { "vscode": {
"extensions": [ "extensions": [
"Angular.ng-template", "Angular.ng-template",
"magnobiet.sass-extension-pack" "magnobiet.sass-extension-pack",
"Anthropic.claude-code"
] ]
} }
} }
+3
View File
@@ -45,3 +45,6 @@ Thumbs.db
# Environment files # Environment files
.env .env
# Dev SQLite database
backend/kmountain.db
+62 -72
View File
@@ -6,27 +6,19 @@ Renders a hero page, about page, broadcast schedule, donation tiers, upcoming ev
## Quick Start ## Quick Start
### Option A — Docker Compose (recommended) ### Option A — Dev Container (recommended)
```bash 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.
# 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). Open [http://localhost:4200](http://localhost:4200).
### Option B — Manual (frontend + backend separately) ### Option B — Manual (frontend + backend separately)
```bash ```bash
# Database # Backend (uses SQLite for local dev — no database server needed)
createdb kmountain cd backend
export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain pip install -r requirements.txt
export KMTN_DATABASE_URL="sqlite+aiosqlite:///./kmountain.db"
# Backend
cd backend && pip install -r requirements.txt
uvicorn app.main:app --reload uvicorn app.main:app --reload
# Frontend (new terminal) # Frontend (new terminal)
@@ -39,11 +31,10 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
| Requirement | Version | Notes | | Requirement | Version | Notes |
|---|---|---| |---|---|---|
| Docker + Docker Compose | 24+ | For the Docker Compose path | | Node.js | 20+ | For frontend development |
| Node.js | 20+ | For manual frontend development |
| npm | 11+ | | | npm | 11+ | |
| Python | 3.12+ | For manual backend development | | Python | 3.12+ | For backend development |
| PostgreSQL | 16+ | For manual backend development (Docker handles it) | | PostgreSQL | 16+ | For production only (dev uses SQLite) |
## Architecture ## Architecture
@@ -58,29 +49,28 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
┌────▼─────┐ ┌──────▼─────┐ ┌────▼─────┐ ┌──────▼─────┐
│ Nginx │ /api/* │ FastAPI │ │ Nginx │ /api/* │ FastAPI │
│ (static) │─ proxy ──▶ │ :8000 │ │ (static) │─ proxy ──▶ │ :8000 │
└─────────┘ └──────┬─────┘ └─────────┘ └──────┬─────┘
┌────▼─────┐ ┌──────▼─────┐ ┌──────▼─────┐
│ Angular 21│ │ PostgreSQL │ │ PostgreSQL │
SPA │ :5432 │ │ :5432 │
└──────────┘ └────────────┘ └────────────┘
``` ```
**Three tiers:** **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`. 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 ## Project Structure
``` ```
. .
├── docker-compose.yml # 3-service stack (db, api, frontend) ├── docker-compose.dev.yml # Dev-sidecar compose (dev container)
├── Dockerfile # Multi-stage: Angular build → Nginx serve
├── nginx.conf # Static files + /api/* proxy
├── .env.example # Environment variable template
├── .devcontainer/ # VS Code 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 ├── src/ # Angular 21 frontend
│ ├── app/ # Standalone components │ ├── 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 │ └── _themes.scss # SCSS → CSS custom properties
└── backend/ # FastAPI backend └── backend/ # FastAPI backend
├── Dockerfile # Python gunicorn/uvicorn
├── requirements.txt ├── requirements.txt
├── seed.py # Initial data for all models ├── seed.py # Initial data for all models
└── app/ └── app/
@@ -112,43 +101,40 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
## Local Development ## Local Development
### Docker Compose ### Dev Container (recommended)
```bash Open the repo in VS Code → **Reopen in Container**. The dev container starts both services automatically. See [.devcontainer/](.devcontainer/) for the configuration.
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.
### Manual Development ### Manual Development
Run all three layers in parallel: Run both layers in parallel — development uses SQLite (no database server needed):
```bash ```bash
# 1. Database (install PostgreSQL 16 locally) # 1. Backend (new terminal)
createdb kmountain
# 2. Backend (new terminal)
cd backend cd backend
pip install -r requirements.txt 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"]' export KMTN_CORS_ORIGINS='["http://localhost:4200"]'
uvicorn app.main:app --reload uvicorn app.main:app --reload
# 3. Frontend (another terminal) # 2. Frontend (another terminal)
npm install npm install
ng serve 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 ## Configuration
| Variable | Default | Where | Description | | 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_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 | | `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. 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 ```bash
# Frontend (produces dist/radio-station/browser/) # Frontend (produces dist/radio-station/browser/)
ng build --configuration production 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 ## Testing
```bash ```bash
ng test # Angular + Vitest 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 ## API Reference
@@ -200,33 +182,41 @@ Full interactive API docs at [http://localhost:8000/docs](http://localhost:8000/
## Deployment ## 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 ```bash
# 1. Clone repo on server # 1. Build the frontend
git clone <repo-url> kmountain ng build --configuration production
cd kmountain
# 2. Edit docker-compose.yml # 2. Configure the backend
# - Change POSTGRES_PASSWORD to a strong value export KMTN_DATABASE_URL="postgresql+asyncpg://user:pass@host:5432/kmountain"
# - Update KMTN_CORS_ORIGINS to your domain export KMTN_CORS_ORIGINS='["https://yourdomain.com"]'
# - Optionally set KMTN_DATABASE_URL to a managed PostgreSQL export KMTN_ENV="production"
# 3. Build and start # 3. Start the backend
docker compose up --build -d cd backend
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000
# 4. Seed the database # 4. Serve the frontend with Nginx (or your preferred web server),
docker compose exec api python seed.py # pointing /api/* requests to http://localhost:8000
``` ```
The database auto-seeds on first startup.
### Production hardening checklist ### 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`) - [ ] 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 - [ ] Add a reverse proxy (Caddy, Traefik) in front of Nginx for HTTPS
- [ ] Set up log rotation for Docker containers - [ ] Set up log rotation
- [ ] Configure Docker image registry and push/pull workflow - [ ] Configure a process manager (systemd, PM2) for the backend
## Development Tools ## Development Tools
@@ -245,6 +235,6 @@ docker compose exec api python seed.py
| Styling | SCSS with design tokens (variables → mixins → CSS custom properties) | | Styling | SCSS with design tokens (variables → mixins → CSS custom properties) |
| Routing | Angular Router (lazy loaded) + Nginx SPA fallback | | Routing | Angular Router (lazy loaded) + Nginx SPA fallback |
| HTTP | RxJS observables (frontend → FastAPI) | | 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)_ | | CI/CD | _(not yet configured)_ |
| Testing | Vitest (via Angular CLI) | | 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.
+26
View File
@@ -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"}
+1
View File
@@ -4,6 +4,7 @@ from pydantic_settings import BaseSettings
class Settings(BaseSettings): class Settings(BaseSettings):
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain" DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain"
CORS_ORIGINS: list[str] = ["http://localhost:4200", "http://localhost:3000"] CORS_ORIGINS: list[str] = ["http://localhost:4200", "http://localhost:3000"]
ENV: str = "development"
model_config = {"env_prefix": "KMTN_"} model_config = {"env_prefix": "KMTN_"}
+19 -3
View File
@@ -2,18 +2,29 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import func, select
from app.config import settings from app.config import settings
from app.database import engine from app.database import async_session, engine
from app.models import Base from app.models import Base, Program
from app.api import programs, events, tiers from app.api import programs, events, tiers
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# Create tables on startup (seed.py handles data seeding) # Create tables on startup
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) 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 yield
@@ -36,3 +47,8 @@ app.add_middleware(
app.include_router(programs.router, prefix="/api/programs", tags=["programs"]) app.include_router(programs.router, prefix="/api/programs", tags=["programs"])
app.include_router(events.router, prefix="/api/events", tags=["events"]) app.include_router(events.router, prefix="/api/events", tags=["events"])
app.include_router(tiers.router, prefix="/api/tiers", tags=["tiers"]) 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"])
+2 -2
View File
@@ -6,7 +6,7 @@ from sqlalchemy import (
Numeric, Numeric,
Boolean, Boolean,
Date, Date,
ARRAY, JSON,
) )
from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import DeclarativeBase
@@ -47,5 +47,5 @@ class DonationTier(Base):
name = Column(String(50), unique=True, nullable=False) name = Column(String(50), unique=True, nullable=False)
amount = Column(Numeric(10, 2), nullable=False) amount = Column(Numeric(10, 2), nullable=False)
icon = Column(String(10), 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) display_order = Column(Integer, nullable=False)
+1
View File
@@ -2,6 +2,7 @@ fastapi==0.115.6
uvicorn[standard]==0.34.0 uvicorn[standard]==0.34.0
psycopg2-binary==2.9.10 psycopg2-binary==2.9.10
asyncpg==0.30.0 asyncpg==0.30.0
aiosqlite==0.20.0
sqlalchemy[asyncio]==2.0.36 sqlalchemy[asyncio]==2.0.36
alembic==1.14.1 alembic==1.14.1
python-multipart==0.0.20 python-multipart==0.0.20
+68 -10
View File
@@ -1,14 +1,18 @@
""" """
Seed script: populates the database with station data. Seed script: populates the database with station data.
Idempotent — safe to run multiple times. Uses get-or-create (upsert) for each entity.
Usage: Usage:
DATABASE_URL=postgresql://... python -m app.seed KMTN_DATABASE_URL=... python seed.py
(or from the repo root: cd backend && python seed.py) (run from the backend/ directory)
""" """
import asyncio import asyncio
from datetime import date from datetime import date
from sqlalchemy import select
from app.database import async_session from app.database import async_session
from app.models import Program, Event, DonationTier 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 ────────────────────────────────────────────────── # ── Main ──────────────────────────────────────────────────
async def seed(): async def seed() -> None:
"""Populate the database with station data (idempotent)."""
async with async_session() as session: async with async_session() as session:
# Programs
for data in PROGRAMS: for data in PROGRAMS:
session.add(Program(**data)) await _upsert_program(session, data)
await session.commit() await session.commit()
print(f" ✓ Seeded {len(PROGRAMS)} programs") print(f" ✓ Seeded {len(PROGRAMS)} programs")
# Events
for data in EVENTS: for data in EVENTS:
event = Event(**data, active=True) await _upsert_event(session, data)
session.add(event)
await session.commit() await session.commit()
print(f" ✓ Seeded {len(EVENTS)} events") print(f" ✓ Seeded {len(EVENTS)} events")
# Tiers
for data in TIERS: for data in TIERS:
session.add(DonationTier(**data)) await _upsert_tier(session, data)
await session.commit() await session.commit()
print(f" ✓ Seeded {len(TIERS)} donation tiers") 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__": if __name__ == "__main__":
asyncio.run(seed()) asyncio.run(seed())
+3 -23
View File
@@ -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. # Used by .devcontainer/devcontainer.json via dockerComposeFile.
# Development uses SQLite — no Docker-in-Docker database needed.
services: services:
app: app:
@@ -11,15 +12,12 @@ services:
- "8000:8000" # FastAPI - "8000:8000" # FastAPI
- "4200:4200" # Angular dev server - "4200:4200" # Angular dev server
environment: 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"]' KMTN_CORS_ORIGINS: '["http://localhost:4200"]'
volumes: volumes:
- ..:/workspaces/web_app:cached - ..:/workspaces/web_app:cached
- node_modules:/workspaces/web_app/node_modules - node_modules:/workspaces/web_app/node_modules
- backend_venv:/app/.venv - backend_venv:/app/.venv
depends_on:
db:
condition: service_healthy
command: > command: >
sh -c " sh -c "
pip install -r backend/requirements.txt && pip install -r backend/requirements.txt &&
@@ -30,24 +28,6 @@ services:
npx ng serve --host 0.0.0.0 --port 4200 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: 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: node_modules:
backend_venv: backend_venv:
-90
View File
@@ -2204,9 +2204,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2224,9 +2221,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2244,9 +2238,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2264,9 +2255,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2284,9 +2272,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2304,9 +2289,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2324,9 +2306,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2770,9 +2749,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2794,9 +2770,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2818,9 +2791,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2842,9 +2812,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2866,9 +2833,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2890,9 +2854,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3070,9 +3031,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3090,9 +3048,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3110,9 +3065,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3130,9 +3082,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3309,9 +3258,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3326,9 +3272,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3343,9 +3286,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3360,9 +3300,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3377,9 +3314,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3394,9 +3328,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3411,9 +3342,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3428,9 +3356,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3445,9 +3370,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3462,9 +3384,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3479,9 +3398,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3496,9 +3412,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3513,9 +3426,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [