feat: fetch all artist albums with caching, filter by type in handler
- Provider fetches all release groups via paginated browse, no type filter - Service caches all albums in DB, ensures referenced artists exist first - Server handler filters by album_types (default: album|ep|single), excludes secondary types - Add secondary_types to domain, DB schema, and MB types - Add ErrBadRequest handling for MusicBrainz 400 errors - Replace migrations with single schema.sql - Add GetAllByArtistID and SaveAll to album repository
This commit is contained in:
@@ -22,8 +22,8 @@ func NewAlbumRepository(pool *pgxpool.Pool) *AlbumRepository {
|
||||
|
||||
func (r *AlbumRepository) GetByID(ctx context.Context, id string) (*domain.Album, error) {
|
||||
query := `
|
||||
SELECT id, title, album_type, release_date, upc, total_tracks, total_discs,
|
||||
cover_url, source, source_id
|
||||
SELECT id, title, album_type, secondary_types, release_date, upc, total_tracks,
|
||||
total_discs, cover_url, source, source_id
|
||||
FROM albums
|
||||
WHERE id = $1`
|
||||
|
||||
@@ -41,8 +41,8 @@ func (r *AlbumRepository) GetByID(ctx context.Context, id string) (*domain.Album
|
||||
|
||||
func (r *AlbumRepository) GetByExternalID(ctx context.Context, source, sourceID string) (*domain.Album, error) {
|
||||
query := `
|
||||
SELECT a.id, a.title, a.album_type, a.release_date, a.upc, a.total_tracks,
|
||||
a.total_discs, a.cover_url, a.source, a.source_id
|
||||
SELECT a.id, a.title, a.album_type, a.secondary_types, a.release_date, a.upc,
|
||||
a.total_tracks, a.total_discs, a.cover_url, a.source, a.source_id
|
||||
FROM albums a
|
||||
JOIN album_external_ids e ON a.id = e.album_id
|
||||
WHERE e.source = $1 AND e.source_id = $2`
|
||||
@@ -68,8 +68,8 @@ func (r *AlbumRepository) GetByArtistID(ctx context.Context, artistID string, li
|
||||
WHERE ae.source_id = $1`
|
||||
|
||||
searchQuery := `
|
||||
SELECT DISTINCT a.id, a.title, a.album_type, a.release_date, a.upc,
|
||||
a.total_tracks, a.total_discs, a.cover_url, a.source, a.source_id
|
||||
SELECT DISTINCT a.id, a.title, a.album_type, a.secondary_types, a.release_date,
|
||||
a.upc, a.total_tracks, a.total_discs, a.cover_url, a.source, a.source_id
|
||||
FROM albums a
|
||||
JOIN album_artists aa ON a.id = aa.album_id
|
||||
JOIN artist_external_ids ae ON aa.artist_id = ae.artist_id
|
||||
@@ -105,6 +105,106 @@ func (r *AlbumRepository) GetByArtistID(ctx context.Context, artistID string, li
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *AlbumRepository) GetAllByArtistID(ctx context.Context, artistID string) ([]domain.Album, error) {
|
||||
query := `
|
||||
SELECT DISTINCT a.id, a.title, a.album_type, a.secondary_types, a.release_date,
|
||||
a.upc, a.total_tracks, a.total_discs, a.cover_url, a.source, a.source_id
|
||||
FROM albums a
|
||||
JOIN album_artists aa ON a.id = aa.album_id
|
||||
JOIN artist_external_ids ae ON aa.artist_id = ae.artist_id
|
||||
WHERE ae.source_id = $1
|
||||
ORDER BY a.release_date DESC NULLS LAST`
|
||||
|
||||
rows, err := r.pool.Query(ctx, query, artistID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var albums []domain.Album
|
||||
for rows.Next() {
|
||||
album, err := r.scanAlbumFromRow(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.loadRelations(ctx, album); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
albums = append(albums, *album)
|
||||
}
|
||||
|
||||
return albums, rows.Err()
|
||||
}
|
||||
|
||||
func (r *AlbumRepository) SaveAll(ctx context.Context, albums []domain.Album) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
for i := range albums {
|
||||
album := &albums[i]
|
||||
|
||||
var source, sourceID string
|
||||
if len(album.ExternalIDs) > 0 {
|
||||
source = album.ExternalIDs[0].Source
|
||||
sourceID = album.ExternalIDs[0].SourceID
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO albums (id, title, album_type, secondary_types, release_date, upc,
|
||||
total_tracks, total_discs, cover_url, source, source_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
album_type = EXCLUDED.album_type,
|
||||
secondary_types = EXCLUDED.secondary_types,
|
||||
release_date = EXCLUDED.release_date,
|
||||
upc = EXCLUDED.upc,
|
||||
total_tracks = EXCLUDED.total_tracks,
|
||||
total_discs = EXCLUDED.total_discs,
|
||||
cover_url = EXCLUDED.cover_url,
|
||||
updated_at = now()`
|
||||
|
||||
_, err = tx.Exec(ctx, query,
|
||||
album.ID, album.Title, nullString(album.Type), album.SecondaryTypes,
|
||||
album.ReleaseDate, nullString(album.UPC), album.TotalTracks, album.TotalDiscs,
|
||||
nullString(album.CoverURL), source, sourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ext := range album.ExternalIDs {
|
||||
extQuery := `
|
||||
INSERT INTO album_external_ids (album_id, source, source_id, url)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (album_id, source, source_id) DO UPDATE SET
|
||||
url = EXCLUDED.url,
|
||||
fetched_at = now()`
|
||||
|
||||
_, err = tx.Exec(ctx, extQuery, album.ID, ext.Source, ext.SourceID, nullString(ext.URL))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, ac := range album.Artists {
|
||||
artistQuery := `
|
||||
INSERT INTO album_artists (album_id, artist_id, role, position)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (album_id, artist_id, role) DO NOTHING`
|
||||
|
||||
_, err = tx.Exec(ctx, artistQuery, album.ID, ac.Artist.ID, ac.Role, ac.Position)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *AlbumRepository) Save(ctx context.Context, album *domain.Album) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -119,12 +219,13 @@ func (r *AlbumRepository) Save(ctx context.Context, album *domain.Album) error {
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO albums (id, title, album_type, release_date, upc, total_tracks,
|
||||
total_discs, cover_url, source, source_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
INSERT INTO albums (id, title, album_type, secondary_types, release_date, upc,
|
||||
total_tracks, total_discs, cover_url, source, source_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
title = EXCLUDED.title,
|
||||
album_type = EXCLUDED.album_type,
|
||||
secondary_types = EXCLUDED.secondary_types,
|
||||
release_date = EXCLUDED.release_date,
|
||||
upc = EXCLUDED.upc,
|
||||
total_tracks = EXCLUDED.total_tracks,
|
||||
@@ -133,8 +234,8 @@ func (r *AlbumRepository) Save(ctx context.Context, album *domain.Album) error {
|
||||
updated_at = now()`
|
||||
|
||||
_, err = tx.Exec(ctx, query,
|
||||
album.ID, album.Title, nullString(album.Type), album.ReleaseDate,
|
||||
nullString(album.UPC), album.TotalTracks, album.TotalDiscs,
|
||||
album.ID, album.Title, nullString(album.Type), album.SecondaryTypes,
|
||||
album.ReleaseDate, nullString(album.UPC), album.TotalTracks, album.TotalDiscs,
|
||||
nullString(album.CoverURL), source, sourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -192,7 +293,7 @@ func (r *AlbumRepository) scanAlbumRow(row pgx.Row) (*domain.Album, error) {
|
||||
)
|
||||
|
||||
err := row.Scan(
|
||||
&album.ID, &album.Title, &albumType, &releaseDate, &upc,
|
||||
&album.ID, &album.Title, &albumType, &album.SecondaryTypes, &releaseDate, &upc,
|
||||
&totalTracks, &totalDiscs, &coverURL, &source, &sourceID,
|
||||
)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
|
||||
Reference in New Issue
Block a user