fix: restore S3 error stubs, add CI timeouts, gofmt handlers.go, fix comment
CI / Env docs check (push) Successful in 14s
CI / Docker compose check (push) Successful in 20s
CI / Nginx config check (push) Successful in 20s
CI / Frontend major deps (push) Failing after 30s
CI / Frontend deps check (push) Successful in 31s
CI / Secrets scan (push) Successful in 38s
CI / Go build (push) Successful in 38s
CI / Frontend build (push) Successful in 42s
CI / Knip (push) Successful in 40s
CI / Frontend a11y check (push) Successful in 2m2s
CI / Go vet (prod) (push) Successful in 1m56s
CI / Go vet (dev) (push) Successful in 2m7s
CI / Staticcheck (prod) (push) Successful in 2m51s
CI / Staticcheck (dev) (push) Successful in 3m3s
CI / go mod tidy (push) Successful in 1m17s
CI / Frontend QC (audit) (push) Successful in 1m34s
CI / golangci-lint (push) Successful in 3m44s
CI / Go vulnerabilities (push) Successful in 2m10s
CI / Frontend QC (typecheck) (push) Successful in 1m29s
CI / Security scan (prod) (push) Successful in 4m3s
CI / Security scan (dev) (push) Successful in 4m37s
CI / Frontend QC (lint) (push) Successful in 1m40s
CI / Svelte strict check (push) Successful in 1m22s
CI / Tests (prod) (push) Successful in 3m12s
CI / Tests (dev) (push) Successful in 3m32s
CI / Race (prod) (push) Successful in 7m12s
CI / Race (dev) (push) Successful in 7m22s

- s3.go: restore error returns on prod Upload/Download/Delete stubs
  (silent nil was dangerous — callers would think writes succeeded)
