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:
Gabe Farrell 2026-01-10 01:45:31 -05:00 committed by GitHub
parent 2925425750
commit f48dd6c039
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 368 additions and 343 deletions

View file

@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"strings"
"time"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
@ -19,7 +20,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
rangeStr := r.URL.Query().Get("range")
_range, err := strconv.Atoi(rangeStr)
if err != nil {
if err != nil && rangeStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid range parameter")
utils.WriteError(w, "invalid range parameter", http.StatusBadRequest)
return
@ -27,7 +28,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
monthStr := r.URL.Query().Get("month")
month, err := strconv.Atoi(monthStr)
if err != nil {
if err != nil && monthStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid month parameter")
utils.WriteError(w, "invalid month parameter", http.StatusBadRequest)
return
@ -35,7 +36,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
yearStr := r.URL.Query().Get("year")
year, err := strconv.Atoi(yearStr)
if err != nil {
if err != nil && yearStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid year parameter")
utils.WriteError(w, "invalid year parameter", http.StatusBadRequest)
return
@ -43,7 +44,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
artistIdStr := r.URL.Query().Get("artist_id")
artistId, err := strconv.Atoi(artistIdStr)
if err != nil {
if err != nil && artistIdStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid artist ID parameter")
utils.WriteError(w, "invalid artist ID parameter", http.StatusBadRequest)
return
@ -51,7 +52,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
albumIdStr := r.URL.Query().Get("album_id")
albumId, err := strconv.Atoi(albumIdStr)
if err != nil {
if err != nil && albumIdStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid album ID parameter")
utils.WriteError(w, "invalid album ID parameter", http.StatusBadRequest)
return
@ -59,7 +60,7 @@ func GetListenActivityHandler(store db.DB) func(w http.ResponseWriter, r *http.R
trackIdStr := r.URL.Query().Get("track_id")
trackId, err := strconv.Atoi(trackIdStr)
if err != nil {
if err != nil && trackIdStr != "" {
l.Debug().AnErr("error", err).Msg("GetListenActivityHandler: Invalid track ID parameter")
utils.WriteError(w, "invalid track ID parameter", http.StatusBadRequest)
return
@ -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)
}
}

View file

@ -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()
}