279 lines
9.0 KiB
Go
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)
|
|
}
|