diff --git a/internal/database/db.go b/internal/database/db.go index f83ad46..3bdff47 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -287,6 +287,16 @@ func UpdateEventStatus(db *sql.DB, id, status string) error { return err } +// DeleteEvent removes an event and all its expenses from the database. +func DeleteEvent(db *sql.DB, id string) error { + _, err := db.Exec("DELETE FROM events WHERE id = ?", id) + if err != nil { + log.Printf("ERROR [%s] database: DeleteEvent(%s): %v", + time.Now().Format(time.RFC3339), id, err) + } + return err +} + // UpdateEvent updates the base currency and exchange rate of an existing event. func UpdateEvent(db *sql.DB, id, baseCurrency string, exchangeRate float64) error { _, err := db.Exec("UPDATE events SET base_currency = ?, exchange_rate = ? WHERE id = ?", baseCurrency, exchangeRate, id) diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index c5c512f..4e4b993 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -315,13 +315,16 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { } func collectOTP(r *http.Request) string { - var b strings.Builder - for i := 0; i < 6; i++ { - digit := r.FormValue(fmt.Sprintf("digit_%d", i)) - if digit == "" { - return "" + code := r.FormValue("otp_code") + // Strip any non-digit characters (paste may include spaces/dashes). + code = strings.Map(func(r rune) rune { + if r >= '0' && r <= '9' { + return r } - b.WriteString(digit) + return -1 + }, code) + if len(code) != 6 { + return "" } - return b.String() + return code } diff --git a/internal/handlers/events.go b/internal/handlers/events.go index aae6e6b..47d4c55 100644 --- a/internal/handlers/events.go +++ b/internal/handlers/events.go @@ -360,3 +360,24 @@ func (h *EventHandler) UpdateEvent(w http.ResponseWriter, r *http.Request) { w.Header().Set("HX-Redirect", "/dashboard") w.WriteHeader(http.StatusOK) } + +// --------------------------------------------------------------------------- +// DELETE /events/{id} — DeleteEvent +// --------------------------------------------------------------------------- + +// DeleteEvent removes an event and its expenses after ownership verification. +func (h *EventHandler) DeleteEvent(w http.ResponseWriter, r *http.Request) { + eventID := chi.URLParam(r, "id") + event, err := database.GetEventByID(h.DB, eventID) + if err != nil || event == nil || event.UserID != getUserID(r) { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + if err := database.DeleteEvent(h.DB, eventID); err != nil { + log.Printf("ERROR [%s] handlers: DeleteEvent(%s): %v", time.Now().Format(time.RFC3339), eventID, err) + http.Error(w, "Failed to delete event", http.StatusInternalServerError) + return + } + w.Header().Set("HX-Redirect", "/dashboard") + w.WriteHeader(http.StatusOK) +} diff --git a/internal/handlers/templates.go b/internal/handlers/templates.go index a292ae7..d699582 100644 --- a/internal/handlers/templates.go +++ b/internal/handlers/templates.go @@ -49,30 +49,15 @@ func loadTemplates() { log.Printf("Loaded %d templates", len(templates)) } -// otpFormHTML is the inline OTP form template fragment with auto-advance. +// otpFormHTML is the inline OTP form template fragment with single 6-digit field. const otpFormHTML = `