package admin import ( "context" "strconv" "time" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/Wei-Shaw/sub2api/internal/pkg/logger" "github.com/Wei-Shaw/sub2api/internal/pkg/response" middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" ) type idempotencyStoreUnavailableMode int const ( idempotencyStoreUnavailableFailClose idempotencyStoreUnavailableMode = iota idempotencyStoreUnavailableFailOpen ) func executeAdminIdempotent( c *gin.Context, scope string, payload any, ttl time.Duration, execute func(context.Context) (any, error), ) (*service.IdempotencyExecuteResult, error) { coordinator := service.DefaultIdempotencyCoordinator() if coordinator == nil { data, err := execute(c.Request.Context()) if err != nil { return nil, err } return &service.IdempotencyExecuteResult{Data: data}, nil } actorScope := "admin:0" if subject, ok := middleware2.GetAuthSubjectFromContext(c); ok { actorScope = "admin:" + strconv.FormatInt(subject.UserID, 10) } return coordinator.Execute(c.Request.Context(), service.IdempotencyExecuteOptions{ Scope: scope, ActorScope: actorScope, Method: c.Request.Method, Route: c.FullPath(), IdempotencyKey: c.GetHeader("Idempotency-Key"), Payload: payload, RequireKey: true, TTL: ttl, }, execute) } func executeAdminIdempotentJSON( c *gin.Context, scope string, payload any, ttl time.Duration, execute func(context.Context) (any, error), ) { executeAdminIdempotentJSONWithMode(c, scope, payload, ttl, idempotencyStoreUnavailableFailClose, execute) } func executeAdminIdempotentJSONFailOpenOnStoreUnavailable( c *gin.Context, scope string, payload any, ttl time.Duration, execute func(context.Context) (any, error), ) { executeAdminIdempotentJSONWithMode(c, scope, payload, ttl, idempotencyStoreUnavailableFailOpen, execute) } func executeAdminIdempotentJSONWithMode( c *gin.Context, scope string, payload any, ttl time.Duration, mode idempotencyStoreUnavailableMode, execute func(context.Context) (any, error), ) { result, err := executeAdminIdempotent(c, scope, payload, ttl, execute) if err != nil { if infraerrors.Code(err) == infraerrors.Code(service.ErrIdempotencyStoreUnavail) { strategy := "fail_close" if mode == idempotencyStoreUnavailableFailOpen { strategy = "fail_open" } service.RecordIdempotencyStoreUnavailable(c.FullPath(), scope, "handler_"+strategy) logger.LegacyPrintf("handler.idempotency", "[Idempotency] store unavailable: method=%s route=%s scope=%s strategy=%s", c.Request.Method, c.FullPath(), scope, strategy) if mode == idempotencyStoreUnavailableFailOpen { data, fallbackErr := execute(c.Request.Context()) if fallbackErr != nil { response.ErrorFrom(c, fallbackErr) return } c.Header("X-Idempotency-Degraded", "store-unavailable") response.Success(c, data) return } } if retryAfter := service.RetryAfterSecondsFromError(err); retryAfter > 0 { c.Header("Retry-After", strconv.Itoa(retryAfter)) } response.ErrorFrom(c, err) return } if result != nil && result.Replayed { c.Header("X-Idempotency-Replayed", "true") } response.Success(c, result.Data) }