package handlers import ( "context" "fmt" "github.com/gin-gonic/gin" "github.com/timetracker/backend/internal/api/dto" "github.com/timetracker/backend/internal/api/responses" "github.com/timetracker/backend/internal/api/utils" "github.com/timetracker/backend/internal/models" "github.com/timetracker/backend/internal/types" ) // ProjectHandler handles project-related API endpoints type ProjectHandler struct{} // NewProjectHandler creates a new ProjectHandler func NewProjectHandler() *ProjectHandler { return &ProjectHandler{} } // GetProjects handles GET /projects // // @Summary Get all projects // @Description Get a list of all projects // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} utils.Response{data=[]dto.ProjectDto} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects [get] func (h *ProjectHandler) GetProjects(c *gin.Context) { utils.HandleGetAll(c, models.GetAllProjects, convertProjectToDTO, "projects") } // GetProjectsWithCustomers handles GET /projects/with-customers // // @Summary Get all projects with customer information // @Description Get a list of all projects with their associated customer information // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} utils.Response{data=[]dto.ProjectDto} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/with-customers [get] func (h *ProjectHandler) GetProjectsWithCustomers(c *gin.Context) { utils.HandleGetAll(c, models.GetAllProjectsWithCustomers, convertProjectToDTO, "projects with customers") } // GetProjectByID handles GET /projects/:id // // @Summary Get project by ID // @Description Get a project by its ID // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Project ID" // @Success 200 {object} utils.Response{data=dto.ProjectDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 404 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/{id} [get] func (h *ProjectHandler) GetProjectByID(c *gin.Context) { utils.HandleGetByID(c, models.GetProjectByID, convertProjectToDTO, "project") } // GetProjectsByCustomerID handles GET /projects/customer/:customerId // // @Summary Get projects by customer ID // @Description Get a list of projects for a specific customer // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Param customerId path string true "Customer ID" // @Success 200 {object} utils.Response{data=[]dto.ProjectDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/customer/{customerId} [get] func (h *ProjectHandler) GetProjectsByCustomerID(c *gin.Context) { utils.HandleGetByFilter(c, models.GetProjectsByCustomerID, convertProjectToDTO, "projects", "customerId") } // CreateProject handles POST /projects // // @Summary Create a new project // @Description Create a new project // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Param project body dto.ProjectCreateDto true "Project data" // @Success 201 {object} utils.Response{data=dto.ProjectDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects [post] func (h *ProjectHandler) CreateProject(c *gin.Context) { utils.HandleCreate(c, createProjectWrapper, convertProjectToDTO, "project") } // UpdateProject handles PUT /projects/:id // // @Summary Update a project // @Description Update an existing project // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Project ID" // @Param project body dto.ProjectUpdateDto true "Project data" // @Success 200 {object} utils.Response{data=dto.ProjectDto} // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 404 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/{id} [put] func (h *ProjectHandler) UpdateProject(c *gin.Context) { utils.HandleUpdate(c, models.UpdateProject, convertProjectToDTO, prepareProjectUpdate, "project") } // DeleteProject handles DELETE /projects/:id // // @Summary Delete a project // @Description Delete a project by its ID // @Tags projects // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Project ID" // @Success 204 {object} utils.Response // @Failure 400 {object} utils.Response{error=utils.ErrorInfo} // @Failure 401 {object} utils.Response{error=utils.ErrorInfo} // @Failure 500 {object} utils.Response{error=utils.ErrorInfo} // @Router /projects/{id} [delete] func (h *ProjectHandler) DeleteProject(c *gin.Context) { utils.HandleDelete(c, models.DeleteProject, "project") } // Helper functions for DTO conversion func convertProjectToDTO(project *models.Project) dto.ProjectDto { var customerIdPtr *string if project.CustomerID != nil { customerIdStr := project.CustomerID.String() customerIdPtr = &customerIdStr } return dto.ProjectDto{ ID: project.ID.String(), CreatedAt: project.CreatedAt, UpdatedAt: project.UpdatedAt, Name: project.Name, CustomerID: customerIdPtr, } } func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) { 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) } create.CustomerID = &customerID } return create, nil } func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto, id types.ULID) (models.ProjectUpdate, error) { update := models.ProjectUpdate{ ID: id, } if dto.Name != nil { update.Name = dto.Name } if dto.CustomerID.Valid { if dto.CustomerID.Value == nil { update.CustomerID = types.Null[types.ULID]() } else { customerID, err := types.ULIDFromString(*dto.CustomerID.Value) if err != nil { return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err) } update.CustomerID = types.NewNullable(customerID) } } return update, nil } // prepareProjectUpdate prepares the project update object by parsing the ID, binding the JSON, and converting the DTO to a model func prepareProjectUpdate(c *gin.Context) (models.ProjectUpdate, error) { // Parse ID from URL id, err := utils.ParseID(c, "id") if err != nil { responses.BadRequestResponse(c, "Invalid project ID format") return models.ProjectUpdate{}, err } // Parse request body var projectUpdateDTO dto.ProjectUpdateDto if err := utils.BindJSON(c, &projectUpdateDTO); err != nil { responses.BadRequestResponse(c, err.Error()) return models.ProjectUpdate{}, err } // Convert DTO to model projectUpdate, err := convertUpdateProjectDTOToModel(projectUpdateDTO, id) if err != nil { responses.BadRequestResponse(c, err.Error()) return models.ProjectUpdate{}, err } return projectUpdate, nil } // createProjectWrapper is a wrapper function for models.CreateProject that takes a DTO as input func createProjectWrapper(ctx context.Context, createDTO dto.ProjectCreateDto) (*models.Project, error) { // Convert DTO to model projectCreate, err := convertCreateProjectDTOToModel(createDTO) if err != nil { return nil, err } // Call the original function return models.CreateProject(ctx, projectCreate) }