Initial commit: AuroraMusic monorepo
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import auth, schemas
|
||||
from ..config import settings
|
||||
from ..database import get_db
|
||||
from ..jobs import enqueue_generation
|
||||
from ..models import Like, Song, User
|
||||
|
||||
router = APIRouter(prefix="/songs", tags=["songs"])
|
||||
|
||||
|
||||
def _serialize(song: Song, current_user: User | None, liked_ids: set[uuid.UUID] | None = None) -> schemas.SongOut:
|
||||
liked = False
|
||||
if current_user is not None:
|
||||
if liked_ids is not None:
|
||||
liked = song.id in liked_ids
|
||||
return schemas.SongOut(
|
||||
id=song.id,
|
||||
user_id=song.user_id,
|
||||
owner_display_name=song.owner.display_name if song.owner else None,
|
||||
title=song.title,
|
||||
prompt=song.prompt,
|
||||
lyrics=song.lyrics,
|
||||
mode=song.mode,
|
||||
status=song.status,
|
||||
error_message=song.error_message,
|
||||
duration_seconds=song.duration_seconds,
|
||||
cover_seed=song.cover_seed,
|
||||
is_public=song.is_public,
|
||||
like_count=song.like_count,
|
||||
liked_by_me=liked,
|
||||
audio_url=f"/media/{song.audio_path}" if song.audio_path else None,
|
||||
created_at=song.created_at,
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=schemas.SongOut, status_code=status.HTTP_201_CREATED)
|
||||
def create_song(
|
||||
payload: schemas.SongCreateRequest,
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if payload.mode == "vocals" and not payload.lyrics:
|
||||
raise HTTPException(status_code=422, detail="Lyrics are required when mode is 'vocals'")
|
||||
|
||||
song = Song(
|
||||
user_id=current_user.id,
|
||||
title=payload.title,
|
||||
prompt=payload.prompt,
|
||||
lyrics=payload.lyrics,
|
||||
mode=payload.mode,
|
||||
is_public=payload.is_public,
|
||||
status="pending",
|
||||
cover_seed=random.randint(0, 2_147_483_647),
|
||||
)
|
||||
db.add(song)
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
|
||||
enqueue_generation(song.id)
|
||||
|
||||
return _serialize(song, current_user, set())
|
||||
|
||||
|
||||
@router.get("/mine", response_model=schemas.SongListResponse)
|
||||
def my_songs(
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
songs = (
|
||||
db.query(Song)
|
||||
.filter(Song.user_id == current_user.id)
|
||||
.order_by(Song.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
liked_ids = _liked_song_ids(db, current_user, [s.id for s in songs])
|
||||
return schemas.SongListResponse(items=[_serialize(s, current_user, liked_ids) for s in songs], total=len(songs))
|
||||
|
||||
|
||||
@router.get("/feed", response_model=schemas.SongListResponse)
|
||||
def feed(
|
||||
limit: int = Query(default=30, le=100),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
current_user: User | None = Depends(auth.get_current_user_optional),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
base_query = db.query(Song).filter(Song.is_public.is_(True), Song.status == "completed")
|
||||
total = base_query.with_entities(func.count(Song.id)).scalar() or 0
|
||||
songs = base_query.order_by(Song.created_at.desc()).offset(offset).limit(limit).all()
|
||||
liked_ids = _liked_song_ids(db, current_user, [s.id for s in songs]) if current_user else set()
|
||||
return schemas.SongListResponse(items=[_serialize(s, current_user, liked_ids) for s in songs], total=total)
|
||||
|
||||
|
||||
@router.get("/{song_id}", response_model=schemas.SongOut)
|
||||
def get_song(
|
||||
song_id: uuid.UUID,
|
||||
current_user: User | None = Depends(auth.get_current_user_optional),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
song = db.get(Song, song_id)
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
if not song.is_public and (current_user is None or song.user_id != current_user.id):
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
liked_ids = _liked_song_ids(db, current_user, [song.id]) if current_user else set()
|
||||
return _serialize(song, current_user, liked_ids)
|
||||
|
||||
|
||||
@router.patch("/{song_id}", response_model=schemas.SongOut)
|
||||
def update_song(
|
||||
song_id: uuid.UUID,
|
||||
payload: schemas.SongUpdateRequest,
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
song = db.get(Song, song_id)
|
||||
if not song or song.user_id != current_user.id:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
if payload.title is not None:
|
||||
song.title = payload.title
|
||||
if payload.is_public is not None:
|
||||
song.is_public = payload.is_public
|
||||
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
liked_ids = _liked_song_ids(db, current_user, [song.id])
|
||||
return _serialize(song, current_user, liked_ids)
|
||||
|
||||
|
||||
@router.delete("/{song_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_song(
|
||||
song_id: uuid.UUID,
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
song = db.get(Song, song_id)
|
||||
if not song or song.user_id != current_user.id:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
db.delete(song)
|
||||
db.commit()
|
||||
|
||||
|
||||
@router.post("/{song_id}/like", response_model=schemas.SongOut)
|
||||
def like_song(
|
||||
song_id: uuid.UUID,
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
song = db.get(Song, song_id)
|
||||
if not song or not song.is_public:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
existing = db.query(Like).filter(Like.user_id == current_user.id, Like.song_id == song_id).first()
|
||||
if not existing:
|
||||
db.add(Like(user_id=current_user.id, song_id=song_id))
|
||||
song.like_count += 1
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
|
||||
liked_ids = _liked_song_ids(db, current_user, [song.id])
|
||||
return _serialize(song, current_user, liked_ids)
|
||||
|
||||
|
||||
@router.delete("/{song_id}/like", response_model=schemas.SongOut)
|
||||
def unlike_song(
|
||||
song_id: uuid.UUID,
|
||||
current_user: User = Depends(auth.get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
song = db.get(Song, song_id)
|
||||
if not song:
|
||||
raise HTTPException(status_code=404, detail="Song not found")
|
||||
|
||||
existing = db.query(Like).filter(Like.user_id == current_user.id, Like.song_id == song_id).first()
|
||||
if existing:
|
||||
db.delete(existing)
|
||||
song.like_count = max(0, song.like_count - 1)
|
||||
db.commit()
|
||||
db.refresh(song)
|
||||
|
||||
liked_ids = _liked_song_ids(db, current_user, [song.id])
|
||||
return _serialize(song, current_user, liked_ids)
|
||||
|
||||
|
||||
def _liked_song_ids(db: Session, current_user: User | None, song_ids: list[uuid.UUID]) -> set[uuid.UUID]:
|
||||
if not current_user or not song_ids:
|
||||
return set()
|
||||
rows = (
|
||||
db.execute(
|
||||
select(Like.song_id).where(Like.user_id == current_user.id, Like.song_id.in_(song_ids))
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
return set(rows)
|
||||
Reference in New Issue
Block a user