feat: add db functions for counting new items

This commit is contained in:
Gabe Farrell 2025-12-29 17:53:01 -05:00
parent ef27dce5e3
commit 3b585f748a
9 changed files with 235 additions and 24 deletions

View file

@ -78,6 +78,17 @@ FROM listens l
JOIN artist_tracks at ON l.track_id = at.track_id JOIN artist_tracks at ON l.track_id = at.track_id
WHERE l.listened_at BETWEEN $1 AND $2; WHERE l.listened_at BETWEEN $1 AND $2;
-- name: CountNewArtists :one
SELECT COUNT(*) AS total_count
FROM (
SELECT at.artist_id
FROM listens l
JOIN tracks t ON l.track_id = t.id
JOIN artist_tracks at ON t.id = at.track_id
GROUP BY at.artist_id
HAVING MIN(l.listened_at) BETWEEN $1 AND $2
) first_appearances;
-- name: UpdateArtistMbzID :exec -- name: UpdateArtistMbzID :exec
UPDATE artists SET musicbrainz_id = $2 UPDATE artists SET musicbrainz_id = $2
WHERE id = $1; WHERE id = $1;

View file

@ -73,6 +73,16 @@ FROM releases r
JOIN artist_releases ar ON r.id = ar.release_id JOIN artist_releases ar ON r.id = ar.release_id
WHERE ar.artist_id = $1; WHERE ar.artist_id = $1;
-- name: CountNewReleases :one
SELECT COUNT(*) AS total_count
FROM (
SELECT t.release_id
FROM listens l
JOIN tracks t ON l.track_id = t.id
GROUP BY t.release_id
HAVING MIN(l.listened_at) BETWEEN $1 AND $2
) first_appearances;
-- name: AssociateArtistToRelease :exec -- name: AssociateArtistToRelease :exec
INSERT INTO artist_releases (artist_id, release_id, is_primary) INSERT INTO artist_releases (artist_id, release_id, is_primary)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)

View file

@ -109,6 +109,15 @@ JOIN tracks t ON l.track_id = t.id
WHERE l.listened_at BETWEEN $1 AND $2 WHERE l.listened_at BETWEEN $1 AND $2
AND t.release_id = $3; AND t.release_id = $3;
-- name: CountNewTracks :one
SELECT COUNT(*) AS total_count
FROM (
SELECT track_id
FROM listens
GROUP BY track_id
HAVING MIN(listened_at) BETWEEN $1 AND $2
) first_appearances;
-- name: UpdateTrackMbzID :exec -- name: UpdateTrackMbzID :exec
UPDATE tracks SET musicbrainz_id = $2 UPDATE tracks SET musicbrainz_id = $2
WHERE id = $1; WHERE id = $1;

View file

@ -67,6 +67,9 @@ type DB interface {
CountTracks(ctx context.Context, timeframe Timeframe) (int64, error) CountTracks(ctx context.Context, timeframe Timeframe) (int64, error)
CountAlbums(ctx context.Context, timeframe Timeframe) (int64, error) CountAlbums(ctx context.Context, timeframe Timeframe) (int64, error)
CountArtists(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)
CountTimeListened(ctx context.Context, timeframe Timeframe) (int64, error) CountTimeListened(ctx context.Context, timeframe Timeframe) (int64, error)
CountTimeListenedToItem(ctx context.Context, opts TimeListenedOpts) (int64, error) CountTimeListenedToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
CountUsers(ctx context.Context) (int64, error) CountUsers(ctx context.Context) (int64, error)

View file

@ -142,3 +142,60 @@ func (p *Psql) CountTimeListenedToItem(ctx context.Context, opts db.TimeListened
} }
return 0, errors.New("CountTimeListenedToItem: an id must be provided") 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)
}
count, err := p.q.CountNewTracks(ctx, repository.CountNewTracksParams{
ListenedAt: t1,
ListenedAt_2: t2,
})
if err != nil {
return 0, fmt.Errorf("CountNewTracks: %w", err)
}
return count, nil
}
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)
}
count, err := p.q.CountNewReleases(ctx, repository.CountNewReleasesParams{
ListenedAt: t1,
ListenedAt_2: t2,
})
if err != nil {
return 0, fmt.Errorf("CountNewAlbums: %w", err)
}
return count, nil
}
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)
}
count, err := p.q.CountNewArtists(ctx, repository.CountNewArtistsParams{
ListenedAt: t1,
ListenedAt_2: t2,
})
if err != nil {
return 0, fmt.Errorf("CountNewArtists: %w", err)
}
return count, nil
}

View file

