diff --git a/db/migration/20240426202913_add_id_to_scrobble_buffer.go b/db/migration/20240426202913_add_id_to_scrobble_buffer.go new file mode 100644 index 00000000..e3df6359 --- /dev/null +++ b/db/migration/20240426202913_add_id_to_scrobble_buffer.go @@ -0,0 +1,30 @@ +package migrations + +import ( + "context" + "database/sql" + + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(upAddIdToScrobbleBuffer, downAddIdToScrobbleBuffer) +} + +func upAddIdToScrobbleBuffer(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, ` +delete from scrobble_buffer where user_id <> ''; +alter table scrobble_buffer add id varchar not null default ''; +create unique index scrobble_buffer_id_ix + on scrobble_buffer (id); +`) + return err +} + +func downAddIdToScrobbleBuffer(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, ` +drop index scrobble_buffer_id_ix; +alter table scrobble_buffer drop id; +`) + return err +} diff --git a/model/scrobble_buffer.go b/model/scrobble_buffer.go index 4714a2fe..c75a8285 100644 --- a/model/scrobble_buffer.go +++ b/model/scrobble_buffer.go @@ -3,11 +3,13 @@ package model import "time" type ScrobbleEntry struct { - MediaFile + ID string Service string - UserID string `structs:"user_id"` + UserID string PlayTime time.Time EnqueueTime time.Time + MediaFileID string + MediaFile } type ScrobbleEntries []ScrobbleEntry diff --git a/persistence/scrobble_buffer_repository.go b/persistence/scrobble_buffer_repository.go index 9407e68c..b68a7159 100644 --- a/persistence/scrobble_buffer_repository.go +++ b/persistence/scrobble_buffer_repository.go @@ -6,6 +6,7 @@ import ( "time" . "github.com/Masterminds/squirrel" + "github.com/google/uuid" "github.com/navidrome/navidrome/model" "github.com/pocketbase/dbx" ) @@ -37,6 +38,7 @@ func (r *scrobbleBufferRepository) UserIDs(service string) ([]string, error) { func (r *scrobbleBufferRepository) Enqueue(service, userId, mediaFileId string, playTime time.Time) error { ins := Insert(r.tableName).SetMap(map[string]interface{}{ + "id": uuid.NewString(), "user_id": userId, "service": service, "media_file_id": mediaFileId, @@ -48,7 +50,8 @@ func (r *scrobbleBufferRepository) Enqueue(service, userId, mediaFileId string, } func (r *scrobbleBufferRepository) Next(service string, userId string) (*model.ScrobbleEntry, error) { - sql := Select().Columns("s.*, m.*"). + // Put `s.*` last or else m.id overrides s.id + sql := Select().Columns("m.*, s.*"). From(r.tableName+" s"). LeftJoin("media_file m on m.id = s.media_file_id"). Where(And{ @@ -57,24 +60,20 @@ func (r *scrobbleBufferRepository) Next(service string, userId string) (*model.S }). OrderBy("play_time", "s.rowid").Limit(1) - res := model.ScrobbleEntries{} - // TODO Rewrite queryOne to use QueryRows, to workaround the recursive embedded structs issue - err := r.queryAll(sql, &res) - if errors.Is(err, model.ErrNotFound) || len(res) == 0 { + res := &model.ScrobbleEntry{} + err := r.queryOne(sql, res) + if errors.Is(err, model.ErrNotFound) { return nil, nil } if err != nil { return nil, err } - return &res[0], nil + res.MediaFile.ID = res.MediaFileID + return res, nil } func (r *scrobbleBufferRepository) Dequeue(entry *model.ScrobbleEntry) error { - return r.delete(And{ - Eq{"service": entry.Service}, - Eq{"media_file_id": entry.MediaFile.ID}, - Eq{"play_time": entry.PlayTime}, - }) + return r.delete(Eq{"id": entry.ID}) } func (r *scrobbleBufferRepository) Length() (int64, error) { diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index 289e15f6..43bb35c5 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -157,8 +157,6 @@ func (r sqlRepository) toSQL(sq Sqlizer) (string, dbx.Params, error) { return query, params, nil } -// Note: Due to a bug in the QueryRow method, this function does not map any embedded structs (ex: annotations) -// In this case, use the queryAll method and get the first item of the returned list func (r sqlRepository) queryOne(sq Sqlizer, response interface{}) error { query, args, err := r.toSQL(sq) if err != nil {