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, }) }