218 lines
5.4 KiB
Go
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,
|
|
})
|
|
}
|