@ -3,6 +3,7 @@ package psql_test
import ( import (
"context" "context"
"testing" "testing"
"time"
"github.com/gabehf/koito/internal/db" "github.com/gabehf/koito/internal/db"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -35,6 +36,23 @@ func TestCountTracks(t *testing.T) {
truncateTestData(t) truncateTestData(t)
} }
func TestCountNewTracks(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
testDataAbsoluteListenTimes(t)
// Test CountTracks
t1, _ := time.Parse(time.DateOnly, "2025-01-01")
t1u := t1.Unix()
t2, _ := time.Parse(time.DateOnly, "2025-12-31")
t2u := t2.Unix()
count, err := store.CountNewTracks(ctx, db.Timeframe{T1u: t1u, T2u: t2u})
require.NoError(t, err)
assert.Equal(t, int64(1), count, "expected tracks count to match inserted data")
truncateTestData(t)
}
func TestCountAlbums(t *testing.T) { func TestCountAlbums(t *testing.T) {
ctx := context.Background() ctx := context.Background()
testDataForTopItems(t) testDataForTopItems(t)
@ -48,6 +66,23 @@ func TestCountAlbums(t *testing.T) {
truncateTestData(t) truncateTestData(t)
} }
func TestCountNewAlbums(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
testDataAbsoluteListenTimes(t)
// Test CountTracks
t1, _ := time.Parse(time.DateOnly, "2025-01-01")
t1u := t1.Unix()
t2, _ := time.Parse(time.DateOnly, "2025-12-31")
t2u := t2.Unix()
count, err := store.CountNewAlbums(ctx, db.Timeframe{T1u: t1u, T2u: t2u})
require.NoError(t, err)
assert.Equal(t, int64(1), count, "expected albums count to match inserted data")
truncateTestData(t)
}
func TestCountArtists(t *testing.T) { func TestCountArtists(t *testing.T) {
ctx := context.Background() ctx := context.Background()
testDataForTopItems(t) testDataForTopItems(t)
@ -61,6 +96,23 @@ func TestCountArtists(t *testing.T) {
truncateTestData(t) truncateTestData(t)
} }
func TestCountNewArtists(t *testing.T) {
ctx := context.Background()
testDataForTopItems(t)
testDataAbsoluteListenTimes(t)
// Test CountTracks
t1, _ := time.Parse(time.DateOnly, "2025-01-01")
t1u := t1.Unix()
t2, _ := time.Parse(time.DateOnly, "2025-12-31")
t2u := t2.Unix()
count, err := store.CountNewArtists(ctx, db.Timeframe{T1u: t1u, T2u: t2u})
require.NoError(t, err)
assert.Equal(t, int64(1), count, "expected artists count to match inserted data")
truncateTestData(t)
}
func TestCountTimeListened(t *testing.T) { func TestCountTimeListened(t *testing.T) {
ctx := context.Background() ctx := context.Background()
testDataForTopItems(t) testDataForTopItems(t)

View file

@ -13,6 +13,30 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const countNewArtists = `-- name: CountNewArtists :one
SELECT COUNT(*) AS total_count
FROM (
SELECT at.artist_id
FROM listens l
JOIN tracks t ON l.track_id = t.id
JOIN artist_tracks at ON t.id = at.track_id
GROUP BY at.artist_id
HAVING MIN(l.listened_at) BETWEEN $1 AND $2
) first_appearances
`
type CountNewArtistsParams struct {
ListenedAt time.Time
ListenedAt_2 time.Time
}
func (q *Queries) CountNewArtists(ctx context.Context, arg CountNewArtistsParams) (int64, error) {
row := q.db.QueryRow(ctx, countNewArtists, arg.ListenedAt, arg.ListenedAt_2)
var total_count int64
err := row.Scan(&total_count)
return total_count, err
}
const countTopArtists = `-- name: CountTopArtists :one const countTopArtists = `-- name: CountTopArtists :one
SELECT COUNT(DISTINCT at.artist_id) AS total_count SELECT COUNT(DISTINCT at.artist_id) AS total_count
FROM listens l FROM listens l

View file

@ -30,6 +30,29 @@ func (q *Queries) AssociateArtistToRelease(ctx context.Context, arg AssociateArt
return err return err
} }
const countNewReleases = `-- name: CountNewReleases :one
SELECT COUNT(*) AS total_count
FROM (
SELECT t.release_id
FROM listens l
JOIN tracks t ON l.track_id = t.id
GROUP BY t.release_id
HAVING MIN(l.listened_at) BETWEEN $1 AND $2
) first_appearances
`
type CountNewReleasesParams struct {
ListenedAt time.Time
ListenedAt_2 time.Time
}
func (q *Queries) CountNewReleases(ctx context.Context, arg CountNewReleasesParams) (int64, error) {
row := q.db.QueryRow(ctx, countNewReleases, arg.ListenedAt, arg.ListenedAt_2)
var total_count int64
err := row.Scan(&total_count)
return total_count, err
}
const countReleasesFromArtist = `-- name: CountReleasesFromArtist :one const countReleasesFromArtist = `-- name: CountReleasesFromArtist :one
SELECT COUNT(*) SELECT COUNT(*)
FROM releases r FROM releases r

View file

@ -29,6 +29,28 @@ func (q *Queries) AssociateArtistToTrack(ctx context.Context, arg AssociateArtis
return err return err
} }
const countNewTracks = `-- name: CountNewTracks :one
SELECT COUNT(*) AS total_count
FROM (
SELECT track_id
FROM listens
GROUP BY track_id
HAVING MIN(listened_at) BETWEEN $1 AND $2
) first_appearances
`
type CountNewTracksParams struct {
ListenedAt time.Time
ListenedAt_2 time.Time
}
func (q *Queries) CountNewTracks(ctx context.Context, arg CountNewTracksParams) (int64, error) {
row := q.db.QueryRow(ctx, countNewTracks, arg.ListenedAt, arg.ListenedAt_2)
var total_count int64
err := row.Scan(&total_count)
return total_count, err
}
const countTopTracks = `-- name: CountTopTracks :one const countTopTracks = `-- name: CountTopTracks :one
SELECT COUNT(DISTINCT l.track_id) AS total_count SELECT COUNT(DISTINCT l.track_id) AS total_count
FROM listens l FROM listens l