From da2bbf370d505bd0ee8d7f632e4aae08d3e87686 Mon Sep 17 00:00:00 2001 From: oren Date: Wed, 28 Dec 2022 20:50:06 -0500 Subject: [PATCH 01/14] remove reactive returns in business rules --- .../cmd/keyservice/businessrules/userkeybr.go | 133 ++++++++---------- .../keyservice/services/userkey-service.go | 18 ++- .../cmd/noteservice/businessrules/notebr.go | 95 +++++-------- .../cmd/noteservice/services/note-service.go | 35 +++-- .../cmd/userservice/businessrules/userbr.go | 52 ++++--- .../cmd/userservice/services/user-service.go | 18 +-- .../validationutils/validation-utils.go | 54 +++---- 7 files changed, 179 insertions(+), 226 deletions(-) diff --git a/microservices/go/cmd/keyservice/businessrules/userkeybr.go b/microservices/go/cmd/keyservice/businessrules/userkeybr.go index b0fce2a..f5d6238 100644 --- a/microservices/go/cmd/keyservice/businessrules/userkeybr.go +++ b/microservices/go/cmd/keyservice/businessrules/userkeybr.go @@ -13,16 +13,16 @@ import ( ) type UserKeyBr interface { - ValidateSessionTokenHash(session models.UserKeySession, tokenBytes []byte) single.Single[any] - ValidateKeyFromSession(userKeyGen models.UserKeyGenerator, key []byte) single.Single[any] - ValidateKeyFromPassword(userKeyGen models.UserKeyGenerator, key []byte) single.Single[any] + ValidateSessionTokenHash(session models.UserKeySession, tokenBytes []byte) error + ValidateKeyFromSession(userKeyGen models.UserKeyGenerator, key []byte) error + ValidateKeyFromPassword(userKeyGen models.UserKeyGenerator, key []byte) error ValidateProxyKeyCiphersFromSession( ctx context.Context, proxyKey []byte, userId string, keyVersion int64, session models.UserKeySession, - ) single.Single[any] + ) error } type UserKeyBrImpl struct { @@ -30,33 +30,31 @@ type UserKeyBrImpl struct { userService sharedservices.UserService } -func (u UserKeyBrImpl) ValidateSessionTokenHash(session models.UserKeySession, tokenBytes []byte) single.Single[any] { - hashCheckSrc := single.FromSupplierCached(func() (bool, error) { - return cipherutils.VerifyHashWithSaltSHA256(session.TokenHash, tokenBytes) - }) - tokenHashValidate := single.Map(hashCheckSrc, func(verified bool) []apperrors.RuleError { - var ruleErrs []apperrors.RuleError - if !verified { - ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) - } - return ruleErrs - }) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(tokenHashValidate) +func (u UserKeyBrImpl) ValidateSessionTokenHash(session models.UserKeySession, tokenBytes []byte) error { + var ruleErrs []apperrors.RuleError + verified, err := cipherutils.VerifyHashWithSaltSHA256(session.TokenHash, tokenBytes) + if err != nil { + return err + } + if !verified { + ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) + } + return validationutils.MergeAppErrors(ruleErrs) } -func (u UserKeyBrImpl) ValidateKeyFromSession(userKeyGen models.UserKeyGenerator, key []byte) single.Single[any] { - verifiedKeyHashSrc := single.FromSupplierCached(func() (bool, error) { - return cipherutils.VerifyKeyHashBcrypt(userKeyGen.KeyHash, key) - }) - keyHashValidationSrc := single.Map(verifiedKeyHashSrc, func(verified bool) []apperrors.RuleError { - var ruleErrs []apperrors.RuleError - if !verified { - ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) - } - return ruleErrs - }) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(keyHashValidationSrc) +func (u UserKeyBrImpl) ValidateKeyFromSession(userKeyGen models.UserKeyGenerator, key []byte) error { + var ruleErrs []apperrors.RuleError + + verified, err := cipherutils.VerifyKeyHashBcrypt(userKeyGen.KeyHash, key) + if err != nil { + return err + } + if !verified { + ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) + } + + return validationutils.MergeAppErrors(ruleErrs) } func (u UserKeyBrImpl) ValidateProxyKeyCiphersFromSession( @@ -65,56 +63,45 @@ func (u UserKeyBrImpl) ValidateProxyKeyCiphersFromSession( userId string, keyVersion int64, session models.UserKeySession, -) single.Single[any] { - validateProxyKeyCiphersSrc := single.FromSupplierCached(func() ([]apperrors.RuleError, error) { - var ruleErrs []apperrors.RuleError +) error { + var ruleErrs []apperrors.RuleError + + // Validate Proxy Key Ciphers + savedUserIdBytes, err := cipherutils.DecryptAES(proxyKey, session.UserIdCipher) + if err != nil { + logger.Log.WithError(err).Debug() + return err + } + userIdInvalid := string(savedUserIdBytes) != userId + savedKeyVersionBytes, err := cipherutils.DecryptAES(proxyKey, session.KeyVersionCipher) + if err != nil { + logger.Log.WithError(err).Debug() + return err + } + keyInvalid := string(savedKeyVersionBytes) != utils.Int64ToStr(keyVersion) - savedUserIdBytes, err := cipherutils.DecryptAES(proxyKey, session.UserIdCipher) - if err != nil { - logger.Log.WithError(err).Debug() - return ruleErrs, err - } - userIdInvalid := string(savedUserIdBytes) != userId + if userIdInvalid || keyInvalid { + ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) + } - savedKeyVersionBytes, err := cipherutils.DecryptAES(proxyKey, session.KeyVersionCipher) - if err != nil { - logger.Log.WithError(err).Debug() - return ruleErrs, err - } - keyInvalid := string(savedKeyVersionBytes) != utils.Int64ToStr(keyVersion) + // Validate User Exists + exists, err := single.RetrieveValue(ctx, u.userService.UserExistsWithId(ctx, userId)) + if !exists { + ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) + } - if userIdInvalid || keyInvalid { - ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) - } - return ruleErrs, nil - }) - validateUserExists := single.MapWithError(u.userService.UserExistsWithId(ctx, userId), - func(exists bool) ([]apperrors.RuleError, error) { - var ruleErrs []apperrors.RuleError - if !exists { - ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) - } - return ruleErrs, nil - }) - ruleErrs := validationutils.ConcatSinglesOfRuleErrs(validateProxyKeyCiphersSrc, validateUserExists) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrs) + return validationutils.MergeAppErrors(ruleErrs) } -func (u UserKeyBrImpl) ValidateKeyFromPassword( - userKeyGen models.UserKeyGenerator, - key []byte, -) single.Single[any] { - verifiedKeyHashSrc := single.FromSupplierCached(func() (bool, error) { - return cipherutils.VerifyKeyHashBcrypt(userKeyGen.KeyHash, key) - }) - keyHashValidationSrc := single.Map(verifiedKeyHashSrc, func(verified bool) []apperrors.RuleError { - var ruleErrs []apperrors.RuleError - if !verified { - ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeIncorrectPasscode)) - } - return ruleErrs - }) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(keyHashValidationSrc) +func (u UserKeyBrImpl) ValidateKeyFromPassword(userKeyGen models.UserKeyGenerator, key []byte) error { + var ruleErrs []apperrors.RuleError + verified, err := cipherutils.VerifyKeyHashBcrypt(userKeyGen.KeyHash, key) + if err != nil { + return err + } else if !verified { + ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeIncorrectPasscode)) + } + return validationutils.MergeAppErrors(ruleErrs) } func NewUserKeyBrImpl(errorService sharedservices.ErrorService, userService sharedservices.UserService) *UserKeyBrImpl { diff --git a/microservices/go/cmd/keyservice/services/userkey-service.go b/microservices/go/cmd/keyservice/services/userkey-service.go index a05b974..2fd76a2 100644 --- a/microservices/go/cmd/keyservice/services/userkey-service.go +++ b/microservices/go/cmd/keyservice/services/userkey-service.go @@ -118,8 +118,9 @@ func (u UserKeyServiceImpl) NewKeySession( key, _, err := cipherutils.DeriveAESKeyFromPassword([]byte(dto.Passcode), userKeyGen.KeyDerivationSalt) return key, err }) - keyValidated := single.FlatMap(newKeySrc, func(key []byte) single.Single[any] { - return u.userKeyBr.ValidateKeyFromPassword(userKeyGen, key) + keyValidated := single.MapWithError(newKeySrc, func(key []byte) (any, error) { + err := u.userKeyBr.ValidateKeyFromPassword(userKeyGen, key) + return any(true), err }) return single.FlatMap(keyValidated, func(_ any) single.Single[[]byte] { return newKeySrc @@ -200,10 +201,10 @@ func (u UserKeyServiceImpl) GetKeyFromSession( ).ScheduleLazyAndCache(ctx) tokenBytesSrc := single.MapWithError(single.Just(sessionDto.Token), encodingutils.DecodeBase64String) - tokenHashVerifiedSrc := single.FlatMap(single.Zip2(storedSessionSrc, tokenBytesSrc), - func(t tuple.T2[models.UserKeySession, []byte]) single.Single[any] { + tokenHashVerifiedSrc := single.MapWithError(single.Zip2(storedSessionSrc, tokenBytesSrc), + func(t tuple.T2[models.UserKeySession, []byte]) (any, error) { session, tokenBytes := t.V1, t.V2 - return u.userKeyBr.ValidateSessionTokenHash(session, tokenBytes) + return any(true), u.userKeyBr.ValidateSessionTokenHash(session, tokenBytes) }, ) appSecretSrc := single.FlatMap(single.Zip2(tokenHashVerifiedSrc, storedSessionSrc), @@ -220,14 +221,17 @@ func (u UserKeyServiceImpl) GetKeyFromSession( keyBytesSrc := single.FlatMap(single.Zip2(storedSessionSrc, proxyKeySrc), func(t tuple.T2[models.UserKeySession, []byte]) single.Single[[]byte] { session, proxyKey := t.V1, t.V2 - validateProxyKeysSrc := u.userKeyBr.ValidateProxyKeyCiphersFromSession( + err := u.userKeyBr.ValidateProxyKeyCiphersFromSession( ctx, proxyKey, sessionDto.UserId, sessionDto.KeyVersion, session, ) - return single.MapWithError(validateProxyKeysSrc, func(_ any) ([]byte, error) { + if err != nil { + return single.Error[[]byte](err) + } + return single.FromSupplierCached(func() ([]byte, error) { return cipherutils.DecryptAES(proxyKey, session.KeyCipher) }) }, diff --git a/microservices/go/cmd/noteservice/businessrules/notebr.go b/microservices/go/cmd/noteservice/businessrules/notebr.go index f800b46..5752deb 100644 --- a/microservices/go/cmd/noteservice/businessrules/notebr.go +++ b/microservices/go/cmd/noteservice/businessrules/notebr.go @@ -7,19 +7,14 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/pagination" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" ) type NoteBr interface { - ValidateNoteUpdate( - userBo userbos.UserBo, - keyDto keydtos.UserKeyDto, - existing models.Note, - ) single.Single[any] - ValidateNoteRead(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) single.Single[any] - ValidateNoteDelete(userBo userbos.UserBo, existing models.Note) single.Single[any] - ValidateGetNotes(pageRequest pagination.PageRequest) single.Single[any] + ValidateNoteUpdate(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error + ValidateNoteRead(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error + ValidateNoteDelete(userBo userbos.UserBo, existing models.Note) error + ValidateGetNotes(pageRequest pagination.PageRequest) error } type NoteBrImpl struct { @@ -27,70 +22,44 @@ type NoteBrImpl struct { validSortFields map[string]any } -func (n NoteBrImpl) ValidateGetNotes(pageRequest pagination.PageRequest) single.Single[any] { - validateSortingSrc := single.FromSupplierCached(func() ([]apperrors.RuleError, error) { - var ruleErrors []apperrors.RuleError - if len(pageRequest.Sort) != 1 { - ruleErrors = append(ruleErrors, n.errorService.RuleErrorFromCode(apperrors.ErrCodeMustSortByOneOption)) - } else if _, ok := n.validSortFields[pageRequest.Sort[0].Field]; !ok { - ruleErrors = append(ruleErrors, n.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSortOptions)) - } - return ruleErrors, nil - }) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(validateSortingSrc) +func (n NoteBrImpl) ValidateGetNotes(pageRequest pagination.PageRequest) error { + var ruleErrors []apperrors.RuleError + if len(pageRequest.Sort) != 1 { + ruleErrors = append(ruleErrors, n.errorService.RuleErrorFromCode(apperrors.ErrCodeMustSortByOneOption)) + } else if _, ok := n.validSortFields[pageRequest.Sort[0].Field]; !ok { + ruleErrors = append(ruleErrors, n.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSortOptions)) + } + return validationutils.MergeAppErrors(ruleErrors) } -func (n NoteBrImpl) ValidateNoteRead( - userBo userbos.UserBo, - keyDto keydtos.UserKeyDto, - existing models.Note, -) single.Single[any] { - validateKeyVersionSrc := n.validateKeyVersion(keyDto, existing) - validateNoteOwnedByUser := n.validateNoteOwnership(userBo, existing) - ruleErrs := validationutils.ConcatSinglesOfRuleErrs(validateKeyVersionSrc, validateNoteOwnedByUser) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrs) +func (n NoteBrImpl) ValidateNoteRead(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error { + ruleErrs := append(n.validateKeyVersion(keyDto, existing), n.validateNoteOwnership(userBo, existing)...) + return validationutils.MergeAppErrors(ruleErrs) } -func (n NoteBrImpl) ValidateNoteUpdate( - userBo userbos.UserBo, - keyDto keydtos.UserKeyDto, - existing models.Note, -) single.Single[any] { - validateKeyVersionSrc := n.validateKeyVersion(keyDto, existing) - validateNoteOwnedByUser := n.validateNoteOwnership(userBo, existing) - ruleErrs := validationutils.ConcatSinglesOfRuleErrs(validateKeyVersionSrc, validateNoteOwnedByUser) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrs) +func (n NoteBrImpl) ValidateNoteUpdate(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error { + ruleErrs := append(n.validateKeyVersion(keyDto, existing), n.validateNoteOwnership(userBo, existing)...) + return validationutils.MergeAppErrors(ruleErrs) } -func (n NoteBrImpl) ValidateNoteDelete(userBo userbos.UserBo, existing models.Note) single.Single[any] { - validateNoteOwnedByUser := n.validateNoteOwnership(userBo, existing) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(validateNoteOwnedByUser) +func (n NoteBrImpl) ValidateNoteDelete(userBo userbos.UserBo, existing models.Note) error { + return validationutils.MergeAppErrors(n.validateNoteOwnership(userBo, existing)) } -func (n NoteBrImpl) validateKeyVersion( - keyDto keydtos.UserKeyDto, - existing models.Note, -) single.Single[[]apperrors.RuleError] { - return single.FromSupplierCached(func() ([]apperrors.RuleError, error) { - var ruleErrs []apperrors.RuleError - if keyDto.KeyVersion != existing.KeyVersion { - ruleErrs = append(ruleErrs, n.errorService.RuleErrorFromCode(apperrors.ErrCodeDataRace)) - } - return ruleErrs, nil - }) +func (n NoteBrImpl) validateKeyVersion(keyDto keydtos.UserKeyDto, existing models.Note) []apperrors.RuleError { + var ruleErrs []apperrors.RuleError + if keyDto.KeyVersion != existing.KeyVersion { + ruleErrs = append(ruleErrs, n.errorService.RuleErrorFromCode(apperrors.ErrCodeDataRace)) + } + return ruleErrs } -func (n NoteBrImpl) validateNoteOwnership( - userBo userbos.UserBo, - existing models.Note, -) single.Single[[]apperrors.RuleError] { - return single.FromSupplierCached(func() ([]apperrors.RuleError, error) { - var ruleErrs []apperrors.RuleError - if userBo.Id != existing.UserId { - ruleErrs = append(ruleErrs, n.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) - } - return ruleErrs, nil - }) +func (n NoteBrImpl) validateNoteOwnership(userBo userbos.UserBo, existing models.Note) []apperrors.RuleError { + var ruleErrs []apperrors.RuleError + if userBo.Id != existing.UserId { + ruleErrs = append(ruleErrs, n.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) + } + return ruleErrs } func NewNoteBrImpl(errorService sharedservices.ErrorService) *NoteBrImpl { diff --git a/microservices/go/cmd/noteservice/services/note-service.go b/microservices/go/cmd/noteservice/services/note-service.go index f93512d..e0e7e11 100644 --- a/microservices/go/cmd/noteservice/services/note-service.go +++ b/microservices/go/cmd/noteservice/services/note-service.go @@ -104,13 +104,14 @@ func (n NoteServiceImpl) UpdateNoteTransaction( sessDto, noteUpdateDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) existingSrc := n.getExistingNote(ctx, noteUpdateDto.Id).ScheduleLazyAndCache(ctx) keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessDto).ScheduleLazyAndCache(ctx) - keySrc := single.FlatMap(single.Zip2(existingSrc, keyDtoSrc), - func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) single.Single[[]byte] { + keySrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), + func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) ([]byte, error) { existing, keyDto := t.V1, t.V2 - noteUpdateValidationSrc := n.noteBr.ValidateNoteUpdate(userBo, keyDto, existing) - return single.MapWithError(noteUpdateValidationSrc, func(_ any) ([]byte, error) { - return keyDto.GetKey() - }) + err := n.noteBr.ValidateNoteUpdate(userBo, keyDto, existing) + if err != nil { + return nil, err + } + return keyDto.GetKey() }).ScheduleLazyAndCache(ctx) titleCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { return cipherutils.EncryptAES(key, []byte(noteUpdateDto.Title)) @@ -140,9 +141,10 @@ func (n NoteServiceImpl) DeleteNoteTransaction( return dshandlers.TransactionalSingle(ctx, n.crudDSHandler, func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { existingSrc := n.getExistingNote(ctx, noteIdDto.Id).ScheduleLazyAndCache(ctx) - validateDeleteSrc := single.FlatMap(existingSrc, - func(existing models.Note) single.Single[any] { - return n.noteBr.ValidateNoteDelete(userBo, existing) + validateDeleteSrc := single.MapWithError(existingSrc, + func(existing models.Note) (any, error) { + err := n.noteBr.ValidateNoteDelete(userBo, existing) + return any(true), err }) noteDeleteSrc := single.FlatMap(single.Zip2(existingSrc, validateDeleteSrc), func(t tuple.T2[models.Note, any]) single.Single[models.Note] { @@ -163,10 +165,10 @@ func (n NoteServiceImpl) GetNoteById( sessDto, noteIdDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) existingSrc := n.getExistingNote(ctx, noteIdDto.Id).ScheduleLazyAndCache(ctx) keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessDto).ScheduleLazyAndCache(ctx) - validationSrc := single.FlatMap(single.Zip2(existingSrc, keyDtoSrc), - func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) single.Single[any] { + validationSrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), + func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) (any, error) { existing, keyDto := t.V1, t.V2 - return n.noteBr.ValidateNoteRead(userBo, keyDto, existing) + return any(true), n.noteBr.ValidateNoteRead(userBo, keyDto, existing) }) keySrc := single.MapWithError(single.Zip2(keyDtoSrc, validationSrc), func(t tuple.T2[kDTOs.UserKeyDto, any]) ([]byte, error) { @@ -202,12 +204,15 @@ func (n NoteServiceImpl) GetNotesPage( sessReqDto cDTOs.UKeySessionReqDto[pagination.PageRequest], ) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { sessionDto, pageRequest := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - validationSrc := n.noteBr.ValidateGetNotes(pageRequest) - zippedSrc := single.FlatMap(validationSrc, func(_ any) single.Single[tuple.T3[kDTOs.UserKeyDto, []byte, int64]] { + err := n.noteBr.ValidateGetNotes(pageRequest) + if err != nil { + return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) + } + zippedSrc := single.FromSupplierCached(func() (tuple.T3[kDTOs.UserKeyDto, []byte, int64], error) { keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessionDto) keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey) countSrc := n.noteRepository.CountByUserId(ctx, userBo.Id) - return single.Zip3(keyDtoSrc, keySrc, countSrc) + return single.RetrieveValue(ctx, single.Zip3(keyDtoSrc, keySrc, countSrc)) }) return single.FlatMap(zippedSrc, func(t tuple.T3[kDTOs.UserKeyDto, []byte, int64]) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { diff --git a/microservices/go/cmd/userservice/businessrules/userbr.go b/microservices/go/cmd/userservice/businessrules/userbr.go index 07455e2..6890748 100644 --- a/microservices/go/cmd/userservice/businessrules/userbr.go +++ b/microservices/go/cmd/userservice/businessrules/userbr.go @@ -14,8 +14,8 @@ import ( ) type UserBr interface { - ValidateUserCreate(ctx context.Context, identity security.Identity, dto userdtos.UserSaveDto) single.Single[any] - ValidateUserUpdate(ctx context.Context, dto userdtos.UserSaveDto, existing models.User) single.Single[any] + ValidateUserCreate(ctx context.Context, identity security.Identity, dto userdtos.UserSaveDto) error + ValidateUserUpdate(ctx context.Context, dto userdtos.UserSaveDto, existing models.User) error } type UserBrImpl struct { @@ -28,39 +28,45 @@ func (u UserBrImpl) ValidateUserCreate( ctx context.Context, identity security.Identity, dto userdtos.UserSaveDto, -) single.Single[any] { - userNameNotTakenValidationSrc := u.validateUserNameNotTaken(ctx, dto) - userNotCreatedValidationSrc := validationutils.ValidateValueIsNotPresent( +) error { + userNameNotTakenValidationErrs, err := u.validateUserNameNotTaken(ctx, dto) + if err != nil { + return err + } + userFindByAuthIdMaybe, err := single.RetrieveValue(ctx, u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId())) + if err != nil { + return err + } + userNotCreatedValidationErrs := validationutils.ValidateValueIsNotPresent( u.errorService, - u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()), + userFindByAuthIdMaybe, apperrors.ErrCodeResourceAlreadyCreated, ) - ruleErrorsSrc := validationutils.ConcatSinglesOfRuleErrs(userNameNotTakenValidationSrc, userNotCreatedValidationSrc) - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrorsSrc) + ruleErrorsSrc := append(userNameNotTakenValidationErrs, userNotCreatedValidationErrs...) + return validationutils.MergeAppErrors(ruleErrorsSrc) } -func (u UserBrImpl) ValidateUserUpdate( - ctx context.Context, - dto userdtos.UserSaveDto, - existing models.User, -) single.Single[any] { - ruleErrorsSrc := single.Just([]apperrors.RuleError{}) +func (u UserBrImpl) ValidateUserUpdate(ctx context.Context, dto userdtos.UserSaveDto, existing models.User) error { + var ruleErrs []apperrors.RuleError if dto.UserName != existing.UserName { - userNameNotTakenValidationSrc := u.validateUserNameNotTaken(ctx, dto) - ruleErrorsSrc = validationutils.ConcatSinglesOfRuleErrs(ruleErrorsSrc, userNameNotTakenValidationSrc) + valErrs, err := u.validateUserNameNotTaken(ctx, dto) + if err != nil { + return err + } + ruleErrs = append(ruleErrs, valErrs...) } - return validationutils.PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrorsSrc) + return validationutils.MergeAppErrors(ruleErrs) } func (u UserBrImpl) validateUserNameNotTaken( ctx context.Context, dto userdtos.UserSaveDto, -) single.Single[[]apperrors.RuleError] { - return validationutils.ValidateValueIsNotPresent( - u.errorService, - u.userRepository.FindByUsernameAndNotToBeDeleted(ctx, dto.UserName), - apperrors.ErrCodeUsernameTaken, - ) +) ([]apperrors.RuleError, error) { + maybe, err := single.RetrieveValue(ctx, u.userRepository.FindByUsernameAndNotToBeDeleted(ctx, dto.UserName)) + if err != nil { + return nil, err + } + return validationutils.ValidateValueIsNotPresent(u.errorService, maybe, apperrors.ErrCodeUsernameTaken), nil } func NewUserBrImpl( diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index 220d133..da240aa 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -53,14 +53,17 @@ func (u UserServiceImpl) AddUserTransaction( ) single.Single[userdtos.UserReadDto] { return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { - userCreateValidationSrc := u.userBr.ValidateUserCreate(ctx, identity, userSaveDto) - userCreateSrc := single.FlatMap(userCreateValidationSrc, func(any2 any) single.Single[models.User] { + err := u.userBr.ValidateUserCreate(ctx, identity, userSaveDto) + if err != nil { + return single.Error[userdtos.UserReadDto](err) + } + userCreateSrc := single.FromSupplierCached(func() (models.User, error) { user := models.User{} mappers.UserSaveDtoToUser(userSaveDto, &user) user.AuthId = identity.GetAuthId() user.Distributed = false user.ToBeDeleted = false - return u.userRepository.Create(ctx, user) + return single.RetrieveValue(ctx, u.userRepository.Create(ctx, user)) }) return single.Map(userCreateSrc, func(user models.User) userdtos.UserReadDto { logger.Log.Debug("Saved user ", user) @@ -68,7 +71,6 @@ func (u UserServiceImpl) AddUserTransaction( }) }, ) - } func (u UserServiceImpl) UpdateUserTransaction( @@ -90,10 +92,10 @@ func (u UserServiceImpl) UpdateUserTransaction( } }, ) - userValidatedSrc := single.FlatMap(userExistsSrc, - func(existingUser models.User) single.Single[models.User] { - validationSrc := u.userBr.ValidateUserUpdate(ctx, userSaveDto, existingUser) - return single.Map(validationSrc, func(any2 any) models.User { return existingUser }) + userValidatedSrc := single.MapWithError(userExistsSrc, + func(existingUser models.User) (models.User, error) { + err := u.userBr.ValidateUserUpdate(ctx, userSaveDto, existingUser) + return existingUser, err }, ) userSavedSrc := single.FlatMap(userValidatedSrc, func(user models.User) single.Single[models.User] { diff --git a/microservices/go/pkg/apperrors/validationutils/validation-utils.go b/microservices/go/pkg/apperrors/validationutils/validation-utils.go index 87b7d31..e32ca3b 100644 --- a/microservices/go/pkg/apperrors/validationutils/validation-utils.go +++ b/microservices/go/pkg/apperrors/validationutils/validation-utils.go @@ -1,56 +1,36 @@ package validationutils import ( - "github.com/barweiss/go-tuple" "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) func ValidateValueIsNotPresent[V any]( errorService sharedservices.ErrorService, - valSrc single.Single[option.Maybe[V]], + maybe option.Maybe[V], notPresentErrorCode string, -) single.Single[[]apperrors.RuleError] { - return single.Map(valSrc, func(maybe option.Maybe[V]) []apperrors.RuleError { - if maybe.IsPresent() { - return []apperrors.RuleError{errorService.RuleErrorFromCode(notPresentErrorCode)} - } - return []apperrors.RuleError{} - }) +) []apperrors.RuleError { + if maybe.IsPresent() { + return []apperrors.RuleError{errorService.RuleErrorFromCode(notPresentErrorCode)} + } + return []apperrors.RuleError{} } func ValidateValueIsPresent[V any]( errorService sharedservices.ErrorService, - valSrc single.Single[option.Maybe[V]], + maybe option.Maybe[V], notPresentErrorCode string, -) single.Single[[]apperrors.RuleError] { - return single.Map(valSrc, func(maybe option.Maybe[V]) []apperrors.RuleError { - if maybe.IsEmpty() { - return []apperrors.RuleError{errorService.RuleErrorFromCode(notPresentErrorCode)} - } - return []apperrors.RuleError{} - }) +) []apperrors.RuleError { + if maybe.IsEmpty() { + return []apperrors.RuleError{errorService.RuleErrorFromCode(notPresentErrorCode)} + } + return []apperrors.RuleError{} } -func ConcatSinglesOfRuleErrs( - src1 single.Single[[]apperrors.RuleError], - src2 single.Single[[]apperrors.RuleError], -) single.Single[[]apperrors.RuleError] { - return single.Map( - single.Zip2(src1, src2), - func(rulErrsTuple tuple.T2[[]apperrors.RuleError, []apperrors.RuleError]) []apperrors.RuleError { - return append(rulErrsTuple.V1, rulErrsTuple.V2...) - }, - ) -} - -func PassRuleErrorsIfEmptyElsePassBadReqError(ruleErrsSrc single.Single[[]apperrors.RuleError]) single.Single[any] { - return single.MapWithError(ruleErrsSrc, func(ruleErrors []apperrors.RuleError) (any, error) { - if len(ruleErrors) == 0 { - return any(true), nil - } - return any(false), apperrors.NewBadReqErrorFromRuleErrors(ruleErrors...) - }) +func MergeAppErrors(ruleErrs []apperrors.RuleError) error { + if len(ruleErrs) == 0 { + return nil + } + return apperrors.NewBadReqErrorFromRuleErrors(ruleErrs...) } From 8a76f4e20c62f0afa4c2d74a6d729ae4c691e59f Mon Sep 17 00:00:00 2001 From: oren Date: Sun, 8 Jan 2023 23:33:37 -0500 Subject: [PATCH 02/14] Add async type & rm reactivity in crud repos --- .../userkey-generator-repository.go | 52 ++++++------- .../keyservice/services/userkey-service.go | 17 +++-- .../repositories/note-repository.go | 74 ++++++++----------- .../cmd/noteservice/services/note-service.go | 46 +++++++----- .../cmd/userservice/businessrules/userbr.go | 5 +- .../repositories/user-repository.go | 58 ++++++--------- .../cmd/userservice/services/user-service.go | 67 +++++++++-------- microservices/go/pkg/concurrent/async.go | 62 ++++++++++++++++ .../datasource/baserepos/crud-repository.go | 9 +-- .../go/pkg/datasource/dshandlers/dshandler.go | 4 +- .../go/pkg/datasource/mgmtools/mgmutils.go | 11 +-- .../go/pkg/sharedrepos/user-repository.go | 41 +++++----- .../go/pkg/sharedservices/user-service.go | 28 ++++--- 13 files changed, 258 insertions(+), 216 deletions(-) create mode 100644 microservices/go/pkg/concurrent/async.go diff --git a/microservices/go/cmd/keyservice/repositories/userkey-generator-repository.go b/microservices/go/cmd/keyservice/repositories/userkey-generator-repository.go index f0813e5..7e91e56 100644 --- a/microservices/go/cmd/keyservice/repositories/userkey-generator-repository.go +++ b/microservices/go/cmd/keyservice/repositories/userkey-generator-repository.go @@ -6,16 +6,14 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" ) type UserKeyGeneratorRepository interface { baserepos.CRUDRepository[models.UserKeyGenerator, string] - FindOneByUserId(ctx context.Context, userId string) single.Single[option.Maybe[models.UserKeyGenerator]] - DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] + FindOneByUserId(ctx context.Context, userId string) (option.Maybe[models.UserKeyGenerator], error) + DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) } type UserKeyGeneratorRepositoryImpl struct { @@ -25,38 +23,32 @@ type UserKeyGeneratorRepositoryImpl struct { func (u UserKeyGeneratorRepositoryImpl) Create( ctx context.Context, model models.UserKeyGenerator, -) single.Single[models.UserKeyGenerator] { - return single.FromSupplierCached(func() (models.UserKeyGenerator, error) { - err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +) (models.UserKeyGenerator, error) { + err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } func (u UserKeyGeneratorRepositoryImpl) Update( ctx context.Context, model models.UserKeyGenerator, -) single.Single[models.UserKeyGenerator] { - return single.FromSupplierCached(func() (models.UserKeyGenerator, error) { - err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +) (models.UserKeyGenerator, error) { + err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } func (u UserKeyGeneratorRepositoryImpl) Delete( ctx context.Context, model models.UserKeyGenerator, -) single.Single[models.UserKeyGenerator] { - return single.FromSupplierCached(func() (models.UserKeyGenerator, error) { - err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +) (models.UserKeyGenerator, error) { + err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } func (u UserKeyGeneratorRepositoryImpl) FindById( ctx context.Context, id string, -) single.Single[option.Maybe[models.UserKeyGenerator]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.UserKeyGenerator, error) { +) (option.Maybe[models.UserKeyGenerator], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.UserKeyGenerator, error) { model := models.UserKeyGenerator{} err := mgm.Coll(u.ModelColl).FindByIDWithCtx(u.MongoDBHandler.ToChildCtx(ctx), id, &model) return model, err @@ -66,8 +58,8 @@ func (u UserKeyGeneratorRepositoryImpl) FindById( func (u UserKeyGeneratorRepositoryImpl) FindOneByUserId( ctx context.Context, userId string, -) single.Single[option.Maybe[models.UserKeyGenerator]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.UserKeyGenerator, error) { +) (option.Maybe[models.UserKeyGenerator], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.UserKeyGenerator, error) { user := models.UserKeyGenerator{} err := mgm.Coll(u.ModelColl). FirstWithCtx(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}, &user) @@ -78,14 +70,12 @@ func (u UserKeyGeneratorRepositoryImpl) FindOneByUserId( func (u UserKeyGeneratorRepositoryImpl) DeleteByUserIdAndGetCount( ctx context.Context, userId string, -) single.Single[int64] { - return single.FromSupplierCached(func() (int64, error) { - res, err := mgm.Coll(u.ModelColl).DeleteMany(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}) - deletedCount := option. - Map(option.Perhaps(res), func(r *mongo.DeleteResult) int64 { return r.DeletedCount }). - OrElse(-1) - return deletedCount, err - }) +) (int64, error) { + res, err := mgm.Coll(u.ModelColl).DeleteMany(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}) + if res != nil { + return res.DeletedCount, err + } + return -1, err } diff --git a/microservices/go/cmd/keyservice/services/userkey-service.go b/microservices/go/cmd/keyservice/services/userkey-service.go index 2fd76a2..02808cc 100644 --- a/microservices/go/cmd/keyservice/services/userkey-service.go +++ b/microservices/go/cmd/keyservice/services/userkey-service.go @@ -60,7 +60,9 @@ func (u UserKeyServiceImpl) UserKeyExists( ctx context.Context, userBo userbos.UserBo, ) single.Single[commondtos.ExistsDto] { - userFindSrc := u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) + userFindSrc := single.FromSupplierCached(func() (option.Maybe[models.UserKeyGenerator], error) { + return u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) + }) return single.Map(userFindSrc, func(maybe option.Maybe[models.UserKeyGenerator]) commondtos.ExistsDto { return commondtos.NewExistsDto(maybe.IsPresent()) }) @@ -95,8 +97,8 @@ func (u UserKeyServiceImpl) CreateUserKey( KeyVersion: 0, } }) - userKeyGenSaveSrc := single.FlatMap(newUserKeyGenSrc, - func(userKeyGen models.UserKeyGenerator) single.Single[models.UserKeyGenerator] { + userKeyGenSaveSrc := single.MapWithError(newUserKeyGenSrc, + func(userKeyGen models.UserKeyGenerator) (models.UserKeyGenerator, error) { return u.userKeyGeneratorRepository.Create(ctx, userKeyGen) }, ) @@ -245,7 +247,10 @@ func (u UserKeyServiceImpl) getUserKeyGenerator( ctx context.Context, userBo userbos.UserBo, ) single.Single[models.UserKeyGenerator] { - return single.FlatMap(u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id), + userKeyFind := single.FromSupplierCached(func() (option.Maybe[models.UserKeyGenerator], error) { + return u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) + }) + return single.FlatMap(userKeyFind, func(maybe option.Maybe[models.UserKeyGenerator]) single.Single[models.UserKeyGenerator] { return option.Map(maybe, single.Just[models.UserKeyGenerator]). OrElseGet(func() single.Single[models.UserKeyGenerator] { @@ -257,7 +262,9 @@ func (u UserKeyServiceImpl) getUserKeyGenerator( } func (u UserKeyServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] { - return u.userKeyGeneratorRepository.DeleteByUserIdAndGetCount(ctx, userId) + return single.FromSupplierCached(func() (int64, error) { + return u.userKeyGeneratorRepository.DeleteByUserIdAndGetCount(ctx, userId) + }) } func NewUserKeyServiceImpl( diff --git a/microservices/go/cmd/noteservice/repositories/note-repository.go b/microservices/go/cmd/noteservice/repositories/note-repository.go index 7693b7e..9c6d27e 100644 --- a/microservices/go/cmd/noteservice/repositories/note-repository.go +++ b/microservices/go/cmd/noteservice/repositories/note-repository.go @@ -2,17 +2,14 @@ package repositories import ( "context" - "github.com/joamaki/goreactive/stream" "github.com/kamva/mgm/v3" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/mgmtools" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/pagination" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" ) type NoteRepository interface { @@ -21,38 +18,32 @@ type NoteRepository interface { ctx context.Context, userId string, pageReq pagination.PageRequest, - ) stream.Observable[models.Note] - CountByUserId(ctx context.Context, userId string) single.Single[int64] - DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] + ) ([]models.Note, error) + CountByUserId(ctx context.Context, userId string) (int64, error) + DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) } type NoteRepositoryImpl struct { baserepos.BaseRepositoryMongo[models.Note] } -func (u NoteRepositoryImpl) Create(ctx context.Context, model models.Note) single.Single[models.Note] { - return single.FromSupplierCached(func() (models.Note, error) { - err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u NoteRepositoryImpl) Create(ctx context.Context, model models.Note) (models.Note, error) { + err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u NoteRepositoryImpl) Update(ctx context.Context, model models.Note) single.Single[models.Note] { - return single.FromSupplierCached(func() (models.Note, error) { - err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u NoteRepositoryImpl) Update(ctx context.Context, model models.Note) (models.Note, error) { + err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u NoteRepositoryImpl) Delete(ctx context.Context, model models.Note) single.Single[models.Note] { - return single.FromSupplierCached(func() (models.Note, error) { - err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u NoteRepositoryImpl) Delete(ctx context.Context, model models.Note) (models.Note, error) { + err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u NoteRepositoryImpl) FindById(ctx context.Context, id string) single.Single[option.Maybe[models.Note]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.Note, error) { +func (u NoteRepositoryImpl) FindById(ctx context.Context, id string) (option.Maybe[models.Note], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.Note, error) { model := models.Note{} err := mgm.Coll(u.ModelColl).FindByIDWithCtx(u.MongoDBHandler.ToChildCtx(ctx), id, &model) return model, err @@ -63,32 +54,25 @@ func (u NoteRepositoryImpl) GetPaginatedByUserId( ctx context.Context, userId string, pageReq pagination.PageRequest, -) stream.Observable[models.Note] { - return stream.FlatMap(stream.Just(any(true)), func(_ any) stream.Observable[models.Note] { - findOpts := mgmtools.CreatePaginatedFindOpts(pageReq) - filter := bson.D{{"userId", userId}} - ctx := u.MongoDBHandler.ToChildCtx(ctx) - cursor, err := mgm.Coll(u.ModelColl).Find(ctx, filter, findOpts) - return mgmtools.HandleFindManyRes[models.Note](ctx, cursor, err) - }) +) ([]models.Note, error) { + findOpts := mgmtools.CreatePaginatedFindOpts(pageReq) + filter := bson.D{{"userId", userId}} + childCtx := u.MongoDBHandler.ToChildCtx(ctx) + cursor, err := mgm.Coll(u.ModelColl).Find(childCtx, filter, findOpts) + return mgmtools.HandleFindManyRes[models.Note](childCtx, cursor, err) } -func (u NoteRepositoryImpl) CountByUserId(ctx context.Context, userId string) single.Single[int64] { - return single.FromSupplierCached(func() (int64, error) { - filter := bson.D{{"userId", userId}} - return mgm.Coll(u.ModelColl).CountDocuments(u.MongoDBHandler.ToChildCtx(ctx), filter) - }) +func (u NoteRepositoryImpl) CountByUserId(ctx context.Context, userId string) (int64, error) { + filter := bson.D{{"userId", userId}} + return mgm.Coll(u.ModelColl).CountDocuments(u.MongoDBHandler.ToChildCtx(ctx), filter) } -func (u NoteRepositoryImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] { - return single.FromSupplierCached(func() (int64, error) { - res, err := mgm.Coll(u.ModelColl).DeleteMany(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}) - deletedCount := option. - Map(option.Perhaps(res), func(r *mongo.DeleteResult) int64 { return r.DeletedCount }). - OrElse(-1) - return deletedCount, err - }) - +func (u NoteRepositoryImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) { + res, err := mgm.Coll(u.ModelColl).DeleteMany(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}) + if res != nil { + return res.DeletedCount, err + } + return -1, err } func NewNoteRepositoryImpl(mongoDBHandler *dshandlers.MongoDBHandler) *NoteRepositoryImpl { diff --git a/microservices/go/cmd/noteservice/services/note-service.go b/microservices/go/cmd/noteservice/services/note-service.go index e0e7e11..bf20510 100644 --- a/microservices/go/cmd/noteservice/services/note-service.go +++ b/microservices/go/cmd/noteservice/services/note-service.go @@ -3,7 +3,6 @@ package services import ( "context" "github.com/barweiss/go-tuple" - "github.com/joamaki/goreactive/stream" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/businessrules" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/mappers" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/models" @@ -76,8 +75,8 @@ func (n NoteServiceImpl) AddNoteTransaction( textCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { return cipherutils.EncryptAES(key, []byte(noteCreateDto.Text)) }) - noteSaveSrc := single.FlatMap(single.Zip3(keyDtoSrc, titleCipherSrc, textCipherSrc), - func(t tuple.T3[kDTOs.UserKeyDto, []byte, []byte]) single.Single[models.Note] { + noteSaveSrc := single.MapWithError(single.Zip3(keyDtoSrc, titleCipherSrc, textCipherSrc), + func(t tuple.T3[kDTOs.UserKeyDto, []byte, []byte]) (models.Note, error) { keyDto, titleCipher, textCipher := t.V1, t.V2, t.V3 note := models.Note{ UserId: userBo.Id, @@ -119,8 +118,8 @@ func (n NoteServiceImpl) UpdateNoteTransaction( textCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { return cipherutils.EncryptAES(key, []byte(noteUpdateDto.Text)) }) - noteSaveSrc := single.FlatMap(single.Zip4(existingSrc, keyDtoSrc, titleCipherSrc, textCipherSrc), - func(t tuple.T4[models.Note, kDTOs.UserKeyDto, []byte, []byte]) single.Single[models.Note] { + noteSaveSrc := single.MapWithError(single.Zip4(existingSrc, keyDtoSrc, titleCipherSrc, textCipherSrc), + func(t tuple.T4[models.Note, kDTOs.UserKeyDto, []byte, []byte]) (models.Note, error) { existing, keyDto, titleCipher, textCipher := t.V1, t.V2, t.V3, t.V4 existing.TitleCipher = titleCipher existing.TextCipher = textCipher @@ -146,8 +145,8 @@ func (n NoteServiceImpl) DeleteNoteTransaction( err := n.noteBr.ValidateNoteDelete(userBo, existing) return any(true), err }) - noteDeleteSrc := single.FlatMap(single.Zip2(existingSrc, validateDeleteSrc), - func(t tuple.T2[models.Note, any]) single.Single[models.Note] { + noteDeleteSrc := single.MapWithError(single.Zip2(existingSrc, validateDeleteSrc), + func(t tuple.T2[models.Note, any]) (models.Note, error) { existing := t.V1 return n.noteRepository.Delete(ctx, existing) }) @@ -211,34 +210,41 @@ func (n NoteServiceImpl) GetNotesPage( zippedSrc := single.FromSupplierCached(func() (tuple.T3[kDTOs.UserKeyDto, []byte, int64], error) { keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessionDto) keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey) - countSrc := n.noteRepository.CountByUserId(ctx, userBo.Id) + countSrc := single.FromSupplierCached(func() (int64, error) { + return n.noteRepository.CountByUserId(ctx, userBo.Id) + }) return single.RetrieveValue(ctx, single.Zip3(keyDtoSrc, keySrc, countSrc)) }) return single.FlatMap(zippedSrc, func(t tuple.T3[kDTOs.UserKeyDto, []byte, int64]) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { keyDto, keyBytes, count := t.V1, t.V2, t.V3 - findManyObs := n.noteRepository.GetPaginatedByUserId(ctx, userBo.Id, pageRequest) - noteDetailsObs := stream.FlatMap(findManyObs, func(note models.Note) stream.Observable[nDTOs.NotePreviewDto] { + notes, err := n.noteRepository.GetPaginatedByUserId(ctx, userBo.Id, pageRequest) + if err != nil { + return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) + } + noteDTOs := make([]nDTOs.NotePreviewDto, 0, len(notes)) + for _, note := range notes { if note.KeyVersion != keyDto.KeyVersion { ruleErr := n.errorService.RuleErrorFromCode(apperrors.ErrCodeDataRace) - return stream.Error[nDTOs.NotePreviewDto](apperrors.NewBadReqErrorFromRuleError(ruleErr)) + return single.Error[pagination.Page[nDTOs.NotePreviewDto]]( + apperrors.NewBadReqErrorFromRuleError(ruleErr)) } txtBytes, err := cipherutils.DecryptAES(keyBytes, note.TextCipher) if err != nil { - return stream.Error[nDTOs.NotePreviewDto](err) + return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) } titleBytes, err := cipherutils.DecryptAES(keyBytes, note.TitleCipher) if err != nil { - return stream.Error[nDTOs.NotePreviewDto](err) + return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) } txt, title := string(txtBytes), string(titleBytes) textPreview := utils.StringFirstNChars(txt, 60) coreNoteDto := nDTOs.NewCoreNoteDto(title) noteReadDto := nDTOs.NotePreviewDto{} mappers.MapTextPreviewAndCoreNoteAndNoteToNotePreviewDto(textPreview, &coreNoteDto, ¬e, ¬eReadDto) - return stream.Just(noteReadDto) - }) - noteDTOsSrc := single.FromObservableAsList(noteDetailsObs) + noteDTOs = append(noteDTOs, noteReadDto) + } + noteDTOsSrc := single.Just(noteDTOs) return single.Map(noteDTOsSrc, func(noteDTOs []nDTOs.NotePreviewDto) pagination.Page[nDTOs.NotePreviewDto] { return pagination.NewPage(noteDTOs, count) }) @@ -246,11 +252,15 @@ func (n NoteServiceImpl) GetNotesPage( } func (u NoteServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] { - return u.noteRepository.DeleteByUserIdAndGetCount(ctx, userId) + return single.FromSupplierCached(func() (int64, error) { + return u.noteRepository.DeleteByUserIdAndGetCount(ctx, userId) + }) } func (n NoteServiceImpl) getExistingNote(ctx context.Context, id string) single.Single[models.Note] { - findSrc := n.noteRepository.FindById(ctx, id) + findSrc := single.FromSupplierCached(func() (option.Maybe[models.Note], error) { + return n.noteRepository.FindById(ctx, id) + }) return single.FlatMap(findSrc, func(m option.Maybe[models.Note]) single.Single[models.Note] { return option.Map(m, single.Just[models.Note]). OrElseGet(func() single.Single[models.Note] { diff --git a/microservices/go/cmd/userservice/businessrules/userbr.go b/microservices/go/cmd/userservice/businessrules/userbr.go index 6890748..cb7fee7 100644 --- a/microservices/go/cmd/userservice/businessrules/userbr.go +++ b/microservices/go/cmd/userservice/businessrules/userbr.go @@ -8,7 +8,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors/validationutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" ) @@ -33,7 +32,7 @@ func (u UserBrImpl) ValidateUserCreate( if err != nil { return err } - userFindByAuthIdMaybe, err := single.RetrieveValue(ctx, u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId())) + userFindByAuthIdMaybe, err := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) if err != nil { return err } @@ -62,7 +61,7 @@ func (u UserBrImpl) validateUserNameNotTaken( ctx context.Context, dto userdtos.UserSaveDto, ) ([]apperrors.RuleError, error) { - maybe, err := single.RetrieveValue(ctx, u.userRepository.FindByUsernameAndNotToBeDeleted(ctx, dto.UserName)) + maybe, err := u.userRepository.FindByUsernameAndNotToBeDeleted(ctx, dto.UserName) if err != nil { return nil, err } diff --git a/microservices/go/cmd/userservice/repositories/user-repository.go b/microservices/go/cmd/userservice/repositories/user-repository.go index 56d647f..8c9bbe5 100644 --- a/microservices/go/cmd/userservice/repositories/user-repository.go +++ b/microservices/go/cmd/userservice/repositories/user-repository.go @@ -2,14 +2,12 @@ package repositories import ( "context" - "github.com/joamaki/goreactive/stream" "github.com/kamva/mgm/v3" "github.com/kamva/mgm/v3/operator" "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/mgmtools" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -17,38 +15,32 @@ import ( type UserRepository interface { baserepos.CRUDRepository[models.User, string] - FindByAuthIdAndNotToBeDeleted(ctx context.Context, authId string) single.Single[option.Maybe[models.User]] - FindByUsernameAndNotToBeDeleted(ctx context.Context, username string) single.Single[option.Maybe[models.User]] - SampleUndistributedUsers(ctx context.Context, size int64) stream.Observable[models.User] + FindByAuthIdAndNotToBeDeleted(ctx context.Context, authId string) (option.Maybe[models.User], error) + FindByUsernameAndNotToBeDeleted(ctx context.Context, username string) (option.Maybe[models.User], error) + SampleUndistributedUsers(ctx context.Context, size int64) ([]models.User, error) } type UserRepositoryImpl struct { baserepos.BaseRepositoryMongo[models.User] } -func (u UserRepositoryImpl) Create(ctx context.Context, model models.User) single.Single[models.User] { - return single.FromSupplierCached(func() (models.User, error) { - err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u UserRepositoryImpl) Create(ctx context.Context, model models.User) (models.User, error) { + err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u UserRepositoryImpl) Update(ctx context.Context, model models.User) single.Single[models.User] { - return single.FromSupplierCached(func() (models.User, error) { - err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u UserRepositoryImpl) Update(ctx context.Context, model models.User) (models.User, error) { + err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u UserRepositoryImpl) Delete(ctx context.Context, model models.User) single.Single[models.User] { - return single.FromSupplierCached(func() (models.User, error) { - err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) - return model, err - }) +func (u UserRepositoryImpl) Delete(ctx context.Context, model models.User) (models.User, error) { + err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &model) + return model, err } -func (u UserRepositoryImpl) FindById(ctx context.Context, id string) single.Single[option.Maybe[models.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.User, error) { +func (u UserRepositoryImpl) FindById(ctx context.Context, id string) (option.Maybe[models.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.User, error) { model := models.User{} err := mgm.Coll(u.ModelColl).FindByIDWithCtx(u.MongoDBHandler.ToChildCtx(ctx), id, &model) return model, err @@ -58,8 +50,8 @@ func (u UserRepositoryImpl) FindById(ctx context.Context, id string) single.Sing func (u UserRepositoryImpl) FindByAuthIdAndNotToBeDeleted( ctx context.Context, authId string, -) single.Single[option.Maybe[models.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.User, error) { +) (option.Maybe[models.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.User, error) { user := models.User{} err := mgm.Coll(u.ModelColl).FirstWithCtx(u.MongoDBHandler.ToChildCtx(ctx), bson.M{ @@ -73,8 +65,8 @@ func (u UserRepositoryImpl) FindByAuthIdAndNotToBeDeleted( func (u UserRepositoryImpl) FindByUsernameAndNotToBeDeleted( ctx context.Context, username string, -) single.Single[option.Maybe[models.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (models.User, error) { +) (option.Maybe[models.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (models.User, error) { user := models.User{} err := mgm.Coll(u.ModelColl).FirstWithCtx( u.MongoDBHandler.ToChildCtx(ctx), @@ -88,15 +80,13 @@ func (u UserRepositoryImpl) FindByUsernameAndNotToBeDeleted( }) } -func (u UserRepositoryImpl) SampleUndistributedUsers(ctx context.Context, size int64) stream.Observable[models.User] { - return stream.FlatMap(stream.Just(any(true)), func(_ any) stream.Observable[models.User] { - ctx := u.MongoDBHandler.ToChildCtx(ctx) - cursor, err := mgm.Coll(u.ModelColl).Aggregate(ctx, mongo.Pipeline{ - {{operator.Match, bson.D{{"distributed", bson.D{{operator.Ne, true}}}}}}, - {{operator.Sample, bson.D{{"size", size}}}}, - }) - return mgmtools.HandleFindManyRes[models.User](ctx, cursor, err) +func (u UserRepositoryImpl) SampleUndistributedUsers(ctx context.Context, size int64) ([]models.User, error) { + childCtx := u.MongoDBHandler.ToChildCtx(ctx) + cursor, err := mgm.Coll(u.ModelColl).Aggregate(ctx, mongo.Pipeline{ + {{operator.Match, bson.D{{"distributed", bson.D{{operator.Ne, true}}}}}}, + {{operator.Sample, bson.D{{"size", size}}}}, }) + return mgmtools.HandleFindManyRes[models.User](childCtx, cursor, err) } func NewUserRepositoryImpl(mongoDBHandler *dshandlers.MongoDBHandler) *UserRepositoryImpl { diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index da240aa..886caa7 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -2,7 +2,6 @@ package services import ( "context" - "github.com/joamaki/goreactive/stream" "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/businessrules" "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/mappers" "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/models" @@ -15,7 +14,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) @@ -63,7 +61,7 @@ func (u UserServiceImpl) AddUserTransaction( user.AuthId = identity.GetAuthId() user.Distributed = false user.ToBeDeleted = false - return single.RetrieveValue(ctx, u.userRepository.Create(ctx, user)) + return u.userRepository.Create(ctx, user) }) return single.Map(userCreateSrc, func(user models.User) userdtos.UserReadDto { logger.Log.Debug("Saved user ", user) @@ -80,7 +78,9 @@ func (u UserServiceImpl) UpdateUserTransaction( ) single.Single[userdtos.UserReadDto] { return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { - userSearchSrc := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { + return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + }) userExistsSrc := single.MapWithError(userSearchSrc, func(userMaybe option.Maybe[models.User]) (models.User, error) { if user, isPresent := userMaybe.Get(); isPresent { @@ -98,7 +98,7 @@ func (u UserServiceImpl) UpdateUserTransaction( return existingUser, err }, ) - userSavedSrc := single.FlatMap(userValidatedSrc, func(user models.User) single.Single[models.User] { + userSavedSrc := single.MapWithError(userValidatedSrc, func(user models.User) (models.User, error) { mappers.UserSaveDtoToUser(userSaveDto, &user) user.Distributed = false user.ToBeDeleted = false @@ -119,7 +119,9 @@ func (u UserServiceImpl) BeginDeletingUserTransaction( ) single.Single[userdtos.UserReadDto] { return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { - userSearchSrc := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { + return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + }) userExistsSrc := single.MapWithError( userSearchSrc, func(userMaybe option.Maybe[models.User]) (models.User, error) { @@ -132,7 +134,7 @@ func (u UserServiceImpl) BeginDeletingUserTransaction( } }, ) - userToBeDeletedSrc := single.FlatMap(userExistsSrc, func(user models.User) single.Single[models.User] { + userToBeDeletedSrc := single.MapWithError(userExistsSrc, func(user models.User) (models.User, error) { user.Distributed = false user.ToBeDeleted = true return u.userRepository.Update(ctx, user) @@ -158,7 +160,9 @@ func (u UserServiceImpl) GetUserIdentity( } func (u UserServiceImpl) GetByAuthId(ctx context.Context, authId string) single.Single[userdtos.UserReadDto] { - userSearchSrc := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, authId) + userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { + return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, authId) + }) return single.Map(userSearchSrc, func(userMaybe option.Maybe[models.User]) userdtos.UserReadDto { user := userMaybe.OrElse(models.User{}) return userToUserReadDto(user) @@ -166,7 +170,9 @@ func (u UserServiceImpl) GetByAuthId(ctx context.Context, authId string) single. } func (u UserServiceImpl) GetById(ctx context.Context, userId string) single.Single[userdtos.UserReadDto] { - userSearchSrc := u.userRepository.FindById(ctx, userId) + userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { + return u.userRepository.FindById(ctx, userId) + }) return single.Map(userSearchSrc, func(userMaybe option.Maybe[models.User]) userdtos.UserReadDto { user := userMaybe.Filter(models.User.WillNotDeleted).OrElse(models.User{}) return userToUserReadDto(user) @@ -174,24 +180,19 @@ func (u UserServiceImpl) GetById(ctx context.Context, userId string) single.Sing } func (u UserServiceImpl) UsersChangeTask(ctx context.Context) { - userSampleSrc := u.userRepository.SampleUndistributedUsers(ctx, 100) - usersCh, errCh := stream.ToChannels(ctx, userSampleSrc) - var actionSingles []single.Single[any] - for user := range usersCh { - var src single.Single[any] - if user.ToBeDeleted { - src = single.Map(u.deleteUserTransaction(ctx, user), utils.CastToAny[userdtos.UserChangeEventDto]) - } else { - src = single.Map(u.distributeUserChangeTransaction(ctx, user), utils.CastToAny[userdtos.UserChangeEventDto]) - } - actionSingles = append(actionSingles, src.ScheduleEagerAsyncCached(ctx)) - } - err := <-errCh + userSample, err := u.userRepository.SampleUndistributedUsers(ctx, 100) if err != nil { logger.Log.Error(err) + return } - for _, actionSrc := range actionSingles { - if _, err := single.RetrieveValue(ctx, actionSrc); err != nil { + for _, user := range userSample { + var err error + if user.ToBeDeleted { + _, err = single.RetrieveValue(ctx, u.deleteUserTransaction(ctx, user)) + } else { + _, err = single.RetrieveValue(ctx, u.distributeUserChangeTransaction(ctx, user)) + } + if err != nil { logger.Log.Error(err) } } @@ -205,7 +206,9 @@ func (u UserServiceImpl) deleteUserTransaction( return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventDto] { sendUserChangeSrc := u.sendUserChange(user, userdtos.UserDelete) - userDeletedLocalDBSrc := u.userRepository.Delete(ctx, user) + userDeletedLocalDBSrc := single.FromSupplierCached(func() (models.User, error) { + return u.userRepository.Delete(ctx, user) + }) userDeletedAuthServerSrc := single.FlatMap( userDeletedLocalDBSrc, func(user models.User) single.Single[models.User] { @@ -230,17 +233,17 @@ func (u UserServiceImpl) distributeUserChangeTransaction( return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, func(session dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventDto] { sendUserChangeSrc := u.sendUserChange(user, userdtos.UserSave) - return single.FlatMap( + return single.MapWithError( sendUserChangeSrc, - func(uce userdtos.UserChangeEventDto) single.Single[userdtos.UserChangeEventDto] { + func(event userdtos.UserChangeEventDto) (userdtos.UserChangeEventDto, error) { user := user user.ToBeDeleted = false user.Distributed = true - updateSrc := u.userRepository.Update(ctx, user) - return single.Map(updateSrc, func(a models.User) userdtos.UserChangeEventDto { - logger.Log.Debugf("Sent user save event for user %v", a) - return uce - }) + updatedUser, err := u.userRepository.Update(ctx, user) + if err == nil { + logger.Log.Debugf("Sent user save event for user %v", updatedUser) + } + return event, err }, ) diff --git a/microservices/go/pkg/concurrent/async.go b/microservices/go/pkg/concurrent/async.go new file mode 100644 index 0000000..28b8de6 --- /dev/null +++ b/microservices/go/pkg/concurrent/async.go @@ -0,0 +1,62 @@ +package concurrent + +import "sync" + +type Future[T any] interface { + Await() (T, error) +} + +type FutureImpl[T any] struct { + _channelRead bool + _ch <-chan T + _chErr <-chan error + _value T + _error error + _valueRWLock sync.RWMutex +} + +func (a FutureImpl[T]) Await() (T, error) { + shouldReadChannel := func() bool { + a._valueRWLock.RLock() + defer a._valueRWLock.RUnlock() + return !a._channelRead + }() + + if shouldReadChannel { + func() { + a._valueRWLock.Lock() + defer a._valueRWLock.Unlock() + if !a._channelRead { + a._channelRead = true + if a._chErr != nil { + if err := <-a._chErr; err != nil { + a._error = err + } + } + if a._error == nil { + a._value = <-a._ch + } + } + }() + } + return func() (T, error) { + a._valueRWLock.RLock() + defer a._valueRWLock.RUnlock() + return a._value, a._error + }() +} + +func Async[T any](supplier func() (T, error)) *FutureImpl[T] { + ch := make(chan T) + chErr := make(chan error) + go func() { + defer func() { + close(ch) + close(chErr) + }() + v, err := supplier() + ch <- v + chErr <- err + }() + return &FutureImpl[T]{_channelRead: false, _error: nil} +} diff --git a/microservices/go/pkg/datasource/baserepos/crud-repository.go b/microservices/go/pkg/datasource/baserepos/crud-repository.go index ab6004a..a5d7389 100644 --- a/microservices/go/pkg/datasource/baserepos/crud-repository.go +++ b/microservices/go/pkg/datasource/baserepos/crud-repository.go @@ -3,7 +3,6 @@ package baserepos import ( "context" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) @@ -11,21 +10,21 @@ type CRUDRepository[VModel any, VID any] interface { // Create saves a new model to a data store. The model is updated with the saved // values from the database onto the same model and then is emitted by a Single. // The model should be a pointer. - Create(ctx context.Context, model VModel) single.Single[VModel] + Create(ctx context.Context, model VModel) (VModel, error) // Update saves an existing model to a data store. The model is updated with the // saved values from the data store onto the same model and then is emitted by a // Single. - Update(ctx context.Context, model VModel) single.Single[VModel] + Update(ctx context.Context, model VModel) (VModel, error) // Delete deletes an existing model to a data store. The model is then emitted by // a Single. - Delete(ctx context.Context, model VModel) single.Single[VModel] + Delete(ctx context.Context, model VModel) (VModel, error) // FindById queries the data store by an entity's id and saves the value to the // provided model. The same model is then emitted by a Single. The model should // be a pointer. - FindById(ctx context.Context, id VID) single.Single[option.Maybe[VModel]] + FindById(ctx context.Context, id VID) (option.Maybe[VModel], error) } // BaseRepositoryMongo is a MongoDB implementation of CRUDRepository diff --git a/microservices/go/pkg/datasource/dshandlers/dshandler.go b/microservices/go/pkg/datasource/dshandlers/dshandler.go index aa5d47c..862f09f 100644 --- a/microservices/go/pkg/datasource/dshandlers/dshandler.go +++ b/microservices/go/pkg/datasource/dshandlers/dshandler.go @@ -16,11 +16,11 @@ func OptionalSingleQuerySrc[TQueryResult any]( supplier func() (TQueryResult, error), ) single.Single[option.Maybe[TQueryResult]] { return single.FromSupplierCached(func() (option.Maybe[TQueryResult], error) { - return runOptionalSingleQuery(dbHandler, supplier) + return HandleSingleFind(dbHandler, supplier) }) } -func runOptionalSingleQuery[TQueryResult any]( +func HandleSingleFind[TQueryResult any]( dbHandler DataSourceHandler, supplier func() (TQueryResult, error), ) (option.Maybe[TQueryResult], error) { diff --git a/microservices/go/pkg/datasource/mgmtools/mgmutils.go b/microservices/go/pkg/datasource/mgmtools/mgmutils.go index 1bac9fa..36e54f5 100644 --- a/microservices/go/pkg/datasource/mgmtools/mgmutils.go +++ b/microservices/go/pkg/datasource/mgmtools/mgmutils.go @@ -2,7 +2,6 @@ package mgmtools import ( "context" - "github.com/joamaki/goreactive/stream" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -42,13 +41,11 @@ func SetPaginatedFindOpts(findOpt *options.FindOptions, pageReq pagination.PageR // HandleFindManyRes handles the result of a find many method of *mgm.Collection // and transforms it into an observable. -func HandleFindManyRes[T any](ctx context.Context, cursor *mongo.Cursor, err error) stream.Observable[T] { +func HandleFindManyRes[T any](ctx context.Context, cursor *mongo.Cursor, err error) ([]T, error) { var results []T if err != nil { - return stream.Error[T](err) + return results, err } - if err = cursor.All(ctx, &results); err != nil { - return stream.Error[T](err) - } - return stream.FromSlice(results) + err = cursor.All(ctx, &results) + return results, err } diff --git a/microservices/go/pkg/sharedrepos/user-repository.go b/microservices/go/pkg/sharedrepos/user-repository.go index 67ecaf6..694887c 100644 --- a/microservices/go/pkg/sharedrepos/user-repository.go +++ b/microservices/go/pkg/sharedrepos/user-repository.go @@ -5,7 +5,6 @@ import ( "github.com/kamva/mgm/v3" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmodels" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "go.mongodb.org/mongo-driver/bson" @@ -13,53 +12,47 @@ import ( type UserRepository interface { baserepos.CRUDRepository[sharedmodels.User, string] - FindByUserId(ctx context.Context, userId string) single.Single[option.Maybe[sharedmodels.User]] - FindByAuthId(ctx context.Context, authId string) single.Single[option.Maybe[sharedmodels.User]] + FindByUserId(ctx context.Context, userId string) (option.Maybe[sharedmodels.User], error) + FindByAuthId(ctx context.Context, authId string) (option.Maybe[sharedmodels.User], error) } type UserRepositoryImpl struct { baserepos.BaseRepositoryMongo[sharedmodels.User] } -func (u UserRepositoryImpl) Create(ctx context.Context, user sharedmodels.User) single.Single[sharedmodels.User] { - return single.FromSupplierCached(func() (sharedmodels.User, error) { - err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) - return user, err - }) +func (u UserRepositoryImpl) Create(ctx context.Context, user sharedmodels.User) (sharedmodels.User, error) { + err := mgm.Coll(u.ModelColl).CreateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) + return user, err } -func (u UserRepositoryImpl) Update(ctx context.Context, user sharedmodels.User) single.Single[sharedmodels.User] { - return single.FromSupplierCached(func() (sharedmodels.User, error) { - err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) - return user, err - }) +func (u UserRepositoryImpl) Update(ctx context.Context, user sharedmodels.User) (sharedmodels.User, error) { + err := mgm.Coll(u.ModelColl).UpdateWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) + return user, err } -func (u UserRepositoryImpl) Delete(ctx context.Context, user sharedmodels.User) single.Single[sharedmodels.User] { - return single.FromSupplierCached(func() (sharedmodels.User, error) { - err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) - return user, err - }) +func (u UserRepositoryImpl) Delete(ctx context.Context, user sharedmodels.User) (sharedmodels.User, error) { + err := mgm.Coll(u.ModelColl).DeleteWithCtx(u.MongoDBHandler.ToChildCtx(ctx), &user) + return user, err } -func (u UserRepositoryImpl) FindById(ctx context.Context, id string) single.Single[option.Maybe[sharedmodels.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (sharedmodels.User, error) { +func (u UserRepositoryImpl) FindById(ctx context.Context, id string) (option.Maybe[sharedmodels.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (sharedmodels.User, error) { user := sharedmodels.User{} err := mgm.Coll(u.ModelColl).FindByIDWithCtx(u.MongoDBHandler.ToChildCtx(ctx), id, &user) return user, err }) } -func (u UserRepositoryImpl) FindByUserId(ctx context.Context, userId string) single.Single[option.Maybe[sharedmodels.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (sharedmodels.User, error) { +func (u UserRepositoryImpl) FindByUserId(ctx context.Context, userId string) (option.Maybe[sharedmodels.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (sharedmodels.User, error) { user := sharedmodels.User{} err := mgm.Coll(u.ModelColl).FirstWithCtx(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"userId": userId}, &user) return user, err }) } -func (u UserRepositoryImpl) FindByAuthId(ctx context.Context, authId string) single.Single[option.Maybe[sharedmodels.User]] { - return dshandlers.OptionalSingleQuerySrc(u.MongoDBHandler, func() (sharedmodels.User, error) { +func (u UserRepositoryImpl) FindByAuthId(ctx context.Context, authId string) (option.Maybe[sharedmodels.User], error) { + return dshandlers.HandleSingleFind(u.MongoDBHandler, func() (sharedmodels.User, error) { user := sharedmodels.User{} err := mgm.Coll(u.ModelColl).FirstWithCtx(u.MongoDBHandler.ToChildCtx(ctx), bson.M{"authId": authId}, &user) return user, err diff --git a/microservices/go/pkg/sharedservices/user-service.go b/microservices/go/pkg/sharedservices/user-service.go index 9c9006e..265f7ac 100644 --- a/microservices/go/pkg/sharedservices/user-service.go +++ b/microservices/go/pkg/sharedservices/user-service.go @@ -47,18 +47,20 @@ func (u UserServiceImpl) DeleteUser( userEventDto userdtos.UserChangeEventDto, ) single.Single[userbos.UserBo] { logger.Log.Debugf("deleting user %v", userEventDto) - userFindSrc := u.userRepository.FindByUserId(ctx, userEventDto.Id) - userSavedSrc := single.FlatMap( + userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { + return u.userRepository.FindByUserId(ctx, userEventDto.Id) + }) + userDelSrc := single.MapWithError( userFindSrc, - func(userMaybe option.Maybe[cModels.User]) single.Single[cModels.User] { + func(userMaybe option.Maybe[cModels.User]) (cModels.User, error) { if user, isPresent := userMaybe.Get(); isPresent { return u.userRepository.Delete(ctx, user) } else { - return single.Just(cModels.User{}) + return cModels.User{}, nil } }, ) - return single.Map(userSavedSrc, func(u cModels.User) userbos.UserBo { + return single.Map(userDelSrc, func(u cModels.User) userbos.UserBo { userBo := userbos.UserBo{} sharedmappers.UserModelToUserBo(u, &userBo) return userBo @@ -66,7 +68,9 @@ func (u UserServiceImpl) DeleteUser( } func (u UserServiceImpl) RequireUser(ctx context.Context, identity security.Identity) single.Single[userbos.UserBo] { - userFindSrc := u.userRepository.FindByAuthId(ctx, identity.GetAuthId()) + userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { + return u.userRepository.FindByAuthId(ctx, identity.GetAuthId()) + }) userSrc := single.FlatMap(userFindSrc, func(userMaybe option.Maybe[cModels.User]) single.Single[cModels.User] { if user, isPresent := userMaybe.Get(); isPresent { return single.Just(user) @@ -90,7 +94,9 @@ func (u UserServiceImpl) RequireUser(ctx context.Context, identity security.Iden } func (u UserServiceImpl) UserExistsWithId(ctx context.Context, userId string) single.Single[bool] { - userFindSrc := u.userRepository.FindByUserId(ctx, userId) + userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { + return u.userRepository.FindByUserId(ctx, userId) + }) return single.FlatMap(userFindSrc, func(userMaybe option.Maybe[cModels.User]) single.Single[bool] { return option.Map(userMaybe, func(_ cModels.User) single.Single[bool] { return single.Just(true) @@ -108,10 +114,12 @@ func (u UserServiceImpl) saveUserDataAndGetModel( authId string, userPublicDto embeddeduser.BaseUserPublicDto, ) single.Single[cModels.User] { - userFindSrc := u.userRepository.FindByUserId(ctx, userPublicDto.Id) - return single.FlatMap( + userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { + return u.userRepository.FindByUserId(ctx, userPublicDto.Id) + }) + return single.MapWithError( userFindSrc, - func(userMaybe option.Maybe[cModels.User]) single.Single[cModels.User] { + func(userMaybe option.Maybe[cModels.User]) (cModels.User, error) { user, isPresent := userMaybe.Get() if !isPresent { user = cModels.User{} From cb8a32de4cd5fd0b34e9a96cdce78b11561d1e0e Mon Sep 17 00:00:00 2001 From: oren Date: Sun, 8 Jan 2023 23:43:01 -0500 Subject: [PATCH 03/14] Add non-reactive transactional function --- .../datasource/dshandlers/crud-dshandler.go | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go b/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go index ee7d8a5..f0433c7 100644 --- a/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go +++ b/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go @@ -29,6 +29,27 @@ type CrudDSHandler interface { ExecTransaction(ctx context.Context, runner func(Session, context.Context) error) error } +// Transactional executes a transaction +func Transactional[T any]( + ctx context.Context, + d CrudDSHandler, + supplier func(Session, context.Context) (T, error), +) (T, error) { + var res T + var err error = nil + transactionErr := d.ExecTransaction(ctx, func(session Session, ctx context.Context) error { + res, err = supplier(session, ctx) + if err != nil { + return session.AbortTransaction(ctx) + } + return session.CommitTransaction(ctx) + }) + if err == nil { + err = transactionErr + } + return res, err +} + // TransactionalSingle creates a Single that executes a transaction when // evaluated from a Single created from the supplier. The supplier and the // evaluation of the single runs within the scope of the transaction. From 4c7f79ffbf04c1e17bf96e9202331e08bf6ffa2a Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 00:07:04 -0500 Subject: [PATCH 04/14] RM reactive from kv-timed repos --- .../repositories/appsecret-repository.go | 5 ++--- .../primary-appsecret-ref-repository.go | 19 ++++++++--------- .../userkey-session-repository.go | 5 ++--- .../keyservice/services/appsecret-service.go | 21 +++++++++++-------- .../keyservice/services/userkey-service.go | 9 ++++++-- .../baserepos/kvtimed-repository.go | 20 +++--------------- .../go/pkg/datasource/dshandlers/dshandler.go | 10 --------- 7 files changed, 35 insertions(+), 54 deletions(-) diff --git a/microservices/go/cmd/keyservice/repositories/appsecret-repository.go b/microservices/go/cmd/keyservice/repositories/appsecret-repository.go index 850b4e6..59a2250 100644 --- a/microservices/go/cmd/keyservice/repositories/appsecret-repository.go +++ b/microservices/go/cmd/keyservice/repositories/appsecret-repository.go @@ -5,7 +5,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/kvstoreutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "time" @@ -20,7 +19,7 @@ type AppSecretRepositoryImpl struct { baseRepo baserepos.KeyValueTimedRepository[models.AppSecret] } -func (a AppSecretRepositoryImpl) Get(ctx context.Context, key string) single.Single[option.Maybe[models.AppSecret]] { +func (a AppSecretRepositoryImpl) Get(ctx context.Context, key string) (option.Maybe[models.AppSecret], error) { return a.baseRepo.Get(ctx, kvstoreutils.CombineKeySections(a.prefix, key)) } @@ -29,7 +28,7 @@ func (a AppSecretRepositoryImpl) Set( key string, value models.AppSecret, expiration time.Duration, -) single.Single[models.AppSecret] { +) (models.AppSecret, error) { return a.baseRepo.Set(ctx, kvstoreutils.CombineKeySections(a.prefix, key), value, expiration) } diff --git a/microservices/go/cmd/keyservice/repositories/primary-appsecret-ref-repository.go b/microservices/go/cmd/keyservice/repositories/primary-appsecret-ref-repository.go index cd25500..0523c75 100644 --- a/microservices/go/cmd/keyservice/repositories/primary-appsecret-ref-repository.go +++ b/microservices/go/cmd/keyservice/repositories/primary-appsecret-ref-repository.go @@ -5,38 +5,37 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/kvstoreutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "time" ) type PrimaryAppSecretRefRepository interface { - Get(ctx context.Context) single.Single[option.Maybe[models.PrimaryAppSecretRef]] - Set(ctx context.Context, value models.PrimaryAppSecretRef, expr time.Duration) single.Single[models.PrimaryAppSecretRef] + Get(ctx context.Context) (option.Maybe[models.PrimaryAppSecretRef], error) + Set(ctx context.Context, value models.PrimaryAppSecretRef, expr time.Duration) (models.PrimaryAppSecretRef, error) } type PrimaryAppSecretRefRepositoryImpl struct { - prefix string + key string baseRepo baserepos.KeyValueTimedRepository[models.PrimaryAppSecretRef] } -func (a PrimaryAppSecretRefRepositoryImpl) Get(ctx context.Context) single.Single[option.Maybe[models.PrimaryAppSecretRef]] { - return a.baseRepo.Get(ctx, a.prefix) +func (a PrimaryAppSecretRefRepositoryImpl) Get(ctx context.Context) (option.Maybe[models.PrimaryAppSecretRef], error) { + return a.baseRepo.Get(ctx, a.key) } func (a PrimaryAppSecretRefRepositoryImpl) Set( ctx context.Context, value models.PrimaryAppSecretRef, expiration time.Duration, -) single.Single[models.PrimaryAppSecretRef] { - return a.baseRepo.Set(ctx, a.prefix, value, expiration) +) (models.PrimaryAppSecretRef, error) { + return a.baseRepo.Set(ctx, a.key, value, expiration) } func NewPrimaryAppSecretRefRepositoryImpl( redisDBHandler *dshandlers.RedisDBHandler, ) *PrimaryAppSecretRefRepositoryImpl { - appSecretKeyPrefix := kvstoreutils.CombineKeySections(kvStoreKeyPrefix, "mainAppSecretRef") + key := kvstoreutils.CombineKeySections(kvStoreKeyPrefix, "mainAppSecretRef") baseRepo := baserepos.NewKeyValueTimedRepositoryRedis[models.PrimaryAppSecretRef](redisDBHandler) - return &PrimaryAppSecretRefRepositoryImpl{prefix: appSecretKeyPrefix, baseRepo: baseRepo} + return &PrimaryAppSecretRefRepositoryImpl{key: key, baseRepo: baseRepo} } diff --git a/microservices/go/cmd/keyservice/repositories/userkey-session-repository.go b/microservices/go/cmd/keyservice/repositories/userkey-session-repository.go index ff1e672..286f89b 100644 --- a/microservices/go/cmd/keyservice/repositories/userkey-session-repository.go +++ b/microservices/go/cmd/keyservice/repositories/userkey-session-repository.go @@ -5,7 +5,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/baserepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/kvstoreutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "time" @@ -23,7 +22,7 @@ type UserKeySessionRepositoryImpl struct { func (a UserKeySessionRepositoryImpl) Get( ctx context.Context, key string, -) single.Single[option.Maybe[models.UserKeySession]] { +) (option.Maybe[models.UserKeySession], error) { return a.baseRepo.Get(ctx, kvstoreutils.CombineKeySections(a.prefix, key)) } @@ -32,7 +31,7 @@ func (a UserKeySessionRepositoryImpl) Set( key string, value models.UserKeySession, expiration time.Duration, -) single.Single[models.UserKeySession] { +) (models.UserKeySession, error) { return a.baseRepo.Set(ctx, kvstoreutils.CombineKeySections(a.prefix, key), value, expiration) } diff --git a/microservices/go/cmd/keyservice/services/appsecret-service.go b/microservices/go/cmd/keyservice/services/appsecret-service.go index 8673657..e696307 100644 --- a/microservices/go/cmd/keyservice/services/appsecret-service.go +++ b/microservices/go/cmd/keyservice/services/appsecret-service.go @@ -34,7 +34,9 @@ type AppSecretServiceImpl struct { } func (a AppSecretServiceImpl) GetPrimaryAppSecret(ctx context.Context) single.Single[bos.AppSecretBo] { - refFindSrc := a.primaryAppSecretRefRepository.Get(ctx) + refFindSrc := single.FromSupplierCached(func() (option.Maybe[models.PrimaryAppSecretRef], error) { + return a.primaryAppSecretRefRepository.Get(ctx) + }) return single.FlatMap(refFindSrc, func(maybe option.Maybe[models.PrimaryAppSecretRef]) single.Single[bos.AppSecretBo] { if ref, ok := maybe.Get(); ok { @@ -46,7 +48,9 @@ func (a AppSecretServiceImpl) GetPrimaryAppSecret(ctx context.Context) single.Si } func (a AppSecretServiceImpl) GetAppSecret(ctx context.Context, kid string) single.Single[bos.AppSecretBo] { - appSecretFindSrc := a.appSecretRepository.Get(ctx, kid) + appSecretFindSrc := single.FromSupplierCached(func() (option.Maybe[models.AppSecret], error) { + return a.appSecretRepository.Get(ctx, kid) + }) return single.MapWithError(appSecretFindSrc, func(maybe option.Maybe[models.AppSecret]) (bos.AppSecretBo, error) { appSecretBoMaybe := option.Map(maybe, func(appSecret models.AppSecret) bos.AppSecretBo { return bos.NewAppSecretBo(kid, appSecret.SecretKey) @@ -69,13 +73,12 @@ func (a AppSecretServiceImpl) GeneratePrimaryAppSecret(ctx context.Context) sing return single.FlatMap(kidKeySrc, func(t tuple.T2[string, []byte]) single.Single[bos.AppSecretBo] { ref := models.PrimaryAppSecretRef{Kid: t.V1} appSecret := models.AppSecret{SecretKey: t.V2} - secretSaveSrc := a.appSecretRepository.Set(ctx, ref.Kid, appSecret, a.keyConf.GetSecretDuration()) - refSavedSrc := single.FlatMap( - secretSaveSrc, - func(_ models.AppSecret) single.Single[models.PrimaryAppSecretRef] { - return a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()) - }, - ) + secretSaveSrc := single.FromSupplierCached(func() (models.AppSecret, error) { + return a.appSecretRepository.Set(ctx, ref.Kid, appSecret, a.keyConf.GetSecretDuration()) + }) + refSavedSrc := single.MapWithError(secretSaveSrc, func(_ models.AppSecret) (models.PrimaryAppSecretRef, error) { + return a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()) + }) return single.Map(refSavedSrc, func(_ models.PrimaryAppSecretRef) bos.AppSecretBo { return bos.NewAppSecretBo(ref.Kid, appSecret.SecretKey) }) diff --git a/microservices/go/cmd/keyservice/services/userkey-service.go b/microservices/go/cmd/keyservice/services/userkey-service.go index 02808cc..4390778 100644 --- a/microservices/go/cmd/keyservice/services/userkey-service.go +++ b/microservices/go/cmd/keyservice/services/userkey-service.go @@ -171,7 +171,9 @@ func (u UserKeyServiceImpl) NewKeySession( } startTime := time.Now().UnixMilli() sessionDuration := u.keyConf.GetTokenSessionDuration() - savedKeySession := u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration) + savedKeySession := single.FromSupplierCached(func() (models.UserKeySession, error) { + return u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration) + }) return single.Map(savedKeySession, func(_ models.UserKeySession) commondtos.UKeySessionDto { return commondtos.UKeySessionDto{ Token: token, @@ -192,7 +194,10 @@ func (u UserKeyServiceImpl) GetKeyFromSession( ctx context.Context, sessionDto commondtos.UKeySessionDto, ) single.Single[keydtos.UserKeyDto] { - storedSessionSrc := single.FlatMap(u.userKeySessionRepository.Get(ctx, sessionDto.ProxyKid), + findStoresSessSrc := single.FromSupplierCached(func() (option.Maybe[models.UserKeySession], error) { + return u.userKeySessionRepository.Get(ctx, sessionDto.ProxyKid) + }) + storedSessionSrc := single.FlatMap(findStoresSessSrc, func(maybe option.Maybe[models.UserKeySession]) single.Single[models.UserKeySession] { return option.Map(maybe, single.Just[models.UserKeySession]). OrElseGet(func() single.Single[models.UserKeySession] { diff --git a/microservices/go/pkg/datasource/baserepos/kvtimed-repository.go b/microservices/go/pkg/datasource/baserepos/kvtimed-repository.go index 68dc836..de3a3e5 100644 --- a/microservices/go/pkg/datasource/baserepos/kvtimed-repository.go +++ b/microservices/go/pkg/datasource/baserepos/kvtimed-repository.go @@ -5,15 +5,14 @@ import ( "encoding/json" "errors" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "time" ) type KeyValueTimedRepository[Value any] interface { - Get(ctx context.Context, key string) single.Single[option.Maybe[Value]] - Set(ctx context.Context, key string, value Value, expiration time.Duration) single.Single[Value] + Get(ctx context.Context, key string) (option.Maybe[Value], error) + Set(ctx context.Context, key string, value Value, expiration time.Duration) (Value, error) } // KeyValueTimedRepositoryRedis is a Redis implementation of KeyValueTimedRepository @@ -21,11 +20,7 @@ type KeyValueTimedRepositoryRedis[Value any] struct { redisDBHandler *dshandlers.RedisDBHandler } -func (k KeyValueTimedRepositoryRedis[Value]) Get(ctx context.Context, key string) single.Single[option.Maybe[Value]] { - return single.FromSupplierCached[option.Maybe[Value]](func() (option.Maybe[Value], error) { return k.runGet(ctx, key) }) -} - -func (k KeyValueTimedRepositoryRedis[Value]) runGet(ctx context.Context, key string) (option.Maybe[Value], error) { +func (k KeyValueTimedRepositoryRedis[Value]) Get(ctx context.Context, key string) (option.Maybe[Value], error) { if utils.StringIsBlank(key) { return option.None[Value](), errors.New("key cannot be empty") } @@ -46,15 +41,6 @@ func (k KeyValueTimedRepositoryRedis[Value]) Set( key string, value Value, expiration time.Duration, -) single.Single[Value] { - return single.FromSupplierCached[Value](func() (Value, error) { return k.runSet(ctx, key, value, expiration) }) -} - -func (k KeyValueTimedRepositoryRedis[Value]) runSet( - ctx context.Context, - key string, - value Value, - expiration time.Duration, ) (Value, error) { if utils.StringIsBlank(key) { return value, errors.New("key cannot be empty") diff --git a/microservices/go/pkg/datasource/dshandlers/dshandler.go b/microservices/go/pkg/datasource/dshandlers/dshandler.go index 862f09f..633a04d 100644 --- a/microservices/go/pkg/datasource/dshandlers/dshandler.go +++ b/microservices/go/pkg/datasource/dshandlers/dshandler.go @@ -1,7 +1,6 @@ package dshandlers import ( - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) @@ -11,15 +10,6 @@ type DataSourceHandler interface { IsNotFoundError(err error) bool } -func OptionalSingleQuerySrc[TQueryResult any]( - dbHandler DataSourceHandler, - supplier func() (TQueryResult, error), -) single.Single[option.Maybe[TQueryResult]] { - return single.FromSupplierCached(func() (option.Maybe[TQueryResult], error) { - return HandleSingleFind(dbHandler, supplier) - }) -} - func HandleSingleFind[TQueryResult any]( dbHandler DataSourceHandler, supplier func() (TQueryResult, error), From b29f2a6030d29aaa4907e59813263b6b9a72e28e Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 00:23:48 -0500 Subject: [PATCH 05/14] rm reactive code in grpc tools --- .../go/pkg/grpc/gtools/connection.go | 32 ------------------- .../grpcconnection-provider.go | 28 +++++++++------- 2 files changed, 16 insertions(+), 44 deletions(-) delete mode 100644 microservices/go/pkg/grpc/gtools/connection.go diff --git a/microservices/go/pkg/grpc/gtools/connection.go b/microservices/go/pkg/grpc/gtools/connection.go deleted file mode 100644 index aa9a127..0000000 --- a/microservices/go/pkg/grpc/gtools/connection.go +++ /dev/null @@ -1,32 +0,0 @@ -package gtools - -import ( - "github.com/akrennmair/slice" - "github.com/barweiss/go-tuple" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" - "google.golang.org/grpc" -) - -type DialOptionSingleCreator func() single.Single[grpc.DialOption] - -func CreateSingleWithDialOptions( - dialOptionSingles []single.Single[grpc.DialOption]) single.Single[[]grpc.DialOption] { - return slice.ReduceWithInitialValue( - dialOptionSingles, - single.Just([]grpc.DialOption{}), - func( - dialOptionsSrc single.Single[[]grpc.DialOption], - dialOptSrc single.Single[grpc.DialOption], - ) single.Single[[]grpc.DialOption] { - return single.Map(single.Zip2(dialOptionsSrc, dialOptSrc), - func(zipped tuple.T2[[]grpc.DialOption, grpc.DialOption]) []grpc.DialOption { - return append(zipped.V1, zipped.V2) - }, - ) - }, - ) -} - -func CreateConnectionWithOptions(addr string, options ...grpc.DialOption) (*grpc.ClientConn, error) { - return grpc.Dial(addr, options...) -} diff --git a/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go b/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go index c7a5a2f..140b5a4 100644 --- a/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go +++ b/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go @@ -2,6 +2,7 @@ package externalservices import ( "context" + "github.com/obenkenobi/cypher-log/microservices/go/pkg/concurrent" "github.com/obenkenobi/cypher-log/microservices/go/pkg/conf" "github.com/obenkenobi/cypher-log/microservices/go/pkg/environment" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/gtools" @@ -18,22 +19,25 @@ type CoreGrpcConnProviderImpl struct { tlsConf conf.TLSConf } -func (u CoreGrpcConnProviderImpl) CreateConnectionSingle(ctx context.Context, address string) single.Single[*grpc.ClientConn] { - var dialOptSources []single.Single[grpc.DialOption] +func (u CoreGrpcConnProviderImpl) CreateConnectionSingle(_ context.Context, address string) single.Single[*grpc.ClientConn] { + var dialOptions []grpc.DialOption if environment.ActivateGRPCAuth() { - oathTokenSrc := single.FromSupplierCached(u.systemAccessTokenClient.GetGRPCAccessToken) + oathTokenFuture := concurrent.Async(u.systemAccessTokenClient.GetGRPCAccessToken) if u.tlsConf.WillLoadCACert() { - tlsOptSrc := single.FromSupplierCached(func() (grpc.DialOption, error) { - return gtools.LoadTLSCredentialsOption(u.tlsConf.CACertPath(), environment.IsDevelopment()) - }) - dialOptSources = append(dialOptSources, tlsOptSrc) + tlsOpt, err := gtools.LoadTLSCredentialsOption(u.tlsConf.CACertPath(), environment.IsDevelopment()) + if err != nil { + return single.Error[*grpc.ClientConn](err) + } + dialOptions = append(dialOptions, tlsOpt) } - oathOptSrc := single.Map(oathTokenSrc, gtools.OathAccessOption) - dialOptSources = append(dialOptSources, oathOptSrc) + token, err := oathTokenFuture.Await() + if err != nil { + return single.Error[*grpc.ClientConn](err) + } + dialOptions = append(dialOptions, gtools.OathAccessOption(token)) } - optsSrc := gtools.CreateSingleWithDialOptions(dialOptSources) - return single.MapWithError(optsSrc, func(opts []grpc.DialOption) (*grpc.ClientConn, error) { - return gtools.CreateConnectionWithOptions(address, opts...) + return single.FromSupplierCached(func() (*grpc.ClientConn, error) { + return grpc.Dial(address, dialOptions...) }) } From a9710f9813acb26c1c1751ebd526bf79fdff6306 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 00:28:23 -0500 Subject: [PATCH 06/14] rm reactive code from messaging pkg --- .../cmd/userservice/services/user-service.go | 4 +- microservices/go/pkg/messaging/rmq/sender.go | 48 +++++++++---------- microservices/go/pkg/messaging/sender.go | 6 +-- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index 886caa7..fc523d2 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -258,7 +258,9 @@ func (u UserServiceImpl) sendUserChange( distUserDto := userdtos.UserChangeEventDto{} mappers.UserToUserChangeEventDto(user, &distUserDto) distUserDto.Action = action - return u.userMsgSendService.UserSaveSender().Send(distUserDto) + return single.FromSupplierCached(func() (userdtos.UserChangeEventDto, error) { + return u.userMsgSendService.UserSaveSender().Send(distUserDto) + }) } func userToUserReadDto(user models.User) userdtos.UserReadDto { diff --git a/microservices/go/pkg/messaging/rmq/sender.go b/microservices/go/pkg/messaging/rmq/sender.go index 0af50c7..816a042 100644 --- a/microservices/go/pkg/messaging/rmq/sender.go +++ b/microservices/go/pkg/messaging/rmq/sender.go @@ -3,7 +3,6 @@ package rmq import ( "encoding/json" "github.com/obenkenobi/cypher-log/microservices/go/pkg/messaging" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/wagslane/go-rabbitmq" ) @@ -27,31 +26,28 @@ func NewSender[T any]( } } -func (r *Sender[T]) Send(body T) single.Single[T] { - return single.FromSupplierCached(func() (T, error) { - var msgBytes []byte - var contentType string - { - var bodyMatcher interface{} = body - switch v := bodyMatcher.(type) { - case string: - msgBytes = []byte(v) - contentType = ContentTypePlainText - default: - var err error - if msgBytes, err = json.Marshal(body); err != nil { - return body, err - } - contentType = ContentTypeJson +func (r *Sender[T]) Send(body T) (T, error) { + var msgBytes []byte + var contentType string + { + var bodyMatcher interface{} = body + switch v := bodyMatcher.(type) { + case string: + msgBytes = []byte(v) + contentType = ContentTypePlainText + default: + var err error + if msgBytes, err = json.Marshal(body); err != nil { + return body, err } + contentType = ContentTypeJson } - publishOpts := append(r.publishOpts, - rabbitmq.WithPublishOptionsContentType(contentType), - rabbitmq.WithPublishOptionsPersistentDelivery, - rabbitmq.WithPublishOptionsMandatory) - r.publisher.NotifyReturn() - err := r.publisher.Publish(msgBytes, r.routingKeys, publishOpts...) - return body, err - }) - + } + publishOpts := append(r.publishOpts, + rabbitmq.WithPublishOptionsContentType(contentType), + rabbitmq.WithPublishOptionsPersistentDelivery, + rabbitmq.WithPublishOptionsMandatory) + r.publisher.NotifyReturn() + err := r.publisher.Publish(msgBytes, r.routingKeys, publishOpts...) + return body, err } diff --git a/microservices/go/pkg/messaging/sender.go b/microservices/go/pkg/messaging/sender.go index 402e804..bd1b7bf 100644 --- a/microservices/go/pkg/messaging/sender.go +++ b/microservices/go/pkg/messaging/sender.go @@ -1,9 +1,5 @@ package messaging -import ( - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" -) - type Sender[T any] interface { - Send(body T) single.Single[T] + Send(body T) (T, error) } From 228fef49a81188fd8c2e3f61b16e7615fa5792b2 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 00:54:35 -0500 Subject: [PATCH 07/14] rm reactive code from external services --- .../cmd/noteservice/services/note-service.go | 16 +++-- .../grpcconnection-provider.go | 13 ++-- .../externalservices/user-service.go | 72 ++++++++++--------- .../externalservices/userkeyservice.go | 41 ++++++----- .../go/pkg/sharedservices/user-service.go | 8 ++- 5 files changed, 87 insertions(+), 63 deletions(-) diff --git a/microservices/go/cmd/noteservice/services/note-service.go b/microservices/go/cmd/noteservice/services/note-service.go index bf20510..596fff7 100644 --- a/microservices/go/cmd/noteservice/services/note-service.go +++ b/microservices/go/cmd/noteservice/services/note-service.go @@ -67,7 +67,9 @@ func (n NoteServiceImpl) AddNoteTransaction( return dshandlers.TransactionalSingle(ctx, n.crudDSHandler, func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { sessDto, noteCreateDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessDto).ScheduleLazyAndCache(ctx) + keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { + return n.userKeyService.GetKeyFromSession(ctx, sessDto) + }) keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey).ScheduleLazyAndCache(ctx) titleCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { return cipherutils.EncryptAES(key, []byte(noteCreateDto.Title)) @@ -102,7 +104,9 @@ func (n NoteServiceImpl) UpdateNoteTransaction( func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { sessDto, noteUpdateDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) existingSrc := n.getExistingNote(ctx, noteUpdateDto.Id).ScheduleLazyAndCache(ctx) - keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessDto).ScheduleLazyAndCache(ctx) + keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { + return n.userKeyService.GetKeyFromSession(ctx, sessDto) + }) keySrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) ([]byte, error) { existing, keyDto := t.V1, t.V2 @@ -163,7 +167,9 @@ func (n NoteServiceImpl) GetNoteById( ) single.Single[nDTOs.NoteReadDto] { sessDto, noteIdDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) existingSrc := n.getExistingNote(ctx, noteIdDto.Id).ScheduleLazyAndCache(ctx) - keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessDto).ScheduleLazyAndCache(ctx) + keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { + return n.userKeyService.GetKeyFromSession(ctx, sessDto) + }) validationSrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) (any, error) { existing, keyDto := t.V1, t.V2 @@ -208,7 +214,9 @@ func (n NoteServiceImpl) GetNotesPage( return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) } zippedSrc := single.FromSupplierCached(func() (tuple.T3[kDTOs.UserKeyDto, []byte, int64], error) { - keyDtoSrc := n.userKeyService.GetKeyFromSession(ctx, sessionDto) + keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { + return n.userKeyService.GetKeyFromSession(ctx, sessionDto) + }) keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey) countSrc := single.FromSupplierCached(func() (int64, error) { return n.noteRepository.CountByUserId(ctx, userBo.Id) diff --git a/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go b/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go index 140b5a4..5e2e3f4 100644 --- a/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go +++ b/microservices/go/pkg/sharedservices/externalservices/grpcconnection-provider.go @@ -6,12 +6,11 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/conf" "github.com/obenkenobi/cypher-log/microservices/go/pkg/environment" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/gtools" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "google.golang.org/grpc" ) type CoreGrpcConnProvider interface { - CreateConnectionSingle(ctx context.Context, address string) single.Single[*grpc.ClientConn] + CreateConnectionSingle(ctx context.Context, address string) (*grpc.ClientConn, error) } type CoreGrpcConnProviderImpl struct { @@ -19,26 +18,24 @@ type CoreGrpcConnProviderImpl struct { tlsConf conf.TLSConf } -func (u CoreGrpcConnProviderImpl) CreateConnectionSingle(_ context.Context, address string) single.Single[*grpc.ClientConn] { +func (u CoreGrpcConnProviderImpl) CreateConnectionSingle(_ context.Context, address string) (*grpc.ClientConn, error) { var dialOptions []grpc.DialOption if environment.ActivateGRPCAuth() { oathTokenFuture := concurrent.Async(u.systemAccessTokenClient.GetGRPCAccessToken) if u.tlsConf.WillLoadCACert() { tlsOpt, err := gtools.LoadTLSCredentialsOption(u.tlsConf.CACertPath(), environment.IsDevelopment()) if err != nil { - return single.Error[*grpc.ClientConn](err) + return nil, err } dialOptions = append(dialOptions, tlsOpt) } token, err := oathTokenFuture.Await() if err != nil { - return single.Error[*grpc.ClientConn](err) + return nil, err } dialOptions = append(dialOptions, gtools.OathAccessOption(token)) } - return single.FromSupplierCached(func() (*grpc.ClientConn, error) { - return grpc.Dial(address, dialOptions...) - }) + return grpc.Dial(address, dialOptions...) } func NewCoreGrpcConnProviderImpl( diff --git a/microservices/go/pkg/sharedservices/externalservices/user-service.go b/microservices/go/pkg/sharedservices/externalservices/user-service.go index 08ac439..5e008e6 100644 --- a/microservices/go/pkg/sharedservices/externalservices/user-service.go +++ b/microservices/go/pkg/sharedservices/externalservices/user-service.go @@ -6,14 +6,13 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/gtools" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/userpb" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers/grpcmappers" "google.golang.org/grpc" ) type ExtUserService interface { - GetByAuthId(ctx context.Context, authId string) single.Single[userdtos.UserReadDto] - GetById(ctx context.Context, id string) single.Single[userdtos.UserReadDto] + GetByAuthId(ctx context.Context, authId string) (userdtos.UserReadDto, error) + GetById(ctx context.Context, id string) (userdtos.UserReadDto, error) } type ExtUserServiceImpl struct { @@ -21,37 +20,46 @@ type ExtUserServiceImpl struct { coreGrpcConnProvider CoreGrpcConnProvider } -func (u ExtUserServiceImpl) GetById(ctx context.Context, id string) single.Single[userdtos.UserReadDto] { - connectionSrc := u.coreGrpcConnProvider.CreateConnectionSingle(ctx, u.grpcClientConf.UserServiceAddress()) - userReplySrc := single.MapWithError( - connectionSrc, - func(conn *grpc.ClientConn) (reply *userpb.UserReply, err error) { - defer func(conn *grpc.ClientConn) { err = conn.Close() }(conn) - userService := userpb.NewUserServiceClient(conn) - reply, err = userService.GetUserById(ctx, &userpb.IdRequest{Id: id}) - return reply, gtools.NewErrorResponseHandler(err).GetProcessedError() - }, - ) - return single.Map(userReplySrc, func(reply *userpb.UserReply) userdtos.UserReadDto { - dto := userdtos.UserReadDto{} - grpcmappers.UserReplyToUserReadDto(reply, &dto) - return dto - }) +func (u ExtUserServiceImpl) GetById(ctx context.Context, id string) (userDto userdtos.UserReadDto, err error) { + conn, err := u.coreGrpcConnProvider.CreateConnectionSingle(ctx, u.grpcClientConf.UserServiceAddress()) + if err != nil { + return userDto, err + } + defer func(conn *grpc.ClientConn) { + err = conn.Close() + }(conn) + + userService := userpb.NewUserServiceClient(conn) + reply, err := userService.GetUserById(ctx, &userpb.IdRequest{Id: id}) + if err != nil { + err = gtools.NewErrorResponseHandler(err).GetProcessedError() + return userDto, err + } + + dto := userdtos.UserReadDto{} + grpcmappers.UserReplyToUserReadDto(reply, &dto) + return dto, nil } -func (u ExtUserServiceImpl) GetByAuthId(ctx context.Context, authId string) single.Single[userdtos.UserReadDto] { - connectionSrc := u.coreGrpcConnProvider.CreateConnectionSingle(ctx, u.grpcClientConf.UserServiceAddress()) - userReplySrc := single.MapWithError(connectionSrc, func(conn *grpc.ClientConn) (*userpb.UserReply, error) { - defer conn.Close() - userService := userpb.NewUserServiceClient(conn) - reply, err := userService.GetUserByAuthId(ctx, &userpb.AuthIdRequest{AuthId: authId}) - return reply, gtools.NewErrorResponseHandler(err).GetProcessedError() - }) - return single.Map(userReplySrc, func(reply *userpb.UserReply) userdtos.UserReadDto { - dto := userdtos.UserReadDto{} - grpcmappers.UserReplyToUserReadDto(reply, &dto) - return dto - }) +func (u ExtUserServiceImpl) GetByAuthId(ctx context.Context, authId string) (userDto userdtos.UserReadDto, err error) { + conn, err := u.coreGrpcConnProvider.CreateConnectionSingle(ctx, u.grpcClientConf.UserServiceAddress()) + if err != nil { + return userDto, err + } + defer func(conn *grpc.ClientConn) { + err = conn.Close() + }(conn) + + userService := userpb.NewUserServiceClient(conn) + reply, err := userService.GetUserByAuthId(ctx, &userpb.AuthIdRequest{AuthId: authId}) + if err != nil { + err = gtools.NewErrorResponseHandler(err).GetProcessedError() + return userDto, err + } + + dto := userdtos.UserReadDto{} + grpcmappers.UserReplyToUserReadDto(reply, &dto) + return dto, nil } func NewExtUserServiceImpl( diff --git a/microservices/go/pkg/sharedservices/externalservices/userkeyservice.go b/microservices/go/pkg/sharedservices/externalservices/userkeyservice.go index a03eaff..a2eab92 100644 --- a/microservices/go/pkg/sharedservices/externalservices/userkeyservice.go +++ b/microservices/go/pkg/sharedservices/externalservices/userkeyservice.go @@ -7,7 +7,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/userkeypb" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers/grpcmappers" "google.golang.org/grpc" ) @@ -16,7 +15,7 @@ type ExtUserKeyService interface { GetKeyFromSession( ctx context.Context, userKeySessionDto commondtos.UKeySessionDto, - ) single.Single[keydtos.UserKeyDto] + ) (keydtos.UserKeyDto, error) } type ExtUserKeyServiceImpl struct { @@ -27,21 +26,29 @@ type ExtUserKeyServiceImpl struct { func (e ExtUserKeyServiceImpl) GetKeyFromSession( ctx context.Context, userKeySessionDto commondtos.UKeySessionDto, -) single.Single[keydtos.UserKeyDto] { - connectionSrc := e.coreGrpcConnProvider.CreateConnectionSingle(ctx, e.grpcClientConf.KeyServiceAddress()) - replySrc := single.MapWithError(connectionSrc, func(conn *grpc.ClientConn) (*userkeypb.UserKey, error) { - defer conn.Close() - client := userkeypb.NewUserKeyServiceClient(conn) - userKeySession := &userkeypb.UserKeySession{} - grpcmappers.UserKeySessionDtoToUserKeySession(&userKeySessionDto, userKeySession) - reply, err := client.GetKeyFromSession(ctx, userKeySession) - return reply, gtools.NewErrorResponseHandler(err).GetProcessedError() - }) - return single.Map(replySrc, func(res *userkeypb.UserKey) keydtos.UserKeyDto { - dto := keydtos.UserKeyDto{} - grpcmappers.UserKeyToUserKeyDto(res, &dto) - return dto - }) +) (keyDto keydtos.UserKeyDto, err error) { + conn, err := e.coreGrpcConnProvider.CreateConnectionSingle(ctx, e.grpcClientConf.KeyServiceAddress()) + if err != nil { + return keyDto, err + } + defer func(conn *grpc.ClientConn) { + err = conn.Close() + }(conn) + + client := userkeypb.NewUserKeyServiceClient(conn) + + userKeySession := &userkeypb.UserKeySession{} + grpcmappers.UserKeySessionDtoToUserKeySession(&userKeySessionDto, userKeySession) + + reply, err := client.GetKeyFromSession(ctx, userKeySession) + if err != nil { + err = gtools.NewErrorResponseHandler(err).GetProcessedError() + return keyDto, err + } + + dto := keydtos.UserKeyDto{} + grpcmappers.UserKeyToUserKeyDto(reply, &dto) + return dto, nil } func NewExtUserKeyServiceImpl( diff --git a/microservices/go/pkg/sharedservices/user-service.go b/microservices/go/pkg/sharedservices/user-service.go index 265f7ac..426267f 100644 --- a/microservices/go/pkg/sharedservices/user-service.go +++ b/microservices/go/pkg/sharedservices/user-service.go @@ -76,7 +76,9 @@ func (u UserServiceImpl) RequireUser(ctx context.Context, identity security.Iden return single.Just(user) } else { // If user is not stored locally in the database - extUserFindSrc := u.extUserService.GetByAuthId(ctx, identity.GetAuthId()) + extUserFindSrc := single.FromSupplierCached(func() (userdtos.UserReadDto, error) { + return u.extUserService.GetByAuthId(ctx, identity.GetAuthId()) + }) return single.FlatMap(extUserFindSrc, func(extUserDto userdtos.UserReadDto) single.Single[cModels.User] { if !extUserDto.Exists { userReqFailRuleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeUserRequireFail) @@ -101,7 +103,9 @@ func (u UserServiceImpl) UserExistsWithId(ctx context.Context, userId string) si return option.Map(userMaybe, func(_ cModels.User) single.Single[bool] { return single.Just(true) }).OrElseGet(func() single.Single[bool] { - extUserFindSrc := u.extUserService.GetById(ctx, userId) + extUserFindSrc := single.FromSupplierCached(func() (userdtos.UserReadDto, error) { + return u.extUserService.GetById(ctx, userId) + }) return single.FlatMap(extUserFindSrc, func(extUserDto userdtos.UserReadDto) single.Single[bool] { return single.Just(extUserDto.Exists) }) From 4cbb784271c2b32b0746ff77ad2f073a1bb37849 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 01:30:46 -0500 Subject: [PATCH 08/14] rm singles from gin ctx service --- .../controllers/userkeycontroller.go | 48 ++++++------ .../noteservice/controllers/notecontroller.go | 77 +++++++++++-------- .../controllers/user-controller.go | 36 +++++---- .../ginservices/ginctx-service.go | 47 ++++++----- 4 files changed, 113 insertions(+), 95 deletions(-) diff --git a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go index fe42943..7deea53 100644 --- a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go +++ b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go @@ -1,7 +1,6 @@ package controllers import ( - "github.com/barweiss/go-tuple" "github.com/gin-gonic/gin" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/services" "github.com/obenkenobi/cypher-log/microservices/go/pkg/middlewares" @@ -40,50 +39,55 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userKeyGroupV1.POST("/passcode", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, keydtos.PasscodeCreateDto]) single.Single[commondtos.SuccessDto] { - userBos, passcodeDto := t.V1, t.V2 - return u.userKeyService.CreateUserKey(c, userBos, passcodeDto) + body, err := ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) + if err != nil { + u.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[commondtos.SuccessDto] { + return u.userKeyService.CreateUserKey(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userKeyGroupV1.POST("/newSession", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, keydtos.PasscodeDto]) single.Single[commondtos.UKeySessionDto] { - userBos, passcodeDto := t.V1, t.V2 - return u.userKeyService.NewKeySession(c, userBos, passcodeDto) + body, err := ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) + if err != nil { + u.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[commondtos.UKeySessionDto] { + return u.userKeyService.NewKeySession(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userKeyGroupV1.POST("/getKeyFromSession", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsSystemClient: true}), func(c *gin.Context) { - bodySrc := ginservices.ReadValueFromBody[commondtos.UKeySessionDto](u.ginCtxService, c) - businessLogicSrc := single.FlatMap(bodySrc, - func(sessionDto commondtos.UKeySessionDto) single.Single[keydtos.UserKeyDto] { - return u.userKeyService.GetKeyFromSession(c, sessionDto) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + body, err := ginservices.ReadValueFromBody[commondtos.UKeySessionDto](u.ginCtxService, c) + if err != nil { + u.ginCtxService.HandleErrorResponse(c, err) + return + } + resBody, err := single.RetrieveValue(c, u.userKeyService.GetKeyFromSession(c, body)) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) } diff --git a/microservices/go/cmd/noteservice/controllers/notecontroller.go b/microservices/go/cmd/noteservice/controllers/notecontroller.go index eb24602..0003052 100644 --- a/microservices/go/cmd/noteservice/controllers/notecontroller.go +++ b/microservices/go/cmd/noteservice/controllers/notecontroller.go @@ -1,7 +1,6 @@ package controllers import ( - "github.com/barweiss/go-tuple" "github.com/gin-gonic/gin" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/services" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/pagination" @@ -35,71 +34,85 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]](n.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]]) single.Single[cDTOs.SuccessDto] { - userBos, dto := t.V1, t.V2 - return n.noteService.AddNoteTransaction(c, userBos, dto) - }, + body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]](n.ginCtxService, c) + if err != nil { + n.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { + return n.noteService.AddNoteTransaction(c, userBos, body) + }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOkOrError(c, resBody, err) + n.ginCtxService.RespondJsonOk(c, resBody, err) }) noteGroupV1.PUT("", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]](n.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]]) single.Single[cDTOs.SuccessDto] { - userBos, dto := t.V1, t.V2 - return n.noteService.UpdateNoteTransaction(c, userBos, dto) + body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]](n.ginCtxService, c) + if err != nil { + n.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { + return n.noteService.UpdateNoteTransaction(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOkOrError(c, resBody, err) + n.ginCtxService.RespondJsonOk(c, resBody, err) }) noteGroupV1.DELETE("", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, nDTOs.NoteIdDto]) single.Single[cDTOs.SuccessDto] { - userBos, dto := t.V1, t.V2 - return n.noteService.DeleteNoteTransaction(c, userBos, dto) + body, err := ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) + if err != nil { + n.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { + return n.noteService.DeleteNoteTransaction(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOkOrError(c, resBody, err) + n.ginCtxService.RespondJsonOk(c, resBody, err) }) noteGroupV1.POST("/getById", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]](n.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]]) single.Single[nDTOs.NoteReadDto] { - userBos, dto := t.V1, t.V2 - return n.noteService.GetNoteById(c, userBos, dto) + body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]](n.ginCtxService, c) + if err != nil { + n.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[nDTOs.NoteReadDto] { + return n.noteService.GetNoteById(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOkOrError(c, resBody, err) + n.ginCtxService.RespondJsonOk(c, resBody, err) }) noteGroupV1.POST("/getPage", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - bodySrc := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[pagination.PageRequest]](n.ginCtxService, c) - businessLogicSrc := single.FlatMap(single.Zip2(reqUserSrc, bodySrc), - func(t tuple.T2[userbos.UserBo, cDTOs.UKeySessionReqDto[pagination.PageRequest]]) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { - userBos, dto := t.V1, t.V2 - return n.noteService.GetNotesPage(c, userBos, dto) + body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[pagination.PageRequest]](n.ginCtxService, c) + if err != nil { + n.ginCtxService.HandleErrorResponse(c, err) + return + } + businessLogicSrc := single.FlatMap(reqUserSrc, + func(userBos userbos.UserBo) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { + return n.noteService.GetNotesPage(c, userBos, body) }, ) resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOkOrError(c, resBody, err) + n.ginCtxService.RespondJsonOk(c, resBody, err) }) } diff --git a/microservices/go/cmd/userservice/controllers/user-controller.go b/microservices/go/cmd/userservice/controllers/user-controller.go index c349ec5..8a38570 100644 --- a/microservices/go/cmd/userservice/controllers/user-controller.go +++ b/microservices/go/cmd/userservice/controllers/user-controller.go @@ -28,25 +28,27 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { userGroupV1.POST("", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - bodySrc := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) - businessLogicSrc := single.FlatMap(bodySrc, - func(userSaveDto userdtos.UserSaveDto) single.Single[userdtos.UserReadDto] { - return u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto) - }) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + userSaveDto, err := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) + if err != nil { + u.ginCtxService.HandleErrorResponse(c, err) + return + } + resBody, err := single.RetrieveValue(c, + u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto)) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userGroupV1.PUT("", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - bodySrc := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) - businessLogicSrc := single.FlatMap(bodySrc, - func(userSaveDto userdtos.UserSaveDto) single.Single[userdtos.UserReadDto] { - return u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto) - }) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + userSaveDto, err := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) + if err != nil { + u.ginCtxService.HandleErrorResponse(c, err) + return + } + resBody, err := single.RetrieveValue(c, + u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto)) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userGroupV1.DELETE("", @@ -54,7 +56,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { func(c *gin.Context) { businessLogicSrc := u.userService.BeginDeletingUserTransaction(c, security.GetIdentityFromGinContext(c)) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userGroupV1.GET("/me", @@ -62,7 +64,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { func(c *gin.Context) { businessLogicSrc := u.userService.GetUserIdentity(c, security.GetIdentityFromGinContext(c)) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) userGroupV1.GET("/byAuthId/:id", @@ -71,7 +73,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { authId := c.Param("id") businessLogicSrc := u.userService.GetByAuthId(c, authId) resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOkOrError(c, resBody, err) + u.ginCtxService.RespondJsonOk(c, resBody, err) }) } diff --git a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go index 2c9aabd..c9c6a7f 100644 --- a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go +++ b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go @@ -6,7 +6,6 @@ import ( "github.com/go-playground/validator/v10" "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors" "github.com/obenkenobi/cypher-log/microservices/go/pkg/logger" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/queryreq" "net/http" @@ -28,12 +27,12 @@ type GinCtxService interface { // something went wrong with the server. HandleErrorResponse(c *gin.Context, err error) + // RespondJsonOk responds with json value with a 200 status code or an + // error if the error != nil. + RespondJsonOk(c *gin.Context, model any, err error) + // processBindError takes an error from binding a value from a request body processes it into a BadRequestError. processBindError(err error) apperrors.BadRequestError - - // RespondJsonOkOrError responds with json value with a 200 status code or an - // error if the error != nil. - RespondJsonOkOrError(c *gin.Context, model any, err error) } type GinCtxServiceImpl struct { @@ -48,18 +47,6 @@ func (q GinCtxServiceImpl) ReqQueryReader(c *gin.Context) queryreq.ReqQueryReade return queryreq.NewGinCtxReqQueryReaderImpl(c, q.errorMessageService) } -func (g GinCtxServiceImpl) processBindError(err error) apperrors.BadRequestError { - if fieldErrors, ok := err.(validator.ValidationErrors); ok { - appValErrors := slice.Map(fieldErrors, func(fieldError validator.FieldError) apperrors.ValidationError { - return apperrors.ValidationError{Field: fieldError.Field(), Message: fieldError.ActualTag()} - }) - return apperrors.NewBadReqErrorFromValidationErrors(appValErrors) - } - logger.Log.WithError(err).Info("Unable to bind json") - cannotBindJsonRuleErr := g.errorMessageService.RuleErrorFromCode(apperrors.ErrCodeCannotBindJson) - return apperrors.NewBadReqErrorFromRuleError(cannotBindJsonRuleErr) -} - func (g GinCtxServiceImpl) HandleErrorResponse(c *gin.Context, err error) { if badReqErr, ok := err.(apperrors.BadRequestError); ok { c.JSON(http.StatusBadRequest, badReqErr) @@ -69,7 +56,7 @@ func (g GinCtxServiceImpl) HandleErrorResponse(c *gin.Context, err error) { } } -func (g GinCtxServiceImpl) RespondJsonOkOrError(c *gin.Context, model any, err error) { +func (g GinCtxServiceImpl) RespondJsonOk(c *gin.Context, model any, err error) { if err != nil { g.HandleErrorResponse(c, err) return @@ -77,6 +64,18 @@ func (g GinCtxServiceImpl) RespondJsonOkOrError(c *gin.Context, model any, err e c.JSON(http.StatusOK, model) } +func (g GinCtxServiceImpl) processBindError(err error) apperrors.BadRequestError { + if fieldErrors, ok := err.(validator.ValidationErrors); ok { + appValErrors := slice.Map(fieldErrors, func(fieldError validator.FieldError) apperrors.ValidationError { + return apperrors.ValidationError{Field: fieldError.Field(), Message: fieldError.ActualTag()} + }) + return apperrors.NewBadReqErrorFromValidationErrors(appValErrors) + } + logger.Log.WithError(err).Info("Unable to bind json") + cannotBindJsonRuleErr := g.errorMessageService.RuleErrorFromCode(apperrors.ErrCodeCannotBindJson) + return apperrors.NewBadReqErrorFromRuleError(cannotBindJsonRuleErr) +} + // NewGinCtxServiceImpl creates a new GinCtxService instance func NewGinCtxServiceImpl(errorService sharedservices.ErrorService) *GinCtxServiceImpl { return &GinCtxServiceImpl{errorMessageService: errorService} @@ -85,18 +84,18 @@ func NewGinCtxServiceImpl(errorService sharedservices.ErrorService) *GinCtxServi // ReadValueFromBody reads the request body from a gin context and binds it to a // value provided in the type parameter. Using a pointer type is not permitted // and will trigger a panic. -func ReadValueFromBody[V any](ginCtxService GinCtxService, c *gin.Context) single.Single[V] { +func ReadValueFromBody[V any](ginCtxService GinCtxService, c *gin.Context) (V, error) { var value V if err := c.ShouldBind(&value); err != nil { - return single.Error[V](ginCtxService.processBindError(err)) + return value, ginCtxService.processBindError(err) } - return single.Just(value) + return value, nil } // BindBodyToPointer reads the request type and writes it to a value referenced by the pointer provided. -func BindBodyToPointer[V any](ginCtxService GinCtxService, c *gin.Context, value *V) single.Single[*V] { +func BindBodyToPointer[V any](ginCtxService GinCtxService, c *gin.Context, value *V) (*V, error) { if err := c.ShouldBind(value); err != nil { - return single.Error[*V](ginCtxService.processBindError(err)) + return value, ginCtxService.processBindError(err) } - return single.Just(value) + return value, nil } From 4abcfe04b3e669b014fb271b062de135d287a17c Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 03:04:13 -0500 Subject: [PATCH 09/14] Created a web request pipeline --- .../controllers/userkeycontroller.go | 96 ++++++----- .../noteservice/controllers/notecontroller.go | 154 ++++++++++-------- .../controllers/user-controller.go | 81 ++++++--- .../ginservices/ginctx-service.go | 17 +- .../go/pkg/web/controller/pipeline.go | 29 ++++ 5 files changed, 248 insertions(+), 129 deletions(-) create mode 100644 microservices/go/pkg/web/controller/pipeline.go diff --git a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go index 7deea53..bdbf095 100644 --- a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go +++ b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go @@ -13,6 +13,7 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/controller" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/routing" + "net/http" ) type UserKeyController interface { @@ -32,62 +33,81 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { userKeyGroupV1.GET("/exists", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[commondtos.ExistsDto] { - return u.userKeyService.UserKeyExists(c, userBos) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var resBody commondtos.ExistsDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, u.userKeyService.UserKeyExists(c, userBo)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) userKeyGroupV1.POST("/passcode", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) - if err != nil { - u.ginCtxService.HandleErrorResponse(c, err) + var userBo userbos.UserBo + var reqBody keydtos.PasscodeCreateDto + var resBody commondtos.SuccessDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, u.userKeyService.CreateUserKey(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[commondtos.SuccessDto] { - return u.userKeyService.CreateUserKey(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + }) }) userKeyGroupV1.POST("/newSession", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) - if err != nil { - u.ginCtxService.HandleErrorResponse(c, err) + var userBo userbos.UserBo + var reqBody keydtos.PasscodeDto + var resBody commondtos.UKeySessionDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, u.userKeyService.NewKeySession(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[commondtos.UKeySessionDto] { - return u.userKeyService.NewKeySession(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + }) }) userKeyGroupV1.POST("/getKeyFromSession", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsSystemClient: true}), func(c *gin.Context) { - body, err := ginservices.ReadValueFromBody[commondtos.UKeySessionDto](u.ginCtxService, c) - if err != nil { - u.ginCtxService.HandleErrorResponse(c, err) + var reqBody commondtos.UKeySessionDto + var resBody keydtos.UserKeyDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[commondtos.UKeySessionDto](u.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, u.userKeyService.GetKeyFromSession(c, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) return - } - resBody, err := single.RetrieveValue(c, u.userKeyService.GetKeyFromSession(c, body)) - u.ginCtxService.RespondJsonOk(c, resBody, err) + }) }) } diff --git a/microservices/go/cmd/noteservice/controllers/notecontroller.go b/microservices/go/cmd/noteservice/controllers/notecontroller.go index 0003052..d2d3d9b 100644 --- a/microservices/go/cmd/noteservice/controllers/notecontroller.go +++ b/microservices/go/cmd/noteservice/controllers/notecontroller.go @@ -14,6 +14,7 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/controller" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/routing" + "net/http" ) type NoteController interface { @@ -33,86 +34,111 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { noteGroupV1.POST("", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]](n.ginCtxService, c) - if err != nil { - n.ginCtxService.HandleErrorResponse(c, err) - return - } - businessLogicSrc := single.FlatMap(reqUserSrc, func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { - return n.noteService.AddNoteTransaction(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var reqBody cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto] + var resBody cDTOs.SuccessDto + + n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]]( + n.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, n.noteService.AddNoteTransaction(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) noteGroupV1.PUT("", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]](n.ginCtxService, c) - if err != nil { - n.ginCtxService.HandleErrorResponse(c, err) - return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { - return n.noteService.UpdateNoteTransaction(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var reqBody cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto] + var resBody cDTOs.SuccessDto + + n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]]( + n.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, n.noteService.UpdateNoteTransaction(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) noteGroupV1.DELETE("", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) - if err != nil { - n.ginCtxService.HandleErrorResponse(c, err) - return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[cDTOs.SuccessDto] { - return n.noteService.DeleteNoteTransaction(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var reqBody nDTOs.NoteIdDto + var resBody cDTOs.SuccessDto + + n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, n.noteService.DeleteNoteTransaction(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) noteGroupV1.POST("/getById", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]](n.ginCtxService, c) - if err != nil { - n.ginCtxService.HandleErrorResponse(c, err) - return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[nDTOs.NoteReadDto] { - return n.noteService.GetNoteById(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var reqBody cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto] + var resBody nDTOs.NoteReadDto + + n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]]( + n.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, n.noteService.GetNoteById(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) noteGroupV1.POST("/getPage", n.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - reqUserSrc := n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) - body, err := ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[pagination.PageRequest]](n.ginCtxService, c) - if err != nil { - n.ginCtxService.HandleErrorResponse(c, err) - return - } - businessLogicSrc := single.FlatMap(reqUserSrc, - func(userBos userbos.UserBo) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { - return n.noteService.GetNotesPage(c, userBos, body) - }, - ) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - n.ginCtxService.RespondJsonOk(c, resBody, err) + var userBo userbos.UserBo + var reqBody cDTOs.UKeySessionReqDto[pagination.PageRequest] + var resBody pagination.Page[nDTOs.NotePreviewDto] + + n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + return + }).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[pagination.PageRequest]]( + n.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, n.noteService.GetNotesPage(c, userBo, reqBody)) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) } diff --git a/microservices/go/cmd/userservice/controllers/user-controller.go b/microservices/go/cmd/userservice/controllers/user-controller.go index 8a38570..9c3fad2 100644 --- a/microservices/go/cmd/userservice/controllers/user-controller.go +++ b/microservices/go/cmd/userservice/controllers/user-controller.go @@ -10,6 +10,7 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/controller" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/routing" + "net/http" ) type UserController interface { @@ -28,52 +29,86 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { userGroupV1.POST("", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - userSaveDto, err := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) - if err != nil { - u.ginCtxService.HandleErrorResponse(c, err) + var reqBody userdtos.UserSaveDto + var resBody userdtos.UserReadDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, + u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody)) return - } - resBody, err := single.RetrieveValue(c, - u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto)) - u.ginCtxService.RespondJsonOk(c, resBody, err) + }).Next(func() error { + c.JSON(http.StatusOK, resBody) + return nil + }) }) userGroupV1.PUT("", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - userSaveDto, err := ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) - if err != nil { - u.ginCtxService.HandleErrorResponse(c, err) + var reqBody userdtos.UserSaveDto + var resBody userdtos.UserReadDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + reqBody, err = ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) + return + }).Next(func() (err error) { + resBody, err = single.RetrieveValue(c, + u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody)) return - } - resBody, err := single.RetrieveValue(c, - u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), userSaveDto)) - u.ginCtxService.RespondJsonOk(c, resBody, err) + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return nil + }) }) userGroupV1.DELETE("", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - businessLogicSrc := u.userService.BeginDeletingUserTransaction(c, security.GetIdentityFromGinContext(c)) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + var resBody userdtos.UserReadDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + businessLogicSrc := u.userService.BeginDeletingUserTransaction(c, security.GetIdentityFromGinContext(c)) + resBody, err = single.RetrieveValue(c, businessLogicSrc) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) userGroupV1.GET("/me", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsUser: true}), func(c *gin.Context) { - businessLogicSrc := u.userService.GetUserIdentity(c, security.GetIdentityFromGinContext(c)) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + var resBody userdtos.UserIdentityDto + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + businessLogicSrc := u.userService.GetUserIdentity(c, security.GetIdentityFromGinContext(c)) + resBody, err = single.RetrieveValue(c, businessLogicSrc) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) }) userGroupV1.GET("/byAuthId/:id", u.authMiddleware.Authorization(middlewares.AuthorizerSettings{VerifyIsSystemClient: true}), func(c *gin.Context) { + var resBody userdtos.UserReadDto authId := c.Param("id") - businessLogicSrc := u.userService.GetByAuthId(c, authId) - resBody, err := single.RetrieveValue(c, businessLogicSrc) - u.ginCtxService.RespondJsonOk(c, resBody, err) + + u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { + businessLogicSrc := u.userService.GetByAuthId(c, authId) + resBody, err = single.RetrieveValue(c, businessLogicSrc) + return + }).Next(func() (err error) { + c.JSON(http.StatusOK, resBody) + return + }) + }) } diff --git a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go index c9c6a7f..2d3d661 100644 --- a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go +++ b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go @@ -7,6 +7,7 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors" "github.com/obenkenobi/cypher-log/microservices/go/pkg/logger" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" + "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/controller" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/queryreq" "net/http" ) @@ -21,11 +22,11 @@ type GinCtxService interface { // ReqQueryReader returns a reader to parse your request query params ReqQueryReader(c *gin.Context) queryreq.ReqQueryReader - // HandleErrorResponse takes an error and parses it to set the appropriate http + // RespondError takes an error and parses it to set the appropriate http // response. Certain errors relating to user input will trigger a 4XX status // code. Otherwise, a 5XX code will be thrown indicating the error means // something went wrong with the server. - HandleErrorResponse(c *gin.Context, err error) + RespondError(c *gin.Context, err error) // RespondJsonOk responds with json value with a 200 status code or an // error if the error != nil. @@ -33,6 +34,8 @@ type GinCtxService interface { // processBindError takes an error from binding a value from a request body processes it into a BadRequestError. processBindError(err error) apperrors.BadRequestError + + StartCtxPipeline(c *gin.Context) controller.Pipeline } type GinCtxServiceImpl struct { @@ -47,7 +50,7 @@ func (q GinCtxServiceImpl) ReqQueryReader(c *gin.Context) queryreq.ReqQueryReade return queryreq.NewGinCtxReqQueryReaderImpl(c, q.errorMessageService) } -func (g GinCtxServiceImpl) HandleErrorResponse(c *gin.Context, err error) { +func (g GinCtxServiceImpl) RespondError(c *gin.Context, err error) { if badReqErr, ok := err.(apperrors.BadRequestError); ok { c.JSON(http.StatusBadRequest, badReqErr) } else { @@ -58,7 +61,7 @@ func (g GinCtxServiceImpl) HandleErrorResponse(c *gin.Context, err error) { func (g GinCtxServiceImpl) RespondJsonOk(c *gin.Context, model any, err error) { if err != nil { - g.HandleErrorResponse(c, err) + g.RespondError(c, err) return } c.JSON(http.StatusOK, model) @@ -76,6 +79,12 @@ func (g GinCtxServiceImpl) processBindError(err error) apperrors.BadRequestError return apperrors.NewBadReqErrorFromRuleError(cannotBindJsonRuleErr) } +func (g GinCtxServiceImpl) StartCtxPipeline(c *gin.Context) controller.Pipeline { + return controller.NewPipelineImpl(func(err error) { + g.RespondError(c, err) + }) +} + // NewGinCtxServiceImpl creates a new GinCtxService instance func NewGinCtxServiceImpl(errorService sharedservices.ErrorService) *GinCtxServiceImpl { return &GinCtxServiceImpl{errorMessageService: errorService} diff --git a/microservices/go/pkg/web/controller/pipeline.go b/microservices/go/pkg/web/controller/pipeline.go new file mode 100644 index 0000000..696d306 --- /dev/null +++ b/microservices/go/pkg/web/controller/pipeline.go @@ -0,0 +1,29 @@ +package controller + +// Pipeline manages controller handlers by providing a consistent way to +// propagate errors and handle web requests and responses +type Pipeline interface { + // Next executes an action that may return an error that will be handled as a + // http response + Next(action func() error) Pipeline +} + +type PipelineImpl struct { + errorHandler func(err error) + err error +} + +func (p PipelineImpl) Next(action func() error) Pipeline { + if p.err != nil { + return p + } + p.err = action() + if p.err != nil { + p.errorHandler(p.err) + } + return p +} + +func NewPipelineImpl(errorHandler func(err error)) *PipelineImpl { + return &PipelineImpl{errorHandler: errorHandler, err: nil} +} From bdcc50fae8e8322c90f21ba65a49aef22be0b3e5 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 03:42:12 -0500 Subject: [PATCH 10/14] Made shared userservice not reactive --- .../cmd/keyservice/businessrules/userkeybr.go | 3 +- .../controllers/userkeycontroller.go | 6 +- .../services/userchange-event-service.go | 8 +- .../noteservice/controllers/notecontroller.go | 10 +- .../services/userchange-event-service.go | 8 +- .../go/pkg/sharedservices/user-service.go | 178 +++++++++--------- 6 files changed, 107 insertions(+), 106 deletions(-) diff --git a/microservices/go/cmd/keyservice/businessrules/userkeybr.go b/microservices/go/cmd/keyservice/businessrules/userkeybr.go index f5d6238..007a4ce 100644 --- a/microservices/go/cmd/keyservice/businessrules/userkeybr.go +++ b/microservices/go/cmd/keyservice/businessrules/userkeybr.go @@ -6,7 +6,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors" "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors/validationutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/logger" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/cipherutils" @@ -85,7 +84,7 @@ func (u UserKeyBrImpl) ValidateProxyKeyCiphersFromSession( } // Validate User Exists - exists, err := single.RetrieveValue(ctx, u.userService.UserExistsWithId(ctx, userId)) + exists, err := u.userService.UserExistsWithId(ctx, userId) if !exists { ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) } diff --git a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go index bdbf095..c09297e 100644 --- a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go +++ b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go @@ -37,7 +37,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { var resBody commondtos.ExistsDto u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { resBody, err = single.RetrieveValue(c, u.userKeyService.UserKeyExists(c, userBo)) @@ -56,7 +56,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { var resBody commondtos.SuccessDto u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) @@ -78,7 +78,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { var resBody commondtos.UKeySessionDto u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, u.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) diff --git a/microservices/go/cmd/keyservice/services/userchange-event-service.go b/microservices/go/cmd/keyservice/services/userchange-event-service.go index 14f1feb..e27c79d 100644 --- a/microservices/go/cmd/keyservice/services/userchange-event-service.go +++ b/microservices/go/cmd/keyservice/services/userchange-event-service.go @@ -34,12 +34,16 @@ func (u UserChangeEventServiceImpl) HandleUserChangeEventTransaction( var userResSrc single.Single[userdtos.UserChangeEventResponseDto] switch userEventDto.Action { case userdtos.UserSave: - saveUserSrc := u.userService.SaveUser(ctx, userEventDto) + saveUserSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { + return u.userService.SaveUser(ctx, userEventDto) + }) userResSrc = single.Map(saveUserSrc, func(a userbos.UserBo) userdtos.UserChangeEventResponseDto { return userdtos.UserChangeEventResponseDto{Discarded: false} }) case userdtos.UserDelete: - userDeleteSrc := u.userService.DeleteUser(ctx, userEventDto) + userDeleteSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { + return u.userService.DeleteUser(ctx, userEventDto) + }) userKeyDeleteSrc := u.userKeyService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) userResSrc = single.Map(single.Zip2(userDeleteSrc, userKeyDeleteSrc), func(_ tuple.T2[userbos.UserBo, int64]) userdtos.UserChangeEventResponseDto { diff --git a/microservices/go/cmd/noteservice/controllers/notecontroller.go b/microservices/go/cmd/noteservice/controllers/notecontroller.go index d2d3d9b..0f11f87 100644 --- a/microservices/go/cmd/noteservice/controllers/notecontroller.go +++ b/microservices/go/cmd/noteservice/controllers/notecontroller.go @@ -39,7 +39,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { var resBody cDTOs.SuccessDto n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto]]( @@ -61,7 +61,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { var resBody cDTOs.SuccessDto n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto]]( @@ -83,7 +83,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { var resBody cDTOs.SuccessDto n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) @@ -104,7 +104,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { var resBody nDTOs.NoteReadDto n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto]]( @@ -126,7 +126,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { var resBody pagination.Page[nDTOs.NotePreviewDto] n.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - userBo, err = single.RetrieveValue(c, n.userService.RequireUser(c, security.GetIdentityFromGinContext(c))) + userBo, err = n.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { reqBody, err = ginservices.ReadValueFromBody[cDTOs.UKeySessionReqDto[pagination.PageRequest]]( diff --git a/microservices/go/cmd/noteservice/services/userchange-event-service.go b/microservices/go/cmd/noteservice/services/userchange-event-service.go index e3c7ce4..f57c46c 100644 --- a/microservices/go/cmd/noteservice/services/userchange-event-service.go +++ b/microservices/go/cmd/noteservice/services/userchange-event-service.go @@ -34,12 +34,16 @@ func (u UserChangeEventServiceImpl) HandleUserChangeEventTransaction( var userResSrc single.Single[userdtos.UserChangeEventResponseDto] switch userEventDto.Action { case userdtos.UserSave: - saveUserSrc := u.userService.SaveUser(ctx, userEventDto) + saveUserSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { + return u.userService.SaveUser(ctx, userEventDto) + }) userResSrc = single.Map(saveUserSrc, func(a userbos.UserBo) userdtos.UserChangeEventResponseDto { return userdtos.UserChangeEventResponseDto{Discarded: false} }) case userdtos.UserDelete: - userDeleteSrc := u.userService.DeleteUser(ctx, userEventDto) + userDeleteSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { + return u.userService.DeleteUser(ctx, userEventDto) + }) noteDeleteSrc := u.noteService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) userResSrc = single.Map(single.Zip2(userDeleteSrc, noteDeleteSrc), func(_ tuple.T2[userbos.UserBo, int64]) userdtos.UserChangeEventResponseDto { diff --git a/microservices/go/pkg/sharedservices/user-service.go b/microservices/go/pkg/sharedservices/user-service.go index 426267f..e3fce20 100644 --- a/microservices/go/pkg/sharedservices/user-service.go +++ b/microservices/go/pkg/sharedservices/user-service.go @@ -7,20 +7,18 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/embedded/embeddeduser" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers" cModels "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmodels" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedrepos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/externalservices" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) type UserService interface { - RequireUser(ctx context.Context, identity security.Identity) single.Single[userbos.UserBo] - SaveUser(ctx context.Context, userEventDto userdtos.UserChangeEventDto) single.Single[userbos.UserBo] - DeleteUser(ctx context.Context, userEventDto userdtos.UserChangeEventDto) single.Single[userbos.UserBo] - UserExistsWithId(ctx context.Context, userId string) single.Single[bool] + RequireUser(ctx context.Context, identity security.Identity) (userbos.UserBo, error) + SaveUser(ctx context.Context, userEventDto userdtos.UserChangeEventDto) (userbos.UserBo, error) + DeleteUser(ctx context.Context, userEventDto userdtos.UserChangeEventDto) (userbos.UserBo, error) + UserExistsWithId(ctx context.Context, userId string) (bool, error) } type UserServiceImpl struct { @@ -32,110 +30,106 @@ type UserServiceImpl struct { func (u UserServiceImpl) SaveUser( ctx context.Context, userEventDto userdtos.UserChangeEventDto, -) single.Single[userbos.UserBo] { +) (userbos.UserBo, error) { logger.Log.Debugf("saving user %v", userEventDto) - userSavedSrc := u.saveUserDataAndGetModel(ctx, userEventDto.AuthId, userEventDto.BaseUserPublicDto) - return single.Map(userSavedSrc, func(u cModels.User) userbos.UserBo { - userBo := userbos.UserBo{} - sharedmappers.UserModelToUserBo(u, &userBo) - return userBo - }) + user, err := u.saveUserDataAndGetModel(ctx, userEventDto.AuthId, userEventDto.BaseUserPublicDto) + if err != nil { + return userbos.UserBo{}, err + } + userBo := userbos.UserBo{} + sharedmappers.UserModelToUserBo(user, &userBo) + return userBo, nil } func (u UserServiceImpl) DeleteUser( ctx context.Context, userEventDto userdtos.UserChangeEventDto, -) single.Single[userbos.UserBo] { +) (userbos.UserBo, error) { logger.Log.Debugf("deleting user %v", userEventDto) - userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { - return u.userRepository.FindByUserId(ctx, userEventDto.Id) - }) - userDelSrc := single.MapWithError( - userFindSrc, - func(userMaybe option.Maybe[cModels.User]) (cModels.User, error) { - if user, isPresent := userMaybe.Get(); isPresent { - return u.userRepository.Delete(ctx, user) - } else { - return cModels.User{}, nil - } - }, - ) - return single.Map(userDelSrc, func(u cModels.User) userbos.UserBo { - userBo := userbos.UserBo{} - sharedmappers.UserModelToUserBo(u, &userBo) - return userBo - }) + userMaybe, err := u.userRepository.FindByUserId(ctx, userEventDto.Id) + if err != nil { + return userbos.UserBo{}, err + } + + deletedUser := cModels.User{} + if user, isPresent := userMaybe.Get(); isPresent { + deletedUser, err = u.userRepository.Delete(ctx, user) + if err != nil { + return userbos.UserBo{}, err + } + } else { + return userbos.UserBo{}, nil + } + + userBo := userbos.UserBo{} + sharedmappers.UserModelToUserBo(deletedUser, &userBo) + return userBo, nil } -func (u UserServiceImpl) RequireUser(ctx context.Context, identity security.Identity) single.Single[userbos.UserBo] { - userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { - return u.userRepository.FindByAuthId(ctx, identity.GetAuthId()) - }) - userSrc := single.FlatMap(userFindSrc, func(userMaybe option.Maybe[cModels.User]) single.Single[cModels.User] { - if user, isPresent := userMaybe.Get(); isPresent { - return single.Just(user) - } else { - // If user is not stored locally in the database - extUserFindSrc := single.FromSupplierCached(func() (userdtos.UserReadDto, error) { - return u.extUserService.GetByAuthId(ctx, identity.GetAuthId()) - }) - return single.FlatMap(extUserFindSrc, func(extUserDto userdtos.UserReadDto) single.Single[cModels.User] { - if !extUserDto.Exists { - userReqFailRuleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeUserRequireFail) - return single.Error[cModels.User](apperrors.NewBadReqErrorFromRuleError(userReqFailRuleErr)) - } - return u.saveUserDataAndGetModel(ctx, identity.GetAuthId(), extUserDto.BaseUserPublicDto) - }) +func (u UserServiceImpl) RequireUser(ctx context.Context, identity security.Identity) (userbos.UserBo, error) { + userMaybe, err := u.userRepository.FindByAuthId(ctx, identity.GetAuthId()) + if err != nil { + return userbos.UserBo{}, err + } + user, isPresent := userMaybe.Get() + + if !isPresent { + extUserDto, err := u.extUserService.GetByAuthId(ctx, identity.GetAuthId()) + if err != nil { + return userbos.UserBo{}, err + } + if !extUserDto.Exists { + userReqFailRuleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeUserRequireFail) + return userbos.UserBo{}, apperrors.NewBadReqErrorFromRuleError(userReqFailRuleErr) } - }) - return single.Map(userSrc, func(user cModels.User) userbos.UserBo { - userBo := userbos.UserBo{} - sharedmappers.UserModelToUserBo(user, &userBo) - return userBo - }) + user, err = u.saveUserDataAndGetModel(ctx, identity.GetAuthId(), extUserDto.BaseUserPublicDto) + if err != nil { + return userbos.UserBo{}, err + } + } + + userBo := userbos.UserBo{} + sharedmappers.UserModelToUserBo(user, &userBo) + return userBo, nil } -func (u UserServiceImpl) UserExistsWithId(ctx context.Context, userId string) single.Single[bool] { - userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { - return u.userRepository.FindByUserId(ctx, userId) - }) - return single.FlatMap(userFindSrc, func(userMaybe option.Maybe[cModels.User]) single.Single[bool] { - return option.Map(userMaybe, func(_ cModels.User) single.Single[bool] { - return single.Just(true) - }).OrElseGet(func() single.Single[bool] { - extUserFindSrc := single.FromSupplierCached(func() (userdtos.UserReadDto, error) { - return u.extUserService.GetById(ctx, userId) - }) - return single.FlatMap(extUserFindSrc, func(extUserDto userdtos.UserReadDto) single.Single[bool] { - return single.Just(extUserDto.Exists) - }) - }) - }) +func (u UserServiceImpl) UserExistsWithId(ctx context.Context, userId string) (bool, error) { + userMaybe, err := u.userRepository.FindByUserId(ctx, userId) + if err != nil { + return false, err + } + + _, existsInRepo := userMaybe.Get() + if existsInRepo { + return true, nil + } + + extUserDto, err := u.extUserService.GetById(ctx, userId) + if err != nil { + return false, err + } + return extUserDto.Exists, nil } func (u UserServiceImpl) saveUserDataAndGetModel( ctx context.Context, authId string, userPublicDto embeddeduser.BaseUserPublicDto, -) single.Single[cModels.User] { - userFindSrc := single.FromSupplierCached(func() (option.Maybe[cModels.User], error) { - return u.userRepository.FindByUserId(ctx, userPublicDto.Id) - }) - return single.MapWithError( - userFindSrc, - func(userMaybe option.Maybe[cModels.User]) (cModels.User, error) { - user, isPresent := userMaybe.Get() - if !isPresent { - user = cModels.User{} - } - sharedmappers.AuthIdAndUserPublicDtoToUserModel(authId, userPublicDto, &user) - if isPresent { - return u.userRepository.Update(ctx, user) - } else { - return u.userRepository.Create(ctx, user) - } - }, - ) +) (cModels.User, error) { + userMaybe, err := u.userRepository.FindByUserId(ctx, userPublicDto.Id) + if err != nil { + return cModels.User{}, err + } + user, isPresent := userMaybe.Get() + if !isPresent { + user = cModels.User{} + } + sharedmappers.AuthIdAndUserPublicDtoToUserModel(authId, userPublicDto, &user) + if isPresent { + return u.userRepository.Update(ctx, user) + } else { + return u.userRepository.Create(ctx, user) + } } func NewUserServiceImpl( From e871d34fa8fc15561ccb174f852d446cac0f3c37 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 04:29:53 -0500 Subject: [PATCH 11/14] User service is non-reactive --- .../controllers/user-controller.go | 16 +- .../grpcapis/userservice-server.go | 32 +- .../go/cmd/userservice/models/user.go | 2 +- .../services/authserver-mgmt-service.go | 14 +- .../cmd/userservice/services/user-service.go | 292 +++++++++--------- 5 files changed, 162 insertions(+), 194 deletions(-) diff --git a/microservices/go/cmd/userservice/controllers/user-controller.go b/microservices/go/cmd/userservice/controllers/user-controller.go index 9c3fad2..3fc2c98 100644 --- a/microservices/go/cmd/userservice/controllers/user-controller.go +++ b/microservices/go/cmd/userservice/controllers/user-controller.go @@ -5,7 +5,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/services" "github.com/obenkenobi/cypher-log/microservices/go/pkg/middlewares" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/web/controller" @@ -36,8 +35,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, - u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody)) + resBody, err = u.userService.AddUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody) return }).Next(func() error { c.JSON(http.StatusOK, resBody) @@ -55,8 +53,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[userdtos.UserSaveDto](u.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, - u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody)) + resBody, err = u.userService.UpdateUserTransaction(c, security.GetIdentityFromGinContext(c), reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -70,8 +67,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { var resBody userdtos.UserReadDto u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - businessLogicSrc := u.userService.BeginDeletingUserTransaction(c, security.GetIdentityFromGinContext(c)) - resBody, err = single.RetrieveValue(c, businessLogicSrc) + resBody, err = u.userService.BeginDeletingUserTransaction(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -85,8 +81,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { var resBody userdtos.UserIdentityDto u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - businessLogicSrc := u.userService.GetUserIdentity(c, security.GetIdentityFromGinContext(c)) - resBody, err = single.RetrieveValue(c, businessLogicSrc) + resBody, err = u.userService.GetUserIdentity(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -101,8 +96,7 @@ func (u UserControllerImpl) AddRoutes(r *gin.Engine) { authId := c.Param("id") u.ginCtxService.StartCtxPipeline(c).Next(func() (err error) { - businessLogicSrc := u.userService.GetByAuthId(c, authId) - resBody, err = single.RetrieveValue(c, businessLogicSrc) + resBody, err = u.userService.GetByAuthId(c, authId) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) diff --git a/microservices/go/cmd/userservice/grpcapis/userservice-server.go b/microservices/go/cmd/userservice/grpcapis/userservice-server.go index 48cc909..fd931b0 100644 --- a/microservices/go/cmd/userservice/grpcapis/userservice-server.go +++ b/microservices/go/cmd/userservice/grpcapis/userservice-server.go @@ -5,8 +5,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/cmd/userservice/services" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/gtools" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/userpb" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers/grpcmappers" ) @@ -16,28 +14,26 @@ type UserServiceServerImpl struct { } func (u UserServiceServerImpl) GetUserById(ctx context.Context, request *userpb.IdRequest) (*userpb.UserReply, error) { - userFindSrc := u.userService.GetById(ctx, request.GetId()) - userReplySrc := single.Map(userFindSrc, func(userDto userdtos.UserReadDto) *userpb.UserReply { - userReply := &userpb.UserReply{} - grpcmappers.UserReadDtoToUserReply(&userDto, userReply) - return userReply - }) - res, err := single.RetrieveValue(ctx, userReplySrc) - return res, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + userDto, err := u.userService.GetById(ctx, request.GetId()) + if err != nil { + return nil, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + } + userReply := &userpb.UserReply{} + grpcmappers.UserReadDtoToUserReply(&userDto, userReply) + return userReply, err } func (u UserServiceServerImpl) GetUserByAuthId( ctx context.Context, request *userpb.AuthIdRequest, ) (*userpb.UserReply, error) { - userFindSrc := u.userService.GetByAuthId(ctx, request.GetAuthId()) - userReplySrc := single.Map(userFindSrc, func(userDto userdtos.UserReadDto) *userpb.UserReply { - userReply := &userpb.UserReply{} - grpcmappers.UserReadDtoToUserReply(&userDto, userReply) - return userReply - }) - res, err := single.RetrieveValue(ctx, userReplySrc) - return res, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + userDto, err := u.userService.GetByAuthId(ctx, request.GetAuthId()) + if err != nil { + return nil, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + } + userReply := &userpb.UserReply{} + grpcmappers.UserReadDtoToUserReply(&userDto, userReply) + return userReply, err } func NewUserServiceServerImpl(userService services.UserService) *UserServiceServerImpl { diff --git a/microservices/go/cmd/userservice/models/user.go b/microservices/go/cmd/userservice/models/user.go index f57acfd..fe55234 100644 --- a/microservices/go/cmd/userservice/models/user.go +++ b/microservices/go/cmd/userservice/models/user.go @@ -35,6 +35,6 @@ func (u User) GetUpdatedAt() time.Time { return u.UpdatedAt } -func (u User) WillNotDeleted() bool { +func (u User) WillNotBeDeleted() bool { return !u.ToBeDeleted } diff --git a/microservices/go/cmd/userservice/services/authserver-mgmt-service.go b/microservices/go/cmd/userservice/services/authserver-mgmt-service.go index 3b14c48..4ff835d 100644 --- a/microservices/go/cmd/userservice/services/authserver-mgmt-service.go +++ b/microservices/go/cmd/userservice/services/authserver-mgmt-service.go @@ -2,28 +2,18 @@ package services import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/conf/authconf" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "gopkg.in/auth0.v5/management" ) type AuthServerMgmtService interface { - DeleteUser(authId string) single.Single[bool] - DeleteAsync(authId string) single.Single[bool] + DeleteUser(authId string) (bool, error) } type AuthServerMgmtServiceImpl struct { auth0SecurityConf authconf.Auth0SecurityConf } -func (a AuthServerMgmtServiceImpl) DeleteUser(authId string) single.Single[bool] { - return single.FromSupplierCached(func() (bool, error) { return a.runDeleteUser(authId) }) -} - -func (a AuthServerMgmtServiceImpl) DeleteAsync(authId string) single.Single[bool] { - return single.FromSupplierCached(func() (bool, error) { return a.runDeleteUser(authId) }) -} - -func (a AuthServerMgmtServiceImpl) runDeleteUser(authId string) (bool, error) { +func (a AuthServerMgmtServiceImpl) DeleteUser(authId string) (bool, error) { m, err := management.New(a.auth0SecurityConf.GetDomain(), management.WithClientCredentials( a.auth0SecurityConf.GetClientCredentialsId(), a.auth0SecurityConf.GetClientCredentialsSecret(), diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index fc523d2..b6fde1e 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -10,11 +10,9 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/logger" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) type UserService interface { @@ -22,16 +20,16 @@ type UserService interface { ctx context.Context, identity security.Identity, userSaveDto userdtos.UserSaveDto, - ) single.Single[userdtos.UserReadDto] + ) (userdtos.UserReadDto, error) UpdateUserTransaction( ctx context.Context, identity security.Identity, userSaveDto userdtos.UserSaveDto, - ) single.Single[userdtos.UserReadDto] - BeginDeletingUserTransaction(ctx context.Context, identity security.Identity) single.Single[userdtos.UserReadDto] - GetByAuthId(ctx context.Context, authId string) single.Single[userdtos.UserReadDto] - GetById(ctx context.Context, userId string) single.Single[userdtos.UserReadDto] - GetUserIdentity(ctx context.Context, identity security.Identity) single.Single[userdtos.UserIdentityDto] + ) (userdtos.UserReadDto, error) + BeginDeletingUserTransaction(ctx context.Context, identity security.Identity) (userdtos.UserReadDto, error) + GetByAuthId(ctx context.Context, authId string) (userdtos.UserReadDto, error) + GetById(ctx context.Context, userId string) (userdtos.UserReadDto, error) + GetUserIdentity(ctx context.Context, identity security.Identity) (userdtos.UserIdentityDto, error) UsersChangeTask(ctx context.Context) } @@ -48,25 +46,27 @@ func (u UserServiceImpl) AddUserTransaction( ctx context.Context, identity security.Identity, userSaveDto userdtos.UserSaveDto, -) single.Single[userdtos.UserReadDto] { - return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, - func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { +) (userdtos.UserReadDto, error) { + return dshandlers.Transactional(ctx, u.crudDSHandler, + func(s dshandlers.Session, ctx context.Context) (userdtos.UserReadDto, error) { err := u.userBr.ValidateUserCreate(ctx, identity, userSaveDto) if err != nil { - return single.Error[userdtos.UserReadDto](err) + return userdtos.UserReadDto{}, err } - userCreateSrc := single.FromSupplierCached(func() (models.User, error) { - user := models.User{} - mappers.UserSaveDtoToUser(userSaveDto, &user) - user.AuthId = identity.GetAuthId() - user.Distributed = false - user.ToBeDeleted = false - return u.userRepository.Create(ctx, user) - }) - return single.Map(userCreateSrc, func(user models.User) userdtos.UserReadDto { - logger.Log.Debug("Saved user ", user) - return userToUserReadDto(user) - }) + + user := models.User{} + mappers.UserSaveDtoToUser(userSaveDto, &user) + user.AuthId = identity.GetAuthId() + user.Distributed = false + user.ToBeDeleted = false + + createdUser, err := u.userRepository.Create(ctx, user) + if err != nil { + return userdtos.UserReadDto{}, err + } + + logger.Log.Debug("Saved user ", user) + return userToUserReadDto(createdUser), nil }, ) } @@ -75,39 +75,37 @@ func (u UserServiceImpl) UpdateUserTransaction( ctx context.Context, identity security.Identity, userSaveDto userdtos.UserSaveDto, -) single.Single[userdtos.UserReadDto] { - return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, - func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { - userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { - return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) - }) - userExistsSrc := single.MapWithError(userSearchSrc, - func(userMaybe option.Maybe[models.User]) (models.User, error) { - if user, isPresent := userMaybe.Get(); isPresent { - return user, nil - } else { - err := apperrors.NewBadReqErrorFromRuleError( - u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) - return user, err - } - }, - ) - userValidatedSrc := single.MapWithError(userExistsSrc, - func(existingUser models.User) (models.User, error) { - err := u.userBr.ValidateUserUpdate(ctx, userSaveDto, existingUser) - return existingUser, err - }, - ) - userSavedSrc := single.MapWithError(userValidatedSrc, func(user models.User) (models.User, error) { - mappers.UserSaveDtoToUser(userSaveDto, &user) - user.Distributed = false - user.ToBeDeleted = false - return u.userRepository.Update(ctx, user) - }) - return single.Map(userSavedSrc, func(user models.User) userdtos.UserReadDto { - logger.Log.Debug("Saved user ", user) - return userToUserReadDto(user) - }) +) (userdtos.UserReadDto, error) { + return dshandlers.Transactional(ctx, u.crudDSHandler, + func(s dshandlers.Session, ctx context.Context) (userdtos.UserReadDto, error) { + userSearch, err := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + if err != nil { + return userdtos.UserReadDto{}, err + } + + user, isPresent := userSearch.Get() + if !isPresent { + err := apperrors.NewBadReqErrorFromRuleError( + u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) + return userdtos.UserReadDto{}, err + } + + err = u.userBr.ValidateUserUpdate(ctx, userSaveDto, user) + if err != nil { + return userdtos.UserReadDto{}, err + } + + mappers.UserSaveDtoToUser(userSaveDto, &user) + user.Distributed = false + user.ToBeDeleted = false + + updatedUser, err := u.userRepository.Update(ctx, user) + if err != nil { + return userdtos.UserReadDto{}, err + } + + logger.Log.Debug("Saved user ", updatedUser) + return userToUserReadDto(updatedUser), nil }, ) @@ -116,67 +114,63 @@ func (u UserServiceImpl) UpdateUserTransaction( func (u UserServiceImpl) BeginDeletingUserTransaction( ctx context.Context, identity security.Identity, -) single.Single[userdtos.UserReadDto] { - return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, - func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserReadDto] { - userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { - return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) - }) - userExistsSrc := single.MapWithError( - userSearchSrc, - func(userMaybe option.Maybe[models.User]) (models.User, error) { - if user, isPresent := userMaybe.Get(); isPresent { - return user, nil - } else { - err := apperrors.NewBadReqErrorFromRuleError( - u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) - return user, err - } - }, - ) - userToBeDeletedSrc := single.MapWithError(userExistsSrc, func(user models.User) (models.User, error) { - user.Distributed = false - user.ToBeDeleted = true - return u.userRepository.Update(ctx, user) - }) - return single.Map(userToBeDeletedSrc, func(user models.User) userdtos.UserReadDto { - logger.Log.Debug("Starting to delete user ", user) - return userToUserReadDto(user) - }) +) (userdtos.UserReadDto, error) { + return dshandlers.Transactional(ctx, u.crudDSHandler, + func(s dshandlers.Session, ctx context.Context) (userdtos.UserReadDto, error) { + userSearch, err := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, identity.GetAuthId()) + if err != nil { + return userdtos.UserReadDto{}, err + } + + user, isPresent := userSearch.Get() + if !isPresent { + err := apperrors.NewBadReqErrorFromRuleError( + u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound)) + return userdtos.UserReadDto{}, err + } + + user.Distributed = false + user.ToBeDeleted = true + + updatedUser, err := u.userRepository.Update(ctx, user) + if err != nil { + return userdtos.UserReadDto{}, err + } + + logger.Log.Debug("Starting to delete user ", updatedUser) + return userToUserReadDto(updatedUser), nil }) } func (u UserServiceImpl) GetUserIdentity( ctx context.Context, identity security.Identity, -) single.Single[userdtos.UserIdentityDto] { - userSrc := u.GetByAuthId(ctx, identity.GetAuthId()) - return single.Map(userSrc, func(userDto userdtos.UserReadDto) userdtos.UserIdentityDto { - userIdentityDto := userdtos.UserIdentityDto{} - sharedmappers.UserReadDtoAndIdentityToUserIdentityDto(userDto, identity, &userIdentityDto) - return userIdentityDto - }) - +) (userdtos.UserIdentityDto, error) { + userDto, err := u.GetByAuthId(ctx, identity.GetAuthId()) + if err != nil { + return userdtos.UserIdentityDto{}, err + } + userIdentityDto := userdtos.UserIdentityDto{} + sharedmappers.UserReadDtoAndIdentityToUserIdentityDto(userDto, identity, &userIdentityDto) + return userIdentityDto, nil } -func (u UserServiceImpl) GetByAuthId(ctx context.Context, authId string) single.Single[userdtos.UserReadDto] { - userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { - return u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, authId) - }) - return single.Map(userSearchSrc, func(userMaybe option.Maybe[models.User]) userdtos.UserReadDto { - user := userMaybe.OrElse(models.User{}) - return userToUserReadDto(user) - }) +func (u UserServiceImpl) GetByAuthId(ctx context.Context, authId string) (userdtos.UserReadDto, error) { + userSearch, err := u.userRepository.FindByAuthIdAndNotToBeDeleted(ctx, authId) + if err != nil { + return userdtos.UserReadDto{}, err + } + user := userSearch.OrElse(models.User{}) + return userToUserReadDto(user), nil } -func (u UserServiceImpl) GetById(ctx context.Context, userId string) single.Single[userdtos.UserReadDto] { - userSearchSrc := single.FromSupplierCached(func() (option.Maybe[models.User], error) { - return u.userRepository.FindById(ctx, userId) - }) - return single.Map(userSearchSrc, func(userMaybe option.Maybe[models.User]) userdtos.UserReadDto { - user := userMaybe.Filter(models.User.WillNotDeleted).OrElse(models.User{}) - return userToUserReadDto(user) - }) +func (u UserServiceImpl) GetById(ctx context.Context, userId string) (userdtos.UserReadDto, error) { + userSearch, err := u.userRepository.FindById(ctx, userId) + if err != nil { + return userdtos.UserReadDto{}, err + } + user := userSearch.Filter(models.User.WillNotBeDeleted).OrElse(models.User{}) + return userToUserReadDto(user), nil } func (u UserServiceImpl) UsersChangeTask(ctx context.Context) { @@ -188,9 +182,9 @@ func (u UserServiceImpl) UsersChangeTask(ctx context.Context) { for _, user := range userSample { var err error if user.ToBeDeleted { - _, err = single.RetrieveValue(ctx, u.deleteUserTransaction(ctx, user)) + _, err = u.deleteUserTransaction(ctx, user) } else { - _, err = single.RetrieveValue(ctx, u.distributeUserChangeTransaction(ctx, user)) + _, err = u.distributeUserChangeTransaction(ctx, user) } if err != nil { logger.Log.Error(err) @@ -202,51 +196,47 @@ func (u UserServiceImpl) UsersChangeTask(ctx context.Context) { func (u UserServiceImpl) deleteUserTransaction( ctx context.Context, user models.User, -) single.Single[userdtos.UserChangeEventDto] { - return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, - func(s dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventDto] { - sendUserChangeSrc := u.sendUserChange(user, userdtos.UserDelete) - userDeletedLocalDBSrc := single.FromSupplierCached(func() (models.User, error) { - return u.userRepository.Delete(ctx, user) - }) - userDeletedAuthServerSrc := single.FlatMap( - userDeletedLocalDBSrc, - func(user models.User) single.Single[models.User] { - return single.Map(u.authServerMgmtService.DeleteUser(user.AuthId), - func(_ bool) models.User { return user }) - }, - ) - return single.FlatMap( - userDeletedAuthServerSrc, - func(user models.User) single.Single[userdtos.UserChangeEventDto] { - logger.Log.Debug("Deleted user ", user) - return sendUserChangeSrc - }, - ) +) (userdtos.UserChangeEventDto, error) { + return dshandlers.Transactional(ctx, u.crudDSHandler, + func(s dshandlers.Session, ctx context.Context) (userdtos.UserChangeEventDto, error) { + event, err := u.sendUserChange(user, userdtos.UserDelete) + if err != nil { + return event, err + } + + deletedUser, err := u.userRepository.Delete(ctx, user) + if err != nil { + return event, err + } + + _, err = u.authServerMgmtService.DeleteUser(deletedUser.AuthId) + if err != nil { + return event, err + } + + logger.Log.Debug("Deleted user ", deletedUser) + return event, err }) } func (u UserServiceImpl) distributeUserChangeTransaction( ctx context.Context, user models.User, -) single.Single[userdtos.UserChangeEventDto] { - return dshandlers.TransactionalSingle(ctx, u.crudDSHandler, - func(session dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventDto] { - sendUserChangeSrc := u.sendUserChange(user, userdtos.UserSave) - return single.MapWithError( - sendUserChangeSrc, - func(event userdtos.UserChangeEventDto) (userdtos.UserChangeEventDto, error) { - user := user - user.ToBeDeleted = false - user.Distributed = true - updatedUser, err := u.userRepository.Update(ctx, user) - if err == nil { - logger.Log.Debugf("Sent user save event for user %v", updatedUser) - } - return event, err - }, - ) +) (userdtos.UserChangeEventDto, error) { + return dshandlers.Transactional(ctx, u.crudDSHandler, + func(session dshandlers.Session, ctx context.Context) (userdtos.UserChangeEventDto, error) { + event, err := u.sendUserChange(user, userdtos.UserDelete) + if err != nil { + return event, err + } + user.ToBeDeleted = false + user.Distributed = true + updatedUser, err := u.userRepository.Update(ctx, user) + if err == nil { + logger.Log.Debugf("Sent user save event for user %v", updatedUser) + } + return event, err }, ) } @@ -254,13 +244,11 @@ func (u UserServiceImpl) distributeUserChangeTransaction( func (u UserServiceImpl) sendUserChange( user models.User, action userdtos.UserChangeAction, -) single.Single[userdtos.UserChangeEventDto] { +) (userdtos.UserChangeEventDto, error) { distUserDto := userdtos.UserChangeEventDto{} mappers.UserToUserChangeEventDto(user, &distUserDto) distUserDto.Action = action - return single.FromSupplierCached(func() (userdtos.UserChangeEventDto, error) { - return u.userMsgSendService.UserSaveSender().Send(distUserDto) - }) + return u.userMsgSendService.UserSaveSender().Send(distUserDto) } func userToUserReadDto(user models.User) userdtos.UserReadDto { From f90581bd188958db675ab36f65a6d4f89910b51c Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 14:02:17 -0500 Subject: [PATCH 12/14] Remove reactive code in key service --- .../cmd/keyservice/businessrules/userkeybr.go | 12 +- .../controllers/userkeycontroller.go | 9 +- .../grpcapis/userkeyservice-server.go | 17 +- .../cmd/keyservice/listeners/rmqlistener.go | 4 +- .../keyservice/services/appsecret-service.go | 100 +++-- .../services/userchange-event-service.go | 38 +- .../keyservice/services/userkey-service.go | 363 ++++++++---------- .../cmd/noteservice/businessrules/notebr.go | 8 +- .../cmd/userservice/businessrules/userbr.go | 4 +- .../cmd/userservice/services/user-service.go | 6 +- .../validationutils/validation-utils.go | 2 +- .../ginservices/ginctx-service.go | 10 +- 12 files changed, 259 insertions(+), 314 deletions(-) diff --git a/microservices/go/cmd/keyservice/businessrules/userkeybr.go b/microservices/go/cmd/keyservice/businessrules/userkeybr.go index 007a4ce..6a96c2e 100644 --- a/microservices/go/cmd/keyservice/businessrules/userkeybr.go +++ b/microservices/go/cmd/keyservice/businessrules/userkeybr.go @@ -38,7 +38,7 @@ func (u UserKeyBrImpl) ValidateSessionTokenHash(session models.UserKeySession, t if !verified { ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) } - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } @@ -53,7 +53,7 @@ func (u UserKeyBrImpl) ValidateKeyFromSession(userKeyGen models.UserKeyGenerator ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) } - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func (u UserKeyBrImpl) ValidateProxyKeyCiphersFromSession( @@ -84,12 +84,12 @@ func (u UserKeyBrImpl) ValidateProxyKeyCiphersFromSession( } // Validate User Exists - exists, err := u.userService.UserExistsWithId(ctx, userId) - if !exists { + userExists, err := u.userService.UserExistsWithId(ctx, userId) + if !userExists { ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession)) } - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func (u UserKeyBrImpl) ValidateKeyFromPassword(userKeyGen models.UserKeyGenerator, key []byte) error { @@ -100,7 +100,7 @@ func (u UserKeyBrImpl) ValidateKeyFromPassword(userKeyGen models.UserKeyGenerato } else if !verified { ruleErrs = append(ruleErrs, u.errorService.RuleErrorFromCode(apperrors.ErrCodeIncorrectPasscode)) } - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func NewUserKeyBrImpl(errorService sharedservices.ErrorService, userService sharedservices.UserService) *UserKeyBrImpl { diff --git a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go index c09297e..8fa65e4 100644 --- a/microservices/go/cmd/keyservice/controllers/userkeycontroller.go +++ b/microservices/go/cmd/keyservice/controllers/userkeycontroller.go @@ -7,7 +7,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" @@ -40,7 +39,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { userBo, err = u.userService.RequireUser(c, security.GetIdentityFromGinContext(c)) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, u.userKeyService.UserKeyExists(c, userBo)) + resBody, err = u.userKeyService.UserKeyExists(c, userBo) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -62,7 +61,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeCreateDto](u.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, u.userKeyService.CreateUserKey(c, userBo, reqBody)) + resBody, err = u.userKeyService.CreateUserKey(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -84,7 +83,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[keydtos.PasscodeDto](u.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, u.userKeyService.NewKeySession(c, userBo, reqBody)) + resBody, err = u.userKeyService.NewKeySession(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -102,7 +101,7 @@ func (u UserKeyControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[commondtos.UKeySessionDto](u.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, u.userKeyService.GetKeyFromSession(c, reqBody)) + resBody, err = u.userKeyService.GetKeyFromSession(c, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) diff --git a/microservices/go/cmd/keyservice/grpcapis/userkeyservice-server.go b/microservices/go/cmd/keyservice/grpcapis/userkeyservice-server.go index 580f04e..0674afe 100644 --- a/microservices/go/cmd/keyservice/grpcapis/userkeyservice-server.go +++ b/microservices/go/cmd/keyservice/grpcapis/userkeyservice-server.go @@ -6,8 +6,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/gtools" "github.com/obenkenobi/cypher-log/microservices/go/pkg/grpc/userkeypb" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedmappers/grpcmappers" ) @@ -22,14 +20,13 @@ func (u UserKeyServiceServerImpl) GetKeyFromSession( ) (*userkeypb.UserKey, error) { userKeySessionDto := commondtos.UKeySessionDto{} grpcmappers.UserKeySessionToUserKeySessionDto(userKeySession, &userKeySessionDto) - keySrc := u.userKeyService.GetKeyFromSession(ctx, userKeySessionDto) - replySrc := single.Map(keySrc, func(keyDto keydtos.UserKeyDto) *userkeypb.UserKey { - userKey := &userkeypb.UserKey{} - grpcmappers.UserKeyDtoToUserKey(&keyDto, userKey) - return userKey - }) - res, err := single.RetrieveValue(ctx, replySrc) - return res, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + keyDto, err := u.userKeyService.GetKeyFromSession(ctx, userKeySessionDto) + if err != nil { + return nil, gtools.ProcessErrorToGrpcStatusError(gtools.ReadAction, err) + } + userKey := &userkeypb.UserKey{} + grpcmappers.UserKeyDtoToUserKey(&keyDto, userKey) + return userKey, nil } func NewUserKeyServiceServerImpl(userKeyService services.UserKeyService) *UserKeyServiceServerImpl { diff --git a/microservices/go/cmd/keyservice/listeners/rmqlistener.go b/microservices/go/cmd/keyservice/listeners/rmqlistener.go index c7f5d47..2a05646 100644 --- a/microservices/go/cmd/keyservice/listeners/rmqlistener.go +++ b/microservices/go/cmd/keyservice/listeners/rmqlistener.go @@ -8,7 +8,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/messaging/rmq" "github.com/obenkenobi/cypher-log/microservices/go/pkg/messaging/rmq/exchanges" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/rmqservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/taskrunner" "github.com/wagslane/go-rabbitmq" @@ -36,8 +35,7 @@ func (r RmqListenerImpl) ListenUserChange() { rabbitmq.WithConsumeOptionsQuorum, ) userCreateReceiver.Listen(func(d msg.Delivery[userdtos.UserChangeEventDto]) msg.ReceiverAction { - resSrc := r.userChangeEventService.HandleUserChangeEventTransaction(r.ctx, d.Body()) - res, err := single.RetrieveValue(r.ctx, resSrc) + res, err := r.userChangeEventService.HandleUserChangeEventTransaction(r.ctx, d.Body()) if err != nil { return d.Resend() } else if res.Discarded { diff --git a/microservices/go/cmd/keyservice/services/appsecret-service.go b/microservices/go/cmd/keyservice/services/appsecret-service.go index e696307..6ab69e3 100644 --- a/microservices/go/cmd/keyservice/services/appsecret-service.go +++ b/microservices/go/cmd/keyservice/services/appsecret-service.go @@ -3,14 +3,12 @@ package services import ( "context" "errors" - "github.com/barweiss/go-tuple" "github.com/google/uuid" bos "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/businessobjects" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/conf" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/repositories" "github.com/obenkenobi/cypher-log/microservices/go/pkg/apperrors" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/cipherutils" @@ -19,11 +17,11 @@ import ( type AppSecretService interface { // GetAppSecret gets the app secret marked by the KID - GetAppSecret(ctx context.Context, kid string) single.Single[bos.AppSecretBo] + GetAppSecret(ctx context.Context, kid string) (bos.AppSecretBo, error) // GetPrimaryAppSecret gets the primary app secret - GetPrimaryAppSecret(ctx context.Context) single.Single[bos.AppSecretBo] + GetPrimaryAppSecret(ctx context.Context) (bos.AppSecretBo, error) // GeneratePrimaryAppSecret generates a new primary app secret - GeneratePrimaryAppSecret(ctx context.Context) single.Single[bos.AppSecretBo] + GeneratePrimaryAppSecret(ctx context.Context) (bos.AppSecretBo, error) } type AppSecretServiceImpl struct { @@ -33,65 +31,61 @@ type AppSecretServiceImpl struct { errorService sharedservices.ErrorService } -func (a AppSecretServiceImpl) GetPrimaryAppSecret(ctx context.Context) single.Single[bos.AppSecretBo] { - refFindSrc := single.FromSupplierCached(func() (option.Maybe[models.PrimaryAppSecretRef], error) { - return a.primaryAppSecretRefRepository.Get(ctx) - }) - return single.FlatMap(refFindSrc, - func(maybe option.Maybe[models.PrimaryAppSecretRef]) single.Single[bos.AppSecretBo] { - if ref, ok := maybe.Get(); ok { - return a.GetAppSecret(ctx, ref.Kid) - } - return a.GeneratePrimaryAppSecret(ctx) - }, - ) +func (a AppSecretServiceImpl) GetPrimaryAppSecret(ctx context.Context) (bos.AppSecretBo, error) { + refFind, err := a.primaryAppSecretRefRepository.Get(ctx) + if err != nil { + return bos.AppSecretBo{}, err + } + if ref, ok := refFind.Get(); ok { + return a.GetAppSecret(ctx, ref.Kid) + } + return a.GeneratePrimaryAppSecret(ctx) } -func (a AppSecretServiceImpl) GetAppSecret(ctx context.Context, kid string) single.Single[bos.AppSecretBo] { - appSecretFindSrc := single.FromSupplierCached(func() (option.Maybe[models.AppSecret], error) { - return a.appSecretRepository.Get(ctx, kid) - }) - return single.MapWithError(appSecretFindSrc, func(maybe option.Maybe[models.AppSecret]) (bos.AppSecretBo, error) { - appSecretBoMaybe := option.Map(maybe, func(appSecret models.AppSecret) bos.AppSecretBo { - return bos.NewAppSecretBo(kid, appSecret.SecretKey) - }) - return appSecretServiceReadMaybeModel(a, appSecretBoMaybe) +func (a AppSecretServiceImpl) GetAppSecret(ctx context.Context, kid string) (bos.AppSecretBo, error) { + appSecretFind, err := a.appSecretRepository.Get(ctx, kid) + if err != nil { + return bos.AppSecretBo{}, err + } + appSecretBoMaybe := option.Map(appSecretFind, func(appSecret models.AppSecret) bos.AppSecretBo { + return bos.NewAppSecretBo(kid, appSecret.SecretKey) }) + return appSecretServiceReadMaybeModel(a, appSecretBoMaybe) + } -func (a AppSecretServiceImpl) GeneratePrimaryAppSecret(ctx context.Context) single.Single[bos.AppSecretBo] { - kidGuidSrc := single.FromSupplierCached(uuid.NewRandom) - kidSrc := single.MapWithError(kidGuidSrc, func(kidGuid uuid.UUID) (string, error) { - newKid := kidGuid.String() - if utils.StringIsBlank(newKid) { - return newKid, errors.New("generated KID is blank") - } - return newKid, nil - }) - newKeySrc := single.FromSupplierCached(cipherutils.GenerateRandomKeyAES) - kidKeySrc := single.Zip2(kidSrc, newKeySrc) - return single.FlatMap(kidKeySrc, func(t tuple.T2[string, []byte]) single.Single[bos.AppSecretBo] { - ref := models.PrimaryAppSecretRef{Kid: t.V1} - appSecret := models.AppSecret{SecretKey: t.V2} - secretSaveSrc := single.FromSupplierCached(func() (models.AppSecret, error) { - return a.appSecretRepository.Set(ctx, ref.Kid, appSecret, a.keyConf.GetSecretDuration()) - }) - refSavedSrc := single.MapWithError(secretSaveSrc, func(_ models.AppSecret) (models.PrimaryAppSecretRef, error) { - return a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()) - }) - return single.Map(refSavedSrc, func(_ models.PrimaryAppSecretRef) bos.AppSecretBo { - return bos.NewAppSecretBo(ref.Kid, appSecret.SecretKey) - }) - }) +func (a AppSecretServiceImpl) GeneratePrimaryAppSecret(ctx context.Context) (bos.AppSecretBo, error) { + kidGuid, err := uuid.NewRandom() + if err != nil { + return bos.AppSecretBo{}, err + } + kid := kidGuid.String() + if utils.StringIsBlank(kid) { + return bos.AppSecretBo{}, errors.New("generated KID is blank") + } + key, err := cipherutils.GenerateRandomKeyAES() + if err != nil { + return bos.AppSecretBo{}, err + } + + ref := models.PrimaryAppSecretRef{Kid: kid} + appSecret := models.AppSecret{SecretKey: key} + + if _, err := a.appSecretRepository.Set(ctx, ref.Kid, appSecret, a.keyConf.GetSecretDuration()); err != nil { + return bos.AppSecretBo{}, err + } + if _, err = a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()); err != nil { + return bos.AppSecretBo{}, err + } + return bos.NewAppSecretBo(ref.Kid, appSecret.SecretKey), nil } func appSecretServiceReadMaybeModel[T any](a AppSecretServiceImpl, maybe option.Maybe[T]) (T, error) { val, ok := maybe.Get() var err error = nil if !ok { - err = apperrors.NewBadReqErrorFromRuleError( - a.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound), - ) + ruleErr := a.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound) + err = apperrors.NewBadReqErrorFromRuleError(ruleErr) } return val, err } diff --git a/microservices/go/cmd/keyservice/services/userchange-event-service.go b/microservices/go/cmd/keyservice/services/userchange-event-service.go index e27c79d..fc5116a 100644 --- a/microservices/go/cmd/keyservice/services/userchange-event-service.go +++ b/microservices/go/cmd/keyservice/services/userchange-event-service.go @@ -2,11 +2,8 @@ package services import ( "context" - "github.com/barweiss/go-tuple" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" ) @@ -14,7 +11,7 @@ type UserChangeEventService interface { HandleUserChangeEventTransaction( ctx context.Context, userEventDto userdtos.UserChangeEventDto, - ) single.Single[userdtos.UserChangeEventResponseDto] + ) (userdtos.UserChangeEventResponseDto, error) } type UserChangeEventServiceImpl struct { @@ -26,34 +23,25 @@ type UserChangeEventServiceImpl struct { func (u UserChangeEventServiceImpl) HandleUserChangeEventTransaction( ctx context.Context, userEventDto userdtos.UserChangeEventDto, -) single.Single[userdtos.UserChangeEventResponseDto] { - return dshandlers.TransactionalSingle( +) (userdtos.UserChangeEventResponseDto, error) { + return dshandlers.Transactional( ctx, u.crudDSHandler, - func(session dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventResponseDto] { - var userResSrc single.Single[userdtos.UserChangeEventResponseDto] + func(session dshandlers.Session, ctx context.Context) (userdtos.UserChangeEventResponseDto, error) { switch userEventDto.Action { case userdtos.UserSave: - saveUserSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { - return u.userService.SaveUser(ctx, userEventDto) - }) - userResSrc = single.Map(saveUserSrc, func(a userbos.UserBo) userdtos.UserChangeEventResponseDto { - return userdtos.UserChangeEventResponseDto{Discarded: false} - }) + _, err := u.userService.SaveUser(ctx, userEventDto) + return userdtos.UserChangeEventResponseDto{Discarded: false}, err case userdtos.UserDelete: - userDeleteSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { - return u.userService.DeleteUser(ctx, userEventDto) - }) - userKeyDeleteSrc := u.userKeyService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) - userResSrc = single.Map(single.Zip2(userDeleteSrc, userKeyDeleteSrc), - func(_ tuple.T2[userbos.UserBo, int64]) userdtos.UserChangeEventResponseDto { - return userdtos.UserChangeEventResponseDto{Discarded: false} - }, - ) + _, err := u.userService.DeleteUser(ctx, userEventDto) + if err != nil { + return userdtos.UserChangeEventResponseDto{Discarded: false}, err + } + _, err = u.userKeyService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) + return userdtos.UserChangeEventResponseDto{Discarded: false}, err default: - userResSrc = single.Just(userdtos.UserChangeEventResponseDto{Discarded: true}) + return userdtos.UserChangeEventResponseDto{Discarded: true}, nil } - return userResSrc }, ) diff --git a/microservices/go/cmd/keyservice/services/userkey-service.go b/microservices/go/cmd/keyservice/services/userkey-service.go index 4390778..662b7b8 100644 --- a/microservices/go/cmd/keyservice/services/userkey-service.go +++ b/microservices/go/cmd/keyservice/services/userkey-service.go @@ -3,9 +3,7 @@ package services import ( "context" "errors" - "github.com/barweiss/go-tuple" "github.com/google/uuid" - bos "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/businessobjects" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/businessrules" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/conf" "github.com/obenkenobi/cypher-log/microservices/go/cmd/keyservice/models" @@ -15,36 +13,31 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/cipherutils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/encodingutils" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" "time" ) type UserKeyService interface { - UserKeyExists(ctx context.Context, userBo userbos.UserBo) single.Single[commondtos.ExistsDto] + UserKeyExists(ctx context.Context, userBo userbos.UserBo) (commondtos.ExistsDto, error) CreateUserKey( ctx context.Context, userBo userbos.UserBo, passwordDto keydtos.PasscodeCreateDto, - ) single.Single[commondtos.SuccessDto] + ) (commondtos.SuccessDto, error) NewKeySession( ctx context.Context, userBo userbos.UserBo, dto keydtos.PasscodeDto, - ) single.Single[commondtos.UKeySessionDto] + ) (commondtos.UKeySessionDto, error) - GetKeyFromSession( - ctx context.Context, - sessionDto commondtos.UKeySessionDto, - ) single.Single[keydtos.UserKeyDto] + GetKeyFromSession(ctx context.Context, sessionDto commondtos.UKeySessionDto) (keydtos.UserKeyDto, error) - DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] + DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) } type UserKeyServiceImpl struct { @@ -56,220 +49,190 @@ type UserKeyServiceImpl struct { keyConf conf.KeyConf } -func (u UserKeyServiceImpl) UserKeyExists( - ctx context.Context, - userBo userbos.UserBo, -) single.Single[commondtos.ExistsDto] { - userFindSrc := single.FromSupplierCached(func() (option.Maybe[models.UserKeyGenerator], error) { - return u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) - }) - return single.Map(userFindSrc, func(maybe option.Maybe[models.UserKeyGenerator]) commondtos.ExistsDto { - return commondtos.NewExistsDto(maybe.IsPresent()) - }) +func (u UserKeyServiceImpl) UserKeyExists(ctx context.Context, userBo userbos.UserBo) (commondtos.ExistsDto, error) { + userFind, err := u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) + if err != nil { + return commondtos.ExistsDto{}, err + } + return commondtos.NewExistsDto(userFind.IsPresent()), nil } func (u UserKeyServiceImpl) CreateUserKey( ctx context.Context, userBo userbos.UserBo, passcodeDto keydtos.PasscodeCreateDto, -) single.Single[commondtos.SuccessDto] { - type derivedKey struct { - key []byte - keyDerivationSalt []byte +) (commondtos.SuccessDto, error) { + key, keyDerivationSalt, err := cipherutils.DeriveAESKeyFromPassword([]byte(passcodeDto.Passcode), nil) + if err != nil { + return commondtos.SuccessDto{}, err + } + keyHash, err := cipherutils.HashKeyBcrypt(key) + if err != nil { + return commondtos.SuccessDto{}, err } - type keyGeneration struct { - keyHash []byte - keyDerivationSalt []byte + + newUserKeyGen := models.UserKeyGenerator{ + UserId: userBo.Id, + KeyDerivationSalt: keyDerivationSalt, + KeyHash: keyHash, + KeyVersion: 0, } - newKeySrc := single.FromSupplierCached(func() (derivedKey, error) { - key, keyDerivationSalt, err := cipherutils.DeriveAESKeyFromPassword([]byte(passcodeDto.Passcode), nil) - return derivedKey{key: key, keyDerivationSalt: keyDerivationSalt}, err - }) - newKeyAndHashSrc := single.MapWithError(newKeySrc, func(dk derivedKey) (keyGeneration, error) { - keyHash, err := cipherutils.HashKeyBcrypt(dk.key) - return keyGeneration{keyHash: keyHash, keyDerivationSalt: dk.keyDerivationSalt}, err - }) - newUserKeyGenSrc := single.Map(newKeyAndHashSrc, func(kg keyGeneration) models.UserKeyGenerator { - return models.UserKeyGenerator{ - UserId: userBo.Id, - KeyDerivationSalt: kg.keyDerivationSalt, - KeyHash: kg.keyHash, - KeyVersion: 0, - } - }) - userKeyGenSaveSrc := single.MapWithError(newUserKeyGenSrc, - func(userKeyGen models.UserKeyGenerator) (models.UserKeyGenerator, error) { - return u.userKeyGeneratorRepository.Create(ctx, userKeyGen) - }, - ) - return single.Map(userKeyGenSaveSrc, func(_ models.UserKeyGenerator) commondtos.SuccessDto { - return commondtos.NewSuccessTrue() - }) + + _, err = u.userKeyGeneratorRepository.Create(ctx, newUserKeyGen) + if err != nil { + return commondtos.SuccessDto{}, err + } + return commondtos.NewSuccessTrue(), nil } func (u UserKeyServiceImpl) NewKeySession( ctx context.Context, userBo userbos.UserBo, dto keydtos.PasscodeDto, -) single.Single[commondtos.UKeySessionDto] { - userKeyGenSrc := u.getUserKeyGenerator(ctx, userBo).ScheduleLazyAndCache(ctx) - KeySrc := single.FlatMap(userKeyGenSrc, - func(userKeyGen models.UserKeyGenerator) single.Single[[]byte] { - newKeySrc := single.FromSupplierCached(func() ([]byte, error) { - logger.Log.Debugf("Generating key from password") - key, _, err := cipherutils.DeriveAESKeyFromPassword([]byte(dto.Passcode), userKeyGen.KeyDerivationSalt) - return key, err - }) - keyValidated := single.MapWithError(newKeySrc, func(key []byte) (any, error) { - err := u.userKeyBr.ValidateKeyFromPassword(userKeyGen, key) - return any(true), err - }) - return single.FlatMap(keyValidated, func(_ any) single.Single[[]byte] { - return newKeySrc - }) - }) - proxyKeySrc := single.FromSupplierCached(cipherutils.GenerateRandomKeyAES) - appSecretSrc := u.appSecretService.GetPrimaryAppSecret(ctx) - return single.FlatMap(single.Zip4(proxyKeySrc, appSecretSrc, KeySrc, userKeyGenSrc), - func(t tuple.T4[[]byte, bos.AppSecretBo, []byte, models.UserKeyGenerator]) single.Single[commondtos.UKeySessionDto] { - proxyKey, appSecret, key, userKeyGen := t.V1, t.V2, t.V3, t.V4 - tokenBytesSrc := single.FromSupplierCached(func() ([]byte, error) { - return cipherutils.EncryptAES(appSecret.Key, proxyKey) - }) - tokenHashSrc := single.MapWithError(tokenBytesSrc, cipherutils.HashWithSaltSHA256) - tokenSrc := single.Map(tokenBytesSrc, encodingutils.EncodeBase64String) - proxyKidSrc := single.MapWithError( - single.FromSupplierCached(uuid.NewRandom), - func(uuid uuid.UUID) (string, error) { - proxyKid := uuid.String() - if utils.StringIsBlank(proxyKid) { - return proxyKid, errors.New("generated proxy KID is blank") - } - return proxyKid, nil - }, - ) - keyCipherSrc := single.FromSupplierCached(func() ([]byte, error) { - return cipherutils.EncryptAES(proxyKey, key) - }) - userIdCipherSrc := single.FromSupplierCached(func() ([]byte, error) { - return cipherutils.EncryptAES(proxyKey, []byte(userBo.Id)) - }) - keyVersionCipherSrc := single.FromSupplierCached(func() ([]byte, error) { - return cipherutils.EncryptAES(proxyKey, []byte(utils.Int64ToStr(userKeyGen.KeyVersion))) - }) - return single.FlatMap( - single.Zip6(tokenHashSrc, tokenSrc, proxyKidSrc, keyCipherSrc, userIdCipherSrc, keyVersionCipherSrc), - func(t tuple.T6[[]byte, string, string, []byte, []byte, []byte]) single.Single[commondtos.UKeySessionDto] { - tokenHash, token, proxyKid := t.V1, t.V2, t.V3 - keyCipher, userIdCipher, keyVersionCipher := t.V4, t.V5, t.V6 - keySessionModel := models.UserKeySession{ - KeyCipher: keyCipher, - TokenHash: tokenHash, - AppSecretKid: appSecret.Kid, - UserIdCipher: userIdCipher, - KeyVersionCipher: keyVersionCipher, - } - startTime := time.Now().UnixMilli() - sessionDuration := u.keyConf.GetTokenSessionDuration() - savedKeySession := single.FromSupplierCached(func() (models.UserKeySession, error) { - return u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration) - }) - return single.Map(savedKeySession, func(_ models.UserKeySession) commondtos.UKeySessionDto { - return commondtos.UKeySessionDto{ - Token: token, - ProxyKid: proxyKid, - UserId: userBo.Id, - KeyVersion: userKeyGen.KeyVersion, - StartTime: startTime, - DurationMilli: sessionDuration.Milliseconds(), - } - }) - }, - ) - }, - ) +) (commondtos.UKeySessionDto, error) { + userKeyGen, err := u.getUserKeyGenerator(ctx, userBo) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + + logger.Log.Debugf("Generating key from password") + key, _, err := cipherutils.DeriveAESKeyFromPassword([]byte(dto.Passcode), userKeyGen.KeyDerivationSalt) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + + if err := u.userKeyBr.ValidateKeyFromPassword(userKeyGen, key); err != nil { + return commondtos.UKeySessionDto{}, err + } + + proxyKey, err := cipherutils.GenerateRandomKeyAES() + if err != nil { + return commondtos.UKeySessionDto{}, err + } + appSecret, err := u.appSecretService.GetPrimaryAppSecret(ctx) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + tokenBytes, err := cipherutils.EncryptAES(appSecret.Key, proxyKey) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + tokenHash, err := cipherutils.HashWithSaltSHA256(tokenBytes) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + token := encodingutils.EncodeBase64String(tokenBytes) + + proxyKidUUID, err := uuid.NewRandom() + if err != nil { + return commondtos.UKeySessionDto{}, err + } + proxyKid := proxyKidUUID.String() + if utils.StringIsBlank(proxyKid) { + return commondtos.UKeySessionDto{}, errors.New("generated proxy KID is blank") + } + keyCipher, err := cipherutils.EncryptAES(proxyKey, key) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + userIdCipher, err := cipherutils.EncryptAES(proxyKey, []byte(userBo.Id)) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + keyVersionCipher, err := cipherutils.EncryptAES(proxyKey, []byte(utils.Int64ToStr(userKeyGen.KeyVersion))) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + + keySessionModel := models.UserKeySession{ + KeyCipher: keyCipher, + TokenHash: tokenHash, + AppSecretKid: appSecret.Kid, + UserIdCipher: userIdCipher, + KeyVersionCipher: keyVersionCipher, + } + + startTime := time.Now().UnixMilli() + sessionDuration := u.keyConf.GetTokenSessionDuration() + + _, err = u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration) + if err != nil { + return commondtos.UKeySessionDto{}, err + } + + sessionDto := commondtos.UKeySessionDto{ + Token: token, + ProxyKid: proxyKid, + UserId: userBo.Id, + KeyVersion: userKeyGen.KeyVersion, + StartTime: startTime, + DurationMilli: sessionDuration.Milliseconds(), + } + return sessionDto, nil } func (u UserKeyServiceImpl) GetKeyFromSession( ctx context.Context, sessionDto commondtos.UKeySessionDto, -) single.Single[keydtos.UserKeyDto] { - findStoresSessSrc := single.FromSupplierCached(func() (option.Maybe[models.UserKeySession], error) { - return u.userKeySessionRepository.Get(ctx, sessionDto.ProxyKid) - }) - storedSessionSrc := single.FlatMap(findStoresSessSrc, - func(maybe option.Maybe[models.UserKeySession]) single.Single[models.UserKeySession] { - return option.Map(maybe, single.Just[models.UserKeySession]). - OrElseGet(func() single.Single[models.UserKeySession] { - ruleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession) - return single.Error[models.UserKeySession](apperrors.NewBadReqErrorFromRuleError(ruleErr)) - }) - }, - ).ScheduleLazyAndCache(ctx) - - tokenBytesSrc := single.MapWithError(single.Just(sessionDto.Token), encodingutils.DecodeBase64String) - tokenHashVerifiedSrc := single.MapWithError(single.Zip2(storedSessionSrc, tokenBytesSrc), - func(t tuple.T2[models.UserKeySession, []byte]) (any, error) { - session, tokenBytes := t.V1, t.V2 - return any(true), u.userKeyBr.ValidateSessionTokenHash(session, tokenBytes) - }, - ) - appSecretSrc := single.FlatMap(single.Zip2(tokenHashVerifiedSrc, storedSessionSrc), - func(t tuple.T2[any, models.UserKeySession]) single.Single[bos.AppSecretBo] { - session := t.V2 - return u.appSecretService.GetAppSecret(ctx, session.AppSecretKid) - }) - proxyKeySrc := single.MapWithError(single.Zip2(appSecretSrc, tokenBytesSrc), - func(t tuple.T2[bos.AppSecretBo, []byte]) ([]byte, error) { - appSecret, tokenBytes := t.V1, t.V2 - return cipherutils.DecryptAES(appSecret.Key, tokenBytes) - }, - ) - keyBytesSrc := single.FlatMap(single.Zip2(storedSessionSrc, proxyKeySrc), - func(t tuple.T2[models.UserKeySession, []byte]) single.Single[[]byte] { - session, proxyKey := t.V1, t.V2 - err := u.userKeyBr.ValidateProxyKeyCiphersFromSession( - ctx, - proxyKey, - sessionDto.UserId, - sessionDto.KeyVersion, - session, - ) - if err != nil { - return single.Error[[]byte](err) - } - return single.FromSupplierCached(func() ([]byte, error) { - return cipherutils.DecryptAES(proxyKey, session.KeyCipher) - }) - }, - ) - return single.Map(keyBytesSrc, func(keyBytes []byte) keydtos.UserKeyDto { - return keydtos.NewUserKeyDto(keyBytes, sessionDto.KeyVersion) - }) +) (keydtos.UserKeyDto, error) { + findStoredSession, err := u.userKeySessionRepository.Get(ctx, sessionDto.ProxyKid) + if err != nil { + return keydtos.UserKeyDto{}, err + } + session, sessionPresent := findStoredSession.Get() + if !sessionPresent { + ruleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSession) + return keydtos.UserKeyDto{}, apperrors.NewBadReqErrorFromRuleError(ruleErr) + } + tokenBytes, err := encodingutils.DecodeBase64String(sessionDto.Token) + if err != nil { + return keydtos.UserKeyDto{}, err + } + if err := u.userKeyBr.ValidateSessionTokenHash(session, tokenBytes); err != nil { + return keydtos.UserKeyDto{}, err + } + appSecret, err := u.appSecretService.GetAppSecret(ctx, session.AppSecretKid) + if err != nil { + return keydtos.UserKeyDto{}, err + } + proxyKey, err := cipherutils.DecryptAES(appSecret.Key, tokenBytes) + if err != nil { + return keydtos.UserKeyDto{}, err + } + if err := u.userKeyBr.ValidateProxyKeyCiphersFromSession( + ctx, + proxyKey, + sessionDto.UserId, + sessionDto.KeyVersion, + session, + ); err != nil { + return keydtos.UserKeyDto{}, err + } + keyBytes, err := cipherutils.DecryptAES(proxyKey, session.KeyCipher) + if err != nil { + return keydtos.UserKeyDto{}, err + } + return keydtos.NewUserKeyDto(keyBytes, sessionDto.KeyVersion), nil } func (u UserKeyServiceImpl) getUserKeyGenerator( ctx context.Context, userBo userbos.UserBo, -) single.Single[models.UserKeyGenerator] { - userKeyFind := single.FromSupplierCached(func() (option.Maybe[models.UserKeyGenerator], error) { - return u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) - }) - return single.FlatMap(userKeyFind, - func(maybe option.Maybe[models.UserKeyGenerator]) single.Single[models.UserKeyGenerator] { - return option.Map(maybe, single.Just[models.UserKeyGenerator]). - OrElseGet(func() single.Single[models.UserKeyGenerator] { - ruleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound) - return single.Error[models.UserKeyGenerator](apperrors.NewBadReqErrorFromRuleError(ruleErr)) - }) - }, - ) +) (models.UserKeyGenerator, error) { + userKeyFind, err := u.userKeyGeneratorRepository.FindOneByUserId(ctx, userBo.Id) + if err != nil { + return models.UserKeyGenerator{}, err + } + if userKey, ok := userKeyFind.Get(); ok { + return userKey, nil + } else { + ruleErr := u.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound) + return models.UserKeyGenerator{}, apperrors.NewBadReqErrorFromRuleError(ruleErr) + } } -func (u UserKeyServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] { - return single.FromSupplierCached(func() (int64, error) { - return u.userKeyGeneratorRepository.DeleteByUserIdAndGetCount(ctx, userId) - }) +func (u UserKeyServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) { + return u.userKeyGeneratorRepository.DeleteByUserIdAndGetCount(ctx, userId) } func NewUserKeyServiceImpl( diff --git a/microservices/go/cmd/noteservice/businessrules/notebr.go b/microservices/go/cmd/noteservice/businessrules/notebr.go index 5752deb..bfaabc3 100644 --- a/microservices/go/cmd/noteservice/businessrules/notebr.go +++ b/microservices/go/cmd/noteservice/businessrules/notebr.go @@ -29,21 +29,21 @@ func (n NoteBrImpl) ValidateGetNotes(pageRequest pagination.PageRequest) error { } else if _, ok := n.validSortFields[pageRequest.Sort[0].Field]; !ok { ruleErrors = append(ruleErrors, n.errorService.RuleErrorFromCode(apperrors.ErrCodeInvalidSortOptions)) } - return validationutils.MergeAppErrors(ruleErrors) + return validationutils.MergeRuleErrors(ruleErrors) } func (n NoteBrImpl) ValidateNoteRead(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error { ruleErrs := append(n.validateKeyVersion(keyDto, existing), n.validateNoteOwnership(userBo, existing)...) - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func (n NoteBrImpl) ValidateNoteUpdate(userBo userbos.UserBo, keyDto keydtos.UserKeyDto, existing models.Note) error { ruleErrs := append(n.validateKeyVersion(keyDto, existing), n.validateNoteOwnership(userBo, existing)...) - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func (n NoteBrImpl) ValidateNoteDelete(userBo userbos.UserBo, existing models.Note) error { - return validationutils.MergeAppErrors(n.validateNoteOwnership(userBo, existing)) + return validationutils.MergeRuleErrors(n.validateNoteOwnership(userBo, existing)) } func (n NoteBrImpl) validateKeyVersion(keyDto keydtos.UserKeyDto, existing models.Note) []apperrors.RuleError { diff --git a/microservices/go/cmd/userservice/businessrules/userbr.go b/microservices/go/cmd/userservice/businessrules/userbr.go index cb7fee7..1d39e90 100644 --- a/microservices/go/cmd/userservice/businessrules/userbr.go +++ b/microservices/go/cmd/userservice/businessrules/userbr.go @@ -42,7 +42,7 @@ func (u UserBrImpl) ValidateUserCreate( apperrors.ErrCodeResourceAlreadyCreated, ) ruleErrorsSrc := append(userNameNotTakenValidationErrs, userNotCreatedValidationErrs...) - return validationutils.MergeAppErrors(ruleErrorsSrc) + return validationutils.MergeRuleErrors(ruleErrorsSrc) } func (u UserBrImpl) ValidateUserUpdate(ctx context.Context, dto userdtos.UserSaveDto, existing models.User) error { @@ -54,7 +54,7 @@ func (u UserBrImpl) ValidateUserUpdate(ctx context.Context, dto userdtos.UserSav } ruleErrs = append(ruleErrs, valErrs...) } - return validationutils.MergeAppErrors(ruleErrs) + return validationutils.MergeRuleErrors(ruleErrs) } func (u UserBrImpl) validateUserNameNotTaken( diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index b6fde1e..ab83583 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -49,8 +49,7 @@ func (u UserServiceImpl) AddUserTransaction( ) (userdtos.UserReadDto, error) { return dshandlers.Transactional(ctx, u.crudDSHandler, func(s dshandlers.Session, ctx context.Context) (userdtos.UserReadDto, error) { - err := u.userBr.ValidateUserCreate(ctx, identity, userSaveDto) - if err != nil { + if err := u.userBr.ValidateUserCreate(ctx, identity, userSaveDto); err != nil { return userdtos.UserReadDto{}, err } @@ -90,8 +89,7 @@ func (u UserServiceImpl) UpdateUserTransaction( return userdtos.UserReadDto{}, err } - err = u.userBr.ValidateUserUpdate(ctx, userSaveDto, user) - if err != nil { + if err = u.userBr.ValidateUserUpdate(ctx, userSaveDto, user); err != nil { return userdtos.UserReadDto{}, err } diff --git a/microservices/go/pkg/apperrors/validationutils/validation-utils.go b/microservices/go/pkg/apperrors/validationutils/validation-utils.go index e32ca3b..a666a0b 100644 --- a/microservices/go/pkg/apperrors/validationutils/validation-utils.go +++ b/microservices/go/pkg/apperrors/validationutils/validation-utils.go @@ -28,7 +28,7 @@ func ValidateValueIsPresent[V any]( return []apperrors.RuleError{} } -func MergeAppErrors(ruleErrs []apperrors.RuleError) error { +func MergeRuleErrors(ruleErrs []apperrors.RuleError) error { if len(ruleErrs) == 0 { return nil } diff --git a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go index 2d3d661..677011b 100644 --- a/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go +++ b/microservices/go/pkg/sharedservices/ginservices/ginctx-service.go @@ -26,15 +26,23 @@ type GinCtxService interface { // response. Certain errors relating to user input will trigger a 4XX status // code. Otherwise, a 5XX code will be thrown indicating the error means // something went wrong with the server. + // + // Deprecated: You will now need to use the gin.Context directly or rely on the + // StartCtxPipeline() method to handle errors in the background. RespondError(c *gin.Context, err error) // RespondJsonOk responds with json value with a 200 status code or an // error if the error != nil. + // + // Deprecated: Use the gin.Context directly to create a JSON response RespondJsonOk(c *gin.Context, model any, err error) - // processBindError takes an error from binding a value from a request body processes it into a BadRequestError. + // processBindError takes an error from binding a value from a request body + // processes it into a BadRequestError. processBindError(err error) apperrors.BadRequestError + // StartCtxPipeline initializes a controller.Pipeline that manages errors in the + // background using the gin context StartCtxPipeline(c *gin.Context) controller.Pipeline } From 564060d844e5a7b70703572ea80a2063b5fcf7e9 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 15:12:36 -0500 Subject: [PATCH 13/14] Note service is non-reactive --- .../keyservice/services/appsecret-service.go | 2 +- .../services/userchange-event-service.go | 1 - .../keyservice/services/userkey-service.go | 6 +- .../noteservice/controllers/notecontroller.go | 11 +- .../cmd/noteservice/listeners/rmqlistener.go | 4 +- .../cmd/noteservice/services/note-service.go | 353 +++++++++--------- .../services/userchange-event-service.go | 39 +- .../cmd/userservice/services/user-service.go | 3 +- .../datasource/dshandlers/crud-dshandler.go | 58 --- microservices/go/pkg/reactive/observable.go | 16 - .../go/pkg/reactive/observable_test.go | 35 -- .../go/pkg/reactive/single/single.go | 308 --------------- .../go/pkg/reactive/single/single_test.go | 44 --- 13 files changed, 193 insertions(+), 687 deletions(-) delete mode 100644 microservices/go/pkg/reactive/observable.go delete mode 100644 microservices/go/pkg/reactive/observable_test.go delete mode 100644 microservices/go/pkg/reactive/single/single.go delete mode 100644 microservices/go/pkg/reactive/single/single_test.go diff --git a/microservices/go/cmd/keyservice/services/appsecret-service.go b/microservices/go/cmd/keyservice/services/appsecret-service.go index 6ab69e3..6784f85 100644 --- a/microservices/go/cmd/keyservice/services/appsecret-service.go +++ b/microservices/go/cmd/keyservice/services/appsecret-service.go @@ -74,7 +74,7 @@ func (a AppSecretServiceImpl) GeneratePrimaryAppSecret(ctx context.Context) (bos if _, err := a.appSecretRepository.Set(ctx, ref.Kid, appSecret, a.keyConf.GetSecretDuration()); err != nil { return bos.AppSecretBo{}, err } - if _, err = a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()); err != nil { + if _, err := a.primaryAppSecretRefRepository.Set(ctx, ref, a.keyConf.GetPrimaryAppSecretDuration()); err != nil { return bos.AppSecretBo{}, err } return bos.NewAppSecretBo(ref.Kid, appSecret.SecretKey), nil diff --git a/microservices/go/cmd/keyservice/services/userchange-event-service.go b/microservices/go/cmd/keyservice/services/userchange-event-service.go index fc5116a..614a867 100644 --- a/microservices/go/cmd/keyservice/services/userchange-event-service.go +++ b/microservices/go/cmd/keyservice/services/userchange-event-service.go @@ -44,7 +44,6 @@ func (u UserChangeEventServiceImpl) HandleUserChangeEventTransaction( } }, ) - } func NewUserChangeEventServiceImpl( diff --git a/microservices/go/cmd/keyservice/services/userkey-service.go b/microservices/go/cmd/keyservice/services/userkey-service.go index 662b7b8..a29941a 100644 --- a/microservices/go/cmd/keyservice/services/userkey-service.go +++ b/microservices/go/cmd/keyservice/services/userkey-service.go @@ -78,8 +78,7 @@ func (u UserKeyServiceImpl) CreateUserKey( KeyVersion: 0, } - _, err = u.userKeyGeneratorRepository.Create(ctx, newUserKeyGen) - if err != nil { + if _, err := u.userKeyGeneratorRepository.Create(ctx, newUserKeyGen); err != nil { return commondtos.SuccessDto{}, err } return commondtos.NewSuccessTrue(), nil @@ -155,8 +154,7 @@ func (u UserKeyServiceImpl) NewKeySession( startTime := time.Now().UnixMilli() sessionDuration := u.keyConf.GetTokenSessionDuration() - _, err = u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration) - if err != nil { + if _, err := u.userKeySessionRepository.Set(ctx, proxyKid, keySessionModel, sessionDuration); err != nil { return commondtos.UKeySessionDto{}, err } diff --git a/microservices/go/cmd/noteservice/controllers/notecontroller.go b/microservices/go/cmd/noteservice/controllers/notecontroller.go index 0f11f87..e1acd73 100644 --- a/microservices/go/cmd/noteservice/controllers/notecontroller.go +++ b/microservices/go/cmd/noteservice/controllers/notecontroller.go @@ -8,7 +8,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" cDTOs "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" nDTOs "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/notedtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/security" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/ginservices" @@ -46,7 +45,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { n.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, n.noteService.AddNoteTransaction(c, userBo, reqBody)) + resBody, err = n.noteService.AddNoteTransaction(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -68,7 +67,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { n.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, n.noteService.UpdateNoteTransaction(c, userBo, reqBody)) + resBody, err = n.noteService.UpdateNoteTransaction(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -89,7 +88,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { reqBody, err = ginservices.ReadValueFromBody[nDTOs.NoteIdDto](n.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, n.noteService.DeleteNoteTransaction(c, userBo, reqBody)) + resBody, err = n.noteService.DeleteNoteTransaction(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -111,7 +110,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { n.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, n.noteService.GetNoteById(c, userBo, reqBody)) + resBody, err = n.noteService.GetNoteById(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) @@ -133,7 +132,7 @@ func (n NoteControllerImpl) AddRoutes(r *gin.Engine) { n.ginCtxService, c) return }).Next(func() (err error) { - resBody, err = single.RetrieveValue(c, n.noteService.GetNotesPage(c, userBo, reqBody)) + resBody, err = n.noteService.GetNotesPage(c, userBo, reqBody) return }).Next(func() (err error) { c.JSON(http.StatusOK, resBody) diff --git a/microservices/go/cmd/noteservice/listeners/rmqlistener.go b/microservices/go/cmd/noteservice/listeners/rmqlistener.go index 77da54f..c6dbbe0 100644 --- a/microservices/go/cmd/noteservice/listeners/rmqlistener.go +++ b/microservices/go/cmd/noteservice/listeners/rmqlistener.go @@ -8,7 +8,6 @@ import ( "github.com/obenkenobi/cypher-log/microservices/go/pkg/messaging/rmq" "github.com/obenkenobi/cypher-log/microservices/go/pkg/messaging/rmq/exchanges" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/rmqservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/taskrunner" "github.com/wagslane/go-rabbitmq" @@ -36,8 +35,7 @@ func (r RmqListenerImpl) ListenUserChange() { rabbitmq.WithConsumeOptionsQuorum, ) userCreateReceiver.Listen(func(d msg.Delivery[userdtos.UserChangeEventDto]) msg.ReceiverAction { - resSrc := r.userChangeEventService.HandleUserChangeEventTransaction(r.ctx, d.Body()) - res, err := single.RetrieveValue(r.ctx, resSrc) + res, err := r.userChangeEventService.HandleUserChangeEventTransaction(r.ctx, d.Body()) if err != nil { return d.Resend() } else if res.Discarded { diff --git a/microservices/go/cmd/noteservice/services/note-service.go b/microservices/go/cmd/noteservice/services/note-service.go index 596fff7..8da46b9 100644 --- a/microservices/go/cmd/noteservice/services/note-service.go +++ b/microservices/go/cmd/noteservice/services/note-service.go @@ -2,7 +2,6 @@ package services import ( "context" - "github.com/barweiss/go-tuple" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/businessrules" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/mappers" "github.com/obenkenobi/cypher-log/microservices/go/cmd/noteservice/models" @@ -14,12 +13,10 @@ import ( cDTOs "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/commondtos" kDTOs "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/keydtos" nDTOs "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/notedtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices/externalservices" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils" "github.com/obenkenobi/cypher-log/microservices/go/pkg/utils/cipherutils" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/wrappers/option" ) type NoteService interface { @@ -27,28 +24,28 @@ type NoteService interface { ctx context.Context, userBo userbos.UserBo, dto cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto], - ) single.Single[cDTOs.SuccessDto] + ) (cDTOs.SuccessDto, error) UpdateNoteTransaction( ctx context.Context, userBo userbos.UserBo, dto cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto], - ) single.Single[cDTOs.SuccessDto] + ) (cDTOs.SuccessDto, error) DeleteNoteTransaction( ctx context.Context, userBo userbos.UserBo, noteIdDto nDTOs.NoteIdDto, - ) single.Single[cDTOs.SuccessDto] + ) (cDTOs.SuccessDto, error) GetNoteById( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto], - ) single.Single[nDTOs.NoteReadDto] + ) (nDTOs.NoteReadDto, error) GetNotesPage( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[pagination.PageRequest], - ) single.Single[pagination.Page[nDTOs.NotePreviewDto]] - DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] + ) (pagination.Page[nDTOs.NotePreviewDto], error) + DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) } type NoteServiceImpl struct { @@ -63,34 +60,36 @@ func (n NoteServiceImpl) AddNoteTransaction( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[nDTOs.NoteCreateDto], -) single.Single[cDTOs.SuccessDto] { - return dshandlers.TransactionalSingle(ctx, n.crudDSHandler, - func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { +) (cDTOs.SuccessDto, error) { + return dshandlers.Transactional(ctx, n.crudDSHandler, + func(_ dshandlers.Session, ctx context.Context) (cDTOs.SuccessDto, error) { sessDto, noteCreateDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { - return n.userKeyService.GetKeyFromSession(ctx, sessDto) - }) - keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey).ScheduleLazyAndCache(ctx) - titleCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { - return cipherutils.EncryptAES(key, []byte(noteCreateDto.Title)) - }) - textCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { - return cipherutils.EncryptAES(key, []byte(noteCreateDto.Text)) - }) - noteSaveSrc := single.MapWithError(single.Zip3(keyDtoSrc, titleCipherSrc, textCipherSrc), - func(t tuple.T3[kDTOs.UserKeyDto, []byte, []byte]) (models.Note, error) { - keyDto, titleCipher, textCipher := t.V1, t.V2, t.V3 - note := models.Note{ - UserId: userBo.Id, - TextCipher: textCipher, - TitleCipher: titleCipher, - KeyVersion: keyDto.KeyVersion, - } - return n.noteRepository.Create(ctx, note) - }) - return single.Map(noteSaveSrc, func(_ models.Note) cDTOs.SuccessDto { - return cDTOs.NewSuccessTrue() - }) + keyDto, err := n.userKeyService.GetKeyFromSession(ctx, sessDto) + if err != nil { + return cDTOs.SuccessDto{}, err + } + key, err := kDTOs.UserKeyDto.GetKey(keyDto) + if err != nil { + return cDTOs.SuccessDto{}, err + } + titleCipher, err := cipherutils.EncryptAES(key, []byte(noteCreateDto.Title)) + if err != nil { + return cDTOs.SuccessDto{}, err + } + textCipher, err := cipherutils.EncryptAES(key, []byte(noteCreateDto.Text)) + if err != nil { + return cDTOs.SuccessDto{}, err + } + note := models.Note{ + UserId: userBo.Id, + TextCipher: textCipher, + TitleCipher: titleCipher, + KeyVersion: keyDto.KeyVersion, + } + if _, err := n.noteRepository.Create(ctx, note); err != nil { + return cDTOs.SuccessDto{}, err + } + return cDTOs.NewSuccessTrue(), nil }) } @@ -99,40 +98,40 @@ func (n NoteServiceImpl) UpdateNoteTransaction( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[nDTOs.NoteUpdateDto], -) single.Single[cDTOs.SuccessDto] { - return dshandlers.TransactionalSingle(ctx, n.crudDSHandler, - func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { +) (cDTOs.SuccessDto, error) { + return dshandlers.Transactional(ctx, n.crudDSHandler, + func(_ dshandlers.Session, ctx context.Context) (cDTOs.SuccessDto, error) { sessDto, noteUpdateDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - existingSrc := n.getExistingNote(ctx, noteUpdateDto.Id).ScheduleLazyAndCache(ctx) - keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { - return n.userKeyService.GetKeyFromSession(ctx, sessDto) - }) - keySrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), - func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) ([]byte, error) { - existing, keyDto := t.V1, t.V2 - err := n.noteBr.ValidateNoteUpdate(userBo, keyDto, existing) - if err != nil { - return nil, err - } - return keyDto.GetKey() - }).ScheduleLazyAndCache(ctx) - titleCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { - return cipherutils.EncryptAES(key, []byte(noteUpdateDto.Title)) - }) - textCipherSrc := single.MapWithError(keySrc, func(key []byte) ([]byte, error) { - return cipherutils.EncryptAES(key, []byte(noteUpdateDto.Text)) - }) - noteSaveSrc := single.MapWithError(single.Zip4(existingSrc, keyDtoSrc, titleCipherSrc, textCipherSrc), - func(t tuple.T4[models.Note, kDTOs.UserKeyDto, []byte, []byte]) (models.Note, error) { - existing, keyDto, titleCipher, textCipher := t.V1, t.V2, t.V3, t.V4 - existing.TitleCipher = titleCipher - existing.TextCipher = textCipher - existing.KeyVersion = keyDto.KeyVersion - return n.noteRepository.Update(ctx, existing) - }) - return single.Map(noteSaveSrc, func(_ models.Note) cDTOs.SuccessDto { - return cDTOs.NewSuccessTrue() - }) + existingNote, err := n.getExistingNote(ctx, noteUpdateDto.Id) + if err != nil { + return cDTOs.SuccessDto{}, err + } + keyDto, err := n.userKeyService.GetKeyFromSession(ctx, sessDto) + if err != nil { + return cDTOs.SuccessDto{}, err + } + if err := n.noteBr.ValidateNoteUpdate(userBo, keyDto, existingNote); err != nil { + return cDTOs.SuccessDto{}, err + } + key, err := keyDto.GetKey() + if err != nil { + return cDTOs.SuccessDto{}, err + } + titleCipher, err := cipherutils.EncryptAES(key, []byte(noteUpdateDto.Title)) + if err != nil { + return cDTOs.SuccessDto{}, err + } + textCipher, err := cipherutils.EncryptAES(key, []byte(noteUpdateDto.Text)) + if err != nil { + return cDTOs.SuccessDto{}, err + } + existingNote.TitleCipher = titleCipher + existingNote.TextCipher = textCipher + existingNote.KeyVersion = keyDto.KeyVersion + if _, err := n.noteRepository.Update(ctx, existingNote); err != nil { + return cDTOs.SuccessDto{}, err + } + return cDTOs.NewSuccessTrue(), nil }) } @@ -140,23 +139,20 @@ func (n NoteServiceImpl) DeleteNoteTransaction( ctx context.Context, userBo userbos.UserBo, noteIdDto nDTOs.NoteIdDto, -) single.Single[cDTOs.SuccessDto] { - return dshandlers.TransactionalSingle(ctx, n.crudDSHandler, - func(_ dshandlers.Session, ctx context.Context) single.Single[cDTOs.SuccessDto] { - existingSrc := n.getExistingNote(ctx, noteIdDto.Id).ScheduleLazyAndCache(ctx) - validateDeleteSrc := single.MapWithError(existingSrc, - func(existing models.Note) (any, error) { - err := n.noteBr.ValidateNoteDelete(userBo, existing) - return any(true), err - }) - noteDeleteSrc := single.MapWithError(single.Zip2(existingSrc, validateDeleteSrc), - func(t tuple.T2[models.Note, any]) (models.Note, error) { - existing := t.V1 - return n.noteRepository.Delete(ctx, existing) - }) - return single.Map(noteDeleteSrc, func(_ models.Note) cDTOs.SuccessDto { - return cDTOs.NewSuccessTrue() - }) +) (cDTOs.SuccessDto, error) { + return dshandlers.Transactional(ctx, n.crudDSHandler, + func(_ dshandlers.Session, ctx context.Context) (cDTOs.SuccessDto, error) { + existingNote, err := n.getExistingNote(ctx, noteIdDto.Id) + if err != nil { + return cDTOs.SuccessDto{}, err + } + if err := n.noteBr.ValidateNoteDelete(userBo, existingNote); err != nil { + return cDTOs.SuccessDto{}, err + } + if _, err := n.noteRepository.Delete(ctx, existingNote); err != nil { + return cDTOs.SuccessDto{}, err + } + return cDTOs.NewSuccessTrue(), nil }) } @@ -164,118 +160,109 @@ func (n NoteServiceImpl) GetNoteById( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[nDTOs.NoteIdDto], -) single.Single[nDTOs.NoteReadDto] { +) (nDTOs.NoteReadDto, error) { sessDto, noteIdDto := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - existingSrc := n.getExistingNote(ctx, noteIdDto.Id).ScheduleLazyAndCache(ctx) - keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { - return n.userKeyService.GetKeyFromSession(ctx, sessDto) - }) - validationSrc := single.MapWithError(single.Zip2(existingSrc, keyDtoSrc), - func(t tuple.T2[models.Note, kDTOs.UserKeyDto]) (any, error) { - existing, keyDto := t.V1, t.V2 - return any(true), n.noteBr.ValidateNoteRead(userBo, keyDto, existing) - }) - keySrc := single.MapWithError(single.Zip2(keyDtoSrc, validationSrc), - func(t tuple.T2[kDTOs.UserKeyDto, any]) ([]byte, error) { - keyDto := t.V1 - return keyDto.GetKey() - }, - ).ScheduleLazyAndCache(ctx) + existingNote, err := n.getExistingNote(ctx, noteIdDto.Id) + if err != nil { + return nDTOs.NoteReadDto{}, err + } + keyDto, err := n.userKeyService.GetKeyFromSession(ctx, sessDto) + if err != nil { + return nDTOs.NoteReadDto{}, err + } + if err := n.noteBr.ValidateNoteRead(userBo, keyDto, existingNote); err != nil { + return nDTOs.NoteReadDto{}, err + } + key, err := kDTOs.UserKeyDto.GetKey(keyDto) + if err != nil { + return nDTOs.NoteReadDto{}, err + } + textBytes, err := cipherutils.DecryptAES(key, existingNote.TextCipher) + if err != nil { + return nDTOs.NoteReadDto{}, err + } + text := string(textBytes) + titleBytes, err := cipherutils.DecryptAES(key, existingNote.TitleCipher) + if err != nil { + return nDTOs.NoteReadDto{}, err + } + title := string(titleBytes) - return single.FlatMap(single.Zip3(existingSrc, keySrc, validationSrc), - func(t tuple.T3[models.Note, []byte, any]) single.Single[nDTOs.NoteReadDto] { - existing, keyBytes := t.V1, t.V2 - textSrc := single.FromSupplierCached(func() (string, error) { - txtBytes, err := cipherutils.DecryptAES(keyBytes, existing.TextCipher) - return string(txtBytes), err - }) - titleSrc := single.FromSupplierCached(func() (string, error) { - titleBytes, err := cipherutils.DecryptAES(keyBytes, existing.TitleCipher) - return string(titleBytes), err - }) - return single.Map(single.Zip2(textSrc, titleSrc), func(t tuple.T2[string, string]) nDTOs.NoteReadDto { - text, title := t.V1, t.V2 - coreNoteDetails := nDTOs.NewCoreNoteDetailsDto(title, text) - noteDetailsDto := nDTOs.NoteReadDto{} - mappers.MapCoreNoteDetailsAndNoteToNoteReadDto(&coreNoteDetails, &existing, ¬eDetailsDto) - return noteDetailsDto - }) - }) + coreNoteDetails := nDTOs.NewCoreNoteDetailsDto(title, text) + noteDetailsDto := nDTOs.NoteReadDto{} + mappers.MapCoreNoteDetailsAndNoteToNoteReadDto(&coreNoteDetails, &existingNote, ¬eDetailsDto) + return noteDetailsDto, nil } func (n NoteServiceImpl) GetNotesPage( ctx context.Context, userBo userbos.UserBo, sessReqDto cDTOs.UKeySessionReqDto[pagination.PageRequest], -) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { +) (pagination.Page[nDTOs.NotePreviewDto], error) { sessionDto, pageRequest := sessReqDto.SetUserIdAndUnwrap(userBo.Id) - err := n.noteBr.ValidateGetNotes(pageRequest) + + if err := n.noteBr.ValidateGetNotes(pageRequest); err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + + keyDto, err := n.userKeyService.GetKeyFromSession(ctx, sessionDto) if err != nil { - return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) + return pagination.Page[nDTOs.NotePreviewDto]{}, err } - zippedSrc := single.FromSupplierCached(func() (tuple.T3[kDTOs.UserKeyDto, []byte, int64], error) { - keyDtoSrc := single.FromSupplierCached(func() (kDTOs.UserKeyDto, error) { - return n.userKeyService.GetKeyFromSession(ctx, sessionDto) - }) - keySrc := single.MapWithError(keyDtoSrc, kDTOs.UserKeyDto.GetKey) - countSrc := single.FromSupplierCached(func() (int64, error) { - return n.noteRepository.CountByUserId(ctx, userBo.Id) - }) - return single.RetrieveValue(ctx, single.Zip3(keyDtoSrc, keySrc, countSrc)) - }) - return single.FlatMap(zippedSrc, - func(t tuple.T3[kDTOs.UserKeyDto, []byte, int64]) single.Single[pagination.Page[nDTOs.NotePreviewDto]] { - keyDto, keyBytes, count := t.V1, t.V2, t.V3 - notes, err := n.noteRepository.GetPaginatedByUserId(ctx, userBo.Id, pageRequest) - if err != nil { - return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) - } - noteDTOs := make([]nDTOs.NotePreviewDto, 0, len(notes)) - for _, note := range notes { - if note.KeyVersion != keyDto.KeyVersion { - ruleErr := n.errorService.RuleErrorFromCode(apperrors.ErrCodeDataRace) - return single.Error[pagination.Page[nDTOs.NotePreviewDto]]( - apperrors.NewBadReqErrorFromRuleError(ruleErr)) - } - txtBytes, err := cipherutils.DecryptAES(keyBytes, note.TextCipher) - if err != nil { - return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) - } - titleBytes, err := cipherutils.DecryptAES(keyBytes, note.TitleCipher) - if err != nil { - return single.Error[pagination.Page[nDTOs.NotePreviewDto]](err) - } - txt, title := string(txtBytes), string(titleBytes) - textPreview := utils.StringFirstNChars(txt, 60) - coreNoteDto := nDTOs.NewCoreNoteDto(title) - noteReadDto := nDTOs.NotePreviewDto{} - mappers.MapTextPreviewAndCoreNoteAndNoteToNotePreviewDto(textPreview, &coreNoteDto, ¬e, ¬eReadDto) - noteDTOs = append(noteDTOs, noteReadDto) - } - noteDTOsSrc := single.Just(noteDTOs) - return single.Map(noteDTOsSrc, func(noteDTOs []nDTOs.NotePreviewDto) pagination.Page[nDTOs.NotePreviewDto] { - return pagination.NewPage(noteDTOs, count) - }) - }) + key, err := kDTOs.UserKeyDto.GetKey(keyDto) + if err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + + count, err := n.noteRepository.CountByUserId(ctx, userBo.Id) + if err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + + notes, err := n.noteRepository.GetPaginatedByUserId(ctx, userBo.Id, pageRequest) + if err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + + noteDTOs := make([]nDTOs.NotePreviewDto, 0, len(notes)) + for _, note := range notes { + if note.KeyVersion != keyDto.KeyVersion { + ruleErr := n.errorService.RuleErrorFromCode(apperrors.ErrCodeDataRace) + return pagination.Page[nDTOs.NotePreviewDto]{}, apperrors.NewBadReqErrorFromRuleError(ruleErr) + } + txtBytes, err := cipherutils.DecryptAES(key, note.TextCipher) + if err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + titleBytes, err := cipherutils.DecryptAES(key, note.TitleCipher) + if err != nil { + return pagination.Page[nDTOs.NotePreviewDto]{}, err + } + txt, title := string(txtBytes), string(titleBytes) + textPreview := utils.StringFirstNChars(txt, 60) + coreNoteDto := nDTOs.NewCoreNoteDto(title) + noteReadDto := nDTOs.NotePreviewDto{} + mappers.MapTextPreviewAndCoreNoteAndNoteToNotePreviewDto(textPreview, &coreNoteDto, ¬e, ¬eReadDto) + noteDTOs = append(noteDTOs, noteReadDto) + } + return pagination.NewPage(noteDTOs, count), nil } -func (u NoteServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) single.Single[int64] { - return single.FromSupplierCached(func() (int64, error) { - return u.noteRepository.DeleteByUserIdAndGetCount(ctx, userId) - }) +func (u NoteServiceImpl) DeleteByUserIdAndGetCount(ctx context.Context, userId string) (int64, error) { + return u.noteRepository.DeleteByUserIdAndGetCount(ctx, userId) } -func (n NoteServiceImpl) getExistingNote(ctx context.Context, id string) single.Single[models.Note] { - findSrc := single.FromSupplierCached(func() (option.Maybe[models.Note], error) { - return n.noteRepository.FindById(ctx, id) - }) - return single.FlatMap(findSrc, func(m option.Maybe[models.Note]) single.Single[models.Note] { - return option.Map(m, single.Just[models.Note]). - OrElseGet(func() single.Single[models.Note] { - ruleErr := n.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound) - return single.Error[models.Note](apperrors.NewBadReqErrorFromRuleError(ruleErr)) - }) - }) +func (n NoteServiceImpl) getExistingNote(ctx context.Context, id string) (models.Note, error) { + noteSearch, err := n.noteRepository.FindById(ctx, id) + if err != nil { + return models.Note{}, err + } + if note, ok := noteSearch.Get(); ok { + return note, nil + } else { + ruleErr := n.errorService.RuleErrorFromCode(apperrors.ErrCodeReqResourcesNotFound) + return models.Note{}, apperrors.NewBadReqErrorFromRuleError(ruleErr) + } } func NewNoteServiceImpl( diff --git a/microservices/go/cmd/noteservice/services/userchange-event-service.go b/microservices/go/cmd/noteservice/services/userchange-event-service.go index f57c46c..86b993a 100644 --- a/microservices/go/cmd/noteservice/services/userchange-event-service.go +++ b/microservices/go/cmd/noteservice/services/userchange-event-service.go @@ -2,11 +2,8 @@ package services import ( "context" - "github.com/barweiss/go-tuple" "github.com/obenkenobi/cypher-log/microservices/go/pkg/datasource/dshandlers" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/businessobjects/userbos" "github.com/obenkenobi/cypher-log/microservices/go/pkg/objects/dtos/userdtos" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "github.com/obenkenobi/cypher-log/microservices/go/pkg/sharedservices" ) @@ -14,7 +11,7 @@ type UserChangeEventService interface { HandleUserChangeEventTransaction( ctx context.Context, userEventDto userdtos.UserChangeEventDto, - ) single.Single[userdtos.UserChangeEventResponseDto] + ) (userdtos.UserChangeEventResponseDto, error) } type UserChangeEventServiceImpl struct { @@ -26,37 +23,27 @@ type UserChangeEventServiceImpl struct { func (u UserChangeEventServiceImpl) HandleUserChangeEventTransaction( ctx context.Context, userEventDto userdtos.UserChangeEventDto, -) single.Single[userdtos.UserChangeEventResponseDto] { - return dshandlers.TransactionalSingle( +) (userdtos.UserChangeEventResponseDto, error) { + return dshandlers.Transactional( ctx, u.crudDSHandler, - func(session dshandlers.Session, ctx context.Context) single.Single[userdtos.UserChangeEventResponseDto] { - var userResSrc single.Single[userdtos.UserChangeEventResponseDto] + func(session dshandlers.Session, ctx context.Context) (userdtos.UserChangeEventResponseDto, error) { switch userEventDto.Action { case userdtos.UserSave: - saveUserSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { - return u.userService.SaveUser(ctx, userEventDto) - }) - userResSrc = single.Map(saveUserSrc, func(a userbos.UserBo) userdtos.UserChangeEventResponseDto { - return userdtos.UserChangeEventResponseDto{Discarded: false} - }) + _, err := u.userService.SaveUser(ctx, userEventDto) + return userdtos.UserChangeEventResponseDto{Discarded: false}, err case userdtos.UserDelete: - userDeleteSrc := single.FromSupplierCached(func() (userbos.UserBo, error) { - return u.userService.DeleteUser(ctx, userEventDto) - }) - noteDeleteSrc := u.noteService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) - userResSrc = single.Map(single.Zip2(userDeleteSrc, noteDeleteSrc), - func(_ tuple.T2[userbos.UserBo, int64]) userdtos.UserChangeEventResponseDto { - return userdtos.UserChangeEventResponseDto{Discarded: false} - }, - ) + _, err := u.userService.DeleteUser(ctx, userEventDto) + if err != nil { + return userdtos.UserChangeEventResponseDto{Discarded: false}, err + } + _, err = u.noteService.DeleteByUserIdAndGetCount(ctx, userEventDto.Id) + return userdtos.UserChangeEventResponseDto{Discarded: false}, err default: - userResSrc = single.Just(userdtos.UserChangeEventResponseDto{Discarded: true}) + return userdtos.UserChangeEventResponseDto{Discarded: true}, nil } - return userResSrc }, ) - } func NewUserChangeEventServiceImpl( diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index ab83583..b8758a8 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -207,8 +207,7 @@ func (u UserServiceImpl) deleteUserTransaction( return event, err } - _, err = u.authServerMgmtService.DeleteUser(deletedUser.AuthId) - if err != nil { + if _, err := u.authServerMgmtService.DeleteUser(deletedUser.AuthId); err != nil { return event, err } diff --git a/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go b/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go index f0433c7..e133024 100644 --- a/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go +++ b/microservices/go/pkg/datasource/dshandlers/crud-dshandler.go @@ -2,11 +2,9 @@ package dshandlers import ( "context" - "github.com/joamaki/goreactive/stream" "github.com/kamva/mgm/v3" "github.com/obenkenobi/cypher-log/microservices/go/pkg/conf" "github.com/obenkenobi/cypher-log/microservices/go/pkg/logger" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -50,62 +48,6 @@ func Transactional[T any]( return res, err } -// TransactionalSingle creates a Single that executes a transaction when -// evaluated from a Single created from the supplier. The supplier and the -// evaluation of the single runs within the scope of the transaction. -func TransactionalSingle[T any]( - ctx context.Context, - d CrudDSHandler, - supplier func(Session, context.Context) single.Single[T], -) single.Single[T] { - return single.FromSupplierCached(func() (T, error) { - var res T - var err error = nil - transactionErr := d.ExecTransaction(ctx, func(session Session, ctx context.Context) error { - res, err = single.RetrieveValue(ctx, supplier(session, ctx)) - if err != nil { - return session.AbortTransaction(ctx) - } - return session.CommitTransaction(ctx) - }) - if err == nil { - err = transactionErr - } - return res, err - }) -} - -// TransactionalObservable creates a deferred Observable that waits for -// a transaction to be completed. The supplier function runs within the -// transaction scope. The returned observable from the supplier is evaluated -// asynchronously and eagerly within the transaction scope. -func TransactionalObservable[T any]( - ctx context.Context, - d CrudDSHandler, - supplier func(Session, context.Context) stream.Observable[T], -) stream.Observable[T] { - src, start := stream.Deferred[T]() - go func() { - var res []T - var err error - transactionErr := d.ExecTransaction(ctx, func(session Session, ctx context.Context) error { - res, err = stream.ToSlice(ctx, supplier(session, ctx)) - if err != nil { - return session.AbortTransaction(ctx) - } - return session.CommitTransaction(ctx) - }) - if err == nil { - err = transactionErr - } - if err != nil { - start(stream.Error[T](err)) - } - start(stream.FromSlice(res)) - }() - return src -} - // MongoDBHandler is a CrudDSHandler implementation for MongoDB type MongoDBHandler struct { mongoConf conf.MongoConf diff --git a/microservices/go/pkg/reactive/observable.go b/microservices/go/pkg/reactive/observable.go deleted file mode 100644 index 9eb159d..0000000 --- a/microservices/go/pkg/reactive/observable.go +++ /dev/null @@ -1,16 +0,0 @@ -package reactive - -import ( - "fmt" - "github.com/joamaki/goreactive/stream" -) - -// MapDerefPtr takes an observable of a pointer and maps it to an observable of a de-referenced value of that pointer -func MapDerefPtr[T any](pointerX stream.Observable[*T]) stream.Observable[T] { - return stream.FlatMap(pointerX, func(ptr *T) stream.Observable[T] { - if ptr == nil { - return stream.Error[T](fmt.Errorf("attempted to dereference a nil ptr")) - } - return stream.Just(*ptr) - }) -} diff --git a/microservices/go/pkg/reactive/observable_test.go b/microservices/go/pkg/reactive/observable_test.go deleted file mode 100644 index 47ad7d8..0000000 --- a/microservices/go/pkg/reactive/observable_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package reactive - -import ( - "context" - "github.com/joamaki/goreactive/stream" - cv "github.com/smartystreets/goconvey/convey" - "testing" -) - -func TestObservableFromChannels(t *testing.T) { - cv.Convey("When creating a channel for an observable that sends 3 numbers", t, func() { - intCh := make(chan int) - go func() { - defer close(intCh) - for i := range [3]int{} { - intCh <- i + 1 - } - }() - ctx := context.Background() - intObs := stream.FromChannel(intCh) - resCh, err := stream.ToChannels(ctx, intObs) - var intList []int - for i := range resCh { - intList = append(intList, i) - if i == 3 { - break - } - } - cv.Convey("3 numbers are listened to for the observable", func() { - cv.So(len(intList), cv.ShouldEqual, 3) - cv.So(intList, cv.ShouldResemble, []int{1, 2, 3}) - cv.So(<-err, cv.ShouldBeNil) - }) - }) -} diff --git a/microservices/go/pkg/reactive/single/single.go b/microservices/go/pkg/reactive/single/single.go deleted file mode 100644 index 14e3b54..0000000 --- a/microservices/go/pkg/reactive/single/single.go +++ /dev/null @@ -1,308 +0,0 @@ -package single - -import ( - "context" - "github.com/akrennmair/slice" - "github.com/barweiss/go-tuple" - "github.com/joamaki/goreactive/stream" - "sync" -) - -// Single is a listener for a single value. It by default listens for a value -// lazily or if specified, asynchronously. Async singles will cache the stored -// result. -type Single[T any] struct { - src stream.Observable[T] -} - -// ToObservable turns the Single into an observable. (Warning: the provided -// single can no longer reliably be used to read values as they may be used up by -// the observable instead.) -func (s Single[T]) ToObservable() stream.Observable[T] { return s.src } - -// ScheduleEagerAsyncCached takes a single and returns a new Single that is scheduled -// to be evaluated eagerly and asynchronously up until point of execution of the -// returning single. The emitted value is cached if observed twice. -func (s Single[T]) ScheduleEagerAsyncCached(ctx context.Context) Single[T] { - ch, errCh := ToChannels(ctx, s) - return FromChannelsCached(ch, errCh) -} - -// ScheduleLazyAndCache takes a single and returns a new single that is lazy -// evaluated. The single's emitted value is cached if observed twice. -func (s Single[T]) ScheduleLazyAndCache(ctx context.Context) Single[T] { - return FromSupplierCached(func() (T, error) { - return RetrieveValue(ctx, s) - }) -} - -// Thread safe Observable that reads from a channel, used for ensuring when a -// single is made from a channel, it will always emit the same value even when -// observed multiple times. This is done by caching the channel result in a -// thread safe manner. -type singleChanReadObservable[T any] struct { - _channelRead bool - _ch <-chan T - _chErr <-chan error - _value T - _error error - _valueRWLock sync.RWMutex -} - -func (a *singleChanReadObservable[T]) Observe(ctx context.Context, next func(T) error) error { - if ctx.Err() != nil { - // Context already cancelled, stop before emitting items. - return ctx.Err() - } - shouldAttemptWriteValue := func() bool { - a._valueRWLock.RLock() - defer a._valueRWLock.RUnlock() - return !a._channelRead - }() - - if shouldAttemptWriteValue { - func() { - a._valueRWLock.Lock() - defer a._valueRWLock.Unlock() - if !a._channelRead { - a._channelRead = true - if a._chErr != nil { - if err := <-a._chErr; err != nil { - a._error = err - } - } - if a._error == nil { - a._value = <-a._ch - } - } - }() - } - value, err := func() (T, error) { - a._valueRWLock.RLock() - defer a._valueRWLock.RUnlock() - return a._value, a._error - }() - if err != nil { - return err - } - return next(value) -} - -type singleSupplierReadObservable[T any] struct { - _supplierRan bool - _supplier func() (T, error) - _value T - _err error - _valueRWLock sync.RWMutex -} - -func (a *singleSupplierReadObservable[T]) Observe(ctx context.Context, next func(T) error) error { - if ctx.Err() != nil { - // Context already cancelled, stop before emitting items. - return ctx.Err() - } - shouldAttemptWriteValue := func() bool { - a._valueRWLock.RLock() - defer a._valueRWLock.RUnlock() - return !a._supplierRan - }() - - if shouldAttemptWriteValue { - func() { - a._valueRWLock.Lock() - defer a._valueRWLock.Unlock() - if !a._supplierRan { - val, err := a._supplier() - if err != nil { - a._err = err - } else { - a._value = val - } - a._supplierRan = true - } - }() - } - value, err := func() (T, error) { - a._valueRWLock.RLock() - defer a._valueRWLock.RUnlock() - return a._value, a._err - }() - if err != nil { - return err - } - return next(value) -} - -func FromObservableAsList[T any](obs stream.Observable[T]) Single[[]T] { - listObs := stream.Reduce(obs, []T{}, func(list []T, v T) []T { return append(list, v) }) - return fromSingleObservable(listObs) -} - -// FromChannelCached Creates a single that listens to a single value from a channel. -// Recommended for channels that only emmit one value. Once the channel is read, -// the value is cached so the single can be observed more than one time with -// little overhead. If a channel emits multiple values, it is recommended you use -// observables instead. -func FromChannelCached[T any](ch <-chan T) Single[T] { return FromChannelsCached(ch, nil) } - -// FromChannelsCached Creates a single that listens to a single value from a channel -// and checks for errors in the error channel. Once the channel is read, the -// value is cached so the single can be observed more than one time with little -// overhead. Recommended for channels that only emmit one value. If a channel -// emits multiple values, it is recommended you use observables instead. -func FromChannelsCached[T any](ch <-chan T, chErr <-chan error) Single[T] { - return Single[T]{src: &singleChanReadObservable[T]{_ch: ch, _chErr: chErr}} -} - -// Just creates a single from a single item -func Just[T any](val T) Single[T] { return fromSingleObservable(stream.Just(val)) } - -// Error creates a single that fails immediately with the given error -func Error[T any](err error) Single[T] { return fromSingleObservable(stream.Error[T](err)) } - -// FromSupplierCached creates a single out of a supplier function that returns a -// value or an error to emit. If the supplier is successful, the result value is -// emitted. Otherwise, if an error is returned, an error is emitted. When this -// single is observed, the emitted value from the supplier is cached if observed -// again. -func FromSupplierCached[T any](supplier func() (T, error)) Single[T] { - var src stream.Observable[T] = &singleSupplierReadObservable[T]{_supplier: supplier} - return fromSingleObservable(src) -} - -// Map applies a function onto a Single -func Map[A any, B any](src Single[A], apply func(A) B) Single[B] { - return fromSingleObservable[B](stream.Map(src.ToObservable(), func(a A) B { return apply(a) })) -} - -// MapWithError applies a function onto a Single where if an error is returned, the Single fails -func MapWithError[A any, B any](src Single[A], apply func(A) (B, error)) Single[B] { - return fromSingleObservable[B]( - stream.FuncObservable[B](func(ctx context.Context, next func(B) error) error { - return src.ToObservable().Observe( - ctx, - func(a A) error { - if res, err := apply(a); err != nil { - return err - } else { - return next(res) - } - }) - }), - ) -} - -// Zip2 Takes 2 Singles and returns a Single that emits a tuple of each of the -// singles in the order they are supplied -func Zip2[V1 any, V2 any](src1 Single[V1], src2 Single[V2]) Single[tuple.T2[V1, V2]] { - return FlatMap(src1, func(v1 V1) Single[tuple.T2[V1, V2]] { - return Map(src2, func(v2 V2) tuple.T2[V1, V2] { - return tuple.New2(v1, v2) - }) - - }) -} - -// Zip3 Takes 3 Singles and returns a Single that emits a tuple of each of the -// singles in the order they are supplied -func Zip3[V1 any, V2 any, V3 any]( - src1 Single[V1], - src2 Single[V2], - src3 Single[V3], -) Single[tuple.T3[V1, V2, V3]] { - return FlatMap( - Zip2(src1, src2), - func(t tuple.T2[V1, V2]) Single[tuple.T3[V1, V2, V3]] { - return Map(src3, func(v3 V3) tuple.T3[V1, V2, V3] { - return tuple.New3(t.V1, t.V2, v3) - }) - }, - ) -} - -// Zip4 Takes 4 Singles and returns a Single that emits a tuple of each of the -// singles in the order they are supplied. -func Zip4[V1 any, V2 any, V3 any, V4 any]( - src1 Single[V1], - src2 Single[V2], - src3 Single[V3], - src4 Single[V4], -) Single[tuple.T4[V1, V2, V3, V4]] { - return FlatMap( - Zip2(src1, src2), - func(t1 tuple.T2[V1, V2]) Single[tuple.T4[V1, V2, V3, V4]] { - return Map(Zip2(src3, src4), func(t2 tuple.T2[V3, V4]) tuple.T4[V1, V2, V3, V4] { - return tuple.New4(t1.V1, t1.V2, t2.V1, t2.V2) - }) - }, - ) -} - -// Zip5 Takes 5 Singles and returns a Single that emits a tuple of each of the -// singles in the order they are supplied. -func Zip5[V1 any, V2 any, V3 any, V4 any, V5 any]( - src1 Single[V1], - src2 Single[V2], - src3 Single[V3], - src4 Single[V4], - src5 Single[V5], -) Single[tuple.T5[V1, V2, V3, V4, V5]] { - return FlatMap( - Zip3(src1, src2, src3), - func(t1 tuple.T3[V1, V2, V3]) Single[tuple.T5[V1, V2, V3, V4, V5]] { - return Map(Zip2(src4, src5), func(t2 tuple.T2[V4, V5]) tuple.T5[V1, V2, V3, V4, V5] { - return tuple.New5(t1.V1, t1.V2, t1.V3, t2.V1, t2.V2) - }) - }, - ) -} - -// Zip6 Takes 6 Singles and returns a Single that emits a tuple of each of the -// singles in the order they are supplied. -func Zip6[V1 any, V2 any, V3 any, V4 any, V5 any, V6 any]( - src1 Single[V1], - src2 Single[V2], - src3 Single[V3], - src4 Single[V4], - src5 Single[V5], - src6 Single[V6], -) Single[tuple.T6[V1, V2, V3, V4, V5, V6]] { - return FlatMap( - Zip3(src1, src2, src3), - func(t1 tuple.T3[V1, V2, V3]) Single[tuple.T6[V1, V2, V3, V4, V5, V6]] { - return Map(Zip3(src4, src5, src6), func(t2 tuple.T3[V4, V5, V6]) tuple.T6[V1, V2, V3, V4, V5, V6] { - return tuple.New6(t1.V1, t1.V2, t1.V3, t2.V1, t2.V2, t2.V3) - }) - }, - ) -} - -func MergeSingles[T any](singles []Single[T]) stream.Observable[T] { - observables := slice.Map(singles, func(s Single[T]) stream.Observable[T] { - return s.ToObservable() - }) - return stream.Merge(observables...) -} - -// FlatMap applies a function that returns a single of V2 to the source single of V1. -// The Single from the function is flattened (hence FlatMap). -func FlatMap[A any, B any](src Single[A], apply func(A) Single[B]) Single[B] { - return Single[B]{ - stream.FlatMap(src.ToObservable(), func(a A) stream.Observable[B] { return apply(a).ToObservable() }), - } -} - -// RetrieveValue returns the value emitted by the Single -func RetrieveValue[T any](ctx context.Context, src Single[T]) (T, error) { - return stream.First(ctx, src.ToObservable()) -} - -// ToChannels returns channels that can emit a value created by a single. It -// ensures asynchronous execution when the value is evaluated. -func ToChannels[T any](ctx context.Context, src Single[T]) (<-chan T, <-chan error) { - return stream.ToChannels(ctx, src.ToObservable()) -} - -func fromSingleObservable[T any](src stream.Observable[T]) Single[T] { - return Single[T]{src: src} -} diff --git a/microservices/go/pkg/reactive/single/single_test.go b/microservices/go/pkg/reactive/single/single_test.go deleted file mode 100644 index 2e7c760..0000000 --- a/microservices/go/pkg/reactive/single/single_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package single_test - -import ( - "context" - "github.com/barweiss/go-tuple" - "github.com/obenkenobi/cypher-log/microservices/go/pkg/reactive/single" - cv "github.com/smartystreets/goconvey/convey" - "testing" - "time" -) - -func TestSingleFromSupplierAsync(t *testing.T) { - cv.Convey("When creating an observable with a supplier returns 1 that runs asynchronously", t, func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Hour*24) - defer cancel() - oneSrc := single.FromSupplierCached(func() (int, error) { return 1, nil }).ScheduleEagerAsyncCached(ctx) - twoSrc := single.Map(oneSrc, func(v int) int { return v + 1 }).ScheduleEagerAsyncCached(ctx) - threeSrc := single.Map(oneSrc, func(v int) int { return v + 2 }).ScheduleEagerAsyncCached(ctx) - twoExpected, threeExpected := 2, 3 - tupleExpected := tuple.New2(twoExpected, threeExpected) - cv.Convey("The same source oneSrc is then mapped to other Singles twoSrc and threeSrc,\n"+ - "incrementing the source value", func() { - cv.Convey("Expect zipping twoSrc and threeSrc successfully results in the expected tuples", func() { - zipped := single.Zip2(twoSrc, threeSrc) - value, err := single.RetrieveValue(ctx, zipped) - cv.So(err, cv.ShouldBeNil) - cv.So(tupleExpected, cv.ShouldResemble, value) - }) - - cv.Convey("Expect twoSrc returns the expected value when the value is retrieved", func() { - value, err := single.RetrieveValue(ctx, twoSrc) - cv.So(err, cv.ShouldBeNil) - cv.So(twoExpected, cv.ShouldResemble, value) - }) - - cv.Convey("Expect threeSrc returns the expected value when the value is retrieved", func() { - value, err := single.RetrieveValue(ctx, threeSrc) - cv.So(err, cv.ShouldBeNil) - cv.So(threeExpected, cv.ShouldResemble, value) - }, - ) - }) - }) -} From 956a64bef37440eb33fcb03ecbc85548190441c5 Mon Sep 17 00:00:00 2001 From: oren Date: Mon, 9 Jan 2023 16:33:13 -0500 Subject: [PATCH 14/14] Fix user sync bugs --- .../cmd/userservice/services/user-service.go | 2 +- microservices/go/pkg/concurrent/async.go | 32 +++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/microservices/go/cmd/userservice/services/user-service.go b/microservices/go/cmd/userservice/services/user-service.go index b8758a8..90274b8 100644 --- a/microservices/go/cmd/userservice/services/user-service.go +++ b/microservices/go/cmd/userservice/services/user-service.go @@ -222,7 +222,7 @@ func (u UserServiceImpl) distributeUserChangeTransaction( ) (userdtos.UserChangeEventDto, error) { return dshandlers.Transactional(ctx, u.crudDSHandler, func(session dshandlers.Session, ctx context.Context) (userdtos.UserChangeEventDto, error) { - event, err := u.sendUserChange(user, userdtos.UserDelete) + event, err := u.sendUserChange(user, userdtos.UserSave) if err != nil { return event, err } diff --git a/microservices/go/pkg/concurrent/async.go b/microservices/go/pkg/concurrent/async.go index 28b8de6..bab6da7 100644 --- a/microservices/go/pkg/concurrent/async.go +++ b/microservices/go/pkg/concurrent/async.go @@ -1,6 +1,9 @@ package concurrent -import "sync" +import ( + "github.com/barweiss/go-tuple" + "sync" +) type Future[T any] interface { Await() (T, error) @@ -8,8 +11,7 @@ type Future[T any] interface { type FutureImpl[T any] struct { _channelRead bool - _ch <-chan T - _chErr <-chan error + _ch <-chan tuple.T2[T, error] _value T _error error _valueRWLock sync.RWMutex @@ -28,14 +30,9 @@ func (a FutureImpl[T]) Await() (T, error) { defer a._valueRWLock.Unlock() if !a._channelRead { a._channelRead = true - if a._chErr != nil { - if err := <-a._chErr; err != nil { - a._error = err - } - } - if a._error == nil { - a._value = <-a._ch - } + t := <-a._ch + a._value = t.V1 + a._error = t.V2 } }() } @@ -47,16 +44,11 @@ func (a FutureImpl[T]) Await() (T, error) { } func Async[T any](supplier func() (T, error)) *FutureImpl[T] { - ch := make(chan T) - chErr := make(chan error) + ch := make(chan tuple.T2[T, error]) go func() { - defer func() { - close(ch) - close(chErr) - }() + defer close(ch) v, err := supplier() - ch <- v - chErr <- err + ch <- tuple.New2(v, err) }() - return &FutureImpl[T]{_channelRead: false, _error: nil} + return &FutureImpl[T]{_channelRead: false, _error: nil, _ch: ch} }