fix quota reset reservation race
Desktop Client Build / Resolve Build Metadata (push) Successful in 28s
Frontend CI / Frontend (push) Successful in 3m57s
Backend CI / Backend (push) Successful in 17m3s
Desktop Client Build / Build Desktop Client (push) Successful in 26m8s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 51s

This commit is contained in:
2026-05-12 02:24:57 +08:00
parent 1aad002a3e
commit 01fa2b8309
13 changed files with 337 additions and 168 deletions
@@ -13,7 +13,7 @@ import (
const confirmReservation = `-- name: ConfirmReservation :exec
UPDATE quota_reservations SET status = 'confirmed', consumed_amount = reserved_amount, updated_at = NOW()
WHERE id = $1 AND tenant_id = $2
WHERE id = $1 AND tenant_id = $2 AND status = 'pending'
`
type ConfirmReservationParams struct {
@@ -114,7 +114,7 @@ func (q *Queries) InsertQuotaLedger(ctx context.Context, arg InsertQuotaLedgerPa
const refundReservation = `-- name: RefundReservation :exec
UPDATE quota_reservations SET status = 'refunded', refunded_amount = reserved_amount, updated_at = NOW()
WHERE id = $1 AND tenant_id = $2
WHERE id = $1 AND tenant_id = $2 AND status = 'pending'
`
type RefundReservationParams struct {
@@ -0,0 +1,19 @@
package generated
import (
"strings"
"testing"
)
func TestQuotaReservationTransitionsOnlyUpdatePending(t *testing.T) {
for name, query := range map[string]string{
"confirm": confirmReservation,
"refund": refundReservation,
} {
t.Run(name, func(t *testing.T) {
if !strings.Contains(query, "status = 'pending'") {
t.Fatalf("%s reservation query must only update pending reservations; query:\n%s", name, query)
}
})
}
}
@@ -23,8 +23,8 @@ WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: ConfirmReservation :exec
UPDATE quota_reservations SET status = 'confirmed', consumed_amount = reserved_amount, updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id) AND status = 'pending';
-- name: RefundReservation :exec
UPDATE quota_reservations SET status = 'refunded', refunded_amount = reserved_amount, updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id) AND status = 'pending';
@@ -59,6 +59,7 @@ type QuotaRepository interface {
UpdateQuotaReservationResource(ctx context.Context, reservationID, tenantID, resourceID int64) error
ConfirmReservation(ctx context.Context, reservationID, tenantID int64) error
RefundReservation(ctx context.Context, reservationID, tenantID int64) error
RefundPendingReservation(ctx context.Context, reservationID, tenantID int64) (bool, error)
}
type quotaRepository struct {
@@ -256,3 +257,19 @@ func (r *quotaRepository) RefundReservation(ctx context.Context, reservationID,
TenantID: tenantID,
})
}
func (r *quotaRepository) RefundPendingReservation(ctx context.Context, reservationID, tenantID int64) (bool, error) {
tag, err := r.db.Exec(ctx, `
UPDATE quota_reservations
SET status = 'refunded',
refunded_amount = reserved_amount,
updated_at = NOW()
WHERE id = $1
AND tenant_id = $2
AND status = 'pending'
`, reservationID, tenantID)
if err != nil {
return false, err
}
return tag.RowsAffected() > 0, nil
}