package customer import ( "strconv" "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()) } // 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) 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.ToResponse(), "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 { idStr := c.Param("id") customer, err := h.service.GetCustomerByIDWithCompanies(c.Request().Context(), idStr) 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 { idStr := 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) 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.ToResponse(), "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 { idStr := 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) 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 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) // @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) ListCustomers(c echo.Context) error { form, err := response.Parse[ListCustomersForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } result, err := h.service.ListCustomersWithCompanies(c.Request().Context(), form) if err != nil { return response.InternalServerError(c, "Failed to list customers") } return response.Success(c, result, "Customers retrieved successfully") } // ListCustomersWithCompanies lists customers with companies and filters // @Summary List customers with companies and filters // @Description Retrieve a paginated list of customers with their assigned companies and 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 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) // @Param sort_order query string false "Sort order" Enums(asc, desc) default(desc) // @Success 200 {object} response.APIResponse{data=CustomerListResponse} "Customers with companies 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/with-companies [get] func (h *Handler) ListCustomersWithCompanies(c echo.Context) error { form, err := response.Parse[ListCustomersForm](c) if err != nil { return response.ValidationError(c, "Invalid request data", err.Error()) } result, err := h.service.ListCustomersWithCompanies(c.Request().Context(), form) if err != nil { return response.InternalServerError(c, "Failed to list customers with companies") } return response.Success(c, result, "Customers with companies 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 UpdateCustomerStatusForm 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) UpdateCustomerStatus(c echo.Context) error { idStr := c.Param("id") form, err := response.Parse[UpdateCustomerStatusForm](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.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") } return response.Success(c, map[string]interface{}{ "message": "Customer status updated successfully", }, "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") } // 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") } customer, err := h.service.GetProfile(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, customer.ToResponse(), "Profile retrieved successfully") } // GetProfileWithCompanies retrieves customer profile with companies (Mobile) // @Summary Get customer profile with companies // @Description Retrieve current customer profile information along with their assigned companies. // @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/with-companies [get] func (h *Handler) GetProfileWithCompanies(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") } customer, 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 with companies") } return response.Success(c, customer, "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") }