Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
// PUT /api/me/timezone — accept the client's IANA timezone and store
|
|
// it on the user row. The system-playlist scheduler reads this column
|
|
// to fire each user's daily 03:00 build in their local time. The
|
|
// handler calls Scheduler.Refresh so a change takes effect at once rather
|
|
// than at 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)
|
|
}
|