feat: add refresh and delete artist endpoints (sections 1.2, 1.3)

- Add POST /api/artists/{id}/refresh to re-fetch metadata from gRPC service
- Add DELETE /api/artists/{id} with cascade delete via PostgreSQL
- Add e2e tests for both flows covering happy path, not found, idempotency
- Extend testutil with GetArtistUpdatedAt, CountAlbumsByArtist, DELETE helper
This commit is contained in:
Alexander
2026-04-29 13:08:53 +02:00
parent 25deaf4621
commit b08a0b1646
7 changed files with 717 additions and 0 deletions
+90
View File
@@ -289,6 +289,96 @@ func parseUUID(s string) ([16]byte, error) {
return id, nil
}
type RefreshResult struct {
ArtistID string `json:"artist_id"`
ArtistName string `json:"artist_name"`
AlbumsUpdated int `json:"albums_updated"`
AlbumsAdded int `json:"albums_added"`
}
func RefreshArtist(
ctx context.Context,
foreignArtistID string,
metadataClient *metadata.Client,
db *database.DB,
) (*RefreshResult, error) {
if db == nil {
return nil, &NotFoundError{Message: "database not available"}
}
existingArtist, err := db.GetArtistByForeignID(ctx, foreignArtistID)
if err != nil {
return nil, &NotFoundError{Message: "artist not found: " + foreignArtistID}
}
artist, err := metadataClient.GetArtist(ctx, foreignArtistID)
if err != nil {
return nil, err
}
dbArtist := &database.Artist{
ID: artist.Id,
Name: artist.Name,
SortName: artist.SortName,
ArtistType: artist.ArtistType,
Description: artist.Description,
}
for _, g := range artist.Genres {
dbArtist.Genres = append(dbArtist.Genres, database.Genre{ID: g.Id, Name: g.Name})
}
for _, e := range artist.ExternalIds {
dbArtist.ExternalIDs = append(dbArtist.ExternalIDs, database.ExternalID{
Source: e.Source,
SourceID: e.SourceId,
URL: e.Url,
})
}
artistMetadataID, err := db.UpsertArtistMetadata(ctx, dbArtist)
if err != nil {
return nil, err
}
existingAlbumCount, _ := db.CountAlbumsByArtist(ctx, existingArtist.ID)
albumsResponse, err := metadataClient.GetArtistAlbums(ctx, foreignArtistID, 500, 0)
if err != nil {
return nil, err
}
var albumsUpdated int
for _, album := range albumsResponse.Albums {
dbAlbum := &database.Album{
ID: album.Id,
Title: album.Title,
AlbumType: album.AlbumType,
ReleaseDate: album.ReleaseDate,
}
for _, g := range album.Genres {
dbAlbum.Genres = append(dbAlbum.Genres, database.Genre{ID: g.Id, Name: g.Name})
}
if _, err := db.UpsertAlbum(ctx, dbAlbum, artistMetadataID); err != nil {
log.Warn().Err(err).Str("album", album.Title).Msg("failed to upsert album during refresh")
} else {
albumsUpdated++
}
}
newAlbumCount, _ := db.CountAlbumsByArtist(ctx, artistMetadataID)
albumsAdded := int(newAlbumCount - existingAlbumCount)
if albumsAdded < 0 {
albumsAdded = 0
}
return &RefreshResult{
ArtistID: foreignArtistID,
ArtistName: artist.Name,
AlbumsUpdated: albumsUpdated,
AlbumsAdded: albumsAdded,
}, nil
}
type NotFoundError struct {
Message string
}