85 lines
2.2 KiB
Go
85 lines
2.2 KiB
Go
package verification
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model"
|
|
"strings"
|
|
|
|
mutil "git.sendico.io/tech/sxchnge/shared/database/query"
|
|
"git.sendico.io/tech/sxchnge/shared/database/repository"
|
|
"git.sendico.io/tech/sxchnge/shared/database/repository/builder"
|
|
"git.sendico.io/tech/sxchnge/shared/errors"
|
|
"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) (*verificationmodel.VerificationToken, bool, error) {
|
|
tokens, err := mutil.GetObjects[verificationmodel.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 *verificationmodel.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
|
|
}
|