wip: endpoint working

This commit is contained in:
Gabe Farrell 2025-12-30 03:33:02 -05:00
parent 3b585f748a
commit 6b73f83484
19 changed files with 510 additions and 243 deletions

View file

@ -11,6 +11,7 @@ import (
type DB interface {
// Get
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)
@ -28,7 +29,9 @@ type DB interface {
GetUserBySession(ctx context.Context, sessionId uuid.UUID) (*models.User, error)
GetUserByUsername(ctx context.Context, username string) (*models.User, error)
GetUserByApiKey(ctx context.Context, key string) (*models.User, error)
// Save
SaveArtist(ctx context.Context, opts SaveArtistOpts) (*models.Artist, error)
SaveArtistAliases(ctx context.Context, id int32, aliases []string, source string) error
SaveAlbum(ctx context.Context, opts SaveAlbumOpts) (*models.Album, error)
@ -39,7 +42,9 @@ type DB interface {
SaveUser(ctx context.Context, opts SaveUserOpts) (*models.User, error)
SaveApiKey(ctx context.Context, opts SaveApiKeyOpts) (*models.ApiKey, error)
SaveSession(ctx context.Context, userId int32, expiresAt time.Time, persistent bool) (*models.Session, error)
// Update
UpdateArtist(ctx context.Context, opts UpdateArtistOpts) error
UpdateTrack(ctx context.Context, opts UpdateTrackOpts) error
UpdateAlbum(ctx context.Context, opts UpdateAlbumOpts) error
@ -52,7 +57,9 @@ type DB interface {
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
DeleteTrack(ctx context.Context, id int32) error
@ -62,26 +69,36 @@ type DB interface {
DeleteTrackAlias(ctx context.Context, id int32, alias string) error
DeleteSession(ctx context.Context, sessionId uuid.UUID) error
DeleteApiKey(ctx context.Context, id int32) error
// Count
CountListens(ctx context.Context, timeframe Timeframe) (int64, error)
CountListensToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
CountTracks(ctx context.Context, timeframe Timeframe) (int64, error)
CountAlbums(ctx context.Context, timeframe Timeframe) (int64, error)
CountArtists(ctx context.Context, timeframe Timeframe) (int64, error)
CountNewTracks(ctx context.Context, timeframe Timeframe) (int64, error)
CountNewAlbums(ctx context.Context, timeframe Timeframe) (int64, error)
CountNewArtists(ctx context.Context, timeframe Timeframe) (int64, error)
// in seconds
CountTimeListened(ctx context.Context, timeframe Timeframe) (int64, error)
// in seconds
CountTimeListenedToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
CountUsers(ctx context.Context) (int64, error)
// Search
SearchArtists(ctx context.Context, q string) ([]*models.Artist, error)
SearchAlbums(ctx context.Context, q string) ([]*models.Album, error)
SearchTracks(ctx context.Context, q string) ([]*models.Track, error)
// Merge
MergeTracks(ctx context.Context, fromId, toId int32) error
MergeAlbums(ctx context.Context, fromId, toId int32, replaceImage bool) error
MergeArtists(ctx context.Context, fromId, toId int32, replaceImage bool) error
// Etc
ImageHasAssociation(ctx context.Context, image uuid.UUID) (bool, error)
GetImageSource(ctx context.Context, image uuid.UUID) (string, error)
AlbumsWithoutImages(ctx context.Context, from int32) ([]*models.Album, error)

View file

@ -122,8 +122,8 @@ type GetItemsOpts struct {
Week int // 1-52
Month int // 1-12
Year int
From int // unix timestamp
To int // unix timestamp
From int64 // unix timestamp
To int64 // unix timestamp
// Used only for getting top tracks
ArtistID int
@ -144,10 +144,10 @@ type ListenActivityOpts struct {
}
type TimeListenedOpts struct {
Period Period
AlbumID int32
ArtistID int32
TrackID int32
Timeframe Timeframe
AlbumID int32
ArtistID int32
TrackID int32
}
type GetExportPageOpts struct {

View file

@ -12,6 +12,17 @@ type Timeframe struct {
T2u int64
}
func TimeframeToTimeRange(timeframe Timeframe) (t1, t2 time.Time) {
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
return
}
type Period string
const (

View file

@ -91,8 +91,8 @@ func (d *Psql) GetAlbum(ctx context.Context, opts db.GetAlbumOpts) (*models.Albu
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
AlbumID: ret.ID,
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
AlbumID: ret.ID,
})
if err != nil {
return nil, fmt.Errorf("GetAlbum: CountTimeListenedToItem: %w", err)

View file

@ -35,8 +35,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
ArtistID: row.ID,
})
if err != nil {
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
@ -70,8 +70,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
ArtistID: row.ID,
})
if err != nil {
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)
@ -105,8 +105,8 @@ func (d *Psql) GetArtist(ctx context.Context, opts db.GetArtistOpts) (*models.Ar
return nil, fmt.Errorf("GetArtist: CountListensFromArtist: %w", err)
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
ArtistID: row.ID,
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
ArtistID: row.ID,
})
if err != nil {
return nil, fmt.Errorf("GetArtist: CountTimeListenedToItem: %w", err)

View file

@ -4,21 +4,13 @@ import (
"context"
"errors"
"fmt"
"time"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/repository"
)
func (p *Psql) CountListens(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountListens(ctx, repository.CountListensParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -30,14 +22,7 @@ func (p *Psql) CountListens(ctx context.Context, timeframe db.Timeframe) (int64,
}
func (p *Psql) CountTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountTopTracks(ctx, repository.CountTopTracksParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -49,14 +34,7 @@ func (p *Psql) CountTracks(ctx context.Context, timeframe db.Timeframe) (int64,
}
func (p *Psql) CountAlbums(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountTopReleases(ctx, repository.CountTopReleasesParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -68,14 +46,7 @@ func (p *Psql) CountAlbums(ctx context.Context, timeframe db.Timeframe) (int64,
}
func (p *Psql) CountArtists(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountTopArtists(ctx, repository.CountTopArtistsParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -86,15 +57,9 @@ func (p *Psql) CountArtists(ctx context.Context, timeframe db.Timeframe) (int64,
return count, nil
}
// in seconds
func (p *Psql) CountTimeListened(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountTimeListened(ctx, repository.CountTimeListenedParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -105,9 +70,9 @@ func (p *Psql) CountTimeListened(ctx context.Context, timeframe db.Timeframe) (i
return count, nil
}
// in seconds
func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListenedOpts) (int64, error) {
t2 := time.Now()
t1 := db.StartTimeFromPeriod(opts.Period)
t1, t2 := db.TimeframeToTimeRange(opts.Timeframe)
if opts.ArtistID > 0 {
count, err := p.q.CountTimeListenedToArtist(ctx, repository.CountTimeListenedToArtistParams{
@ -143,15 +108,45 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
return 0, errors.New("CountTimeListenedToItem: an id must be provided")
}
func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
func (p *Psql) CountListensToItem(ctx context.Context, opts db.TimeListenedOpts) (int64, error) {
t1, t2 := db.TimeframeToTimeRange(opts.Timeframe)
if opts.ArtistID > 0 {
count, err := p.q.CountListensFromArtist(ctx, repository.CountListensFromArtistParams{
ListenedAt: t1,
ListenedAt_2: t2,
ArtistID: opts.ArtistID,
})
if err != nil {
return 0, fmt.Errorf("CountListensToItem (Artist): %w", err)
}
return count, nil
} else if opts.AlbumID > 0 {
count, err := p.q.CountListensFromRelease(ctx, repository.CountListensFromReleaseParams{
ListenedAt: t1,
ListenedAt_2: t2,
ReleaseID: opts.AlbumID,
})
if err != nil {
return 0, fmt.Errorf("CountListensToItem (Album): %w", err)
}
return count, nil
} else if opts.TrackID > 0 {
count, err := p.q.CountListensFromTrack(ctx, repository.CountListensFromTrackParams{
ListenedAt: t1,
ListenedAt_2: t2,
TrackID: opts.TrackID,
})
if err != nil {
return 0, fmt.Errorf("CountListensToItem (Track): %w", err)
}
return count, nil
}
return 0, errors.New("CountListensToItem: an id must be provided")
}
func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int64, error) {
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountNewTracks(ctx, repository.CountNewTracksParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -163,14 +158,7 @@ func (p *Psql) CountNewTracks(ctx context.Context, timeframe db.Timeframe) (int6
}
func (p *Psql) CountNewAlbums(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountNewReleases(ctx, repository.CountNewReleasesParams{
ListenedAt: t1,
ListenedAt_2: t2,
@ -182,14 +170,7 @@ func (p *Psql) CountNewAlbums(ctx context.Context, timeframe db.Timeframe) (int6
}
func (p *Psql) CountNewArtists(ctx context.Context, timeframe db.Timeframe) (int64, error) {
var t1, t2 time.Time
if timeframe.T1u == 0 && timeframe.T2u == 0 {
t2 = time.Now()
t1 = db.StartTimeFromPeriod(timeframe.Period)
} else {
t1 = time.Unix(timeframe.T1u, 0)
t2 = time.Unix(timeframe.T2u, 0)
}
t1, t2 := db.TimeframeToTimeRange(timeframe)
count, err := p.q.CountNewArtists(ctx, repository.CountNewArtistsParams{
ListenedAt: t1,
ListenedAt_2: t2,

View file

@ -131,7 +131,7 @@ func TestCountTimeListenedToArtist(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, ArtistID: 1})
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, ArtistID: 1})
require.NoError(t, err)
assert.EqualValues(t, 400, count)
truncateTestData(t)
@ -141,7 +141,7 @@ func TestCountTimeListenedToAlbum(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, AlbumID: 2})
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, AlbumID: 2})
require.NoError(t, err)
assert.EqualValues(t, 300, count)
truncateTestData(t)
@ -151,8 +151,38 @@ func TestCountTimeListenedToTrack(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Period: period, TrackID: 3})
count, err := store.CountTimeListenedToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, TrackID: 3})
require.NoError(t, err)
assert.EqualValues(t, 200, count)
truncateTestData(t)
}
func TestListensToArtist(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, ArtistID: 1})
require.NoError(t, err)
assert.EqualValues(t, 4, count)
truncateTestData(t)
}
func TestListensToAlbum(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, AlbumID: 2})
require.NoError(t, err)
assert.EqualValues(t, 3, count)
truncateTestData(t)
}
func TestListensToTrack(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
period := db.PeriodAllTime
count, err := store.CountListensToItem(ctx, db.TimeListenedOpts{Timeframe: db.Timeframe{Period: period}, TrackID: 3})
require.NoError(t, err)
assert.EqualValues(t, 2, count)
truncateTestData(t)
}

View file

@ -25,6 +25,10 @@ func (d *Psql) GetTopAlbumsPaginated(ctx context.Context, opts db.GetItemsOpts)
t2 = time.Now()
t1 = db.StartTimeFromPeriod(opts.Period)
}
if opts.From != 0 || opts.To != 0 {
t1 = time.Unix(opts.From, 0)
t2 = time.Unix(opts.To, 0)
}
if opts.Limit == 0 {
opts.Limit = DefaultItemsPerPage
}

View file

@ -24,6 +24,10 @@ func (d *Psql) GetTopArtistsPaginated(ctx context.Context, opts db.GetItemsOpts)
t2 = time.Now()
t1 = db.StartTimeFromPeriod(opts.Period)
}
if opts.From != 0 || opts.To != 0 {
t1 = time.Unix(opts.From, 0)
t2 = time.Unix(opts.To, 0)
}
if opts.Limit == 0 {
opts.Limit = DefaultItemsPerPage
}

View file

@ -25,6 +25,10 @@ func (d *Psql) GetTopTracksPaginated(ctx context.Context, opts db.GetItemsOpts)
t2 = time.Now()
t1 = db.StartTimeFromPeriod(opts.Period)
}
if opts.From != 0 || opts.To != 0 {
t1 = time.Unix(opts.From, 0)
t2 = time.Unix(opts.To, 0)
}
if opts.Limit == 0 {
opts.Limit = DefaultItemsPerPage
}

View file

@ -82,8 +82,8 @@ func (d *Psql) GetTrack(ctx context.Context, opts db.GetTrackOpts) (*models.Trac
}
seconds, err := d.CountTimeListenedToItem(ctx, db.TimeListenedOpts{
Period: db.PeriodAllTime,
TrackID: track.ID,
Timeframe: db.Timeframe{Period: db.PeriodAllTime},
TrackID: track.ID,
})
if err != nil {
return nil, fmt.Errorf("GetTrack: CountTimeListenedToItem: %w", err)