package main import ( _ "embed" "encoding/json" "fmt" "net/http" "strings" "github.com/kami/maven/internal/ipc" ) //go:embed reminders.html var remindersHTML string var remindersTmpl = parsePage("reminders", remindersHTML, nil) // reminderRow is one line on /reminders, with the payload unwrapped and both // timestamps already in his clock. // // The page rendered `{{.Payload}}` and the UTC instant, so a reminder read // `{"text":"выпить таблетки"}` and fired an hour off what he was told // (Vikunja #469). Neither is a formatting nicety: the envelope is an internal // shape he never chose, and a time on a page he reads is the time on his wall. type reminderRow struct { Created string Fires string Status string Detail string Text string } // reminderText unwraps the {"text":...} payload the router writes. // // A copy of store.ReminderText rather than a call to it, because mavweb is one // of the pure-Go daemons and internal/store carries the CGO sqlite driver. The // ipc DTO is decoupled from the store on purpose, so the unwrap belongs to // whoever renders it. Payload that is not that shape is shown as he said it. func reminderText(payload string) string { var m map[string]any if err := json.Unmarshal([]byte(payload), &m); err == nil { if t, ok := m["text"]; ok { if s, isStr := t.(string); isStr && s != "" { return s } } } return strings.TrimSpace(payload) } func reminderRows(rs []ipc.Reminder) []reminderRow { out := make([]reminderRow, 0, len(rs)) for _, r := range rs { status := r.Status detail := "" if !r.DeliveryBlockedTs.IsZero() { status = "blocked" detail = r.DeliveryBlockedError } else if r.DeliveryAttempts > 0 && !r.NextAttemptTs.IsZero() { detail = "retry " + r.NextAttemptTs.Local().Format("02 Jan 15:04") } out = append(out, reminderRow{ Created: r.CreatedTs.Local().Format("02 Jan 15:04"), Fires: r.FireTs.Local().Format("02 Jan 15:04"), Status: status, Detail: detail, Text: reminderText(r.Payload), }) } return out } func handleReminders(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) { if !requireCore(w, r, core, "reminders") { return } reminders, err := core.ListReminders(r.Context(), 50) if err != nil { writeProblem(w, r, http.StatusBadGateway, problemCoreReadFailed, "reminders unavailable", fmt.Errorf("list reminders: %w", err)) return } renderPage(w, remindersTmpl, map[string]any{"Reminders": reminderRows(reminders)}) }