refactor(backend): migrate from sql.ErrNoRows to pgx.ErrNoRows
Replace all database/sql.ErrNoRows checks with pgx.ErrNoRows across backend handlers. Migration includes: jwt.go, local.go, admin_reserve.go, custom_services.go, discount_campaigns.go, services.go, account.go, customer_relationship.go, guest.go. Also removes unused test_helpers.go resetTestData function. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
+2
-2
@@ -3,12 +3,12 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"crussell/db"
|
"crussell/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
)
|
)
|
||||||
@@ -196,7 +196,7 @@ func VerifyRefreshToken(ctx context.Context, tokenString string) (userID string,
|
|||||||
|
|
||||||
err = db.DB.QueryRow(ctx, query, tokenString).Scan(&userID, &role)
|
err = db.DB.QueryRow(ctx, query, tokenString).Scan(&userID, &role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return "", "", fmt.Errorf("invalid or expired refresh token")
|
return "", "", fmt.Errorf("invalid or expired refresh token")
|
||||||
}
|
}
|
||||||
return "", "", fmt.Errorf("failed to verify refresh token: %w", err)
|
return "", "", fmt.Errorf("failed to verify refresh token: %w", err)
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import (
|
|||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomService struct {
|
type CustomService struct {
|
||||||
@@ -268,7 +270,7 @@ func GetCustomService(w http.ResponseWriter, r *http.Request) {
|
|||||||
SELECT id, name, description, price, duration_minutes, minimum_age_required, notes, created_at, created_by, usage_count, last_used_at
|
SELECT id, name, description, price, duration_minutes, minimum_age_required, notes, created_at, created_by, usage_count, last_used_at
|
||||||
FROM custom_services WHERE id = $1
|
FROM custom_services WHERE id = $1
|
||||||
`, id).Scan(&cs.ID, &cs.Name, &desc, &cs.Price, &cs.DurationMinutes, &cs.MinimumAgeRequired, ¬es, &cs.CreatedAt, &createdBy, &cs.UsageCount, &lastUsedAt)
|
`, id).Scan(&cs.ID, &cs.Name, &desc, &cs.Price, &cs.DurationMinutes, &cs.MinimumAgeRequired, ¬es, &cs.CreatedAt, &createdBy, &cs.UsageCount, &lastUsedAt)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "Custom service not found", http.StatusNotFound)
|
http.Error(w, "Custom service not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -386,7 +388,7 @@ func PromoteCustomService(w http.ResponseWriter, r *http.Request) {
|
|||||||
SELECT name, description, price, duration_minutes, minimum_age_required, notes, created_by
|
SELECT name, description, price, duration_minutes, minimum_age_required, notes, created_by
|
||||||
FROM custom_services WHERE id = $1
|
FROM custom_services WHERE id = $1
|
||||||
`, id).Scan(&name, &desc, &price, &durationMinutes, &minimumAgeRequired, ¬es, &createdBy)
|
`, id).Scan(&name, &desc, &price, &durationMinutes, &minimumAgeRequired, ¬es, &createdBy)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "Custom service not found", http.StatusNotFound)
|
http.Error(w, "Custom service not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,7 +456,7 @@ func DeleteCustomService(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var usageCount int
|
var usageCount int
|
||||||
err := db.DB.QueryRow(r.Context(), `SELECT usage_count FROM custom_services WHERE id = $1`, id).Scan(&usageCount)
|
err := db.DB.QueryRow(r.Context(), `SELECT usage_count FROM custom_services WHERE id = $1`, id).Scan(&usageCount)
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "Custom service not found", http.StatusNotFound)
|
http.Error(w, "Custom service not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -666,7 +667,7 @@ func GetCampaignStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
&createdBy,
|
&createdBy,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "Campaign not found", http.StatusNotFound)
|
http.Error(w, "Campaign not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"crussell/db"
|
|
||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"crussell/testutils/testdb"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// resetTestData truncates tables to clean up data between tests
|
|
||||||
func resetTestData(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
testdb.TruncateTables(t, db.DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeAdminRequest creates a request with admin context
|
// makeAdminRequest creates a request with admin context
|
||||||
// Note: Using 12-char IDs to match CHAR(12) columns in schema (e.g., created_by)
|
// Note: Using 12-char IDs to match CHAR(12) columns in schema (e.g., created_by)
|
||||||
func makeAdminRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
func makeAdminRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crussell/auth"
|
"crussell/auth"
|
||||||
"crussell/db"
|
"crussell/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"crussell/internal/dav"
|
"crussell/internal/dav"
|
||||||
"crussell/internal/validators"
|
"crussell/internal/validators"
|
||||||
"crussell/internal/zxcvbnjs"
|
"crussell/internal/zxcvbnjs"
|
||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -520,7 +520,7 @@ func GenerateVerificationCodeHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
"SELECT id FROM users WHERE LOWER(email) = $1", email,
|
"SELECT id FROM users WHERE LOWER(email) = $1", email,
|
||||||
).Scan(&userID)
|
).Scan(&userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "If the email exists, a verification code will be sent"})
|
json.NewEncoder(w).Encode(VerificationResponse{Success: true, Message: "If the email exists, a verification code will be sent"})
|
||||||
return
|
return
|
||||||
@@ -576,7 +576,7 @@ func VerifyCodeHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
code,
|
code,
|
||||||
).Scan(&userID, &purpose, &expiresAt)
|
).Scan(&userID, &purpose, &expiresAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
// Check if code exists but was already used or expired
|
// Check if code exists but was already used or expired
|
||||||
var checkUsedAt *time.Time
|
var checkUsedAt *time.Time
|
||||||
checkErr := db.DB.QueryRow(r.Context(),
|
checkErr := db.DB.QueryRow(r.Context(),
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package bookings
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crussell/db"
|
"crussell/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"crussell/handlers/scheduling"
|
"crussell/handlers/scheduling"
|
||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -111,7 +111,7 @@ func AdminReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
weekday := int((localStart.Weekday() + 6) % 7)
|
weekday := int((localStart.Weekday() + 6) % 7)
|
||||||
var closeStr string
|
var closeStr string
|
||||||
if err := db.DB.QueryRow(r.Context(), `SELECT end_time::text FROM working_hours WHERE weekday = $1`, weekday).Scan(&closeStr); err != nil {
|
if err := db.DB.QueryRow(r.Context(), `SELECT end_time::text FROM working_hours WHERE weekday = $1`, weekday).Scan(&closeStr); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "Not open on this day", http.StatusBadRequest)
|
http.Error(w, "Not open on this day", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crussell/auth"
|
"crussell/auth"
|
||||||
"crussell/db"
|
"crussell/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"crussell/internal/validators"
|
"crussell/internal/validators"
|
||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
@@ -407,7 +408,7 @@ func ServicesEligibleForUserHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Get user's date of birth
|
// Get user's date of birth
|
||||||
var dob time.Time
|
var dob time.Time
|
||||||
err := db.DB.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
err := db.DB.QueryRow(r.Context(), `SELECT date_of_birth FROM users WHERE id = $1`, userID).Scan(&dob)
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "user not found", http.StatusNotFound)
|
http.Error(w, "user not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"crussell/internal/dav"
|
"crussell/internal/dav"
|
||||||
"crussell/internal/s3"
|
"crussell/internal/s3"
|
||||||
"crussell/mw"
|
"crussell/mw"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DELETE /api/user/account
|
// DELETE /api/user/account
|
||||||
@@ -28,7 +30,7 @@ func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
err := db.DB.QueryRow(r.Context(), `SELECT account_role, profile_pic_url FROM users WHERE id = $1`, userID).
|
err := db.DB.QueryRow(r.Context(), `SELECT account_role, profile_pic_url FROM users WHERE id = $1`, userID).
|
||||||
Scan(&accountRole, &profilePicURL)
|
Scan(&accountRole, &profilePicURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
http.Error(w, "user not found", http.StatusNotFound)
|
http.Error(w, "user not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
"crussell/db"
|
"crussell/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"crussell/internal/validators"
|
"crussell/internal/validators"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ func GetCustomerRelationshipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
LEFT JOIN payments p ON p.booking_id = b.id
|
LEFT JOIN payments p ON p.booking_id = b.id
|
||||||
WHERE b.user_id = $1
|
WHERE b.user_id = $1
|
||||||
`, userID).Scan(&result.TotalSpend, &result.TotalSaved, &result.TotalTips, &result.TotalVisits, &firstVisit, &lastVisit)
|
`, userID).Scan(&result.TotalSpend, &result.TotalSaved, &result.TotalTips, &result.TotalVisits, &firstVisit, &lastVisit)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
log.Printf("Failed to get customer relationship data for user %s: %v", userID, err)
|
log.Printf("Failed to get customer relationship data for user %s: %v", userID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ func GetCustomerRelationshipHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ORDER BY count DESC
|
ORDER BY count DESC
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
`, userID)
|
`, userID)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
|
||||||
log.Printf("Failed to get top services for user %s: %v", userID, err)
|
log.Printf("Failed to get top services for user %s: %v", userID, err)
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
@@ -12,6 +11,7 @@ import (
|
|||||||
"crussell/db"
|
"crussell/db"
|
||||||
"crussell/handlers/auth"
|
"crussell/handlers/auth"
|
||||||
"crussell/internal/validators"
|
"crussell/internal/validators"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateGuestUserRequest struct {
|
type CreateGuestUserRequest struct {
|
||||||
@@ -172,7 +172,7 @@ func CheckEmailHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
s := "check"
|
s := "check"
|
||||||
suggestion = &s
|
suggestion = &s
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
} else if !errors.Is(err, pgx.ErrNoRows) {
|
||||||
log.Printf("Failed to check email: %v", err)
|
log.Printf("Failed to check email: %v", err)
|
||||||
http.Error(w, "database error", http.StatusInternalServerError)
|
http.Error(w, "database error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user