feat: persist booked dates in JSON file and exclude booked dates from selection

This commit is contained in:
2026-08-12 18:10:53 +02:00
parent 8685b3c466
commit 23baff9ef0
6 changed files with 102 additions and 5 deletions
+1
View File
@@ -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
@@ -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.
+1
View File
@@ -1,2 +1,3 @@
.env
*.exe
booked_dates.json
+1
View File
@@ -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` |
+79
View File
@@ -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")
+19 -5
View File
@@ -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 });
}
});