diff --git a/backend-go/cmd/actatempus/main.go b/backend-go/cmd/actatempus/main.go index f19816e..aa6ab95 100755 --- a/backend-go/cmd/actatempus/main.go +++ b/backend-go/cmd/actatempus/main.go @@ -31,10 +31,10 @@ func main() { authRepo := repository.NewInMemoryAuthRepositoryImpl("secret") // Initialize services - userService := services.NewUserService(userRepo) - projectService := services.NewProjectService(projectRepo) - projectTaskService := services.NewProjectTaskService(projectTaskRepo) - timeEntryService := services.NewTimeEntryService(timeEntryRepo) + userService := services.NewUserService(authRepo, userRepo) + projectService := services.NewProjectService(authRepo, projectRepo) + projectTaskService := services.NewProjectTaskService(authRepo, projectTaskRepo) + timeEntryService := services.NewTimeEntryService(authRepo, timeEntryRepo) authService := services.NewAuthService(authRepo, userRepo) // Initialize and start the server diff --git a/backend-go/internal/application/services/auth_service.go b/backend-go/internal/application/services/auth_service.go index 9be41a3..b7426eb 100644 --- a/backend-go/internal/application/services/auth_service.go +++ b/backend-go/internal/application/services/auth_service.go @@ -67,40 +67,34 @@ func (s *AuthService) Login(c *gin.Context) { // Validate handles token validation. func (s *AuthService) Validate(c *gin.Context) { - var tokenRequest dto.TokenRequestDTO - if err := c.ShouldBindJSON(&tokenRequest); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - F.Pipe2( - s.authRepository.ValidateToken(c.Request.Context())(tokenRequest.Token), - E.Map[error](func(userID string) dto.TokenResponseDTO { - return dto.TokenResponseDTO{ - Token: tokenRequest.Token, - UserID: userID, - } + F.Pipe4( + ReadSessionToken(c), + E.FromOption[string, error]( + F.Constant[error](app_error.NewUnauthorizedError("No session token found"))), + E.Chain( + s.authRepository.ValidateToken(c.Request.Context()), + ), + E.Map[error](func(userID string) gin.H { + return gin.H{"message": "Token validated", "user_id": userID} }), E.Fold( HandleError(c), - HandleSuccess[dto.TokenResponseDTO](c, http.StatusOK), + HandleSuccess[gin.H](c, http.StatusOK), ), ) } // Logout and token revocation. func (s *AuthService) Logout(c *gin.Context) { - var tokenRequest dto.TokenRequestDTO - if err := c.ShouldBindJSON(&tokenRequest); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - deleteSessionTokenCookie(c) - - F.Pipe2( - s.authRepository.RevokeToken(c.Request.Context())(tokenRequest.Token), + F.Pipe4( + ReadSessionToken(c), + E.FromOption[string, error]( + F.Constant[error](app_error.NewUnauthorizedError("No session token found"))), + E.Chain( + s.authRepository.RevokeToken(c.Request.Context()), + ), E.Map[error](func(userID string) gin.H { + deleteSessionTokenCookie(c) return gin.H{"message": "Token revoked", "user_id": userID} }), E.Fold( diff --git a/backend-go/internal/application/services/dto/auth_dto.go b/backend-go/internal/application/services/dto/auth_dto.go index 642a17e..79e5e69 100644 --- a/backend-go/internal/application/services/dto/auth_dto.go +++ b/backend-go/internal/application/services/dto/auth_dto.go @@ -6,11 +6,6 @@ type TokenResponseDTO struct { UserID string `json:"user_id"` } -// TokenRequestDTO represents a request for operations involving tokens. -type TokenRequestDTO struct { - Token string `json:"token"` -} - // LoginRequestDTO represents the login request. type LoginRequestDTO struct { Email string `json:"email"` diff --git a/backend-go/internal/application/services/helpers.go b/backend-go/internal/application/services/helpers.go index 4967bce..86df207 100644 --- a/backend-go/internal/application/services/helpers.go +++ b/backend-go/internal/application/services/helpers.go @@ -2,8 +2,12 @@ package services import ( "actatempus_backend/internal/domain/app_error" + "actatempus_backend/internal/domain/repository" "net/http" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + O "github.com/IBM/fp-go/option" "github.com/gin-gonic/gin" ) @@ -41,3 +45,29 @@ func HandleSuccess[T any](c *gin.Context, statusCode int) func(T) any { return nil } } + +func ReadSessionToken(c *gin.Context) O.Option[string] { + sessionToken, err := c.Cookie("session_token") + if err != nil || sessionToken == "" { + return O.None[string]() + } + return O.Some(sessionToken) +} + +func CheckAuth(c *gin.Context, authRepository repository.AuthRepository) E.Either[error, string] { + return F.Pipe2( + ReadSessionToken(c), + E.FromOption[string, error]( + F.Constant[error](app_error.NewUnauthorizedError("No session token found"))), + E.Chain( + authRepository.ValidateToken(c.Request.Context()), + ), + //E.MapLeft[string]( + // func(e error) error { + // HandleError(c)(e) + // return e + // }, + //), + ) + +} diff --git a/backend-go/internal/application/services/project_service.go b/backend-go/internal/application/services/project_service.go index 71b5edb..4fd5ca8 100644 --- a/backend-go/internal/application/services/project_service.go +++ b/backend-go/internal/application/services/project_service.go @@ -14,12 +14,13 @@ import ( // ProjectService handles project-related HTTP requests. type ProjectService struct { - repository repository.ProjectRepository + authRepository repository.AuthRepository + repository repository.ProjectRepository } // NewProjectService creates a new instance of ProjectService. -func NewProjectService(repo repository.ProjectRepository) *ProjectService { - return &ProjectService{repository: repo} +func NewProjectService(authRepo repository.AuthRepository, repo repository.ProjectRepository) *ProjectService { + return &ProjectService{authRepository: authRepo, repository: repo} } // RegisterRoutes registers the project-related routes with Gin. @@ -38,9 +39,11 @@ func (s *ProjectService) CreateProject(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - F.Pipe3( - mappers.MapCreateDTOToProject(projectCreateDTO), - s.repository.Create(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](projectCreateDTO)), + E.Map[error](mappers.MapCreateDTOToProject), + E.Chain(s.repository.Create(c.Request.Context())), E.Map[error](mappers.MapProjectToDTO), E.Fold( HandleError(c), @@ -51,9 +54,10 @@ func (s *ProjectService) CreateProject(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.FindByID(c.Request.Context())), E.Map[error](mappers.MapProjectToDTO), E.Fold( HandleError(c), @@ -64,8 +68,9 @@ func (s *ProjectService) GetProjectByID(c *gin.Context) { // GetAllProjects handles fetching all projects. func (s *ProjectService) GetAllProjects(c *gin.Context) { - F.Pipe2( - s.repository.FindAll(c.Request.Context()), + F.Pipe3( + CheckAuth(c, s.authRepository), + E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))), E.Map[error](A.Map(mappers.MapProjectToDTO)), E.Fold( HandleError(c), @@ -76,7 +81,6 @@ func (s *ProjectService) GetAllProjects(c *gin.Context) { // 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 { @@ -84,9 +88,13 @@ func (s *ProjectService) UpdateProject(c *gin.Context) { return } - F.Pipe3( - mappers.MapUpdateDTOToProject(projectUpdateDTO, id), - s.repository.Update(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Map[error]( + F.Curry2(mappers.MapUpdateDTOToProject)(projectUpdateDTO), + ), + E.Chain(s.repository.Update(c.Request.Context())), E.Map[error](mappers.MapProjectToDTO), E.Fold( HandleError(c), @@ -97,9 +105,10 @@ func (s *ProjectService) UpdateProject(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.Delete(c.Request.Context())), E.Map[error](mappers.MapProjectToDTO), E.Fold( HandleError(c), diff --git a/backend-go/internal/application/services/project_task_service.go b/backend-go/internal/application/services/project_task_service.go index 34080ec..87797c5 100644 --- a/backend-go/internal/application/services/project_task_service.go +++ b/backend-go/internal/application/services/project_task_service.go @@ -14,12 +14,13 @@ import ( // ProjectTaskService handles project task-related HTTP requests. type ProjectTaskService struct { - repository repository.ProjectTaskRepository + authRepository repository.AuthRepository + repository repository.ProjectTaskRepository } // NewProjectTaskService creates a new instance of ProjectTaskService. -func NewProjectTaskService(repo repository.ProjectTaskRepository) *ProjectTaskService { - return &ProjectTaskService{repository: repo} +func NewProjectTaskService(authRepo repository.AuthRepository, repo repository.ProjectTaskRepository) *ProjectTaskService { + return &ProjectTaskService{authRepository: authRepo, repository: repo} } // RegisterRoutes registers the project task-related routes with Gin. @@ -39,9 +40,13 @@ func (s *ProjectTaskService) CreateTask(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - F.Pipe3( - mappers.MapCreateDTOToProjectTask(taskCreateDTO), - s.repository.Create(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](taskCreateDTO)), + E.Map[error](mappers.MapCreateDTOToProjectTask), + E.Chain( + s.repository.Create(c.Request.Context()), + ), E.Map[error](mappers.MapProjectTaskToDTO), E.Fold( HandleError(c), @@ -52,9 +57,10 @@ func (s *ProjectTaskService) CreateTask(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.FindByID(c.Request.Context())), E.Map[error](mappers.MapProjectTaskToDTO), E.Fold( HandleError(c), @@ -65,9 +71,10 @@ func (s *ProjectTaskService) GetTaskByID(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("projectID"))), + E.Chain(s.repository.FindByProjectID(c.Request.Context())), E.Map[error](A.Map(mappers.MapProjectTaskToDTO)), E.Fold( HandleError(c), @@ -78,8 +85,9 @@ func (s *ProjectTaskService) GetTasksByProjectID(c *gin.Context) { // GetAllTasks handles fetching all project tasks. func (s *ProjectTaskService) GetAllTasks(c *gin.Context) { - F.Pipe2( - s.repository.FindAll(c.Request.Context()), + F.Pipe3( + CheckAuth(c, s.authRepository), + E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))), E.Map[error](A.Map(mappers.MapProjectTaskToDTO)), E.Fold( HandleError(c), @@ -90,7 +98,6 @@ func (s *ProjectTaskService) GetAllTasks(c *gin.Context) { // 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 { @@ -98,9 +105,15 @@ func (s *ProjectTaskService) UpdateTask(c *gin.Context) { return } - F.Pipe3( - mappers.MapUpdateDTOToProjectTask(taskUpdateDTO, id), - s.repository.Update(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Map[error]( + F.Curry2(mappers.MapUpdateDTOToProjectTask)(taskUpdateDTO), + ), + E.Chain( + s.repository.Update(c.Request.Context()), + ), E.Map[error](mappers.MapProjectTaskToDTO), E.Fold( HandleError(c), @@ -111,9 +124,10 @@ func (s *ProjectTaskService) UpdateTask(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.Delete(c.Request.Context())), E.Map[error](mappers.MapProjectTaskToDTO), E.Fold( HandleError(c), diff --git a/backend-go/internal/application/services/time_entry_service.go b/backend-go/internal/application/services/time_entry_service.go index 3dd47f7..38351d7 100644 --- a/backend-go/internal/application/services/time_entry_service.go +++ b/backend-go/internal/application/services/time_entry_service.go @@ -14,12 +14,13 @@ import ( // TimeEntryService handles time entry-related HTTP requests. type TimeEntryService struct { - repository repository.TimeEntryRepository + authRepository repository.AuthRepository + repository repository.TimeEntryRepository } // NewTimeEntryService creates a new instance of TimeEntryService. -func NewTimeEntryService(repo repository.TimeEntryRepository) *TimeEntryService { - return &TimeEntryService{repository: repo} +func NewTimeEntryService(authRepo repository.AuthRepository, repo repository.TimeEntryRepository) *TimeEntryService { + return &TimeEntryService{authRepository: authRepo, repository: repo} } // RegisterRoutes registers the time entry-related routes with Gin. @@ -40,9 +41,11 @@ func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - F.Pipe3( - mappers.MapCreateDTOToTimeEntry(timeEntryCreateDTO), - s.repository.Create(c.Request.Context()), + F.Pipe5( // disabled for creating the initial user + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](timeEntryCreateDTO)), + E.Map[error](mappers.MapCreateDTOToTimeEntry), + E.Chain(s.repository.Create(c.Request.Context())), E.Map[error](mappers.MapTimeEntryToDTO), E.Fold( HandleError(c), @@ -53,9 +56,10 @@ func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.FindByID(c.Request.Context())), E.Map[error](mappers.MapTimeEntryToDTO), E.Fold( HandleError(c), @@ -66,9 +70,10 @@ func (s *TimeEntryService) GetTimeEntryByID(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("userID"))), + E.Chain(s.repository.FindByUserID(c.Request.Context())), E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), E.Fold( HandleError(c), @@ -79,9 +84,10 @@ func (s *TimeEntryService) GetTimeEntriesByUserID(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("projectID"))), + E.Chain(s.repository.FindByProjectID(c.Request.Context())), E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), E.Fold( HandleError(c), @@ -92,8 +98,9 @@ func (s *TimeEntryService) GetTimeEntriesByProjectID(c *gin.Context) { // GetAllTimeEntries handles fetching all time entries. func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) { - F.Pipe2( - s.repository.FindAll(c.Request.Context()), + F.Pipe3( + CheckAuth(c, s.authRepository), + E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))), E.Map[error](A.Map(mappers.MapTimeEntryToDTO)), E.Fold( HandleError(c), @@ -104,7 +111,6 @@ func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) { // 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 { @@ -112,9 +118,12 @@ func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) { return } - F.Pipe3( - mappers.MapUpdateDTOToTimeEntry(timeEntryUpdateDTO, id), - s.repository.Update(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Map[error]( + F.Curry2(mappers.MapUpdateDTOToTimeEntry)(timeEntryUpdateDTO)), + E.Chain(s.repository.Update(c.Request.Context())), E.Map[error](mappers.MapTimeEntryToDTO), E.Fold( HandleError(c), @@ -125,9 +134,10 @@ func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) { // 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()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.Delete(c.Request.Context())), E.Map[error](mappers.MapTimeEntryToDTO), E.Fold( HandleError(c), diff --git a/backend-go/internal/application/services/user_service.go b/backend-go/internal/application/services/user_service.go index 77d64c1..e90ee27 100644 --- a/backend-go/internal/application/services/user_service.go +++ b/backend-go/internal/application/services/user_service.go @@ -14,12 +14,13 @@ import ( // UserService handles user-related HTTP requests. type UserService struct { - repository repository.UserRepository + authRepository repository.AuthRepository + repository repository.UserRepository } // NewUserService creates a new instance of UserService. -func NewUserService(repo repository.UserRepository) *UserService { - return &UserService{repository: repo} +func NewUserService(authRepo repository.AuthRepository, repo repository.UserRepository) *UserService { + return &UserService{authRepository: authRepo, repository: repo} } // RegisterRoutes registers the user-related routes with Gin. @@ -47,13 +48,28 @@ func (s *UserService) CreateUser(c *gin.Context) { HandleSuccess[dto.UserDTO](c, http.StatusCreated), ), ) + + //F.Pipe5( // disabled for creating the initial user + // CheckAuth(c, s.authRepository), + // E.Map[error](F.Constant1[string](userCreateDTO)), + // E.Map[error](mappers.MapCreateDTOToUser), + // E.Chain( + // s.repository.Create(c.Request.Context()), + // ), + // E.Map[error](mappers.MapUserToDTO), + // E.Fold( + // HandleError(c), + // HandleSuccess[dto.UserDTO](c, http.StatusCreated), + // ), + //) } // GetUserByID handles fetching a user by their ID. func (s *UserService) GetUserByID(c *gin.Context) { - F.Pipe3( - c.Param("id"), - s.repository.FindByID(c.Request.Context()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.FindByID(c.Request.Context())), E.Map[error](mappers.MapUserToDTO), E.Fold( HandleError(c), @@ -64,8 +80,9 @@ func (s *UserService) GetUserByID(c *gin.Context) { // GetAllUsers handles fetching all users. func (s *UserService) GetAllUsers(c *gin.Context) { - F.Pipe2( - s.repository.FindAll(c.Request.Context()), + F.Pipe3( + CheckAuth(c, s.authRepository), + E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))), E.Map[error](A.Map(mappers.MapUserToDTO)), E.Fold( HandleError(c), @@ -76,17 +93,17 @@ func (s *UserService) GetAllUsers(c *gin.Context) { // UpdateUser handles updating an existing user. func (s *UserService) UpdateUser(c *gin.Context) { - id := c.Param("id") var userUpdateDTO dto.UserUpdateDTO - if err := c.ShouldBindJSON(&userUpdateDTO); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - F.Pipe3( - mappers.MapUpdateDTOToUser(userUpdateDTO, id), - s.repository.Update(c.Request.Context()), + F.Pipe5( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Map[error](F.Curry2(mappers.MapUpdateDTOToUser)(userUpdateDTO)), + E.Chain(s.repository.Update(c.Request.Context())), E.Map[error](mappers.MapUserToDTO), E.Fold( HandleError(c), @@ -97,9 +114,10 @@ func (s *UserService) UpdateUser(c *gin.Context) { // DeleteUser handles deleting a user by their ID. func (s *UserService) DeleteUser(c *gin.Context) { - F.Pipe3( - c.Param("id"), - s.repository.Delete(c.Request.Context()), + F.Pipe4( + CheckAuth(c, s.authRepository), + E.Map[error](F.Constant1[string](c.Param("id"))), + E.Chain(s.repository.Delete(c.Request.Context())), E.Map[error](mappers.MapUserToDTO), E.Fold( HandleError(c),