fix edit requests

This commit is contained in:
2026-02-27 16:07:46 +00:00
parent 72124bac98
commit e00740a8c9
4 changed files with 38 additions and 42 deletions
+1
View File
@@ -33,6 +33,7 @@ require (
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/kovidgoyal/go-parallel v1.1.1 // indirect
github.com/kovidgoyal/go-shm v1.0.0 // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/image v0.36.0 // indirect
+2
View File
@@ -75,6 +75,8 @@ github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVf
github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/nyaruka/phonenumbers v1.6.10 h1:kGTxTzd320dUamRB/MPeZSIwKNLn4vHlysOt5Cp8uoU=
github.com/nyaruka/phonenumbers v1.6.10/go.mod h1:IUu45lj2bSeYXQuxDyyuzOrdV10tyRa1YSsfH8EKN5c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+33 -21
View File
@@ -15,6 +15,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/lib/pq"
)
// UserCancelBookingHandler allows an authenticated user to cancel a booking they own.
@@ -726,8 +727,17 @@ func DeleteEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Use transaction to delete edit request and associated admin notification
tx, err := db.DB.Begin(r.Context())
if err != nil {
log.Printf("Failed to start transaction: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer tx.Rollback(r.Context())
// Delete the edit request for this booking
res, err := db.DB.Exec(r.Context(), `
res, err := tx.Exec(r.Context(), `
DELETE FROM booking_edit_requests
WHERE booking_id = $1 AND requested_by = $2
`, bookingID, userID)
@@ -743,6 +753,23 @@ func DeleteEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Delete the admin notification for this edit request
_, err = tx.Exec(r.Context(), `
DELETE FROM admin_notifications
WHERE booking_id = $1 AND reason = 'edit_request' AND user_id = $2
`, bookingID, userID)
if err != nil {
log.Printf("Failed to delete admin notification for booking %s: %v", bookingID, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if err := tx.Commit(r.Context()); err != nil {
log.Printf("Failed to commit delete edit request: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
@@ -951,14 +978,14 @@ func AdminListEditRequestsHandler(w http.ResponseWriter, r *http.Request) {
var origStartTime time.Time
var bookingStatus string
var userName string
var newServicesJSON []byte
var newServices []string
err := rows.Scan(
&req.ID,
&req.BookingID,
&req.RequestedBy,
&req.NewStartTime,
&newServicesJSON,
pq.Array(&newServices),
&req.Notes,
&req.HasOverrides,
&req.UpdatedAt,
@@ -971,12 +998,7 @@ func AdminListEditRequestsHandler(w http.ResponseWriter, r *http.Request) {
continue
}
// Unmarshal JSON services
if len(newServicesJSON) > 0 {
if err := json.Unmarshal(newServicesJSON, &req.NewServices); err != nil {
log.Printf("Failed to unmarshal new_services: %v", err)
}
}
req.NewServices = newServices
req.Booking = &Booking{
ID: req.BookingID,
@@ -1021,14 +1043,14 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
// Get the edit request
var bookingID string
var newStartTime *time.Time
var newServicesJSON []byte
var newServices []string
var notes *string
var hasOverrides bool
err = tx.QueryRow(r.Context(), `
SELECT booking_id, new_start_time, new_services, notes, has_overrides
FROM booking_edit_requests
WHERE id = $1
`, requestID).Scan(&bookingID, &newStartTime, &newServicesJSON, &notes, &hasOverrides)
`, requestID).Scan(&bookingID, &newStartTime, pq.Array(&newServices), &notes, &hasOverrides)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "Edit request not found", http.StatusNotFound)
@@ -1039,16 +1061,6 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Parse new_services
var newServices []string
if len(newServicesJSON) > 0 {
if err := json.Unmarshal(newServicesJSON, &newServices); err != nil {
log.Printf("Failed to unmarshal new_services: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
}
// If new_services provided and has_overrides is true, block with error
if len(newServices) > 0 && hasOverrides {
http.Error(w, "Cannot change services on a booking that has overrides. Please update services manually.", http.StatusForbidden)
+2 -21
View File
@@ -241,37 +241,18 @@ CREATE TABLE booking_edit_requests (
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
requested_by CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
new_start_time TIMESTAMPTZ,
new_services JSONB DEFAULT '[]'::jsonb, -- Array of service IDs to replace booking_services
new_services CHAR(12)[] DEFAULT '{}', -- Array of service IDs to replace booking_services
notes TEXT,
has_overrides BOOLEAN NOT NULL DEFAULT FALSE, -- If TRUE, cannot change services, use existing overrides for duration
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_at_least_one_field CHECK (
new_start_time IS NOT NULL OR
new_services IS NOT NULL OR
array_length(new_services, 1) IS NOT NULL OR
notes IS NOT NULL
)
);
CREATE INDEX idx_booking_edit_requests_booking ON booking_edit_requests(booking_id);
id CHAR(12) PRIMARY KEY DEFAULT generate_booking_id(),
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
requested_by CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
new_start_time TIMESTAMPTZ,
new_services JSONB DEFAULT '[]'::jsonb,
new_notes TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
admin_notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT chk_at_least_one_field CHECK (
new_start_time IS NOT NULL OR
new_services IS NOT NULL OR
new_notes IS NOT NULL
)
);
CREATE INDEX idx_booking_edit_requests_booking ON booking_edit_requests(booking_id);
CREATE INDEX idx_booking_edit_requests_status ON booking_edit_requests(status);
CREATE TABLE user_referrals (
referrer_id CHAR(12) NOT NULL REFERENCES users(id) ON DELETE CASCADE,