Refine gameday bootstrap layout

This commit is contained in:
Codex
2026-04-22 17:08:02 -05:00
parent 3bf3950899
commit 72360cc4dd
11 changed files with 58 additions and 467 deletions

View File

@@ -81,20 +81,3 @@ class GameAssignment(Base):
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
clip: Mapped[AudioClip] = relationship()
class PlaybackSession(Base):
__tablename__ = "playback_sessions"
id: Mapped[int] = mapped_column(primary_key=True)
external_team_id: Mapped[str] = mapped_column(String(128), index=True)
external_game_id: Mapped[str] = mapped_column(String(128), index=True)
gameday_session_id: Mapped[int | None] = mapped_column(ForeignKey("user_sessions.id"))
current_assignment_id: Mapped[int | None] = mapped_column(ForeignKey("game_assignments.id"))
state: Mapped[str] = mapped_column(String(32), default="idle")
last_triggered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, onupdate=utcnow)
gameday_session: Mapped[UserSession | None] = relationship()
current_assignment: Mapped[GameAssignment | None] = relationship()

View File

@@ -3,19 +3,16 @@ from __future__ import annotations
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select, update
from sqlalchemy import select
from sqlalchemy.orm import Session
from ..auth import require_session
from ..database import get_db
from ..models import AudioClip, GameAssignment, PlaybackSession, UserSession
from ..models import AudioClip, GameAssignment, UserSession
from ..schemas import (
GameAssignmentCreate,
GameAssignmentResponse,
GamePrepResponse,
PlaybackAction,
PlaybackSessionCreate,
PlaybackSessionResponse,
)
router = APIRouter(prefix="/games", tags=["games"])
@@ -133,7 +130,6 @@ def delete_assignment(
if external_player_id is not None and assignment.external_player_id != external_player_id:
raise HTTPException(status_code=403, detail="Pin does not belong to that player")
db.execute(update(PlaybackSession).where(PlaybackSession.current_assignment_id == assignment.id).values(current_assignment_id=None))
db.delete(assignment)
db.commit()
@@ -157,61 +153,3 @@ def prepare_game(
prepared_at=datetime.now(timezone.utc),
assignments=[assignment_to_response(assignment) for assignment in assignments],
)
@router.post("/{external_game_id}/gameday/session", response_model=PlaybackSessionResponse)
def create_gameday_session(
external_game_id: str,
payload: PlaybackSessionCreate,
session: UserSession = Depends(require_session),
db: Session = Depends(get_db),
) -> PlaybackSessionResponse:
playback = PlaybackSession(
external_team_id=payload.external_team_id,
external_game_id=external_game_id,
gameday_session_id=session.id,
state="idle",
)
db.add(playback)
db.commit()
db.refresh(playback)
return PlaybackSessionResponse.model_validate(playback, from_attributes=True)
@router.post("/{external_game_id}/gameday/session/{playback_session_id}/trigger", response_model=PlaybackSessionResponse)
def trigger_gameday(
external_game_id: str,
playback_session_id: int,
payload: PlaybackAction,
_: UserSession = Depends(require_session),
db: Session = Depends(get_db),
) -> PlaybackSessionResponse:
playback = db.get(PlaybackSession, playback_session_id)
if playback is None or playback.external_game_id != external_game_id:
raise HTTPException(status_code=404, detail="Playback session not found")
if payload.assignment_id is None and payload.clip_id is None:
raise HTTPException(status_code=422, detail="Provide a pin or clip to trigger")
if payload.assignment_id is not None:
assignment = db.get(GameAssignment, payload.assignment_id)
if assignment is None or assignment.external_game_id != external_game_id:
raise HTTPException(status_code=404, detail="Pin not found")
if assignment.clip.hidden:
raise HTTPException(status_code=404, detail="Pin not found")
playback.current_assignment_id = assignment.id
else:
clip = db.get(AudioClip, payload.clip_id)
if clip is None or clip.asset.external_team_id != playback.external_team_id:
raise HTTPException(status_code=404, detail="Clip not found")
if clip.hidden:
raise HTTPException(status_code=404, detail="Clip not found")
if payload.external_player_id and clip.asset.owner_external_player_id != payload.external_player_id:
raise HTTPException(status_code=403, detail="Clip does not belong to that player")
playback.current_assignment_id = None
playback.state = payload.state
playback.last_triggered_at = datetime.now(timezone.utc)
db.commit()
db.refresh(playback)
return PlaybackSessionResponse.model_validate(playback, from_attributes=True)

View File

@@ -6,12 +6,12 @@ from pathlib import Path
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse
from sqlalchemy import delete, func, select, update
from sqlalchemy import delete, func, select
from sqlalchemy.orm import Session
from ..auth import require_session
from ..database import get_db
from ..models import AudioAsset, AudioClip, GameAssignment, PlaybackSession, UserSession
from ..models import AudioAsset, AudioClip, GameAssignment, UserSession
from ..schemas import (
AudioAssetImportCreate,
AudioAssetResponse,
@@ -239,14 +239,7 @@ def delete_asset(
clips = db.scalars(select(AudioClip).where(AudioClip.asset_id == asset.id)).all()
clip_ids = [clip.id for clip in clips]
if clip_ids:
assignment_ids = db.scalars(select(GameAssignment.id).where(GameAssignment.clip_id.in_(clip_ids))).all()
db.execute(delete(GameAssignment).where(GameAssignment.clip_id.in_(clip_ids)))
if assignment_ids:
db.execute(
update(PlaybackSession)
.where(PlaybackSession.current_assignment_id.in_(assignment_ids))
.values(current_assignment_id=None)
)
for clip in clips:
if clip.normalized_path:
storage.delete_relative_path(clip.normalized_path)
@@ -361,14 +354,7 @@ def delete_clip(
if not can_manage_asset(session, clip.asset, owner_external_player_id):
raise HTTPException(status_code=403, detail="You can only delete clips from your own uploads")
assignment_ids = db.scalars(select(GameAssignment.id).where(GameAssignment.clip_id == clip.id)).all()
db.execute(delete(GameAssignment).where(GameAssignment.clip_id == clip.id))
if assignment_ids:
db.execute(
update(PlaybackSession)
.where(PlaybackSession.current_assignment_id.in_(assignment_ids))
.values(current_assignment_id=None)
)
if clip.normalized_path:
storage.delete_relative_path(clip.normalized_path)
db.delete(clip)

View File

@@ -126,21 +126,7 @@ class AudioClipReorder(BaseModel):
clip_ids: list[int] = Field(min_length=1)
class PlaybackSessionCreate(BaseModel):
external_team_id: str
class PlaybackAction(BaseModel):
assignment_id: int | None = None
clip_id: int | None = None
external_player_id: str | None = None
state: str = "playing"
class PlaybackSessionResponse(BaseModel):
id: int
external_team_id: str
external_game_id: str
current_assignment_id: int | None
state: str
last_triggered_at: datetime | None