diff --git a/backend/app/__pycache__/main.cpython-312.pyc b/backend/app/__pycache__/main.cpython-312.pyc index 985cda8..a02c9d8 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/api/__pycache__/shows.cpython-312.pyc b/backend/app/api/__pycache__/shows.cpython-312.pyc index 6d07bfe..ee5d992 100644 Binary files a/backend/app/api/__pycache__/shows.cpython-312.pyc and b/backend/app/api/__pycache__/shows.cpython-312.pyc differ diff --git a/backend/app/api/__pycache__/upload.cpython-312.pyc b/backend/app/api/__pycache__/upload.cpython-312.pyc new file mode 100644 index 0000000..41cfc31 Binary files /dev/null and b/backend/app/api/__pycache__/upload.cpython-312.pyc differ diff --git a/backend/app/api/shows.py b/backend/app/api/shows.py index 0115bb8..159e0f4 100644 --- a/backend/app/api/shows.py +++ b/backend/app/api/shows.py @@ -89,6 +89,7 @@ async def update_show( # Replace all schedule slots for sched in show.schedules: await session.delete(sched) + await session.flush() # execute deletes before inserts to avoid UNIQUE conflict for slot in payload.schedules: session.add(ShowSchedule(show_id=show.id, **slot.model_dump())) diff --git a/backend/app/api/upload.py b/backend/app/api/upload.py new file mode 100644 index 0000000..8050a55 --- /dev/null +++ b/backend/app/api/upload.py @@ -0,0 +1,59 @@ +"""Shared image upload router — admin-only, reused across all forms.""" + +import os +import uuid + +from fastapi import APIRouter, Depends, HTTPException, UploadFile, status + +from app.auth import get_current_admin_user +from app.user_models import User + +router = APIRouter() + +UPLOAD_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", +) +MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB + + +@router.post("/image") +async def upload_image( + file: UploadFile, + current_user: User = Depends(get_current_admin_user), +): + """Upload an image file. Admin only. + + Saves the file to the uploads/ directory with a UUID filename. + Returns the relative URL path. + """ + # Validate MIME type + if not file.content_type or not file.content_type.startswith("image/"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Only image files are allowed", + ) + + # Read and validate size + content = await file.read() + if len(content) > MAX_FILE_SIZE: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="File size exceeds 5 MB limit", + ) + + # Ensure upload directory exists + os.makedirs(UPLOAD_DIR, exist_ok=True) + + # Generate unique filename preserving extension + ext = ( + os.path.splitext(file.filename or "upload")[1] + if file.filename + else ".bin" + ) + filename = f"{uuid.uuid4().hex}{ext}" + filepath = os.path.join(UPLOAD_DIR, filename) + + with open(filepath, "wb") as f: + f.write(content) + + return {"url": f"uploads/{filename}"} diff --git a/backend/app/main.py b/backend/app/main.py index 390a9d2..c042cf3 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -10,7 +10,7 @@ from app.config import settings from app.database import async_session, engine from app.models import Base, Program, Show, StationConfig, HistoryEntry, TeamMember, CommunityHighlight from app.user_models import User -from app.api import programs, events, tiers, auth, station_config, history, team, community, shows +from app.api import programs, events, tiers, auth, station_config, history, team, community, shows, upload async def _ensure_station_config(session): @@ -100,6 +100,7 @@ app.include_router(history.router, prefix="/api/history", tags=["history"]) app.include_router(team.router, prefix="/api/team", tags=["team"]) app.include_router(community.router, prefix="/api/community", tags=["community"]) app.include_router(shows.router, prefix="/api/shows", tags=["shows"]) +app.include_router(upload.router, prefix="/api/upload", tags=["upload"]) # Serve uploaded files (dev only — Nginx handles this in production) if settings.ENV != "production": diff --git a/backend/uploads/310efff04f7541fd99fe9999ed3c9514.png b/backend/uploads/310efff04f7541fd99fe9999ed3c9514.png new file mode 100644 index 0000000..f26e887 Binary files /dev/null and b/backend/uploads/310efff04f7541fd99fe9999ed3c9514.png differ diff --git a/backend/uploads/505882024b134ecab7e18cd3b379ffb4.avif b/backend/uploads/505882024b134ecab7e18cd3b379ffb4.avif new file mode 100644 index 0000000..85d4c6c Binary files /dev/null and b/backend/uploads/505882024b134ecab7e18cd3b379ffb4.avif differ diff --git a/backend/uploads/6716edc12951477ab017fc4ca1631f58.jpeg b/backend/uploads/6716edc12951477ab017fc4ca1631f58.jpeg new file mode 100644 index 0000000..29e377c Binary files /dev/null and b/backend/uploads/6716edc12951477ab017fc4ca1631f58.jpeg differ diff --git a/backend/uploads/84e7deb652ed4680abfd5b24af6e1115.avif b/backend/uploads/84e7deb652ed4680abfd5b24af6e1115.avif new file mode 100644 index 0000000..22189ee Binary files /dev/null and b/backend/uploads/84e7deb652ed4680abfd5b24af6e1115.avif differ diff --git a/backend/uploads/90e029c6be204cb8b227b9362b7b6cd6.svg b/backend/uploads/90e029c6be204cb8b227b9362b7b6cd6.svg new file mode 100644 index 0000000..51236a5 --- /dev/null +++ b/backend/uploads/90e029c6be204cb8b227b9362b7b6cd6.svg @@ -0,0 +1,116 @@ + diff --git a/backend/uploads/ae5da74eca0345a09406c58a638c15c1.avif b/backend/uploads/ae5da74eca0345a09406c58a638c15c1.avif new file mode 100644 index 0000000..1293528 Binary files /dev/null and b/backend/uploads/ae5da74eca0345a09406c58a638c15c1.avif differ diff --git a/backend/uploads/b174d771f87343249ff90e8e310876cc.png b/backend/uploads/b174d771f87343249ff90e8e310876cc.png new file mode 100644 index 0000000..f26e887 Binary files /dev/null and b/backend/uploads/b174d771f87343249ff90e8e310876cc.png differ diff --git a/backend/uploads/bd117493900e4cc89d5bcc8ff0071355.svg b/backend/uploads/bd117493900e4cc89d5bcc8ff0071355.svg new file mode 100644 index 0000000..51236a5 --- /dev/null +++ b/backend/uploads/bd117493900e4cc89d5bcc8ff0071355.svg @@ -0,0 +1,116 @@ + diff --git a/backend/uploads/f4e5c71530a24463b665666a14ad9996.png b/backend/uploads/f4e5c71530a24463b665666a14ad9996.png new file mode 100644 index 0000000..f26e887 Binary files /dev/null and b/backend/uploads/f4e5c71530a24463b665666a14ad9996.png differ diff --git a/backend/uploads/ff1b086f19a248708ad8093559adbc6f.jpeg b/backend/uploads/ff1b086f19a248708ad8093559adbc6f.jpeg new file mode 100644 index 0000000..d6cc8aa Binary files /dev/null and b/backend/uploads/ff1b086f19a248708ad8093559adbc6f.jpeg differ diff --git a/src/app/admin/admin-show-form.component.html b/src/app/admin/admin-show-form.component.html index 2da8825..87656bf 100644 --- a/src/app/admin/admin-show-form.component.html +++ b/src/app/admin/admin-show-form.component.html @@ -8,6 +8,9 @@ @if (error) {