feat: v0.0.5

This commit is contained in:
Gabe Farrell 2025-06-15 19:09:44 -04:00
parent 4c4ebc593d
commit 242a82ad8c
36 changed files with 694 additions and 174 deletions

View file

@ -42,8 +42,19 @@ type LbzTrackMeta struct {
ArtistName string `json:"artist_name"` // required
TrackName string `json:"track_name"` // required
ReleaseName string `json:"release_name,omitempty"`
MBIDMapping LbzMBIDMapping `json:"mbid_mapping"`
AdditionalInfo LbzAdditionalInfo `json:"additional_info,omitempty"`
}
type LbzArtist struct {
ArtistMBID string `json:"artist_mbid"`
ArtistName string `json:"artist_credit_name"`
}
type LbzMBIDMapping struct {
ReleaseMBID string `json:"release_mbid"`
RecordingMBID string `json:"recording_mbid"`
ArtistMBIDs []string `json:"artist_mbids"`
Artists []LbzArtist `json:"artists"`
}
type LbzAdditionalInfo struct {
MediaPlayer string `json:"media_player,omitempty"`
@ -128,17 +139,30 @@ func LbzSubmitListenHandler(store db.DB, mbzc mbz.MusicBrainzCaller) func(w http
if err != nil {
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
}
if len(artistMbzIDs) < 1 {
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Attempting to parse artist UUIDs from mbid_mapping")
utils.ParseUUIDSlice(payload.TrackMeta.MBIDMapping.ArtistMBIDs)
if err != nil {
l.Debug().Err(err).Msg("LbzSubmitListenHandler: Failed to parse one or more UUIDs")
}
}
rgMbzID, err := uuid.Parse(payload.TrackMeta.AdditionalInfo.ReleaseGroupMBID)
if err != nil {
rgMbzID = uuid.Nil
}
releaseMbzID, err := uuid.Parse(payload.TrackMeta.AdditionalInfo.ReleaseMBID)
if err != nil {
releaseMbzID = uuid.Nil
releaseMbzID, err = uuid.Parse(payload.TrackMeta.MBIDMapping.ReleaseMBID)
if err != nil {
releaseMbzID = uuid.Nil
}
}
recordingMbzID, err := uuid.Parse(payload.TrackMeta.AdditionalInfo.RecordingMBID)
if err != nil {
recordingMbzID = uuid.Nil
recordingMbzID, err = uuid.Parse(payload.TrackMeta.MBIDMapping.RecordingMBID)
if err != nil {
recordingMbzID = uuid.Nil
}
}
var client string
@ -160,20 +184,33 @@ func LbzSubmitListenHandler(store db.DB, mbzc mbz.MusicBrainzCaller) func(w http
listenedAt = time.Unix(payload.ListenedAt, 0)
}
var artistMbidMap []catalog.ArtistMbidMap
for _, a := range payload.TrackMeta.MBIDMapping.Artists {
if a.ArtistMBID == "" || a.ArtistName == "" {
continue
}
mbid, err := uuid.Parse(a.ArtistMBID)
if err != nil {
l.Err(err).Msgf("LbzSubmitListenHandler: Failed to parse UUID for artist '%s'", a.ArtistName)
}
artistMbidMap = append(artistMbidMap, catalog.ArtistMbidMap{Artist: a.ArtistName, Mbid: mbid})
}
opts := catalog.SubmitListenOpts{
MbzCaller: mbzc,
ArtistNames: payload.TrackMeta.AdditionalInfo.ArtistNames,
Artist: payload.TrackMeta.ArtistName,
ArtistMbzIDs: artistMbzIDs,
TrackTitle: payload.TrackMeta.TrackName,
RecordingMbzID: recordingMbzID,
ReleaseTitle: payload.TrackMeta.ReleaseName,
ReleaseMbzID: releaseMbzID,
ReleaseGroupMbzID: rgMbzID,
Duration: duration,
Time: listenedAt,
UserID: u.ID,
Client: client,
MbzCaller: mbzc,
ArtistNames: payload.TrackMeta.AdditionalInfo.ArtistNames,
Artist: payload.TrackMeta.ArtistName,
ArtistMbzIDs: artistMbzIDs,
TrackTitle: payload.TrackMeta.TrackName,
RecordingMbzID: recordingMbzID,
ReleaseTitle: payload.TrackMeta.ReleaseName,
ReleaseMbzID: releaseMbzID,
ReleaseGroupMbzID: rgMbzID,
ArtistMbidMappings: artistMbidMap,
Duration: duration,
Time: listenedAt,
UserID: u.ID,
Client: client,
}
if req.ListenType == ListenTypePlayingNow {

View file

@ -3,6 +3,7 @@ package handlers
import (
"net/http"
"strconv"
"strings"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
@ -67,9 +68,16 @@ func MergeReleaseGroupsHandler(store db.DB) http.HandlerFunc {
return
}
var replaceImage bool
replaceImgStr := r.URL.Query().Get("replace_image")
if strings.ToLower(replaceImgStr) == "true" {
l.Debug().Msg("MergeReleaseGroupsHandler: Merge will replace image")
replaceImage = true
}
l.Debug().Msgf("MergeReleaseGroupsHandler: Merging release groups from ID %d to ID %d", fromId, toId)
err = store.MergeAlbums(r.Context(), int32(fromId), int32(toId))
err = store.MergeAlbums(r.Context(), int32(fromId), int32(toId), replaceImage)
if err != nil {
l.Err(err).Msg("MergeReleaseGroupsHandler: Failed to merge release groups")
utils.WriteError(w, "Failed to merge release groups: "+err.Error(), http.StatusInternalServerError)
@ -103,9 +111,16 @@ func MergeArtistsHandler(store db.DB) http.HandlerFunc {
return
}
var replaceImage bool
replaceImgStr := r.URL.Query().Get("replace_image")
if strings.ToLower(replaceImgStr) == "true" {
l.Debug().Msg("MergeReleaseGroupsHandler: Merge will replace image")
replaceImage = true
}
l.Debug().Msgf("MergeArtistsHandler: Merging artists from ID %d to ID %d", fromId, toId)
err = store.MergeArtists(r.Context(), int32(fromId), int32(toId))
err = store.MergeArtists(r.Context(), int32(fromId), int32(toId), replaceImage)
if err != nil {
l.Err(err).Msg("MergeArtistsHandler: Failed to merge artists")
utils.WriteError(w, "Failed to merge artists: "+err.Error(), http.StatusInternalServerError)

View file

@ -119,6 +119,40 @@ func TestImportLastFM(t *testing.T) {
truncateTestData(t)
}
func TestImportLastFM_MbzDisabled(t *testing.T) {
src := path.Join("..", "test_assets", "recenttracks-shoko2-1749776100.json")
destDir := filepath.Join(cfg.ConfigDir(), "import")
dest := filepath.Join(destDir, "recenttracks-shoko2-1749776100.json")
// not going to make the dest dir because engine should make it already
input, err := os.ReadFile(src)
require.NoError(t, err)
require.NoError(t, os.WriteFile(dest, input, os.ModePerm))
engine.RunImporter(logger.Get(), store, &mbz.MbzErrorCaller{})
album, err := store.GetAlbum(context.Background(), db.GetAlbumOpts{MusicBrainzID: uuid.MustParse("e9e78802-0bf8-4ca3-9655-1d943d2d2fa0")})
require.NoError(t, err)
assert.Equal(t, "ZOO!!", album.Title)
artist, err := store.GetArtist(context.Background(), db.GetArtistOpts{MusicBrainzID: uuid.MustParse("4b00640f-3be6-43f8-9b34-ff81bd89320a")})
require.NoError(t, err)
assert.Equal(t, "OurR", artist.Name)
artist, err = store.GetArtist(context.Background(), db.GetArtistOpts{Name: "CHUU"})
require.NoError(t, err)
track, err := store.GetTrack(context.Background(), db.GetTrackOpts{Title: "because I'm stupid?", ArtistIDs: []int32{artist.ID}})
require.NoError(t, err)
t.Log(track)
listens, err := store.GetListensPaginated(context.Background(), db.GetItemsOpts{TrackID: int(track.ID), Period: db.PeriodAllTime})
require.NoError(t, err)
require.Len(t, listens.Items, 1)
assert.WithinDuration(t, time.Unix(1749776100, 0), listens.Items[0].Time, 1*time.Second)
truncateTestData(t)
}
func TestImportListenBrainz(t *testing.T) {
src := path.Join("..", "test_assets", "listenbrainz_shoko1_1749780844.zip")
@ -188,3 +222,41 @@ func TestImportListenBrainz(t *testing.T) {
truncateTestData(t)
}
func TestImportListenBrainz_MbzDisabled(t *testing.T) {
src := path.Join("..", "test_assets", "listenbrainz_shoko1_1749780844.zip")
destDir := filepath.Join(cfg.ConfigDir(), "import")
dest := filepath.Join(destDir, "listenbrainz_shoko1_1749780844.zip")
// not going to make the dest dir because engine should make it already
input, err := os.ReadFile(src)
require.NoError(t, err)
require.NoError(t, os.WriteFile(dest, input, os.ModePerm))
engine.RunImporter(logger.Get(), store, &mbz.MbzErrorCaller{})
album, err := store.GetAlbum(context.Background(), db.GetAlbumOpts{MusicBrainzID: uuid.MustParse("ce330d67-9c46-4a3b-9d62-08406370f234")})
require.NoError(t, err)
assert.Equal(t, "酸欠少女", album.Title)
artist, err := store.GetArtist(context.Background(), db.GetArtistOpts{MusicBrainzID: uuid.MustParse("4b00640f-3be6-43f8-9b34-ff81bd89320a")})
require.NoError(t, err)
assert.Equal(t, "OurR", artist.Name)
artist, err = store.GetArtist(context.Background(), db.GetArtistOpts{MusicBrainzID: uuid.MustParse("09887aa7-226e-4ecc-9a0c-02d2ae5777e1")})
require.NoError(t, err)
assert.Equal(t, "Carly Rae Jepsen", artist.Name)
artist, err = store.GetArtist(context.Background(), db.GetArtistOpts{MusicBrainzID: uuid.MustParse("78e46ae5-9bfd-433b-be3f-19e993d67ecc")})
require.NoError(t, err)
assert.Equal(t, "Rufus Wainwright", artist.Name)
track, err := store.GetTrack(context.Background(), db.GetTrackOpts{MusicBrainzID: uuid.MustParse("08e8f55b-f1a4-46b8-b2d1-fab4c592165c")})
require.NoError(t, err)
assert.Equal(t, "Desert", track.Title)
listens, err := store.GetListensPaginated(context.Background(), db.GetItemsOpts{TrackID: int(track.ID), Period: db.PeriodAllTime})
require.NoError(t, err)
assert.Len(t, listens.Items, 1)
assert.WithinDuration(t, time.Unix(1749780612, 0), listens.Items[0].Time, 1*time.Second)
truncateTestData(t)
}

View file

@ -87,16 +87,16 @@ func ValidateApiKey(store db.DB) func(next http.Handler) http.Handler {
}
authh := r.Header.Get("Authorization")
s := strings.Split(authh, "Token ")
if len(s) < 2 {
l.Debug().Msg("ValidateApiKey: Authorization header must be formatted 'Token {token}'")
var token string
if strings.HasPrefix(strings.ToLower(authh), "token ") {
token = strings.TrimSpace(authh[6:]) // strip "Token "
} else {
l.Error().Msg("ValidateApiKey: Authorization header must be formatted 'Token {token}'")
utils.WriteError(w, "unauthorized", http.StatusUnauthorized)
return
}
key := s[1]
u, err := store.GetUserByApiKey(ctx, key)
u, err := store.GetUserByApiKey(ctx, token)
if err != nil {
l.Err(err).Msg("Failed to get user from database using api key")
utils.WriteError(w, "internal server error", http.StatusInternalServerError)