From aca98554d0f5f21da8629a586bf40a34ce2608ab Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Fri, 3 Jan 2025 16:18:25 +0000 Subject: [PATCH] completed go backend --- backend-dart/lib/interfaces/http/router.dart | 8 +- backend-go/cmd/actatempus/main.go | 26 +++- .../internal/application/repository/helper.go | 2 +- .../repository/project_repository_impl.go | 22 +-- .../project_task_repository_impl.go | 26 ++-- .../repository/time_entry_repository_impl.go | 38 ++--- .../repository/user_repository_impl.go | 18 ++- .../internal/application/services/helpers.go | 26 +++- .../application/services/project_service.go | 109 ++++++++++++++ .../services/project_task_service.go | 123 ++++++++++++++++ .../services/time_entry_service.go | 137 ++++++++++++++++++ backend-go/internal/domain/data/database.go | 8 + .../domain/repository/project_repository.go | 13 +- .../repository/project_task_repository.go | 12 +- .../repository/time_entry_repository.go | 14 +- .../internal/infrastructure/data/helper.go | 3 +- .../internal/infrastructure/data/mapper.go | 27 ++-- .../infrastructure/data/primsa_database.go | 2 +- .../data/prisma_project_data_source.go | 13 +- .../data/prisma_project_task_data_source.go | 12 +- .../data/prisma_time_entries_data_source.go | 18 +-- .../data/prisma_user_data_source.go | 35 +++-- backend-go/internal/interfaces/http/server.go | 66 +++++++-- backend-go/run.sh | 2 + 24 files changed, 610 insertions(+), 150 deletions(-) create mode 100644 backend-go/internal/application/services/project_service.go create mode 100644 backend-go/internal/application/services/project_task_service.go create mode 100644 backend-go/internal/application/services/time_entry_service.go create mode 100644 backend-go/internal/domain/data/database.go create mode 100644 backend-go/run.sh diff --git a/backend-dart/lib/interfaces/http/router.dart b/backend-dart/lib/interfaces/http/router.dart index fe68920..4b235a8 100644 --- a/backend-dart/lib/interfaces/http/router.dart +++ b/backend-dart/lib/interfaces/http/router.dart @@ -22,10 +22,10 @@ Router getRouter(ProviderContainer container) { final timeEntryService = container.read(timeEntryServiceProvider); // UserService-Router - router.mount('/users/', userService.router.call); - router.mount('/projects/', projectService.router.call); - router.mount('/project-tasks/', projectTaskService.router.call); - router.mount('/time-entries/', timeEntryService.router.call); + router.mount('/api/users/', userService.router.call); + router.mount('/api/projects/', projectService.router.call); + router.mount('/api/project-tasks/', projectTaskService.router.call); + router.mount('/api/time-entries/', timeEntryService.router.call); return router; } diff --git a/backend-go/cmd/actatempus/main.go b/backend-go/cmd/actatempus/main.go index 4ec72e4..b8c781f 100755 --- a/backend-go/cmd/actatempus/main.go +++ b/backend-go/cmd/actatempus/main.go @@ -1,7 +1,10 @@ package main import ( + "actatempus_backend/internal/application/repository" + "actatempus_backend/internal/application/services" "actatempus_backend/internal/infrastructure/config" + "actatempus_backend/internal/infrastructure/data" "actatempus_backend/internal/interfaces/http" "fmt" "log" @@ -14,8 +17,27 @@ func main() { log.Fatalf("could not load config: %v", err) } - // Starte den HTTP-Server - server := http.NewServer(cfg) + database, err := data.NewPrismaDatabase() + + if err != nil { + log.Fatalf("could not initialize database: %v", err) + } + + // Initialize repositories + userRepo := repository.NewUserRepository(database.Users()) + projectRepo := repository.NewProjectRepository(database.Projects()) + projectTaskRepo := repository.NewProjectTaskRepository(database.ProjectTasks()) + timeEntryRepo := repository.NewTimeEntryRepository(database.TimeEntries()) + + // Initialize services + userService := services.NewUserService(userRepo) + projectService := services.NewProjectService(projectRepo) + projectTaskService := services.NewProjectTaskService(projectTaskRepo) + timeEntryService := services.NewTimeEntryService(timeEntryRepo) + + // Initialize and start the server + server := http.NewServer(cfg, userService, projectService, projectTaskService, timeEntryService) + fmt.Println("Starting ActaTempus server on port 8080...") if err := server.Start(); err != nil { log.Fatalf("server failed to start: %v", err) diff --git a/backend-go/internal/application/repository/helper.go b/backend-go/internal/application/repository/helper.go index c6687c1..4fe8780 100644 --- a/backend-go/internal/application/repository/helper.go +++ b/backend-go/internal/application/repository/helper.go @@ -6,7 +6,7 @@ import ( E "github.com/IBM/fp-go/either" ) -// curried delegiert eine Methode mit Kontext und gibt eine Funktion zurück. +// curried is a helper function to simplify currying by taking a context and returning a function. func curried[T any, R any](ctx context.Context, fn func(context.Context, T) E.Either[error, R]) func(T) E.Either[error, R] { return func(input T) E.Either[error, R] { return fn(ctx, input) diff --git a/backend-go/internal/application/repository/project_repository_impl.go b/backend-go/internal/application/repository/project_repository_impl.go index 419cfd7..aaad895 100644 --- a/backend-go/internal/application/repository/project_repository_impl.go +++ b/backend-go/internal/application/repository/project_repository_impl.go @@ -20,28 +20,28 @@ func NewProjectRepository(dataSource data.ProjectDataSource) repository.ProjectR } // Create delegates the creation of a project to the data source. -func (r *ProjectRepositoryImpl) Create(ctx context.Context, project entities.ProjectCreate) E.Either[error, entities.Project] { - return r.dataSource.Create(ctx, project) +func (r *ProjectRepositoryImpl) Create(ctx context.Context) func(project entities.ProjectCreate) E.Either[error, entities.Project] { + return curried(ctx, r.dataSource.Create) } // FindByID delegates fetching a project by ID to the data source. -func (r *ProjectRepositoryImpl) FindByID(ctx context.Context, id string) E.Either[error, entities.Project] { - return r.dataSource.FindByID(ctx, id) +func (r *ProjectRepositoryImpl) FindByID(ctx context.Context) func(id string) E.Either[error, entities.Project] { + return curried(ctx, r.dataSource.FindByID) } -// FindByUserID delegates fetching all projects for a user to the data source. -func (r *ProjectRepositoryImpl) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.Project] { - return r.dataSource.FindByUserID(ctx, userID) +// FindByUserID delegates fetching projects by user ID to the data source. +func (r *ProjectRepositoryImpl) FindByUserID(ctx context.Context) func(userID string) E.Either[error, []entities.Project] { + return curried(ctx, r.dataSource.FindByUserID) } // Update delegates updating a project to the data source. -func (r *ProjectRepositoryImpl) Update(ctx context.Context, project entities.ProjectUpdate) E.Either[error, entities.Project] { - return r.dataSource.Update(ctx, project) +func (r *ProjectRepositoryImpl) Update(ctx context.Context) func(project entities.ProjectUpdate) E.Either[error, entities.Project] { + return curried(ctx, r.dataSource.Update) } // Delete delegates deleting a project to the data source. -func (r *ProjectRepositoryImpl) Delete(ctx context.Context, id string) E.Either[error, entities.Project] { - return r.dataSource.Delete(ctx, id) +func (r *ProjectRepositoryImpl) Delete(ctx context.Context) func(id string) E.Either[error, entities.Project] { + return curried(ctx, r.dataSource.Delete) } // FindAll delegates fetching all projects to the data source. diff --git a/backend-go/internal/application/repository/project_task_repository_impl.go b/backend-go/internal/application/repository/project_task_repository_impl.go index a100352..8637980 100644 --- a/backend-go/internal/application/repository/project_task_repository_impl.go +++ b/backend-go/internal/application/repository/project_task_repository_impl.go @@ -13,33 +13,35 @@ import ( type ProjectTaskRepositoryImpl struct { dataSource data.ProjectTaskDataSource } + // NewProjectTaskRepository creates a new instance of ProjectTaskRepositoryImpl. func NewProjectTaskRepository(dataSource data.ProjectTaskDataSource) repository.ProjectTaskRepository { return &ProjectTaskRepositoryImpl{dataSource: dataSource} } -// FindByProjectID implements repository.ProjectTaskRepository. -func (r *ProjectTaskRepositoryImpl) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.ProjectTask] { - return r.dataSource.FindByProjectID(ctx, projectID) -} // Create delegates the creation of a project task to the data source. -func (r *ProjectTaskRepositoryImpl) Create(ctx context.Context, task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] { - return r.dataSource.Create(ctx, task) +func (r *ProjectTaskRepositoryImpl) Create(ctx context.Context) func(task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] { + return curried(ctx, r.dataSource.Create) } // FindByID delegates fetching a project task by ID to the data source. -func (r *ProjectTaskRepositoryImpl) FindByID(ctx context.Context, id string) E.Either[error, entities.ProjectTask] { - return r.dataSource.FindByID(ctx, id) +func (r *ProjectTaskRepositoryImpl) FindByID(ctx context.Context) func(id string) E.Either[error, entities.ProjectTask] { + return curried(ctx, r.dataSource.FindByID) +} + +// FindByProjectID delegates fetching project tasks by project ID to the data source. +func (r *ProjectTaskRepositoryImpl) FindByProjectID(ctx context.Context) func(projectID string) E.Either[error, []entities.ProjectTask] { + return curried(ctx, r.dataSource.FindByProjectID) } // Update delegates updating a project task to the data source. -func (r *ProjectTaskRepositoryImpl) Update(ctx context.Context, task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] { - return r.dataSource.Update(ctx, task) +func (r *ProjectTaskRepositoryImpl) Update(ctx context.Context) func(task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] { + return curried(ctx, r.dataSource.Update) } // Delete delegates deleting a project task to the data source. -func (r *ProjectTaskRepositoryImpl) Delete(ctx context.Context, id string) E.Either[error, entities.ProjectTask] { - return r.dataSource.Delete(ctx, id) +func (r *ProjectTaskRepositoryImpl) Delete(ctx context.Context) func(id string) E.Either[error, entities.ProjectTask] { + return curried(ctx, r.dataSource.Delete) } // FindAll delegates fetching all project tasks to the data source. diff --git a/backend-go/internal/application/repository/time_entry_repository_impl.go b/backend-go/internal/application/repository/time_entry_repository_impl.go index ea74f03..e6857a9 100644 --- a/backend-go/internal/application/repository/time_entry_repository_impl.go +++ b/backend-go/internal/application/repository/time_entry_repository_impl.go @@ -19,37 +19,37 @@ func NewTimeEntryRepository(dataSource data.TimeEntryDataSource) repository.Time return &TimeEntryRepositoryImpl{dataSource: dataSource} } -// FindByProjectID implements repository.TimeEntryRepository. -func (r *TimeEntryRepositoryImpl) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.TimeEntry] { - return r.dataSource.FindByProjectID(ctx, projectID) +// Create delegates the creation of a TimeEntry to the data source. +func (r *TimeEntryRepositoryImpl) Create(ctx context.Context) func(entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] { + return curried(ctx, r.dataSource.Create) } -// FindByUserID implements repository.TimeEntryRepository. -func (r *TimeEntryRepositoryImpl) FindByUserID(ctx context.Context, userID string) E.Either[error, []entities.TimeEntry] { - return r.dataSource.FindByUserID(ctx, userID) +// FindByID delegates fetching a TimeEntry by ID to the data source. +func (r *TimeEntryRepositoryImpl) FindByID(ctx context.Context) func(id string) E.Either[error, entities.TimeEntry] { + return curried(ctx, r.dataSource.FindByID) } -// Create delegates the creation of a time entry to the data source. -func (r *TimeEntryRepositoryImpl) Create(ctx context.Context, entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] { - return r.dataSource.Create(ctx, entry) +// FindByUserID delegates fetching TimeEntries by UserID to the data source. +func (r *TimeEntryRepositoryImpl) FindByUserID(ctx context.Context) func(userID string) E.Either[error, []entities.TimeEntry] { + return curried(ctx, r.dataSource.FindByUserID) } -// FindByID delegates fetching a time entry by ID to the data source. -func (r *TimeEntryRepositoryImpl) FindByID(ctx context.Context, id string) E.Either[error, entities.TimeEntry] { - return r.dataSource.FindByID(ctx, id) +// FindByProjectID delegates fetching TimeEntries by ProjectID to the data source. +func (r *TimeEntryRepositoryImpl) FindByProjectID(ctx context.Context) func(projectID string) E.Either[error, []entities.TimeEntry] { + return curried(ctx, r.dataSource.FindByProjectID) } -// Update delegates updating a time entry to the data source. -func (r *TimeEntryRepositoryImpl) Update(ctx context.Context, entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] { - return r.dataSource.Update(ctx, entry) +// Update delegates updating a TimeEntry to the data source. +func (r *TimeEntryRepositoryImpl) Update(ctx context.Context) func(entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] { + return curried(ctx, r.dataSource.Update) } -// Delete delegates deleting a time entry to the data source. -func (r *TimeEntryRepositoryImpl) Delete(ctx context.Context, id string) E.Either[error, entities.TimeEntry] { - return r.dataSource.Delete(ctx, id) +// Delete delegates deleting a TimeEntry to the data source. +func (r *TimeEntryRepositoryImpl) Delete(ctx context.Context) func(id string) E.Either[error, entities.TimeEntry] { + return curried(ctx, r.dataSource.Delete) } -// FindAll delegates fetching all time entries to the data source. +// FindAll delegates fetching all TimeEntries to the data source. func (r *TimeEntryRepositoryImpl) FindAll(ctx context.Context) E.Either[error, []entities.TimeEntry] { return r.dataSource.FindAll(ctx) } diff --git a/backend-go/internal/application/repository/user_repository_impl.go b/backend-go/internal/application/repository/user_repository_impl.go index 86d658e..68b381e 100644 --- a/backend-go/internal/application/repository/user_repository_impl.go +++ b/backend-go/internal/application/repository/user_repository_impl.go @@ -3,6 +3,7 @@ package repository import ( "actatempus_backend/internal/domain/data" "actatempus_backend/internal/domain/entities" + "actatempus_backend/internal/domain/repository" "context" E "github.com/IBM/fp-go/either" @@ -13,32 +14,37 @@ type UserRepositoryImpl struct { dataSource data.UserDataSource } -// Create delegiert die Erstellung eines Benutzers an die Datenquelle. +// NewUserRepository creates a new instance of UserRepositoryImpl. +func NewUserRepository(dataSource data.UserDataSource) repository.UserRepository { + return &UserRepositoryImpl{dataSource: dataSource} +} + +// Create delegates the creation of a user to the data source. func (r *UserRepositoryImpl) Create(ctx context.Context) func(user entities.UserCreate) E.Either[error, entities.User] { return curried(ctx, r.dataSource.Create) } -// FindByID delegiert das Abrufen eines Benutzers nach ID an die Datenquelle. +// FindByID delegates fetching a user by ID to the data source. func (r *UserRepositoryImpl) FindByID(ctx context.Context) func(id string) E.Either[error, entities.User] { return curried(ctx, r.dataSource.FindByID) } -// FindByEmail delegiert das Abrufen eines Benutzers nach E-Mail an die Datenquelle. +// FindByEmail delegates fetching a user by email to the data source. func (r *UserRepositoryImpl) FindByEmail(ctx context.Context) func(email string) E.Either[error, entities.User] { return curried(ctx, r.dataSource.FindByEmail) } -// Update delegiert das Aktualisieren eines Benutzers an die Datenquelle. +// Update delegates updating a user to the data source. func (r *UserRepositoryImpl) Update(ctx context.Context) func(user entities.UserUpdate) E.Either[error, entities.User] { return curried(ctx, r.dataSource.Update) } -// Delete delegiert das Löschen eines Benutzers an die Datenquelle. +// Delete delegates deleting a user to the data source. func (r *UserRepositoryImpl) Delete(ctx context.Context) func(id string) E.Either[error, entities.User] { return curried(ctx, r.dataSource.Delete) } -// FindAll delegiert das Abrufen aller Benutzer an die Datenquelle. +// FindAll delegates fetching all users to the data source. func (r *UserRepositoryImpl) FindAll(ctx context.Context) E.Either[error, []entities.User] { return r.dataSource.FindAll(ctx) } diff --git a/backend-go/internal/application/services/helpers.go b/backend-go/internal/application/services/helpers.go index 6f34f1e..efeb493 100644 --- a/backend-go/internal/application/services/helpers.go +++ b/backend-go/internal/application/services/helpers.go @@ -1,21 +1,37 @@ package services import ( + "actatempus_backend/internal/domain/app_error" "net/http" "github.com/gin-gonic/gin" ) +// HandleError handles errors by formatting them as JSON. func HandleError(c *gin.Context) func(error) any { return func(err error) any { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return nil + // Check if the error is of type *AppError + if appErr, ok := err.(*app_error.AppError); ok { + // Use the AppError fields for the JSON response + c.JSON(appErr.Status, gin.H{ + "code": appErr.Code, + "message": appErr.Message, + "details": appErr.Err.Error(), // Original error if available + }) + } else { + // Fallback for non-AppError errors + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + } + + return nil } } func HandleSuccess[T any](c *gin.Context, statusCode int) func(T) any { return func(data T) any { - c.JSON(statusCode, data) - return nil + c.JSON(statusCode, data) + return nil } -} \ No newline at end of file +} diff --git a/backend-go/internal/application/services/project_service.go b/backend-go/internal/application/services/project_service.go new file mode 100644 index 0000000..71b5edb --- /dev/null +++ b/backend-go/internal/application/services/project_service.go @@ -0,0 +1,109 @@ +package services + +import ( + "actatempus_backend/internal/application/services/dto" + mappers "actatempus_backend/internal/application/services/mapper" + "actatempus_backend/internal/domain/repository" + "net/http" + + A "github.com/IBM/fp-go/array" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + "github.com/gin-gonic/gin" +) + +// ProjectService handles project-related HTTP requests. +type ProjectService struct { + repository repository.ProjectRepository +} + +// NewProjectService creates a new instance of ProjectService. +func NewProjectService(repo repository.ProjectRepository) *ProjectService { + return &ProjectService{repository: repo} +} + +// RegisterRoutes registers the project-related routes with Gin. +func (s *ProjectService) RegisterRoutes(router *gin.RouterGroup) { + router.POST("/", s.CreateProject) + router.GET("/:id", s.GetProjectByID) + router.GET("/", s.GetAllProjects) + router.PUT("/:id", s.UpdateProject) + router.DELETE("/:id", s.DeleteProject) +} + +// CreateProject handles the creation of a new project. +func (s *ProjectService) CreateProject(c *gin.Context) { + var projectCreateDTO dto.ProjectCreateDTO + if err := c.ShouldBindJSON(&projectCreateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + F.Pipe3( + mappers.MapCreateDTOToProject(projectCreateDTO), + s.repository.Create(c.Request.Context()), + E.Map[error](mappers.MapProjectToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectDTO](c, http.StatusCreated), + ), + ) +} + +// GetProjectByID handles fetching a project by its ID. +func (s *ProjectService) GetProjectByID(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.FindByID(c.Request.Context()), + E.Map[error](mappers.MapProjectToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectDTO](c, http.StatusOK), + ), + ) +} + +// GetAllProjects handles fetching all projects. +func (s *ProjectService) GetAllProjects(c *gin.Context) { + F.Pipe2( + s.repository.FindAll(c.Request.Context()), + E.Map[error](A.Map(mappers.MapProjectToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.ProjectDTO](c, http.StatusOK), + ), + ) +} + +// UpdateProject handles updating an existing project. +func (s *ProjectService) UpdateProject(c *gin.Context) { + id := c.Param("id") + var projectUpdateDTO dto.ProjectUpdateDTO + + if err := c.ShouldBindJSON(&projectUpdateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + F.Pipe3( + mappers.MapUpdateDTOToProject(projectUpdateDTO, id), + s.repository.Update(c.Request.Context()), + E.Map[error](mappers.MapProjectToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectDTO](c, http.StatusOK), + ), + ) +} + +// DeleteProject handles deleting a project by its ID. +func (s *ProjectService) DeleteProject(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.Delete(c.Request.Context()), + E.Map[error](mappers.MapProjectToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectDTO](c, http.StatusOK), + ), + ) +} diff --git a/backend-go/internal/application/services/project_task_service.go b/backend-go/internal/application/services/project_task_service.go new file mode 100644 index 0000000..34080ec --- /dev/null +++ b/backend-go/internal/application/services/project_task_service.go @@ -0,0 +1,123 @@ +package services + +import ( + "actatempus_backend/internal/application/services/dto" + mappers "actatempus_backend/internal/application/services/mapper" + "actatempus_backend/internal/domain/repository" + "net/http" + + A "github.com/IBM/fp-go/array" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + "github.com/gin-gonic/gin" +) + +// ProjectTaskService handles project task-related HTTP requests. +type ProjectTaskService struct { + repository repository.ProjectTaskRepository +} + +// NewProjectTaskService creates a new instance of ProjectTaskService. +func NewProjectTaskService(repo repository.ProjectTaskRepository) *ProjectTaskService { + return &ProjectTaskService{repository: repo} +} + +// RegisterRoutes registers the project task-related routes with Gin. +func (s *ProjectTaskService) RegisterRoutes(router *gin.RouterGroup) { + router.POST("/", s.CreateTask) + router.GET("/:id", s.GetTaskByID) + router.GET("/", s.GetAllTasks) + router.GET("/project/:projectID", s.GetTasksByProjectID) + router.PUT("/:id", s.UpdateTask) + router.DELETE("/:id", s.DeleteTask) +} + +// CreateTask handles the creation of a new project task. +func (s *ProjectTaskService) CreateTask(c *gin.Context) { + var taskCreateDTO dto.ProjectTaskCreateDTO + if err := c.ShouldBindJSON(&taskCreateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + F.Pipe3( + mappers.MapCreateDTOToProjectTask(taskCreateDTO), + s.repository.Create(c.Request.Context()), + E.Map[error](mappers.MapProjectTaskToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectTaskDTO](c, http.StatusCreated), + ), + ) +} + +// GetTaskByID handles fetching a project task by its ID. +func (s *ProjectTaskService) GetTaskByID(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.FindByID(c.Request.Context()), + E.Map[error](mappers.MapProjectTaskToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectTaskDTO](c, http.StatusOK), + ), + ) +} + +// GetTasksByProjectID handles fetching project tasks by project ID. +func (s *ProjectTaskService) GetTasksByProjectID(c *gin.Context) { + F.Pipe3( + c.Param("projectID"), + s.repository.FindByProjectID(c.Request.Context()), + E.Map[error](A.Map(mappers.MapProjectTaskToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.ProjectTaskDTO](c, http.StatusOK), + ), + ) +} + +// GetAllTasks handles fetching all project tasks. +func (s *ProjectTaskService) GetAllTasks(c *gin.Context) { + F.Pipe2( + s.repository.FindAll(c.Request.Context()), + E.Map[error](A.Map(mappers.MapProjectTaskToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.ProjectTaskDTO](c, http.StatusOK), + ), + ) +} + +// UpdateTask handles updating an existing project task. +func (s *ProjectTaskService) UpdateTask(c *gin.Context) { + id := c.Param("id") + var taskUpdateDTO dto.ProjectTaskUpdateDTO + + if err := c.ShouldBindJSON(&taskUpdateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + F.Pipe3( + mappers.MapUpdateDTOToProjectTask(taskUpdateDTO, id), + s.repository.Update(c.Request.Context()), + E.Map[error](mappers.MapProjectTaskToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectTaskDTO](c, http.StatusOK), + ), + ) +} + +// DeleteTask handles deleting a project task by its ID and returns the deleted object. +func (s *ProjectTaskService) DeleteTask(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.Delete(c.Request.Context()), + E.Map[error](mappers.MapProjectTaskToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.ProjectTaskDTO](c, http.StatusOK), + ), + ) +} diff --git a/backend-go/internal/application/services/time_entry_service.go b/backend-go/internal/application/services/time_entry_service.go new file mode 100644 index 0000000..3dd47f7 --- /dev/null +++ b/backend-go/internal/application/services/time_entry_service.go @@ -0,0 +1,137 @@ +package services + +import ( + "actatempus_backend/internal/application/services/dto" + mappers "actatempus_backend/internal/application/services/mapper" + "actatempus_backend/internal/domain/repository" + "net/http" + + A "github.com/IBM/fp-go/array" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + "github.com/gin-gonic/gin" +) + +// TimeEntryService handles time entry-related HTTP requests. +type TimeEntryService struct { + repository repository.TimeEntryRepository +} + +// NewTimeEntryService creates a new instance of TimeEntryService. +func NewTimeEntryService(repo repository.TimeEntryRepository) *TimeEntryService { + return &TimeEntryService{repository: repo} +} + +// RegisterRoutes registers the time entry-related routes with Gin. +func (s *TimeEntryService) RegisterRoutes(router *gin.RouterGroup) { + router.POST("/", s.CreateTimeEntry) + router.GET("/:id", s.GetTimeEntryByID) + router.GET("/", s.GetAllTimeEntries) + router.GET("/user/:userID", s.GetTimeEntriesByUserID) + router.GET("/project/:projectID", s.GetTimeEntriesByProjectID) + router.PUT("/:id", s.UpdateTimeEntry) + router.DELETE("/:id", s.DeleteTimeEntry) +} + +// CreateTimeEntry handles the creation of a new time entry. +func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) { + var timeEntryCreateDTO dto.TimeEntryCreateDTO + if err := c.ShouldBindJSON(&timeEntryCreateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + F.Pipe3( + mappers.MapCreateDTOToTimeEntry(timeEntryCreateDTO), + s.repository.Create(c.Request.Context()), + E.Map[error](mappers.MapTimeEntryToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.TimeEntryDTO](c, http.StatusCreated), + ), + ) +} + +// GetTimeEntryByID handles fetching a time entry by its ID. +func (s *TimeEntryService) GetTimeEntryByID(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.FindByID(c.Request.Context()), + E.Map[error](mappers.MapTimeEntryToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} + +// GetTimeEntriesByUserID handles fetching time entries by user ID. +func (s *TimeEntryService) GetTimeEntriesByUserID(c *gin.Context) { + F.Pipe3( + c.Param("userID"), + s.repository.FindByUserID(c.Request.Context()), + E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} + +// GetTimeEntriesByProjectID handles fetching time entries by project ID. +func (s *TimeEntryService) GetTimeEntriesByProjectID(c *gin.Context) { + F.Pipe3( + c.Param("projectID"), + s.repository.FindByProjectID(c.Request.Context()), + E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} + +// GetAllTimeEntries handles fetching all time entries. +func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) { + F.Pipe2( + s.repository.FindAll(c.Request.Context()), + E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), + E.Fold( + HandleError(c), + HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} + +// UpdateTimeEntry handles updating an existing time entry. +func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) { + id := c.Param("id") + var timeEntryUpdateDTO dto.TimeEntryUpdateDTO + + if err := c.ShouldBindJSON(&timeEntryUpdateDTO); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + F.Pipe3( + mappers.MapUpdateDTOToTimeEntry(timeEntryUpdateDTO, id), + s.repository.Update(c.Request.Context()), + E.Map[error](mappers.MapTimeEntryToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} + +// DeleteTimeEntry handles deleting a time entry by its ID and returns the deleted object. +func (s *TimeEntryService) DeleteTimeEntry(c *gin.Context) { + F.Pipe3( + c.Param("id"), + s.repository.Delete(c.Request.Context()), + E.Map[error](mappers.MapTimeEntryToDTO), + E.Fold( + HandleError(c), + HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK), + ), + ) +} diff --git a/backend-go/internal/domain/data/database.go b/backend-go/internal/domain/data/database.go new file mode 100644 index 0000000..64ca9fa --- /dev/null +++ b/backend-go/internal/domain/data/database.go @@ -0,0 +1,8 @@ +package data + +type Database struct { + users UserDataSource + timeEntries TimeEntryDataSource + projectTasks ProjectTaskDataSource + projects ProjectDataSource +} diff --git a/backend-go/internal/domain/repository/project_repository.go b/backend-go/internal/domain/repository/project_repository.go index 6273a1e..0e9963b 100644 --- a/backend-go/internal/domain/repository/project_repository.go +++ b/backend-go/internal/domain/repository/project_repository.go @@ -9,10 +9,11 @@ import ( // ProjectRepository defines the operations for interacting with project data. type ProjectRepository interface { - Create(ctx context.Context, project entities.ProjectCreate) E.Either[error,entities.Project] - FindByID(ctx context.Context, id string) E.Either[error,entities.Project] - FindByUserID(ctx context.Context, userID string) E.Either[error,[]entities.Project] - Update(ctx context.Context, project entities.ProjectUpdate) E.Either[error,entities.Project] - Delete(ctx context.Context, id string) E.Either[error,entities.Project] - FindAll(ctx context.Context) E.Either[error,[]entities.Project] + Create(ctx context.Context) func(project entities.ProjectCreate) E.Either[error, entities.Project] + FindByID(ctx context.Context) func(id string) E.Either[error, entities.Project] + FindByUserID(ctx context.Context) func(userID string) E.Either[error, []entities.Project] + Update(ctx context.Context) func(project entities.ProjectUpdate) E.Either[error, entities.Project] + Delete(ctx context.Context) func(id string) E.Either[error, entities.Project] + FindAll(ctx context.Context) E.Either[error, []entities.Project] } + diff --git a/backend-go/internal/domain/repository/project_task_repository.go b/backend-go/internal/domain/repository/project_task_repository.go index 93e66ec..77a8813 100644 --- a/backend-go/internal/domain/repository/project_task_repository.go +++ b/backend-go/internal/domain/repository/project_task_repository.go @@ -9,10 +9,10 @@ import ( // ProjectTaskRepository defines the operations for interacting with project task data. type ProjectTaskRepository interface { - Create(ctx context.Context, task entities.ProjectTaskCreate) E.Either[error,entities.ProjectTask] - FindByID(ctx context.Context, id string) E.Either[error,entities.ProjectTask] - FindByProjectID(ctx context.Context, projectID string) E.Either[error,[]entities.ProjectTask] - Update(ctx context.Context, task entities.ProjectTaskUpdate) E.Either[error,entities.ProjectTask] - Delete(ctx context.Context, id string) E.Either[error,entities.ProjectTask] - FindAll(ctx context.Context) E.Either[error,[]entities.ProjectTask] + Create(ctx context.Context) func(task entities.ProjectTaskCreate) E.Either[error, entities.ProjectTask] + FindByID(ctx context.Context) func(id string) E.Either[error, entities.ProjectTask] + FindByProjectID(ctx context.Context) func(projectID string) E.Either[error, []entities.ProjectTask] + Update(ctx context.Context) func(task entities.ProjectTaskUpdate) E.Either[error, entities.ProjectTask] + Delete(ctx context.Context) func(id string) E.Either[error, entities.ProjectTask] + FindAll(ctx context.Context) E.Either[error, []entities.ProjectTask] } diff --git a/backend-go/internal/domain/repository/time_entry_repository.go b/backend-go/internal/domain/repository/time_entry_repository.go index 1fe4c34..f82aaee 100644 --- a/backend-go/internal/domain/repository/time_entry_repository.go +++ b/backend-go/internal/domain/repository/time_entry_repository.go @@ -9,11 +9,11 @@ import ( // TimeEntryRepository defines the operations for interacting with time entry data. type TimeEntryRepository interface { - Create(ctx context.Context, entry entities.TimeEntryCreate) E.Either[error,entities.TimeEntry] - FindByID(ctx context.Context, id string) E.Either[error,entities.TimeEntry] - FindByUserID(ctx context.Context, userID string) E.Either[error,[]entities.TimeEntry] - FindByProjectID(ctx context.Context, projectID string) E.Either[error,[]entities.TimeEntry] - Update(ctx context.Context, entry entities.TimeEntryUpdate) E.Either[error,entities.TimeEntry] - Delete(ctx context.Context, id string) E.Either[error,entities.TimeEntry] - FindAll(ctx context.Context) E.Either[error,[]entities.TimeEntry] + Create(ctx context.Context) func(entry entities.TimeEntryCreate) E.Either[error, entities.TimeEntry] + FindByID(ctx context.Context) func(id string) E.Either[error, entities.TimeEntry] + FindByUserID(ctx context.Context) func(userID string) E.Either[error, []entities.TimeEntry] + FindByProjectID(ctx context.Context) func(projectID string) E.Either[error, []entities.TimeEntry] + Update(ctx context.Context) func(entry entities.TimeEntryUpdate) E.Either[error, entities.TimeEntry] + Delete(ctx context.Context) func(id string) E.Either[error, entities.TimeEntry] + FindAll(ctx context.Context) E.Either[error, []entities.TimeEntry] } diff --git a/backend-go/internal/infrastructure/data/helper.go b/backend-go/internal/infrastructure/data/helper.go index 8d8e510..0ec913a 100644 --- a/backend-go/internal/infrastructure/data/helper.go +++ b/backend-go/internal/infrastructure/data/helper.go @@ -13,10 +13,9 @@ func handleDBError(err error, notFoundMessage string) error { return app_error.NewInternalError(err) } - func NullableField[T any](getter func() (T, bool)) *T { if value, ok := getter(); ok { return &value } return nil -} \ No newline at end of file +} diff --git a/backend-go/internal/infrastructure/data/mapper.go b/backend-go/internal/infrastructure/data/mapper.go index d1d910d..0735f0b 100644 --- a/backend-go/internal/infrastructure/data/mapper.go +++ b/backend-go/internal/infrastructure/data/mapper.go @@ -38,7 +38,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 { @@ -49,14 +48,14 @@ func mapPrismaProjectsToDomain(projects []db.ProjectDboModel) []entities.Project func mapPrismaTimeEntryToDomain(timeEntry db.TimeEntryDboModel) entities.TimeEntry { return entities.TimeEntry{ - ID: timeEntry.ID, - ProjectID: timeEntry.ProjectID, - UserID: timeEntry.UserID, - StartTime: timeEntry.StartTime, - EndTime: NullableField(timeEntry.EndTime), + ID: timeEntry.ID, + ProjectID: timeEntry.ProjectID, + UserID: timeEntry.UserID, + StartTime: timeEntry.StartTime, + EndTime: NullableField(timeEntry.EndTime), Description: NullableField(timeEntry.Description), - CreatedAt: timeEntry.CreatedAt, - UpdatedAt: timeEntry.UpdatedAt, + CreatedAt: timeEntry.CreatedAt, + UpdatedAt: timeEntry.UpdatedAt, } } @@ -70,12 +69,12 @@ func mapPrismaTimeEntriesToDomain(timeEntries []db.TimeEntryDboModel) []entities func mapPrismaProjectTaskToDomain(projectTask db.ProjectTaskDboModel) entities.ProjectTask { return entities.ProjectTask{ - ID: projectTask.ID, - ProjectID: projectTask.ProjectID, - Name: projectTask.Name, + ID: projectTask.ID, + ProjectID: projectTask.ProjectID, + Name: projectTask.Name, Description: NullableField(projectTask.Description), - CreatedAt: projectTask.CreatedAt, - UpdatedAt: projectTask.UpdatedAt, + CreatedAt: projectTask.CreatedAt, + UpdatedAt: projectTask.UpdatedAt, } } @@ -85,4 +84,4 @@ func mapPrismaProjectTasksToDomain(projectTasks []db.ProjectTaskDboModel) []enti domainProjectTasks[i] = mapPrismaProjectTaskToDomain(projectTask) } return domainProjectTasks -} \ No newline at end of file +} diff --git a/backend-go/internal/infrastructure/data/primsa_database.go b/backend-go/internal/infrastructure/data/primsa_database.go index e265189..ea4fc5d 100644 --- a/backend-go/internal/infrastructure/data/primsa_database.go +++ b/backend-go/internal/infrastructure/data/primsa_database.go @@ -62,5 +62,5 @@ func (db *PrismaDatabase) Projects() data.ProjectDataSource { // Close releases the Prisma client connection. func (db *PrismaDatabase) Close(ctx context.Context) error { log.Println("Closing database connection") - return db.client.Disconnect(); + return db.client.Disconnect() } 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 81b6789..88aab4f 100644 --- a/backend-go/internal/infrastructure/data/prisma_project_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_project_data_source.go @@ -25,17 +25,16 @@ func (ds *PrismaProjectDataSource) Create(ctx context.Context, project entities. db.ProjectDbo.User.Link( db.UserDbo.ID.Equals(project.UserID), ), - db.ProjectDbo.UserID.Set(project.UserID), db.ProjectDbo.Description.SetIfPresent(project.Description), db.ProjectDbo.ClientID.SetIfPresent(project.ClientID), ).Exec(ctx) if err != nil { - return E.Left[entities.Project,error](app_error.NewInternalError(err)) + 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 E.Left[entities.Project, error](app_error.NewInternalError(fmt.Errorf("Could not create project"))) } return E.Right[error](mapPrismaProjectToDomain(*createdProject)) @@ -52,7 +51,7 @@ func (ds *PrismaProjectDataSource) FindByID(ctx context.Context, id string) E.Ei } if project == nil { - return E.Left[entities.Project,error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) + return E.Left[entities.Project, error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) } return E.Right[error](mapPrismaProjectToDomain(*project)) @@ -77,7 +76,7 @@ func (ds *PrismaProjectDataSource) Update(ctx context.Context, project entities. } if updatedProject == nil { - return E.Left[entities.Project,error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", project.ID))) + 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)) @@ -94,7 +93,7 @@ func (ds *PrismaProjectDataSource) Delete(ctx context.Context, id string) E.Eith } if deleted == nil { - return E.Left[entities.Project,error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) + return E.Left[entities.Project, error](app_error.NewNotFoundError(fmt.Sprintf("Project with ID %s not found", id))) } return E.Right[error](mapPrismaProjectToDomain(*deleted)) @@ -120,4 +119,4 @@ func (ds *PrismaProjectDataSource) FindByUserID(ctx context.Context, userID stri } return E.Right[error](mapPrismaProjectsToDomain(projects)) -} \ No newline at end of file +} 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 5d7dcd9..7e5dbaf 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 @@ -29,11 +29,11 @@ func (ds *PrismaProjectTaskDataSource) Create(ctx context.Context, task entities ).Exec(ctx) if err != nil { - return E.Left[entities.ProjectTask,error](handleDBError(err, fmt.Sprintf("Could not create project task"))) + 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.Left[entities.ProjectTask, error](app_error.NewInternalError(fmt.Errorf("Could not create project task"))) } return E.Right[error](mapPrismaProjectTaskToDomain(*createdTask)) @@ -50,7 +50,7 @@ func (ds *PrismaProjectTaskDataSource) FindByID(ctx context.Context, id string) } if task == nil { - return E.Left[entities.ProjectTask,error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) + return E.Left[entities.ProjectTask, error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) } return E.Right[error](mapPrismaProjectTaskToDomain(*task)) @@ -73,7 +73,7 @@ func (ds *PrismaProjectTaskDataSource) Update(ctx context.Context, task entities } if updatedTask == nil { - return E.Left[entities.ProjectTask,error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", task.ID))) + 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)) @@ -90,7 +90,7 @@ func (ds *PrismaProjectTaskDataSource) Delete(ctx context.Context, id string) E. } if deletedTask == nil { - return E.Left[entities.ProjectTask,error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) + return E.Left[entities.ProjectTask, error](app_error.NewNotFoundError(fmt.Sprintf("ProjectTask with ID %s not found", id))) } return E.Right[error](mapPrismaProjectTaskToDomain(*deletedTask)) @@ -116,4 +116,4 @@ func (ds *PrismaProjectTaskDataSource) FindByProjectID(ctx context.Context, proj } return E.Right[error](mapPrismaProjectTasksToDomain(tasks)) -} \ No newline at end of file +} 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 876902b..367e170 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 @@ -24,20 +24,19 @@ func (ds *PrismaTimeEntryDataSource) Create(ctx context.Context, entry entities. db.TimeEntryDbo.StartTime.Set(entry.StartTime), db.TimeEntryDbo.User.Link( db.UserDbo.ID.Equals(entry.UserID), - ), db.TimeEntryDbo.Project.Link( + ), db.TimeEntryDbo.Project.Link( db.ProjectDbo.ID.Equals(entry.ProjectID), ), db.TimeEntryDbo.EndTime.SetIfPresent(entry.EndTime), db.TimeEntryDbo.Description.SetIfPresent(entry.Description), - ).Exec(ctx) if err != nil { - return E.Left[entities.TimeEntry,error](app_error.NewInternalError(err)) + 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.Left[entities.TimeEntry, error](app_error.NewInternalError(fmt.Errorf("Could not create time entry"))) } return E.Right[error](mapPrismaTimeEntryToDomain(*createdEntry)) @@ -54,7 +53,7 @@ func (ds *PrismaTimeEntryDataSource) FindByID(ctx context.Context, id string) E. } if entry == nil { - return E.Left[entities.TimeEntry,error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) + return E.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) } return E.Right[error](mapPrismaTimeEntryToDomain(*entry)) @@ -77,11 +76,11 @@ func (ds *PrismaTimeEntryDataSource) Update(ctx context.Context, entry entities. ).Exec(ctx) if err != nil { - return E.Left[entities.TimeEntry,error](handleDBError(err, fmt.Sprintf("Could not update time entry with ID %s", entry.ID))) + 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.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", entry.ID))) } return E.Right[error](mapPrismaTimeEntryToDomain(*updatedEntry)) @@ -98,7 +97,7 @@ func (ds *PrismaTimeEntryDataSource) Delete(ctx context.Context, id string) E.Ei } if deletedEntry == nil { - return E.Left[entities.TimeEntry,error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) + return E.Left[entities.TimeEntry, error](app_error.NewNotFoundError(fmt.Sprintf("TimeEntry with ID %s not found", id))) } return E.Right[error](mapPrismaTimeEntryToDomain(*deletedEntry)) @@ -126,7 +125,6 @@ func (ds *PrismaTimeEntryDataSource) FindByUserID(ctx context.Context, userID st return E.Right[error](mapPrismaTimeEntriesToDomain(entries)) } - // FindByProjectID retrieves all TimeEntries by ProjectID func (ds *PrismaTimeEntryDataSource) FindByProjectID(ctx context.Context, projectID string) E.Either[error, []entities.TimeEntry] { entries, err := ds.client.TimeEntryDbo.FindMany( @@ -137,4 +135,4 @@ func (ds *PrismaTimeEntryDataSource) FindByProjectID(ctx context.Context, projec } return E.Right[error](mapPrismaTimeEntriesToDomain(entries)) -} \ No newline at end of file +} 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 49e1dfe..379ec04 100644 --- a/backend-go/internal/infrastructure/data/prisma_user_data_source.go +++ b/backend-go/internal/infrastructure/data/prisma_user_data_source.go @@ -18,7 +18,7 @@ func NewPrismaUserDataSource(client *db.PrismaClient) *PrismaUserDataSource { return &PrismaUserDataSource{client: client} } -func (ds *PrismaUserDataSource) Create(ctx context.Context, user entities.UserCreate) E.Either[error,entities.User] { +func (ds *PrismaUserDataSource) Create(ctx context.Context, user entities.UserCreate) E.Either[error, entities.User] { createdUser, err := ds.client.UserDbo.CreateOne( db.UserDbo.Name.Set(user.Name), db.UserDbo.Email.Set(user.Email), @@ -26,48 +26,48 @@ func (ds *PrismaUserDataSource) Create(ctx context.Context, user entities.UserCr ).Exec(ctx) if err != nil { - return E.Left[entities.User,error](app_error.NewInternalError(err)) + 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.Left[entities.User, error](app_error.NewInternalError(fmt.Errorf("Could not create user"))) } return E.Right[error](mapPrismaUserToDomain(*createdUser)) } -func (ds *PrismaUserDataSource) FindByID(ctx context.Context, id string) E.Either[error,entities.User] { +func (ds *PrismaUserDataSource) FindByID(ctx context.Context, id string) E.Either[error, entities.User] { user, err := ds.client.UserDbo.FindUnique( db.UserDbo.ID.Equals(id), ).Exec(ctx) if err != nil { - return E.Left[entities.User,error](handleDBError(err, fmt.Sprintf("Query for user with ID %s failed", id))) + 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.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", id))) } return E.Right[error](mapPrismaUserToDomain(*user)) } -func (ds *PrismaUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error,entities.User]{ +func (ds *PrismaUserDataSource) FindByEmail(ctx context.Context, email string) E.Either[error, entities.User] { user, err := ds.client.UserDbo.FindUnique( db.UserDbo.Email.Equals(email), ).Exec(ctx) if err != nil { - return E.Left[entities.User,error](handleDBError(err, fmt.Sprintf("Query for user with email %s failed", email))) + return E.Left[entities.User, error](handleDBError(err, fmt.Sprintf("Query for user with email %s failed", email))) } if user == nil { - return E.Left[entities.User,error](app_error.NewNotFoundError(fmt.Sprintf("User with email %s not found", email))) + return E.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with email %s not found", email))) } return E.Right[error](mapPrismaUserToDomain(*user)) } -func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error,entities.User] { +func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUpdate) E.Either[error, entities.User] { updatedUser, err := ds.client.UserDbo.FindUnique( db.UserDbo.ID.Equals(user.ID), @@ -78,37 +78,36 @@ func (ds *PrismaUserDataSource) Update(ctx context.Context, user entities.UserUp ).Exec(ctx) if err != nil { - return E.Left[entities.User,error](handleDBError(err, fmt.Sprintf("Could not update user with ID %s", user.ID))) + 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.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", user.ID))) } return E.Right[error](mapPrismaUserToDomain(*updatedUser)) } -func (ds *PrismaUserDataSource) Delete(ctx context.Context, id string) E.Either[error,entities.User] { +func (ds *PrismaUserDataSource) Delete(ctx context.Context, id string) E.Either[error, entities.User] { deleted, err := ds.client.UserDbo.FindUnique( db.UserDbo.ID.Equals(id), ).Delete().Exec(ctx) if err != nil { - return E.Left[entities.User,error](handleDBError(err, fmt.Sprintf("Could not delete user with ID %s", id))) + 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.Left[entities.User, error](app_error.NewNotFoundError(fmt.Sprintf("User with ID %s not found", id))) } - return E.Right[error](mapPrismaUserToDomain(*deleted)) } -func (ds *PrismaUserDataSource) FindAll(ctx context.Context) E.Either[error,[]entities.User] { +func (ds *PrismaUserDataSource) FindAll(ctx context.Context) E.Either[error, []entities.User] { users, err := ds.client.UserDbo.FindMany().Exec(ctx) if err != nil { - return E.Left[[]entities.User,error](handleDBError(err, "Could not retrieve users")) + return E.Left[[]entities.User, error](handleDBError(err, "Could not retrieve users")) } return E.Right[error](mapPrismaUsersToDomain(users)) diff --git a/backend-go/internal/interfaces/http/server.go b/backend-go/internal/interfaces/http/server.go index 143b04d..118ebef 100755 --- a/backend-go/internal/interfaces/http/server.go +++ b/backend-go/internal/interfaces/http/server.go @@ -1,32 +1,72 @@ package http import ( + "actatempus_backend/internal/application/services" "actatempus_backend/internal/infrastructure/config" - "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" ) type Server struct { - cfg *config.Config + cfg *config.Config + userService *services.UserService + projectService *services.ProjectService + projectTaskService *services.ProjectTaskService + timeEntryService *services.TimeEntryService } -func NewServer(cfg *config.Config) *Server { - return &Server{cfg: cfg} +// NewServer initializes the Server with its dependencies. +func NewServer( + cfg *config.Config, + userService *services.UserService, + projectService *services.ProjectService, + projectTaskService *services.ProjectTaskService, + timeEntryService *services.TimeEntryService, +) *Server { + return &Server{ + cfg: cfg, + userService: userService, + projectService: projectService, + projectTaskService: projectTaskService, + timeEntryService: timeEntryService, + } } func (s *Server) Start() error { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Welcome to ActaTempus!") + r := gin.Default() + r.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Welcome to ActaTempus!", + }) + }) + // Health Check + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Server is running", + }) }) - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - }) - }) + // Register Service Routes + api := r.Group("/api") + { + userRouter := api.Group("/users") + s.userService.RegisterRoutes(userRouter) - return r.Run() + projectRouter := api.Group("/projects") + s.projectService.RegisterRoutes(projectRouter) + + projectTaskRouter := api.Group("/project-tasks") + s.projectTaskService.RegisterRoutes(projectTaskRouter) + + timeEntryRouter := api.Group("/time-entries") + s.timeEntryService.RegisterRoutes(timeEntryRouter) + } + + port := s.cfg.Port + if !strings.HasPrefix(port, ":") { + port = ":" + port // Add the colon if it's missing + } + return r.Run(port) // Start the server on the configured port } diff --git a/backend-go/run.sh b/backend-go/run.sh new file mode 100644 index 0000000..6e9ded5 --- /dev/null +++ b/backend-go/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +go run cmd/actatempus/main.go \ No newline at end of file