From d840248b2e12b4be25a8a10f0b037a3abb2891dc Mon Sep 17 00:00:00 2001 From: Eric Zorn Date: Sat, 4 Jan 2025 12:29:55 -0500 Subject: [PATCH] feat: handling delete user from db not auth0 --- apps/services/accounts-api/cmd/server/main.go | 2 +- .../connectrpc/account_service_handler.go | 42 +++++++++++++------ .../repositories/account_repository.go | 32 +++++++++++++- .../internal/app/ports/repository.go | 7 ++++ .../internal/app/ports/service.go | 4 ++ .../accounts-api/internal/domain/generic.go | 4 -- .../{registration.go => account_service.go} | 36 ++++++++++++---- 7 files changed, 102 insertions(+), 25 deletions(-) delete mode 100644 apps/services/accounts-api/internal/domain/generic.go rename apps/services/accounts-api/internal/domain/services/{registration.go => account_service.go} (54%) diff --git a/apps/services/accounts-api/cmd/server/main.go b/apps/services/accounts-api/cmd/server/main.go index 27f13b3..5198532 100644 --- a/apps/services/accounts-api/cmd/server/main.go +++ b/apps/services/accounts-api/cmd/server/main.go @@ -88,7 +88,7 @@ func run() error { accountRepo := repositories.NewAccountRespository(params.Logger, params.DB) // Create services - registrationService := services.NewRegistrationService(params.Logger, accountRepo) + registrationService := services.NewAccountService(params.Logger, accountRepo) // Create Application app := app.NewApp( diff --git a/apps/services/accounts-api/internal/adapters/connectrpc/account_service_handler.go b/apps/services/accounts-api/internal/adapters/connectrpc/account_service_handler.go index e08d885..db56222 100644 --- a/apps/services/accounts-api/internal/adapters/connectrpc/account_service_handler.go +++ b/apps/services/accounts-api/internal/adapters/connectrpc/account_service_handler.go @@ -6,7 +6,7 @@ import ( "fmt" "libs/backend/boot" "libs/backend/domain/user/entities" - "libs/backend/domain/user/valueobjects" + userValueObjects "libs/backend/domain/user/valueobjects" accountsapiv1 "libs/backend/proto-gen/go/accounts/accountsapi/v1" "libs/backend/proto-gen/go/accounts/accountsapi/v1/accountsapiv1connect" accountsDomain "libs/backend/proto-gen/go/accounts/domain" @@ -16,7 +16,7 @@ import ( ) // AuthHandler handles all gRPC endpoints for inbound webhooks -type RegistrationServiceHandler struct { +type AccountServiceHandler struct { accountsapiv1connect.UnimplementedAccountServiceHandler // Logger is the logger from the boot framework @@ -27,20 +27,20 @@ type RegistrationServiceHandler struct { } // NewRegistrationHandler will return a pointer to the inbound webhooks API server -func NewRegistrationHandler(logger boot.Logger, app app.App) *RegistrationServiceHandler { - return &RegistrationServiceHandler{ +func NewRegistrationHandler(logger boot.Logger, app app.App) *AccountServiceHandler { + return &AccountServiceHandler{ Logger: logger, App: app, } } // CreateAccount handles user creation and saves them in the database -func (r *RegistrationServiceHandler) CreateAccount( +func (r *AccountServiceHandler) CreateAccount( ctx context.Context, req *connect.Request[accountsapiv1.CreateAccountRequest], ) (*connect.Response[accountsapiv1.CreateAcountResponse], error) { - commonID := valueobjects.NewCommonIDFromString(req.Msg.CommonId) - emailAddress := valueobjects.NewEmailAddress(req.Msg.EmailAddress) + commonID := userValueObjects.NewCommonIDFromString(req.Msg.CommonId) + emailAddress := userValueObjects.NewEmailAddress(req.Msg.EmailAddress) // Convert to user domain type user := entities.NewUser( @@ -56,21 +56,21 @@ func (r *RegistrationServiceHandler) CreateAccount( } // GetAccount handles user creation and saves them in the database -func (r *RegistrationServiceHandler) GetAccount( +func (r *AccountServiceHandler) GetAccount( ctx context.Context, req *connect.Request[accountsapiv1.GetAccountRequest], ) (*connect.Response[accountsapiv1.GetAccountResponse], error) { // Parse the commonID vs emailAddress, depending on which one is passed - var commonID valueobjects.CommonID - var emailAddress valueobjects.EmailAddress + var commonID userValueObjects.CommonID + var emailAddress userValueObjects.EmailAddress if req.Msg.CommonId != nil { - commonID = valueobjects.NewCommonIDFromString(*req.Msg.CommonId) + commonID = userValueObjects.NewCommonIDFromString(*req.Msg.CommonId) } if req.Msg.EmailAddress != nil { - emailAddress = valueobjects.NewEmailAddress(*req.Msg.EmailAddress) + emailAddress = userValueObjects.NewEmailAddress(*req.Msg.EmailAddress) } // Get the user from the database @@ -91,3 +91,21 @@ func (r *RegistrationServiceHandler) GetAccount( return connect.NewResponse(resp), nil } + +// DeleteAccount handles account deletion +func (r *AccountServiceHandler) DeleteAccount( + ctx context.Context, + req *connect.Request[accountsapiv1.DeleteAccountRequest], +) (*connect.Response[accountsapiv1.DeleteAccountResponse], error) { + // Parse incoming request data + parsedCommonID := userValueObjects.NewCommonIDFromString(req.Msg.CommonId) + hardDelete := req.Msg.HardDelete + + // Perform deletion logic + deletedAt, err := r.App.RegistrationService.DeleteUser(ctx, parsedCommonID, hardDelete) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + return connect.NewResponse(&accountsapiv1.DeleteAccountResponse{DeletedAt: timestamppb.New(deletedAt)}), nil +} diff --git a/apps/services/accounts-api/internal/adapters/database/repositories/account_repository.go b/apps/services/accounts-api/internal/adapters/database/repositories/account_repository.go index efa3621..cd53e9c 100644 --- a/apps/services/accounts-api/internal/adapters/database/repositories/account_repository.go +++ b/apps/services/accounts-api/internal/adapters/database/repositories/account_repository.go @@ -4,10 +4,12 @@ import ( "apps/services/accounts-api/internal/models" "context" "errors" + "fmt" "libs/backend/boot" userEntities "libs/backend/domain/user/entities" userValueObjects "libs/backend/domain/user/valueobjects" "log/slog" + "time" "github.com/google/uuid" "gorm.io/gorm" @@ -69,7 +71,7 @@ func (r AccountRepository) GetAccountByEmailAddress(ctx context.Context, emailAd r.Logger.Info("Getting account", slog.String("emailAdress", emailAdress.String())) account := &models.Account{} - r.Database.First(account, "email_address = ?", emailAdress.Value()) + r.Database.First(account, "email_address = ?", emailAdress) if account.ID == uuid.Nil { return userEntities.User{}, errors.New("account not found") @@ -78,6 +80,34 @@ func (r AccountRepository) GetAccountByEmailAddress(ctx context.Context, emailAd return r.convertAccountToUser(account), nil } +// SoftDeleteAccountByCommonID will mark the user as deleted in the database with a timestamp +func (r AccountRepository) SoftDeleteAccountByCommonID(ctx context.Context, commonID userValueObjects.CommonID) (time.Time, error) { + r.Logger.Info("Handling soft deletion of account by commonID", slog.String("commonID", commonID.String())) + + // Handle soft deletion in the database + deletedAccount := &models.Account{} + if err := r.Database.Delete(deletedAccount, "common_id = ?", commonID).Error; err != nil { + r.Logger.Error("Cannot soft delete the user by commonID", slog.String("commonID", commonID.String())) + return time.Time{}, fmt.Errorf("cannot soft delete account: %w", err) + } + + return deletedAccount.DeletedAt.Time, nil +} + +// HardDeleteAccountByCommonID will remove the user from the dataase +func (r AccountRepository) HardDeleteAccountByCommonID(ctx context.Context, commonID userValueObjects.CommonID) (time.Time, error) { + r.Logger.Info("Handling hard deletion of account by commonID", slog.Any("commonID", commonID.String())) + + // Handle soft deletion in the database + deletedAccount := &models.Account{} + if err := r.Database.Unscoped().Delete(deletedAccount, "common_id = ?", commonID).Error; err != nil { + r.Logger.Error("Cannot hard delete the user by commonID", slog.String("commonID", commonID.String())) + return time.Time{}, fmt.Errorf("cannot hard delete accoutn: %w", err) + } + + return deletedAccount.DeletedAt.Time, nil +} + // convertAccountToUser converts an account to a user func (r AccountRepository) convertAccountToUser(account *models.Account) userEntities.User { parsedCommonID := userValueObjects.NewCommonIDFromUUID(account.CommonID) diff --git a/apps/services/accounts-api/internal/app/ports/repository.go b/apps/services/accounts-api/internal/app/ports/repository.go index 53f0f63..003ec45 100644 --- a/apps/services/accounts-api/internal/app/ports/repository.go +++ b/apps/services/accounts-api/internal/app/ports/repository.go @@ -4,6 +4,7 @@ import ( "context" userEntities "libs/backend/domain/user/entities" userValueObjects "libs/backend/domain/user/valueobjects" + "time" ) // AccountRepository is the interface for the account repository @@ -16,4 +17,10 @@ type AccountRepository interface { // GetAccountByEmailAddress gets an account from the database by email address GetAccountByEmailAddress(context.Context, userValueObjects.EmailAddress) (userEntities.User, error) + + // SoftDeleteAccountByCommonID will soft delete the user + SoftDeleteAccountByCommonID(context.Context, userValueObjects.CommonID) (time.Time, error) + + // HardDeleteAccountByCommonID will hard delete the user + HardDeleteAccountByCommonID(context.Context, userValueObjects.CommonID) (time.Time, error) } diff --git a/apps/services/accounts-api/internal/app/ports/service.go b/apps/services/accounts-api/internal/app/ports/service.go index 728a736..fc2025d 100644 --- a/apps/services/accounts-api/internal/app/ports/service.go +++ b/apps/services/accounts-api/internal/app/ports/service.go @@ -4,6 +4,7 @@ import ( "context" userEntities "libs/backend/domain/user/entities" userValueObjects "libs/backend/domain/user/valueobjects" + "time" ) // AccountService is the interface for the registration service @@ -13,4 +14,7 @@ type AccountService interface { // GetUser gets a user from the system GetUser(ctx context.Context, commonID userValueObjects.CommonID, emailAddress userValueObjects.EmailAddress) (userEntities.User, error) + + // Delete user will delete the user from the system (hard or soft deletion) + DeleteUser(ctx context.Context, commonID userValueObjects.CommonID, hardDelete bool) (time.Time, error) } diff --git a/apps/services/accounts-api/internal/domain/generic.go b/apps/services/accounts-api/internal/domain/generic.go deleted file mode 100644 index cdcede9..0000000 --- a/apps/services/accounts-api/internal/domain/generic.go +++ /dev/null @@ -1,4 +0,0 @@ -package domain - -// GenericDomain is a placeholder -type GenericDomain struct{} diff --git a/apps/services/accounts-api/internal/domain/services/registration.go b/apps/services/accounts-api/internal/domain/services/account_service.go similarity index 54% rename from apps/services/accounts-api/internal/domain/services/registration.go rename to apps/services/accounts-api/internal/domain/services/account_service.go index 4ff1286..1f97188 100644 --- a/apps/services/accounts-api/internal/domain/services/registration.go +++ b/apps/services/accounts-api/internal/domain/services/account_service.go @@ -8,10 +8,11 @@ import ( userEntities "libs/backend/domain/user/entities" userValueObjects "libs/backend/domain/user/valueobjects" "log/slog" + "time" ) -// RegistrationService is the registration service -type RegistrationService struct { +// AccountService is the registration service +type AccountService struct { // Logger is the logger from the boot framework Logger boot.Logger @@ -19,16 +20,16 @@ type RegistrationService struct { AccountRepository ports.AccountRepository } -// NewRegistrationService creates a new registration service -func NewRegistrationService(logger boot.Logger, accountRepository ports.AccountRepository) RegistrationService { - return RegistrationService{ +// NewAccountService creates a new registration service +func NewAccountService(logger boot.Logger, accountRepository ports.AccountRepository) AccountService { + return AccountService{ Logger: logger, AccountRepository: accountRepository, } } // RegisterUser registers a user in the system and the database -func (s RegistrationService) RegisterUser(ctx context.Context, user userEntities.User) error { +func (s AccountService) RegisterUser(ctx context.Context, user userEntities.User) error { s.Logger.Info("Registering user", slog.String("commonID", user.CommonID.String())) // Create account @@ -41,7 +42,7 @@ func (s RegistrationService) RegisterUser(ctx context.Context, user userEntities } // GetUser gets a user from the system -func (s RegistrationService) GetUser(ctx context.Context, commonID userValueObjects.CommonID, emailAddress userValueObjects.EmailAddress) (userEntities.User, error) { +func (s AccountService) GetUser(ctx context.Context, commonID userValueObjects.CommonID, emailAddress userValueObjects.EmailAddress) (userEntities.User, error) { var err error s.Logger.Info("Getting user", slog.String("commonID", commonID.String())) @@ -66,3 +67,24 @@ func (s RegistrationService) GetUser(ctx context.Context, commonID userValueObje return user, nil } + +// Delete user will delete the user from the system (hard or soft deletion) +func (s AccountService) DeleteUser(ctx context.Context, commonID userValueObjects.CommonID, hardDelete bool) (time.Time, error) { + s.Logger.Info("Deleting user by commonID", slog.String("commonID", commonID.String()), slog.Bool("hardDelete", hardDelete)) + + switch { + case !hardDelete: + deletedAt, err := s.AccountRepository.SoftDeleteAccountByCommonID(ctx, commonID) + if err != nil { + return time.Time{}, err + } + return deletedAt, nil + default: + deletedAt, err := s.AccountRepository.HardDeleteAccountByCommonID(ctx, commonID) + if err != nil { + return time.Time{}, err + } + + return deletedAt, nil + } +}