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:
Alexander
2026-05-07 20:03:04 +02:00
parent 35ac167952
commit 9f1318e79e
17 changed files with 302 additions and 172 deletions
+35 -13
View File
@@ -3,11 +3,13 @@ package server
import (
"context"
"errors"
"strings"
"github.com/rs/zerolog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/metadata-agregator/internal/domain"
"github.com/metadata-agregator/internal/provider/musicbrainz"
"github.com/metadata-agregator/internal/repository"
"github.com/metadata-agregator/internal/service"
@@ -114,10 +116,9 @@ func (s *MetadataServer) SearchAlbums(ctx context.Context, req *metadatav1.Searc
limit = 25
}
albumTypes := defaultAlbumTypes(req.AlbumTypes)
log.Debug().Str("query", req.Query).Str("artist", req.Artist).Int("limit", limit).Int("offset", int(req.Offset)).Strs("album_types", albumTypes).Msg("searching albums")
log.Debug().Str("query", req.Query).Str("artist", req.Artist).Int("limit", limit).Int("offset", int(req.Offset)).Msg("searching albums")
result, err := svc.SearchAlbums(ctx, req.Query, req.Artist, limit, int(req.Offset), albumTypes)
result, err := svc.SearchAlbums(ctx, req.Query, req.Artist, limit, int(req.Offset))
if err != nil {
return nil, toGRPCError(ctx, err)
}
@@ -172,28 +173,25 @@ func (s *MetadataServer) GetArtistAlbums(ctx context.Context, req *metadatav1.Ge
return nil, err
}
limit := int(req.Limit)
if limit <= 0 {
limit = 25
}
albumTypes := defaultAlbumTypes(req.AlbumTypes)
log.Debug().Str("artist_id", req.ArtistId).Int("limit", limit).Int("offset", int(req.Offset)).Strs("album_types", albumTypes).Msg("getting artist albums")
log.Debug().Str("artist_id", req.ArtistId).Strs("album_types", albumTypes).Msg("getting artist albums")
result, err := svc.GetArtistAlbums(ctx, req.ArtistId, limit, int(req.Offset), albumTypes)
allAlbums, err := svc.GetArtistAlbums(ctx, req.ArtistId)
if err != nil {
return nil, toGRPCError(ctx, err)
}
filtered := filterAlbums(allAlbums, albumTypes)
resp := &metadatav1.GetArtistAlbumsResponse{
Total: int32(result.Total),
Total: int32(len(filtered)),
}
for _, a := range result.Items {
for _, a := range filtered {
resp.Albums = append(resp.Albums, toProtoAlbum(&a))
}
log.Trace().Int("total", result.Total).Int("returned", len(resp.Albums)).Msg("artist albums retrieved")
log.Trace().Int("total_fetched", len(allAlbums)).Int("filtered", len(filtered)).Strs("album_types", albumTypes).Msg("artist albums retrieved")
return resp, nil
}
@@ -279,6 +277,25 @@ func defaultAlbumTypes(types []string) []string {
return defaultTypes
}
func filterAlbums(albums []domain.Album, types []string) []domain.Album {
allowed := make(map[string]bool, len(types))
for _, t := range types {
allowed[strings.ToLower(t)] = true
}
var result []domain.Album
for _, a := range albums {
if len(a.SecondaryTypes) > 0 {
continue
}
if !allowed[strings.ToLower(a.Type)] {
continue
}
result = append(result, a)
}
return result
}
func toGRPCError(ctx context.Context, err error) error {
if err == nil {
return nil
@@ -286,6 +303,11 @@ func toGRPCError(ctx context.Context, err error) error {
log := zerolog.Ctx(ctx)
if errors.Is(err, musicbrainz.ErrBadRequest) {
log.Debug().Err(err).Msg("bad request")
return status.Error(codes.InvalidArgument, err.Error())
}
if errors.Is(err, repository.ErrNotFound) {
log.Debug().Msg("entity not found")
return status.Error(codes.NotFound, "not found")