from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from sqlalchemy import func from typing import List from database import get_db from models.episode import Episode from models.project import Project, ProjectMember from models.user import User, UserRole from models.shot import Shot from schemas.episode import EpisodeCreate, EpisodeUpdate, EpisodeResponse, EpisodeListResponse from utils.auth import get_current_user, require_role, get_current_user_from_token router = APIRouter() def get_current_user_with_db( token_data: dict = Depends(get_current_user_from_token), db: Session = Depends(get_db) ): """Get current user with proper database dependency.""" from utils.auth import _get_user_from_db return _get_user_from_db(db, token_data["user_id"]) def require_coordinator_or_admin( token_data: dict = Depends(get_current_user_from_token), db: Session = Depends(get_db) ): """Require coordinator or admin role.""" from utils.auth import _get_user_from_db current_user = _get_user_from_db(db, token_data["user_id"]) if current_user.role != UserRole.COORDINATOR and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions" ) return current_user @router.get("/", response_model=List[EpisodeListResponse]) async def list_episodes( project_id: int = None, skip: int = 0, limit: int = 100, db: Session = Depends(get_db), current_user: User = Depends(get_current_user_with_db) ): """List episodes, optionally filtered by project""" query = db.query(Episode) if project_id: # Check if project exists and user has access project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Project not found" ) # Check access for artists if current_user.role == UserRole.ARTIST: member = db.query(ProjectMember).filter( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id ).first() if not member: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to this project" ) query = query.filter(Episode.project_id == project_id) else: # For artists, only show episodes from projects they're members of if current_user.role == UserRole.ARTIST: user_project_ids = db.query(ProjectMember.project_id).filter( ProjectMember.user_id == current_user.id ).subquery() query = query.filter(Episode.project_id.in_(user_project_ids)) episodes = query.order_by(Episode.episode_number).offset(skip).limit(limit).all() # Add shot count for each episode result = [] for episode in episodes: shot_count = db.query(Shot).filter(Shot.episode_id == episode.id).count() episode_data = EpisodeListResponse.model_validate(episode) episode_data.shot_count = shot_count result.append(episode_data) return result @router.post("/", response_model=EpisodeResponse, status_code=status.HTTP_201_CREATED) async def create_episode( episode: EpisodeCreate, project_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_coordinator_or_admin) ): """Create a new episode within a project""" # Check if project exists project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Project not found" ) # Check if episode number is already used in this project existing_episode = db.query(Episode).filter( Episode.project_id == project_id, Episode.episode_number == episode.episode_number ).first() if existing_episode: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Episode number {episode.episode_number} already exists in this project" ) # Create new episode db_episode = Episode( project_id=project_id, **episode.model_dump() ) db.add(db_episode) db.commit() db.refresh(db_episode) episode_data = EpisodeResponse.model_validate(db_episode) episode_data.shot_count = 0 # New episode has no shots yet return episode_data @router.get("/{episode_id}", response_model=EpisodeResponse) async def get_episode( episode_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user_with_db) ): """Get a specific episode by ID""" episode = db.query(Episode).filter(Episode.id == episode_id).first() if not episode: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Episode not found" ) # Check access for artists if current_user.role == UserRole.ARTIST: member = db.query(ProjectMember).filter( ProjectMember.project_id == episode.project_id, ProjectMember.user_id == current_user.id ).first() if not member: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to this episode" ) # Add shot count shot_count = db.query(Shot).filter(Shot.episode_id == episode.id).count() episode_data = EpisodeResponse.model_validate(episode) episode_data.shot_count = shot_count return episode_data @router.put("/{episode_id}", response_model=EpisodeResponse) async def update_episode( episode_id: int, episode_update: EpisodeUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_coordinator_or_admin) ): """Update an episode""" db_episode = db.query(Episode).filter(Episode.id == episode_id).first() if not db_episode: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Episode not found" ) # If updating episode number, check for conflicts update_data = episode_update.model_dump(exclude_unset=True) if 'episode_number' in update_data: existing_episode = db.query(Episode).filter( Episode.project_id == db_episode.project_id, Episode.episode_number == update_data['episode_number'], Episode.id != episode_id ).first() if existing_episode: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Episode number {update_data['episode_number']} already exists in this project" ) # Update only provided fields for field, value in update_data.items(): setattr(db_episode, field, value) db.commit() db.refresh(db_episode) # Add shot count shot_count = db.query(Shot).filter(Shot.episode_id == db_episode.id).count() episode_data = EpisodeResponse.model_validate(db_episode) episode_data.shot_count = shot_count return episode_data @router.delete("/{episode_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_episode( episode_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_coordinator_or_admin) ): """Delete an episode""" db_episode = db.query(Episode).filter(Episode.id == episode_id).first() if not db_episode: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Episode not found" ) db.delete(db_episode) db.commit() # Project-specific episode endpoints @router.get("/projects/{project_id}/episodes", response_model=List[EpisodeListResponse]) async def list_project_episodes( project_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db), current_user: User = Depends(get_current_user_with_db) ): """List all episodes for a specific project""" # Check if project exists project = db.query(Project).filter(Project.id == project_id).first() if not project: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Project not found" ) # Check access for artists if current_user.role == UserRole.ARTIST: member = db.query(ProjectMember).filter( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id ).first() if not member: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to this project" ) episodes = db.query(Episode).filter( Episode.project_id == project_id ).order_by(Episode.episode_number).offset(skip).limit(limit).all() # Add shot count for each episode result = [] for episode in episodes: shot_count = db.query(Shot).filter(Shot.episode_id == episode.id).count() episode_data = EpisodeListResponse.model_validate(episode) episode_data.shot_count = shot_count result.append(episode_data) return result @router.post("/projects/{project_id}/episodes", response_model=EpisodeResponse, status_code=status.HTTP_201_CREATED) async def create_project_episode( project_id: int, episode: EpisodeCreate, db: Session = Depends(get_db), current_user: User = Depends(require_coordinator_or_admin) ): """Create a new episode within a specific project""" return await create_episode(episode, project_id, db, current_user)