feat: add scan status and cover art endpoints
Track scanner status for the web API and Subsonic-compatible scan endpoints, add authenticated cover art serving, and wire album artwork into the web UI. Keep Subsonic auth limited to legacy password mode for now so behavior stays honest with the current bcrypt-based user storage.
This commit is contained in:
@@ -3,9 +3,12 @@ package library
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type Artist struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -19,6 +22,7 @@ type Album struct {
|
||||
Title string `json:"title"`
|
||||
Year int `json:"year"`
|
||||
TrackCount int `json:"trackCount"`
|
||||
CoverArtID string `json:"coverArtId"`
|
||||
}
|
||||
|
||||
type Track struct {
|
||||
@@ -32,6 +36,7 @@ type Track struct {
|
||||
DurationSecs int `json:"durationSeconds"`
|
||||
FilePath string `json:"filePath"`
|
||||
ContentType string `json:"contentType"`
|
||||
CoverArtID string `json:"coverArtId"`
|
||||
}
|
||||
|
||||
type HomePayload struct {
|
||||
@@ -96,6 +101,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.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
|
||||
@@ -112,7 +118,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); err != nil {
|
||||
if err := rows.Scan(&album.ID, &album.ArtistID, &album.ArtistName, &album.Title, &album.Year, &album.TrackCount, &album.CoverArtID); err != nil {
|
||||
return nil, fmt.Errorf("scan album: %w", err)
|
||||
}
|
||||
albums = append(albums, album)
|
||||
@@ -125,7 +131,7 @@ 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, '')
|
||||
COALESCE(t.duration_seconds, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
|
||||
FROM tracks t
|
||||
JOIN artists a ON a.id = t.artist_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
@@ -152,6 +158,7 @@ func (s *Service) Tracks(ctx context.Context, limit int) ([]Track, error) {
|
||||
&track.DurationSecs,
|
||||
&track.FilePath,
|
||||
&track.ContentType,
|
||||
&track.CoverArtID,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("scan track: %w", err)
|
||||
}
|
||||
@@ -167,7 +174,7 @@ func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) {
|
||||
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, '')
|
||||
COALESCE(t.duration_seconds, 0), t.file_path, COALESCE(t.content_type, ''), COALESCE(al.cover_art_id, '')
|
||||
FROM tracks t
|
||||
JOIN artists a ON a.id = t.artist_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
@@ -184,6 +191,7 @@ func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) {
|
||||
&track.DurationSecs,
|
||||
&track.FilePath,
|
||||
&track.ContentType,
|
||||
&track.CoverArtID,
|
||||
)
|
||||
if err != nil {
|
||||
return Track{}, fmt.Errorf("query track by id: %w", err)
|
||||
@@ -191,3 +199,35 @@ func (s *Service) TrackByID(ctx context.Context, id string) (Track, error) {
|
||||
|
||||
return track, nil
|
||||
}
|
||||
|
||||
func (s *Service) CoverArtPathByEntityID(ctx context.Context, id string) (string, error) {
|
||||
var path string
|
||||
|
||||
err := s.db.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT cover_art_id FROM albums WHERE id = ? AND cover_art_id <> ''`,
|
||||
id,
|
||||
).Scan(&path)
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return "", fmt.Errorf("query album cover art: %w", err)
|
||||
}
|
||||
|
||||
err = s.db.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT al.cover_art_id
|
||||
FROM tracks t
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
WHERE t.id = ? AND al.cover_art_id <> ''`,
|
||||
id,
|
||||
).Scan(&path)
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return "", fmt.Errorf("query track cover art: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user