// 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 } w.WriteHeader(http.StatusNoContent) }