Files
LinedanceAfspiller/linedance-api/app/routers/sharing.py
2026-04-13 07:23:37 +02:00

395 lines
13 KiB
Python

"""
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)}