maybe actually fixed now

This commit is contained in:
Gabe Farrell 2026-01-10 00:57:01 -05:00
parent 913c6c00e5
commit 738ac871e6
12 changed files with 293 additions and 298 deletions

View file

@ -134,6 +134,7 @@ type ListenActivityOpts struct {
Range int
Month int
Year int
Timezone *time.Location
AlbumID int32
ArtistID int32
TrackID int32

View file

@ -58,16 +58,17 @@ 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 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 +80,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)
}

View file

@ -8,7 +8,6 @@ import (
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
"github.com/gabehf/koito/internal/repository"
"github.com/jackc/pgx/v5/pgtype"
)
func (d *Psql) GetListenActivity(ctx context.Context, opts db.ListenActivityOpts) ([]db.ListenActivityItem, error) {
@ -26,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)
@ -37,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
@ -47,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)
@ -58,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
@ -68,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)
@ -79,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
@ -89,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: pgtype.Date{Time: t1, Valid: true},
Column2: pgtype.Date{Time: t2, Valid: true},
Column3: stepToInterval(opts.Step),
Column1: opts.Timezone.String(),
ListenedAt: t1,
ListenedAt_2: t2,
})
if err != nil {
return nil, fmt.Errorf("GetListenActivity: ListenActivity: %w", err)
@ -99,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.BucketedListensBucketStartDate,
Start: row.Day.Time,
Listens: row.ListenCount,
}
listenActivity[i] = t

View file

@ -13,11 +13,12 @@ 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
// ---------------------------------------------------------------------
// 1. Explicit From / To (time.Time) — highest precedence

View file

@ -695,43 +695,29 @@ func (q *Queries) InsertListen(ctx context.Context, arg InsertListenParams) erro
}
const listenActivity = `-- name: ListenActivity :many
WITH buckets AS (
SELECT
d::date AS bucket_start_date,
(d + $3::interval)::date AS bucket_end_date
FROM generate_series(
$1::date,
$2::date,
$3::interval
) AS d
),
bucketed_listens AS (
SELECT
b.bucket_start_date::timestamptz,
COUNT(l.listened_at) AS listen_count
FROM buckets b
LEFT JOIN listens l
ON l.listened_at >= b.bucket_start_date::timestamptz
AND l.listened_at < b.bucket_end_date::timestamptz
GROUP BY b.bucket_start_date
ORDER BY b.bucket_start_date
)
SELECT bucketed_listens.bucket_start_date::timestamptz, 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 pgtype.Date
Column2 pgtype.Date
Column3 pgtype.Interval
Column1 string
ListenedAt time.Time
ListenedAt_2 time.Time
}
type ListenActivityRow struct {
BucketedListensBucketStartDate time.Time
ListenCount int64
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
}
@ -739,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.BucketedListensBucketStartDate, &i.ListenCount); err != nil {
if err := rows.Scan(&i.Day, &i.ListenCount); err != nil {
return nil, err
}
items = append(items, i)
@ -751,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 {
@ -800,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)
@ -812,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 {
@ -861,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)
@ -873,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 {
@ -922,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)