- CI: add -timeout 300s to tests, -timeout 480s to race, GO_TESTING to race env
- handlers.go: gofmt indentation fix for GetCheckoutStatus struct literal
- validators.go: fix comment to include A-F (matched the regex)
This commit is contained in:
2026-07-10 20:39:17 +01:00
parent e896591098
commit a4a80246d1
4 changed files with 68 additions and 70 deletions
+3 -2
View File
@@ -449,7 +449,7 @@ jobs:
- name: Run tests
working-directory: backend
run: go test -tags "${{ matrix.gotags }}" -count=1 ${{ matrix.verbose }} ${{ matrix.coverflags }} ./...
run: go test -tags "${{ matrix.gotags }}" -count=1 -timeout 300s ${{ matrix.verbose }} ${{ matrix.coverflags }} ./...
env:
GO_TESTING: "1"
POSTGRES_HOST: postgres
@@ -531,8 +531,9 @@ jobs:
- name: Run race detector
working-directory: backend
run: go test -tags "${{ matrix.gotags }}" -race -count=1 ./...
run: go test -tags "${{ matrix.gotags }}" -race -count=1 -timeout 480s ./...
env:
GO_TESTING: "1"
POSTGRES_HOST: postgres
TEST_DB_HOST: postgres
CGO_ENABLED: "1"
+37 -36
View File
@@ -141,7 +141,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var bookingTotal float64
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `
SELECT total_amount FROM bookings WHERE id = $1
`, bookingID).Scan(&bookingTotal)
@@ -164,7 +164,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
ORDER BY discount_percent DESC LIMIT 1
`).Scan(&campaignID, &campaignPercent, &campaignName); err == nil && campaignID != "" {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, campaignID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * campaignPercent / 100)
@@ -179,13 +179,13 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var userBookingCount int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&userBookingCount)
var milestoneCampaignID string
var milestonePercent float64
var milestoneName string
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `
SELECT id, discount_percent, name FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count'
@@ -195,7 +195,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
if milestoneCampaignID != "" {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, milestoneCampaignID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * milestonePercent / 100)
@@ -210,7 +210,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
}
var firstVisitDate time.Time
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT MIN(start_time) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&firstVisitDate)
if !firstVisitDate.IsZero() {
type annCamp struct {
@@ -237,7 +237,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
for _, c := range campaigns {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists)
if exists > 0 {
continue
@@ -274,7 +274,7 @@ func calculateDiscountPreview(ctx context.Context, bookingID string, userID stri
LIMIT 1
`, userID).Scan(&rdID, &rdPercent); err == nil && rdID != "" {
exists := 0
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = db.Conn.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'referral' AND source_id = $2`, bookingID, rdID).Scan(&exists)
if exists == 0 {
amount := roundTo2(bookingTotal * rdPercent / 100)
@@ -354,7 +354,7 @@ func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
// Check booking status inside the transaction.
var status string
@@ -626,7 +626,7 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
// Check for existing payment inside the transaction.
var existingID string
@@ -825,7 +825,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
var status string
if err := tx.QueryRow(r.Context(), `SELECT status FROM bookings WHERE id = $1`, bookingID).Scan(&status); err != nil {
@@ -1021,7 +1021,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
// Promote deposit to confirmed if total paid meets the 20% threshold.
// Check is inside the transaction so it sees the just-inserted payments.
var depositMet bool
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = tx.QueryRow(r.Context(), `
WITH booking_total AS (
SELECT total_amount * 100 AS total_cents FROM bookings WHERE id = $1
@@ -1077,7 +1077,7 @@ func CreateBookingPayment(w http.ResponseWriter, r *http.Request) {
// would create a credit balance or require a refund.
func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingID string, userID string) {
var existingPayment int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `
SELECT COUNT(*) FROM payments
WHERE booking_id = $1 AND status = 'completed' AND payment_method NOT IN ('discount', 'on_the_house')
@@ -1110,7 +1110,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
ORDER BY discount_percent DESC LIMIT 1
`).Scan(&campaignID, &campaignPercent); err == nil && campaignID != "" {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, campaignID).Scan(&exists)
if exists == 0 {
discountAmount := roundTo2(bookingTotal * campaignPercent / 100)
@@ -1120,12 +1120,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
`, bookingID, userID, campaignID, campaignPercent, bookingTotal, discountAmount); err != nil {
log.Printf("Failed to insert time-based campaign discount: %v", err)
} else {
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by)
VALUES ($1, 'partial', 'discount', $2, 'completed', $3)
`, bookingID, discountAmount, userID)
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
UPDATE discount_campaigns SET times_redeemed = times_redeemed + 1 WHERE id = $1
`, campaignID)
@@ -1134,12 +1134,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
}
var userBookingCount int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&userBookingCount)
var milestoneCampaignID string
var milestonePercent float64
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `
SELECT id, discount_percent FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'per_user_booking_count'
@@ -1149,7 +1149,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
if milestoneCampaignID != "" {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, milestoneCampaignID).Scan(&exists)
if exists == 0 {
discountAmount := roundTo2(bookingTotal * milestonePercent / 100)
@@ -1159,12 +1159,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
`, bookingID, userID, milestoneCampaignID, milestonePercent, bookingTotal, discountAmount); err != nil {
log.Printf("Failed to insert per-user milestone discount: %v", err)
} else {
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by)
VALUES ($1, 'partial', 'discount', $2, 'completed', $3)
`, bookingID, discountAmount, userID)
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
UPDATE discount_campaigns SET times_redeemed = times_redeemed + 1 WHERE id = $1
`, milestoneCampaignID)
@@ -1173,7 +1173,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
}
var firstVisitDate time.Time
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT MIN(start_time) FROM bookings WHERE user_id = $1 AND status = 'completed'`, userID).Scan(&firstVisitDate)
if !firstVisitDate.IsZero() {
annRows, err := q.Query(ctx, `
@@ -1199,7 +1199,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
for _, c := range campaigns {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, c.id).Scan(&exists)
if exists > 0 {
continue
@@ -1223,12 +1223,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
`, bookingID, userID, c.id, c.pct, bookingTotal, discountAmount); err != nil {
log.Printf("Failed to insert anniversary discount: %v", err)
} else {
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by)
VALUES ($1, 'partial', 'discount', $2, 'completed', $3)
`, bookingID, discountAmount, userID)
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
UPDATE discount_campaigns SET times_redeemed = times_redeemed + 1 WHERE id = $1
`, c.id)
@@ -1246,12 +1246,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
SELECT payment_method FROM payments WHERE booking_id = $1 AND payment_method NOT IN ('discount', 'on_the_house') ORDER BY created_at ASC LIMIT 1
`, bookingID).Scan(&firstPaymentMethod); err == nil && firstPaymentMethod == "in_person_card" {
var globalCount int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT COUNT(*) FROM bookings WHERE status = 'completed'`).Scan(&globalCount)
var globalCampaignID string
var globalPercent float64
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `
SELECT id, discount_percent FROM discount_campaigns
WHERE status = 'active' AND campaign_type = 'milestone' AND milestone_type = 'global_booking_count'
@@ -1263,7 +1263,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
if globalCampaignID != "" {
var exists int
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND source_id = $2`, bookingID, globalCampaignID).Scan(&exists)
if exists == 0 {
discountAmount := roundTo2(bookingTotal * globalPercent / 100)
@@ -1273,12 +1273,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
`, bookingID, userID, globalCampaignID, globalPercent, bookingTotal, discountAmount); err != nil {
log.Printf("Failed to insert global milestone discount: %v", err)
} else {
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by)
VALUES ($1, 'partial', 'discount', $2, 'completed', $3)
`, bookingID, discountAmount, userID)
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
UPDATE discount_campaigns SET times_redeemed = times_redeemed + 1 WHERE id = $1
`, globalCampaignID)
@@ -1297,7 +1297,7 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
LIMIT 1
`, userID).Scan(&rdID, &rdPercent); err == nil && rdID != "" {
exists := 0
//nolint:errcheck // zero value is acceptable fallback on scan failure
//nolint:errcheck // zero value is acceptable fallback on scan failure
_ = q.QueryRow(ctx, `SELECT 1 FROM booking_discounts WHERE booking_id = $1 AND discount_source = 'referral' AND source_id = $2`, bookingID, rdID).Scan(&exists)
if exists == 0 {
discountAmount := roundTo2(bookingTotal * rdPercent / 100)
@@ -1305,12 +1305,12 @@ func applyEligibleCampaignsAtPayment(ctx context.Context, q db.Querier, bookingI
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, campaign_type, milestone_type, discount_percent, original_total, discount_amount)
VALUES ($1, $2, 'referral', $3, NULL, NULL, $4, $5, $6)
`, bookingID, userID, rdID, rdPercent, bookingTotal, discountAmount); err == nil {
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
INSERT INTO payments (booking_id, payment_type, payment_method, amount, status, created_by)
VALUES ($1, 'partial', 'discount', $2, 'completed', $3)
`, bookingID, discountAmount, userID)
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
//nolint:errcheck // exec errors are non-critical; best-effort inserts/updates
_, _ = q.Exec(ctx, `
UPDATE referral_discounts SET used = TRUE, used_at = NOW() WHERE id = $1
`, rdID)
@@ -1422,6 +1422,7 @@ func buildSplitRecords(primary PaymentRecord, reqPaymentType string, info *Booki
// split payment, following the same rules as the frontend's handlePayFull:
// 'balance' when some payment already exists, 'full' when covering everything,
// 'partial' when leaving a remainder.
//
//lint:ignore U1000 reserved for future use
func nonDepositPaymentType(reqType string, totalPaidAfterThis float64, thisPortion float64, bookingTotal float64) string {
if totalPaidAfterThis >= bookingTotal {
@@ -1622,7 +1623,7 @@ func RefundPayment(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
refundReq := square.RefundPaymentReq{
PaymentID: *payment.SquarePaymentID,
@@ -1968,7 +1969,7 @@ func AcquirePaymentLock(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
if _, err := tx.Exec(r.Context(), `
DELETE FROM time_blockers
@@ -2019,7 +2020,7 @@ func ReleasePaymentLock(w http.ResponseWriter, r *http.Request) {
if err := tx.Rollback(r.Context()); err != nil && err.Error() != "tx is closed" {
slog.Error("failed to rollback transaction", "err", err)
}
}()
}()
if _, err := tx.Exec(r.Context(), `
DELETE FROM time_blockers
+3 -7
View File
@@ -50,19 +50,15 @@ func Connect() error {
}
func (s *S3Client) Upload(ctx context.Context, bucket, key string, body io.Reader, contentType string) error {
// TODO: Wire in AWS SDK v2 for production S3 uploads.
// Currently only available under //go:build dev via RUSTFS.
return nil
return fmt.Errorf("not implemented: production S3 upload requires AWS SDK v2 (use RUSTFS in dev)")
}
func (s *S3Client) Download(ctx context.Context, bucket, key string, w io.Writer) error {
// TODO: Wire in AWS SDK v2 for production S3 downloads.
return nil
return fmt.Errorf("not implemented: production S3 download requires AWS SDK v2 (use RUSTFS in dev)")
}
func (s *S3Client) Delete(ctx context.Context, bucket, key string) error {
// TODO: Wire in AWS SDK v2 for production S3 deletes.
return nil
return fmt.Errorf("not implemented: production S3 delete requires AWS SDK v2 (use RUSTFS in dev)")
}
func (s *S3Client) GetURL(ctx context.Context, bucket, key string) (string, error) {
+1 -1
View File
@@ -27,7 +27,7 @@ func init() {
var validIDRegex = regexp.MustCompile(`^[0-9a-fA-F]{12}$`)
// IsValidID checks if an ID is valid based on the database constraint (CHAR(12) hex string)
// Valid IDs are exactly 12 hexadecimal characters (0-9, a-f)
// Valid IDs are exactly 12 hexadecimal characters (0-9, a-f, A-F)
func IsValidID(id string) bool {
if id == "" {
return false