feat: v0.0.8

This commit is contained in:
Gabe Farrell 2025-06-16 21:55:39 -04:00
parent 00e7782be2
commit 80b6f4deaa
66 changed files with 1559 additions and 916 deletions

View file

@ -14,6 +14,8 @@ type DB interface {
GetArtist(ctx context.Context, opts GetArtistOpts) (*models.Artist, error)
GetAlbum(ctx context.Context, opts GetAlbumOpts) (*models.Album, error)
GetTrack(ctx context.Context, opts GetTrackOpts) (*models.Track, error)
GetArtistsForAlbum(ctx context.Context, id int32) ([]*models.Artist, error)
GetArtistsForTrack(ctx context.Context, id int32) ([]*models.Artist, error)
GetTopTracksPaginated(ctx context.Context, opts GetItemsOpts) (*PaginatedResponse[*models.Track], error)
GetTopArtistsPaginated(ctx context.Context, opts GetItemsOpts) (*PaginatedResponse[*models.Artist], error)
GetTopAlbumsPaginated(ctx context.Context, opts GetItemsOpts) (*PaginatedResponse[*models.Album], error)
@ -48,6 +50,8 @@ type DB interface {
SetPrimaryArtistAlias(ctx context.Context, id int32, alias string) error
SetPrimaryAlbumAlias(ctx context.Context, id int32, alias string) error
SetPrimaryTrackAlias(ctx context.Context, id int32, alias string) error
SetPrimaryAlbumArtist(ctx context.Context, id int32, artistId int32, value bool) error
SetPrimaryTrackArtist(ctx context.Context, id int32, artistId int32, value bool) error
// Delete
DeleteArtist(ctx context.Context, id int32) error
DeleteAlbum(ctx context.Context, id int32) error

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -41,11 +42,11 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
Column1: opts.Titles,
})
} else {
return nil, errors.New("insufficient information to get album")
return nil, errors.New("GetAlbum: insufficient information to get album")
}
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAlbum: %w", err)
}
count, err := d.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
@ -54,7 +55,7 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
ReleaseID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAlbum: CountListensFromRelease: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
@ -62,7 +63,7 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
AlbumID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)
}
return &models.Album{
@ -87,17 +88,17 @@ func (d *Psql) SaveAlbum(ctx context.Context, opts db.SaveAlbumOpts) (*models.Al
insertImage = &opts.Image
}
if len(opts.ArtistIDs) < 1 {
return nil, errors.New("required parameter 'ArtistIDs' missing")
return nil, errors.New("SaveAlbum: required parameter 'ArtistIDs' missing")
}
for _, aid := range opts.ArtistIDs {
if aid == 0 {
return nil, errors.New("none of 'ArtistIDs' may be 0")
return nil, errors.New("SaveAlbum: none of 'ArtistIDs' may be 0")
}
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return nil, err
return nil, fmt.Errorf("SaveAlbum: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -109,7 +110,7 @@ func (d *Psql) SaveAlbum(ctx context.Context, opts db.SaveAlbumOpts) (*models.Al
ImageSource: pgtype.Text{String: opts.ImageSrc, Valid: opts.ImageSrc != ""},
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveAlbum: InsertRelease: %w", err)
}
for _, artistId := range opts.ArtistIDs {
l.Debug().Msgf("Associating release '%s' to artist with ID %d", opts.Title, artistId)
@ -118,7 +119,7 @@ func (d *Psql) SaveAlbum(ctx context.Context, opts db.SaveAlbumOpts) (*models.Al
ReleaseID: r.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveAlbum: AssociateArtistToRelease: %w", err)
}
}
l.Debug().Msgf("Saving canonical alias %s for release %d", opts.Title, r.ID)
@ -130,11 +131,12 @@ func (d *Psql) SaveAlbum(ctx context.Context, opts db.SaveAlbumOpts) (*models.Al
})
if err != nil {
l.Err(err).Msgf("Failed to save canonical alias for album %d", r.ID)
return nil, fmt.Errorf("SaveAlbum: InsertReleaseAlias: %w", err)
}
err = tx.Commit(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveAlbum: Commit: %w", err)
}
return &models.Album{
@ -151,7 +153,7 @@ func (d *Psql) AddArtistsToAlbum(ctx context.Context, opts db.AddArtistsToAlbumO
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("AddArtistsToAlbum: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -162,6 +164,7 @@ func (d *Psql) AddArtistsToAlbum(ctx context.Context, opts db.AddArtistsToAlbumO
})
if err != nil {
l.Error().Err(err).Msgf("Failed to associate release %d with artist %d", opts.AlbumID, id)
return fmt.Errorf("AddArtistsToAlbum: AssociateArtistToRelease: %w", err)
}
}
return tx.Commit(ctx)
@ -175,7 +178,7 @@ func (d *Psql) UpdateAlbum(ctx context.Context, opts db.UpdateAlbumOpts) error {
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("UpdateAlbum: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -186,7 +189,7 @@ func (d *Psql) UpdateAlbum(ctx context.Context, opts db.UpdateAlbumOpts) error {
MusicBrainzID: &opts.MusicBrainzID,
})
if err != nil {
return err
return fmt.Errorf("UpdateAlbum: UpdateReleaseMbzID: %w", err)
}
}
if opts.Image != uuid.Nil {
@ -197,7 +200,7 @@ func (d *Psql) UpdateAlbum(ctx context.Context, opts db.UpdateAlbumOpts) error {
ImageSource: pgtype.Text{String: opts.ImageSrc, Valid: opts.ImageSrc != ""},
})
if err != nil {
return err
return fmt.Errorf("UpdateAlbum: UpdateReleaseImage: %w", err)
}
}
if opts.VariousArtistsUpdate {
@ -207,7 +210,7 @@ func (d *Psql) UpdateAlbum(ctx context.Context, opts db.UpdateAlbumOpts) error {
VariousArtists: opts.VariousArtistsValue,
})
if err != nil {
return err
return fmt.Errorf("UpdateAlbum: UpdateReleaseVariousArtists: %w", err)
}
}
return tx.Commit(ctx)
@ -221,13 +224,13 @@ func (d *Psql) SaveAlbumAliases(ctx context.Context, id int32, aliases []string,
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SaveAlbumAliases: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
existing, err := qtx.GetAllReleaseAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SaveAlbumAliases: GetAllReleaseAliases: %w", err)
}
for _, v := range existing {
aliases = append(aliases, v.Alias)
@ -235,7 +238,7 @@ func (d *Psql) SaveAlbumAliases(ctx context.Context, id int32, aliases []string,
utils.Unique(&aliases)
for _, alias := range aliases {
if strings.TrimSpace(alias) == "" {
return errors.New("aliases cannot be blank")
return errors.New("SaveAlbumAliases: aliases cannot be blank")
}
err = qtx.InsertReleaseAlias(ctx, repository.InsertReleaseAliasParams{
Alias: strings.TrimSpace(alias),
@ -244,7 +247,7 @@ func (d *Psql) SaveAlbumAliases(ctx context.Context, id int32, aliases []string,
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SaveAlbumAliases: InsertReleaseAlias: %w", err)
}
}
return tx.Commit(ctx)
@ -263,7 +266,7 @@ func (d *Psql) DeleteAlbumAlias(ctx context.Context, id int32, alias string) err
func (d *Psql) GetAllAlbumAliases(ctx context.Context, id int32) ([]models.Alias, error) {
rows, err := d.q.GetAllReleaseAliases(ctx, id)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAllAlbumAliases: GetAllReleaseAliases: %w", err)
}
aliases := make([]models.Alias, len(rows))
for i, row := range rows {
@ -285,14 +288,14 @@ func (d *Psql) SetPrimaryAlbumAlias(ctx context.Context, id int32, alias string)
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SetPrimaryAlbumAlias: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
// get all aliases
aliases, err := qtx.GetAllReleaseAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SetPrimaryAlbumAlias: GetAllReleaseAliases: %w", err)
}
primary := ""
exists := false
@ -309,7 +312,7 @@ func (d *Psql) SetPrimaryAlbumAlias(ctx context.Context, id int32, alias string)
return nil
}
if !exists {
return errors.New("alias does not exist")
return errors.New("SetPrimaryAlbumAlias: alias does not exist")
}
err = qtx.SetReleaseAliasPrimaryStatus(ctx, repository.SetReleaseAliasPrimaryStatusParams{
ReleaseID: id,
@ -317,7 +320,7 @@ func (d *Psql) SetPrimaryAlbumAlias(ctx context.Context, id int32, alias string)
IsPrimary: true,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryAlbumAlias: SetReleaseAliasPrimaryStatus: %w", err)
}
err = qtx.SetReleaseAliasPrimaryStatus(ctx, repository.SetReleaseAliasPrimaryStatusParams{
ReleaseID: id,
@ -325,7 +328,61 @@ func (d *Psql) SetPrimaryAlbumAlias(ctx context.Context, id int32, alias string)
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryAlbumAlias: SetReleaseAliasPrimaryStatus: %w", err)
}
return tx.Commit(ctx)
}
func (d *Psql) SetPrimaryAlbumArtist(ctx context.Context, id int32, artistId int32, value bool) error {
l := logger.FromContext(ctx)
if id == 0 {
return errors.New("artist id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return fmt.Errorf("SetPrimaryAlbumArtist: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
// get all artists
artists, err := qtx.GetReleaseArtists(ctx, id)
if err != nil {
return fmt.Errorf("SetPrimaryAlbumArtist: GetReleaseArtists: %w", err)
}
var primary int32
for _, v := range artists {
// i dont get it??? is_primary is not a nullable column??? why use pgtype.Bool???
// why not just use boolean??? is sqlc stupid??? am i stupid???????
if v.IsPrimary.Valid && v.IsPrimary.Bool {
primary = v.ID
}
}
if value && primary == artistId {
// no-op
return nil
}
l.Debug().Msgf("Marking artist with id %d as 'primary = %v' on album with id %d", artistId, value, id)
err = qtx.UpdateReleasePrimaryArtist(ctx, repository.UpdateReleasePrimaryArtistParams{
ReleaseID: id,
ArtistID: artistId,
IsPrimary: value,
})
if err != nil {
return fmt.Errorf("SetPrimaryAlbumArtist: UpdateReleasePrimaryArtist: %w", err)
}
if value && primary != 0 {
// if we were marking a new one as primary and there was already one marked as primary,
// unmark that one as there can only be one
l.Debug().Msgf("Unmarking artist with id %d as primary on album with id %d", primary, id)
err = qtx.UpdateReleasePrimaryArtist(ctx, repository.UpdateReleasePrimaryArtistParams{
ReleaseID: id,
ArtistID: primary,
IsPrimary: false,
})
if err != nil {
return fmt.Errorf("SetPrimaryAlbumArtist: UpdateReleasePrimaryArtist: %w", err)
}
}
return tx.Commit(ctx)
}

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -23,7 +24,7 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
l.Debug().Msgf("Fetching artist from DB with id %d", opts.ID)
row, err := d.q.GetArtist(ctx, opts.ID)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: GetArtist by ID: %w", err)
}
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
ListenedAt: time.Unix(0, 0),
@ -31,14 +32,14 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
}
return &models.Artist{
ID: row.ID,
@ -53,7 +54,7 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
l.Debug().Msgf("Fetching artist from DB with MusicBrainz ID %s", opts.MusicBrainzID)
row, err := d.q.GetArtistByMbzID(ctx, &opts.MusicBrainzID)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: GetArtistByMbzID: %w", err)
}
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
ListenedAt: time.Unix(0, 0),
@ -61,14 +62,14 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
}
return &models.Artist{
ID: row.ID,
@ -83,7 +84,7 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
l.Debug().Msgf("Fetching artist from DB with name '%s'", opts.Name)
row, err := d.q.GetArtistByName(ctx, opts.Name)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: GetArtistByName: %w", err)
}
count, err := d.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
ListenedAt: time.Unix(0, 0),
@ -91,14 +92,14 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
}
return &models.Artist{
ID: row.ID,
@ -118,35 +119,36 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
func (d *Psql) SaveArtistAliases(ctx context.Context, id int32, aliases []string, source string) error {
l := logger.FromContext(ctx)
if id == 0 {
return errors.New("artist id not specified")
return errors.New("SaveArtistAliases: artist id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SaveArtistAliases: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
existing, err := qtx.GetAllArtistAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SaveArtistAliases: GetAllArtistAliases: %w", err)
}
for _, v := range existing {
aliases = append(aliases, v.Alias)
}
utils.Unique(&aliases)
for _, alias := range aliases {
if strings.TrimSpace(alias) == "" {
return errors.New("aliases cannot be blank")
alias = strings.TrimSpace(alias)
if alias == "" {
return errors.New("SaveArtistAliases: aliases cannot be blank")
}
err = qtx.InsertArtistAlias(ctx, repository.InsertArtistAliasParams{
Alias: strings.TrimSpace(alias),
Alias: alias,
ArtistID: id,
Source: source,
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SaveArtistAliases: InsertArtistAlias: %w", err)
}
}
return tx.Commit(ctx)
@ -170,13 +172,13 @@ func (d *Psql) SaveArtist(ctx context.Context, opts db.SaveArtistOpts) (*models.
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return nil, err
return nil, fmt.Errorf("SaveArtist: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
opts.Name = strings.TrimSpace(opts.Name)
if opts.Name == "" {
return nil, errors.New("name must not be blank")
return nil, errors.New("SaveArtist: name must not be blank")
}
l.Debug().Msgf("Inserting artist '%s' into DB", opts.Name)
a, err := qtx.InsertArtist(ctx, repository.InsertArtistParams{
@ -185,7 +187,7 @@ func (d *Psql) SaveArtist(ctx context.Context, opts db.SaveArtistOpts) (*models.
ImageSource: pgtype.Text{String: opts.ImageSrc, Valid: opts.ImageSrc != ""},
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveArtist: InsertArtist: %w", err)
}
l.Debug().Msgf("Inserting canonical alias '%s' into DB for artist with id %d", opts.Name, a.ID)
err = qtx.InsertArtistAlias(ctx, repository.InsertArtistAliasParams{
@ -195,13 +197,13 @@ func (d *Psql) SaveArtist(ctx context.Context, opts db.SaveArtistOpts) (*models.
IsPrimary: true,
})
if err != nil {
l.Error().Err(err).Msgf("Error inserting canonical alias for artist '%s'", opts.Name)
return nil, err
l.Err(err).Msgf("SaveArtist: error inserting canonical alias for artist '%s'", opts.Name)
return nil, fmt.Errorf("SaveArtist: InsertArtistAlias: %w", err)
}
err = tx.Commit(ctx)
if err != nil {
l.Err(err).Msg("Failed to commit insert artist transaction")
return nil, err
return nil, fmt.Errorf("SaveArtist: Commit: %w", err)
}
artist := &models.Artist{
ID: a.ID,
@ -214,7 +216,7 @@ func (d *Psql) SaveArtist(ctx context.Context, opts db.SaveArtistOpts) (*models.
l.Debug().Msgf("Inserting aliases '%v' into DB for artist '%s'", opts.Aliases, opts.Name)
err = d.SaveArtistAliases(ctx, a.ID, opts.Aliases, "MusicBrainz")
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveArtist: SaveArtistAliases: %w", err)
}
artist.Aliases = opts.Aliases
}
@ -224,12 +226,12 @@ func (d *Psql) SaveArtist(ctx context.Context, opts db.SaveArtistOpts) (*models.
func (d *Psql) UpdateArtist(ctx context.Context, opts db.UpdateArtistOpts) error {
l := logger.FromContext(ctx)
if opts.ID == 0 {
return errors.New("artist id not specified")
return errors.New("UpdateArtist: artist id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("UpdateArtist: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -240,7 +242,7 @@ func (d *Psql) UpdateArtist(ctx context.Context, opts db.UpdateArtistOpts) error
MusicBrainzID: &opts.MusicBrainzID,
})
if err != nil {
return err
return fmt.Errorf("UpdateArtist: UpdateArtistMbzID: %w", err)
}
}
if opts.Image != uuid.Nil {
@ -251,10 +253,15 @@ func (d *Psql) UpdateArtist(ctx context.Context, opts db.UpdateArtistOpts) error
ImageSource: pgtype.Text{String: opts.ImageSrc, Valid: opts.ImageSrc != ""},
})
if err != nil {
return err
return fmt.Errorf("UpdateArtist: UpdateArtistImage: %w", err)
}
}
return tx.Commit(ctx)
err = tx.Commit(ctx)
if err != nil {
l.Err(err).Msg("Failed to commit update artist transaction")
return fmt.Errorf("UpdateArtist: Commit: %w", err)
}
return nil
}
func (d *Psql) DeleteArtistAlias(ctx context.Context, id int32, alias string) error {
@ -263,10 +270,11 @@ func (d *Psql) DeleteArtistAlias(ctx context.Context, id int32, alias string) er
Alias: alias,
})
}
func (d *Psql) GetAllArtistAliases(ctx context.Context, id int32) ([]models.Alias, error) {
rows, err := d.q.GetAllArtistAliases(ctx, id)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAllArtistAliases: %w", err)
}
aliases := make([]models.Alias, len(rows))
for i, row := range rows {
@ -283,19 +291,18 @@ func (d *Psql) GetAllArtistAliases(ctx context.Context, id int32) ([]models.Alia
func (d *Psql) SetPrimaryArtistAlias(ctx context.Context, id int32, alias string) error {
l := logger.FromContext(ctx)
if id == 0 {
return errors.New("artist id not specified")
return errors.New("SetPrimaryArtistAlias: artist id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SetPrimaryArtistAlias: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
// get all aliases
aliases, err := qtx.GetAllArtistAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SetPrimaryArtistAlias: GetAllArtistAliases: %w", err)
}
primary := ""
exists := false
@ -308,11 +315,10 @@ func (d *Psql) SetPrimaryArtistAlias(ctx context.Context, id int32, alias string
}
}
if primary == alias {
// no-op rename
return nil
}
if !exists {
return errors.New("alias does not exist")
return errors.New("SetPrimaryArtistAlias: alias does not exist")
}
err = qtx.SetArtistAliasPrimaryStatus(ctx, repository.SetArtistAliasPrimaryStatusParams{
ArtistID: id,
@ -320,7 +326,7 @@ func (d *Psql) SetPrimaryArtistAlias(ctx context.Context, id int32, alias string
IsPrimary: true,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryArtistAlias: SetArtistAliasPrimaryStatus (primary): %w", err)
}
err = qtx.SetArtistAliasPrimaryStatus(ctx, repository.SetArtistAliasPrimaryStatusParams{
ArtistID: id,
@ -328,7 +334,57 @@ func (d *Psql) SetPrimaryArtistAlias(ctx context.Context, id int32, alias string
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryArtistAlias: SetArtistAliasPrimaryStatus (previous primary): %w", err)
}
return tx.Commit(ctx)
err = tx.Commit(ctx)
if err != nil {
l.Err(err).Msg("Failed to commit transaction")
return fmt.Errorf("SetPrimaryArtistAlias: Commit: %w", err)
}
return nil
}
func (d *Psql) GetArtistsForAlbum(ctx context.Context, id int32) ([]*models.Artist, error) {
l := logger.FromContext(ctx)
l.Debug().Msgf("Fetching artists for album ID %d", id)
rows, err := d.q.GetReleaseArtists(ctx, id)
if err != nil {
return nil, fmt.Errorf("GetArtistsForAlbum: %w", err)
}
artists := make([]*models.Artist, len(rows))
for i, row := range rows {
artists[i] = &models.Artist{
ID: row.ID,
Name: row.Name,
MbzID: row.MusicBrainzID,
Image: row.Image,
IsPrimary: row.IsPrimary.Valid && row.IsPrimary.Bool,
}
}
return artists, nil
}
func (d *Psql) GetArtistsForTrack(ctx context.Context, id int32) ([]*models.Artist, error) {
l := logger.FromContext(ctx)
l.Debug().Msgf("Fetching artists for track ID %d", id)
rows, err := d.q.GetTrackArtists(ctx, id)
if err != nil {
return nil, fmt.Errorf("GetArtistsForTrack: %w", err)
}
artists := make([]*models.Artist, len(rows))
for i, row := range rows {
artists[i] = &models.Artist{
ID: row.ID,
Name: row.Name,
MbzID: row.MusicBrainzID,
Image: row.Image,
IsPrimary: row.IsPrimary.Valid && row.IsPrimary.Bool,
}
}
return artists, nil
}

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
@ -17,10 +18,11 @@ func (p *Psql) CountListens(ctx context.Context, period db.Period) (int64, error
ListenedAt_2: t2,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountListens: %w", err)
}
return count, nil
}
func (p *Psql) CountTracks(ctx context.Context, period db.Period) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(period)
@ -29,10 +31,11 @@ func (p *Psql) CountTracks(ctx context.Context, period db.Period) (int64, error)
ListenedAt_2: t2,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountTracks: %w", err)
}
return count, nil
}
func (p *Psql) CountAlbums(ctx context.Context, period db.Period) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(period)
@ -41,10 +44,11 @@ func (p *Psql) CountAlbums(ctx context.Context, period db.Period) (int64, error)
ListenedAt_2: t2,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountAlbums: %w", err)
}
return count, nil
}
func (p *Psql) CountArtists(ctx context.Context, period db.Period) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(period)
@ -53,10 +57,11 @@ func (p *Psql) CountArtists(ctx context.Context, period db.Period) (int64, error
ListenedAt_2: t2,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountArtists: %w", err)
}
return count, nil
}
func (p *Psql) CountTimeListened(ctx context.Context, period db.Period) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(period)
@ -65,10 +70,11 @@ func (p *Psql) CountTimeListened(ctx context.Context, period db.Period) (int64,
ListenedAt_2: t2,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountTimeListened: %w", err)
}
return count, nil
}
func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListenedOpts) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(opts.Period)
@ -80,7 +86,7 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
ArtistID: opts.ArtistID,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountTimeListenedToItem (Artist): %w", err)
}
return count, nil
} else if opts.AlbumID > 0 {
@ -90,10 +96,9 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
ReleaseID: opts.AlbumID,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountTimeListenedToItem (Album): %w", err)
}
return count, nil
} else if opts.TrackID > 0 {
count, err := p.q.CountTimeListenedToTrack(ctx, repository.CountTimeListenedToTrackParams{
ListenedAt: t1,
@ -101,9 +106,9 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
ID: opts.TrackID,
})
if err != nil {
return 0, err
return 0, fmt.Errorf("CountTimeListenedToItem (Track): %w", err)
}
return count, nil
}
return 0, errors.New("an id must be provided")
return 0, errors.New("CountTimeListenedToItem: an id must be provided")
}

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/gabehf/koito/internal/logger"
"github.com/gabehf/koito/internal/models"
@ -15,15 +16,15 @@ import (
func (d *Psql) ImageHasAssociation(ctx context.Context, image uuid.UUID) (bool, error) {
_, err := d.q.GetReleaseByImageID(ctx, &image)
if err == nil {
return true, err
return true, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return false, err
return false, fmt.Errorf("ImageHasAssociation: GetReleaseByImageID: %w", err)
}
_, err = d.q.GetArtistByImage(ctx, &image)
if err == nil {
return true, err
return true, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return false, err
return false, fmt.Errorf("ImageHasAssociation: GetArtistByImage: %w", err)
}
return false, nil
}
@ -31,15 +32,15 @@ func (d *Psql) ImageHasAssociation(ctx context.Context, image uuid.UUID) (bool,
func (d *Psql) GetImageSource(ctx context.Context, image uuid.UUID) (string, error) {
r, err := d.q.GetReleaseByImageID(ctx, &image)
if err == nil {
return r.ImageSource.String, err
return r.ImageSource.String, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return "", err
return "", fmt.Errorf("GetImageSource: GetReleaseByImageID: %w", err)
}
rr, err := d.q.GetArtistByImage(ctx, &image)
if err == nil {
return rr.ImageSource.String, err
return rr.ImageSource.String, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return "", err
return "", fmt.Errorf("GetImageSource: GetArtistByImage: %w", err)
}
return "", nil
}
@ -51,14 +52,13 @@ func (d *Psql) AlbumsWithoutImages(ctx context.Context, from int32) ([]*models.A
ID: from,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("AlbumsWithoutImages: GetReleasesWithoutImages: %w", err)
}
albums := make([]*models.Album, len(rows))
for i, row := range rows {
artists := make([]models.SimpleArtist, 0)
err = json.Unmarshal(row.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for release group with id %d", row.ID)
var artists []models.SimpleArtist
if err := json.Unmarshal(row.Artists, &artists); err != nil {
l.Err(err).Msgf("AlbumsWithoutImages: error unmarshalling artists for release group with id %d", row.ID)
artists = nil
}
albums[i] = &models.Album{

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
@ -18,7 +19,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
offset := (opts.Page - 1) * opts.Limit
t1, t2, err := utils.DateRange(opts.Week, opts.Month, opts.Year)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: %w", err)
}
if opts.Month == 0 && opts.Year == 0 {
// use period, not date range
@ -41,7 +42,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ID: int32(opts.TrackID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: GetLastListensFromTrackPaginated: %w", err)
}
listens = make([]*models.Listen, len(rows))
for i, row := range rows {
@ -54,7 +55,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
}
err = json.Unmarshal(row.Artists, &t.Track.Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: Unmarshal: %w", err)
}
listens[i] = t
}
@ -64,7 +65,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
TrackID: int32(opts.TrackID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: CountListensFromTrack: %w", err)
}
} else if opts.AlbumID > 0 {
l.Debug().Msgf("Fetching %d listens with period %s on page %d from range %v to %v",
@ -77,7 +78,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ReleaseID: int32(opts.AlbumID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: GetLastListensFromReleasePaginated: %w", err)
}
listens = make([]*models.Listen, len(rows))
for i, row := range rows {
@ -90,7 +91,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
}
err = json.Unmarshal(row.Artists, &t.Track.Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: Unmarshal: %w", err)
}
listens[i] = t
}
@ -100,7 +101,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ReleaseID: int32(opts.AlbumID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: CountListensFromRelease: %w", err)
}
} else if opts.ArtistID > 0 {
l.Debug().Msgf("Fetching %d listens with period %s on page %d from range %v to %v",
@ -113,7 +114,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ArtistID: int32(opts.ArtistID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: GetLastListensFromArtistPaginated: %w", err)
}
listens = make([]*models.Listen, len(rows))
for i, row := range rows {
@ -126,7 +127,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
}
err = json.Unmarshal(row.Artists, &t.Track.Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: Unmarshal: %w", err)
}
listens[i] = t
}
@ -136,7 +137,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ArtistID: int32(opts.ArtistID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: CountListensFromArtist: %w", err)
}
} else {
l.Debug().Msgf("Fetching %d listens with period %s on page %d from range %v to %v",
@ -148,7 +149,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
Offset: int32(offset),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: GetLastListensPaginated: %w", err)
}
listens = make([]*models.Listen, len(rows))
for i, row := range rows {
@ -161,7 +162,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
}
err = json.Unmarshal(row.Artists, &t.Track.Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: Unmarshal: %w", err)
}
listens[i] = t
}
@ -170,7 +171,7 @@ func (d *Psql) GetListensPaginated(ctx context.Context, opts db.GetItemsOpts) (*
ListenedAt_2: t2,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListensPaginated: CountListens: %w", err)
}
l.Debug().Msgf("Database responded with %d tracks out of a total %d", len(rows), count)
}

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
@ -30,7 +31,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
ReleaseID: opts.AlbumID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListenActivity: ListenActivityForRelease: %w", err)
}
listenActivity = make([]db.ListenActivityItem, len(rows))
for i, row := range rows {
@ -51,7 +52,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
ArtistID: opts.ArtistID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListenActivity: ListenActivityForArtist: %w", err)
}
listenActivity = make([]db.ListenActivityItem, len(rows))
for i, row := range rows {
@ -72,7 +73,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
ID: opts.TrackID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListenActivity: ListenActivityForTrack: %w", err)
}
listenActivity = make([]db.ListenActivityItem, len(rows))
for i, row := range rows {
@ -92,7 +93,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
Column3: stepToInterval(opts.Step),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetListenActivity: ListenActivity: %w", err)
}
listenActivity = make([]db.ListenActivityItem, len(rows))
for i, row := range rows {

View file

@ -71,7 +71,7 @@ func (d *Psql) MergeAlbums(ctx context.Context, fromId, toId int32, replaceImage
fromArtists, err := qtx.GetReleaseArtists(ctx, fromId)
if err != nil {
return fmt.Errorf("MergeTracks: GetReleaseArtists: %w", err)
return fmt.Errorf("MergeAlbums: GetReleaseArtists: %w", err)
}
err = qtx.UpdateReleaseForAll(ctx, repository.UpdateReleaseForAllParams{

View file

@ -34,34 +34,34 @@ func New() (*Psql, error) {
config, err := pgxpool.ParseConfig(cfg.DatabaseUrl())
if err != nil {
return nil, fmt.Errorf("failed to parse pgx config: %w", err)
return nil, fmt.Errorf("psql.New: failed to parse pgx config: %w", err)
}
config.ConnConfig.ConnectTimeout = 15 * time.Second
pool, err := pgxpool.NewWithConfig(ctx, config)
if err != nil {
return nil, fmt.Errorf("failed to create pgx pool: %w", err)
return nil, fmt.Errorf("psql.New: failed to create pgx pool: %w", err)
}
if err := pool.Ping(ctx); err != nil {
pool.Close()
return nil, fmt.Errorf("database not reachable: %w", err)
return nil, fmt.Errorf("psql.New: database not reachable: %w", err)
}
sqlDB, err := sql.Open("pgx", cfg.DatabaseUrl())
if err != nil {
return nil, fmt.Errorf("failed to open db for migrations: %w", err)
return nil, fmt.Errorf("psql.New: failed to open db for migrations: %w", err)
}
_, filename, _, ok := runtime.Caller(0)
if !ok {
return nil, fmt.Errorf("unable to get caller info")
return nil, fmt.Errorf("psql.New: unable to get caller info")
}
migrationsPath := filepath.Join(filepath.Dir(filename), "..", "..", "..", "db", "migrations")
if err := goose.Up(sqlDB, migrationsPath); err != nil {
return nil, fmt.Errorf("goose failed: %w", err)
return nil, fmt.Errorf("psql.New: goose failed: %w", err)
}
_ = sqlDB.Close()

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"encoding/json"
"fmt"
"github.com/gabehf/koito/internal/models"
"github.com/gabehf/koito/internal/repository"
@ -19,7 +20,7 @@ func (d *Psql) SearchArtists(ctx context.Context, q string) ([]*models.Artist, e
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchArtist: SearchArtistsBySubstring: %w", err)
}
ret := make([]*models.Artist, len(rows))
for i, row := range rows {
@ -37,7 +38,7 @@ func (d *Psql) SearchArtists(ctx context.Context, q string) ([]*models.Artist, e
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchArtist: SearchArtists: %w", err)
}
ret := make([]*models.Artist, len(rows))
for i, row := range rows {
@ -59,7 +60,7 @@ func (d *Psql) SearchAlbums(ctx context.Context, q string) ([]*models.Album, err
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchAlbums: SearchReleasesBySubstring: %w", err)
}
ret := make([]*models.Album, len(rows))
for i, row := range rows {
@ -72,7 +73,7 @@ func (d *Psql) SearchAlbums(ctx context.Context, q string) ([]*models.Album, err
}
err = json.Unmarshal(row.Artists, &ret[i].Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchAlbums: Unmarshal: %w", err)
}
}
return ret, nil
@ -82,7 +83,7 @@ func (d *Psql) SearchAlbums(ctx context.Context, q string) ([]*models.Album, err
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchAlbums: SearchReleases: %w", err)
}
ret := make([]*models.Album, len(rows))
for i, row := range rows {
@ -95,7 +96,7 @@ func (d *Psql) SearchAlbums(ctx context.Context, q string) ([]*models.Album, err
}
err = json.Unmarshal(row.Artists, &ret[i].Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchAlbums: Unmarshal: %w", err)
}
}
return ret, nil
@ -109,7 +110,7 @@ func (d *Psql) SearchTracks(ctx context.Context, q string) ([]*models.Track, err
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchTracks: SearchTracksBySubstring: %w", err)
}
ret := make([]*models.Track, len(rows))
for i, row := range rows {
@ -121,7 +122,7 @@ func (d *Psql) SearchTracks(ctx context.Context, q string) ([]*models.Track, err
}
err = json.Unmarshal(row.Artists, &ret[i].Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchTracks: Unmarshal: %w", err)
}
}
return ret, nil
@ -131,7 +132,7 @@ func (d *Psql) SearchTracks(ctx context.Context, q string) ([]*models.Track, err
Limit: searchItemLimit,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchTracks: SearchTracks: %w", err)
}
ret := make([]*models.Track, len(rows))
for i, row := range rows {
@ -143,7 +144,7 @@ func (d *Psql) SearchTracks(ctx context.Context, q string) ([]*models.Track, err
}
err = json.Unmarshal(row.Artists, &ret[i].Artists)
if err != nil {
return nil, err
return nil, fmt.Errorf("SearchTracks: Unmarshal: %w", err)
}
}
return ret, nil

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"time"
"github.com/gabehf/koito/internal/models"
@ -19,7 +20,7 @@ func (d *Psql) SaveSession(ctx context.Context, userID int32, expiresAt time.Tim
Persistent: persistent,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveSession: InsertSession: %w", err)
}
return &models.Session{
ID: session.ID,
@ -47,7 +48,7 @@ func (d *Psql) GetUserBySession(ctx context.Context, sessionId uuid.UUID) (*mode
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
} else if err != nil {
return nil, err
return nil, fmt.Errorf("SaveSession: GetUserBySession: %w", err)
}
return &models.User{

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
@ -17,7 +18,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
offset := (opts.Page - 1) * opts.Limit
t1, t2, err := utils.DateRange(opts.Week, opts.Month, opts.Year)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopAlbumsPaginated: %w", err)
}
if opts.Month == 0 && opts.Year == 0 {
// use period, not date range
@ -43,7 +44,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
ListenedAt_2: t2,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopAlbumsPaginated: GetTopReleasesFromArtist: %w", err)
}
rgs = make([]*models.Album, len(rows))
l.Debug().Msgf("Database responded with %d items", len(rows))
@ -52,7 +53,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
err = json.Unmarshal(v.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for release group with id %d", v.ID)
artists = nil
return nil, fmt.Errorf("GetTopAlbumsPaginated: Unmarshal: %w", err)
}
rgs[i] = &models.Album{
ID: v.ID,
@ -66,7 +67,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
}
count, err = d.q.CountReleasesFromArtist(ctx, int32(opts.ArtistID))
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopAlbumsPaginated: CountReleasesFromArtist: %w", err)
}
} else {
l.Debug().Msgf("Fetching top %d albums with period %s on page %d from range %v to %v",
@ -78,7 +79,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
Offset: int32(offset),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopAlbumsPaginated: GetTopReleasesPaginated: %w", err)
}
rgs = make([]*models.Album, len(rows))
l.Debug().Msgf("Database responded with %d items", len(rows))
@ -87,7 +88,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
err = json.Unmarshal(row.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for release group with id %d", row.ID)
artists = nil
return nil, fmt.Errorf("GetTopAlbumsPaginated: Unmarshal: %w", err)
}
t := &models.Album{
Title: row.Title,
@ -105,7 +106,7 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
ListenedAt_2: t2,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopAlbumsPaginated: CountTopReleases: %w", err)
}
l.Debug().Msgf("Database responded with %d albums out of a total %d", len(rows), count)
}

View file

@ -2,6 +2,7 @@ package psql
import (
"context"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
@ -16,7 +17,7 @@ func (d *Psql) GetTopArtistsPaginated(ctx context.Context, opts db.GetItemsOpts)
offset := (opts.Page - 1) * opts.Limit
t1, t2, err := utils.DateRange(opts.Week, opts.Month, opts.Year)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopArtistsPaginated: %w", err)
}
if opts.Month == 0 && opts.Year == 0 {
// use period, not date range
@ -35,7 +36,7 @@ func (d *Psql) GetTopArtistsPaginated(ctx context.Context, opts db.GetItemsOpts)
Offset: int32(offset),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopArtistsPaginated: GetTopArtistsPaginated: %w", err)
}
rgs := make([]*models.Artist, len(rows))
for i, row := range rows {
@ -53,7 +54,7 @@ func (d *Psql) GetTopArtistsPaginated(ctx context.Context, opts db.GetItemsOpts)
ListenedAt_2: t2,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopArtistsPaginated: CountTopArtists: %w", err)
}
l.Debug().Msgf("Database responded with %d artists out of a total %d", len(rows), count)

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
@ -17,7 +18,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
offset := (opts.Page - 1) * opts.Limit
t1, t2, err := utils.DateRange(opts.Week, opts.Month, opts.Year)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: %w", err)
}
if opts.Month == 0 && opts.Year == 0 {
// use period, not date range
@ -40,7 +41,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
ReleaseID: int32(opts.AlbumID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: GetTopTracksInReleasePaginated: %w", err)
}
tracks = make([]*models.Track, len(rows))
for i, row := range rows {
@ -48,7 +49,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
err = json.Unmarshal(row.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for track with id %d", row.ID)
artists = nil
return nil, fmt.Errorf("GetTopTracksPaginated: Unmarshal: %w", err)
}
t := &models.Track{
Title: row.Title,
@ -80,7 +81,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
ArtistID: int32(opts.ArtistID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: GetTopTracksByArtistPaginated: %w", err)
}
tracks = make([]*models.Track, len(rows))
for i, row := range rows {
@ -88,7 +89,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
err = json.Unmarshal(row.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for track with id %d", row.ID)
artists = nil
return nil, fmt.Errorf("GetTopTracksPaginated: Unmarshal: %w", err)
}
t := &models.Track{
Title: row.Title,
@ -107,7 +108,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
ArtistID: int32(opts.ArtistID),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: CountTopTracksByArtist: %w", err)
}
} else {
l.Debug().Msgf("Fetching top %d tracks with period %s on page %d from range %v to %v",
@ -119,7 +120,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
Offset: int32(offset),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: GetTopTracksPaginated: %w", err)
}
tracks = make([]*models.Track, len(rows))
for i, row := range rows {
@ -127,7 +128,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
err = json.Unmarshal(row.Artists, &artists)
if err != nil {
l.Err(err).Msgf("Error unmarshalling artists for track with id %d", row.ID)
artists = nil
return nil, fmt.Errorf("GetTopTracksPaginated: Unmarshal: %w", err)
}
t := &models.Track{
Title: row.Title,
@ -145,7 +146,7 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
ListenedAt_2: t2,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTopTracksPaginated: CountTopTracks: %w", err)
}
l.Debug().Msgf("Database responded with %d tracks out of a total %d", len(rows), count)
}

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -23,7 +24,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
l.Debug().Msgf("Fetching track from DB with id %d", opts.ID)
t, err := d.q.GetTrack(ctx, opts.ID)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTrack: GetTrack By ID: %w", err)
}
track = models.Track{
ID: t.ID,
@ -37,7 +38,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
l.Debug().Msgf("Fetching track from DB with MusicBrainz ID %s", opts.MusicBrainzID)
t, err := d.q.GetTrackByMbzID(ctx, &opts.MusicBrainzID)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTrack: GetTrackByMbzID: %w", err)
}
track = models.Track{
ID: t.ID,
@ -53,7 +54,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
Column2: opts.ArtistIDs,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTrack: GetTrackByTitleAndArtists: %w", err)
}
track = models.Track{
ID: t.ID,
@ -63,7 +64,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
Duration: t.Duration,
}
} else {
return nil, errors.New("insufficient information to get track")
return nil, errors.New("GetTrack: insufficient information to get track")
}
count, err := d.q.CountListensFromTrack(ctx, repository.CountListensFromTrackParams{
@ -72,7 +73,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
TrackID: track.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTrack: CountListensFromTrack: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
@ -80,7 +81,7 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
TrackID: track.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("GetTrack: CountTimeListenedToItem: %w", err)
}
track.ListenCount = count
@ -97,20 +98,20 @@ func (d *Psql) SaveTrack(ctx context.Context, opts db.SaveTrackOpts) (*models.Tr
insertMbzID = &opts.RecordingMbzID
}
if len(opts.ArtistIDs) < 1 {
return nil, errors.New("required parameter 'ArtistIDs' missing")
return nil, errors.New("SaveTrack: required parameter 'ArtistIDs' missing")
}
for _, aid := range opts.ArtistIDs {
if aid == 0 {
return nil, errors.New("none of 'ArtistIDs' may be 0")
return nil, errors.New("SaveTrack: none of 'ArtistIDs' may be 0")
}
}
if opts.AlbumID == 0 {
return nil, errors.New("required parameter 'AlbumID' missing")
return nil, errors.New("SaveTrack: required parameter 'AlbumID' missing")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return nil, err
return nil, fmt.Errorf("SaveTrack: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -120,7 +121,7 @@ func (d *Psql) SaveTrack(ctx context.Context, opts db.SaveTrackOpts) (*models.Tr
ReleaseID: opts.AlbumID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveTrack: InsertTrack: %w", err)
}
// insert associated artists
for _, aid := range opts.ArtistIDs {
@ -129,7 +130,7 @@ func (d *Psql) SaveTrack(ctx context.Context, opts db.SaveTrackOpts) (*models.Tr
TrackID: trackRow.ID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveTrack: AssociateArtistToTrack: %w", err)
}
}
// insert primary alias
@ -140,11 +141,11 @@ func (d *Psql) SaveTrack(ctx context.Context, opts db.SaveTrackOpts) (*models.Tr
IsPrimary: true,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveTrack: InsertTrackAlias: %w", err)
}
err = tx.Commit(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveTrack: Commit: %w", err)
}
return &models.Track{
ID: trackRow.ID,
@ -156,12 +157,12 @@ func (d *Psql) SaveTrack(ctx context.Context, opts db.SaveTrackOpts) (*models.Tr
func (d *Psql) UpdateTrack(ctx context.Context, opts db.UpdateTrackOpts) error {
l := logger.FromContext(ctx)
if opts.ID == 0 {
return errors.New("track id not specified")
return errors.New("UpdateTrack: track id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("UpdateTrack: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -172,7 +173,7 @@ func (d *Psql) UpdateTrack(ctx context.Context, opts db.UpdateTrackOpts) error {
MusicBrainzID: &opts.MusicBrainzID,
})
if err != nil {
return err
return fmt.Errorf("UpdateTrack: UpdateTrackMbzID: %w", err)
}
}
if opts.Duration != 0 {
@ -182,7 +183,7 @@ func (d *Psql) UpdateTrack(ctx context.Context, opts db.UpdateTrackOpts) error {
Duration: opts.Duration,
})
if err != nil {
return err
return fmt.Errorf("UpdateTrack: UpdateTrackDuration: %w", err)
}
}
return tx.Commit(ctx)
@ -191,18 +192,18 @@ func (d *Psql) UpdateTrack(ctx context.Context, opts db.UpdateTrackOpts) error {
func (d *Psql) SaveTrackAliases(ctx context.Context, id int32, aliases []string, source string) error {
l := logger.FromContext(ctx)
if id == 0 {
return errors.New("track id not specified")
return errors.New("SaveTrackAliases: track id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SaveTrackAliases: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
existing, err := qtx.GetAllTrackAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SaveTrackAliases: GetAllTrackAliases: %w", err)
}
for _, v := range existing {
aliases = append(aliases, v.Alias)
@ -219,7 +220,7 @@ func (d *Psql) SaveTrackAliases(ctx context.Context, id int32, aliases []string,
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SaveTrackAliases: InsertTrackAlias: %w", err)
}
}
return tx.Commit(ctx)
@ -239,7 +240,7 @@ func (d *Psql) DeleteTrackAlias(ctx context.Context, id int32, alias string) err
func (d *Psql) GetAllTrackAliases(ctx context.Context, id int32) ([]models.Alias, error) {
rows, err := d.q.GetAllTrackAliases(ctx, id)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetAllTrackAliases: GetAllTrackAliases: %w", err)
}
aliases := make([]models.Alias, len(rows))
for i, row := range rows {
@ -261,14 +262,14 @@ func (d *Psql) SetPrimaryTrackAlias(ctx context.Context, id int32, alias string)
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("SetPrimaryTrackAlias: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
// get all aliases
aliases, err := qtx.GetAllTrackAliases(ctx, id)
if err != nil {
return err
return fmt.Errorf("SetPrimaryTrackAlias: GetAllTrackAliases: %w", err)
}
primary := ""
exists := false
@ -293,7 +294,7 @@ func (d *Psql) SetPrimaryTrackAlias(ctx context.Context, id int32, alias string)
IsPrimary: true,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryTrackAlias: SetTrackAliasPrimaryStatus: %w", err)
}
err = qtx.SetTrackAliasPrimaryStatus(ctx, repository.SetTrackAliasPrimaryStatusParams{
TrackID: id,
@ -301,7 +302,61 @@ func (d *Psql) SetPrimaryTrackAlias(ctx context.Context, id int32, alias string)
IsPrimary: false,
})
if err != nil {
return err
return fmt.Errorf("SetPrimaryTrackAlias: SetTrackAliasPrimaryStatus: %w", err)
}
return tx.Commit(ctx)
}
func (d *Psql) SetPrimaryTrackArtist(ctx context.Context, id int32, artistId int32, value bool) error {
l := logger.FromContext(ctx)
if id == 0 {
return errors.New("artist id not specified")
}
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return fmt.Errorf("SetPrimaryTrackArtist: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
// get all artists
artists, err := qtx.GetTrackArtists(ctx, id)
if err != nil {
return fmt.Errorf("SetPrimaryTrackArtist: GetTrackArtists: %w", err)
}
var primary int32
for _, v := range artists {
// i dont get it??? is_primary is not a nullable column??? why use pgtype.Bool???
// why not just use boolean??? is sqlc stupid??? am i stupid???????
if v.IsPrimary.Valid && v.IsPrimary.Bool {
primary = v.ID
}
}
if value && primary == artistId {
// no-op
return nil
}
l.Debug().Msgf("Marking artist with id %d as 'primary = %v' on track with id %d", artistId, value, id)
err = qtx.UpdateTrackPrimaryArtist(ctx, repository.UpdateTrackPrimaryArtistParams{
TrackID: id,
ArtistID: artistId,
IsPrimary: value,
})
if err != nil {
return fmt.Errorf("SetPrimaryTrackArtist: UpdateTrackPrimaryArtist: %w", err)
}
if value && primary != 0 {
l.Debug().Msgf("Unmarking artist with id %d as primary on track with id %d", primary, id)
// if we were marking a new one as primary and there was already one marked as primary,
// unmark that one as there can only be one
err = qtx.UpdateTrackPrimaryArtist(ctx, repository.UpdateTrackPrimaryArtistParams{
TrackID: id,
ArtistID: primary,
IsPrimary: false,
})
if err != nil {
return fmt.Errorf("SetPrimaryTrackArtist: UpdateTrackPrimaryArtist: %w", err)
}
}
return tx.Commit(ctx)
}

View file

@ -3,6 +3,7 @@ package psql
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"unicode/utf8"
@ -21,7 +22,7 @@ func (d *Psql) GetUserByUsername(ctx context.Context, username string) (*models.
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
} else if err != nil {
return nil, err
return nil, fmt.Errorf("GetUserByUsername: %w", err)
}
return &models.User{
ID: row.ID,
@ -37,7 +38,7 @@ func (d *Psql) GetUserByApiKey(ctx context.Context, key string) (*models.User, e
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
} else if err != nil {
return nil, err
return nil, fmt.Errorf("GetUserByApiKey: %w", err)
}
return &models.User{
ID: row.ID,
@ -52,12 +53,12 @@ func (d *Psql) SaveUser(ctx context.Context, opts db.SaveUserOpts) (*models.User
err := ValidateUsername(opts.Username)
if err != nil {
l.Debug().AnErr("validator_notice", err).Msgf("Username failed validation: %s", opts.Username)
return nil, err
return nil, fmt.Errorf("SaveUser: ValidateUsername: %w", err)
}
pw, err := ValidateAndNormalizePassword(opts.Password)
if err != nil {
l.Debug().AnErr("validator_notice", err).Msgf("Password failed validation")
return nil, err
return nil, fmt.Errorf("SaveUser: ValidateAndNormalizePassword: %w", err)
}
if opts.Role == "" {
opts.Role = models.UserRoleUser
@ -65,7 +66,7 @@ func (d *Psql) SaveUser(ctx context.Context, opts db.SaveUserOpts) (*models.User
hashPw, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
if err != nil {
l.Err(err).Msg("Failed to generate hashed password")
return nil, err
return nil, fmt.Errorf("SaveUser: bcrypt.GenerateFromPassword: %w", err)
}
u, err := d.q.InsertUser(ctx, repository.InsertUserParams{
Username: strings.ToLower(opts.Username),
@ -73,7 +74,7 @@ func (d *Psql) SaveUser(ctx context.Context, opts db.SaveUserOpts) (*models.User
Role: repository.Role(opts.Role),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveUser: InsertUser: %w", err)
}
return &models.User{
ID: u.ID,
@ -88,7 +89,7 @@ func (d *Psql) SaveApiKey(ctx context.Context, opts db.SaveApiKeyOpts) (*models.
UserID: opts.UserID,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("SaveApiKey: InsertApiKey: %w", err)
}
return &models.ApiKey{
ID: row.ID,
@ -107,7 +108,7 @@ func (d *Psql) UpdateUser(ctx context.Context, opts db.UpdateUserOpts) error {
tx, err := d.conn.BeginTx(ctx, pgx.TxOptions{})
if err != nil {
l.Err(err).Msg("Failed to begin transaction")
return err
return fmt.Errorf("UpdateUser: BeginTx: %w", err)
}
defer tx.Rollback(ctx)
qtx := d.q.WithTx(tx)
@ -115,33 +116,33 @@ func (d *Psql) UpdateUser(ctx context.Context, opts db.UpdateUserOpts) error {
err := ValidateUsername(opts.Username)
if err != nil {
l.Debug().AnErr("validator_notice", err).Msgf("Username failed validation: %s", opts.Username)
return err
return fmt.Errorf("UpdateUser: ValidateUsername: %w", err)
}
err = qtx.UpdateUserUsername(ctx, repository.UpdateUserUsernameParams{
ID: opts.ID,
Username: opts.Username,
})
if err != nil {
return err
return fmt.Errorf("UpdateUser: UpdateUserUsername: %w", err)
}
}
if opts.Password != "" {
pw, err := ValidateAndNormalizePassword(opts.Password)
if err != nil {
l.Debug().AnErr("validator_notice", err).Msgf("Password failed validation")
return err
return fmt.Errorf("UpdateUser: ValidateAndNormalizePassword: %w", err)
}
hashPw, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
if err != nil {
l.Err(err).Msg("Failed to generate hashed password")
return err
return fmt.Errorf("UpdateUser: bcrypt.GenerateFromPassword: %w", err)
}
err = qtx.UpdateUserPassword(ctx, repository.UpdateUserPasswordParams{
ID: opts.ID,
Password: hashPw,
})
if err != nil {
return err
return fmt.Errorf("UpdateUser: UpdateUserPassword: %w", err)
}
}
return tx.Commit(ctx)
@ -150,7 +151,7 @@ func (d *Psql) UpdateUser(ctx context.Context, opts db.UpdateUserOpts) error {
func (d *Psql) GetApiKeysByUserID(ctx context.Context, id int32) ([]models.ApiKey, error) {
rows, err := d.q.GetAllApiKeysByUserID(ctx, id)
if err != nil {
return nil, err
return nil, fmt.Errorf("GetApiKeysByUserID: %w", err)
}
keys := make([]models.ApiKey, len(rows))
for i, row := range rows {