from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.core.database import get_db from app.core.security import get_current_user from app.models import User, Project, ProjectMember, ProjectSong, Song from app.schemas import ( ProjectCreate, ProjectUpdate, ProjectOut, InviteMember, ProjectSongAdd, ProjectSongStatusUpdate, ProjectSongOut, ) router = APIRouter(prefix="/projects", tags=["projects"]) def _get_project_or_404(project_id: str, db: Session) -> Project: p = db.query(Project).filter(Project.id == project_id).first() if not p: raise HTTPException(404, "Projekt ikke fundet") return p def _assert_role(project: Project, user: User, db: Session, min_role: str = "viewer"): roles = ["viewer", "editor", "owner"] if project.owner_id == user.id: return # ejer har altid adgang member = db.query(ProjectMember).filter_by(project_id=project.id, user_id=user.id, status="accepted").first() if not member: if project.visibility == "public" and min_role == "viewer": return raise HTTPException(403, "Du har ikke adgang til dette projekt") if roles.index(member.role) < roles.index(min_role): raise HTTPException(403, "Din rolle giver ikke rettighed til dette") # ── CRUD ────────────────────────────────────────────────────────────────────── @router.get("/", response_model=list[ProjectOut]) def list_projects(db: Session = Depends(get_db), me: User = Depends(get_current_user)): owned = db.query(Project).filter(Project.owner_id == me.id).all() member_ids = [m.project_id for m in db.query(ProjectMember).filter_by(user_id=me.id, status="accepted").all()] shared = db.query(Project).filter(Project.id.in_(member_ids)).all() return list({p.id: p for p in owned + shared}.values()) @router.post("/", response_model=ProjectOut, status_code=201) def create_project(data: ProjectCreate, db: Session = Depends(get_db), me: User = Depends(get_current_user)): project = Project(owner_id=me.id, **data.model_dump()) db.add(project) db.commit() db.refresh(project) return project @router.get("/{project_id}", response_model=ProjectOut) def get_project(project_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "viewer") return p @router.patch("/{project_id}", response_model=ProjectOut) def update_project(project_id: str, data: ProjectUpdate, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "editor") for field, val in data.model_dump(exclude_none=True).items(): setattr(p, field, val) db.commit() db.refresh(p) return p @router.delete("/{project_id}", status_code=204) def delete_project(project_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) if p.owner_id != me.id: raise HTTPException(403, "Kun ejeren kan slette projektet") db.delete(p) db.commit() # ── Invitationer ────────────────────────────────────────────────────────────── @router.post("/{project_id}/invite", status_code=201) def invite_member(project_id: str, data: InviteMember, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) if p.owner_id != me.id: raise HTTPException(403, "Kun ejeren kan invitere") target = db.query(User).filter(User.username == data.username).first() if not target: raise HTTPException(404, f"Brugeren '{data.username}' findes ikke") if target.id == me.id: raise HTTPException(400, "Du kan ikke invitere dig selv") existing = db.query(ProjectMember).filter_by(project_id=project_id, user_id=target.id).first() if existing: raise HTTPException(400, "Brugeren er allerede inviteret eller medlem") member = ProjectMember(project_id=project_id, user_id=target.id, role=data.role, status="pending") db.add(member) db.commit() return {"detail": f"{data.username} er inviteret som {data.role}"} @router.get("/invitations/pending") def get_pending_invitations(db: Session = Depends(get_db), me: User = Depends(get_current_user)): invitations = db.query(ProjectMember).filter_by(user_id=me.id, status="pending").all() return [ {"invitation_id": inv.id, "project_id": inv.project_id, "role": inv.role, "invited_at": inv.invited_at} for inv in invitations ] @router.post("/invitations/{invitation_id}/accept") def accept_invitation(invitation_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): inv = db.query(ProjectMember).filter_by(id=invitation_id, user_id=me.id).first() if not inv: raise HTTPException(404, "Invitation ikke fundet") inv.status = "accepted" db.commit() return {"detail": "Invitation accepteret"} @router.delete("/invitations/{invitation_id}") def decline_invitation(invitation_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): inv = db.query(ProjectMember).filter_by(id=invitation_id, user_id=me.id).first() if not inv: raise HTTPException(404, "Invitation ikke fundet") db.delete(inv) db.commit() return {"detail": "Invitation afvist"} # ── Danseliste (ProjectSongs) ───────────────────────────────────────────────── @router.get("/{project_id}/songs", response_model=list[ProjectSongOut]) def list_project_songs(project_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "viewer") return p.project_songs @router.post("/{project_id}/songs", response_model=ProjectSongOut, status_code=201) def add_song_to_project(project_id: str, data: ProjectSongAdd, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "editor") song = db.query(Song).filter(Song.id == data.song_id).first() if not song: raise HTTPException(404, "Sang ikke fundet") position = data.position if position is None: last = db.query(ProjectSong).filter_by(project_id=project_id).order_by(ProjectSong.position.desc()).first() position = (last.position + 1) if last else 1 ps = ProjectSong(project_id=project_id, song_id=data.song_id, position=position) db.add(ps) db.commit() db.refresh(ps) return ps @router.patch("/{project_id}/songs/{ps_id}/status", response_model=ProjectSongOut) def update_song_status(project_id: str, ps_id: str, data: ProjectSongStatusUpdate, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "editor") ps = db.query(ProjectSong).filter_by(id=ps_id, project_id=project_id).first() if not ps: raise HTTPException(404, "Sang ikke fundet i projektet") valid = {"pending", "playing", "played", "skipped"} if data.status not in valid: raise HTTPException(400, f"Ugyldig status. Vælg én af: {valid}") ps.status = data.status db.commit() db.refresh(ps) return ps @router.delete("/{project_id}/songs/{ps_id}", status_code=204) def remove_song_from_project(project_id: str, ps_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user)): p = _get_project_or_404(project_id, db) _assert_role(p, me, db, "editor") ps = db.query(ProjectSong).filter_by(id=ps_id, project_id=project_id).first() if not ps: raise HTTPException(404, "Sang ikke fundet i projektet") db.delete(ps) db.commit()