diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1f3fc4f..4bee870 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,10 @@ FROM python:3.12-alpine -RUN apk update && apk add git nodejs npm bash +RUN apk update && apk add git nodejs npm bash curl tmux # Install Angular.js via NPM RUN npm install -g @angular/cli@21.2.14 -RUN /bin/bash \ No newline at end of file +RUN curl -fsSL https://claude.ai/install.sh | bash + +ENV PATH="$PATH:/root/.local/bin" \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 28b2c57..8794749 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,6 +3,11 @@ "build": { "dockerfile": "Dockerfile" }, + "containerEnv": { + "ANTHROPIC_BASE_URL": "http://192.168.1.108:1234", + "ANTHROPIC_AUTH_TOKEN": "lmstudio", + "ANTHROPIC_MODEL": "qwen/qwen3.6-27b" + }, "customizations": { "vscode": { "extensions": [ diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index 025c914..905da32 100644 Binary files a/backend/app/__pycache__/main.cpython-312.pyc and b/backend/app/__pycache__/main.cpython-312.pyc differ diff --git a/backend/app/__pycache__/models.cpython-312.pyc b/backend/app/__pycache__/models.cpython-312.pyc index 99b089b..cc520cf 100644 Binary files a/backend/app/__pycache__/models.cpython-312.pyc and b/backend/app/__pycache__/models.cpython-312.pyc differ diff --git a/backend/app/__pycache__/schemas.cpython-312.pyc b/backend/app/__pycache__/schemas.cpython-312.pyc index 7164094..9d19e86 100644 Binary files a/backend/app/__pycache__/schemas.cpython-312.pyc and b/backend/app/__pycache__/schemas.cpython-312.pyc differ diff --git a/backend/app/main.py b/backend/app/main.py index fa77168..1e63e80 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -11,6 +11,45 @@ from app.user_models import User from app.api import events, auth, station_config, history, team, community, shows, storage, underwriters +def _migrate_add_missing_columns(sync_conn): + """Add missing columns to existing tables (startup migration — no Alembic). + + Handles both SQLite and PostgreSQL. Idempotent: skips columns that already exist. + Receives a sync SQLAlchemy Connection from engine.run_sync(). + """ + import sqlalchemy as sa + + # Column definitions: (table, name, type, default) + pending = [ + ("station_config", "stream_url", sa.String(500), ""), + ("station_config", "stream_metadata_url", sa.String(500), ""), + ] + + # Determine dialect + dialect = sync_conn.dialect.name # "sqlite" | "postgresql" + + inspector = sa.inspect(sync_conn) + + for table_name, col_name, col_type, default in pending: + # Check if column already exists + existing = inspector.get_columns(table_name) + if any(c["name"] == col_name for c in existing): + continue + + print(f" → Migrating: adding column {table_name}.{col_name}") + + if dialect == "sqlite": + sync_conn.execute( + sa.text(f'ALTER TABLE {table_name} ADD COLUMN "{col_name}" TEXT NOT NULL DEFAULT "{default}"') + ) + else: + sync_conn.execute( + sa.text(f'ALTER TABLE {table_name} ADD COLUMN "{col_name}" VARCHAR(500) NOT NULL DEFAULT "{default}"') + ) + + print(f" ✓ Added {table_name}.{col_name}") + + async def _ensure_station_config(session): """Ensure the singleton station config row exists (idempotent).""" result = await session.execute( @@ -28,9 +67,10 @@ async def _ensure_station_config(session): @asynccontextmanager async def lifespan(app: FastAPI): - # Create tables on startup + # Create tables on startup, then patch any missing columns async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) + await conn.run_sync(_migrate_add_missing_columns) # Auto-seed if database is empty async with async_session() as session: diff --git a/backend/app/models.py b/backend/app/models.py index 1937e5e..0c4b842 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -94,6 +94,10 @@ class StationConfig(Base): # Donation donation_url = Column(String(500), nullable=False, default="") + # Stream + stream_url = Column(String(500), nullable=False, default="") + stream_metadata_url = Column(String(500), nullable=False, default="") + class HistoryEntry(Base): __tablename__ = "history_entries" diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 8ca831b..379102a 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -73,6 +73,8 @@ class StationConfigResponse(BaseModel): email: str website: str donation_url: str + stream_url: str + stream_metadata_url: str model_config = {"from_attributes": True} @@ -95,6 +97,8 @@ class StationConfigUpdate(BaseModel): email: Optional[str] = None website: Optional[str] = None donation_url: Optional[str] = None + stream_url: Optional[str] = None + stream_metadata_url: Optional[str] = None # ── HistoryEntry ────────────────────────────────────────── diff --git a/backend/seed.py b/backend/seed.py index 8973089..f2a6f54 100644 --- a/backend/seed.py +++ b/backend/seed.py @@ -212,6 +212,8 @@ STATION_CONFIG: dict = { "email": "hello@kmountainflower.org", "website": "kmountainflower.org", "donation_url": "", + "stream_url": "", + "stream_metadata_url": "", } HISTORY_ENTRIES: list[dict] = [ diff --git a/src/app/admin/admin-station-form.component.html b/src/app/admin/admin-station-form.component.html index f06d5c4..f7a85b7 100644 --- a/src/app/admin/admin-station-form.component.html +++ b/src/app/admin/admin-station-form.component.html @@ -139,6 +139,21 @@ + +