230da7bdcb
Schema + endpoint scaffolding for #392 Half B (per-user timezone scheduling). Adds two columns to the users table: - timezone text NOT NULL DEFAULT 'UTC' (IANA name) - timezone_updated_at timestamptz (nullable; populated on each PUT) PUT /api/me/timezone validates the IANA value via time.LoadLocation and writes the row. No scheduler integration yet — the scheduler struct lands in the next commit and the handler-side Refresh call in the commit after. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
61 lines
1.8 KiB
Go
61 lines
1.8 KiB
Go
// 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)
|
|
}
|