package verification import ( "context" "errors" "strings" "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" "git.sendico.io/tech/sxchnge/bff/pkg/model" mutil "git.sendico.io/tech/sxchnge/bff/pkg/mutil/db" "go.mongodb.org/mongo-driver/v2/bson" ) func normalizedIdempotencyKey(value *string) (string, bool) { if value == nil { return "", false } key := strings.TrimSpace(*value) if key == "" { return "", false } return key, true } func syntheticIdempotencyKey() string { return "auto:" + bson.NewObjectID().Hex() } func idempotencyContextFilter( request *Request, idempotencyKey string, ) builder.Query { return repository.Query().And( repository.Filter("accountRef", request.AccountRef), repository.Filter("purpose", request.Purpose), repository.Filter("target", request.Target), repository.Filter("idempotencyKey", idempotencyKey), ) } func idempotencySeed(request *Request, idempotencyKey string) string { return strings.Join([]string{ request.AccountRef.Hex(), string(request.Purpose), request.Target, request.ContextRef, request.Kind, idempotencyKey, }, "|") } func (db *verificationDB) lookupIdempotencyReplay(ctx context.Context, request *Request, idempotencyKey string) (*model.VerificationToken, bool, error) { tokens, err := mutil.GetObjects[model.VerificationToken]( ctx, db.Logger, idempotencyContextFilter(request, idempotencyKey), nil, db.Repository, ) switch { case err == nil: case errors.Is(err, merrors.ErrNoData): return nil, false, nil default: return nil, false, err } var replayed *model.VerificationToken for i := range tokens { existingContextRef := strings.TrimSpace(tokens[i].ContextRef) // The lookup intentionally ignores contextRef. A reused idempotency key // in another context is a client conflict, not a replay. if existingContextRef != request.ContextRef { return nil, false, ErrorIdempotencyConflict() } if replayed == nil { token := tokens[i] replayed = &token } } return replayed, replayed != nil, nil }