From fb2e4b91071f6ab710cbe6cbed9869d2832b7c2b Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 7 May 2026 14:24:14 +0200 Subject: [PATCH] Add SearchAlbum endpoint --- .gitignore | 1 + database/postgresql.conf | 1 + internal/provider/musicbrainz/provider.go | 43 +++ internal/provider/provider.go | 1 + internal/server/server.go | 27 ++ internal/service/metadata.go | 4 + pkg/gen/metadata/v1/metadata.pb.go | 338 +++++++++++++++------- pkg/gen/metadata/v1/metadata_grpc.pb.go | 40 +++ proto/metadata/v1/metadata.proto | 16 + 9 files changed, 374 insertions(+), 97 deletions(-) diff --git a/.gitignore b/.gitignore index 0613670..0beef97 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ server *.test *.out .env +config.yaml *.log vendor/ .pre-commit-config.yaml diff --git a/database/postgresql.conf b/database/postgresql.conf index 08f7e2e..ebce120 100644 --- a/database/postgresql.conf +++ b/database/postgresql.conf @@ -1,3 +1,4 @@ +listen_addresses = '*' shared_preload_libraries = 'pg_prewarm' pg_prewarm.autoprewarm = true diff --git a/internal/provider/musicbrainz/provider.go b/internal/provider/musicbrainz/provider.go index 66c2ac9..f55e0c1 100644 --- a/internal/provider/musicbrainz/provider.go +++ b/internal/provider/musicbrainz/provider.go @@ -91,6 +91,49 @@ func (p *Provider) GetAlbum(ctx context.Context, id string) (*domain.Album, erro return mapAlbum(mb, release), nil } +func (p *Provider) SearchAlbums(ctx context.Context, query string, artist string, limit, offset int) (*domain.SearchResult[domain.Album], error) { + if limit <= 0 || limit > 100 { + limit = 25 + } + + var luceneQuery string + if artist != "" && query != "" { + luceneQuery = fmt.Sprintf("releasegroup:%s AND artist:%s", escapeQuery(query), escapeQuery(artist)) + } else if artist != "" { + luceneQuery = fmt.Sprintf("artist:%s", escapeQuery(artist)) + } else { + luceneQuery = fmt.Sprintf("releasegroup:%s", escapeQuery(query)) + } + + data, err := p.client.search(ctx, "release-group", luceneQuery, limit, offset) + if err != nil { + return nil, fmt.Errorf("search albums: %w", err) + } + + var resp struct { + Count int `json:"count"` + Offset int `json:"offset"` + ReleaseGroups []*mbReleaseGroup `json:"release-groups"` + } + if err := decodeInto(data, &resp); err != nil { + return nil, err + } + + result := &domain.SearchResult[domain.Album]{ + Total: resp.Count, + Limit: limit, + Offset: offset, + } + + for _, mb := range resp.ReleaseGroups { + if album := mapAlbum(mb, nil); album != nil { + result.Items = append(result.Items, *album) + } + } + + return result, nil +} + func (p *Provider) GetArtistAlbums(ctx context.Context, artistID string, limit, offset int) (*domain.SearchResult[domain.Album], error) { if limit <= 0 || limit > 100 { limit = 25 diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5f71dbc..d350567 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -13,6 +13,7 @@ type Provider interface { SearchArtists(ctx context.Context, query string, limit, offset int) (*domain.SearchResult[domain.Artist], error) GetAlbum(ctx context.Context, id string) (*domain.Album, error) + SearchAlbums(ctx context.Context, query string, artist string, limit, offset int) (*domain.SearchResult[domain.Album], error) GetArtistAlbums(ctx context.Context, artistID string, limit, offset int) (*domain.SearchResult[domain.Album], error) GetTrack(ctx context.Context, id string) (*domain.Track, error) diff --git a/internal/server/server.go b/internal/server/server.go index 9c6f44e..e6b230b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -86,6 +86,33 @@ func (s *MetadataServer) SearchArtists(ctx context.Context, req *metadatav1.Sear return resp, nil } +func (s *MetadataServer) SearchAlbums(ctx context.Context, req *metadatav1.SearchAlbumsRequest) (*metadatav1.SearchAlbumsResponse, error) { + svc, err := s.getService(req.Provider) + if err != nil { + return nil, err + } + + limit := int(req.Limit) + if limit <= 0 { + limit = 25 + } + + result, err := svc.SearchAlbums(ctx, req.Query, req.Artist, limit, int(req.Offset)) + if err != nil { + return nil, toGRPCError(err) + } + + resp := &metadatav1.SearchAlbumsResponse{ + Total: int32(result.Total), + } + + for _, a := range result.Items { + resp.Albums = append(resp.Albums, toProtoAlbum(&a)) + } + + return resp, nil +} + func (s *MetadataServer) GetAlbum(ctx context.Context, req *metadatav1.GetAlbumRequest) (*metadatav1.Album, error) { svc, err := s.getService(req.Provider) if err != nil { diff --git a/internal/service/metadata.go b/internal/service/metadata.go index b7922a6..497270f 100644 --- a/internal/service/metadata.go +++ b/internal/service/metadata.go @@ -60,6 +60,10 @@ func (s *MetadataService) SearchArtists(ctx context.Context, query string, limit return s.provider.SearchArtists(ctx, query, limit, offset) } +func (s *MetadataService) SearchAlbums(ctx context.Context, query string, artist string, limit, offset int) (*domain.SearchResult[domain.Album], error) { + return s.provider.SearchAlbums(ctx, query, artist, limit, offset) +} + func (s *MetadataService) GetAlbum(ctx context.Context, id string) (*domain.Album, error) { album, err := s.albums.GetByExternalID(ctx, s.provider.Name(), id) if err == nil { diff --git a/pkg/gen/metadata/v1/metadata.pb.go b/pkg/gen/metadata/v1/metadata.pb.go index 4bd0eef..d9fc054 100644 --- a/pkg/gen/metadata/v1/metadata.pb.go +++ b/pkg/gen/metadata/v1/metadata.pb.go @@ -541,6 +541,134 @@ func (x *GetAlbumTracksRequest) GetProvider() Provider { return Provider_PROVIDER_UNSPECIFIED } +type SearchAlbumsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + Artist string `protobuf:"bytes,2,opt,name=artist,proto3" json:"artist,omitempty"` + Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int32 `protobuf:"varint,4,opt,name=offset,proto3" json:"offset,omitempty"` + Provider Provider `protobuf:"varint,5,opt,name=provider,proto3,enum=metadata.v1.Provider" json:"provider,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SearchAlbumsRequest) Reset() { + *x = SearchAlbumsRequest{} + mi := &file_metadata_v1_metadata_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchAlbumsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchAlbumsRequest) ProtoMessage() {} + +func (x *SearchAlbumsRequest) ProtoReflect() protoreflect.Message { + mi := &file_metadata_v1_metadata_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchAlbumsRequest.ProtoReflect.Descriptor instead. +func (*SearchAlbumsRequest) Descriptor() ([]byte, []int) { + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{6} +} + +func (x *SearchAlbumsRequest) GetQuery() string { + if x != nil { + return x.Query + } + return "" +} + +func (x *SearchAlbumsRequest) GetArtist() string { + if x != nil { + return x.Artist + } + return "" +} + +func (x *SearchAlbumsRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *SearchAlbumsRequest) GetOffset() int32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *SearchAlbumsRequest) GetProvider() Provider { + if x != nil { + return x.Provider + } + return Provider_PROVIDER_UNSPECIFIED +} + +type SearchAlbumsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Albums []*Album `protobuf:"bytes,1,rep,name=albums,proto3" json:"albums,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SearchAlbumsResponse) Reset() { + *x = SearchAlbumsResponse{} + mi := &file_metadata_v1_metadata_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchAlbumsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchAlbumsResponse) ProtoMessage() {} + +func (x *SearchAlbumsResponse) ProtoReflect() protoreflect.Message { + mi := &file_metadata_v1_metadata_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchAlbumsResponse.ProtoReflect.Descriptor instead. +func (*SearchAlbumsResponse) Descriptor() ([]byte, []int) { + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{7} +} + +func (x *SearchAlbumsResponse) GetAlbums() []*Album { + if x != nil { + return x.Albums + } + return nil +} + +func (x *SearchAlbumsResponse) GetTotal() int32 { + if x != nil { + return x.Total + } + return 0 +} + type SyncArtistRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Target: @@ -555,7 +683,7 @@ type SyncArtistRequest struct { func (x *SyncArtistRequest) Reset() { *x = SyncArtistRequest{} - mi := &file_metadata_v1_metadata_proto_msgTypes[6] + mi := &file_metadata_v1_metadata_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -567,7 +695,7 @@ func (x *SyncArtistRequest) String() string { func (*SyncArtistRequest) ProtoMessage() {} func (x *SyncArtistRequest) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[6] + mi := &file_metadata_v1_metadata_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -580,7 +708,7 @@ func (x *SyncArtistRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncArtistRequest.ProtoReflect.Descriptor instead. func (*SyncArtistRequest) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{6} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{8} } func (x *SyncArtistRequest) GetTarget() isSyncArtistRequest_Target { @@ -641,7 +769,7 @@ type SearchArtistsResponse struct { func (x *SearchArtistsResponse) Reset() { *x = SearchArtistsResponse{} - mi := &file_metadata_v1_metadata_proto_msgTypes[7] + mi := &file_metadata_v1_metadata_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -653,7 +781,7 @@ func (x *SearchArtistsResponse) String() string { func (*SearchArtistsResponse) ProtoMessage() {} func (x *SearchArtistsResponse) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[7] + mi := &file_metadata_v1_metadata_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -666,7 +794,7 @@ func (x *SearchArtistsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SearchArtistsResponse.ProtoReflect.Descriptor instead. func (*SearchArtistsResponse) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{7} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{9} } func (x *SearchArtistsResponse) GetArtists() []*Artist { @@ -693,7 +821,7 @@ type GetArtistAlbumsResponse struct { func (x *GetArtistAlbumsResponse) Reset() { *x = GetArtistAlbumsResponse{} - mi := &file_metadata_v1_metadata_proto_msgTypes[8] + mi := &file_metadata_v1_metadata_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -705,7 +833,7 @@ func (x *GetArtistAlbumsResponse) String() string { func (*GetArtistAlbumsResponse) ProtoMessage() {} func (x *GetArtistAlbumsResponse) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[8] + mi := &file_metadata_v1_metadata_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -718,7 +846,7 @@ func (x *GetArtistAlbumsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetArtistAlbumsResponse.ProtoReflect.Descriptor instead. func (*GetArtistAlbumsResponse) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{8} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{10} } func (x *GetArtistAlbumsResponse) GetAlbums() []*Album { @@ -744,7 +872,7 @@ type GetAlbumTracksResponse struct { func (x *GetAlbumTracksResponse) Reset() { *x = GetAlbumTracksResponse{} - mi := &file_metadata_v1_metadata_proto_msgTypes[9] + mi := &file_metadata_v1_metadata_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +884,7 @@ func (x *GetAlbumTracksResponse) String() string { func (*GetAlbumTracksResponse) ProtoMessage() {} func (x *GetAlbumTracksResponse) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[9] + mi := &file_metadata_v1_metadata_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -769,7 +897,7 @@ func (x *GetAlbumTracksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAlbumTracksResponse.ProtoReflect.Descriptor instead. func (*GetAlbumTracksResponse) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{9} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{11} } func (x *GetAlbumTracksResponse) GetTracks() []*Track { @@ -790,7 +918,7 @@ type SyncArtistResponse struct { func (x *SyncArtistResponse) Reset() { *x = SyncArtistResponse{} - mi := &file_metadata_v1_metadata_proto_msgTypes[10] + mi := &file_metadata_v1_metadata_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -802,7 +930,7 @@ func (x *SyncArtistResponse) String() string { func (*SyncArtistResponse) ProtoMessage() {} func (x *SyncArtistResponse) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[10] + mi := &file_metadata_v1_metadata_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -815,7 +943,7 @@ func (x *SyncArtistResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncArtistResponse.ProtoReflect.Descriptor instead. func (*SyncArtistResponse) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{10} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{12} } func (x *SyncArtistResponse) GetArtist() *Artist { @@ -858,7 +986,7 @@ type Artist struct { func (x *Artist) Reset() { *x = Artist{} - mi := &file_metadata_v1_metadata_proto_msgTypes[11] + mi := &file_metadata_v1_metadata_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -870,7 +998,7 @@ func (x *Artist) String() string { func (*Artist) ProtoMessage() {} func (x *Artist) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[11] + mi := &file_metadata_v1_metadata_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -883,7 +1011,7 @@ func (x *Artist) ProtoReflect() protoreflect.Message { // Deprecated: Use Artist.ProtoReflect.Descriptor instead. func (*Artist) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{11} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{13} } func (x *Artist) GetId() string { @@ -983,7 +1111,7 @@ type Album struct { func (x *Album) Reset() { *x = Album{} - mi := &file_metadata_v1_metadata_proto_msgTypes[12] + mi := &file_metadata_v1_metadata_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -995,7 +1123,7 @@ func (x *Album) String() string { func (*Album) ProtoMessage() {} func (x *Album) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[12] + mi := &file_metadata_v1_metadata_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1008,7 +1136,7 @@ func (x *Album) ProtoReflect() protoreflect.Message { // Deprecated: Use Album.ProtoReflect.Descriptor instead. func (*Album) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{12} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{14} } func (x *Album) GetId() string { @@ -1113,7 +1241,7 @@ type Track struct { func (x *Track) Reset() { *x = Track{} - mi := &file_metadata_v1_metadata_proto_msgTypes[13] + mi := &file_metadata_v1_metadata_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1125,7 +1253,7 @@ func (x *Track) String() string { func (*Track) ProtoMessage() {} func (x *Track) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[13] + mi := &file_metadata_v1_metadata_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1138,7 +1266,7 @@ func (x *Track) ProtoReflect() protoreflect.Message { // Deprecated: Use Track.ProtoReflect.Descriptor instead. func (*Track) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{13} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{15} } func (x *Track) GetId() string { @@ -1224,7 +1352,7 @@ type Work struct { func (x *Work) Reset() { *x = Work{} - mi := &file_metadata_v1_metadata_proto_msgTypes[14] + mi := &file_metadata_v1_metadata_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1236,7 +1364,7 @@ func (x *Work) String() string { func (*Work) ProtoMessage() {} func (x *Work) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[14] + mi := &file_metadata_v1_metadata_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1249,7 +1377,7 @@ func (x *Work) ProtoReflect() protoreflect.Message { // Deprecated: Use Work.ProtoReflect.Descriptor instead. func (*Work) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{14} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{16} } func (x *Work) GetId() string { @@ -1298,7 +1426,7 @@ type Label struct { func (x *Label) Reset() { *x = Label{} - mi := &file_metadata_v1_metadata_proto_msgTypes[15] + mi := &file_metadata_v1_metadata_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1310,7 +1438,7 @@ func (x *Label) String() string { func (*Label) ProtoMessage() {} func (x *Label) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[15] + mi := &file_metadata_v1_metadata_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1323,7 +1451,7 @@ func (x *Label) ProtoReflect() protoreflect.Message { // Deprecated: Use Label.ProtoReflect.Descriptor instead. func (*Label) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{15} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{17} } func (x *Label) GetId() string { @@ -1357,7 +1485,7 @@ type Genre struct { func (x *Genre) Reset() { *x = Genre{} - mi := &file_metadata_v1_metadata_proto_msgTypes[16] + mi := &file_metadata_v1_metadata_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1369,7 +1497,7 @@ func (x *Genre) String() string { func (*Genre) ProtoMessage() {} func (x *Genre) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[16] + mi := &file_metadata_v1_metadata_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1382,7 +1510,7 @@ func (x *Genre) ProtoReflect() protoreflect.Message { // Deprecated: Use Genre.ProtoReflect.Descriptor instead. func (*Genre) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{16} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{18} } func (x *Genre) GetId() string { @@ -1411,7 +1539,7 @@ type ArtistCredit struct { func (x *ArtistCredit) Reset() { *x = ArtistCredit{} - mi := &file_metadata_v1_metadata_proto_msgTypes[17] + mi := &file_metadata_v1_metadata_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1423,7 +1551,7 @@ func (x *ArtistCredit) String() string { func (*ArtistCredit) ProtoMessage() {} func (x *ArtistCredit) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[17] + mi := &file_metadata_v1_metadata_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1436,7 +1564,7 @@ func (x *ArtistCredit) ProtoReflect() protoreflect.Message { // Deprecated: Use ArtistCredit.ProtoReflect.Descriptor instead. func (*ArtistCredit) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{17} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{19} } func (x *ArtistCredit) GetArtist() *Artist { @@ -1478,7 +1606,7 @@ type ExternalID struct { func (x *ExternalID) Reset() { *x = ExternalID{} - mi := &file_metadata_v1_metadata_proto_msgTypes[18] + mi := &file_metadata_v1_metadata_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1490,7 +1618,7 @@ func (x *ExternalID) String() string { func (*ExternalID) ProtoMessage() {} func (x *ExternalID) ProtoReflect() protoreflect.Message { - mi := &file_metadata_v1_metadata_proto_msgTypes[18] + mi := &file_metadata_v1_metadata_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1503,7 +1631,7 @@ func (x *ExternalID) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalID.ProtoReflect.Descriptor instead. func (*ExternalID) Descriptor() ([]byte, []int) { - return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{18} + return file_metadata_v1_metadata_proto_rawDescGZIP(), []int{20} } func (x *ExternalID) GetSource() string { @@ -1563,7 +1691,16 @@ const file_metadata_v1_metadata_proto_rawDesc = "" + "identifier\"e\n" + "\x15GetAlbumTracksRequest\x12\x19\n" + "\balbum_id\x18\x01 \x01(\tR\aalbumId\x121\n" + - "\bprovider\x18\x02 \x01(\x0e2\x15.metadata.v1.ProviderR\bprovider\"\x9d\x01\n" + + "\bprovider\x18\x02 \x01(\x0e2\x15.metadata.v1.ProviderR\bprovider\"\xa4\x01\n" + + "\x13SearchAlbumsRequest\x12\x14\n" + + "\x05query\x18\x01 \x01(\tR\x05query\x12\x16\n" + + "\x06artist\x18\x02 \x01(\tR\x06artist\x12\x14\n" + + "\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x16\n" + + "\x06offset\x18\x04 \x01(\x05R\x06offset\x121\n" + + "\bprovider\x18\x05 \x01(\x0e2\x15.metadata.v1.ProviderR\bprovider\"X\n" + + "\x14SearchAlbumsResponse\x12*\n" + + "\x06albums\x18\x01 \x03(\v2\x12.metadata.v1.AlbumR\x06albums\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\x9d\x01\n" + "\x11SyncArtistRequest\x12\x14\n" + "\x04name\x18\x01 \x01(\tH\x00R\x04name\x125\n" + "\bexternal\x18\x02 \x01(\v2\x17.metadata.v1.ExternalIDH\x00R\bexternal\x121\n" + @@ -1652,14 +1789,15 @@ const file_metadata_v1_metadata_proto_rawDesc = "" + "\x03url\x18\x03 \x01(\tR\x03url*>\n" + "\bProvider\x12\x18\n" + "\x14PROVIDER_UNSPECIFIED\x10\x00\x12\x18\n" + - "\x14PROVIDER_MUSICBRAINZ\x10\x012\xae\x04\n" + + "\x14PROVIDER_MUSICBRAINZ\x10\x012\x83\x05\n" + "\x0fMetadataService\x12?\n" + "\tGetArtist\x12\x1d.metadata.v1.GetArtistRequest\x1a\x13.metadata.v1.Artist\x12V\n" + "\rSearchArtists\x12!.metadata.v1.SearchArtistsRequest\x1a\".metadata.v1.SearchArtistsResponse\x12<\n" + "\bGetAlbum\x12\x1c.metadata.v1.GetAlbumRequest\x1a\x12.metadata.v1.Album\x12\\\n" + "\x0fGetArtistAlbums\x12#.metadata.v1.GetArtistAlbumsRequest\x1a$.metadata.v1.GetArtistAlbumsResponse\x12<\n" + "\bGetTrack\x12\x1c.metadata.v1.GetTrackRequest\x1a\x12.metadata.v1.Track\x12Y\n" + - "\x0eGetAlbumTracks\x12\".metadata.v1.GetAlbumTracksRequest\x1a#.metadata.v1.GetAlbumTracksResponse\x12M\n" + + "\x0eGetAlbumTracks\x12\".metadata.v1.GetAlbumTracksRequest\x1a#.metadata.v1.GetAlbumTracksResponse\x12S\n" + + "\fSearchAlbums\x12 .metadata.v1.SearchAlbumsRequest\x1a!.metadata.v1.SearchAlbumsResponse\x12M\n" + "\n" + "SyncArtist\x12\x1e.metadata.v1.SyncArtistRequest\x1a\x1f.metadata.v1.SyncArtistResponseB\xab\x01\n" + "\x0fcom.metadata.v1B\rMetadataProtoP\x01Z metadata.v1.ExternalID + 21, // 0: metadata.v1.GetArtistRequest.external:type_name -> metadata.v1.ExternalID 0, // 1: metadata.v1.GetArtistRequest.provider:type_name -> metadata.v1.Provider 0, // 2: metadata.v1.SearchArtistsRequest.provider:type_name -> metadata.v1.Provider - 19, // 3: metadata.v1.GetAlbumRequest.external:type_name -> metadata.v1.ExternalID + 21, // 3: metadata.v1.GetAlbumRequest.external:type_name -> metadata.v1.ExternalID 0, // 4: metadata.v1.GetAlbumRequest.provider:type_name -> metadata.v1.Provider 0, // 5: metadata.v1.GetArtistAlbumsRequest.provider:type_name -> metadata.v1.Provider - 19, // 6: metadata.v1.GetTrackRequest.external:type_name -> metadata.v1.ExternalID + 21, // 6: metadata.v1.GetTrackRequest.external:type_name -> metadata.v1.ExternalID 0, // 7: metadata.v1.GetTrackRequest.provider:type_name -> metadata.v1.Provider 0, // 8: metadata.v1.GetAlbumTracksRequest.provider:type_name -> metadata.v1.Provider - 19, // 9: metadata.v1.SyncArtistRequest.external:type_name -> metadata.v1.ExternalID - 0, // 10: metadata.v1.SyncArtistRequest.provider:type_name -> metadata.v1.Provider - 12, // 11: metadata.v1.SearchArtistsResponse.artists:type_name -> metadata.v1.Artist - 13, // 12: metadata.v1.GetArtistAlbumsResponse.albums:type_name -> metadata.v1.Album - 14, // 13: metadata.v1.GetAlbumTracksResponse.tracks:type_name -> metadata.v1.Track - 12, // 14: metadata.v1.SyncArtistResponse.artist:type_name -> metadata.v1.Artist - 17, // 15: metadata.v1.Artist.genres:type_name -> metadata.v1.Genre - 19, // 16: metadata.v1.Artist.external_ids:type_name -> metadata.v1.ExternalID - 18, // 17: metadata.v1.Album.artists:type_name -> metadata.v1.ArtistCredit - 16, // 18: metadata.v1.Album.label:type_name -> metadata.v1.Label - 17, // 19: metadata.v1.Album.genres:type_name -> metadata.v1.Genre - 19, // 20: metadata.v1.Album.external_ids:type_name -> metadata.v1.ExternalID - 18, // 21: metadata.v1.Track.artists:type_name -> metadata.v1.ArtistCredit - 15, // 22: metadata.v1.Track.work:type_name -> metadata.v1.Work - 19, // 23: metadata.v1.Track.external_ids:type_name -> metadata.v1.ExternalID - 18, // 24: metadata.v1.Work.composers:type_name -> metadata.v1.ArtistCredit - 12, // 25: metadata.v1.ArtistCredit.artist:type_name -> metadata.v1.Artist - 1, // 26: metadata.v1.MetadataService.GetArtist:input_type -> metadata.v1.GetArtistRequest - 2, // 27: metadata.v1.MetadataService.SearchArtists:input_type -> metadata.v1.SearchArtistsRequest - 3, // 28: metadata.v1.MetadataService.GetAlbum:input_type -> metadata.v1.GetAlbumRequest - 4, // 29: metadata.v1.MetadataService.GetArtistAlbums:input_type -> metadata.v1.GetArtistAlbumsRequest - 5, // 30: metadata.v1.MetadataService.GetTrack:input_type -> metadata.v1.GetTrackRequest - 6, // 31: metadata.v1.MetadataService.GetAlbumTracks:input_type -> metadata.v1.GetAlbumTracksRequest - 7, // 32: metadata.v1.MetadataService.SyncArtist:input_type -> metadata.v1.SyncArtistRequest - 12, // 33: metadata.v1.MetadataService.GetArtist:output_type -> metadata.v1.Artist - 8, // 34: metadata.v1.MetadataService.SearchArtists:output_type -> metadata.v1.SearchArtistsResponse - 13, // 35: metadata.v1.MetadataService.GetAlbum:output_type -> metadata.v1.Album - 9, // 36: metadata.v1.MetadataService.GetArtistAlbums:output_type -> metadata.v1.GetArtistAlbumsResponse - 14, // 37: metadata.v1.MetadataService.GetTrack:output_type -> metadata.v1.Track - 10, // 38: metadata.v1.MetadataService.GetAlbumTracks:output_type -> metadata.v1.GetAlbumTracksResponse - 11, // 39: metadata.v1.MetadataService.SyncArtist:output_type -> metadata.v1.SyncArtistResponse - 33, // [33:40] is the sub-list for method output_type - 26, // [26:33] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 0, // 9: metadata.v1.SearchAlbumsRequest.provider:type_name -> metadata.v1.Provider + 15, // 10: metadata.v1.SearchAlbumsResponse.albums:type_name -> metadata.v1.Album + 21, // 11: metadata.v1.SyncArtistRequest.external:type_name -> metadata.v1.ExternalID + 0, // 12: metadata.v1.SyncArtistRequest.provider:type_name -> metadata.v1.Provider + 14, // 13: metadata.v1.SearchArtistsResponse.artists:type_name -> metadata.v1.Artist + 15, // 14: metadata.v1.GetArtistAlbumsResponse.albums:type_name -> metadata.v1.Album + 16, // 15: metadata.v1.GetAlbumTracksResponse.tracks:type_name -> metadata.v1.Track + 14, // 16: metadata.v1.SyncArtistResponse.artist:type_name -> metadata.v1.Artist + 19, // 17: metadata.v1.Artist.genres:type_name -> metadata.v1.Genre + 21, // 18: metadata.v1.Artist.external_ids:type_name -> metadata.v1.ExternalID + 20, // 19: metadata.v1.Album.artists:type_name -> metadata.v1.ArtistCredit + 18, // 20: metadata.v1.Album.label:type_name -> metadata.v1.Label + 19, // 21: metadata.v1.Album.genres:type_name -> metadata.v1.Genre + 21, // 22: metadata.v1.Album.external_ids:type_name -> metadata.v1.ExternalID + 20, // 23: metadata.v1.Track.artists:type_name -> metadata.v1.ArtistCredit + 17, // 24: metadata.v1.Track.work:type_name -> metadata.v1.Work + 21, // 25: metadata.v1.Track.external_ids:type_name -> metadata.v1.ExternalID + 20, // 26: metadata.v1.Work.composers:type_name -> metadata.v1.ArtistCredit + 14, // 27: metadata.v1.ArtistCredit.artist:type_name -> metadata.v1.Artist + 1, // 28: metadata.v1.MetadataService.GetArtist:input_type -> metadata.v1.GetArtistRequest + 2, // 29: metadata.v1.MetadataService.SearchArtists:input_type -> metadata.v1.SearchArtistsRequest + 3, // 30: metadata.v1.MetadataService.GetAlbum:input_type -> metadata.v1.GetAlbumRequest + 4, // 31: metadata.v1.MetadataService.GetArtistAlbums:input_type -> metadata.v1.GetArtistAlbumsRequest + 5, // 32: metadata.v1.MetadataService.GetTrack:input_type -> metadata.v1.GetTrackRequest + 6, // 33: metadata.v1.MetadataService.GetAlbumTracks:input_type -> metadata.v1.GetAlbumTracksRequest + 7, // 34: metadata.v1.MetadataService.SearchAlbums:input_type -> metadata.v1.SearchAlbumsRequest + 9, // 35: metadata.v1.MetadataService.SyncArtist:input_type -> metadata.v1.SyncArtistRequest + 14, // 36: metadata.v1.MetadataService.GetArtist:output_type -> metadata.v1.Artist + 10, // 37: metadata.v1.MetadataService.SearchArtists:output_type -> metadata.v1.SearchArtistsResponse + 15, // 38: metadata.v1.MetadataService.GetAlbum:output_type -> metadata.v1.Album + 11, // 39: metadata.v1.MetadataService.GetArtistAlbums:output_type -> metadata.v1.GetArtistAlbumsResponse + 16, // 40: metadata.v1.MetadataService.GetTrack:output_type -> metadata.v1.Track + 12, // 41: metadata.v1.MetadataService.GetAlbumTracks:output_type -> metadata.v1.GetAlbumTracksResponse + 8, // 42: metadata.v1.MetadataService.SearchAlbums:output_type -> metadata.v1.SearchAlbumsResponse + 13, // 43: metadata.v1.MetadataService.SyncArtist:output_type -> metadata.v1.SyncArtistResponse + 36, // [36:44] is the sub-list for method output_type + 28, // [28:36] is the sub-list for method input_type + 28, // [28:28] is the sub-list for extension type_name + 28, // [28:28] is the sub-list for extension extendee + 0, // [0:28] is the sub-list for field type_name } func init() { file_metadata_v1_metadata_proto_init() } @@ -1766,7 +1910,7 @@ func file_metadata_v1_metadata_proto_init() { (*GetTrackRequest_External)(nil), (*GetTrackRequest_Isrc)(nil), } - file_metadata_v1_metadata_proto_msgTypes[6].OneofWrappers = []any{ + file_metadata_v1_metadata_proto_msgTypes[8].OneofWrappers = []any{ (*SyncArtistRequest_Name)(nil), (*SyncArtistRequest_External)(nil), } @@ -1776,7 +1920,7 @@ func file_metadata_v1_metadata_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_metadata_v1_metadata_proto_rawDesc), len(file_metadata_v1_metadata_proto_rawDesc)), NumEnums: 1, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/gen/metadata/v1/metadata_grpc.pb.go b/pkg/gen/metadata/v1/metadata_grpc.pb.go index e9a1b35..0b69e17 100644 --- a/pkg/gen/metadata/v1/metadata_grpc.pb.go +++ b/pkg/gen/metadata/v1/metadata_grpc.pb.go @@ -25,6 +25,7 @@ const ( MetadataService_GetArtistAlbums_FullMethodName = "/metadata.v1.MetadataService/GetArtistAlbums" MetadataService_GetTrack_FullMethodName = "/metadata.v1.MetadataService/GetTrack" MetadataService_GetAlbumTracks_FullMethodName = "/metadata.v1.MetadataService/GetAlbumTracks" + MetadataService_SearchAlbums_FullMethodName = "/metadata.v1.MetadataService/SearchAlbums" MetadataService_SyncArtist_FullMethodName = "/metadata.v1.MetadataService/SyncArtist" ) @@ -46,6 +47,8 @@ type MetadataServiceClient interface { GetTrack(ctx context.Context, in *GetTrackRequest, opts ...grpc.CallOption) (*Track, error) // GetAlbumTracks retrieves all tracks on an album. GetAlbumTracks(ctx context.Context, in *GetAlbumTracksRequest, opts ...grpc.CallOption) (*GetAlbumTracksResponse, error) + // SearchAlbums searches for albums by name, optionally filtered by artist. + SearchAlbums(ctx context.Context, in *SearchAlbumsRequest, opts ...grpc.CallOption) (*SearchAlbumsResponse, error) // SyncArtist triggers ingestion of an artist from external sources. SyncArtist(ctx context.Context, in *SyncArtistRequest, opts ...grpc.CallOption) (*SyncArtistResponse, error) } @@ -118,6 +121,16 @@ func (c *metadataServiceClient) GetAlbumTracks(ctx context.Context, in *GetAlbum return out, nil } +func (c *metadataServiceClient) SearchAlbums(ctx context.Context, in *SearchAlbumsRequest, opts ...grpc.CallOption) (*SearchAlbumsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchAlbumsResponse) + err := c.cc.Invoke(ctx, MetadataService_SearchAlbums_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *metadataServiceClient) SyncArtist(ctx context.Context, in *SyncArtistRequest, opts ...grpc.CallOption) (*SyncArtistResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SyncArtistResponse) @@ -146,6 +159,8 @@ type MetadataServiceServer interface { GetTrack(context.Context, *GetTrackRequest) (*Track, error) // GetAlbumTracks retrieves all tracks on an album. GetAlbumTracks(context.Context, *GetAlbumTracksRequest) (*GetAlbumTracksResponse, error) + // SearchAlbums searches for albums by name, optionally filtered by artist. + SearchAlbums(context.Context, *SearchAlbumsRequest) (*SearchAlbumsResponse, error) // SyncArtist triggers ingestion of an artist from external sources. SyncArtist(context.Context, *SyncArtistRequest) (*SyncArtistResponse, error) mustEmbedUnimplementedMetadataServiceServer() @@ -176,6 +191,9 @@ func (UnimplementedMetadataServiceServer) GetTrack(context.Context, *GetTrackReq func (UnimplementedMetadataServiceServer) GetAlbumTracks(context.Context, *GetAlbumTracksRequest) (*GetAlbumTracksResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetAlbumTracks not implemented") } +func (UnimplementedMetadataServiceServer) SearchAlbums(context.Context, *SearchAlbumsRequest) (*SearchAlbumsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SearchAlbums not implemented") +} func (UnimplementedMetadataServiceServer) SyncArtist(context.Context, *SyncArtistRequest) (*SyncArtistResponse, error) { return nil, status.Error(codes.Unimplemented, "method SyncArtist not implemented") } @@ -308,6 +326,24 @@ func _MetadataService_GetAlbumTracks_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _MetadataService_SearchAlbums_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchAlbumsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MetadataServiceServer).SearchAlbums(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MetadataService_SearchAlbums_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MetadataServiceServer).SearchAlbums(ctx, req.(*SearchAlbumsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _MetadataService_SyncArtist_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SyncArtistRequest) if err := dec(in); err != nil { @@ -357,6 +393,10 @@ var MetadataService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetAlbumTracks", Handler: _MetadataService_GetAlbumTracks_Handler, }, + { + MethodName: "SearchAlbums", + Handler: _MetadataService_SearchAlbums_Handler, + }, { MethodName: "SyncArtist", Handler: _MetadataService_SyncArtist_Handler, diff --git a/proto/metadata/v1/metadata.proto b/proto/metadata/v1/metadata.proto index d0f2b77..2897009 100644 --- a/proto/metadata/v1/metadata.proto +++ b/proto/metadata/v1/metadata.proto @@ -29,6 +29,9 @@ service MetadataService { // GetAlbumTracks retrieves all tracks on an album. rpc GetAlbumTracks(GetAlbumTracksRequest) returns (GetAlbumTracksResponse); + // SearchAlbums searches for albums by name, optionally filtered by artist. + rpc SearchAlbums(SearchAlbumsRequest) returns (SearchAlbumsResponse); + // SyncArtist triggers ingestion of an artist from external sources. rpc SyncArtist(SyncArtistRequest) returns (SyncArtistResponse); } @@ -79,6 +82,19 @@ message GetAlbumTracksRequest { Provider provider = 2; } +message SearchAlbumsRequest { + string query = 1; + string artist = 2; + int32 limit = 3; + int32 offset = 4; + Provider provider = 5; +} + +message SearchAlbumsResponse { + repeated Album albums = 1; + int32 total = 2; +} + message SyncArtistRequest { oneof target { string name = 1;