feat: add subsonic library discovery endpoints

This commit is contained in:
2026-04-03 21:07:58 +03:00
parent 480bdc2476
commit ad9543bf7a
3 changed files with 189 additions and 10 deletions

View File

@@ -25,6 +25,7 @@ type Album struct {
Title string `json:"title"`
Year int `json:"year"`
TrackCount int `json:"trackCount"`
Genre string `json:"genre"`
CoverArtID string `json:"coverArtId"`
}
@@ -73,6 +74,12 @@ type StarredResults struct {
Tracks []Track `json:"tracks"`
}
type GenreSummary struct {
Value string `json:"value"`
AlbumCount int `json:"albumCount"`
SongCount int `json:"songCount"`
}
type Service struct {
db *sql.DB
}
@@ -165,6 +172,7 @@ 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
, COALESCE(al.genre, '')
, COALESCE(al.cover_art_id, '')
FROM albums al
JOIN artists a ON a.id = al.artist_id
@@ -182,7 +190,7 @@ func (s *Service) RecentAlbums(ctx context.Context, limit int) ([]Album, error)
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, &album.CoverArtID); err != nil {
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.Genre, &album.CoverArtID); err != nil {
return nil, fmt.Errorf("scan album: %w", err)
}
albums = append(albums, album)
@@ -194,7 +202,7 @@ func (s *Service) RecentAlbums(ctx context.Context, limit int) ([]Album, error)
func (s *Service) Albums(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, COALESCE(al.cover_art_id, '')
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id) AS track_count, COALESCE(al.genre, ''), COALESCE(al.cover_art_id, '')
FROM albums al
JOIN artists a ON a.id = al.artist_id
LEFT JOIN tracks t ON t.album_id = al.id
@@ -211,7 +219,7 @@ func (s *Service) Albums(ctx context.Context, limit int) ([]Album, error) {
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, &album.CoverArtID); err != nil {
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.Genre, &album.CoverArtID); err != nil {
return nil, fmt.Errorf("scan all albums: %w", err)
}
albums = append(albums, album)
@@ -225,7 +233,7 @@ func (s *Service) AlbumByID(ctx context.Context, id string) (AlbumDetail, error)
err := s.db.QueryRowContext(
ctx,
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0),
COUNT(t.id) AS track_count, COALESCE(al.cover_art_id, '')
COUNT(t.id) AS track_count, COALESCE(al.genre, ''), COALESCE(al.cover_art_id, '')
FROM albums al
JOIN artists a ON a.id = al.artist_id
LEFT JOIN tracks t ON t.album_id = al.id
@@ -239,6 +247,7 @@ func (s *Service) AlbumByID(ctx context.Context, id string) (AlbumDetail, error)
&album.Title,
&album.Year,
&album.TrackCount,
&album.Genre,
&album.CoverArtID,
)
if err != nil {
@@ -380,6 +389,32 @@ func (s *Service) Starred(ctx context.Context, userID string) (StarredResults, e
}, nil
}
func (s *Service) Genres(ctx context.Context) ([]GenreSummary, error) {
rows, err := s.db.QueryContext(
ctx,
`SELECT al.genre, COUNT(DISTINCT al.id) AS album_count, COUNT(t.id) AS song_count
FROM albums al
LEFT JOIN tracks t ON t.album_id = al.id
WHERE TRIM(COALESCE(al.genre, '')) <> ''
GROUP BY al.genre
ORDER BY song_count DESC, album_count DESC, al.genre ASC`,
)
if err != nil {
return nil, fmt.Errorf("query genres: %w", err)
}
defer rows.Close()
var genres []GenreSummary
for rows.Next() {
var genre GenreSummary
if err := rows.Scan(&genre.Value, &genre.AlbumCount, &genre.SongCount); err != nil {
return nil, fmt.Errorf("scan genre: %w", err)
}
genres = append(genres, genre)
}
return genres, rows.Err()
}
func (s *Service) Star(ctx context.Context, userID string, trackIDs, albumIDs, artistIDs []string) error {
return s.updateFavorites(ctx, userID, trackIDs, albumIDs, artistIDs, true)
}
@@ -553,7 +588,7 @@ func (s *Service) PopulateTrackStats(ctx context.Context, userID string, tracks
func (s *Service) albumsByArtistID(ctx context.Context, artistID string) ([]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, COALESCE(al.cover_art_id, '')
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id) AS track_count, COALESCE(al.genre, ''), COALESCE(al.cover_art_id, '')
FROM albums al
JOIN artists a ON a.id = al.artist_id
LEFT JOIN tracks t ON t.album_id = al.id
@@ -570,7 +605,7 @@ func (s *Service) albumsByArtistID(ctx context.Context, artistID string) ([]Albu
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, &album.CoverArtID); err != nil {
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.Genre, &album.CoverArtID); err != nil {
return nil, fmt.Errorf("scan album by artist: %w", err)
}
albums = append(albums, album)
@@ -631,7 +666,7 @@ func (s *Service) searchArtists(ctx context.Context, pattern string, limit int)
func (s *Service) searchAlbums(ctx context.Context, pattern string, 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), COALESCE(al.cover_art_id, '')
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id), COALESCE(al.genre, ''), COALESCE(al.cover_art_id, '')
FROM albums al
JOIN artists a ON a.id = al.artist_id
LEFT JOIN tracks t ON t.album_id = al.id
@@ -651,7 +686,7 @@ func (s *Service) searchAlbums(ctx context.Context, pattern string, limit int) (
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, &album.CoverArtID); err != nil {
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.Genre, &album.CoverArtID); err != nil {
return nil, fmt.Errorf("scan searched album: %w", err)
}
albums = append(albums, album)
@@ -753,7 +788,7 @@ func (s *Service) starredArtists(ctx context.Context, userID string) ([]Artist,
func (s *Service) starredAlbums(ctx context.Context, userID string) ([]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), COALESCE(al.cover_art_id, '')
`SELECT al.id, al.artist_id, a.name, al.title, COALESCE(al.year, 0), COUNT(t.id), COALESCE(al.genre, ''), COALESCE(al.cover_art_id, '')
FROM favorites f
JOIN albums al ON al.id = f.entity_id
JOIN artists a ON a.id = al.artist_id
@@ -771,7 +806,7 @@ func (s *Service) starredAlbums(ctx context.Context, userID string) ([]Album, er
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, &album.CoverArtID); err != nil {
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.Genre, &album.CoverArtID); err != nil {
return nil, fmt.Errorf("scan starred album: %w", err)
}
albums = append(albums, album)