From 294047a2b0f38d6698cb35fa48957a9cb34b0f5a Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Wed, 12 Mar 2025 11:38:24 +0000 Subject: [PATCH] feat: Refactor activity and project handlers to use utility functions for ID parsing and response handling --- .../internal/api/handlers/activity_handler.go | 68 ++------- .../internal/api/handlers/company_handler.go | 69 ++------- .../internal/api/handlers/customer_handler.go | 74 ++-------- .../internal/api/handlers/project_handler.go | 71 ++------- .../api/handlers/timeentry_handler.go | 5 +- backend/internal/api/utils/handler_utils.go | 136 ++++++++++++++++++ backend/internal/models/project.go | 9 +- 7 files changed, 178 insertions(+), 254 deletions(-) create mode 100644 backend/internal/api/utils/handler_utils.go diff --git a/backend/internal/api/handlers/activity_handler.go b/backend/internal/api/handlers/activity_handler.go index bd4670a..610dac4 100644 --- a/backend/internal/api/handlers/activity_handler.go +++ b/backend/internal/api/handlers/activity_handler.go @@ -32,20 +32,7 @@ func NewActivityHandler() *ActivityHandler { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /activities [get] func (h *ActivityHandler) GetActivities(c *gin.Context) { - // Get activities from the database - activities, err := models.GetAllActivities(c.Request.Context()) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving activities: "+err.Error()) - return - } - - // Convert to DTOs - activityDTOs := make([]dto.ActivityDto, len(activities)) - for i, activity := range activities { - activityDTOs[i] = convertActivityToDTO(&activity) - } - - utils.SuccessResponse(c, http.StatusOK, activityDTOs) + utils.HandleGetAll(c, models.GetAllActivities, convertActivityToDTO, "activities") } // GetActivityByID handles GET /activities/:id @@ -64,30 +51,7 @@ func (h *ActivityHandler) GetActivities(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /activities/{id} [get] func (h *ActivityHandler) GetActivityByID(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid activity ID format") - return - } - - // Get activity from the database - activity, err := models.GetActivityByID(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving activity: "+err.Error()) - return - } - - if activity == nil { - utils.NotFoundResponse(c, "Activity not found") - return - } - - // Convert to DTO - activityDTO := convertActivityToDTO(activity) - - utils.SuccessResponse(c, http.StatusOK, activityDTO) + utils.HandleGetByID(c, models.GetActivityByID, convertActivityToDTO, "activity") } // CreateActivity handles POST /activities @@ -107,8 +71,8 @@ func (h *ActivityHandler) GetActivityByID(c *gin.Context) { func (h *ActivityHandler) CreateActivity(c *gin.Context) { // Parse request body var activityCreateDTO dto.ActivityCreateDto - if err := c.ShouldBindJSON(&activityCreateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &activityCreateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -146,8 +110,7 @@ func (h *ActivityHandler) CreateActivity(c *gin.Context) { // @Router /activities/{id} [put] func (h *ActivityHandler) UpdateActivity(c *gin.Context) { // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) + id, err := utils.ParseID(c, "id") if err != nil { utils.BadRequestResponse(c, "Invalid activity ID format") return @@ -155,8 +118,8 @@ func (h *ActivityHandler) UpdateActivity(c *gin.Context) { // Parse request body var activityUpdateDTO dto.ActivityUpdateDto - if err := c.ShouldBindJSON(&activityUpdateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &activityUpdateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -199,22 +162,7 @@ func (h *ActivityHandler) UpdateActivity(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /activities/{id} [delete] func (h *ActivityHandler) DeleteActivity(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid activity ID format") - return - } - - // Delete activity from the database - err = models.DeleteActivity(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error deleting activity: "+err.Error()) - return - } - - utils.SuccessResponse(c, http.StatusNoContent, nil) + utils.HandleDelete(c, models.DeleteActivity, "activity") } // Helper functions for DTO conversion diff --git a/backend/internal/api/handlers/company_handler.go b/backend/internal/api/handlers/company_handler.go index da036ff..b045f58 100644 --- a/backend/internal/api/handlers/company_handler.go +++ b/backend/internal/api/handlers/company_handler.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/oklog/ulid/v2" "github.com/timetracker/backend/internal/api/utils" dto "github.com/timetracker/backend/internal/dtos" "github.com/timetracker/backend/internal/models" @@ -32,20 +31,7 @@ func NewCompanyHandler() *CompanyHandler { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /companies [get] func (h *CompanyHandler) GetCompanies(c *gin.Context) { - // Get companies from the database - companies, err := models.GetAllCompanies(c.Request.Context()) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving companies: "+err.Error()) - return - } - - // Convert to DTOs - companyDTOs := make([]dto.CompanyDto, len(companies)) - for i, company := range companies { - companyDTOs[i] = convertCompanyToDTO(&company) - } - - utils.SuccessResponse(c, http.StatusOK, companyDTOs) + utils.HandleGetAll(c, models.GetAllCompanies, convertCompanyToDTO, "companies") } // GetCompanyByID handles GET /companies/:id @@ -64,30 +50,7 @@ func (h *CompanyHandler) GetCompanies(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /companies/{id} [get] func (h *CompanyHandler) GetCompanyByID(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid company ID format") - return - } - - // Get company from the database - company, err := models.GetCompanyByID(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving company: "+err.Error()) - return - } - - if company == nil { - utils.NotFoundResponse(c, "Company not found") - return - } - - // Convert to DTO - companyDTO := convertCompanyToDTO(company) - - utils.SuccessResponse(c, http.StatusOK, companyDTO) + utils.HandleGetByID(c, models.GetCompanyByID, convertCompanyToDTO, "company") } // CreateCompany handles POST /companies @@ -107,8 +70,8 @@ func (h *CompanyHandler) GetCompanyByID(c *gin.Context) { func (h *CompanyHandler) CreateCompany(c *gin.Context) { // Parse request body var companyCreateDTO dto.CompanyCreateDto - if err := c.ShouldBindJSON(&companyCreateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &companyCreateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -146,8 +109,7 @@ func (h *CompanyHandler) CreateCompany(c *gin.Context) { // @Router /companies/{id} [put] func (h *CompanyHandler) UpdateCompany(c *gin.Context) { // Parse ID from URL - idStr := c.Param("id") - id, err := types.ULIDFromString(idStr) + id, err := utils.ParseID(c, "id") if err != nil { utils.BadRequestResponse(c, "Invalid company ID format") return @@ -155,8 +117,8 @@ func (h *CompanyHandler) UpdateCompany(c *gin.Context) { // Parse request body var companyUpdateDTO dto.CompanyUpdateDto - if err := c.ShouldBindJSON(&companyUpdateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &companyUpdateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -196,22 +158,7 @@ func (h *CompanyHandler) UpdateCompany(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /companies/{id} [delete] func (h *CompanyHandler) DeleteCompany(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid company ID format") - return - } - - // Delete company from the database - err = models.DeleteCompany(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error deleting company: "+err.Error()) - return - } - - utils.SuccessResponse(c, http.StatusNoContent, nil) + utils.HandleDelete(c, models.DeleteCompany, "company") } // Helper functions for DTO conversion diff --git a/backend/internal/api/handlers/customer_handler.go b/backend/internal/api/handlers/customer_handler.go index 7d1fa58..f879616 100644 --- a/backend/internal/api/handlers/customer_handler.go +++ b/backend/internal/api/handlers/customer_handler.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/oklog/ulid/v2" "github.com/timetracker/backend/internal/api/middleware" "github.com/timetracker/backend/internal/api/utils" dto "github.com/timetracker/backend/internal/dtos" @@ -34,20 +33,7 @@ func NewCustomerHandler() *CustomerHandler { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /customers [get] func (h *CustomerHandler) GetCustomers(c *gin.Context) { - // Get customers from the database - customers, err := models.GetAllCustomers(c.Request.Context()) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving customers: "+err.Error()) - return - } - - // Convert to DTOs - customerDTOs := make([]dto.CustomerDto, len(customers)) - for i, customer := range customers { - customerDTOs[i] = convertCustomerToDTO(&customer) - } - - utils.SuccessResponse(c, http.StatusOK, customerDTOs) + utils.HandleGetAll(c, models.GetAllCustomers, convertCustomerToDTO, "customers") } // GetCustomerByID handles GET /customers/:id @@ -66,30 +52,7 @@ func (h *CustomerHandler) GetCustomers(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /customers/{id} [get] func (h *CustomerHandler) GetCustomerByID(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid customer ID format") - return - } - - // Get customer from the database - customer, err := models.GetCustomerByID(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving customer: "+err.Error()) - return - } - - if customer == nil { - utils.NotFoundResponse(c, "Customer not found") - return - } - - // Convert to DTO - customerDTO := convertCustomerToDTO(customer) - - utils.SuccessResponse(c, http.StatusOK, customerDTO) + utils.HandleGetByID(c, models.GetCustomerByID, convertCustomerToDTO, "customer") } // GetCustomersByCompanyID handles GET /customers/company/:companyId @@ -123,10 +86,7 @@ func (h *CustomerHandler) GetCustomersByCompanyID(c *gin.Context) { } // Convert to DTOs - customerDTOs := make([]dto.CustomerDto, len(customers)) - for i, customer := range customers { - customerDTOs[i] = convertCustomerToDTO(&customer) - } + customerDTOs := utils.ConvertToDTO(customers, convertCustomerToDTO) utils.SuccessResponse(c, http.StatusOK, customerDTOs) } @@ -153,8 +113,8 @@ func (h *CustomerHandler) CreateCustomer(c *gin.Context) { } // Parse request body var customerCreateDTO dto.CustomerCreateDto - if err := c.ShouldBindJSON(&customerCreateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &customerCreateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -197,8 +157,7 @@ func (h *CustomerHandler) CreateCustomer(c *gin.Context) { // @Router /customers/{id} [put] func (h *CustomerHandler) UpdateCustomer(c *gin.Context) { // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) + id, err := utils.ParseID(c, "id") if err != nil { utils.BadRequestResponse(c, "Invalid customer ID format") return @@ -206,8 +165,8 @@ func (h *CustomerHandler) UpdateCustomer(c *gin.Context) { // Parse request body var customerUpdateDTO dto.CustomerUpdateDto - if err := c.ShouldBindJSON(&customerUpdateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &customerUpdateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -254,22 +213,7 @@ func (h *CustomerHandler) UpdateCustomer(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /customers/{id} [delete] func (h *CustomerHandler) DeleteCustomer(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid customer ID format") - return - } - - // Delete customer from the database - err = models.DeleteCustomer(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error deleting customer: "+err.Error()) - return - } - - utils.SuccessResponse(c, http.StatusNoContent, nil) + utils.HandleDelete(c, models.DeleteCustomer, "customer") } // Helper functions for DTO conversion diff --git a/backend/internal/api/handlers/project_handler.go b/backend/internal/api/handlers/project_handler.go index 63dda6d..c4f6dca 100644 --- a/backend/internal/api/handlers/project_handler.go +++ b/backend/internal/api/handlers/project_handler.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/oklog/ulid/v2" "github.com/timetracker/backend/internal/api/utils" dto "github.com/timetracker/backend/internal/dtos" "github.com/timetracker/backend/internal/models" @@ -33,20 +32,7 @@ func NewProjectHandler() *ProjectHandler { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects [get] func (h *ProjectHandler) GetProjects(c *gin.Context) { - // Get projects from the database - projects, err := models.GetAllProjects(c.Request.Context()) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving projects: "+err.Error()) - return - } - - // Convert to DTOs - projectDTOs := make([]dto.ProjectDto, len(projects)) - for i, project := range projects { - projectDTOs[i] = convertProjectToDTO(&project) - } - - utils.SuccessResponse(c, http.StatusOK, projectDTOs) + utils.HandleGetAll(c, models.GetAllProjects, convertProjectToDTO, "projects") } // GetProjectsWithCustomers handles GET /projects/with-customers @@ -70,10 +56,7 @@ func (h *ProjectHandler) GetProjectsWithCustomers(c *gin.Context) { } // Convert to DTOs - projectDTOs := make([]dto.ProjectDto, len(projects)) - for i, project := range projects { - projectDTOs[i] = convertProjectToDTO(&project) - } + projectDTOs := utils.ConvertToDTO(projects, convertProjectToDTO) utils.SuccessResponse(c, http.StatusOK, projectDTOs) } @@ -94,30 +77,7 @@ func (h *ProjectHandler) GetProjectsWithCustomers(c *gin.Context) { // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/{id} [get] func (h *ProjectHandler) GetProjectByID(c *gin.Context) { - // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) - if err != nil { - utils.BadRequestResponse(c, "Invalid project ID format") - return - } - - // Get project from the database - project, err := models.GetProjectByID(c.Request.Context(), types.FromULID(id)) - if err != nil { - utils.InternalErrorResponse(c, "Error retrieving project: "+err.Error()) - return - } - - if project == nil { - utils.NotFoundResponse(c, "Project not found") - return - } - - // Convert to DTO - projectDTO := convertProjectToDTO(project) - - utils.SuccessResponse(c, http.StatusOK, projectDTO) + utils.HandleGetByID(c, models.GetProjectByID, convertProjectToDTO, "project") } // GetProjectsByCustomerID handles GET /projects/customer/:customerId @@ -136,8 +96,7 @@ func (h *ProjectHandler) GetProjectByID(c *gin.Context) { // @Router /projects/customer/{customerId} [get] func (h *ProjectHandler) GetProjectsByCustomerID(c *gin.Context) { // Parse customer ID from URL - customerIDStr := c.Param("customerId") - customerID, err := ulid.Parse(customerIDStr) + customerID, err := utils.ParseID(c, "customerId") if err != nil { utils.BadRequestResponse(c, "Invalid customer ID format") return @@ -151,10 +110,7 @@ func (h *ProjectHandler) GetProjectsByCustomerID(c *gin.Context) { } // Convert to DTOs - projectDTOs := make([]dto.ProjectDto, len(projects)) - for i, project := range projects { - projectDTOs[i] = convertProjectToDTO(&project) - } + projectDTOs := utils.ConvertToDTO(projects, convertProjectToDTO) utils.SuccessResponse(c, http.StatusOK, projectDTOs) } @@ -176,8 +132,8 @@ func (h *ProjectHandler) GetProjectsByCustomerID(c *gin.Context) { func (h *ProjectHandler) CreateProject(c *gin.Context) { // Parse request body var projectCreateDTO dto.ProjectCreateDto - if err := c.ShouldBindJSON(&projectCreateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &projectCreateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -219,8 +175,7 @@ func (h *ProjectHandler) CreateProject(c *gin.Context) { // @Router /projects/{id} [put] func (h *ProjectHandler) UpdateProject(c *gin.Context) { // Parse ID from URL - idStr := c.Param("id") - id, err := types.ULIDFromString(idStr) + id, err := utils.ParseID(c, "id") if err != nil { utils.BadRequestResponse(c, "Invalid project ID format") return @@ -228,8 +183,8 @@ func (h *ProjectHandler) UpdateProject(c *gin.Context) { // Parse request body var projectUpdateDTO dto.ProjectUpdateDto - if err := c.ShouldBindJSON(&projectUpdateDTO); err != nil { - utils.BadRequestResponse(c, "Invalid request body: "+err.Error()) + if err := utils.BindJSON(c, &projectUpdateDTO); err != nil { + utils.BadRequestResponse(c, err.Error()) return } @@ -274,8 +229,7 @@ func (h *ProjectHandler) UpdateProject(c *gin.Context) { // @Router /projects/{id} [delete] func (h *ProjectHandler) DeleteProject(c *gin.Context) { // Parse ID from URL - idStr := c.Param("id") - id, err := ulid.Parse(idStr) + id, err := utils.ParseID(c, "id") if err != nil { utils.BadRequestResponse(c, "Invalid project ID format") return @@ -308,7 +262,6 @@ func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCre create := models.ProjectCreate{Name: dto.Name} // Convert CustomerID from int to ULID (this is a simplification, adjust as needed) if dto.CustomerID != nil { - customerID, err := types.ULIDFromString(*dto.CustomerID) if err != nil { return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err) @@ -330,9 +283,7 @@ func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto, id types.ULID) (mo if dto.CustomerID.Valid { if dto.CustomerID.Value == nil { update.CustomerID = nil - } else { - // Convert CustomerID from int to ULID (this is a simplification, adjust as needed) customerID, err := types.ULIDFromString(*dto.CustomerID.Value) if err != nil { return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err) diff --git a/backend/internal/api/handlers/timeentry_handler.go b/backend/internal/api/handlers/timeentry_handler.go index aa2663a..17dc5ae 100644 --- a/backend/internal/api/handlers/timeentry_handler.go +++ b/backend/internal/api/handlers/timeentry_handler.go @@ -184,15 +184,14 @@ func (h *TimeEntryHandler) GetMyTimeEntries(c *gin.Context) { // @Router /time-entries/project/{projectId} [get] func (h *TimeEntryHandler) GetTimeEntriesByProjectID(c *gin.Context) { // Parse project ID from URL - projectIDStr := c.Param("projectId") - projectID, err := ulid.Parse(projectIDStr) + projectID, err := utils.ParseID(c, "projectId") if err != nil { utils.BadRequestResponse(c, "Invalid project ID format") return } // Get time entries from the database - timeEntries, err := models.GetTimeEntriesByProjectID(c.Request.Context(), types.FromULID(projectID)) + timeEntries, err := models.GetTimeEntriesByProjectID(c.Request.Context(), projectID) if err != nil { utils.InternalErrorResponse(c, "Error retrieving time entries: "+err.Error()) return diff --git a/backend/internal/api/utils/handler_utils.go b/backend/internal/api/utils/handler_utils.go new file mode 100644 index 0000000..e5b5914 --- /dev/null +++ b/backend/internal/api/utils/handler_utils.go @@ -0,0 +1,136 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/timetracker/backend/internal/types" +) + +// ParseID parses an ID from the URL parameter and converts it to a types.ULID +func ParseID(c *gin.Context, paramName string) (types.ULID, error) { + idStr := c.Param(paramName) + return types.ULIDFromString(idStr) +} + +// BindJSON binds the request body to the provided struct +func BindJSON(c *gin.Context, obj interface{}) error { + if err := c.ShouldBindJSON(obj); err != nil { + return fmt.Errorf("invalid request body: %w", err) + } + return nil +} + +// ConvertToDTO converts a slice of models to a slice of DTOs using the provided conversion function +func ConvertToDTO[M any, D any](models []M, convertFn func(*M) D) []D { + dtos := make([]D, len(models)) + for i, model := range models { + // Create a copy of the model to avoid issues with loop variable capture + modelCopy := model + dtos[i] = convertFn(&modelCopy) + } + return dtos +} + +// HandleGetAll is a generic function to handle GET all entities endpoints +func HandleGetAll[M any, D any]( + c *gin.Context, + getAllFn func(ctx context.Context) ([]M, error), + convertFn func(*M) D, + entityName string, +) { + // Get entities from the database + entities, err := getAllFn(c.Request.Context()) + if err != nil { + InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error())) + return + } + + // Convert to DTOs + dtos := ConvertToDTO(entities, convertFn) + + SuccessResponse(c, 200, dtos) +} + +// HandleGetByID is a generic function to handle GET entity by ID endpoints +func HandleGetByID[M any, D any]( + c *gin.Context, + getByIDFn func(ctx context.Context, id types.ULID) (*M, error), + convertFn func(*M) D, + entityName string, +) { + // Parse ID from URL + id, err := ParseID(c, "id") + if err != nil { + BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName)) + return + } + + // Get entity from the database + entity, err := getByIDFn(c.Request.Context(), id) + if err != nil { + InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error())) + return + } + + if entity == nil { + NotFoundResponse(c, fmt.Sprintf("%s not found", entityName)) + return + } + + // Convert to DTO + dto := convertFn(entity) + + SuccessResponse(c, 200, dto) +} + +// HandleCreate is a generic function to handle POST entity endpoints +func HandleCreate[C any, M any, D any]( + c *gin.Context, + createFn func(ctx context.Context, create C) (*M, error), + convertFn func(*M) D, + entityName string, +) { + // Parse request body + var createDTO C + if err := BindJSON(c, &createDTO); err != nil { + BadRequestResponse(c, err.Error()) + return + } + + // Create entity in the database + entity, err := createFn(c.Request.Context(), createDTO) + if err != nil { + InternalErrorResponse(c, fmt.Sprintf("Error creating %s: %s", entityName, err.Error())) + return + } + + // Convert to DTO + dto := convertFn(entity) + + SuccessResponse(c, 201, dto) +} + +// HandleDelete is a generic function to handle DELETE entity endpoints +func HandleDelete( + c *gin.Context, + deleteFn func(ctx context.Context, id types.ULID) error, + entityName string, +) { + // Parse ID from URL + id, err := ParseID(c, "id") + if err != nil { + BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName)) + return + } + + // Delete entity from the database + err = deleteFn(c.Request.Context(), id) + if err != nil { + InternalErrorResponse(c, fmt.Sprintf("Error deleting %s: %s", entityName, err.Error())) + return + } + + SuccessResponse(c, 204, nil) +} diff --git a/backend/internal/models/project.go b/backend/internal/models/project.go index 2247098..78b2591 100644 --- a/backend/internal/models/project.go +++ b/backend/internal/models/project.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/oklog/ulid/v2" "github.com/timetracker/backend/internal/types" "gorm.io/gorm" ) @@ -72,7 +71,7 @@ func GetProjectByID(ctx context.Context, id types.ULID) (*Project, error) { } // GetProjectWithCustomer loads a project with the associated customer information -func GetProjectWithCustomer(ctx context.Context, id ulid.ULID) (*Project, error) { +func GetProjectWithCustomer(ctx context.Context, id types.ULID) (*Project, error) { var project Project result := GetEngine(ctx).Preload("Customer").Where("id = ?", id).First(&project) if result.Error != nil { @@ -105,9 +104,9 @@ func GetAllProjectsWithCustomers(ctx context.Context) ([]Project, error) { } // GetProjectsByCustomerID returns all projects of a specific customer -func GetProjectsByCustomerID(ctx context.Context, customerID ulid.ULID) ([]Project, error) { +func GetProjectsByCustomerID(ctx context.Context, customerId types.ULID) ([]Project, error) { var projects []Project - result := GetEngine(ctx).Where("customer_id = ?", customerID).Find(&projects) + result := GetEngine(ctx).Where("customer_id = ?", customerId.ULID).Find(&projects) if result.Error != nil { return nil, result.Error } @@ -180,7 +179,7 @@ func UpdateProject(ctx context.Context, update ProjectUpdate) (*Project, error) } // DeleteProject deletes a project by its ID -func DeleteProject(ctx context.Context, id ulid.ULID) error { +func DeleteProject(ctx context.Context, id types.ULID) error { // Here you could check if dependent entities exist result := GetEngine(ctx).Delete(&Project{}, id) if result.Error != nil {