191 lines
8.0 KiB
Python
191 lines
8.0 KiB
Python
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()
|