From 8e6c578dbd1bf4740abca7a8698e0e782c623350 Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Fri, 3 Jan 2025 18:41:00 +0000 Subject: [PATCH] made data sources more functional --- .../internal/infrastructure/data/helper.go | 34 ++++++++++ .../internal/infrastructure/data/mapper.go | 31 --------- .../data/prisma_project_data_source.go | 58 +++++++++++------ .../data/prisma_project_task_data_source.go | 59 ++++++++++------- .../data/prisma_time_entries_data_source.go | 65 ++++++++++++------- .../data/prisma_user_data_source.go | 64 +++++++++++------- 6 files changed, 189 insertions(+), 122 deletions(-) diff --git a/backend-go/internal/infrastructure/data/helper.go b/backend-go/internal/infrastructure/data/helper.go index 0ec913a..735aa9c 100644 --- a/backend-go/internal/infrastructure/data/helper.go +++ b/backend-go/internal/infrastructure/data/helper.go @@ -3,9 +3,29 @@ package data import ( "actatempus_backend/internal/domain/app_error" "actatempus_backend/internal/infrastructure/data/db" + "crypto/sha256" + "encoding/hex" "errors" + + E "github.com/IBM/fp-go/either" + O "github.com/IBM/fp-go/option" ) +// Dereference safely dereferences a pointer within an Option. +// If the Option is None, it returns None. Otherwise, it dereferences the pointer and wraps the value. +func Dereference[T any](opt O.Option[*T]) O.Option[T] { + return O.Map(func(ptr *T) T { + return *ptr + })(opt) +} + +// FromOptionWithError wraps E.FromOption and simplifies error creation. +func FromOptionWithError[A any](err error) func(O.Option[A]) E.Either[error, A] { + return func(opt O.Option[A]) E.Either[error, A] { + return E.FromOption[A](func() error { return err })(opt) + } +} + func handleDBError(err error, notFoundMessage string) error { if errors.Is(err, db.ErrNotFound) { return app_error.NewNotFoundError(notFoundMessage) @@ -19,3 +39,17 @@ func NullableField[T any](getter func() (T, bool)) *T { } return nil } + +// GenerateSecureHash generates a SHA-256 hash for the given input string. +func GenerateSecureHash(input string) string { + // Convert the input string to bytes + bytes := []byte(input) + + // Compute the SHA-256 hash + hash := sha256.Sum256(bytes) + + // Convert the hash to a hexadecimal string + hashHex := hex.EncodeToString(hash[:]) + + return hashHex +} diff --git a/backend-go/internal/infrastructure/data/mapper.go b/backend-go/internal/infrastructure/data/mapper.go index 0735f0b..fb690fd 100644 --- a/backend-go/internal/infrastructure/data/mapper.go +++ b/backend-go/internal/infrastructure/data/mapper.go @@ -17,15 +17,6 @@ func mapPrismaUserToDomain(user db.UserDboModel) entities.User { } } -// Maps a slice of Prisma User models to domain User models. -func mapPrismaUsersToDomain(users []db.UserDboModel) []entities.User { - domainUsers := make([]entities.User, len(users)) - for i, user := range users { - domainUsers[i] = mapPrismaUserToDomain(user) - } - return domainUsers -} - func mapPrismaProjectToDomain(project db.ProjectDboModel) entities.Project { return entities.Project{ ID: project.ID, @@ -38,13 +29,6 @@ func mapPrismaProjectToDomain(project db.ProjectDboModel) entities.Project { } } -func mapPrismaProjectsToDomain(projects []db.ProjectDboModel) []entities.Project { - domainProjects := make([]entities.Project, len(projects)) - for i, project := range projects { - domainProjects[i] = mapPrismaProjectToDomain(project) - } - return domainProjects -} func mapPrismaTimeEntryToDomain(timeEntry db.TimeEntryDboModel) entities.TimeEntry { return entities.TimeEntry{ @@ -59,13 +43,6 @@ func mapPrismaTimeEntryToDomain(timeEntry db.TimeEntryDboModel) entities.TimeEnt } } -func mapPrismaTimeEntriesToDomain(timeEntries []db.TimeEntryDboModel) []entities.TimeEntry { - domainTimeEntries := make([]entities.TimeEntry, len(timeEntries)) - for i, timeEntry := range timeEntries { - domainTimeEntries[i] = mapPrismaTimeEntryToDomain(timeEntry) - } - return domainTimeEntries -} func mapPrismaProjectTaskToDomain(projectTask db.ProjectTaskDboModel) entities.ProjectTask { return entities.ProjectTask{ @@ -77,11 +54,3 @@ func mapPrismaProjectTaskToDomain(projectTask db.ProjectTaskDboModel) entities.P UpdatedAt: projectTask.UpdatedAt, } } - -func mapPrismaProjectTasksToDomain(projectTasks []db.ProjectTaskDboModel) []entities.ProjectTask { - domainProjectTasks := make([]entities.ProjectTask, len(projectTasks)) - for i, projectTask := range projectTasks { - domainProjectTasks[i] = mapPrismaProjectTaskToDomain(projectTask) - } - return domainProjectTasks -} diff --git a/backend-go/internal/infrastructure/data/prisma_project_data_source.go b/backend-go/internal/infrastructure/data/prisma_project_data_source.go index 88aab4f..6e85fff 100644 --- a/backend-go/internal/infrastructure/data/prisma_project_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_project_data_source.go @@ -7,7 +7,10 @@ import ( "context" "fmt" + A "github.com/IBM/fp-go/array" E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" ) type PrismaProjectDataSource struct { @@ -33,11 +36,13 @@ func (ds *PrismaProjectDataSource) Create(ctx context.Context, project entities. return E.Left[entities.Project, error](app_error.NewInternalError(err)) } - if createdProject == nil { - return E.Left[entities.Project, error](app_error.NewInternalError(fmt.Errorf("Could not create project"))) - } + return F.Pipe3( + O.FromNillable(createdProject), + Dereference, + FromOptionWithError[db.ProjectDboModel](app_error.NewInternalError(fmt.Errorf("Could not create project"))), + E.Map[error](mapPrismaProjectToDomain), + ) - return E.Right[error](mapPrismaProjectToDomain(*createdProject)) } // FindByID retrieves a project by ID @@ -50,11 +55,12 @@ func (ds *PrismaProjectDataSource) FindByID(ctx context.Context, id string) E.Ei return E.Left[entities.Project](handleDBError(err, fmt.Sprintf("Project with ID %s not found", id))) } - if project == nil { - return E.Left[entities.Project, error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) - } - - return E.Right[error](mapPrismaProjectToDomain(*project)) + return F.Pipe3( + O.FromNillable(project), + Dereference, + FromOptionWithError[db.ProjectDboModel](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))), + E.Map[error](mapPrismaProjectToDomain), + ) } // Update updates a project @@ -75,11 +81,12 @@ func (ds *PrismaProjectDataSource) Update(ctx context.Context, project entities. return E.Left[entities.Project](handleDBError(err, fmt.Sprintf("Could not update project with ID %s", project.ID))) } - if updatedProject == nil { - return E.Left[entities.Project, error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", project.ID))) - } - - return E.Right[error](mapPrismaProjectToDomain(*updatedProject)) + return F.Pipe3( + O.FromNillable(updatedProject), + Dereference, + FromOptionWithError[db.ProjectDboModel](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", project.ID))), + E.Map[error](mapPrismaProjectToDomain), + ) } // Delete removes a project @@ -92,11 +99,12 @@ func (ds *PrismaProjectDataSource) Delete(ctx context.Context, id string) E.Eith return E.Left[entities.Project](handleDBError(err, fmt.Sprintf("Could not delete project with ID %s", id))) } - if deleted == nil { - return E.Left[entities.Project, error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) - } - - return E.Right[error](mapPrismaProjectToDomain(*deleted)) + return F.Pipe3( + O.FromNillable(deleted), + Dereference, + FromOptionWithError[db.ProjectDboModel](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))), + E.Map[error](mapPrismaProjectToDomain), + ) } // FindAll retrieves all projects @@ -106,7 +114,11 @@ func (ds *PrismaProjectDataSource) FindAll(ctx context.Context) E.Either[error, return E.Left[[]entities.Project](handleDBError(err, "Could not retrieve projects")) } - return E.Right[error](mapPrismaProjectsToDomain(projects)) + return F.Pipe2( + projects, + A.Map(mapPrismaProjectToDomain), + E.Right[error], + ) } // FindByUserID retrieves all projects for a user @@ -118,5 +130,9 @@ func (ds *PrismaProjectDataSource) FindByUserID(ctx context.Context, userID stri return E.Left[[]entities.Project](handleDBError(err, fmt.Sprintf("Could not retrieve projects for user with ID %s", userID))) } - return E.Right[error](mapPrismaProjectsToDomain(projects)) + return F.Pipe2( + projects, + A.Map(mapPrismaProjectToDomain), + E.Right[error], + ) } diff --git a/backend-go/internal/infrastructure/data/prisma_project_task_data_source.go b/backend-go/internal/infrastructure/data/prisma_project_task_data_source.go index 7e5dbaf..41ea922 100644 --- a/backend-go/internal/infrastructure/data/prisma_project_task_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_project_task_data_source.go @@ -7,7 +7,10 @@ import ( "context" "fmt" + A "github.com/IBM/fp-go/array" E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" ) type PrismaProjectTaskDataSource struct { @@ -32,11 +35,12 @@ func (ds *PrismaProjectTaskDataSource) Create(ctx context.Context, task entities return E.Left[entities.ProjectTask, error](handleDBError(err, fmt.Sprintf("Could not create project task"))) } - if createdTask == nil { - return E.Left[entities.ProjectTask, error](app_error.NewInternalError(fmt.Errorf("Could not create project task"))) - } - - return E.Right[error](mapPrismaProjectTaskToDomain(*createdTask)) + return F.Pipe3( + O.FromNillable(createdTask), + Dereference, + FromOptionWithError[db.ProjectTaskDboModel](app_error.NewInternalError(fmt.Errorf("Could not create project task"))), + E.Map[error](mapPrismaProjectTaskToDomain), + ) } // Find ProjectTask by ID @@ -49,11 +53,12 @@ func (ds *PrismaProjectTaskDataSource) FindByID(ctx context.Context, id string) return E.Left[entities.ProjectTask](handleDBError(err, fmt.Sprintf("ProjectTask with ID %s not found", id))) } - if task == nil { - return E.Left[entities.ProjectTask, error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) - } - - return E.Right[error](mapPrismaProjectTaskToDomain(*task)) + return F.Pipe3( + O.FromNillable(task), + Dereference, + FromOptionWithError[db.ProjectTaskDboModel](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))), + E.Map[error](mapPrismaProjectTaskToDomain), + ) } // Update an existing ProjectTask @@ -72,11 +77,12 @@ func (ds *PrismaProjectTaskDataSource) Update(ctx context.Context, task entities return E.Left[entities.ProjectTask](handleDBError(err, fmt.Sprintf("Could not update project task with ID %s", task.ID))) } - if updatedTask == nil { - return E.Left[entities.ProjectTask, error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", task.ID))) - } - - return E.Right[error](mapPrismaProjectTaskToDomain(*updatedTask)) + return F.Pipe3( + O.FromNillable(updatedTask), + Dereference, + FromOptionWithError[db.ProjectTaskDboModel](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", task.ID))), + E.Map[error](mapPrismaProjectTaskToDomain), + ) } // Delete a ProjectTask @@ -89,11 +95,12 @@ func (ds *PrismaProjectTaskDataSource) Delete(ctx context.Context, id string) E. return E.Left[entities.ProjectTask](handleDBError(err, fmt.Sprintf("Could not delete project task with ID %s", id))) } - if deletedTask == nil { - return E.Left[entities.ProjectTask, error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) - } - - return E.Right[error](mapPrismaProjectTaskToDomain(*deletedTask)) + return F.Pipe3( + O.FromNillable(deletedTask), + Dereference, + FromOptionWithError[db.ProjectTaskDboModel](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))), + E.Map[error](mapPrismaProjectTaskToDomain), + ) } // FindAll retrieves all ProjectTasks @@ -103,7 +110,11 @@ func (ds *PrismaProjectTaskDataSource) FindAll(ctx context.Context) E.Either[err return E.Left[[]entities.ProjectTask](handleDBError(err, "Could not retrieve project tasks")) } - return E.Right[error](mapPrismaProjectTasksToDomain(tasks)) + return F.Pipe2( + tasks, + A.Map(mapPrismaProjectTaskToDomain), + E.Right[error], + ) } // FindByProjectID retrieves all ProjectTasks for a given Project @@ -115,5 +126,9 @@ func (ds *PrismaProjectTaskDataSource) FindByProjectID(ctx context.Context, proj return E.Left[[]entities.ProjectTask](handleDBError(err, fmt.Sprintf("Could not retrieve project tasks for project with ID %s", projectID))) } - return E.Right[error](mapPrismaProjectTasksToDomain(tasks)) + return F.Pipe2( + tasks, + A.Map(mapPrismaProjectTaskToDomain), + E.Right[error], + ) } diff --git a/backend-go/internal/infrastructure/data/prisma_time_entries_data_source.go b/backend-go/internal/infrastructure/data/prisma_time_entries_data_source.go index 367e170..12820e1 100644 --- a/backend-go/internal/infrastructure/data/prisma_time_entries_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_time_entries_data_source.go @@ -7,7 +7,10 @@ import ( "context" "fmt" + A "github.com/IBM/fp-go/array" E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" ) type PrismaTimeEntryDataSource struct { @@ -35,11 +38,12 @@ func (ds *PrismaTimeEntryDataSource) Create(ctx context.Context, entry entities. return E.Left[entities.TimeEntry, error](app_error.NewInternalError(err)) } - if createdEntry == nil { - return E.Left[entities.TimeEntry, error](app_error.NewInternalError(fmt.Errorf("Could not create time entry"))) - } - - return E.Right[error](mapPrismaTimeEntryToDomain(*createdEntry)) + return F.Pipe3( + O.FromNillable(createdEntry), + Dereference, + FromOptionWithError[db.TimeEntryDboModel](app_error.NewInternalError(fmt.Errorf("Could not create time entry"))), + E.Map[error](mapPrismaTimeEntryToDomain), + ) } // Find TimeEntry by ID @@ -52,11 +56,12 @@ func (ds *PrismaTimeEntryDataSource) FindByID(ctx context.Context, id string) E. return E.Left[entities.TimeEntry](handleDBError(err, fmt.Sprintf("TimeEntry with ID %s not found", id))) } - if entry == nil { - return E.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) - } - - return E.Right[error](mapPrismaTimeEntryToDomain(*entry)) + return F.Pipe3( + O.FromNillable(entry), + Dereference, + FromOptionWithError[db.TimeEntryDboModel](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))), + E.Map[error](mapPrismaTimeEntryToDomain), + ) } // Update an existing TimeEntry @@ -79,11 +84,12 @@ func (ds *PrismaTimeEntryDataSource) Update(ctx context.Context, entry entities. return E.Left[entities.TimeEntry, error](handleDBError(err, fmt.Sprintf("Could not update time entry with ID %s", entry.ID))) } - if updatedEntry == nil { - return E.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", entry.ID))) - } - - return E.Right[error](mapPrismaTimeEntryToDomain(*updatedEntry)) + return F.Pipe3( + O.FromNillable(updatedEntry), + Dereference, + FromOptionWithError[db.TimeEntryDboModel](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", entry.ID))), + E.Map[error](mapPrismaTimeEntryToDomain), + ) } // Delete a TimeEntry @@ -96,11 +102,12 @@ func (ds *PrismaTimeEntryDataSource) Delete(ctx context.Context, id string) E.Ei return E.Left[entities.TimeEntry](handleDBError(err, fmt.Sprintf("Could not delete time entry with ID %s", id))) } - if deletedEntry == nil { - return E.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) - } - - return E.Right[error](mapPrismaTimeEntryToDomain(*deletedEntry)) + return F.Pipe3( + O.FromNillable(deletedEntry), + Dereference, + FromOptionWithError[db.TimeEntryDboModel](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))), + E.Map[error](mapPrismaTimeEntryToDomain), + ) } // FindAll retrieves all TimeEntries @@ -110,7 +117,11 @@ func (ds *PrismaTimeEntryDataSource) FindAll(ctx context.Context) E.Either[error return E.Left[[]entities.TimeEntry](handleDBError(err, "Could not retrieve time entries")) } - return E.Right[error](mapPrismaTimeEntriesToDomain(entries)) + return F.Pipe2( + entries, + A.Map(mapPrismaTimeEntryToDomain), + E.Right[error], + ) } // FindByUserID retrieves all TimeEntries by UserID @@ -122,7 +133,11 @@ func (ds *PrismaTimeEntryDataSource) FindByUserID(ctx context.Context, userID st return E.Left[[]entities.TimeEntry](handleDBError(err, fmt.Sprintf("Could not retrieve time entries for user with ID %s", userID))) } - return E.Right[error](mapPrismaTimeEntriesToDomain(entries)) + return F.Pipe2( + entries, + A.Map(mapPrismaTimeEntryToDomain), + E.Right[error], + ) } // FindByProjectID retrieves all TimeEntries by ProjectID @@ -134,5 +149,9 @@ func (ds *PrismaTimeEntryDataSource) FindByProjectID(ctx context.Context, projec return E.Left[[]entities.TimeEntry](handleDBError(err, fmt.Sprintf("Could not retrieve time entries for project with ID %s", projectID))) } - return E.Right[error](mapPrismaTimeEntriesToDomain(entries)) + return F.Pipe2( + entries, + A.Map(mapPrismaTimeEntryToDomain), + E.Right[error], + ) } diff --git a/backend-go/internal/infrastructure/data/prisma_user_data_source.go b/backend-go/internal/infrastructure/data/prisma_user_data_source.go index 379ec04..a4fad0d 100644 --- a/backend-go/internal/infrastructure/data/prisma_user_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_user_data_source.go @@ -7,7 +7,10 @@ import ( "context" "fmt" + A "github.com/IBM/fp-go/array" E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" ) type PrismaUserDataSource struct { @@ -28,10 +31,13 @@ func (ds *PrismaUserDataSource) Create(ctx context.Context, user entities.UserCr if err != nil { return E.Left[entities.User, error](app_error.NewInternalError(err)) } - if createdUser == nil { - return E.Left[entities.User, error](app_error.NewInternalError(fmt.Errorf("Could not create user"))) - } - return E.Right[error](mapPrismaUserToDomain(*createdUser)) + + return F.Pipe3( + O.FromNillable(createdUser), + Dereference, + FromOptionWithError[db.UserDboModel](app_error.NewInternalError(fmt.Errorf("Could not create user"))), + E.Map[error](mapPrismaUserToDomain), + ) } func (ds *PrismaUserDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.User] { @@ -43,11 +49,12 @@ func (ds *PrismaUserDataSource) FindByID(ctx context.Context, id string) E.Eithe return E.Left[entities.User, error](handleDBError(err, fmt.Sprintf("Query for user with ID %s failed", id))) } - if user == nil { - return E.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", id))) - } - - return E.Right[error](mapPrismaUserToDomain(*user)) + return F.Pipe3( + O.FromNillable(user), + Dereference, + FromOptionWithError[db.UserDboModel](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", user.ID))), + E.Map[error](mapPrismaUserToDomain), + ) } func (ds *PrismaUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error, entities.User] { @@ -60,11 +67,12 @@ func (ds *PrismaUserDataSource) FindByEmail(ctx context.Context, email string) E } - if user == nil { - return E.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with email %s not found", email))) - } - - return E.Right[error](mapPrismaUserToDomain(*user)) + return F.Pipe3( + O.FromNillable(user), + Dereference, + FromOptionWithError[db.UserDboModel](app_error.NewNotFoundError(fmt.Sprintf("User with email %s not found", email))), + E.Map[error](mapPrismaUserToDomain), + ) } func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error, entities.User] { @@ -81,11 +89,12 @@ func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUp return E.Left[entities.User, error](handleDBError(err, fmt.Sprintf("Could not update user with ID %s", user.ID))) } - if updatedUser == nil { - return E.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", user.ID))) - } - - return E.Right[error](mapPrismaUserToDomain(*updatedUser)) + return F.Pipe3( + O.FromNillable(updatedUser), + Dereference, + FromOptionWithError[db.UserDboModel](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", user.ID))), + E.Map[error](mapPrismaUserToDomain), + ) } func (ds *PrismaUserDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.User] { @@ -97,11 +106,12 @@ func (ds *PrismaUserDataSource) Delete(ctx context.Context, id string) E.Either[ return E.Left[entities.User, error](handleDBError(err, fmt.Sprintf("Could not delete user with ID %s", id))) } - if deleted == nil { - return E.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", id))) - } - - return E.Right[error](mapPrismaUserToDomain(*deleted)) + return F.Pipe3( + O.FromNillable(deleted), + Dereference, + FromOptionWithError[db.UserDboModel](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", id))), + E.Map[error](mapPrismaUserToDomain), + ) } func (ds *PrismaUserDataSource) FindAll(ctx context.Context) E.Either[error, []entities.User] { @@ -110,5 +120,9 @@ func (ds *PrismaUserDataSource) FindAll(ctx context.Context) E.Either[error, []e return E.Left[[]entities.User, error](handleDBError(err, "Could not retrieve users")) } - return E.Right[error](mapPrismaUsersToDomain(users)) + return F.Pipe2( + users, + A.Map(mapPrismaUserToDomain), + E.Right[error], + ) }