Move runtime data into config directory
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user