66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package handler
|
|
|
|
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"
|
|
)
|
|
|
|
func executeUserIdempotentJSON(
|
|
c *gin.Context,
|
|
scope string,
|
|
payload any,
|
|
ttl time.Duration,
|
|
execute func(context.Context) (any, error),
|
|
) {
|
|
coordinator := service.DefaultIdempotencyCoordinator()
|
|
if coordinator == nil {
|
|
data, err := execute(c.Request.Context())
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, data)
|
|
return
|
|
}
|
|
|
|
actorScope := "user:0"
|
|
if subject, ok := middleware2.GetAuthSubjectFromContext(c); ok {
|
|
actorScope = "user:" + strconv.FormatInt(subject.UserID, 10)
|
|
}
|
|
|
|
result, err := 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)
|
|
if err != nil {
|
|
if infraerrors.Code(err) == infraerrors.Code(service.ErrIdempotencyStoreUnavail) {
|
|
service.RecordIdempotencyStoreUnavailable(c.FullPath(), scope, "handler_fail_close")
|
|
logger.LegacyPrintf("handler.idempotency", "[Idempotency] store unavailable: method=%s route=%s scope=%s strategy=fail_close", c.Request.Method, c.FullPath(), scope)
|
|
}
|
|
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)
|
|
}
|