279 lines
9.0 KiB
Go

package handlers
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/timetracker/backend/internal/api/dto"
"github.com/timetracker/backend/internal/api/middleware"
"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"
)
// CustomerHandler handles customer-related API endpoints
type CustomerHandler struct{}
// NewCustomerHandler creates a new CustomerHandler
func NewCustomerHandler() *CustomerHandler {
return &CustomerHandler{}
}
// GetCustomers handles GET /customers
//
// @Summary Get all customers
// @Description Get a list of all customers
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} utils.Response{data=[]dto.CustomerDto}
// @Failure 401 {object} utils.Response{error=utils.ErrorInfo}
// @Failure 500 {object} utils.Response{error=utils.ErrorInfo}
// @Router /customers [get]
func (h *CustomerHandler) GetCustomers(c *gin.Context) {
utils.HandleGetAll(c, models.GetAllCustomers, convertCustomerToDTO, "customers")
}
// GetCustomerByID handles GET /customers/:id
//
// @Summary Get customer by ID
// @Description Get a customer by its ID
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Customer ID"
// @Success 200 {object} utils.Response{data=dto.CustomerDto}
// @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 /customers/{id} [get]
func (h *CustomerHandler) GetCustomerByID(c *gin.Context) {
utils.HandleGetByID(c, models.GetCustomerByID, convertCustomerToDTO, "customer")
}
// GetCustomersByCompanyID handles GET /customers/company/:companyId
//
// @Summary Get customers by company ID
// @Description Get a list of customers for a specific company
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param companyId path int true "Company ID"
// @Success 200 {object} utils.Response{data=[]dto.CustomerDto}
// @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 /customers/company/{companyId} [get]
func (h *CustomerHandler) GetCustomersByCompanyID(c *gin.Context) {
// Parse company ID from URL
companyIDStr := c.Param("companyId")
companyID, err := parseCompanyID(companyIDStr)
if err != nil {
responses.BadRequestResponse(c, "Invalid company ID format")
return
}
// Create a wrapper function that takes a ULID but calls the original function with an int
getByCompanyIDFn := func(ctx context.Context, _ types.ULID) ([]models.Customer, error) {
return models.GetCustomersByCompanyID(ctx, companyID)
}
// Get customers from the database and convert to DTOs
customers, err := getByCompanyIDFn(c.Request.Context(), types.ULID{})
if err != nil {
responses.InternalErrorResponse(c, "Error retrieving customers: "+err.Error())
return
}
// Convert to DTOs
customerDTOs := utils.ConvertToDTO(customers, convertCustomerToDTO)
responses.SuccessResponse(c, http.StatusOK, customerDTOs)
}
// CreateCustomer handles POST /customers
//
// @Summary Create a new customer
// @Description Create a new customer
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param customer body dto.CustomerCreateDto true "Customer data"
// @Success 201 {object} utils.Response{data=dto.CustomerDto}
// @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 /customers [post]
func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
// We need to use a custom wrapper for CreateCustomer because we need to get the user ID from the context
userID, err := middleware.GetUserIDFromContext(c)
if err != nil {
responses.UnauthorizedResponse(c, "User not authenticated")
return
}
// Use a closure to capture the userID
createFn := func(ctx context.Context, createDTO dto.CustomerCreateDto) (*models.Customer, error) {
return createCustomerWrapper(ctx, createDTO, userID)
}
utils.HandleCreate(c, createFn, convertCustomerToDTO, "customer")
}
// UpdateCustomer handles PUT /customers/:id
//
// @Summary Update a customer
// @Description Update an existing customer
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Customer ID"
// @Param customer body dto.CustomerUpdateDto true "Customer data"
// @Success 200 {object} utils.Response{data=dto.CustomerDto}
// @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 /customers/{id} [put]
func (h *CustomerHandler) UpdateCustomer(c *gin.Context) {
utils.HandleUpdate(c, models.UpdateCustomer, convertCustomerToDTO, prepareCustomerUpdate, "customer")
}
// DeleteCustomer handles DELETE /customers/:id
//
// @Summary Delete a customer
// @Description Delete a customer by its ID
// @Tags customers
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path string true "Customer 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 /customers/{id} [delete]
func (h *CustomerHandler) DeleteCustomer(c *gin.Context) {
utils.HandleDelete(c, models.DeleteCustomer, "customer")
}
// Helper functions for DTO conversion
func convertCustomerToDTO(customer *models.Customer) dto.CustomerDto {
var companyID *string
if customer.CompanyID != nil {
s := customer.CompanyID.String()
companyID = &s
}
return dto.CustomerDto{
ID: customer.ID.String(),
CreatedAt: customer.CreatedAt,
UpdatedAt: customer.UpdatedAt,
Name: customer.Name,
CompanyID: companyID,
}
}
func convertCreateCustomerDTOToModel(dto dto.CustomerCreateDto) (models.CustomerCreate, error) {
var companyID *types.ULID
if dto.CompanyID != nil {
wrapper, err := types.ULIDFromString(*dto.CompanyID) // Ignoring error, validation happens in the model
if err != nil {
return models.CustomerCreate{}, fmt.Errorf("invalid company ID: %w", err)
}
companyID = &wrapper
}
create := models.CustomerCreate{
Name: dto.Name,
CompanyID: companyID,
}
return create, nil
}
func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) (models.CustomerUpdate, error) {
id, err := types.ULIDFromString(dto.ID)
if err != nil {
return models.CustomerUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
}
update := models.CustomerUpdate{
ID: id,
}
if dto.Name != nil {
update.Name = dto.Name
}
if dto.CompanyID.Valid {
companyID, err := types.ULIDFromString(*dto.CompanyID.Value)
if err != nil {
return models.CustomerUpdate{}, fmt.Errorf("invalid company ID: %w", err)
}
update.CompanyID = &companyID
} else {
update.CompanyID = nil
}
return update, nil
}
// Helper function to parse company ID from string
func parseCompanyID(idStr string) (int, error) {
var id int
_, err := fmt.Sscanf(idStr, "%d", &id)
return id, err
}
// prepareCustomerUpdate prepares the customer update object by parsing the ID, binding the JSON, and converting the DTO to a model
func prepareCustomerUpdate(c *gin.Context) (models.CustomerUpdate, error) {
// Parse ID from URL
id, err := utils.ParseID(c, "id")
if err != nil {
responses.BadRequestResponse(c, "Invalid customer ID format")
return models.CustomerUpdate{}, err
}
// Parse request body
var customerUpdateDTO dto.CustomerUpdateDto
if err := utils.BindJSON(c, &customerUpdateDTO); err != nil {
responses.BadRequestResponse(c, err.Error())
return models.CustomerUpdate{}, err
}
// Set ID from URL
customerUpdateDTO.ID = id.String()
// Convert DTO to model
customerUpdate, err := convertUpdateCustomerDTOToModel(customerUpdateDTO)
if err != nil {
responses.BadRequestResponse(c, "Invalid request body: "+err.Error())
return models.CustomerUpdate{}, err
}
return customerUpdate, nil
}
// createCustomerWrapper is a wrapper function for models.CreateCustomer that takes a DTO as input
func createCustomerWrapper(ctx context.Context, createDTO dto.CustomerCreateDto, userID types.ULID) (*models.Customer, error) {
// Convert DTO to model
customerCreate, err := convertCreateCustomerDTOToModel(createDTO)
if err != nil {
return nil, err
}
// Set the owner user ID
customerCreate.OwnerUserID = &userID
// Call the original function
return models.CreateCustomer(ctx, customerCreate)
}