33from typing import List , Optional
44from pathlib import Path
55from fastapi import APIRouter , HTTPException , status , UploadFile , File
6- from pydantic import BaseModel , Field
6+ from pydantic import BaseModel ,
7+ from datetime import datetime
78from sqlalchemy .orm import Session
89
910from qwave .models import User , Track , Artist , Album , Genre , Job , track_artists , track_genres
1011from qwave .config import get_config
1112from qwave .depends import DBDep , UserDep
13+ from qwave .services import import_service
14+ from qwave .utils .log_item import log_item
15+ from qwave .workers .worker import queue_job
1216
1317router = APIRouter ()
1418
@@ -95,6 +99,7 @@ class GenreRequest(BaseModel):
9599class MessageResponse (BaseModel ):
96100 message : str
97101
102+
98103@router .get ("" , response_model = TrackListResponse )
99104def list_tracks (
100105 user : UserDep ,
@@ -108,20 +113,146 @@ def list_tracks(
108113 limit : int = 128 ,
109114 offset : int = 0 ,
110115):
111- pass
116+ query = db .query (Track )
117+
118+ if artist_id :
119+ query = query .join (track_artists ).filter (track_artists .c .artist_id == artist_id )
120+ if album_id :
121+ query = query .filter (Track .album_id == album_id )
122+ if genre_id :
123+ query = query .join (track_genres ).filter (track_genres .c .genre_id == genre_id )
124+ if added_by :
125+ query = query .filter (Track .added_by_user_id == added_by )
126+ if date_from :
127+ query = query .filter (Track .added_date >= datetime .fromisoformat (date_from ))
128+ if date_to :
129+ query = query .filter (Track .added_date <= datetime .fromisoformat (date_to ))
130+
131+ query = query .order_by ()
132+ total = query .count ()
133+ tracks = query .limit (limit ).offset (offset ).all ()
134+
135+ return TrackListResponse (
136+ tracks = [
137+ TrackSummary (
138+ id = t .id ,
139+ title = t .title ,
140+ duration = int (t .duration ),
141+ artists = [
142+ ArtistInfo (
143+ id = a .id ,
144+ name = a .name ,
145+ is_primary = is_primary_artist (db , t .id , a .id )
146+ ) for a in t .artists
147+ ],
148+ album = AlbumInfo (
149+ id = t .album .id ,
150+ title = t .album .title ,
151+ release_date = t .album .release_date .isoformat () if t .album .release_date else None
152+ ) if t .album else None ,
153+ added_date = t .added_date .isoformat ()
154+ ) for t in tracks
155+ ], total = total
156+ )
157+
112158
113159@router .get ("/{track_id}" , response_model = TrackDetail )
114160def get_track (user : UserDep , db : DBDep , track_id : int ):
115- pass
161+ track = db .query (Track ).filter (Track .id == track_id ).first ()
162+ if not track :
163+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Track not found!" )
116164
117- # TODO: allow adding data to track on upload instead of after
165+ return TrackDetail (
166+ id = track .id ,
167+ title = track .title ,
168+ duration = int (track .duration ),
169+ track_number = track .track_number ,
170+ artists = [
171+ ArtistInfo (
172+ id = a .id , name = a .name , is_primary = is_primary_artist (db , track .id , a .id )
173+ ) for a in track .artists
174+ ],
175+ album = AlbumInfo (
176+ id = track .album .id ,
177+ title = track .album .title ,
178+ release_date = track .album .release_date .isoformat () if track .album .release_date else None
179+ ) if track .album else None ,
180+ genres = [GenreInfo (id = g .id , name = g .name ) for g in track .genres ],
181+ lyrics = track .lyrics ,
182+ added_date = track .added_date .isoformat (),
183+ added_by = UserInfo (id = track .added_by .id , username = track .added_by .username )
184+ )
185+
186+ # TODO: MAYBE: allow adding data to track on upload instead of after
118187@router .post ("/upload" , response_model = UploadResponse )
119188async def upload_track (user : UserDep , db : DBDep , file : UploadFile = File (...)):
120- pass
189+ config = get_config ()
190+ max_size = config .max_upload_size_mb * (1024 ** 2 )
191+
192+ if not file .filename :
193+ raise Exception ("WHAT DID YOU DO" ) # just here to get pyright to shut up
194+
195+ with tempfile .NamedTemporaryFile (delete = False , suffix = Path (file .filename ).suffix ) as tmp :
196+ content = await file .read ()
197+ if len (content ) > max_size :
198+ Path (tmp .name ).unlink ()
199+ raise HTTPException (status_code = status .HTTP_413_CONTENT_TOO_LARGE , detail = "file too large!" )
200+ tmp .write (content )
201+ tmp_path = Path (tmp .name )
202+
203+ try :
204+ result = import_service .handle_upload (db = db , file_path = tmp_path , filename = file .filename , user_id = user .id )
205+ job = db .query (Job ).filter (Job .id == result ["job_id" ]).first ()
206+ if job :
207+ queue_job (job )
208+
209+ return UploadResponse (
210+ job_id = result ["job_id" ],
211+ track_id = result ["track_id" ],
212+ status = result ["status" ]
213+ )
214+
215+ except ValueError as e :
216+ tmp_path .unlink ()
217+ raise HTTPException (status_code = status .HTTP_400_BAD_REQUEST , detail = str (e ))
218+
219+ finally :
220+ if tmp_path .exists ():
221+ tmp_path .unlink ()
121222
223+ # TODO: add the rest of the fields? gotta do it different
122224@router .patch ("/{track_id}" , response_model = UpdateTrackResponse )
123225def update_track (user : UserDep , db : DBDep , track_id : int , request : UpdateTrackRequest ): # can i just tack this onto upload_track()?
124- pass
226+ track = db .query (Track ).filter (Track .id == track_id ).first ()
227+ if not track :
228+ raise HTTPException (status .HTTP_404_NOT_FOUND , detail = "Trakc not found!" )
229+ # too lazy to add the legacy permission check
230+
231+ updated_fields = []
232+
233+ if request .title is not None :
234+ track .title = request .title
235+ updated_fields .append ("title" )
236+ if request .track_number is not None :
237+ track .track_number = request .track_number
238+ updated_fields .append ("track_number" )
239+ if request .lyrics is not None :
240+ track .lyrics = request .lyrics
241+ updated_fields .append ("lyrics" )
242+
243+ db .commit ()
244+ db .refresh (track )
245+
246+ return UpdateTrackResponse (
247+ id = track .id ,
248+ updated_fields = updated_fields ,
249+ track = {
250+ "id" : track .id ,
251+ "title" : track .title ,
252+ "track_number" : track .track_number ,
253+ "lyrics" : track .lyrics
254+ }
255+ )
125256
126257# i will hack this club
127258@router .delete ("/{track_id}" , response_model = DeleteTrackResponse )
@@ -142,6 +273,7 @@ def delete_track(user: UserDep, db: DBDep, track_id: int):
142273 db .commit ()
143274 return DeleteTrackResponse (message = f"Deleted track { track_id } " , id = track_id )
144275
276+
145277@router .get ("/{track_id}/artists" , response_model = ArtistListResponse )
146278def get_track_artists (user : UserDep , db : DBDep , track_id : int ):
147279 track = db .query (Track ).filter (Track .id == track_id ).first ()
@@ -153,9 +285,41 @@ def get_track_artists(user: UserDep, db: DBDep, track_id: int):
153285 ) for a in track .artists ]
154286 )
155287
288+
156289@router .post ("/{track_id}/artists" , response_model = AddArtistResponse )
157290def add_track_artist (user : UserDep , db : DBDep , track_id : int , request : AddArtistRequest ):
158- pass
291+ track = db .query (Track ).filter (Track .id == track_id ).first ()
292+ artist = db .query (Artist ).filter (Artist .id == request .artist_id ).first ()
293+
294+ if not track :
295+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Track not found..." )
296+ if not artist :
297+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Artist not found..." )
298+ # if track.added_by_user_id != user.id:
299+ # raise HTTPException(status_code = status.HTTP_403_FORBIDDEN, detail = "You don't own this...")
300+
301+ existing = db .execute ( track_artists .select ().where (
302+ track_artists .c .track_id == track_id ,
303+ track_artists .c .artist_id == request .artist_id
304+ )).first ()
305+
306+ if existing :
307+ raise HTTPException (status_code = status .HTTP_400_BAD_REQUEST , detail = "Artist already selected!" )
308+
309+ db .execute (
310+ track_artists .insert ().values (
311+ track_id = track_id ,
312+ artist_id = request .artist_id ,
313+ is_primary = request .is_primary
314+ ))
315+ db .commit ()
316+
317+ return AddArtistResponse (
318+ message = "Artist added :)" ,
319+ track_id = track_id ,
320+ artist = ArtistInfo (id = artist .id , name = artist .name , is_primary = request .is_primary )
321+ )
322+
159323
160324@router .delete ("/{track_id}/artists/{artist_id}" , response_model = MessageResponse )
161325def remove_track_artist (user : UserDep , db : DBDep , track_id : int , artist_id : int ):
@@ -174,9 +338,25 @@ def remove_track_artist(user: UserDep, db: DBDep, track_id: int, artist_id: int)
174338
175339 return MessageResponse (message = f"Removed artist { artist_id } from track { track_id } " )
176340
341+
177342@router .post ("/{track_id}/genres" , response_model = MessageResponse )
178343def add_track_genre (user : UserDep , db : DBDep , track_id : int , request : GenreRequest ):
179- pass
344+ track = db .query (Track ).filter (Track .id == track_id ).first ()
345+ genre = db .query (Genre ).filter (Genre .id == request .genre_id ).first ()
346+
347+ if not track :
348+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Track not found!" )
349+ if not genre :
350+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = "Genre not found!" )
351+ # auth here?
352+
353+ db .execute (
354+ track_genres .insert ().values (
355+ track_id = track_id ,
356+ genre_id = request .genre_id
357+ )
358+ )
359+ return MessageResponse (message = "Genre added!" )
180360
181361@router .delete ("/{track_id}/genres/{genre_id}" , response_model = MessageResponse )
182362def remove_track_genre (user : UserDep , db : DBDep , track_id : int , genre_id : int ):
@@ -195,6 +375,7 @@ def remove_track_genre(user: UserDep, db: DBDep, track_id: int, genre_id: int):
195375 db .commit ()
196376 return MessageResponse (message = f"Removed genre { genre_id } from { track_id } " )
197377
378+
198379def is_primary_artist (db : Session , track_id : int , artist_id : int ) -> bool :
199380 result = db .execute (
200381 track_artists .select ().where (
0 commit comments