diff --git a/.env.example b/.env.example index 94d5579..a44e2f9 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # Server Port & Configuration PORT=8080 WEB_DIR=public +BOOKED_DATES_FILE=booked_dates.json # SMTP Configuration SMTP_HOST=mail.example.com diff --git a/.github/prompts/9-collect-booking-dates.prompt.md b/.github/prompts/9-collect-booking-dates.prompt.md new file mode 100644 index 0000000..b51cede --- /dev/null +++ b/.github/prompts/9-collect-booking-dates.prompt.md @@ -0,0 +1 @@ +I now want the application to maintain a list of booked dates. That can be a simple text file or json file. every time a booking is submitted, the date should be added to that file. When offering available dates for selection, the application should read that file and exclude any dates that have already been booked. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 91ae1f2..11bbd39 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env *.exe +booked_dates.json diff --git a/README.md b/README.md index 8ac3d80..9dcea11 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Trage in der `.env` deine Server- und SMTP-Zugangsdaten ein: | :--- | :--- | :--- | | `PORT` | HTTP-Port des Go-Servers | `8080` | | `WEB_DIR` | Verzeichnis der statischen Assets | `public` | +| `BOOKED_DATES_FILE` | Speicherpfad für bereits gebuchte Termine | `booked_dates.json` | | `SMTP_HOST` | Postausgangsserver (SMTP) | `mail.example.com` | | `SMTP_PORT` | SMTP-Port (Standard meist 587 oder 465) | `587` | | `SMTP_USER` | Benutzername / E-Mail für SMTP-Auth | `absender@example.com` | diff --git a/main.go b/main.go index e44f28d..bd59a20 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ func main() { } http.HandleFunc("/api/submit", handleSubmit) + http.HandleFunc("/api/booked-dates", handleBookedDates) // Serve static files from webDir directory fs := http.FileServer(http.Dir(webDir)) @@ -81,6 +82,10 @@ func handleSubmit(w http.ResponseWriter, r *http.Request) { log.Printf("Received survey submission from %s for date %s (%s)", payload.Name, payload.Date, payload.Activity) + if err := recordBookedDate(payload.Date); err != nil { + log.Printf("Error recording booked date: %v", err) + } + err = sendEmail(payload) if err != nil { log.Printf("Error sending email: %v", err) @@ -101,6 +106,80 @@ func handleSubmit(w http.ResponseWriter, r *http.Request) { }) } +func handleBookedDates(w http.ResponseWriter, r *http.Request) { + if enableCORS(w, r) { + return + } + + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + dates, err := getBookedDates() + if err != nil { + log.Printf("Error reading booked dates: %v", err) + dates = []string{} + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(dates) +} + +func getBookedDatesFilePath() string { + path := os.Getenv("BOOKED_DATES_FILE") + if path == "" { + path = "booked_dates.json" + } + return path +} + +func getBookedDates() ([]string, error) { + filePath := getBookedDatesFilePath() + file, err := os.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return []string{}, nil + } + return nil, err + } + defer file.Close() + + var dates []string + if err := json.NewDecoder(file).Decode(&dates); err != nil { + return []string{}, nil + } + return dates, nil +} + +func recordBookedDate(dateStr string) error { + if strings.TrimSpace(dateStr) == "" { + return nil + } + + dates, err := getBookedDates() + if err != nil { + dates = []string{} + } + + for _, d := range dates { + if d == dateStr { + return nil + } + } + + dates = append(dates, dateStr) + + filePath := getBookedDatesFilePath() + data, err := json.MarshalIndent(dates, "", " ") + if err != nil { + return err + } + + return os.WriteFile(filePath, data, 0644) +} + func enableCORS(w http.ResponseWriter, r *http.Request) bool { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS") diff --git a/public/datetime.js b/public/datetime.js index 3f61454..69e8dd6 100644 --- a/public/datetime.js +++ b/public/datetime.js @@ -18,6 +18,7 @@ today.setHours(0, 0, 0, 0); let availableDatesMap = new Map(); + let bookedDatesSet = new Set(); let viewYear = today.getFullYear(); let viewMonth = today.getMonth(); let selectedDate = null; @@ -36,15 +37,28 @@ return `${year}-${month}-${day}`; } - fetch("appsettings.json") - .then((response) => response.json()) - .then((settings) => { + Promise.all([ + fetch("appsettings.json").then((res) => res.json()), + fetch("/api/booked-dates") + .then((res) => (res.ok ? res.json() : [])) + .catch(() => []), + ]) + .then(([settings, bookedDates]) => { + bookedDatesSet = new Set(bookedDates || []); + const datesList = settings.availableDates || []; datesList.forEach((item) => { + let key = ""; + let type = "long"; if (typeof item === "string") { - availableDatesMap.set(item, { type: "long" }); + key = item; } else if (item && item.date) { - availableDatesMap.set(item.date, { type: item.type || "long" }); + key = item.date; + type = item.type || "long"; + } + + if (key && !bookedDatesSet.has(key)) { + availableDatesMap.set(key, { type }); } });