""" sharing.py — Del playlister med andre brugere. """ from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks from sqlalchemy.orm import Session from pydantic import BaseModel, EmailStr from typing import Optional from app.core.database import get_db from app.core.security import get_current_user from app.models import User, Project, PlaylistShare router = APIRouter(prefix="/sharing", tags=["sharing"]) class ShareRequest(BaseModel): email: EmailStr permission: str = "view" # view | copy | edit class ShareOut(BaseModel): id: str project_id: str invited_email: str permission: str accepted_at: Optional[str] = None class Config: from_attributes = True # ── Del en playliste ────────────────────────────────────────────────────────── @router.post("/playlists/{project_id}/share", status_code=201) async def share_playlist( project_id: str, data: ShareRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): project = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if not project: raise HTTPException(404, "Playliste ikke fundet eller du er ikke ejer") if data.permission not in ("view", "copy", "edit"): raise HTTPException(400, "Ugyldig rettighed — brug view, copy eller edit") # Find bruger via email target = db.query(User).filter_by(email=data.email).first() # Tjek om deling allerede eksisterer existing = db.query(PlaylistShare).filter_by( project_id=project_id, invited_email=data.email, ).first() if existing: existing.permission = data.permission db.commit() return {"detail": "Rettigheder opdateret", "share_id": existing.id} share = PlaylistShare( project_id=project_id, shared_with_id=target.id if target else None, invited_email=data.email, permission=data.permission, ) db.add(share) db.commit() db.refresh(share) # Send invitation-mail try: from app.core.mail import send_share_invitation from app.core.config import settings background_tasks.add_task( send_share_invitation, email=data.email, owner_name=me.username, playlist_name=project.name, permission=data.permission, accept_url=f"{settings.BASE_URL}/sharing/accept/{share.id}", ) except Exception: pass return {"detail": "Invitation sendt", "share_id": share.id} @router.patch("/playlists/{project_id}/share/{share_id}") def update_share( project_id: str, share_id: str, data: ShareRequest, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): project = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if not project: raise HTTPException(404, "Playliste ikke fundet") share = db.query(PlaylistShare).filter_by(id=share_id, project_id=project_id).first() if not share: raise HTTPException(404, "Deling ikke fundet") share.permission = data.permission db.commit() return {"detail": "Rettigheder opdateret"} @router.delete("/playlists/{project_id}/share/{share_id}", status_code=204) def remove_share( project_id: str, share_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): project = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if not project: raise HTTPException(404, "Playliste ikke fundet") share = db.query(PlaylistShare).filter_by(id=share_id, project_id=project_id).first() if not share: raise HTTPException(404, "Deling ikke fundet") db.delete(share) db.commit() @router.get("/playlists/{project_id}/shares") def list_shares( project_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): project = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if not project: raise HTTPException(404, "Playliste ikke fundet") shares = db.query(PlaylistShare).filter_by(project_id=project_id).all() return [ { "id": s.id, "email": s.invited_email, "permission": s.permission, "accepted": s.accepted_at is not None, } for s in shares ] # ── Visibility ──────────────────────────────────────────────────────────────── @router.patch("/playlists/{project_id}/visibility") def set_visibility( project_id: str, visibility: str, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): if visibility not in ("private", "shared", "public"): raise HTTPException(400, "Ugyldig synlighed — brug private, shared eller public") project = db.query(Project).filter_by(id=project_id, owner_id=me.id).first() if not project: raise HTTPException(404, "Playliste ikke fundet") project.visibility = visibility db.commit() return {"detail": f"Synlighed sat til {visibility}"} # ── Hent delte lister ───────────────────────────────────────────────────────── @router.get("/playlists/shared-with-me") def shared_with_me( db: Session = Depends(get_db), me: User = Depends(get_current_user), ): """Hent alle playlister der er delt med mig.""" # Via direkte deling shares = db.query(PlaylistShare).filter_by( shared_with_id=me.id ).all() project_ids = {s.project_id for s in shares} # Via email-invitation email_shares = db.query(PlaylistShare).filter_by( invited_email=me.email ).all() project_ids.update(s.project_id for s in email_shares) # Public playlister public = db.query(Project).filter_by(visibility="public").all() project_ids.update(p.id for p in public) result = [] for pid in project_ids: p = db.query(Project).filter_by(id=pid).first() if not p or p.owner_id == me.id: continue # Find min rettighed share = db.query(PlaylistShare).filter( PlaylistShare.project_id == pid, (PlaylistShare.shared_with_id == me.id) | (PlaylistShare.invited_email == me.email) ).first() permission = share.permission if share else "view" owner = db.query(User).filter_by(id=p.owner_id).first() result.append({ "project_id": p.id, "name": p.name, "owner": owner.username if owner else "?", "visibility": p.visibility, "permission": permission, "song_count": len(p.project_songs), }) return result # ── Hent en delt playliste ──────────────────────────────────────────────────── @router.get("/playlists/{project_id}") def get_shared_playlist( project_id: str, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): """Hent indholdet af en delt playliste.""" p = db.query(Project).filter_by(id=project_id).first() if not p: raise HTTPException(404, "Playliste ikke fundet") # Tjek adgang if p.owner_id != me.id: if p.visibility != "public": share = db.query(PlaylistShare).filter( PlaylistShare.project_id == project_id, (PlaylistShare.shared_with_id == me.id) | (PlaylistShare.invited_email == me.email) ).first() if not share: raise HTTPException(403, "Du har ikke adgang til denne playliste") from app.models import Song songs = [] for ps in p.project_songs: song = db.query(Song).filter_by(id=ps.song_id).first() if not song: continue songs.append({ "title": song.title, "artist": song.artist, "album": song.album, "bpm": song.bpm, "duration_sec": song.duration_sec, "position": ps.position, "status": ps.status, "is_workshop": ps.is_workshop, "dance_override": ps.dance_override, }) return { "id": p.id, "name": p.name, "description": p.description, "visibility": p.visibility, "songs": sorted(songs, key=lambda x: x["position"]), } # ── Opdater sange i en linket liste ────────────────────────────────────────── class LinkedSongData(BaseModel): title: str artist: str = "" position: int = 1 status: str = "pending" is_workshop: bool = False dance_override: str = "" class LinkedSongsUpdate(BaseModel): songs: list[LinkedSongData] @router.put("/playlists/{project_id}/songs") def update_linked_songs( project_id: str, data: LinkedSongsUpdate, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): """Opdater sange i en linket playliste — kræver edit-rettighed.""" from app.models import Song, ProjectSong p = db.query(Project).filter_by(id=project_id).first() if not p: raise HTTPException(404, "Playliste ikke fundet") # Tjek edit-rettighed if p.owner_id != me.id: share = db.query(PlaylistShare).filter( PlaylistShare.project_id == project_id, (PlaylistShare.shared_with_id == me.id) | (PlaylistShare.invited_email == me.email) ).first() if not share or share.permission != "edit": raise HTTPException(403, "Du har ikke redigerings-rettighed") # Slet eksisterende sange og geninsert db.query(ProjectSong).filter_by(project_id=project_id).delete() for song_data in data.songs: song = db.query(Song).filter_by( title=song_data.title, artist=song_data.artist ).first() if not song: continue ps = ProjectSong( project_id=project_id, song_id=song.id, position=song_data.position, status=song_data.status, is_workshop=song_data.is_workshop, dance_override=song_data.dance_override, ) db.add(ps) db.commit() return {"detail": "Liste opdateret", "songs": len(data.songs)} # ── Opdater sange på en delt playliste ─────────────────────────────────────── class LinkedSongData(BaseModel): title: str artist: str position: int status: str = "pending" is_workshop: bool = False dance_override: str = "" class LinkedPlaylistUpdate(BaseModel): songs: list[LinkedSongData] @router.put("/playlists/{project_id}/songs") def update_linked_playlist_songs( project_id: str, data: LinkedPlaylistUpdate, db: Session = Depends(get_db), me: User = Depends(get_current_user), ): """Opdater sange på en delt playliste — kræver edit-rettighed.""" from app.models import Song p = db.query(Project).filter_by(id=project_id).first() if not p: raise HTTPException(404, "Playliste ikke fundet") # Tjek rettighed if p.owner_id != me.id: from app.models import PlaylistShare share = db.query(PlaylistShare).filter( PlaylistShare.project_id == project_id, (PlaylistShare.shared_with_id == me.id) | (PlaylistShare.invited_email == me.email) ).first() if not share or share.permission != "edit": raise HTTPException(403, "Du har ikke rettighed til at redigere denne liste") # Slet eksisterende sange og indsæt nye from app.models import ProjectSong db.query(ProjectSong).filter_by(project_id=project_id).delete() for song_data in data.songs: # Match sang globalt på titel+artist song = db.query(Song).filter_by( title=song_data.title, artist=song_data.artist ).first() if not song: song = Song( owner_id=me.id, title=song_data.title, artist=song_data.artist, ) db.add(song) db.flush() ps = ProjectSong( project_id=project_id, song_id=song.id, position=song_data.position, status=song_data.status, is_workshop=song_data.is_workshop, dance_override=song_data.dance_override, ) db.add(ps) db.commit() return {"detail": "Playliste opdateret", "songs": len(data.songs)}