mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 12:01:52 -07:00
feat: add db functions for counting new items
This commit is contained in:
parent
ef27dce5e3
commit
3b585f748a
9 changed files with 235 additions and 24 deletions
|
|
@ -4,7 +4,7 @@ VALUES ($1, $2, $3)
|
|||
RETURNING *;
|
||||
|
||||
-- name: GetArtist :one
|
||||
SELECT
|
||||
SELECT
|
||||
a.*,
|
||||
array_agg(aa.alias)::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -13,7 +13,7 @@ WHERE a.id = $1
|
|||
GROUP BY a.id, a.musicbrainz_id, a.image, a.image_source, a.name;
|
||||
|
||||
-- name: GetTrackArtists :many
|
||||
SELECT
|
||||
SELECT
|
||||
a.*,
|
||||
at.is_primary as is_primary
|
||||
FROM artists_with_name a
|
||||
|
|
@ -25,7 +25,7 @@ GROUP BY a.id, a.musicbrainz_id, a.image, a.image_source, a.name, at.is_primary;
|
|||
SELECT * FROM artists WHERE image = $1 LIMIT 1;
|
||||
|
||||
-- name: GetReleaseArtists :many
|
||||
SELECT
|
||||
SELECT
|
||||
a.*,
|
||||
ar.is_primary as is_primary
|
||||
FROM artists_with_name a
|
||||
|
|
@ -35,7 +35,7 @@ GROUP BY a.id, a.musicbrainz_id, a.image, a.image_source, a.name, ar.is_primary;
|
|||
|
||||
-- name: GetArtistByName :one
|
||||
WITH artist_with_aliases AS (
|
||||
SELECT
|
||||
SELECT
|
||||
a.*,
|
||||
COALESCE(array_agg(aa.alias), '{}')::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -48,7 +48,7 @@ WITH artist_with_aliases AS (
|
|||
SELECT * FROM artist_with_aliases;
|
||||
|
||||
-- name: GetArtistByMbzID :one
|
||||
SELECT
|
||||
SELECT
|
||||
a.*,
|
||||
array_agg(aa.alias)::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -78,6 +78,17 @@ FROM listens l
|
|||
JOIN artist_tracks at ON l.track_id = at.track_id
|
||||
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
|
||||
UPDATE artists SET musicbrainz_id = $2
|
||||
WHERE id = $1;
|
||||
|
|
@ -111,4 +122,4 @@ SET artist_id = $2
|
|||
WHERE artist_id = $1;
|
||||
|
||||
-- name: DeleteArtist :exec
|
||||
DELETE FROM artists WHERE id = $1;
|
||||
DELETE FROM artists WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ VALUES ($1, $2, $3, $4)
|
|||
RETURNING *;
|
||||
|
||||
-- name: GetRelease :one
|
||||
SELECT
|
||||
SELECT
|
||||
*,
|
||||
get_artists_for_release(id) AS artists
|
||||
FROM releases_with_title
|
||||
|
|
@ -69,10 +69,20 @@ WHERE l.listened_at BETWEEN $1 AND $2;
|
|||
|
||||
-- name: CountReleasesFromArtist :one
|
||||
SELECT COUNT(*)
|
||||
FROM releases r
|
||||
FROM releases r
|
||||
JOIN artist_releases ar ON r.id = ar.release_id
|
||||
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
|
||||
INSERT INTO artist_releases (artist_id, release_id, is_primary)
|
||||
VALUES ($1, $2, $3)
|
||||
|
|
@ -82,8 +92,8 @@ ON CONFLICT DO NOTHING;
|
|||
SELECT
|
||||
r.*,
|
||||
get_artists_for_release(r.id) AS artists
|
||||
FROM releases_with_title r
|
||||
WHERE r.image IS NULL
|
||||
FROM releases_with_title r
|
||||
WHERE r.image IS NULL
|
||||
AND r.id > $2
|
||||
ORDER BY r.id ASC
|
||||
LIMIT $1;
|
||||
|
|
@ -107,8 +117,8 @@ WHERE id = $1;
|
|||
-- name: DeleteRelease :exec
|
||||
DELETE FROM releases WHERE id = $1;
|
||||
|
||||
-- name: DeleteReleasesFromArtist :exec
|
||||
-- name: DeleteReleasesFromArtist :exec
|
||||
DELETE FROM releases r
|
||||
USING artist_releases ar
|
||||
WHERE ar.release_id = r.id
|
||||
AND ar.artist_id = $1;
|
||||
AND ar.artist_id = $1;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ VALUES ($1, $2, $3)
|
|||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: GetTrack :one
|
||||
SELECT
|
||||
SELECT
|
||||
t.*,
|
||||
get_artists_for_track(t.id) AS artists,
|
||||
r.image
|
||||
|
|
@ -109,6 +109,15 @@ JOIN tracks t ON l.track_id = t.id
|
|||
WHERE l.listened_at BETWEEN $1 AND $2
|
||||
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
|
||||
UPDATE tracks SET musicbrainz_id = $2
|
||||
WHERE id = $1;
|
||||
|
|
@ -126,4 +135,4 @@ UPDATE artist_tracks SET is_primary = $3
|
|||
WHERE artist_id = $1 AND track_id = $2;
|
||||
|
||||
-- name: DeleteTrack :exec
|
||||
DELETE FROM tracks WHERE id = $1;
|
||||
DELETE FROM tracks WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ type DB interface {
|
|||
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)
|
||||
CountTimeListened(ctx context.Context, timeframe Timeframe) (int64, error)
|
||||
CountTimeListenedToItem(ctx context.Context, opts TimeListenedOpts) (int64, error)
|
||||
CountUsers(ctx context.Context) (int64, error)
|
||||
|
|
|
|||
|
|
@ -142,3 +142,60 @@ 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package psql_test
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gabehf/koito/internal/db"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -35,6 +36,23 @@ func TestCountTracks(t *testing.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) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
|
|
@ -48,6 +66,23 @@ func TestCountAlbums(t *testing.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) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
|
|
@ -61,6 +96,23 @@ func TestCountArtists(t *testing.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) {
|
||||
ctx := context.Background()
|
||||
testDataForTopItems(t)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,30 @@ import (
|
|||
"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
|
||||
SELECT COUNT(DISTINCT at.artist_id) AS total_count
|
||||
FROM listens l
|
||||
|
|
@ -78,7 +102,7 @@ func (q *Queries) DeleteConflictingArtistTracks(ctx context.Context, arg DeleteC
|
|||
}
|
||||
|
||||
const getArtist = `-- name: GetArtist :one
|
||||
SELECT
|
||||
SELECT
|
||||
a.id, a.musicbrainz_id, a.image, a.image_source, a.name,
|
||||
array_agg(aa.alias)::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -127,7 +151,7 @@ func (q *Queries) GetArtistByImage(ctx context.Context, image *uuid.UUID) (Artis
|
|||
}
|
||||
|
||||
const getArtistByMbzID = `-- name: GetArtistByMbzID :one
|
||||
SELECT
|
||||
SELECT
|
||||
a.id, a.musicbrainz_id, a.image, a.image_source, a.name,
|
||||
array_agg(aa.alias)::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -161,7 +185,7 @@ func (q *Queries) GetArtistByMbzID(ctx context.Context, musicbrainzID *uuid.UUID
|
|||
|
||||
const getArtistByName = `-- name: GetArtistByName :one
|
||||
WITH artist_with_aliases AS (
|
||||
SELECT
|
||||
SELECT
|
||||
a.id, a.musicbrainz_id, a.image, a.image_source, a.name,
|
||||
COALESCE(array_agg(aa.alias), '{}')::text[] AS aliases
|
||||
FROM artists_with_name a
|
||||
|
|
@ -198,7 +222,7 @@ func (q *Queries) GetArtistByName(ctx context.Context, alias string) (GetArtistB
|
|||
}
|
||||
|
||||
const getReleaseArtists = `-- name: GetReleaseArtists :many
|
||||
SELECT
|
||||
SELECT
|
||||
a.id, a.musicbrainz_id, a.image, a.image_source, a.name,
|
||||
ar.is_primary as is_primary
|
||||
FROM artists_with_name a
|
||||
|
|
@ -307,7 +331,7 @@ func (q *Queries) GetTopArtistsPaginated(ctx context.Context, arg GetTopArtistsP
|
|||
}
|
||||
|
||||
const getTrackArtists = `-- name: GetTrackArtists :many
|
||||
SELECT
|
||||
SELECT
|
||||
a.id, a.musicbrainz_id, a.image, a.image_source, a.name,
|
||||
at.is_primary as is_primary
|
||||
FROM artists_with_name a
|
||||
|
|
|
|||
|
|
@ -30,9 +30,32 @@ func (q *Queries) AssociateArtistToRelease(ctx context.Context, arg AssociateArt
|
|||
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
|
||||
SELECT COUNT(*)
|
||||
FROM releases r
|
||||
FROM releases r
|
||||
JOIN artist_releases ar ON r.id = ar.release_id
|
||||
WHERE ar.artist_id = $1
|
||||
`
|
||||
|
|
@ -86,7 +109,7 @@ func (q *Queries) DeleteReleasesFromArtist(ctx context.Context, artistID int32)
|
|||
}
|
||||
|
||||
const getRelease = `-- name: GetRelease :one
|
||||
SELECT
|
||||
SELECT
|
||||
id, musicbrainz_id, image, various_artists, image_source, title,
|
||||
get_artists_for_release(id) AS artists
|
||||
FROM releases_with_title
|
||||
|
|
@ -213,8 +236,8 @@ const getReleasesWithoutImages = `-- name: GetReleasesWithoutImages :many
|
|||
SELECT
|
||||
r.id, r.musicbrainz_id, r.image, r.various_artists, r.image_source, r.title,
|
||||
get_artists_for_release(r.id) AS artists
|
||||
FROM releases_with_title r
|
||||
WHERE r.image IS NULL
|
||||
FROM releases_with_title r
|
||||
WHERE r.image IS NULL
|
||||
AND r.id > $2
|
||||
ORDER BY r.id ASC
|
||||
LIMIT $1
|
||||
|
|
|
|||
|
|
@ -29,6 +29,28 @@ func (q *Queries) AssociateArtistToTrack(ctx context.Context, arg AssociateArtis
|
|||
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
|
||||
SELECT COUNT(DISTINCT l.track_id) AS total_count
|
||||
FROM listens l
|
||||
|
|
@ -343,7 +365,7 @@ func (q *Queries) GetTopTracksPaginated(ctx context.Context, arg GetTopTracksPag
|
|||
}
|
||||
|
||||
const getTrack = `-- name: GetTrack :one
|
||||
SELECT
|
||||
SELECT
|
||||
t.id, t.musicbrainz_id, t.duration, t.release_id, t.title,
|
||||
get_artists_for_track(t.id) AS artists,
|
||||
r.image
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue