mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
fix: respect client timezone for requests (#119)
* maybe fixed for total listen activity * maybe actually fixed now * fix unset location panics
This commit is contained in:
parent
2925425750
commit
f48dd6c039
13 changed files with 368 additions and 343 deletions
|
|
@ -134,6 +134,7 @@ type ListenActivityOpts struct {
|
|||
Range int
|
||||
Month int
|
||||
Year int
|
||||
Timezone *time.Location
|
||||
AlbumID int32
|
||||
ArtistID int32
|
||||
TrackID int32
|
||||
|
|
|
|||
|
|
@ -58,16 +58,20 @@ const (
|
|||
// If opts.Year (or opts.Year + opts.Month) is provided, start and end will simply by the start and end times of that year/month.
|
||||
func ListenActivityOptsToTimes(opts ListenActivityOpts) (start, end time.Time) {
|
||||
now := time.Now()
|
||||
loc := opts.Timezone
|
||||
if loc == nil {
|
||||
loc, _ = time.LoadLocation("UTC")
|
||||
}
|
||||
|
||||
// If Year (and optionally Month) are specified, use calendar boundaries
|
||||
if opts.Year != 0 {
|
||||
if opts.Month != 0 {
|
||||
// Specific month of a specific year
|
||||
start = time.Date(opts.Year, time.Month(opts.Month), 1, 0, 0, 0, 0, now.Location())
|
||||
start = time.Date(opts.Year, time.Month(opts.Month), 1, 0, 0, 0, 0, loc)
|
||||
end = start.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
} else {
|
||||
// Whole year
|
||||
start = time.Date(opts.Year, 1, 1, 0, 0, 0, 0, now.Location())
|
||||
start = time.Date(opts.Year, 1, 1, 0, 0, 0, 0, loc)
|
||||
end = start.AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
return start, end
|
||||
|
|
@ -79,30 +83,30 @@ func ListenActivityOptsToTimes(opts ListenActivityOpts) (start, end time.Time) {
|
|||
// Determine step and align accordingly
|
||||
switch opts.Step {
|
||||
case StepDay:
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
start = today.AddDate(0, 0, -opts.Range)
|
||||
end = today.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
||||
|
||||
case StepWeek:
|
||||
// Align to most recent Sunday
|
||||
weekday := int(now.Weekday()) // Sunday = 0
|
||||
startOfThisWeek := time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, now.Location())
|
||||
startOfThisWeek := time.Date(now.Year(), now.Month(), now.Day()-weekday, 0, 0, 0, 0, loc)
|
||||
start = startOfThisWeek.AddDate(0, 0, -7*opts.Range)
|
||||
end = startOfThisWeek.AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
|
||||
case StepMonth:
|
||||
firstOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
firstOfThisMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
|
||||
start = firstOfThisMonth.AddDate(0, -opts.Range, 0)
|
||||
end = firstOfThisMonth.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
|
||||
case StepYear:
|
||||
firstOfThisYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
|
||||
firstOfThisYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, loc)
|
||||
start = firstOfThisYear.AddDate(-opts.Range, 0, 0)
|
||||
end = firstOfThisYear.AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
|
||||
default:
|
||||
// Default to daily
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
|
||||
start = today.AddDate(0, 0, -opts.Range)
|
||||
end = today.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
l.Debug().Msgf("Fetching listen activity for %d %s(s) from %v to %v for release group %d",
|
||||
opts.Range, opts.Step, t1.Format("Jan 02, 2006 15:04:05"), t2.Format("Jan 02, 2006 15:04:05"), opts.AlbumID)
|
||||
rows, err := d.q.ListenActivityForRelease(ctx, repository.ListenActivityForReleaseParams{
|
||||
Column1: t1,
|
||||
Column2: t2,
|
||||
Column3: stepToInterval(opts.Step),
|
||||
ReleaseID: opts.AlbumID,
|
||||
Column1: opts.Timezone.String(),
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
ReleaseID: opts.AlbumID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetListenActivity: ListenActivityForRelease: %w", err)
|
||||
|
|
@ -36,7 +36,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
listenActivity = make([]db.ListenActivityItem, len(rows))
|
||||
for i, row := range rows {
|
||||
t := db.ListenActivityItem{
|
||||
Start: row.BucketStart,
|
||||
Start: row.Day.Time,
|
||||
Listens: row.ListenCount,
|
||||
}
|
||||
listenActivity[i] = t
|
||||
|
|
@ -46,10 +46,10 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
l.Debug().Msgf("Fetching listen activity for %d %s(s) from %v to %v for artist %d",
|
||||
opts.Range, opts.Step, t1.Format("Jan 02, 2006 15:04:05"), t2.Format("Jan 02, 2006 15:04:05"), opts.ArtistID)
|
||||
rows, err := d.q.ListenActivityForArtist(ctx, repository.ListenActivityForArtistParams{
|
||||
Column1: t1,
|
||||
Column2: t2,
|
||||
Column3: stepToInterval(opts.Step),
|
||||
ArtistID: opts.ArtistID,
|
||||
Column1: opts.Timezone.String(),
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
ArtistID: opts.ArtistID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetListenActivity: ListenActivityForArtist: %w", err)
|
||||
|
|
@ -57,7 +57,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
listenActivity = make([]db.ListenActivityItem, len(rows))
|
||||
for i, row := range rows {
|
||||
t := db.ListenActivityItem{
|
||||
Start: row.BucketStart,
|
||||
Start: row.Day.Time,
|
||||
Listens: row.ListenCount,
|
||||
}
|
||||
listenActivity[i] = t
|
||||
|
|
@ -67,10 +67,10 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
l.Debug().Msgf("Fetching listen activity for %d %s(s) from %v to %v for track %d",
|
||||
opts.Range, opts.Step, t1.Format("Jan 02, 2006 15:04:05"), t2.Format("Jan 02, 2006 15:04:05"), opts.TrackID)
|
||||
rows, err := d.q.ListenActivityForTrack(ctx, repository.ListenActivityForTrackParams{
|
||||
Column1: t1,
|
||||
Column2: t2,
|
||||
Column3: stepToInterval(opts.Step),
|
||||
ID: opts.TrackID,
|
||||
Column1: opts.Timezone.String(),
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
ID: opts.TrackID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetListenActivity: ListenActivityForTrack: %w", err)
|
||||
|
|
@ -78,7 +78,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
listenActivity = make([]db.ListenActivityItem, len(rows))
|
||||
for i, row := range rows {
|
||||
t := db.ListenActivityItem{
|
||||
Start: row.BucketStart,
|
||||
Start: row.Day.Time,
|
||||
Listens: row.ListenCount,
|
||||
}
|
||||
listenActivity[i] = t
|
||||
|
|
@ -88,9 +88,9 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
l.Debug().Msgf("Fetching listen activity for %d %s(s) from %v to %v",
|
||||
opts.Range, opts.Step, t1.Format("Jan 02, 2006 15:04:05"), t2.Format("Jan 02, 2006 15:04:05"))
|
||||
rows, err := d.q.ListenActivity(ctx, repository.ListenActivityParams{
|
||||
Column1: t1,
|
||||
Column2: t2,
|
||||
Column3: stepToInterval(opts.Step),
|
||||
Column1: opts.Timezone.String(),
|
||||
ListenedAt: t1,
|
||||
ListenedAt_2: t2,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetListenActivity: ListenActivity: %w", err)
|
||||
|
|
@ -98,7 +98,7 @@ func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts
|
|||
listenActivity = make([]db.ListenActivityItem, len(rows))
|
||||
for i, row := range rows {
|
||||
t := db.ListenActivityItem{
|
||||
Start: row.BucketStart,
|
||||
Start: row.Day.Time,
|
||||
Listens: row.ListenCount,
|
||||
}
|
||||
listenActivity[i] = t
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ func TestListenActivity(t *testing.T) {
|
|||
// Test for opts.Step = db.StepDay
|
||||
activity, err := store.GetListenActivity(ctx, db.ListenActivityOpts{Step: db.StepDay})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, db.DefaultRange)
|
||||
assert.Equal(t, []int64{0, 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 0}, flattenListenCounts(activity))
|
||||
require.Len(t, activity, 3)
|
||||
assert.Equal(t, []int64{2, 2, 2}, flattenListenCounts(activity))
|
||||
|
||||
// Truncate listens table and insert specific dates for testing opts.Step = db.StepMonth
|
||||
err = store.Exec(context.Background(), `TRUNCATE TABLE listens`)
|
||||
|
|
@ -126,8 +126,8 @@ func TestListenActivity(t *testing.T) {
|
|||
|
||||
activity, err = store.GetListenActivity(ctx, db.ListenActivityOpts{Step: db.StepYear})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, db.DefaultRange)
|
||||
assert.Equal(t, []int64{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0}, flattenListenCounts(activity))
|
||||
require.Len(t, activity, 3)
|
||||
assert.Equal(t, []int64{1, 1, 2}, flattenListenCounts(activity))
|
||||
// Truncate and insert data for a specific month/year
|
||||
err = store.Exec(context.Background(), `TRUNCATE TABLE listens RESTART IDENTITY`)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -144,10 +144,10 @@ func TestListenActivity(t *testing.T) {
|
|||
Year: 2024,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, 31) // number of days in march
|
||||
require.Len(t, activity, 2) // number of days in march
|
||||
t.Log(activity)
|
||||
assert.EqualValues(t, 1, activity[9].Listens)
|
||||
assert.EqualValues(t, 1, activity[19].Listens)
|
||||
assert.EqualValues(t, 1, activity[0].Listens)
|
||||
assert.EqualValues(t, 1, activity[1].Listens)
|
||||
|
||||
// Truncate and insert listens associated with two different albums
|
||||
err = store.Exec(context.Background(), `TRUNCATE TABLE listens RESTART IDENTITY`)
|
||||
|
|
@ -164,53 +164,29 @@ func TestListenActivity(t *testing.T) {
|
|||
AlbumID: 1, // Track 1 only
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, db.DefaultRange)
|
||||
assert.Equal(t, []int64{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0}, flattenListenCounts(activity))
|
||||
require.Len(t, activity, 2)
|
||||
assert.Equal(t, []int64{1, 1}, flattenListenCounts(activity))
|
||||
|
||||
activity, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Step: db.StepDay,
|
||||
TrackID: 1, // Track 1 only
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, db.DefaultRange)
|
||||
assert.Equal(t, []int64{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0}, flattenListenCounts(activity))
|
||||
require.Len(t, activity, 2)
|
||||
assert.Equal(t, []int64{1, 1}, flattenListenCounts(activity))
|
||||
|
||||
activity, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Step: db.StepDay,
|
||||
ArtistID: 2, // Should only include listens to Track 2
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, activity, db.DefaultRange)
|
||||
assert.Equal(t, []int64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, flattenListenCounts(activity))
|
||||
require.Len(t, activity, 1)
|
||||
assert.Equal(t, []int64{1}, flattenListenCounts(activity))
|
||||
|
||||
// month without year is disallowed
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Step: db.StepDay,
|
||||
Month: 5,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// invalid options
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Year: -10,
|
||||
})
|
||||
require.Error(t, err)
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Year: 2025,
|
||||
Month: -10,
|
||||
})
|
||||
require.Error(t, err)
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
Range: -1,
|
||||
})
|
||||
require.Error(t, err)
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
AlbumID: -1,
|
||||
})
|
||||
require.Error(t, err)
|
||||
_, err = store.GetListenActivity(ctx, db.ListenActivityOpts{
|
||||
ArtistID: -1,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@ type Timeframe struct {
|
|||
ToUnix int64
|
||||
From time.Time
|
||||
To time.Time
|
||||
Timezone *time.Location
|
||||
}
|
||||
|
||||
func TimeframeToTimeRange(tf Timeframe) (t1, t2 time.Time) {
|
||||
now := time.Now()
|
||||
loc := now.Location()
|
||||
loc := tf.Timezone
|
||||
if loc == nil {
|
||||
loc, _ = time.LoadLocation("UTC")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 1. Explicit From / To (time.Time) — highest precedence
|
||||
|
|
|
|||
|
|
@ -190,12 +190,32 @@ func (q *Queries) DeleteListen(ctx context.Context, arg DeleteListenParams) erro
|
|||
return err
|
||||
}
|
||||
|
||||
const getFirstListen = `-- name: GetFirstListen :one
|
||||
SELECT
|
||||
track_id, listened_at, client, user_id
|
||||
FROM listens
|
||||
ORDER BY listened_at ASC
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetFirstListen(ctx context.Context) (Listen, error) {
|
||||
row := q.db.QueryRow(ctx, getFirstListen)
|
||||
var i Listen
|
||||
err := row.Scan(
|
||||
&i.TrackID,
|
||||
&i.ListenedAt,
|
||||
&i.Client,
|
||||
&i.UserID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getFirstListenFromArtist = `-- name: GetFirstListenFromArtist :one
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN tracks_with_title t ON l.track_id = t.id
|
||||
JOIN artist_tracks at ON t.id = at.track_id
|
||||
JOIN artist_tracks at ON t.id = at.track_id
|
||||
WHERE at.artist_id = $1
|
||||
ORDER BY l.listened_at ASC
|
||||
LIMIT 1
|
||||
|
|
@ -214,7 +234,7 @@ func (q *Queries) GetFirstListenFromArtist(ctx context.Context, artistID int32)
|
|||
}
|
||||
|
||||
const getFirstListenFromRelease = `-- name: GetFirstListenFromRelease :one
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
|
|
@ -236,7 +256,7 @@ func (q *Queries) GetFirstListenFromRelease(ctx context.Context, releaseID int32
|
|||
}
|
||||
|
||||
const getFirstListenFromTrack = `-- name: GetFirstListenFromTrack :one
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
|
|
@ -258,14 +278,14 @@ func (q *Queries) GetFirstListenFromTrack(ctx context.Context, id int32) (Listen
|
|||
}
|
||||
|
||||
const getLastListensFromArtistPaginated = `-- name: GetLastListensFromArtistPaginated :many
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id,
|
||||
t.title AS track_title,
|
||||
t.release_id AS release_id,
|
||||
get_artists_for_track(t.id) AS artists
|
||||
FROM listens l
|
||||
JOIN tracks_with_title t ON l.track_id = t.id
|
||||
JOIN artist_tracks at ON t.id = at.track_id
|
||||
JOIN artist_tracks at ON t.id = at.track_id
|
||||
WHERE at.artist_id = $5
|
||||
AND l.listened_at BETWEEN $1 AND $2
|
||||
ORDER BY l.listened_at DESC
|
||||
|
|
@ -325,7 +345,7 @@ func (q *Queries) GetLastListensFromArtistPaginated(ctx context.Context, arg Get
|
|||
}
|
||||
|
||||
const getLastListensFromReleasePaginated = `-- name: GetLastListensFromReleasePaginated :many
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id,
|
||||
t.title AS track_title,
|
||||
t.release_id AS release_id,
|
||||
|
|
@ -391,7 +411,7 @@ func (q *Queries) GetLastListensFromReleasePaginated(ctx context.Context, arg Ge
|
|||
}
|
||||
|
||||
const getLastListensFromTrackPaginated = `-- name: GetLastListensFromTrackPaginated :many
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id,
|
||||
t.title AS track_title,
|
||||
t.release_id AS release_id,
|
||||
|
|
@ -457,7 +477,7 @@ func (q *Queries) GetLastListensFromTrackPaginated(ctx context.Context, arg GetL
|
|||
}
|
||||
|
||||
const getLastListensPaginated = `-- name: GetLastListensPaginated :many
|
||||
SELECT
|
||||
SELECT
|
||||
l.track_id, l.listened_at, l.client, l.user_id,
|
||||
t.title AS track_title,
|
||||
t.release_id AS release_id,
|
||||
|
|
@ -675,36 +695,29 @@ func (q *Queries) InsertListen(ctx context.Context, arg InsertListenParams) erro
|
|||
}
|
||||
|
||||
const listenActivity = `-- name: ListenActivity :many
|
||||
WITH buckets AS (
|
||||
SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
|
||||
),
|
||||
bucketed_listens AS (
|
||||
SELECT
|
||||
b.bucket_start,
|
||||
COUNT(l.listened_at) AS listen_count
|
||||
FROM buckets b
|
||||
LEFT JOIN listens l
|
||||
ON l.listened_at >= b.bucket_start
|
||||
AND l.listened_at < b.bucket_start + $3::interval
|
||||
GROUP BY b.bucket_start
|
||||
ORDER BY b.bucket_start
|
||||
)
|
||||
SELECT bucket_start, listen_count FROM bucketed_listens
|
||||
SELECT
|
||||
(listened_at AT TIME ZONE $1::text)::date as day,
|
||||
COUNT(*) AS listen_count
|
||||
FROM listens
|
||||
WHERE listened_at >= $2
|
||||
AND listened_at < $3
|
||||
GROUP BY day
|
||||
ORDER BY day
|
||||
`
|
||||
|
||||
type ListenActivityParams struct {
|
||||
Column1 time.Time
|
||||
Column2 time.Time
|
||||
Column3 pgtype.Interval
|
||||
Column1 string
|
||||
ListenedAt time.Time
|
||||
ListenedAt_2 time.Time
|
||||
}
|
||||
|
||||
type ListenActivityRow struct {
|
||||
BucketStart time.Time
|
||||
Day pgtype.Date
|
||||
ListenCount int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListenActivity(ctx context.Context, arg ListenActivityParams) ([]ListenActivityRow, error) {
|
||||
rows, err := q.db.Query(ctx, listenActivity, arg.Column1, arg.Column2, arg.Column3)
|
||||
rows, err := q.db.Query(ctx, listenActivity, arg.Column1, arg.ListenedAt, arg.ListenedAt_2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -712,7 +725,7 @@ func (q *Queries) ListenActivity(ctx context.Context, arg ListenActivityParams)
|
|||
var items []ListenActivityRow
|
||||
for rows.Next() {
|
||||
var i ListenActivityRow
|
||||
if err := rows.Scan(&i.BucketStart, &i.ListenCount); err != nil {
|
||||
if err := rows.Scan(&i.Day, &i.ListenCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -724,46 +737,36 @@ func (q *Queries) ListenActivity(ctx context.Context, arg ListenActivityParams)
|
|||
}
|
||||
|
||||
const listenActivityForArtist = `-- name: ListenActivityForArtist :many
|
||||
WITH buckets AS (
|
||||
SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
|
||||
),
|
||||
filtered_listens AS (
|
||||
SELECT l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN artist_tracks t ON l.track_id = t.track_id
|
||||
WHERE t.artist_id = $4
|
||||
),
|
||||
bucketed_listens AS (
|
||||
SELECT
|
||||
b.bucket_start,
|
||||
COUNT(l.listened_at) AS listen_count
|
||||
FROM buckets b
|
||||
LEFT JOIN filtered_listens l
|
||||
ON l.listened_at >= b.bucket_start
|
||||
AND l.listened_at < b.bucket_start + $3::interval
|
||||
GROUP BY b.bucket_start
|
||||
ORDER BY b.bucket_start
|
||||
)
|
||||
SELECT bucket_start, listen_count FROM bucketed_listens
|
||||
SELECT
|
||||
(listened_at AT TIME ZONE $1::text)::date as day,
|
||||
COUNT(*) AS listen_count
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
JOIN artist_tracks at ON t.id = at.track_id
|
||||
WHERE l.listened_at >= $2
|
||||
AND l.listened_at < $3
|
||||
AND at.artist_id = $4
|
||||
GROUP BY day
|
||||
ORDER BY day
|
||||
`
|
||||
|
||||
type ListenActivityForArtistParams struct {
|
||||
Column1 time.Time
|
||||
Column2 time.Time
|
||||
Column3 pgtype.Interval
|
||||
ArtistID int32
|
||||
Column1 string
|
||||
ListenedAt time.Time
|
||||
ListenedAt_2 time.Time
|
||||
ArtistID int32
|
||||
}
|
||||
|
||||
type ListenActivityForArtistRow struct {
|
||||
BucketStart time.Time
|
||||
Day pgtype.Date
|
||||
ListenCount int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListenActivityForArtist(ctx context.Context, arg ListenActivityForArtistParams) ([]ListenActivityForArtistRow, error) {
|
||||
rows, err := q.db.Query(ctx, listenActivityForArtist,
|
||||
arg.Column1,
|
||||
arg.Column2,
|
||||
arg.Column3,
|
||||
arg.ListenedAt,
|
||||
arg.ListenedAt_2,
|
||||
arg.ArtistID,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -773,7 +776,7 @@ func (q *Queries) ListenActivityForArtist(ctx context.Context, arg ListenActivit
|
|||
var items []ListenActivityForArtistRow
|
||||
for rows.Next() {
|
||||
var i ListenActivityForArtistRow
|
||||
if err := rows.Scan(&i.BucketStart, &i.ListenCount); err != nil {
|
||||
if err := rows.Scan(&i.Day, &i.ListenCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -785,46 +788,35 @@ func (q *Queries) ListenActivityForArtist(ctx context.Context, arg ListenActivit
|
|||
}
|
||||
|
||||
const listenActivityForRelease = `-- name: ListenActivityForRelease :many
|
||||
WITH buckets AS (
|
||||
SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
|
||||
),
|
||||
filtered_listens AS (
|
||||
SELECT l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
WHERE t.release_id = $4
|
||||
),
|
||||
bucketed_listens AS (
|
||||
SELECT
|
||||
b.bucket_start,
|
||||
COUNT(l.listened_at) AS listen_count
|
||||
FROM buckets b
|
||||
LEFT JOIN filtered_listens l
|
||||
ON l.listened_at >= b.bucket_start
|
||||
AND l.listened_at < b.bucket_start + $3::interval
|
||||
GROUP BY b.bucket_start
|
||||
ORDER BY b.bucket_start
|
||||
)
|
||||
SELECT bucket_start, listen_count FROM bucketed_listens
|
||||
SELECT
|
||||
(listened_at AT TIME ZONE $1::text)::date as day,
|
||||
COUNT(*) AS listen_count
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
WHERE l.listened_at >= $2
|
||||
AND l.listened_at < $3
|
||||
AND t.release_id = $4
|
||||
GROUP BY day
|
||||
ORDER BY day
|
||||
`
|
||||
|
||||
type ListenActivityForReleaseParams struct {
|
||||
Column1 time.Time
|
||||
Column2 time.Time
|
||||
Column3 pgtype.Interval
|
||||
ReleaseID int32
|
||||
Column1 string
|
||||
ListenedAt time.Time
|
||||
ListenedAt_2 time.Time
|
||||
ReleaseID int32
|
||||
}
|
||||
|
||||
type ListenActivityForReleaseRow struct {
|
||||
BucketStart time.Time
|
||||
Day pgtype.Date
|
||||
ListenCount int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListenActivityForRelease(ctx context.Context, arg ListenActivityForReleaseParams) ([]ListenActivityForReleaseRow, error) {
|
||||
rows, err := q.db.Query(ctx, listenActivityForRelease,
|
||||
arg.Column1,
|
||||
arg.Column2,
|
||||
arg.Column3,
|
||||
arg.ListenedAt,
|
||||
arg.ListenedAt_2,
|
||||
arg.ReleaseID,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -834,7 +826,7 @@ func (q *Queries) ListenActivityForRelease(ctx context.Context, arg ListenActivi
|
|||
var items []ListenActivityForReleaseRow
|
||||
for rows.Next() {
|
||||
var i ListenActivityForReleaseRow
|
||||
if err := rows.Scan(&i.BucketStart, &i.ListenCount); err != nil {
|
||||
if err := rows.Scan(&i.Day, &i.ListenCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -846,46 +838,35 @@ func (q *Queries) ListenActivityForRelease(ctx context.Context, arg ListenActivi
|
|||
}
|
||||
|
||||
const listenActivityForTrack = `-- name: ListenActivityForTrack :many
|
||||
WITH buckets AS (
|
||||
SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
|
||||
),
|
||||
filtered_listens AS (
|
||||
SELECT l.track_id, l.listened_at, l.client, l.user_id
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
WHERE t.id = $4
|
||||
),
|
||||
bucketed_listens AS (
|
||||
SELECT
|
||||
b.bucket_start,
|
||||
COUNT(l.listened_at) AS listen_count
|
||||
FROM buckets b
|
||||
LEFT JOIN filtered_listens l
|
||||
ON l.listened_at >= b.bucket_start
|
||||
AND l.listened_at < b.bucket_start + $3::interval
|
||||
GROUP BY b.bucket_start
|
||||
ORDER BY b.bucket_start
|
||||
)
|
||||
SELECT bucket_start, listen_count FROM bucketed_listens
|
||||
SELECT
|
||||
(listened_at AT TIME ZONE $1::text)::date as day,
|
||||
COUNT(*) AS listen_count
|
||||
FROM listens l
|
||||
JOIN tracks t ON l.track_id = t.id
|
||||
WHERE l.listened_at >= $2
|
||||
AND l.listened_at < $3
|
||||
AND t.id = $4
|
||||
GROUP BY day
|
||||
ORDER BY day
|
||||
`
|
||||
|
||||
type ListenActivityForTrackParams struct {
|
||||
Column1 time.Time
|
||||
Column2 time.Time
|
||||
Column3 pgtype.Interval
|
||||
ID int32
|
||||
Column1 string
|
||||
ListenedAt time.Time
|
||||
ListenedAt_2 time.Time
|
||||
ID int32
|
||||
}
|
||||
|
||||
type ListenActivityForTrackRow struct {
|
||||
BucketStart time.Time
|
||||
Day pgtype.Date
|
||||
ListenCount int64
|
||||
}
|
||||
|
||||
func (q *Queries) ListenActivityForTrack(ctx context.Context, arg ListenActivityForTrackParams) ([]ListenActivityForTrackRow, error) {
|
||||
rows, err := q.db.Query(ctx, listenActivityForTrack,
|
||||
arg.Column1,
|
||||
arg.Column2,
|
||||
arg.Column3,
|
||||
arg.ListenedAt,
|
||||
arg.ListenedAt_2,
|
||||
arg.ID,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -895,7 +876,7 @@ func (q *Queries) ListenActivityForTrack(ctx context.Context, arg ListenActivity
|
|||
var items []ListenActivityForTrackRow
|
||||
for rows.Next() {
|
||||
var i ListenActivityForTrackRow
|
||||
if err := rows.Scan(&i.BucketStart, &i.ListenCount); err != nil {
|
||||
if err := rows.Scan(&i.Day, &i.ListenCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@ func DateRange(week, month, year int) (time.Time, time.Time, error) {
|
|||
return start, end, nil
|
||||
}
|
||||
|
||||
// Returns a time.Time that represents the first moment of the day of t.
|
||||
func BeginningOfDay(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
// CopyFile copies a file from src to dst. If src and dst files exist, and are
|
||||
// the same, then return success. Otherise, attempt to create a hard link
|
||||
// between the two files. If that fail, copy the file contents from src to dst.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue