package main import ( "encoding/json" "net/http" "strconv" ) func (app *App) handleGetSettings(w http.ResponseWriter, r *http.Request) { cfg := app.loadSMTPConfig() baseURL := app.baseURL if baseURL == "" { app.db.QueryRow(`SELECT value FROM config WHERE key = 'base_url'`).Scan(&baseURL) } pass := "" if cfg.Password != "" { pass = "***" } var noteLabel, noteRequired, signupsOpen string app.db.QueryRow(`SELECT value FROM config WHERE key = 'volunteer_note_label'`).Scan(¬eLabel) app.db.QueryRow(`SELECT value FROM config WHERE key = 'volunteer_note_required'`).Scan(¬eRequired) app.db.QueryRow(`SELECT value FROM config WHERE key = 'shift_signups_open'`).Scan(&signupsOpen) if noteLabel == "" { noteLabel = "Additional note" } writeJSON(w, map[string]any{ "smtp_host": cfg.Host, "smtp_port": cfg.Port, "smtp_user": cfg.User, "smtp_password": pass, "smtp_from": cfg.From, "smtp_from_name": cfg.FromName, "base_url": baseURL, "volunteer_note_label": noteLabel, "volunteer_note_required": noteRequired == "true", "shift_signups_open": signupsOpen == "true", }) } func (app *App) handleUpdateSettings(w http.ResponseWriter, r *http.Request) { var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, "invalid request", http.StatusBadRequest) return } keys := []string{"smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_from", "smtp_from_name", "base_url", "volunteer_note_label", "volunteer_note_required"} for _, k := range keys { v, ok := body[k] if !ok { continue } var val string switch vv := v.(type) { case string: if k == "smtp_password" && vv == "" { continue } val = vv case float64: val = strconv.Itoa(int(vv)) case bool: if vv { val = "true" } else { val = "false" } default: continue } app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)`, k, val) } app.handleGetSettings(w, r) } func (app *App) handleResetAttendees(w http.ResponseWriter, r *http.Request) { ts := now() result, err := app.db.Exec(`UPDATE attendees SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleResetTickets(w http.ResponseWriter, r *http.Request) { ts := now() result, err := app.db.Exec(`UPDATE tickets SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleResetVolunteers(w http.ResponseWriter, r *http.Request) { ts := now() result, err := app.db.Exec(`UPDATE volunteers SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleResetShifts(w http.ResponseWriter, r *http.Request) { ts := now() result, err := app.db.Exec(`UPDATE shifts SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() // Also soft-delete orphaned volunteer_shifts app.db.Exec(`UPDATE volunteer_shifts SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL AND shift_id IN (SELECT id FROM shifts WHERE deleted_at IS NOT NULL)`, ts, ts) writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleResetDepartments(w http.ResponseWriter, r *http.Request) { ts := now() // Soft-delete shifts in these departments first (so sync picks them up) app.db.Exec(`UPDATE volunteer_shifts SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL AND shift_id IN (SELECT id FROM shifts WHERE department_id IN (SELECT id FROM departments WHERE deleted_at IS NULL))`, ts, ts) app.db.Exec(`UPDATE shifts SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL AND department_id IN (SELECT id FROM departments WHERE deleted_at IS NULL)`, ts, ts) result, err := app.db.Exec(`UPDATE departments SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleResetVolunteerShifts(w http.ResponseWriter, r *http.Request) { ts := now() result, err := app.db.Exec(`UPDATE volunteer_shifts SET deleted_at=?, updated_at=? WHERE deleted_at IS NULL`, ts, ts) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } n, _ := result.RowsAffected() writeJSON(w, map[string]any{"deleted": n}) } func (app *App) handleTestEmail(w http.ResponseWriter, r *http.Request) { var body struct { To string `json:"to"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.To == "" { writeError(w, "to email address required", http.StatusBadRequest) return } cfg := app.loadSMTPConfig() if err := sendEmail(cfg, body.To, "Turnpike test email", "This is a test email from your Turnpike instance. SMTP is configured correctly."); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]any{"ok": true}) }