// PUT /api/me/timezone — accept the client's IANA timezone and store // it on the user row. The system-playlist scheduler (#392 Half B) // reads this column to fire each user's daily 03:00 build in their // local time. // // Scheduler.Refresh(ctx, userID) is invoked in a later commit (Task 3) // so a timezone change takes effect synchronously rather than waiting // for the hourly reconciliation pass. package api import ( "encoding/json" "net/http" "time" "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) // putTimezoneBody is the JSON body for PUT /api/me/timezone. type putTimezoneBody struct { Timezone string `json:"timezone"` } func (h *handlers) handlePutTimezone(w http.ResponseWriter, r *http.Request) { user, ok := auth.UserFromContext(r.Context()) if !ok { writeErr(w, apierror.Unauthorized("unauthorized", "authentication required")) return } var body putTimezoneBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeErr(w, apierror.BadRequest("bad_body", "invalid JSON body")) return } if body.Timezone == "" { writeErr(w, apierror.BadRequest("bad_body", "timezone is required")) return } if _, err := time.LoadLocation(body.Timezone); err != nil { writeErr(w, apierror.BadRequest("invalid_timezone", "timezone must be a valid IANA name (e.g. America/New_York)")) return } q := dbq.New(h.pool) if err := q.UpdateUserTimezone(r.Context(), dbq.UpdateUserTimezoneParams{ ID: user.ID, Timezone: body.Timezone, }); err != nil { h.logger.Error("api: update timezone", "err", err) writeErr(w, apierror.InternalMsg("update failed", err)) return } // Reschedule the user's daily build at their new timezone. Failure // here doesn't undo the DB write — the hourly reconciliation pass // would pick up the new tz within an hour at worst. if h.playlistScheduler != nil { if err := h.playlistScheduler.Refresh(r.Context(), user.ID); err != nil { h.logger.Warn("api: scheduler refresh after timezone update", "user_id", uuidToString(user.ID), "err", err) } } w.WriteHeader(http.StatusNoContent) }