diff --git a/internal/shiftnotifier/formatting.go b/internal/shiftnotifier/formatting.go new file mode 100644 index 0000000..912835e --- /dev/null +++ b/internal/shiftnotifier/formatting.go @@ -0,0 +1,140 @@ +package shiftnotifier + +import ( + "sort" + "strconv" + "strings" + "time" +) + +func (service *service) diffToMessage(diffs *shiftDiffs) (string, string) { + msg := strings.Builder{} + msgHTML := strings.Builder{} + + defaultTZ, err := time.LoadLocation("Europe/Berlin") + if err != nil { + defaultTZ = time.Local + } + + timeStr := diffs.ReferenceTime. + Add(service.config.NotifyBeforeShiftStart). + In(defaultTZ). + Format("Mon, 15:04") + + // Title. + msg.WriteString("TROLL CHANGES FOR ") + msg.WriteString(timeStr) + msg.WriteString("\n\n") + + msgHTML.WriteString("

Troll Changes for ") + msgHTML.WriteString(timeStr) + msgHTML.WriteString("


\n") + + // Sort by mapkey to have deterministic order. + locations := make([]string, 0, len(diffs.DiffsInLocations)) + for k := range diffs.DiffsInLocations { + locations = append(locations, k) + } + sort.Strings(locations) + + for _, loc := range locations { + // Location. + msg.WriteString("📍 ") + msg.WriteString(loc) + msg.WriteString("\n") + + msgHTML.WriteString("📍 ") + msgHTML.WriteString(loc) + msgHTML.WriteString("
\n") + + // Troll lists. + msg.WriteString("Arriving Trolls 🔜:\n") + msgHTML.WriteString("Arriving Trolls 🔜:
\n") + usersToList(diffs.DiffsInLocations[loc].UsersArriving, &msg, &msgHTML) + + msg.WriteString("Staying Trolls 🔄:\n") + msgHTML.WriteString("Staying Trolls 🔄:
\n") + usersToList(diffs.DiffsInLocations[loc].UsersWorking, &msg, &msgHTML) + + msg.WriteString("Leaving Trolls 🔚:\n") + msgHTML.WriteString("Leaving Trolls 🔚:
\n") + usersToList(diffs.DiffsInLocations[loc].UsersLeaving, &msg, &msgHTML) + + // Summary. + msg.WriteString("\nExpecting ") + msg.WriteString(strconv.Itoa(int(diffs.DiffsInLocations[loc].ExpectedUsers))) + msg.WriteString(" trolls total\n") + msgHTML.WriteString("
\nExpecting ") + msgHTML.WriteString(strconv.Itoa(int(diffs.DiffsInLocations[loc].ExpectedUsers))) + msgHTML.WriteString(" trolls total
\n") + + if len(diffs.DiffsInLocations[loc].OpenUsers) > 0 { + msg.WriteString("🚨 Open positions:\n") + for shiftType, amount := range diffs.DiffsInLocations[loc].OpenUsers { + msg.WriteString("- ") + msg.WriteString(strconv.Itoa(int(amount))) + msg.WriteString("x ") + msg.WriteString(shiftType) + msg.WriteString("\n") + } + + msgHTML.WriteString("🚨 Open positions:
\n") + for shiftType, amount := range diffs.DiffsInLocations[loc].OpenUsers { + msgHTML.WriteString("- ") + msgHTML.WriteString(strconv.Itoa(int(amount))) + msgHTML.WriteString("x ") + msgHTML.WriteString(shiftType) + msgHTML.WriteString("
\n") + } + } + + msg.WriteString("\n") + msgHTML.WriteString("
\n") + } + + return msg.String(), msgHTML.String() +} + +func usersToList(users []shiftUser, msg *strings.Builder, msgHTML *strings.Builder) { + if len(users) == 0 { + msg.WriteString(" _none_\n") + msgHTML.WriteString("  none
\n") + return + } + + for _, user := range users { + msg.WriteString(" - ") + msg.WriteString(user.Nickname) + msg.WriteString(" (") + msg.WriteString(user.ShiftName) + msg.WriteString(shiftNameToEmoji(user.ShiftName)) + msg.WriteString(")\n") + + msgHTML.WriteString("  - ") + msgHTML.WriteString(user.Nickname) + msgHTML.WriteString(" (") + msgHTML.WriteString(user.ShiftName) + msgHTML.WriteString(shiftNameToEmoji(user.ShiftName)) + msgHTML.WriteString(")
\n") + } +} + +func shiftNameToEmoji(shiftName string) string { + shiftName = strings.TrimSpace(strings.ToLower(shiftName)) + switch { + case strings.Contains(shiftName, "orga"): + return " 👑" + case strings.Contains(shiftName, "tschunk"): + return " 🥃" + case strings.Contains(shiftName, "kaffee"): + return " 🍫" + case strings.Contains(shiftName, "runner"): + return " 🏃‍♀️" + case strings.Contains(shiftName, "bottle"): + return " ♻️" + case strings.Contains(shiftName, "bar-theke"): + return " 💶" + default: + return "" + } +} diff --git a/internal/shiftnotifier/service.go b/internal/shiftnotifier/service.go index 2b2bf64..092d6a1 100644 --- a/internal/shiftnotifier/service.go +++ b/internal/shiftnotifier/service.go @@ -2,13 +2,9 @@ package shiftnotifier import ( "context" - "encoding/json" - "errors" "log/slog" "net/http" "os" - "sort" - "strconv" "strings" "sync" "time" @@ -64,56 +60,6 @@ func New(config *Config, angelAPI angelapi.Service, messenger matrixmessenger.Me return s } -func (service *service) serveJSONData(w http.ResponseWriter, r *http.Request) { - err := service.requireToken(r) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte("unauthorized")) - return - } - - data, err := json.Marshal(service.latestDiffs) - if err != nil { - slog.Error("failed marshaling data", "error", err.Error()) - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("internal server error")) - return - } - _, _ = w.Write(data) -} - -func (service *service) serveHumanData(w http.ResponseWriter, r *http.Request) { - err := service.requireToken(r) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte("unauthorized")) - return - } - - if service.latestDiffs == nil { - _, _ = w.Write([]byte("no data")) - return - } - - _, html := service.diffToMessage(service.latestDiffs) - - if refreshSeconds := r.URL.Query().Get("refresh_seconds"); refreshSeconds != "" { - html = `` + html - } - - html = ` - - ` + html + "" - - _, _ = w.Write([]byte(html)) -} - func (service *service) Start() error { go func(config Config) { err := http.ListenAndServe(config.ListenAddr, nil) @@ -392,121 +338,3 @@ func (service *service) cleanUpDiffs(diffs map[string]shiftDiff) map[string]shif return newDiffs } - -func (service *service) diffToMessage(diffs *shiftDiffs) (string, string) { - msg := strings.Builder{} - msgHTML := strings.Builder{} - - defaultTZ, err := time.LoadLocation("Europe/Berlin") - if err != nil { - defaultTZ = time.Local - } - - timeStr := diffs.ReferenceTime. - Add(service.config.NotifyBeforeShiftStart). - In(defaultTZ). - Format("Mon, 15:04") - - // Title. - msg.WriteString("TROLL CHANGES FOR ") - msg.WriteString(timeStr) - msg.WriteString("\n\n") - - msgHTML.WriteString("

Troll Changes for ") - msgHTML.WriteString(timeStr) - msgHTML.WriteString("


\n") - - // Sort by mapkey to have deterministic order. - locations := make([]string, 0, len(diffs.DiffsInLocations)) - for k := range diffs.DiffsInLocations { - locations = append(locations, k) - } - sort.Strings(locations) - - for _, loc := range locations { - // Location. - msg.WriteString("📍 ") - msg.WriteString(loc) - msg.WriteString("\n") - - msgHTML.WriteString("📍 ") - msgHTML.WriteString(loc) - msgHTML.WriteString("
\n") - - // Troll lists. - msg.WriteString("Arriving Trolls 🔜:\n") - msgHTML.WriteString("Arriving Trolls 🔜:
\n") - usersToList(diffs.DiffsInLocations[loc].UsersArriving, &msg, &msgHTML) - - msg.WriteString("Staying Trolls 🔄:\n") - msgHTML.WriteString("Staying Trolls 🔄:
\n") - usersToList(diffs.DiffsInLocations[loc].UsersWorking, &msg, &msgHTML) - - msg.WriteString("Leaving Trolls 🔚:\n") - msgHTML.WriteString("Leaving Trolls 🔚:
\n") - usersToList(diffs.DiffsInLocations[loc].UsersLeaving, &msg, &msgHTML) - - // Summary. - msg.WriteString("\nExpecting ") - msg.WriteString(strconv.Itoa(int(diffs.DiffsInLocations[loc].ExpectedUsers))) - msg.WriteString(" trolls total\n") - msgHTML.WriteString("
\nExpecting ") - msgHTML.WriteString(strconv.Itoa(int(diffs.DiffsInLocations[loc].ExpectedUsers))) - msgHTML.WriteString(" trolls total
\n") - - if len(diffs.DiffsInLocations[loc].OpenUsers) > 0 { - msg.WriteString("🚨 Open positions:\n") - for shiftType, amount := range diffs.DiffsInLocations[loc].OpenUsers { - msg.WriteString("- ") - msg.WriteString(strconv.Itoa(int(amount))) - msg.WriteString("x ") - msg.WriteString(shiftType) - msg.WriteString("\n") - } - - msgHTML.WriteString("🚨 Open positions:
\n") - for shiftType, amount := range diffs.DiffsInLocations[loc].OpenUsers { - msgHTML.WriteString("- ") - msgHTML.WriteString(strconv.Itoa(int(amount))) - msgHTML.WriteString("x ") - msgHTML.WriteString(shiftType) - msgHTML.WriteString("
\n") - } - } - - msg.WriteString("\n") - msgHTML.WriteString("
\n") - } - - return msg.String(), msgHTML.String() -} - -func usersToList(users []shiftUser, msg *strings.Builder, msgHTML *strings.Builder) { - if len(users) == 0 { - msg.WriteString(" _none_\n") - msgHTML.WriteString("  none
\n") - return - } - - for _, user := range users { - msg.WriteString(" - ") - msg.WriteString(user.Nickname) - msg.WriteString(" (") - msg.WriteString(user.ShiftName) - msg.WriteString(")\n") - - msgHTML.WriteString("  - ") - msgHTML.WriteString(user.Nickname) - msgHTML.WriteString(" (") - msgHTML.WriteString(user.ShiftName) - msgHTML.WriteString(")
\n") - } -} - -func (service *service) requireToken(r *http.Request) error { - t := r.URL.Query().Get("token") - if strings.TrimSpace(t) != service.config.Token { - return errors.New("invalid auth") - } - return nil -} diff --git a/internal/shiftnotifier/webview.go b/internal/shiftnotifier/webview.go new file mode 100644 index 0000000..daf1487 --- /dev/null +++ b/internal/shiftnotifier/webview.go @@ -0,0 +1,67 @@ +package shiftnotifier + +import ( + "encoding/json" + "errors" + "log/slog" + "net/http" + "strings" +) + +func (service *service) requireToken(r *http.Request) error { + t := r.URL.Query().Get("token") + if strings.TrimSpace(t) != service.config.Token { + return errors.New("invalid auth") + } + return nil +} + +func (service *service) serveJSONData(w http.ResponseWriter, r *http.Request) { + err := service.requireToken(r) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte("unauthorized")) + return + } + + data, err := json.Marshal(service.latestDiffs) + if err != nil { + slog.Error("failed marshaling data", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte("internal server error")) + return + } + _, _ = w.Write(data) +} + +func (service *service) serveHumanData(w http.ResponseWriter, r *http.Request) { + err := service.requireToken(r) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + _, _ = w.Write([]byte("unauthorized")) + return + } + + if service.latestDiffs == nil { + _, _ = w.Write([]byte("no data")) + return + } + + _, html := service.diffToMessage(service.latestDiffs) + + if refreshSeconds := r.URL.Query().Get("refresh_seconds"); refreshSeconds != "" { + html = `` + html + } + + html = ` + + ` + html + "" + + _, _ = w.Write([]byte(html)) +}