e848b625ce
- 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.
367 lines
15 KiB
Go
367 lines
15 KiB
Go
package customer
|
|
|
|
import (
|
|
"tm/internal/user"
|
|
"tm/pkg/authorization"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// Handler handles HTTP requests for customer operations
|
|
type Handler struct {
|
|
service Service
|
|
authService authorization.AuthorizationService
|
|
userHandler *user.Handler
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewHandler creates a new customer handler
|
|
func NewHandler(service Service, userHandler *user.Handler, authService authorization.AuthorizationService, logger logger.Logger) *Handler {
|
|
return &Handler{
|
|
service: service,
|
|
authService: authService,
|
|
userHandler: userHandler,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CreateCustomer creates a new customer (Web Panel)
|
|
// @Summary Create a new customer
|
|
// @Description Create a new customer with comprehensive information including personal details, company information, address, and business details. This endpoint is used by the web panel for full customer registration.
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param customer body CreateCustomerForm true "Customer information including type (individual|company|government), personal details, company info, address, and business details"
|
|
// @Success 201 {object} response.APIResponse{data=CustomerResponse} "Customer created successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
|
// @Failure 409 {object} response.APIResponse "Conflict - Customer with this email/company already exists"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /admin/v1/customers [post]
|
|
func (h *Handler) CreateCustomer(c echo.Context) error {
|
|
form, err := response.Parse[CreateCustomerForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
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" ||
|
|
err.Error() == "company with this registration number already exists" ||
|
|
err.Error() == "company with this tax ID already exists" {
|
|
return response.Conflict(c, err.Error())
|
|
}
|
|
return response.InternalServerError(c, "Failed to create customer")
|
|
}
|
|
|
|
return response.Created(c, customer, "Customer created successfully")
|
|
}
|
|
|
|
// GetCustomerByID retrieves a customer by ID (Web Panel)
|
|
// @Summary Get customer by ID
|
|
// @Description Retrieve detailed customer information by customer ID
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Customer ID"
|
|
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer retrieved 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} [get]
|
|
func (h *Handler) GetCustomerByID(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
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")
|
|
}
|
|
return response.InternalServerError(c, "Failed to get customer")
|
|
}
|
|
|
|
return response.Success(c, customer, "Customer retrieved successfully")
|
|
}
|
|
|
|
// UpdateCustomer updates a customer (Web Panel)
|
|
// @Summary Update customer information
|
|
// @Description Update customer information including personal details, company information, address, and business details
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Customer ID"
|
|
// @Param customer body UpdateCustomerForm true "Updated customer information"
|
|
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer updated successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
|
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
|
// @Failure 409 {object} response.APIResponse "Conflict - Customer with this email/company already exists"
|
|
// @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} [put]
|
|
func (h *Handler) UpdateCustomer(c echo.Context) error {
|
|
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
|
|
|
|
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")
|
|
}
|
|
if err.Error() == "customer with this email already exists" ||
|
|
err.Error() == "company with this name already exists" ||
|
|
err.Error() == "company with this registration number already exists" ||
|
|
err.Error() == "company with this tax ID already exists" {
|
|
return response.Conflict(c, err.Error())
|
|
}
|
|
return response.InternalServerError(c, "Failed to update customer")
|
|
}
|
|
|
|
return response.Success(c, customer, "Customer updated successfully")
|
|
}
|
|
|
|
// DeleteCustomer deletes a customer (Web Panel)
|
|
// @Summary Delete customer
|
|
// @Description Soft delete a customer by setting status to inactive
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Customer ID"
|
|
// @Success 200 {object} response.APIResponse "Customer deleted 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} [delete]
|
|
func (h *Handler) DeleteCustomer(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
err := h.service.Delete(c.Request().Context(), id)
|
|
if err != nil {
|
|
if err.Error() == "customer not found" {
|
|
return response.NotFound(c, "Customer not found")
|
|
}
|
|
return response.InternalServerError(c, "Failed to delete customer")
|
|
}
|
|
|
|
return response.Success(c, map[string]interface{}{
|
|
"message": "Customer deleted successfully",
|
|
}, "Customer deleted successfully")
|
|
}
|
|
|
|
// ListCustomers lists customers with filters and pagination
|
|
// @Summary List customers with filters and pagination
|
|
// @Description Retrieve a paginated list of customers with advanced filtering options including search, type, status, company, industry, verification status, and compliance status. Supports sorting and pagination.
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param search query string false "Search term to filter customers by name, email, or company name"
|
|
// @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 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)
|
|
// @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc)
|
|
// @Success 200 {object} response.APIResponse{data=CustomerListResponse} "Customers retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid query parameters"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid query parameters"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Security BearerAuth
|
|
// @Router /admin/v1/customers [get]
|
|
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())
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
return response.Success(c, result, "Customers retrieved successfully")
|
|
}
|
|
|
|
// UpdateCustomerStatus updates customer status (Web Panel)
|
|
// @Summary Update customer status
|
|
// @Description Update customer account status (active, inactive, suspended, pending)
|
|
// @Tags Admin-Customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Customer ID"
|
|
// @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"
|
|
// @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}/status [patch]
|
|
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())
|
|
}
|
|
|
|
err = h.service.UpdateStatus(c.Request().Context(), id, form)
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to update customer status")
|
|
}
|
|
|
|
return response.Success(c, map[string]interface{}{
|
|
"message": "Customer status updated successfully",
|
|
}, "Customer status updated successfully")
|
|
}
|
|
|
|
// **************************************************
|
|
// * PROFILE HANDLERS *
|
|
// **************************************************
|
|
|
|
// Login handles customer authentication
|
|
// @Summary Customer login
|
|
// @Description Authenticate customer with username (email) and password. Returns access token, refresh token, and customer information upon successful authentication.
|
|
// @Tags Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param login body LoginForm true "Login credentials (username/email and password)"
|
|
// @Success 200 {object} response.APIResponse{data=AuthResponse} "Login successful"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid credentials or inactive account"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /api/v1/profile/login [post]
|
|
func (h *Handler) Login(c echo.Context) error {
|
|
form, err := response.Parse[LoginForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
// Call service
|
|
authResponse, err := h.service.Login(c.Request().Context(), form)
|
|
if err != nil {
|
|
if err.Error() == "invalid credentials" {
|
|
return response.Unauthorized(c, err.Error())
|
|
}
|
|
if err.Error() == "account is not active" {
|
|
return response.Unauthorized(c, err.Error())
|
|
}
|
|
return response.InternalServerError(c, "Failed to authenticate customer")
|
|
}
|
|
|
|
return response.Success(c, authResponse, "Login successful")
|
|
}
|
|
|
|
// RefreshToken handles customer token refresh
|
|
// @Summary Refresh customer access token
|
|
// @Description Refresh access token using a valid refresh token. This allows customers to maintain their session without re-authentication.
|
|
// @Tags Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param refresh body RefreshTokenForm true "Refresh token for generating new access token"
|
|
// @Success 200 {object} response.APIResponse{data=AuthResponse} "Token refreshed successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid refresh token or feature not implemented"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired refresh token"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /api/v1/profile/refresh-token [post]
|
|
func (h *Handler) RefreshToken(c echo.Context) error {
|
|
form, err := response.Parse[RefreshTokenForm](c)
|
|
if err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
// Call service
|
|
authResponse, err := h.service.RefreshToken(c.Request().Context(), form.RefreshToken)
|
|
if err != nil {
|
|
if err.Error() == "token refresh not implemented yet" {
|
|
return response.BadRequest(c, "Token refresh not implemented yet", "")
|
|
}
|
|
return response.InternalServerError(c, "Failed to refresh token")
|
|
}
|
|
|
|
return response.Success(c, authResponse, "Token refreshed successfully")
|
|
}
|
|
|
|
// GetProfile retrieves customer profile information (Mobile)
|
|
// @Summary Get customer profile
|
|
// @Description Retrieve current customer profile information
|
|
// @Tags Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Profile retrieved successfully"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing token"
|
|
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /api/v1/profile [get]
|
|
func (h *Handler) GetProfile(c echo.Context) error {
|
|
// Extract customer ID from JWT token context
|
|
customerID, err := GetCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Customer not authenticated")
|
|
}
|
|
|
|
resp, err := h.service.GetProfileWithCompanies(c.Request().Context(), customerID)
|
|
if err != nil {
|
|
if err.Error() == "customer not found" {
|
|
return response.NotFound(c, "Customer not found")
|
|
}
|
|
return response.InternalServerError(c, "Failed to get customer profile")
|
|
}
|
|
|
|
return response.Success(c, resp, "Profile retrieved successfully")
|
|
}
|
|
|
|
// Logout handles customer logout (Mobile)
|
|
// @Summary Customer logout
|
|
// @Description Logout customer and invalidate access token
|
|
// @Tags Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse "Logout successful"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing token"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /api/v1/profile/logout [delete]
|
|
func (h *Handler) Logout(c echo.Context) error {
|
|
// Extract customer ID from JWT token context
|
|
customerID, err := GetCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Customer not authenticated")
|
|
}
|
|
|
|
// Get access token from context
|
|
accessToken, err := GetAccessTokenFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Access token not found")
|
|
}
|
|
|
|
err = h.service.Logout(c.Request().Context(), customerID, accessToken)
|
|
if err != nil {
|
|
// Don't return error here as the customer should still be logged out
|
|
h.logger.Warn("Failed to invalidate access token during logout", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"customer_id": customerID,
|
|
})
|
|
}
|
|
|
|
return response.Success(c, map[string]interface{}{
|
|
"message": "Logout successful",
|
|
}, "Logout successful")
|
|
}
|