teg/backend/calendar_settings.go
2026-06-15 20:59:44 +02:00

218 lines
5.4 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"time"
)
type CalendarWorkingHoursDay struct {
Enabled bool `json:"enabled"`
Start string `json:"start"`
End string `json:"end"`
}
type CalendarCoreWorkingHours struct {
Monday CalendarWorkingHoursDay `json:"monday"`
Tuesday CalendarWorkingHoursDay `json:"tuesday"`
Wednesday CalendarWorkingHoursDay `json:"wednesday"`
Thursday CalendarWorkingHoursDay `json:"thursday"`
Friday CalendarWorkingHoursDay `json:"friday"`
Saturday CalendarWorkingHoursDay `json:"saturday"`
Sunday CalendarWorkingHoursDay `json:"sunday"`
}
type CalendarSettings struct {
CoreWorkingHours CalendarCoreWorkingHours `json:"coreWorkingHours"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
}
var calendarTimePattern = regexp.MustCompile(`^\d{2}:\d{2}$`)
func defaultCalendarCoreWorkingHours() CalendarCoreWorkingHours {
workingDay := CalendarWorkingHoursDay{
Enabled: true,
Start: "08:00",
End: "17:00",
}
dayOff := CalendarWorkingHoursDay{
Enabled: false,
Start: "08:00",
End: "17:00",
}
return CalendarCoreWorkingHours{
Monday: workingDay,
Tuesday: workingDay,
Wednesday: workingDay,
Thursday: workingDay,
Friday: workingDay,
Saturday: dayOff,
Sunday: dayOff,
}
}
func (s *Server) ensureCalendarSettingsTable(r *http.Request) error {
defaultHours, err := json.Marshal(defaultCalendarCoreWorkingHours())
if err != nil {
return err
}
_, err = s.db.Exec(
r.Context(),
`
CREATE TABLE IF NOT EXISTS calendar_settings (
id SMALLINT PRIMARY KEY CHECK (id = 1),
core_working_hours JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
`,
)
if err != nil {
return err
}
_, err = s.db.Exec(
r.Context(),
`
INSERT INTO calendar_settings (id, core_working_hours)
VALUES (1, $1::JSONB)
ON CONFLICT (id) DO NOTHING
`,
string(defaultHours),
)
return err
}
func (s *Server) loadCalendarSettings(r *http.Request) (CalendarSettings, error) {
if err := s.ensureCalendarSettingsTable(r); err != nil {
return CalendarSettings{}, err
}
var rawHours []byte
var settings CalendarSettings
var updatedAt time.Time
err := s.db.QueryRow(
r.Context(),
`
SELECT core_working_hours, updated_at
FROM calendar_settings
WHERE id = 1
`,
).Scan(&rawHours, &updatedAt)
if err != nil {
return CalendarSettings{}, err
}
if err := json.Unmarshal(rawHours, &settings.CoreWorkingHours); err != nil {
return CalendarSettings{}, err
}
settings.UpdatedAt = &updatedAt
return settings, nil
}
func validateCalendarWorkingHours(hours CalendarCoreWorkingHours) error {
days := []struct {
name string
hours CalendarWorkingHoursDay
}{
{name: "Montag", hours: hours.Monday},
{name: "Dienstag", hours: hours.Tuesday},
{name: "Mittwoch", hours: hours.Wednesday},
{name: "Donnerstag", hours: hours.Thursday},
{name: "Freitag", hours: hours.Friday},
{name: "Samstag", hours: hours.Saturday},
{name: "Sonntag", hours: hours.Sunday},
}
for _, day := range days {
if !calendarTimePattern.MatchString(day.hours.Start) ||
!calendarTimePattern.MatchString(day.hours.End) {
return fmt.Errorf("%s: Beginn und Ende müssen im Format HH:MM angegeben werden", day.name)
}
start, startErr := time.Parse("15:04", day.hours.Start)
end, endErr := time.Parse("15:04", day.hours.End)
if startErr != nil || endErr != nil {
return fmt.Errorf("%s: ungültige Uhrzeit", day.name)
}
if day.hours.Enabled && !end.After(start) {
return fmt.Errorf("%s: das Ende muss nach dem Beginn liegen", day.name)
}
}
return nil
}
func (s *Server) handleGetCalendarSettings(w http.ResponseWriter, r *http.Request) {
settings, err := s.loadCalendarSettings(r)
if err != nil {
writeError(w, http.StatusInternalServerError, "Kalender-Einstellungen konnten nicht geladen werden")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"settings": settings,
})
}
func (s *Server) handleUpdateCalendarSettings(w http.ResponseWriter, r *http.Request) {
var payload struct {
CoreWorkingHours CalendarCoreWorkingHours `json:"coreWorkingHours"`
}
if err := readJSON(r, &payload); err != nil {
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
return
}
if err := validateCalendarWorkingHours(payload.CoreWorkingHours); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
if err := s.ensureCalendarSettingsTable(r); err != nil {
writeError(w, http.StatusInternalServerError, "Kalender-Einstellungen konnten nicht vorbereitet werden")
return
}
rawHours, err := json.Marshal(payload.CoreWorkingHours)
if err != nil {
writeError(w, http.StatusBadRequest, "Kalender-Einstellungen sind ungültig")
return
}
_, err = s.db.Exec(
r.Context(),
`
INSERT INTO calendar_settings (id, core_working_hours)
VALUES (1, $1::JSONB)
ON CONFLICT (id) DO UPDATE SET
core_working_hours = EXCLUDED.core_working_hours,
updated_at = now()
`,
string(rawHours),
)
if err != nil {
writeError(w, http.StatusInternalServerError, "Kalender-Einstellungen konnten nicht gespeichert werden")
return
}
settings, err := s.loadCalendarSettings(r)
if err != nil {
writeError(w, http.StatusInternalServerError, "Kalender-Einstellungen wurden gespeichert, konnten aber nicht neu geladen werden")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"message": "Kalender-Einstellungen gespeichert",
"settings": settings,
})
}