diff --git a/backend/handlers/admin/custom_services.go b/backend/handlers/admin/custom_services.go index a6a5ae8..cc469aa 100644 --- a/backend/handlers/admin/custom_services.go +++ b/backend/handlers/admin/custom_services.go @@ -105,7 +105,9 @@ func GetCustomServices(w http.ResponseWriter, r *http.Request) { if services == nil { services = []CustomService{} } - _ = json.NewEncoder(w).Encode(services) + if err := json.NewEncoder(w).Encode(services); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -192,12 +194,14 @@ func GetCustomServices(w http.ResponseWriter, r *http.Request) { if services == nil { services = []CustomService{} } - _ = json.NewEncoder(w).Encode(CustomServiceListResponse{ + if err := json.NewEncoder(w).Encode(CustomServiceListResponse{ Services: services, Total: total, PerPage: perPage, NextCursor: nextCursor, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func CreateCustomService(w http.ResponseWriter, r *http.Request) { @@ -266,7 +270,9 @@ func CreateCustomService(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(cs) + if err := json.NewEncoder(w).Encode(cs); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetCustomService(w http.ResponseWriter, r *http.Request) { @@ -306,7 +312,9 @@ func GetCustomService(w http.ResponseWriter, r *http.Request) { cs.LastUsedAt = &lastUsedAt.Time } - _ = json.NewEncoder(w).Encode(cs) + if err := json.NewEncoder(w).Encode(cs); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func UpdateCustomService(w http.ResponseWriter, r *http.Request) { @@ -387,10 +395,10 @@ func UpdateCustomService(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() result, err := tx.Exec(r.Context(), query, args...) if err != nil { @@ -407,7 +415,9 @@ func UpdateCustomService(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{"message": "Custom service updated"}) + if err := json.NewEncoder(w).Encode(map[string]string{"message": "Custom service updated"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func PromoteCustomService(w http.ResponseWriter, r *http.Request) { @@ -423,10 +433,10 @@ func PromoteCustomService(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var name, desc, notes sql.NullString var price float64 @@ -496,11 +506,13 @@ func PromoteCustomService(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "message": "Custom service promoted to regular service", "new_service_id": newServiceID, "custom_service_id": id, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func DeleteCustomService(w http.ResponseWriter, r *http.Request) { @@ -531,10 +543,10 @@ func DeleteCustomService(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() result, err := tx.Exec(r.Context(), `DELETE FROM custom_services WHERE id = $1`, id) if err != nil { @@ -551,7 +563,9 @@ func DeleteCustomService(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{"message": "Custom service deleted"}) + if err := json.NewEncoder(w).Encode(map[string]string{"message": "Custom service deleted"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func joinStrings(strs []string, sep string) string { diff --git a/backend/handlers/admin/discount_campaigns.go b/backend/handlers/admin/discount_campaigns.go index 69cc411..60a22dc 100644 --- a/backend/handlers/admin/discount_campaigns.go +++ b/backend/handlers/admin/discount_campaigns.go @@ -282,10 +282,10 @@ func CreateDiscountCampaign(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Insert new campaign query := ` @@ -520,10 +520,10 @@ func UpdateDiscountCampaign(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), query, args...) if err != nil { @@ -648,10 +648,10 @@ func DeleteDiscountCampaign(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() result, err := tx.Exec(r.Context(), query, campaignID) if err != nil { @@ -671,10 +671,12 @@ func DeleteDiscountCampaign(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "message": "Campaign deleted successfully", "id": campaignID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // GetCampaignStats handles GET /api/admin/discount-campaigns/{id}/stats diff --git a/backend/handlers/admin/patch_tests.go b/backend/handlers/admin/patch_tests.go index a46b430..d6d4814 100644 --- a/backend/handlers/admin/patch_tests.go +++ b/backend/handlers/admin/patch_tests.go @@ -75,7 +75,9 @@ func GetPatchTests(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(patchTests) + if err := json.NewEncoder(w).Encode(patchTests); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // CreatePatchTest handles POST /api/admin/patch-tests @@ -104,10 +106,10 @@ func CreatePatchTest(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var id string err = tx.QueryRow(r.Context(), query, req.Name, req.Description, req.NoticeDurationHours, req.ExpiryMonths, req.ServiceIDs).Scan(&id) @@ -122,7 +124,9 @@ func CreatePatchTest(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(map[string]string{"id": id}) + if err := json.NewEncoder(w).Encode(map[string]string{"id": id}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // UpdatePatchTest handles PUT /api/admin/patch-tests/{id} @@ -185,10 +189,10 @@ func UpdatePatchTest(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), query, args...) if err != nil { @@ -218,10 +222,10 @@ func DeletePatchTest(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), "DELETE FROM patch_tests WHERE id = $1", id) if err != nil { diff --git a/backend/handlers/admin/settings.go b/backend/handlers/admin/settings.go index 6823ad6..266cdda 100644 --- a/backend/handlers/admin/settings.go +++ b/backend/handlers/admin/settings.go @@ -69,7 +69,9 @@ func GetPublicBusinessInfo(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(info) + if err := json.NewEncoder(w).Encode(info); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetBusinessSettings(w http.ResponseWriter, r *http.Request) { @@ -91,7 +93,9 @@ func GetBusinessSettings(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(s) + if err := json.NewEncoder(w).Encode(s); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type UpdateBusinessSettingsRequest struct { @@ -246,10 +250,10 @@ func UpdateBusinessSettings(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), query.String(), args...) if err != nil { diff --git a/backend/handlers/auth/local.go b/backend/handlers/auth/local.go index c465e02..3c54b12 100644 --- a/backend/handlers/auth/local.go +++ b/backend/handlers/auth/local.go @@ -222,10 +222,10 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() now := clock.Now() @@ -384,10 +384,10 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() err = tx.QueryRow(r.Context(), ` UPDATE users @@ -433,10 +433,10 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), `UPDATE users SET failed_attempts = 0, locked_until = NULL, last_login_at = NOW() WHERE id = $1`, userID) if err != nil { @@ -458,10 +458,12 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(auth.AuthResponse{ + if err := json.NewEncoder(w).Encode(auth.AuthResponse{ Token: tokenString, JTI: jti, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // POST /api/refresh-token (requires auth middleware) @@ -501,10 +503,12 @@ func RefreshTokenHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(auth.AuthResponse{ + if err := json.NewEncoder(w).Encode(auth.AuthResponse{ Token: newToken, JTI: jti, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // POST /api/logout (requires auth middleware) @@ -522,7 +526,9 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]bool{"success": true}) + if err := json.NewEncoder(w).Encode(map[string]bool{"success": true}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type VerificationCodeRequest struct { @@ -562,7 +568,9 @@ func GenerateVerificationCodeHandler(w http.ResponseWriter, r *http.Request) { ).Scan(&userID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - _ = json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "If the email exists, a verification code will be sent"}) + if err := json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "If the email exists, a verification code will be sent"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to look up user: %v", err) @@ -583,7 +591,9 @@ func GenerateVerificationCodeHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "Verification code generated"}) + if err := json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "Verification code generated"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func VerifyCodeHandler(w http.ResponseWriter, r *http.Request) { @@ -646,10 +656,10 @@ func VerifyCodeHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), `UPDATE verification_codes SET used_at = NOW() WHERE code = $1`, @@ -679,7 +689,7 @@ func VerifyCodeHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "Email verified successfully"}) + if err := json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "Email verified successfully"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } - - diff --git a/backend/handlers/bookings/bookings.go b/backend/handlers/bookings/bookings.go index 6521e65..dcde1b1 100644 --- a/backend/handlers/bookings/bookings.go +++ b/backend/handlers/bookings/bookings.go @@ -2006,7 +2006,9 @@ func CreateBookingHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(existingBooking) + if err := json.NewEncoder(w).Encode(existingBooking); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } // If err is sql.ErrNoRows, proceed with creation @@ -3258,9 +3260,11 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Refund processing failed for booking %s — cancellation aborted: %v", bookingID, calcErr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) - _ = json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "error": "Refund processing failed — cancellation aborted. Please try again or contact support.", - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } } @@ -3352,7 +3356,9 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(resp) + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -3393,10 +3399,12 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "message": "Booking deleted successfully", "id": bookingID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // GET /api/bookings/{id} diff --git a/backend/handlers/bookings/manage.go b/backend/handlers/bookings/manage.go index 83eccef..13d83eb 100644 --- a/backend/handlers/bookings/manage.go +++ b/backend/handlers/bookings/manage.go @@ -160,10 +160,10 @@ func AdminCancelBookingHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Get current status and user ID — use FOR UPDATE to lock the row so // the refund and status change are atomic. @@ -289,7 +289,9 @@ func AdminCancelBookingHandler(w http.ResponseWriter, r *http.Request) { resp["refund_failed"] = true resp["warning"] = "Booking was cancelled but refund processing failed — please process refund manually or retry" } - _ = json.NewEncoder(w).Encode(resp) + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -417,14 +419,16 @@ func AdminCreateBookingForUserHandler(w http.ResponseWriter, r *http.Request) { // Get deposit info — deposit_required already fetched above in the main booking query. var preStartPaid float64 -if err := db.Conn.QueryRow(r.Context(), `SELECT COALESCE(SUM(amount), 0) FROM payments WHERE booking_id = $1 AND payment_type IN ('deposit', 'full') AND status = 'completed'`, existingID).Scan(&preStartPaid); err != nil { + if err := db.Conn.QueryRow(r.Context(), `SELECT COALESCE(SUM(amount), 0) FROM payments WHERE booking_id = $1 AND payment_type IN ('deposit', 'full') AND status = 'completed'`, existingID).Scan(&preStartPaid); err != nil { log.Printf("Failed to scan preStartPaid for booking %s: %v", existingID, err) } populateDepositFields(&existingBooking, existingBooking.DepositRequired, preStartPaid) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(existingBooking) + if err := json.NewEncoder(w).Encode(existingBooking); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } } @@ -671,10 +675,10 @@ if err := db.Conn.QueryRow(r.Context(), `SELECT COALESCE(SUM(amount), 0) FROM pa return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Evict any pending_release bookings that overlap this slot. if _, evictErr := EvictPendingReleaseOverlapping(r.Context(), tx, req.StartTime, newEnd); evictErr != nil { @@ -1217,10 +1221,10 @@ func DeleteEditRequestHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Delete the edit request for this booking res, err := tx.Exec(r.Context(), ` @@ -1402,10 +1406,10 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Delete any existing edit request for this booking (upsert behavior) _, err = tx.Exec(r.Context(), ` @@ -1567,10 +1571,12 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "auto_approved": true, "edit_request": editReq, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -1667,7 +1673,9 @@ func RequestEditHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(editReq) + if err := json.NewEncoder(w).Encode(editReq); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // AdminListEditRequestsHandler returns all edit requests @@ -1748,10 +1756,12 @@ func AdminListEditRequestsHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "requests": requests, "total": total, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // AdminApproveEditRequestHandler approves an edit request and updates the booking @@ -1775,10 +1785,10 @@ func AdminApproveEditRequestHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Get the edit request var bookingID string @@ -2064,10 +2074,10 @@ func AdminRejectEditRequestHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Delete the edit request _, err = tx.Exec(r.Context(), ` @@ -2162,9 +2172,11 @@ func GetMyEditRequestHandler(w http.ResponseWriter, r *http.Request) { if errors.Is(err, pgx.ErrNoRows) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "edit_request": nil, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to get edit request for booking %s: %v", bookingID, err) @@ -2181,9 +2193,11 @@ func GetMyEditRequestHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "edit_request": enriched, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // GetMyEditRequestsHandler returns all pending edit requests for the current user across all bookings. @@ -2245,9 +2259,11 @@ func GetMyEditRequestsHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "edit_requests": enrichedRequests, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // AdminListAllEditRequestsHandler returns ALL pending edit requests across all bookings. @@ -2302,9 +2318,11 @@ func AdminListAllEditRequestsHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "edit_requests": enrichedRequests, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // AdminGetBookingEditRequestHandler returns the pending edit request for a specific booking. @@ -2344,17 +2362,19 @@ func AdminGetBookingEditRequestHandler(w http.ResponseWriter, r *http.Request) { editReq.NewServices = newServices enriched, err := buildEnrichedEditRequest(r.Context(), &editReq) - if err != nil { - log.Printf("Failed to build enriched edit request: %v", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } + if err != nil { + log.Printf("Failed to build enriched edit request: %v", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ - "edit_request": enriched, - }) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(map[string]any{ + "edit_request": enriched, + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // ======================================== diff --git a/backend/handlers/bookings/reserve.go b/backend/handlers/bookings/reserve.go index 330e33e..deb0535 100644 --- a/backend/handlers/bookings/reserve.go +++ b/backend/handlers/bookings/reserve.go @@ -182,10 +182,10 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Check booking overlap inside transaction (TOCTOU fix) // pending_release is excluded — those bookings are evicted at creation time. @@ -256,10 +256,10 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Check anon rate cap inside transaction tenMinutesAgo := clock.Now().Add(-10 * time.Minute) @@ -343,5 +343,7 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(response) + if err := json.NewEncoder(w).Encode(response); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/notifications/notifications.go b/backend/handlers/notifications/notifications.go index 9edaf69..ba8a322 100644 --- a/backend/handlers/notifications/notifications.go +++ b/backend/handlers/notifications/notifications.go @@ -245,10 +245,10 @@ func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() query := ` UPDATE admin_notifications @@ -274,9 +274,11 @@ func AcknowledgeNotification(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{ + if err := json.NewEncoder(w).Encode(map[string]string{ "status": "ok", - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func AcknowledgePendingBookingNotification(tx any, ctx context.Context, bookingID string) error { diff --git a/backend/handlers/payments/giftcards.go b/backend/handlers/payments/giftcards.go index 440beee..d72cbfc 100644 --- a/backend/handlers/payments/giftcards.go +++ b/backend/handlers/payments/giftcards.go @@ -332,7 +332,9 @@ func GetGiftCards(w http.ResponseWriter, r *http.Request) { } resp.TotalPages = totalPages - _ = json.NewEncoder(w).Encode(resp) + if err := json.NewEncoder(w).Encode(resp); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func CreateGiftCard(w http.ResponseWriter, r *http.Request) { @@ -361,10 +363,10 @@ func CreateGiftCard(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var gc GiftCard var lastUsedAt sql.NullTime @@ -426,7 +428,9 @@ func CreateGiftCard(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(gc) + if err := json.NewEncoder(w).Encode(gc); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func TopUpGiftCard(w http.ResponseWriter, r *http.Request) { @@ -468,10 +472,10 @@ func TopUpGiftCard(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var redeemedBy sql.NullString var isInventory bool @@ -542,7 +546,9 @@ func TopUpGiftCard(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(gc) + if err := json.NewEncoder(w).Encode(gc); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func TransferGiftCard(w http.ResponseWriter, r *http.Request) { @@ -582,10 +588,10 @@ func TransferGiftCard(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var fromRedeemedBy, toRedeemedBy sql.NullString var fromRemaining, toRemaining float64 @@ -654,7 +660,9 @@ func TransferGiftCard(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]string{"status": "success"}) + if err := json.NewEncoder(w).Encode(map[string]string{"status": "success"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- User Handlers --- @@ -686,10 +694,10 @@ func RedeemGiftCard(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var amountRemaining float64 var redeemedBy sql.NullString @@ -762,10 +770,12 @@ func RedeemGiftCard(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "status": "success", "amount_redeemed": amountRemaining, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetGiftCardBalance(w http.ResponseWriter, r *http.Request) { @@ -780,7 +790,9 @@ func GetGiftCardBalance(w http.ResponseWriter, r *http.Request) { err := db.Conn.QueryRow(ctx, "SELECT balance FROM user_giftcard_balances WHERE user_id = $1", userID).Scan(&balance) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - _ = json.NewEncoder(w).Encode(map[string]float64{"balance": 0.00}) + if err := json.NewEncoder(w).Encode(map[string]float64{"balance": 0.00}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to query user balance: %v", err) @@ -788,7 +800,9 @@ func GetGiftCardBalance(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]float64{"balance": balance}) + if err := json.NewEncoder(w).Encode(map[string]float64{"balance": balance}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // GetUserGiftCardBalanceAdmin Handler returns any user's balance for the admin. @@ -806,7 +820,9 @@ func GetUserGiftCardBalanceAdmin(w http.ResponseWriter, r *http.Request) { err := db.Conn.QueryRow(ctx, "SELECT balance FROM user_giftcard_balances WHERE user_id = $1", userID).Scan(&balance) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - _ = json.NewEncoder(w).Encode(map[string]float64{"balance": 0.00}) + if err := json.NewEncoder(w).Encode(map[string]float64{"balance": 0.00}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to query user balance: %v", err) @@ -821,10 +837,10 @@ func GetUserGiftCardBalanceAdmin(w http.ResponseWriter, r *http.Request) { log.Printf("Failed to begin transaction: %v", err) } else { defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() if _, err := tx.Exec(ctx, ` INSERT INTO admin_audit_log (admin_id, action_type, target_user_id, details) @@ -838,7 +854,9 @@ func GetUserGiftCardBalanceAdmin(w http.ResponseWriter, r *http.Request) { } } - _ = json.NewEncoder(w).Encode(map[string]float64{"balance": balance}) + if err := json.NewEncoder(w).Encode(map[string]float64{"balance": balance}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func BuyGiftCard(w http.ResponseWriter, r *http.Request) { @@ -880,7 +898,9 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) { log.Printf("Failed to check idempotency: %v", err) } if existing != nil { - _ = json.NewEncoder(w).Encode(existing) + if err := json.NewEncoder(w).Encode(existing); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } } @@ -933,10 +953,10 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var buyPaymentID string fees := paymentService.CalculateFees(req.Amount, "online") @@ -1107,11 +1127,13 @@ func BuyGiftCard(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "status": "success", "code": cardID, "amount": amountPounds, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- Helpers --- @@ -1179,10 +1201,12 @@ func GetExpiredBalances(w http.ResponseWriter, r *http.Request) { balances = []ExpiredBalance{} } - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "expired_balances": balances, "total": len(balances), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type ClaimExpiredBalanceRequest struct { @@ -1216,10 +1240,10 @@ func ClaimExpiredBalance(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var existingClaimedAt sql.NullTime err = tx.QueryRow(ctx, ` @@ -1258,5 +1282,7 @@ func ClaimExpiredBalance(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]string{"status": "claimed"}) + if err := json.NewEncoder(w).Encode(map[string]string{"status": "claimed"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/payments/handlers.go b/backend/handlers/payments/handlers.go index cf3b23e..ee2de94 100644 --- a/backend/handlers/payments/handlers.go +++ b/backend/handlers/payments/handlers.go @@ -130,7 +130,9 @@ func GetDiscountPreviewHandler(w http.ResponseWriter, r *http.Request) { preview := calculateDiscountPreview(r.Context(), bookingID, userID) - _ = json.NewEncoder(w).Encode(preview) + if err := json.NewEncoder(w).Encode(preview); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // calculateDiscountPreview runs the same queries as applyEligibleCampaignsAtPayment @@ -242,10 +244,10 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri annRows.Close() for _, c := range campaigns { - var exists int - if err := db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists); err != nil { - log.Printf("Failed to scan anniversary discount existence: %v", err) - } + var exists int + if err := db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists); err != nil { + log.Printf("Failed to scan anniversary discount existence: %v", err) + } if exists > 0 { continue } @@ -389,10 +391,12 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) { if err := tx.QueryRow(r.Context(), ` SELECT id, status FROM payments WHERE booking_id = $1 AND idempotency_key = $2 `, bookingID, idempotencyKey).Scan(&existingID, &existingStatus); err == nil { - _ = json.NewEncoder(w).Encode(CheckoutResponse{ + if err := json.NewEncoder(w).Encode(CheckoutResponse{ CheckoutID: existingID, Status: existingStatus, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } else if !errors.Is(err, pgx.ErrNoRows) { log.Printf("Failed to check idempotency: %v", err) @@ -531,10 +535,12 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(CheckoutResponse{ + if err := json.NewEncoder(w).Encode(CheckoutResponse{ CheckoutID: paymentID, Status: "COMPLETED", - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -561,10 +567,12 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) { log.Printf("Failed to check idempotency: %v", err) } if existingPayment != nil { - _ = json.NewEncoder(w).Encode(CheckoutResponse{ + if err := json.NewEncoder(w).Encode(CheckoutResponse{ CheckoutID: existingPayment.ID, Status: existingPayment.Status, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -583,10 +591,12 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(CheckoutResponse{ + if err := json.NewEncoder(w).Encode(CheckoutResponse{ CheckoutID: checkout.ID, Status: checkout.Status, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) { @@ -613,7 +623,9 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) { paymentResult, err := SquareClient.GetCheckout(r.Context(), checkoutID) if err != nil { if err.Error() == "checkout pending" { - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}) + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to get checkout status: %v", err) @@ -647,14 +659,16 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) { WHERE booking_id = $1 AND idempotency_key = $2 `, bookingID, idempotencyKey).Scan(&existingID, &existingSquarePayID); err == nil { if existingSquarePayID.Valid && existingSquarePayID.String == paymentResult.SquarePayID { - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{ + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{ Status: "COMPLETED", PaymentID: existingID, Amount: paymentResult.Amount, CardBrand: paymentResult.CardBrand, CardLast4: paymentResult.CardLast4, ReceiptURL: paymentResult.ReceiptURL, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } } else if !errors.Is(err, pgx.ErrNoRows) { @@ -688,14 +702,16 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{ + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{ Status: "COMPLETED", PaymentID: paymentID, Amount: paymentResult.Amount, CardBrand: paymentResult.CardBrand, CardLast4: paymentResult.CardLast4, ReceiptURL: paymentResult.ReceiptURL, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -773,7 +789,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { } if err := ValidatePartialAmount(req.Amount, remainingCents); err != nil { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } } @@ -876,14 +892,16 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { FROM payments WHERE booking_id = $1 AND idempotency_key = $2 `, bookingID, req.IdempotencyKey).Scan(&existingID, &existingBookingID, &existingPaymentType, &existingStatus, &existingAmount, &existingCreatedAt); err == nil { - _ = json.NewEncoder(w).Encode(PaymentResponse{ + if err := json.NewEncoder(w).Encode(PaymentResponse{ ID: existingID.String, BookingID: existingBookingID.String, PaymentType: existingPaymentType.String, Status: existingStatus.String, Amount: int64(existingAmount.Float64 * 100), CreatedAt: existingCreatedAt.Time.Format(time.RFC3339), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } else if !errors.Is(err, pgx.ErrNoRows) { log.Printf("Failed to check idempotency: %v", err) @@ -1073,7 +1091,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(PaymentResponse{ + if err := json.NewEncoder(w).Encode(PaymentResponse{ ID: primaryPaymentID, BookingID: bookingID, PaymentType: req.PaymentType, @@ -1083,7 +1101,9 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { CardLast4: paymentResult.CardLast4, ReceiptURL: paymentResult.ReceiptURL, CreatedAt: clock.Now().Format(time.RFC3339), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // applyEligibleCampaignsAtPayment checks and applies any eligible discount @@ -1226,10 +1246,10 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI annRows.Close() for _, c := range campaigns { - var exists int - if err := q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists); err != nil { - log.Printf("Failed to scan anniversary discount existence: %v", err) - } + var exists int + if err := q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists); err != nil { + log.Printf("Failed to scan anniversary discount existence: %v", err) + } if exists > 0 { continue } @@ -1311,13 +1331,13 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by) VALUES ($1, 'partial', 'discount', $2, 'completed', $3) `, bookingID, discountAmount, userID); err != nil { - log.Printf("ALERT: failed to insert discount payment record for campaign %s, booking %s: %v", globalCampaignID, bookingID, err) - } + log.Printf("ALERT: failed to insert discount payment record for campaign %s, booking %s: %v", globalCampaignID, bookingID, err) + } if _, err := q.Exec(ctx, ` UPDATE discount_campaigns SET times_redeemed = times_redeemed + 1 WHERE id = $1 `, globalCampaignID); err != nil { - log.Printf("ALERT: failed to increment times_redeemed for campaign %s, booking %s: %v", globalCampaignID, bookingID, err) - } + log.Printf("ALERT: failed to increment times_redeemed for campaign %s, booking %s: %v", globalCampaignID, bookingID, err) + } } } } @@ -1472,7 +1492,9 @@ func GetUserPaymentMethods(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(cards) + if err := json.NewEncoder(w).Encode(cards); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func AdminGetUserPaymentMethods(w http.ResponseWriter, r *http.Request) { @@ -1490,7 +1512,9 @@ func AdminGetUserPaymentMethods(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(cards) + if err := json.NewEncoder(w).Encode(cards); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func DeletePaymentMethod(w http.ResponseWriter, r *http.Request) { @@ -1514,7 +1538,9 @@ func DeletePaymentMethod(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{"status": "deleted"}) + if err := json.NewEncoder(w).Encode(map[string]string{"status": "deleted"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type CreatePaymentMethodRequest struct { @@ -1555,7 +1581,7 @@ func CreatePaymentMethod(w http.ResponseWriter, r *http.Request) { if err != nil { if strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "expired") { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } log.Printf("Failed to create payment method: %v", err) @@ -1563,7 +1589,9 @@ func CreatePaymentMethod(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(card) + if err := json.NewEncoder(w).Encode(card); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func RefundPayment(w http.ResponseWriter, r *http.Request) { @@ -1691,14 +1719,16 @@ func RefundPayment(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(RefundResponse{ + if err := json.NewEncoder(w).Encode(RefundResponse{ ID: refundID, PaymentID: paymentID, Amount: req.Amount, Status: "completed", Reason: req.Reason, CreatedAt: clock.Now().Format(time.RFC3339), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func CreateTipPayment(w http.ResponseWriter, r *http.Request) { @@ -1846,7 +1876,7 @@ func CreateTipPayment(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(PaymentResponse{ + if err := json.NewEncoder(w).Encode(PaymentResponse{ ID: paymentID, BookingID: bookingID, PaymentType: "tip", @@ -1856,7 +1886,9 @@ func CreateTipPayment(w http.ResponseWriter, r *http.Request) { CardLast4: paymentResult.CardLast4, ReceiptURL: paymentResult.ReceiptURL, CreatedAt: clock.Now().Format(time.RFC3339), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetBookingPaymentSummary(w http.ResponseWriter, r *http.Request) { @@ -1921,7 +1953,7 @@ func GetBookingPaymentSummary(w http.ResponseWriter, r *http.Request) { } } - _ = json.NewEncoder(w).Encode(PaymentSummaryResponse{ + if err := json.NewEncoder(w).Encode(PaymentSummaryResponse{ TotalAmount: int64(summary.TotalAmount * 100), PaidAmount: int64(summary.PaidAmount * 100), RefundedAmount: int64(summary.RefundedAmount * 100), @@ -1930,7 +1962,9 @@ func GetBookingPaymentSummary(w http.ResponseWriter, r *http.Request) { TotalNetAmount: int64(summary.TotalNetAmount * 100), Payments: payments, Refunds: refunds, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // PaymentLockDuration is the TTL for a payment-in-flight lock in minutes. @@ -2025,11 +2059,13 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "status": "locked", "ttl_min": PaymentLockDuration, "bookingID": bookingID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // ReleasePaymentLock removes the PAYMENT_IN_FLIGHT time_blocker for a booking. diff --git a/backend/handlers/payments/loyalty.go b/backend/handlers/payments/loyalty.go index 9bcb24b..15621ad 100644 --- a/backend/handlers/payments/loyalty.go +++ b/backend/handlers/payments/loyalty.go @@ -98,10 +98,10 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var bookingTotal float64 if err := tx.QueryRow(r.Context(), ` @@ -155,8 +155,10 @@ func ApplyLoyaltyRedemption(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "success": true, "discount_amount": discountAmount, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/payments/till.go b/backend/handlers/payments/till.go index af6cbd7..31b3df2 100644 --- a/backend/handlers/payments/till.go +++ b/backend/handlers/payments/till.go @@ -98,13 +98,15 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) { err := db.Conn.QueryRow(ctx, `SELECT id FROM till_sales WHERE idempotency_key = $1`, req.IdempotencyKey).Scan(&existingID) if err == nil { // Existing sale found — return it (idempotent) - _ = json.NewEncoder(w).Encode(TillSaleResponse{ - ID: existingID, - ItemType: req.ItemType, - TotalAmount: req.Amount, - PaymentMethod: req.PaymentMethod, - Status: "completed", - }) + if err := json.NewEncoder(w).Encode(TillSaleResponse{ + ID: existingID, + ItemType: req.ItemType, + TotalAmount: req.Amount, + PaymentMethod: req.PaymentMethod, + Status: "completed", + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } } @@ -118,10 +120,10 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var giftCardID string if req.Action == "create" { @@ -447,7 +449,7 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(TillSaleResponse{ + if err := json.NewEncoder(w).Encode(TillSaleResponse{ ID: tillSaleID, ItemType: req.ItemType, ItemID: &giftCardID, @@ -455,7 +457,9 @@ func CreateTillSale(w http.ResponseWriter, r *http.Request) { PaymentMethod: req.PaymentMethod, Status: saleStatus, CheckoutID: squareCheckoutID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) { @@ -482,16 +486,20 @@ func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) { } if currentStatus == "completed" { - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{ + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{ Status: "COMPLETED", - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } paymentResult, err := SquareClient.GetCheckout(r.Context(), checkoutID) if err != nil { if err.Error() == "checkout pending" { - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}) + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } log.Printf("Failed to get checkout status: %v", err) @@ -507,10 +515,10 @@ func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), ` UPDATE till_sales @@ -532,16 +540,20 @@ func GetTillCheckoutStatus(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{ + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{ Status: "COMPLETED", PaymentID: tillSaleID, Amount: paymentResult.Amount, CardBrand: paymentResult.CardBrand, CardLast4: paymentResult.CardLast4, ReceiptURL: paymentResult.ReceiptURL, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } - _ = json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}) + if err := json.NewEncoder(w).Encode(PaymentStatusResponse{Status: "PENDING"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/portfolio/images.go b/backend/handlers/portfolio/images.go index bef3ae7..9c0054f 100644 --- a/backend/handlers/portfolio/images.go +++ b/backend/handlers/portfolio/images.go @@ -23,8 +23,8 @@ import ( "strings" "time" - "github.com/kovidgoyal/imaging" "github.com/go-chi/chi/v5" + "github.com/kovidgoyal/imaging" ) const MaxInputLength = 256 @@ -136,14 +136,14 @@ func ListImages(w http.ResponseWriter, r *http.Request) { if tagFilter != "" { if err := validateInputLength(tagFilter); err != nil { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } } if tagsFilter != "" { if err := validateInputLength(tagsFilter); err != nil { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } } @@ -187,7 +187,7 @@ func ListImages(w http.ResponseWriter, r *http.Request) { // Validate input length if err := validateInputLength(category + ":" + value); err != nil { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } @@ -381,10 +381,12 @@ func ListImages(w http.ResponseWriter, r *http.Request) { nextCursor = &cursor } - _ = json.NewEncoder(w).Encode(ImageListResponse{ - Images: images, - NextCursor: nextCursor, - }) + if err := json.NewEncoder(w).Encode(ImageListResponse{ + Images: images, + NextCursor: nextCursor, + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func ListTags(w http.ResponseWriter, r *http.Request) { @@ -450,7 +452,9 @@ func ListTags(w http.ResponseWriter, r *http.Request) { tags = []Tag{} } - _ = json.NewEncoder(w).Encode(tags) + if err := json.NewEncoder(w).Encode(tags); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type FilterCategory struct { @@ -622,7 +626,9 @@ func ListFilters(w http.ResponseWriter, r *http.Request) { return sumI > sumJ }) - _ = json.NewEncoder(w).Encode(filters) + if err := json.NewEncoder(w).Encode(filters); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } @@ -676,7 +682,9 @@ func ListFilters(w http.ResponseWriter, r *http.Request) { } filters = uniqueFilters - _ = json.NewEncoder(w).Encode(filters) + if err := json.NewEncoder(w).Encode(filters); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func UploadImage(w http.ResponseWriter, r *http.Request) { @@ -895,10 +903,10 @@ func UploadImage(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var imgID string err = tx.QueryRow(r.Context(), ` @@ -932,7 +940,7 @@ func UploadImage(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(Image{ + if err := json.NewEncoder(w).Encode(Image{ ID: imgID, URL: fullURLs.Avif, ThumbnailURL: thumbURLs.Webp, @@ -940,7 +948,9 @@ func UploadImage(w http.ResponseWriter, r *http.Request) { Thumb: thumbURLs, TagNames: tags, CreatedAt: clock.Now(), - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func DeleteImage(w http.ResponseWriter, r *http.Request) { @@ -1016,8 +1026,8 @@ func DeleteImage(w http.ResponseWriter, r *http.Request) { } key := extractKey(u) if err := s3.Client.Delete(context.Background(), "crussell", key); err != nil { - slog.Warn("failed to delete S3 object", "err", err) - } + slog.Warn("failed to delete S3 object", "err", err) + } } } @@ -1028,10 +1038,10 @@ func DeleteImage(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), `DELETE FROM images WHERE id = $1`, imageID) if err != nil { @@ -1139,5 +1149,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { img.Thumb.Jpg = thumbJpg.String } - _ = json.NewEncoder(w).Encode(img) + if err := json.NewEncoder(w).Encode(img); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/scheduling/default-hours.go b/backend/handlers/scheduling/default-hours.go index ee5c1a6..8bb6427 100644 --- a/backend/handlers/scheduling/default-hours.go +++ b/backend/handlers/scheduling/default-hours.go @@ -58,7 +58,9 @@ func GetDefaultHours(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(hours) + if err := json.NewEncoder(w).Encode(hours); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) { @@ -71,7 +73,7 @@ func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) { for _, h := range hours { if err := validators.Validate.Struct(&h); err != nil { log.Printf("Failed to process request: %v", err) - http.Error(w, "Invalid request", http.StatusBadRequest) + http.Error(w, "Invalid request", http.StatusBadRequest) return } } @@ -96,10 +98,10 @@ func UpdateDefaultHours(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() weekdays := make([]int, len(hours)) startTimes := make([]string, len(hours)) @@ -294,7 +296,9 @@ func GetWorkingHours(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(results) + if err := json.NewEncoder(w).Encode(results); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // isValidTime15Min checks that a time string (HH:MM or HH:MM:SS) has minutes in {00, 15, 30, 45}. @@ -610,7 +614,9 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(results) + if err := json.NewEncoder(w).Encode(results); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // normalizeTime strips seconds from HH:MM:SS to HH:MM for consistent string diff --git a/backend/handlers/scheduling/exceptional-hours.go b/backend/handlers/scheduling/exceptional-hours.go index a3805a3..58103b0 100644 --- a/backend/handlers/scheduling/exceptional-hours.go +++ b/backend/handlers/scheduling/exceptional-hours.go @@ -119,8 +119,9 @@ func ListExceptionalGroups(w http.ResponseWriter, r *http.Request) { } } - - _ = json.NewEncoder(w).Encode(groups) + if err := json.NewEncoder(w).Encode(groups); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- Create Group with Hours and Applications (bulk) --- @@ -187,10 +188,10 @@ func CreateExceptionalGroup(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Create group err = tx.QueryRow(r.Context(), ` @@ -236,9 +237,10 @@ func CreateExceptionalGroup(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(g) + if err := json.NewEncoder(w).Encode(g); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- Delete Group (cascades to hours and applications) --- @@ -264,10 +266,10 @@ func DeleteExceptionalGroup(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() result, err := tx.Exec(r.Context(), ` DELETE FROM exceptional_working_hours_groups WHERE id=$1 @@ -333,10 +335,10 @@ func UpdateExceptionalApplications(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Delete existing applications for this group _, err = tx.Exec(r.Context(), ` diff --git a/backend/handlers/scheduling/time-blockers.go b/backend/handlers/scheduling/time-blockers.go index 92ce014..72634ca 100644 --- a/backend/handlers/scheduling/time-blockers.go +++ b/backend/handlers/scheduling/time-blockers.go @@ -9,8 +9,8 @@ import ( "net/http" "time" - "crussell/db" "crussell/clock" + "crussell/db" "crussell/internal/validators" "crussell/mw" @@ -113,7 +113,9 @@ func ListTimeBlockers(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(blockers) + if err := json.NewEncoder(w).Encode(blockers); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- Create Time Blocker --- @@ -153,10 +155,10 @@ func CreateTimeBlocker(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Insert the time blocker var blocker TimeBlocker @@ -180,7 +182,9 @@ func CreateTimeBlocker(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(blocker) + if err := json.NewEncoder(w).Encode(blocker); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // --- Delete Time Blocker --- @@ -198,10 +202,10 @@ func DeleteTimeBlocker(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() result, err := tx.Exec(r.Context(), ` DELETE FROM time_blockers WHERE id = $1 @@ -361,14 +365,14 @@ func CheckTimeBlockerOverlap(ctx context.Context, startTime, endTime time.Time, } // CleanupOldReservations deletes expired reservations: -// - Logged-in (RESERVATION:user): older than 1 hour -// - Anonymous (RESERVATION:anon): older than 10 minutes -// - Admin walk-in (RESERVATION:admin:walkin:%): older than 15 minutes -// - Admin call-in (RESERVATION:admin:callin:%): older than 15 minutes -// - Edit request (RESERVATION:edit_request:%): older than 24 hours -// - Payment in-flight (PAYMENT_IN_FLIGHT:%): TTL via duration_minutes column -// (AcquirePaymentLock sets duration_minutes = PaymentLockDuration = 5min and -// start_time = NOW(), so the condition evaluates to "cleanup after 5 minutes". +// - Logged-in (RESERVATION:user): older than 1 hour +// - Anonymous (RESERVATION:anon): older than 10 minutes +// - Admin walk-in (RESERVATION:admin:walkin:%): older than 15 minutes +// - Admin call-in (RESERVATION:admin:callin:%): older than 15 minutes +// - Edit request (RESERVATION:edit_request:%): older than 24 hours +// - Payment in-flight (PAYMENT_IN_FLIGHT:%): TTL via duration_minutes column +// (AcquirePaymentLock sets duration_minutes = PaymentLockDuration = 5min and +// start_time = NOW(), so the condition evaluates to "cleanup after 5 minutes". func CleanupOldReservations(ctx context.Context) (int, error) { oneHourAgo := clock.Now().Add(-1 * time.Hour) tenMinutesAgo := clock.Now().Add(-10 * time.Minute) @@ -380,10 +384,10 @@ func CleanupOldReservations(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() tag, err := tx.Exec(ctx, ` DELETE FROM time_blockers @@ -411,10 +415,10 @@ func AnonymizeStaleGuestAccounts(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var totalRows int @@ -507,10 +511,10 @@ func CleanupExpiredLoyaltyRedemptions(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() tag, err := tx.Exec(ctx, ` DELETE FROM loyalty_redemptions @@ -555,10 +559,10 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var totalRows int @@ -584,7 +588,7 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) { LEFT JOIN bookings b ON p.booking_id = b.id LEFT JOIN users u ON b.user_id = u.id WHERE p.created_at < NOW() - INTERVAL '7 years' - ` + retentionFilter + ` + `+retentionFilter+` GROUP BY DATE_TRUNC('month', p.created_at)::date ON CONFLICT (month) DO UPDATE SET total_payments = financial_aggregates.total_payments + EXCLUDED.total_payments, @@ -617,7 +621,7 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) { LEFT JOIN bookings b ON p.booking_id = b.id LEFT JOIN users u ON b.user_id = u.id WHERE p.created_at < NOW() - INTERVAL '7 years' - ` + retentionFilter + ` + `+retentionFilter+` GROUP BY DATE_TRUNC('month', r.created_at)::date ON CONFLICT (month) DO UPDATE SET total_refunds = financial_aggregates.total_refunds + EXCLUDED.total_refunds @@ -633,7 +637,7 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) { LEFT JOIN users u ON b.user_id = u.id WHERE p.booking_id = b.id AND p.created_at < NOW() - INTERVAL '7 years' - ` + retentionFilter + ` + `+retentionFilter+` `) if err != nil { return 0, fmt.Errorf("failed to delete expired payments: %w", err) @@ -647,7 +651,7 @@ func CleanupExpiredFinancialRecords(ctx context.Context) (int, error) { LEFT JOIN users u ON b.user_id = u.id WHERE r.payment_id = p.id AND p.created_at < NOW() - INTERVAL '7 years' - ` + retentionFilter + ` + `+retentionFilter+` `) if err != nil { return 0, fmt.Errorf("failed to delete expired refunds: %w", err) @@ -671,10 +675,10 @@ func CleanupExpiredDeposits(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() // Collect all evicted booking IDs from both updates so we can notify // and clean up in one pass instead of re-scanning via updated_at = NOW(). @@ -803,10 +807,10 @@ func CleanupExpiredGiftCards(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() rows, err := tx.Query(ctx, ` SELECT id, amount_remaining @@ -905,10 +909,10 @@ func CleanupIdleAccounts(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() rowsWithBalance, err := tx.Query(ctx, ` SELECT u.id, COALESCE(b.balance, 0) as balance @@ -1014,10 +1018,10 @@ func CleanupOldIdempotencyKeys(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var totalRows int @@ -1069,10 +1073,10 @@ func CleanupOldNameHistory(ctx context.Context) (int, error) { return 0, fmt.Errorf("failed to begin transaction: %w", err) } defer func() { - if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(ctx); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() tag, err := tx.Exec(ctx, ` DELETE FROM name_history diff --git a/backend/handlers/services/services.go b/backend/handlers/services/services.go index 47fc124..17bf40b 100644 --- a/backend/handlers/services/services.go +++ b/backend/handlers/services/services.go @@ -67,10 +67,10 @@ func ToggleService(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() query := "UPDATE services SET is_active = NOT is_active WHERE id = $1" result, err := tx.Exec(r.Context(), query, serviceID) @@ -90,10 +90,12 @@ func ToggleService(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "message": "Service toggled successfully", "id": serviceID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // POST /api/admin/services @@ -145,10 +147,10 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() query := ` INSERT INTO services ( @@ -229,10 +231,10 @@ func DeleteServiceHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() query := "UPDATE services SET is_active = FALSE WHERE id = $1" result, err := tx.Exec(r.Context(), query, serviceID) @@ -252,10 +254,12 @@ func DeleteServiceHandler(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "message": "Service deleted successfully", "id": serviceID, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // ServicesHandler returns all services from the database diff --git a/backend/handlers/today/today.go b/backend/handlers/today/today.go index 3bb35c8..03bfb51 100644 --- a/backend/handlers/today/today.go +++ b/backend/handlers/today/today.go @@ -103,10 +103,10 @@ func GetCurrentAndNextHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), ` UPDATE bookings @@ -715,10 +715,10 @@ func GetTodayAppointmentsHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() _, err = tx.Exec(r.Context(), ` UPDATE bookings @@ -799,7 +799,9 @@ func GetTodayAppointmentsHandler(w http.ResponseWriter, r *http.Request) { rows.Close() if len(raw) == 0 { - _ = json.NewEncoder(w).Encode(TodayAppointmentsResponse{Appointments: []TodayAppointment{}}) + if err := json.NewEncoder(w).Encode(TodayAppointmentsResponse{Appointments: []TodayAppointment{}}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } return } diff --git a/backend/handlers/user/guest.go b/backend/handlers/user/guest.go index 8c9b710..469319a 100644 --- a/backend/handlers/user/guest.go +++ b/backend/handlers/user/guest.go @@ -115,10 +115,10 @@ func CreateGuestUserHandler(w http.ResponseWriter, r *http.Request) { return } defer func() { - if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) - } -}() + if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { + slog.Error("failed to rollback transaction", "err", err) + } + }() var userID string err = tx.QueryRow(r.Context(), ` @@ -144,7 +144,9 @@ func CreateGuestUserHandler(w http.ResponseWriter, r *http.Request) { // Note: We intentionally don't sync to CardDAV - guests don't need calendar contacts w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(CreateGuestUserResponse{ID: userID, Role: "guest"}) + if err := json.NewEncoder(w).Encode(CreateGuestUserResponse{ID: userID, Role: "guest"}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // GET /api/check-email?email=...&firstName=...&lastName=...&phone=... @@ -199,7 +201,9 @@ func CheckEmailHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "suggestion": suggestion, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/user/loyalty.go b/backend/handlers/user/loyalty.go index 7885b4e..19a7e62 100644 --- a/backend/handlers/user/loyalty.go +++ b/backend/handlers/user/loyalty.go @@ -2,6 +2,7 @@ package user import ( "encoding/json" + "log" "net/http" "crussell/db" @@ -29,6 +30,7 @@ func GetLoyaltyHandler(w http.ResponseWriter, r *http.Request) { return } - - _ = json.NewEncoder(w).Encode(loyalty) + if err := json.NewEncoder(w).Encode(loyalty); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/handlers/user/profile.go b/backend/handlers/user/profile.go index e750d99..a6c6a28 100644 --- a/backend/handlers/user/profile.go +++ b/backend/handlers/user/profile.go @@ -170,7 +170,9 @@ func GetProfileHandler(w http.ResponseWriter, r *http.Request) { } } - _ = json.NewEncoder(w).Encode(user) + if err := json.NewEncoder(w).Encode(user); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // PUT /api/user/profile @@ -320,8 +322,8 @@ func UpdateProfileHandler(w http.ResponseWriter, r *http.Request) { } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() // If first or last name changed (via user edit), track the old names in history @@ -730,8 +732,8 @@ func ChangePasswordHandler(w http.ResponseWriter, r *http.Request) { } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() _, err = tx.Exec(r.Context(), `UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2`, string(newHash), userID) @@ -815,7 +817,9 @@ func GetEligiblePatchTestServicesHandler(w http.ResponseWriter, r *http.Request) services = []ServiceForPatchTest{} } - _ = json.NewEncoder(w).Encode(services) + if err := json.NewEncoder(w).Encode(services); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } type AddPatchTestRequest struct { @@ -869,8 +873,8 @@ func AddPatchTestHandler(w http.ResponseWriter, r *http.Request) { } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() _, err = tx.Exec(r.Context(), ` @@ -940,7 +944,9 @@ func GetUserPatchTestsHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(tests) + if err := json.NewEncoder(w).Encode(tests); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // DELETE /api/admin/users/{user_id}/patch-tests/{test_id} @@ -964,8 +970,8 @@ func DeletePatchTestHandler(w http.ResponseWriter, r *http.Request) { } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() result, err := tx.Exec(r.Context(), ` @@ -1079,8 +1085,8 @@ func UploadProfilePictureHandler(w http.ResponseWriter, r *http.Request) { } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() _, err = tx.Exec(r.Context(), `UPDATE users SET profile_pic_url = $1 WHERE id = $2`, url, userID) @@ -1096,7 +1102,9 @@ func UploadProfilePictureHandler(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(UploadProfilePicResponse{URL: url}) + if err := json.NewEncoder(w).Encode(UploadProfilePicResponse{URL: url}); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func processProfileImage(data []byte) ([]byte, error) { @@ -1165,7 +1173,9 @@ func GetNotificationPreferencesHandler(w http.ResponseWriter, r *http.Request) { } } - _ = json.NewEncoder(w).Encode(prefs) + if err := json.NewEncoder(w).Encode(prefs); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // PUT /api/user/notification-preferences @@ -1200,8 +1210,8 @@ func UpdateNotificationPreferencesHandler(w http.ResponseWriter, r *http.Request } defer func() { if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" { - slog.Error("failed to rollback transaction", "err", err) -} + slog.Error("failed to rollback transaction", "err", err) + } }() if exists { @@ -1258,5 +1268,7 @@ func GetContactInfoHandler(w http.ResponseWriter, r *http.Request) { contact.Role = "Owner / Beauty Specialist" - _ = json.NewEncoder(w).Encode(contact) + if err := json.NewEncoder(w).Encode(contact); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } diff --git a/backend/main.go b/backend/main.go index 7fbbc8a..edc53aa 100644 --- a/backend/main.go +++ b/backend/main.go @@ -151,10 +151,12 @@ func healthCheckHandler(w http.ResponseWriter, r *http.Request) { } else { w.WriteHeader(http.StatusOK) } - _ = json.NewEncoder(w).Encode(map[string]any{ + if err := json.NewEncoder(w).Encode(map[string]any{ "status": status, "services": services, - }) + }); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } func main() { diff --git a/backend/mw/response.go b/backend/mw/response.go index 3d7ece1..96d5aff 100644 --- a/backend/mw/response.go +++ b/backend/mw/response.go @@ -2,6 +2,7 @@ package mw import ( "encoding/json" + "log" "net/http" ) @@ -11,7 +12,9 @@ import ( func RespondJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(data) + if err := json.NewEncoder(w).Encode(data); err != nil { + log.Printf("Failed to encode JSON response: %v", err) + } } // RespondError sends a JSON error response with the given status code and message. @@ -19,6 +22,7 @@ func RespondJSON(w http.ResponseWriter, status int, data any) { func RespondError(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) + if err := json.NewEncoder(w).Encode(map[string]string{"error": msg}); err != nil { + log.Printf("Failed to encode error response: %v", err) + } } -