Næster version
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
sharing.py — Del playlister med andre brugere.
|
||||
sharing.py — Forenklet deling af playlister.
|
||||
Kun ejeren kan redigere. Delte brugere får read-only via sync.
|
||||
"""
|
||||
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
|
||||
@@ -14,22 +14,10 @@ router = APIRouter(prefix="/sharing", tags=["sharing"])
|
||||
|
||||
|
||||
class ShareRequest(BaseModel):
|
||||
email: EmailStr
|
||||
permission: str = "view" # view | copy | edit
|
||||
email: EmailStr
|
||||
|
||||
|
||||
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 ──────────────────────────────────────────────────────────
|
||||
# ── Del med bruger ────────────────────────────────────────────────────────────
|
||||
|
||||
@router.post("/playlists/{project_id}/share", status_code=201)
|
||||
async def share_playlist(
|
||||
@@ -39,35 +27,27 @@ async def share_playlist(
|
||||
db: Session = Depends(get_db),
|
||||
me: User = Depends(get_current_user),
|
||||
):
|
||||
"""Del en playliste med en bruger — de får listen ved næste sync."""
|
||||
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,
|
||||
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}
|
||||
return {"detail": "Allerede delt med denne bruger"}
|
||||
|
||||
share = PlaylistShare(
|
||||
project_id=project_id,
|
||||
shared_with_id=target.id if target else None,
|
||||
invited_email=data.email,
|
||||
permission=data.permission,
|
||||
permission="view",
|
||||
)
|
||||
db.add(share)
|
||||
db.commit()
|
||||
db.refresh(share)
|
||||
|
||||
# Send invitation-mail
|
||||
try:
|
||||
@@ -78,32 +58,13 @@ async def share_playlist(
|
||||
email=data.email,
|
||||
owner_name=me.username,
|
||||
playlist_name=project.name,
|
||||
permission=data.permission,
|
||||
accept_url=f"{settings.BASE_URL}/sharing/accept/{share.id}",
|
||||
permission="view",
|
||||
accept_url=f"{settings.BASE_URL}/sharing/playlists/{project_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"}
|
||||
return {"detail": f"Delt med {data.email}"}
|
||||
|
||||
|
||||
@router.delete("/playlists/{project_id}/share/{share_id}", status_code=204)
|
||||
@@ -133,15 +94,7 @@ def list_shares(
|
||||
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
|
||||
]
|
||||
return [{"id": s.id, "email": s.invited_email} for s in shares]
|
||||
|
||||
|
||||
# ── Visibility ────────────────────────────────────────────────────────────────
|
||||
@@ -154,64 +107,16 @@ def set_visibility(
|
||||
me: User = Depends(get_current_user),
|
||||
):
|
||||
if visibility not in ("private", "shared", "public"):
|
||||
raise HTTPException(400, "Ugyldig synlighed — brug private, shared eller public")
|
||||
raise HTTPException(400, "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}"}
|
||||
return {"detail": f"Synlighed: {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 ────────────────────────────────────────────────────
|
||||
# ── Hent playliste-indhold ────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/playlists/{project_id}")
|
||||
def get_shared_playlist(
|
||||
@@ -219,12 +124,9 @@ def get_shared_playlist(
|
||||
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(
|
||||
@@ -233,7 +135,7 @@ def get_shared_playlist(
|
||||
(PlaylistShare.invited_email == me.email)
|
||||
).first()
|
||||
if not share:
|
||||
raise HTTPException(403, "Du har ikke adgang til denne playliste")
|
||||
raise HTTPException(403, "Ingen adgang")
|
||||
|
||||
from app.models import Song
|
||||
songs = []
|
||||
@@ -242,153 +144,18 @@ def get_shared_playlist(
|
||||
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,
|
||||
"title": song.title,
|
||||
"artist": song.artist,
|
||||
"position": ps.position,
|
||||
"status": ps.status,
|
||||
"is_workshop": ps.is_workshop,
|
||||
"dance_override": ps.dance_override or "",
|
||||
})
|
||||
|
||||
return {
|
||||
"id": p.id,
|
||||
"name": p.name,
|
||||
"description": p.description,
|
||||
"description": p.description or "",
|
||||
"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)}
|
||||
|
||||
@@ -268,28 +268,37 @@ def pull(
|
||||
"dance_id": cd.dance_id,
|
||||
})
|
||||
|
||||
# Delte playlister
|
||||
shared_ids = [
|
||||
s.project_id for s in
|
||||
db.query(PlaylistShare).filter_by(shared_with_id=me.id).all()
|
||||
]
|
||||
# Delte playlister (read-only — kun ejeren kan redigere)
|
||||
shared_ids = set()
|
||||
for s in db.query(PlaylistShare).filter(
|
||||
(PlaylistShare.shared_with_id == me.id) |
|
||||
(PlaylistShare.invited_email == me.email)
|
||||
).all():
|
||||
shared_ids.add(s.project_id)
|
||||
|
||||
shared = []
|
||||
for p in db.query(Project).filter(Project.id.in_(shared_ids)).all():
|
||||
if p.owner_id == me.id:
|
||||
continue # Egne lister håndteres separat
|
||||
owner = db.query(User).filter_by(id=p.owner_id).first()
|
||||
songs_out = []
|
||||
for ps in p.project_songs:
|
||||
song = db.query(Song).filter_by(id=ps.song_id).first()
|
||||
if not song:
|
||||
continue
|
||||
songs_out.append({
|
||||
"title": song.title,
|
||||
"artist": song.artist,
|
||||
"position": ps.position,
|
||||
"status": ps.status,
|
||||
"is_workshop": ps.is_workshop,
|
||||
"dance_override": ps.dance_override or "",
|
||||
})
|
||||
shared.append({
|
||||
"id": p.id,
|
||||
"name": p.name,
|
||||
"owner_id": p.owner_id,
|
||||
"visibility": p.visibility,
|
||||
"songs": [
|
||||
{
|
||||
"song_id": ps.song_id,
|
||||
"position": ps.position,
|
||||
"status": ps.status,
|
||||
"is_workshop": ps.is_workshop,
|
||||
"dance_override": ps.dance_override,
|
||||
}
|
||||
for ps in p.project_songs
|
||||
]
|
||||
"server_id": p.id,
|
||||
"name": p.name,
|
||||
"owner": owner.username if owner else "?",
|
||||
"songs": sorted(songs_out, key=lambda x: x["position"]),
|
||||
})
|
||||
|
||||
# Egne playlister
|
||||
|
||||
Reference in New Issue
Block a user