From 2e182d63a32090f169a700a105238e797d99bd64 Mon Sep 17 00:00:00 2001 From: troogs Date: Thu, 1 Oct 2026 18:20:05 +0200 Subject: [PATCH] Move runtime data into config directory --- .env.example | 1 - .../13-config-and-booking-storage.prompt.md | 75 +++++++ Dockerfile | 2 +- README.md | 30 ++- config/activities.json | 92 ++++++++ config/appsettings.json | 4 + config/dates_available.json | 114 ++++++++++ config/dates_booked.json | 27 +++ docker-compose.yml | 5 +- main.go | 205 +++++++++++++----- public/checkout.js | 4 +- public/name.js | 14 +- start.sh | 15 +- 13 files changed, 509 insertions(+), 79 deletions(-) create mode 100644 .github/prompts/13-config-and-booking-storage.prompt.md create mode 100644 config/activities.json create mode 100644 config/appsettings.json create mode 100644 config/dates_available.json create mode 100644 config/dates_booked.json diff --git a/.env.example b/.env.example index a44e2f9..94d5579 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # 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/13-config-and-booking-storage.prompt.md b/.github/prompts/13-config-and-booking-storage.prompt.md new file mode 100644 index 0000000..a1bdeee --- /dev/null +++ b/.github/prompts/13-config-and-booking-storage.prompt.md @@ -0,0 +1,75 @@ +--- +agent: agent +description: Split Date Wizard JSON persistence into a fixed config directory, store complete structured bookings, and skip the name step when no names are configured. +--- + +Implement or review the Date Wizard configuration and booking persistence layout. Preserve the existing Go server, authenticated admin behavior, public booking flow, German user-facing language, and Docker deployment conventions. + +## Fixed config directory + +All JSON data files belong under one project config directory: + +- `config/appsettings.json`: admin password and optional configured names. +- `config/activities.json`: activity definitions. +- `config/dates_available.json`: available date definitions. +- `config/dates_booked.json`: structured booking records. + +The application must use these fixed paths and must not require one environment variable per JSON file. Do not add or retain `ACTIVITIES_FILE`, `AVAILABLE_DATES_FILE`, or `BOOKED_DATES_FILE` configuration requirements. The runtime paths should resolve to `/app/config/...` in the container and `config/...` during local execution. + +Keep legacy read compatibility where practical for existing deployments, but new writes must use the new fixed files and names. The old root-level JSON files should not remain as the active project data source. + +## Docker Compose deployment + +The Compose deployment must expose one persistent volume only: + +```yaml +volumes: + - /docker/data/date:/app/config +``` + +The container must not require individual file mounts or data-file environment variables. Startup must provision missing files from image defaults when the mounted config directory is empty. Preserve the existing `.env` usage for server and SMTP settings. + +The Dockerfile must include the config templates in the image, and the startup script must initialize the four files beneath `/app/config` without overwriting existing host data. + +Document the host initialization and migration process, including migration from the old `booked_dates.json` format. + +## Structured booking records + +When a booking is submitted, record one object per booked date in `config/dates_booked.json` with: + +- `bookedAt`: UTC booking timestamp in a machine-readable format. +- `date`: the selected date. +- `dateType`: the selected date duration/type. +- `name`: the selected person name, or empty when the name step was skipped. +- `activities`: every selected activity with its `name`, `icon`, and `type`. + +Do not persist `customDescription` in the booked activity records. Custom descriptions may remain transient in the submission payload and may still be used for the notification email and generated iCalendar content. + +Avoid duplicate booking records for the same date. Existing date-only booked-date consumers may continue receiving a list of date strings through the public API. Preserve booked-date protection in the admin date editor. + +## Name page shortcut + +When `appsettings.json` contains an empty `names` array: + +- Skip the name-selection page entirely after activity selection. +- Store an empty selected name and navigate directly to checkout. +- Checkout must accept an empty name and display a clear fallback such as `Nicht angegeben`. + +When names are configured, preserve the existing name-selection page and selection behavior. + +## Validation + +Before editing, inspect the current settings model, file paths, booking submission payload, name-page navigation, Dockerfile, Compose file, startup script, and existing prompt conventions. + +After editing: + +1. Run `gofmt` on changed Go files. +2. Run the available Go test/build command. +3. Run JavaScript syntax checks for changed frontend scripts. +4. Validate all config JSON files. +5. Check diagnostics and whitespace errors. +6. Smoke-test the public settings and booked-date APIs with the fixed local config paths. +7. Verify that Compose has one config-directory volume and no per-file data environment variables. +8. Do not overwrite user-edited config data or unrelated fixtures. + +When reporting the result, summarize the fixed paths, Docker contract, booking JSON shape, name-page behavior, migration compatibility, and validation results. diff --git a/Dockerfile b/Dockerfile index 70291ef..966d6b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ WORKDIR /app # Copy binary and static front-end assets COPY --from=builder /app/server /app/server COPY --from=builder /app/public /app/public -COPY --from=builder /app/appsettings.json /app/appsettings.default.json +COPY --from=builder /app/config /app/config.default COPY start.sh /app/start.sh RUN chmod +x /app/start.sh diff --git a/README.md b/README.md index 625bfab..58c6c7a 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,12 @@ Eine spielerische Web-App ("Choose Your Own Adventure") zum Planen gemeinsamer D ```text datetest/ +├── config/ # Persistente JSON-Konfiguration und Buchungen +│ ├── appsettings.json # Admin-Passwort und optionale Namen +│ ├── activities.json # Aktivitäten +│ ├── dates_available.json # Verfügbare Termine +│ └── dates_booked.json # Strukturierte Buchungen ├── public/ # Statische Webseiten & Assets -│ ├── appsettings.json # Konfiguration der Aktivitäten & Termine │ ├── index.html # Startseite ("Willst du mit mir gehen?") │ ├── datetime.html/.js # Datumsauswahl (Kalender) │ ├── activity.html/.js # Aktivitätsauswahl @@ -41,7 +45,6 @@ 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` | @@ -51,18 +54,29 @@ Trage in der `.env` deine Server- und SMTP-Zugangsdaten ein: *(Hinweis: Wenn `SMTP_HOST` oder `TO_EMAIL` leer bleiben, läuft der Server im Testmodus ohne E-Mail-Versand.)* -### 2. Aktivitäten & Termine anpassen (`appsettings.json`) +### 2. Einstellungen und Daten -Passe in `appsettings.json` die zur Auswahl stehenden Aktivitäten, verfügbaren Tage und das Admin-Passwort an: +Die persistenten Daten liegen in getrennten JSON-Dateien: - **`adminPassword`**: Passwort für die Terminverwaltung unter `/admin`. Ändere den Standardwert `change-me`, bevor du die Anwendung veröffentlichst. -- **`activities`**: Liste von Objekten mit `name`, `icon` (Unicode Emoji), `type` (`short` oder `long`) und optional `disabled` (`true` blendet die Aktivität vorübergehend aus). -- **`availableDates`**: Liste von verfügbaren Tagen mit `date` (`YYYY-MM-DD`) und `type` (`short` oder `long`). -- **`names`**: Optional vordefinierte Namen zur Auswahl (ist die Liste leer, wird ein Standard-Fallback angeboten). +- **`activities.json`**: Liste von Objekten mit `name`, `icon` (Unicode Emoji), `type` (`short`, `long` oder `custom`) und optional `disabled` (`true` blendet die Aktivität vorübergehend aus). +- **`dates_available.json`**: Liste von verfügbaren Tagen mit `date` (`YYYY-MM-DD`) und `type` (`short` oder `long`). +- **`dates_booked.json`**: Strukturierte Buchungen mit `bookedAt`, `date`, `dateType`, `name` und den ausgewählten Aktivitäten inklusive Name, Typ und Icon. +- **`names`**: Optional vordefinierte Namen zur Auswahl (ist die Liste leer, wird die Namensseite übersprungen). Die Termin- und Aktivitätenlisten lassen sich über die passwortgeschützte Verwaltung unter `http://localhost:8080/admin` bearbeiten. Das Passwort bleibt dabei auf dem Server und wird nicht an Besucher ausgeliefert. -Bei Docker Compose werden `appsettings.json` und `booked_dates.json` dauerhaft unter `/docker/data/date` gespeichert. Beim ersten Start legt der Container die Einstellungen dort aus der im Image enthaltenen Vorlage an. Ändere danach den Standardwert `change-me` in `/docker/data/date/appsettings.json` oder übernimm vor dem ersten Start eine vorbereitete Konfigurationsdatei. +Bei Docker Compose wird `/docker/data/date` als ein Volume nach `/app/config` eingebunden. Die vier JSON-Dateien müssen dort vor dem ersten Start vorhanden sein. Du kannst sie aus dem Projektordner initialisieren: + +```bash +mkdir -p /docker/data/date +cp config/appsettings.json /docker/data/date/appsettings.json +cp config/activities.json /docker/data/date/activities.json +cp config/dates_available.json /docker/data/date/dates_available.json +cp config/dates_booked.json /docker/data/date/dates_booked.json +``` + +Wenn bisher `/docker/data/date/booked_dates.json` verwendet wurde, übernimm die alten Buchungsdaten zuerst in `dates_booked.json`. Das neue Format speichert Buchungen als Objekte statt als reine Datumsstrings. --- diff --git a/config/activities.json b/config/activities.json new file mode 100644 index 0000000..bec039f --- /dev/null +++ b/config/activities.json @@ -0,0 +1,92 @@ +[ + { + "name": "Cocktailabend zu zweit", + "icon": "🚲", + "type": "long" + }, + { + "name": "Massage zu Hause", + "icon": "💆", + "type": "short" + }, + { + "name": "Ausgedehnte Wanderung", + "icon": "🛋️", + "type": "long" + }, + { + "name": "Kinofilm", + "icon": "🎬", + "type": "short" + }, + { + "name": "Sushi selber machen", + "icon": "🍣", + "type": "short" + }, + { + "name": "Untersetzer aus Ton basteln", + "icon": "🪨", + "type": "long" + }, + { + "name": "Freizeitpark", + "icon": "🎢", + "type": "long" + }, + { + "name": "Spaziergang im Wald", + "icon": "🌲", + "type": "short" + }, + { + "name": "Kirschbier Tasting", + "icon": "🍒", + "type": "short" + }, + { + "name": "Nudeln selber machen", + "icon": "🍜", + "type": "long" + }, + { + "name": "Chillen im Freibad", + "icon": "🏊", + "type": "short" + }, + { + "name": "Film zu Hause + Essen bestellen", + "icon": "🛋️", + "type": "long" + }, + { + "name": "Squash spielen", + "icon": "🏸", + "type": "short" + }, + { + "name": "Essen im Namaste", + "icon": "🍛", + "type": "short" + }, + { + "name": "Museum für Gegenwartskunst", + "icon": "🖼️", + "type": "short" + }, + { + "name": "Playstation Co-Op", + "icon": "🎮", + "type": "short" + }, + { + "name": "l", + "icon": "✏️", + "type": "custom" + }, + { + "name": "asdss", + "icon": "📚", + "type": "short" + } +] \ No newline at end of file diff --git a/config/appsettings.json b/config/appsettings.json new file mode 100644 index 0000000..4e866f2 --- /dev/null +++ b/config/appsettings.json @@ -0,0 +1,4 @@ +{ + "names": [], + "adminPassword": "change-me" +} diff --git a/config/dates_available.json b/config/dates_available.json new file mode 100644 index 0000000..57ae781 --- /dev/null +++ b/config/dates_available.json @@ -0,0 +1,114 @@ +[ + { + "date": "2026-08-18", + "type": "short" + }, + { + "date": "2026-08-20", + "type": "short" + }, + { + "date": "2026-08-22", + "type": "long" + }, + { + "date": "2026-08-23", + "type": "long" + }, + { + "date": "2026-08-24", + "type": "short" + }, + { + "date": "2026-08-26", + "type": "short" + }, + { + "date": "2026-08-27", + "type": "short" + }, + { + "date": "2026-08-28", + "type": "short" + }, + { + "date": "2026-08-29", + "type": "long" + }, + { + "date": "2026-08-31", + "type": "short" + }, + { + "date": "2026-09-01", + "type": "short" + }, + { + "date": "2026-09-07", + "type": "short" + }, + { + "date": "2026-09-08", + "type": "short" + }, + { + "date": "2026-09-14", + "type": "short" + }, + { + "date": "2026-09-15", + "type": "short" + }, + { + "date": "2026-09-17", + "type": "short" + }, + { + "date": "2026-09-18", + "type": "short" + }, + { + "date": "2026-09-25", + "type": "short" + }, + { + "date": "2026-09-26", + "type": "long" + }, + { + "date": "2026-09-27", + "type": "short" + }, + { + "date": "2026-09-28", + "type": "short" + }, + { + "date": "2026-09-29", + "type": "short" + }, + { + "date": "2026-09-30", + "type": "short" + }, + { + "date": "2026-10-03", + "type": "long" + }, + { + "date": "2026-10-04", + "type": "long" + }, + { + "date": "2026-10-10", + "type": "long" + }, + { + "date": "2026-10-21", + "type": "short" + }, + { + "date": "2026-10-22", + "type": "long" + } +] diff --git a/config/dates_booked.json b/config/dates_booked.json new file mode 100644 index 0000000..ae78a3c --- /dev/null +++ b/config/dates_booked.json @@ -0,0 +1,27 @@ +[ + { + "bookedAt": "", + "date": "2026-10-10", + "dateType": "long", + "name": "", + "activities": [] + }, + { + "bookedAt": "2026-10-01T16:10:50Z", + "date": "2026-10-22", + "dateType": "long", + "name": "Du weißt, wer ich bin", + "activities": [ + { + "name": "Chillen im Freibad", + "icon": "🏊", + "type": "short" + }, + { + "name": "Sushi selber machen", + "icon": "🍣", + "type": "short" + } + ] + } +] diff --git a/docker-compose.yml b/docker-compose.yml index a50aef1..8f07f33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,11 +9,8 @@ services: - proxy env_file: - .env - environment: - SETTINGS_FILE: /data/appsettings.json - BOOKED_DATES_FILE: /data/booked_dates.json volumes: - - /docker/data/date:/data + - /docker/data/date:/app/config labels: - "traefik.enable=true" - "traefik.http.routers.date-wizard.rule=Host(`date.allen.beging.de`) || Host(`date.beging.de`)" diff --git a/main.go b/main.go index 2a3043c..c98a2cc 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "net/http" "net/smtp" "os" + "path/filepath" "sort" "strings" "sync" @@ -44,10 +45,24 @@ type AvailableDate struct { } type AppSettings struct { - Activities []Activity `json:"activities"` - AvailableDates []AvailableDate `json:"availableDates"` Names []string `json:"names"` AdminPassword string `json:"adminPassword"` + Activities []Activity `json:"-"` + AvailableDates []AvailableDate `json:"-"` +} + +type Booking struct { + BookedAt string `json:"bookedAt"` + Date string `json:"date"` + DateType string `json:"dateType"` + Name string `json:"name"` + Activities []BookingActivity `json:"activities"` +} + +type BookingActivity struct { + Name string `json:"name"` + Icon string `json:"icon"` + Type string `json:"type"` } type PublicSettings struct { @@ -61,6 +76,7 @@ type passwordRequest struct { } var settingsMu sync.Mutex +var bookingsMu sync.Mutex var sessionsMu sync.Mutex var adminSessions = make(map[string]time.Time) @@ -486,11 +502,7 @@ func handleAdminBulkDates(w http.ResponseWriter, r *http.Request) { } func getSettingsFilePath() string { - path := os.Getenv("SETTINGS_FILE") - if path == "" { - path = "appsettings.json" - } - return path + return filepath.Join("config", "appsettings.json") } func readSettings() (AppSettings, error) { @@ -508,9 +520,42 @@ func readSettingsFile() (AppSettings, error) { if err := json.Unmarshal(data, &settings); err != nil { return settings, err } + var legacy struct { + Activities []Activity `json:"activities"` + AvailableDates []AvailableDate `json:"availableDates"` + } + if err := json.Unmarshal(data, &legacy); err != nil { + return settings, err + } + settings.Activities = legacy.Activities + settings.AvailableDates = legacy.AvailableDates + if activities, found, err := readOptionalJSON(getActivitiesFilePath(), &[]Activity{}); err != nil { + return settings, err + } else if found { + settings.Activities = *activities.(*[]Activity) + } + if dates, found, err := readOptionalJSON(getAvailableDatesFilePath(), &[]AvailableDate{}); err != nil { + return settings, err + } else if found { + settings.AvailableDates = *dates.(*[]AvailableDate) + } return settings, nil } +func readOptionalJSON(path string, target interface{}) (interface{}, bool, error) { + data, err := os.ReadFile(path) + if os.IsNotExist(err) { + return target, false, nil + } + if err != nil { + return target, false, err + } + if err := json.Unmarshal(data, target); err != nil { + return target, false, err + } + return target, true, nil +} + func updateAvailableDates(update func([]AvailableDate) ([]AvailableDate, error)) error { settingsMu.Lock() defer settingsMu.Unlock() @@ -526,15 +571,7 @@ func updateAvailableDates(update func([]AvailableDate) ([]AvailableDate, error)) sort.Slice(dates, func(i, j int) bool { return dates[i].Date < dates[j].Date }) settings.AvailableDates = dates - data, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return err - } - temporaryPath := getSettingsFilePath() + ".tmp" - if err := os.WriteFile(temporaryPath, data, 0600); err != nil { - return err - } - return os.Rename(temporaryPath, getSettingsFilePath()) + return writeJSONFile(getAvailableDatesFilePath(), dates) } func updateActivities(update func([]Activity) ([]Activity, error)) error { @@ -551,15 +588,27 @@ func updateActivities(update func([]Activity) ([]Activity, error)) error { } settings.Activities = activities - data, err := json.MarshalIndent(settings, "", " ") + return writeJSONFile(getActivitiesFilePath(), activities) +} + +func writeJSONFile(path string, value interface{}) error { + data, err := json.MarshalIndent(value, "", " ") if err != nil { return err } - temporaryPath := getSettingsFilePath() + ".tmp" + temporaryPath := path + ".tmp" if err := os.WriteFile(temporaryPath, data, 0600); err != nil { return err } - return os.Rename(temporaryPath, getSettingsFilePath()) + return os.Rename(temporaryPath, path) +} + +func getActivitiesFilePath() string { + return filepath.Join("config", "activities.json") +} + +func getAvailableDatesFilePath() string { + return filepath.Join("config", "dates_available.json") } func validateActivity(activity Activity) error { @@ -577,11 +626,22 @@ func validateActivity(activity Activity) error { func enabledActivities(activities []Activity) []Activity { enabled := make([]Activity, 0, len(activities)) + var customActivity *Activity for _, activity := range activities { + if activity.Type == "custom" { + if !activity.Disabled { + custom := activity + customActivity = &custom + } + continue + } if !activity.Disabled { enabled = append(enabled, activity) } } + if customActivity != nil { + enabled = append(enabled, *customActivity) + } return enabled } @@ -665,7 +725,7 @@ 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 { + if err := recordBooking(payload); err != nil { log.Printf("Error recording booked date: %v", err) } @@ -706,65 +766,98 @@ func handleBookedDates(w http.ResponseWriter, r *http.Request) { } func getBookedDatesFilePath() string { - path := os.Getenv("BOOKED_DATES_FILE") - if path == "" { - path = "booked_dates.json" - } - return path + return filepath.Join("config", "dates_booked.json") } func getBookedDates() ([]string, error) { - filePath := getBookedDatesFilePath() - file, err := os.Open(filePath) + bookings, err := getBookings() if err != nil { - if os.IsNotExist(err) { - return []string{}, nil - } return nil, err } - defer file.Close() - - var rawDates []string - if err := json.NewDecoder(file).Decode(&rawDates); err != nil { - return []string{}, nil - } - - dates := make([]string, 0, len(rawDates)) - for _, d := range rawDates { - trimmed := strings.TrimSpace(d) - if trimmed != "" { + dates := make([]string, 0, len(bookings)) + for _, booking := range bookings { + if trimmed := strings.TrimSpace(booking.Date); trimmed != "" { dates = append(dates, trimmed) } } return dates, nil } -func recordBookedDate(dateStr string) error { - trimmedDate := strings.TrimSpace(dateStr) - if trimmedDate == "" { +func getBookings() ([]Booking, error) { + bookingsMu.Lock() + defer bookingsMu.Unlock() + return readBookings() +} + +func readBookings() ([]Booking, error) { + filePath := getBookedDatesFilePath() + data, err := os.ReadFile(filePath) + if os.IsNotExist(err) { + legacyPath := os.Getenv("LEGACY_BOOKED_DATES_FILE") + if legacyPath == "" { + legacyPath = filepath.Join("config", "booked_dates.json") + } + data, err = os.ReadFile(legacyPath) + if os.IsNotExist(err) { + return []Booking{}, nil + } + } + if err != nil { + return nil, err + } + var bookings []Booking + if err := json.Unmarshal(data, &bookings); err == nil { + return bookings, nil + } + + var legacyDates []string + if err := json.Unmarshal(data, &legacyDates); err != nil { + return nil, err + } + bookings = make([]Booking, 0, len(legacyDates)) + for _, date := range legacyDates { + if trimmed := strings.TrimSpace(date); trimmed != "" { + bookings = append(bookings, Booking{Date: trimmed}) + } + } + return bookings, nil +} + +func recordBooking(payload SurveyPayload) error { + date := strings.TrimSpace(payload.Date) + if date == "" { return nil } - dates, err := getBookedDates() + bookingsMu.Lock() + defer bookingsMu.Unlock() + bookings, err := readBookings() if err != nil { - dates = []string{} + bookings = []Booking{} } - - for _, d := range dates { - if d == trimmedDate { + for _, booking := range bookings { + if booking.Date == date { return nil } } - dates = append(dates, trimmedDate) - - filePath := getBookedDatesFilePath() - data, err := json.MarshalIndent(dates, "", " ") - if err != nil { - return err + bookingActivities := make([]BookingActivity, 0, len(payload.Activities)) + for _, activity := range payload.Activities { + bookingActivities = append(bookingActivities, BookingActivity{ + Name: activity.Name, + Icon: activity.Icon, + Type: activity.Type, + }) } - return os.WriteFile(filePath, data, 0644) + bookings = append(bookings, Booking{ + BookedAt: time.Now().UTC().Format(time.RFC3339), + Date: date, + DateType: payload.DateType, + Name: payload.Name, + Activities: bookingActivities, + }) + return writeJSONFile(getBookedDatesFilePath(), bookings) } func enableCORS(w http.ResponseWriter, r *http.Request) bool { diff --git a/public/checkout.js b/public/checkout.js index 82c89b4..f96242b 100644 --- a/public/checkout.js +++ b/public/checkout.js @@ -19,13 +19,13 @@ const selectedDate = localStorage.getItem("selectedDate"); const selectedName = localStorage.getItem("selectedName"); - if (!selectedActivity || !selectedDate || !selectedName) { + if (!selectedActivity || !selectedDate) { window.location.href = "index.html"; return; } summaryActivity.textContent = selectedActivity; - summaryName.textContent = selectedName; + summaryName.textContent = selectedName || "Nicht angegeben"; const dateObj = new Date(`${selectedDate}T00:00:00`); const formattedDate = dateObj.toLocaleDateString("de-DE", { diff --git a/public/name.js b/public/name.js index 23cd2f5..b2b1dce 100644 --- a/public/name.js +++ b/public/name.js @@ -30,15 +30,21 @@ fetch("/api/settings", { cache: "no-store" }) .then((response) => response.json()) - .then((settings) => renderNames(settings.names || [])) + .then((settings) => { + const names = settings.names || []; + if (names.length === 0) { + localStorage.setItem("selectedName", ""); + window.location.href = "checkout.html"; + return; + } + renderNames(names); + }) .catch(() => { nameList.textContent = "Namen konnten nicht geladen werden."; }); function renderNames(names) { - const options = names.length > 0 ? names : ["Du weißt, wer ich bin"]; - - options.forEach((name) => { + names.forEach((name) => { const card = document.createElement("button"); card.type = "button"; card.className = "name-card"; diff --git a/start.sh b/start.sh index 1b0cd79..0f86048 100644 --- a/start.sh +++ b/start.sh @@ -1,11 +1,20 @@ #!/bin/sh set -eu -settings_file="${SETTINGS_FILE:-/app/appsettings.json}" +config_dir="/app/config" +settings_file="$config_dir/appsettings.json" if [ ! -f "$settings_file" ]; then - mkdir -p "$(dirname "$settings_file")" - cp /app/appsettings.default.json "$settings_file" + mkdir -p "$config_dir" + cp /app/config.default/appsettings.json "$settings_file" fi +activities_file="$config_dir/activities.json" +available_dates_file="$config_dir/dates_available.json" +booked_dates_file="$config_dir/dates_booked.json" + +if [ ! -f "$activities_file" ] && ! grep -q '"activities"' "$settings_file"; then cp /app/config.default/activities.json "$activities_file"; fi +if [ ! -f "$available_dates_file" ] && ! grep -q '"availableDates"' "$settings_file"; then cp /app/config.default/dates_available.json "$available_dates_file"; fi +if [ ! -f "$booked_dates_file" ] && [ ! -f "$config_dir/booked_dates.json" ]; then cp /app/config.default/dates_booked.json "$booked_dates_file"; fi + exec /app/server \ No newline at end of file