package library import ( "context" "database/sql" "fmt" ) type Artist struct { ID string `json:"id"` Name string `json:"name"` AlbumCount int `json:"albumCount"` } type Album struct { ID string `json:"id"` ArtistID string `json:"artistId"` ArtistName string `json:"artistName"` Title string `json:"title"` Year int `json:"year"` TrackCount int `json:"trackCount"` } type Track struct { ID string `json:"id"` AlbumID string `json:"albumId"` ArtistID string `json:"artistId"` Title string `json:"title"` ArtistName string `json:"artistName"` AlbumTitle string `json:"albumTitle"` TrackNumber int `json:"trackNumber"` DurationSecs int `json:"durationSeconds"` FilePath string `json:"filePath"` ContentType string `json:"contentType"` } type HomePayload struct { RecentAlbums []Album `json:"recentAlbums"` Artists []Artist `json:"artists"` } type Service struct { db *sql.DB } func NewService(db *sql.DB) *Service { return &Service{db: db} } func (s *Service) Home(ctx context.Context) (HomePayload, error) { albums, err := s.RecentAlbums(ctx, 6) if err != nil { return HomePayload{}, err } artists, err := s.Artists(ctx, 12) if err != nil { return HomePayload{}, err } return HomePayload{ RecentAlbums: albums, Artists: artists, }, nil } func (s *Service) Artists(ctx context.Context, limit int) ([]Artist, error) { rows, err := s.db.QueryContext( ctx, `SELECT a.id, a.name, COUNT(al.id) AS album_count FROM artists a LEFT JOIN albums al ON al.artist_id = a.id GROUP BY a.id, a.name ORDER BY a.name ASC LIMIT ?`, limit, ) if err != nil { return nil, fmt.Errorf("query artists: %w", err) } defer rows.Close() var artists []Artist for rows.Next() { var artist Artist if err := rows.Scan(&artist.ID, &artist.Name, &artist.AlbumCount); err != nil { return nil, fmt.Errorf("scan artist: %w", err) } artists = append(artists, artist) } return artists, rows.Err() } func (s *Service) RecentAlbums(ctx context.Context, limit int) ([]Album, error) { rows, err := s.db.QueryContext( ctx, `SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id) AS track_count FROM albums al JOIN artists a ON a.id = al.artist_id LEFT JOIN tracks t ON t.album_id = al.id GROUP BY al.id, al.artist_id, a.name, al.title, al.year ORDER BY al.updated_at DESC, al.title ASC LIMIT ?`, limit, ) if err != nil { return nil, fmt.Errorf("query albums: %w", err) } defer rows.Close() var albums []Album for rows.Next() { var album Album if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount); err != nil { return nil, fmt.Errorf("scan album: %w", err) } albums = append(albums, album) } return albums, rows.Err() } func (s *Service) Tracks(ctx context.Context, limit int) ([]Track, error) { rows, err := s.db.QueryContext( ctx, `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), COALESCE(t.duration_seconds, 0), t.file_path, COALESCE(t.content_type, '') FROM tracks t JOIN artists a ON a.id = t.artist_id JOIN albums al ON al.id = t.album_id ORDER BY a.name ASC, al.year DESC, al.title ASC, t.disc_number ASC, t.track_number ASC LIMIT ?`, limit, ) if err != nil { return nil, fmt.Errorf("query tracks: %w", err) } defer rows.Close() var tracks []Track for rows.Next() { var track Track if err := rows.Scan( &track.ID, &track.AlbumID, &track.ArtistID, &track.Title, &track.ArtistName, &track.AlbumTitle, &track.TrackNumber, &track.DurationSecs, &track.FilePath, &track.ContentType, ); err != nil { return nil, fmt.Errorf("scan track: %w", err) } tracks = append(tracks, track) } return tracks, rows.Err() } func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) { var track Track err := s.db.QueryRowContext( ctx, `SELECT t.id, t.album_id, t.artist_id, t.title, a.name, al.title, COALESCE(t.track_number, 0), COALESCE(t.duration_seconds, 0), t.file_path, COALESCE(t.content_type, '') FROM tracks t JOIN artists a ON a.id = t.artist_id JOIN albums al ON al.id = t.album_id WHERE t.id = ?`, id, ).Scan( &track.ID, &track.AlbumID, &track.ArtistID, &track.Title, &track.ArtistName, &track.AlbumTitle, &track.TrackNumber, &track.DurationSecs, &track.FilePath, &track.ContentType, ) if err != nil { return Track{}, fmt.Errorf("query track by id: %w", err) } return track, nil }