Refactor Customer API Endpoints and Update Documentation

- Changed customer-related API endpoints to improve clarity and consistency, including renaming and restructuring routes.
- Removed unused query parameters and handlers, streamlining the customer management functionality.
- Updated customer entity and forms to reflect new example values for enhanced API documentation clarity.
- Enhanced response structures to return customer entities directly, simplifying the response handling in API endpoints.
- Updated API documentation to reflect changes in endpoint structure and response formats, ensuring consistency for API consumers.
This commit is contained in:
n.nakhostin
2025-09-06 16:08:53 +03:30
parent 05e7b50ec4
commit e848b625ce
9 changed files with 432 additions and 4437 deletions
+23 -445
View File
@@ -1,7 +1,6 @@
package customer
import (
"strconv"
"tm/internal/user"
"tm/pkg/authorization"
"tm/pkg/logger"
@@ -48,13 +47,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
createdBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
customer, err := h.service.CreateCustomer(c.Request().Context(), form, &createdBy)
customer, err := h.service.Register(c.Request().Context(), form)
if err != nil {
if err.Error() == "customer with this email already exists" ||
err.Error() == "company with this name already exists" ||
@@ -65,7 +58,7 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
return response.InternalServerError(c, "Failed to create customer")
}
return response.Created(c, customer.ToResponse(), "Customer created successfully")
return response.Created(c, customer, "Customer created successfully")
}
// GetCustomerByID retrieves a customer by ID (Web Panel)
@@ -82,9 +75,9 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
// @Security BearerAuth
// @Router /admin/v1/customers/{id} [get]
func (h *Handler) GetCustomerByID(c echo.Context) error {
idStr := c.Param("id")
id := c.Param("id")
customer, err := h.service.GetCustomerByIDWithCompanies(c.Request().Context(), idStr)
customer, err := h.service.GetByID(c.Request().Context(), id)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
@@ -112,19 +105,15 @@ func (h *Handler) GetCustomerByID(c echo.Context) error {
// @Security BearerAuth
// @Router /admin/v1/customers/{id} [put]
func (h *Handler) UpdateCustomer(c echo.Context) error {
idStr := c.Param("id")
id := c.Param("id")
form, err := response.Parse[UpdateCustomerForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
customer, err := h.service.UpdateCustomer(c.Request().Context(), idStr, form, &updatedBy)
customer, err := h.service.Update(c.Request().Context(), id, form)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
@@ -138,7 +127,7 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
return response.InternalServerError(c, "Failed to update customer")
}
return response.Success(c, customer.ToResponse(), "Customer updated successfully")
return response.Success(c, customer, "Customer updated successfully")
}
// DeleteCustomer deletes a customer (Web Panel)
@@ -155,15 +144,9 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
// @Security BearerAuth
// @Router /admin/v1/customers/{id} [delete]
func (h *Handler) DeleteCustomer(c echo.Context) error {
idStr := c.Param("id")
id := c.Param("id")
// Get user ID from JWT token context
deletedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.DeleteCustomer(c.Request().Context(), idStr, &deletedBy)
err := h.service.Delete(c.Request().Context(), id)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
@@ -186,11 +169,6 @@ func (h *Handler) DeleteCustomer(c echo.Context) error {
// @Param type query string false "Filter by customer type" Enums(individual, company, government)
// @Param status query string false "Filter by customer status" Enums(active, inactive, suspended, pending)
// @Param company_id query string false "Filter by company ID" format(uuid)
// @Param industry query string false "Filter by industry"
// @Param is_verified query boolean false "Filter by verification status"
// @Param is_compliant query boolean false "Filter by compliance status"
// @Param language query string false "Filter by preferred language"
// @Param currency query string false "Filter by preferred currency"
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
// @Param sort_by query string false "Field to sort by" Enums(email, company_name, created_at, updated_at, status) default(created_at)
@@ -201,13 +179,15 @@ func (h *Handler) DeleteCustomer(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers [get]
func (h *Handler) ListCustomers(c echo.Context) error {
form, err := response.Parse[ListCustomersForm](c)
func (h *Handler) Search(c echo.Context) error {
form, err := response.Parse[SearchCustomersForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
result, err := h.service.ListCustomers(c.Request().Context(), form)
pagination := response.NewPagination(c)
result, err := h.service.Search(c.Request().Context(), form, pagination)
if err != nil {
return response.InternalServerError(c, "Failed to list customers")
}
@@ -222,7 +202,7 @@ func (h *Handler) ListCustomers(c echo.Context) error {
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Param status body UpdateCustomerStatusForm true "New customer status"
// @Param status body UpdateStatusForm true "New customer status"
// @Success 200 {object} response.APIResponse "Customer status updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
@@ -230,24 +210,16 @@ func (h *Handler) ListCustomers(c echo.Context) error {
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/status [patch]
func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
idStr := c.Param("id")
form, err := response.Parse[UpdateCustomerStatusForm](c)
func (h *Handler) UpdateStatus(c echo.Context) error {
id := c.Param("id")
form, err := response.Parse[UpdateStatusForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
err = h.service.UpdateStatus(c.Request().Context(), id, form)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.UpdateCustomerStatus(c.Request().Context(), idStr, form, &updatedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to update customer status")
}
@@ -256,403 +228,9 @@ func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
}, "Customer status updated successfully")
}
// UpdateCustomerVerification updates customer verification status (Web Panel)
// @Summary Update customer verification status
// @Description Update customer verification and compliance status
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Param verification body UpdateCustomerVerificationForm true "Verification status update"
// @Success 200 {object} response.APIResponse "Customer verification updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/verification [patch]
func (h *Handler) UpdateCustomerVerification(c echo.Context) error {
idStr := c.Param("id")
form, err := response.Parse[UpdateCustomerVerificationForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
updatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.UpdateCustomerVerification(c.Request().Context(), idStr, form, &updatedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to update customer verification")
}
return response.Success(c, map[string]interface{}{
"message": "Customer verification updated successfully",
}, "Customer verification updated successfully")
}
// VerifyCustomer verifies a customer (Web Panel)
// @Summary Verify customer
// @Description Mark a customer as verified
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Success 200 {object} response.APIResponse "Customer verified successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/verify [post]
func (h *Handler) VerifyCustomer(c echo.Context) error {
idStr := c.Param("id")
// Get user ID from JWT token context
verifiedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.VerifyCustomer(c.Request().Context(), idStr, &verifiedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to verify customer")
}
return response.Success(c, map[string]interface{}{
"message": "Customer verified successfully",
}, "Customer verified successfully")
}
// SuspendCustomer suspends a customer (Web Panel)
// @Summary Suspend customer
// @Description Suspend a customer account with optional reason
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Param suspension body SuspendCustomerForm true "Suspension information"
// @Success 200 {object} response.APIResponse "Customer suspended successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/suspend [post]
func (h *Handler) SuspendCustomer(c echo.Context) error {
idStr := c.Param("id")
form, err := response.Parse[SuspendCustomerForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
suspendedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.SuspendCustomer(c.Request().Context(), idStr, form.Reason, &suspendedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to suspend customer")
}
return response.Success(c, map[string]interface{}{
"message": "Customer suspended successfully",
}, "Customer suspended successfully")
}
// ActivateCustomer activates a customer (Web Panel)
// @Summary Activate customer
// @Description Activate a suspended customer account
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Success 200 {object} response.APIResponse "Customer activated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/activate [post]
func (h *Handler) ActivateCustomer(c echo.Context) error {
idStr := c.Param("id")
// Get user ID from JWT token context
activatedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.ActivateCustomer(c.Request().Context(), idStr, &activatedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to activate customer")
}
return response.Success(c, map[string]interface{}{
"message": "Customer activated successfully",
}, "Customer activated successfully")
}
// GetCustomersByCompanyID retrieves customers by company ID (Web Panel)
// @Summary Get customers by company ID
// @Description Retrieve all customers associated with a specific company
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param companyId path string true "Company ID"
// @Param limit query int false "Number of customers to return (default: 10, max: 100)"
// @Param offset query int false "Number of customers to skip (default: 0)"
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse} "Customers retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/company/{companyId} [get]
func (h *Handler) GetCustomersByCompanyID(c echo.Context) error {
companyIDStr := c.Param("companyId")
// Parse pagination parameters
limit := 10 // Default limit
offset := 0 // Default offset
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
customers, total, err := h.service.GetCustomersByCompanyID(c.Request().Context(), companyIDStr, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to get customers by company")
}
// Convert to responses
var customerResponses []*CustomerResponse
for _, customer := range customers {
customerResponses = append(customerResponses, customer.ToResponse())
}
return response.Success(c, map[string]interface{}{
"customers": customerResponses,
"total": total,
"limit": limit,
"offset": offset,
}, "Customers retrieved successfully")
}
// GetCustomersByType retrieves customers by type
// @Summary Get customers by type
// @Description Retrieve customers filtered by their type (individual, company, or government). Useful for administrative reporting and management.
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param type path string true "Customer type" Enums(individual, company, government)
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer type"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/type/{type} [get]
func (h *Handler) GetCustomersByType(c echo.Context) error {
customerTypeStr := c.Param("type")
customerType := CustomerType(customerTypeStr)
// Validate customer type
if customerType != CustomerTypeIndividual && customerType != CustomerTypeCompany && customerType != CustomerTypeGovernment {
return response.BadRequest(c, "Invalid customer type", "Must be individual, company, or government")
}
limit := 20
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
customers, total, err := h.service.GetCustomersByType(c.Request().Context(), customerType, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to retrieve customers by type")
}
var customerResponses []*CustomerResponse
for _, customer := range customers {
customerResponses = append(customerResponses, customer.ToResponse())
}
meta := &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
}
// GetCustomersByStatus retrieves customers by status
// @Summary Get customers by status
// @Description Retrieve customers filtered by their account status (active, inactive, suspended, or pending). Useful for administrative monitoring and management.
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param status path string true "Customer status" Enums(active, inactive, suspended, pending)
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20)
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0)
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer status"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/status/{status} [get]
func (h *Handler) GetCustomersByStatus(c echo.Context) error {
statusStr := c.Param("status")
status := CustomerStatus(statusStr)
// Validate customer status
if status != CustomerStatusActive && status != CustomerStatusInactive && status != CustomerStatusSuspended && status != CustomerStatusPending {
return response.BadRequest(c, "Invalid customer status", "Must be active, inactive, suspended, or pending")
}
limit := 20
if limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset
}
}
customers, total, err := h.service.GetCustomersByStatus(c.Request().Context(), status, limit, offset)
if err != nil {
return response.InternalServerError(c, "Failed to retrieve customers by status")
}
var customerResponses []*CustomerResponse
for _, customer := range customers {
customerResponses = append(customerResponses, customer.ToResponse())
}
meta := &response.Meta{
Total: int(total),
Limit: limit,
Offset: offset,
}
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
}
// AssignCompaniesToCustomer assigns companies to a customer (Web Panel)
// @Summary Assign companies to a customer
// @Description Assign one or more companies to a specific customer.
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Param companies body AssignCompaniesForm true "List of company IDs to assign"
// @Success 200 {object} response.APIResponse "Companies assigned successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/companies/assign [post]
func (h *Handler) AssignCompaniesToCustomer(c echo.Context) error {
idStr := c.Param("id")
form, err := response.Parse[AssignCompaniesForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
assignedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.AssignCompaniesToCustomer(c.Request().Context(), idStr, form.CompanyIDs, &assignedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to assign companies to customer")
}
return response.Success(c, map[string]interface{}{
"message": "Companies assigned successfully",
}, "Companies assigned successfully")
}
// RemoveCompaniesFromCustomer removes companies from a customer (Web Panel)
// @Summary Remove companies from a customer
// @Description Remove one or more companies from a specific customer.
// @Tags Admin-Customers
// @Accept json
// @Produce json
// @Param id path string true "Customer ID"
// @Param companies body RemoveCompaniesForm true "List of company IDs to remove"
// @Success 200 {object} response.APIResponse "Companies removed successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth
// @Router /admin/v1/customers/{id}/companies/remove [post]
func (h *Handler) RemoveCompaniesFromCustomer(c echo.Context) error {
idStr := c.Param("id")
form, err := response.Parse[RemoveCompaniesForm](c)
if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error())
}
// Get user ID from JWT token context
removedBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
err = h.service.RemoveCompaniesFromCustomer(c.Request().Context(), idStr, form.CompanyIDs, &removedBy)
if err != nil {
if err.Error() == "customer not found" {
return response.NotFound(c, "Customer not found")
}
return response.InternalServerError(c, "Failed to remove companies from customer")
}
return response.Success(c, map[string]interface{}{
"message": "Companies removed successfully",
}, "Companies removed successfully")
}
// **************************************************
// * PROFILE HANDLERS *
// **************************************************
// Login handles customer authentication
// @Summary Customer login