-
-
-
-
-

-
-
{message}
-
{details}
-
-
- {stack && (
-
- {stack}
-
- )}
-
-
-
+ return (
+
+
+ {title}
+
+
+
+
+
+

+
-
-
- );
+
+ {stack && (
+
+ {stack}
+
+ )}
+
+
+
+
+
+
+ );
}
diff --git a/client/app/tz.ts b/client/app/tz.ts
new file mode 100644
index 0000000..3d82e0c
--- /dev/null
+++ b/client/app/tz.ts
@@ -0,0 +1,10 @@
+export function initTimezoneCookie() {
+ if (typeof window === "undefined") return;
+
+ if (document.cookie.includes("tz=")) return;
+
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
+ if (!tz) return;
+
+ document.cookie = `tz=${tz}; Path=/; Max-Age=31536000; SameSite=Lax`;
+}
diff --git a/db/queries/listen.sql b/db/queries/listen.sql
index cba443a..fab9687 100644
--- a/db/queries/listen.sql
+++ b/db/queries/listen.sql
@@ -144,97 +144,51 @@ WHERE l.listened_at BETWEEN $1 AND $2
AND t.id = $3;
-- 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;
-- name: ListenActivityForArtist :many
-WITH buckets AS (
- SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
-),
-filtered_listens AS (
- SELECT l.*
- 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 * 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;
-- name: ListenActivityForRelease :many
-WITH buckets AS (
- SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
-),
-filtered_listens AS (
- SELECT l.*
- 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 * 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;
-- name: ListenActivityForTrack :many
-WITH buckets AS (
- SELECT generate_series($1::timestamptz, $2::timestamptz, $3::interval) AS bucket_start
-),
-filtered_listens AS (
- SELECT l.*
- 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 * 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;
-- name: UpdateTrackIdForListens :exec
UPDATE listens SET track_id = $2
diff --git a/engine/handlers/get_listen_activity.go b/engine/handlers/get_listen_activity.go
index 0e3526c..22d23fa 100644
--- a/engine/handlers/get_listen_activity.go
+++ b/engine/handlers/get_listen_activity.go
@@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"strings"
+ "time"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
@@ -85,11 +86,17 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
Range: _range,
Month: month,
Year: year,
+ Timezone: parseTZ(r),
AlbumID: int32(albumId),
ArtistID: int32(artistId),
TrackID: int32(trackId),
}
+ if strings.ToLower(opts.Timezone.String()) == "local" {
+ opts.Timezone, _ = time.LoadLocation("UTC")
+ l.Warn().Msg("GetListenActivityHandler: Timezone is unset, using UTC")
+ }
+
l.Debug().Msgf("GetListenActivityHandler: Retrieving listen activity with options: %+v", opts)
activity, err := store.GetListenActivity(ctx, opts)
@@ -99,7 +106,51 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
return
}
+ activity = fillMissingActivity(activity, opts)
+
l.Debug().Msg("GetListenActivityHandler: Successfully retrieved listen activity")
utils.WriteJSON(w, http.StatusOK, activity)
}
}
+
+// ngl i hate this
+func fillMissingActivity(
+ items []db.ListenActivityItem,
+ opts db.ListenActivityOpts,
+) []db.ListenActivityItem {
+ from, to := db.ListenActivityOptsToTimes(opts)
+
+ existing := make(map[string]int64, len(items))
+ for _, item := range items {
+ existing[item.Start.Format("2006-01-02")] = item.Listens
+ }
+
+ var result []db.ListenActivityItem
+
+ for t := from; t.Before(to); t = addStep(t, opts.Step) {
+ listens := int64(0)
+ if v, ok := existing[t.Format("2006-01-02")]; ok {
+ listens = v
+ }
+
+ result = append(result, db.ListenActivityItem{
+ Start: t,
+ Listens: int64(listens),
+ })
+ }
+
+ return result
+}
+
+func addStep(t time.Time, step db.StepInterval) time.Time {
+ switch step {
+ case db.StepDay:
+ return t.AddDate(0, 0, 1)
+ case db.StepWeek:
+ return t.AddDate(0, 0, 7)
+ case db.StepMonth:
+ return t.AddDate(0, 1, 0)
+ default:
+ return t.AddDate(0, 0, 1)
+ }
+}
diff --git a/engine/handlers/handlers.go b/engine/handlers/handlers.go
index 57a5301..06127aa 100644
--- a/engine/handlers/handlers.go
+++ b/engine/handlers/handlers.go
@@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
"strings"
+ "time"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
@@ -100,5 +101,23 @@ func TimeframeFromRequest(r *http.Request) db.Timeframe {
Week: parseInt("week"),
FromUnix: parseInt64("from"),
ToUnix: parseInt64("to"),
+ Timezone: parseTZ(r),
}
}
+
+func parseTZ(r *http.Request) *time.Location {
+
+ if tz := r.URL.Query().Get("tz"); tz != "" {
+ if loc, err := time.LoadLocation(tz); err == nil {
+ return loc
+ }
+ }
+
+ if c, err := r.Cookie("tz"); err == nil {
+ if loc, err := time.LoadLocation(c.Value); err == nil {
+ return loc
+ }
+ }
+
+ return time.Now().Location()
+}
diff --git a/internal/db/opts.go b/internal/db/opts.go
index ce6b292..65834f2 100644
--- a/internal/db/opts.go
+++ b/internal/db/opts.go
@@ -134,6 +134,7 @@ type ListenActivityOpts struct {
Range int
Month int
Year int
+ Timezone *time.Location
AlbumID int32
ArtistID int32
TrackID int32
diff --git a/internal/db/period.go b/internal/db/period.go
index c3cd5ec..71617c0 100644
--- a/internal/db/period.go
+++ b/internal/db/period.go
@@ -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)
}
diff --git a/internal/db/psql/listen_activity.go b/internal/db/psql/listen_activity.go
index 1953766..7a3a776 100644
--- a/internal/db/psql/listen_activity.go
+++ b/internal/db/psql/listen_activity.go
@@ -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
diff --git a/internal/db/timeframe.go b/internal/db/timeframe.go
index ee0b043..303f29c 100644
--- a/internal/db/timeframe.go
+++ b/internal/db/timeframe.go
@@ -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
diff --git a/internal/repository/listen.sql.go b/internal/repository/listen.sql.go
index d3ff0c8..d3db4bb 100644
--- a/internal/repository/listen.sql.go
+++ b/internal/repository/listen.sql.go
@@ -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)