360 lines
11 KiB
Go

package handlers
import (
"fmt"
"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"
)
// 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) {
// 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)
}
// 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) {
// Get projects with customers from the database
projects, err := models.GetAllProjectsWithCustomers(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)
}
// 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) {
// 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(), 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)
}
// 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) {
// Parse customer ID from URL
customerIDStr := c.Param("customerId")
customerID, err := ulid.Parse(customerIDStr)
if err != nil {
utils.BadRequestResponse(c, "Invalid customer ID format")
return
}
// Get projects from the database
projects, err := models.GetProjectsByCustomerID(c.Request.Context(), customerID)
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)
}
// 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) {
// Parse request body
var projectCreateDTO dto.ProjectCreateDto
if err := c.ShouldBindJSON(&projectCreateDTO); err != nil {
utils.BadRequestResponse(c, "Invalid request body: "+err.Error())
return
}
// Convert DTO to model
projectCreate, err := convertCreateProjectDTOToModel(projectCreateDTO)
if err != nil {
utils.BadRequestResponse(c, err.Error())
return
}
// Create project in the database
project, err := models.CreateProject(c.Request.Context(), projectCreate)
if err != nil {
utils.InternalErrorResponse(c, "Error creating project: "+err.Error())
return
}
// Convert to DTO
projectDTO := convertProjectToDTO(project)
utils.SuccessResponse(c, http.StatusCreated, projectDTO)
}
// 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) {
// Parse ID from URL
idStr := c.Param("id")
id, err := ulid.Parse(idStr)
if err != nil {
utils.BadRequestResponse(c, "Invalid project ID format")
return
}
// Parse request body
var projectUpdateDTO dto.ProjectUpdateDto
if err := c.ShouldBindJSON(&projectUpdateDTO); err != nil {
utils.BadRequestResponse(c, "Invalid request body: "+err.Error())
return
}
// Set ID from URL
projectUpdateDTO.ID = id.String()
// Convert DTO to model
projectUpdate, err := convertUpdateProjectDTOToModel(projectUpdateDTO)
if err != nil {
utils.BadRequestResponse(c, err.Error())
return
}
// Update project in the database
project, err := models.UpdateProject(c.Request.Context(), projectUpdate)
if err != nil {
utils.InternalErrorResponse(c, "Error updating 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)
}
// 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) {
// Parse ID from URL
idStr := c.Param("id")
id, err := ulid.Parse(idStr)
if err != nil {
utils.BadRequestResponse(c, "Invalid project ID format")
return
}
// Delete project from the database
err = models.DeleteProject(c.Request.Context(), id)
if err != nil {
utils.InternalErrorResponse(c, "Error deleting project: "+err.Error())
return
}
utils.SuccessResponse(c, http.StatusNoContent, nil)
}
// Helper functions for DTO conversion
func convertProjectToDTO(project *models.Project) dto.ProjectDto {
customerID := 0
if project.CustomerID.Compare(ulid.ULID{}) != 0 {
// This is a simplification, adjust as needed
customerID = int(project.CustomerID.Time())
}
return dto.ProjectDto{
ID: project.ID.String(),
CreatedAt: project.CreatedAt,
UpdatedAt: project.UpdatedAt,
Name: project.Name,
CustomerID: customerID,
}
}
func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) {
// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
customerID, err := customerIDToULID(dto.CustomerID)
if err != nil {
return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err)
}
return models.ProjectCreate{
Name: dto.Name,
CustomerID: customerID,
}, nil
}
func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto) (models.ProjectUpdate, error) {
id, _ := ulid.Parse(dto.ID)
update := models.ProjectUpdate{
ID: id,
}
if dto.Name != nil {
update.Name = dto.Name
}
if dto.CustomerID != nil {
// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
customerID, err := customerIDToULID(*dto.CustomerID)
if err != nil {
return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
}
update.CustomerID = &customerID
}
return update, nil
}
// Helper function to convert customer ID from int to ULID
func customerIDToULID(id int) (ulid.ULID, error) {
// This is a simplification, in a real application you would need to
// fetch the actual ULID from the database or use a proper conversion method
// For now, we'll create a deterministic ULID based on the int value
entropy := ulid.Monotonic(nil, 0)
timestamp := uint64(id)
// Create a new ULID with the timestamp and entropy
return ulid.MustNew(timestamp, entropy), nil
}