Implement customer management domain with authorization and API enhancements
- Introduced a new customer management domain, including entities, services, handlers, and forms for customer operations. - Added JWT-based user authorization settings in the configuration file for both user and customer management. - Updated API endpoints to reflect the new structure, including changes to health check and user management routes. - Enhanced Swagger documentation to include new customer-related endpoints and authorization details. - Refactored the Makefile to include a target for generating API documentation. - Removed obsolete documentation files to streamline the project structure.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CustomerStatus represents customer account status
|
||||
type CustomerStatus string
|
||||
|
||||
const (
|
||||
CustomerStatusActive CustomerStatus = "active"
|
||||
CustomerStatusInactive CustomerStatus = "inactive"
|
||||
CustomerStatusSuspended CustomerStatus = "suspended"
|
||||
CustomerStatusPending CustomerStatus = "pending"
|
||||
)
|
||||
|
||||
// CustomerType represents the type of customer
|
||||
type CustomerType string
|
||||
|
||||
const (
|
||||
CustomerTypeIndividual CustomerType = "individual"
|
||||
CustomerTypeCompany CustomerType = "company"
|
||||
CustomerTypeGovernment CustomerType = "government"
|
||||
)
|
||||
|
||||
// Customer represents a customer in the tender management system
|
||||
type Customer struct {
|
||||
ID uuid.UUID `bson:"_id"`
|
||||
Type CustomerType `bson:"type"`
|
||||
Status CustomerStatus `bson:"status"`
|
||||
CompanyID *uuid.UUID `bson:"company_id,omitempty"`
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `bson:"first_name,omitempty"`
|
||||
LastName *string `bson:"last_name,omitempty"`
|
||||
FullName *string `bson:"full_name,omitempty"`
|
||||
Username string `bson:"username"` // Username for authentication
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"` // Hashed password for authentication
|
||||
Phone *string `bson:"phone,omitempty"`
|
||||
Mobile *string `bson:"mobile,omitempty"`
|
||||
|
||||
// Company customer fields
|
||||
CompanyName *string `bson:"company_name,omitempty"`
|
||||
RegistrationNumber *string `bson:"registration_number,omitempty"`
|
||||
TaxID *string `bson:"tax_id,omitempty"`
|
||||
Industry *string `bson:"industry,omitempty"`
|
||||
|
||||
// Address information
|
||||
Address *Address `bson:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `bson:"business_type,omitempty"`
|
||||
EmployeeCount *int `bson:"employee_count,omitempty"`
|
||||
AnnualRevenue *float64 `bson:"annual_revenue,omitempty"`
|
||||
FoundedYear *int `bson:"founded_year,omitempty"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPerson `bson:"contact_person,omitempty"`
|
||||
|
||||
// Verification and compliance
|
||||
IsVerified bool `bson:"is_verified"`
|
||||
IsCompliant bool `bson:"is_compliant"`
|
||||
ComplianceNotes *string `bson:"compliance_notes,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language string `bson:"language"` // Default: "en"
|
||||
Currency string `bson:"currency"` // Default: "USD"
|
||||
Timezone string `bson:"timezone"` // Default: "UTC"
|
||||
|
||||
// Audit fields
|
||||
CreatedAt int64 `bson:"created_at"` // Unix timestamp
|
||||
UpdatedAt int64 `bson:"updated_at"` // Unix timestamp
|
||||
CreatedBy *uuid.UUID `bson:"created_by,omitempty"`
|
||||
UpdatedBy *uuid.UUID `bson:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
// Address represents a customer's address
|
||||
type Address struct {
|
||||
Street string `bson:"street"`
|
||||
City string `bson:"city"`
|
||||
State string `bson:"state"`
|
||||
PostalCode string `bson:"postal_code"`
|
||||
Country string `bson:"country"`
|
||||
AddressType string `bson:"address_type"` // billing, shipping, etc.
|
||||
IsDefault bool `bson:"is_default"`
|
||||
}
|
||||
|
||||
// ContactPerson represents a contact person for company customers
|
||||
type ContactPerson struct {
|
||||
FirstName string `bson:"first_name"`
|
||||
LastName string `bson:"last_name"`
|
||||
FullName string `bson:"full_name"`
|
||||
Position string `bson:"position"`
|
||||
Email string `bson:"email"`
|
||||
Phone string `bson:"phone"`
|
||||
Mobile *string `bson:"mobile,omitempty"`
|
||||
IsPrimary bool `bson:"is_primary"`
|
||||
}
|
||||
|
||||
// CustomerResponse represents the customer data sent in API responses
|
||||
type CustomerResponse struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
CompanyID *string `json:"company_id,omitempty"`
|
||||
FirstName *string `json:"first_name,omitempty"`
|
||||
LastName *string `json:"last_name,omitempty"`
|
||||
FullName *string `json:"full_name,omitempty"`
|
||||
Email string `json:"email"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Mobile *string `json:"mobile,omitempty"`
|
||||
CompanyName *string `json:"company_name,omitempty"`
|
||||
RegistrationNumber *string `json:"registration_number,omitempty"`
|
||||
TaxID *string `json:"tax_id,omitempty"`
|
||||
Industry *string `json:"industry,omitempty"`
|
||||
Address *Address `json:"address,omitempty"`
|
||||
BusinessType *string `json:"business_type,omitempty"`
|
||||
EmployeeCount *int `json:"employee_count,omitempty"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue,omitempty"`
|
||||
FoundedYear *int `json:"founded_year,omitempty"`
|
||||
ContactPerson *ContactPerson `json:"contact_person,omitempty"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsCompliant bool `json:"is_compliant"`
|
||||
ComplianceNotes *string `json:"compliance_notes,omitempty"`
|
||||
Language string `json:"language"`
|
||||
Username string `json:"username"`
|
||||
Currency string `json:"currency"`
|
||||
Timezone string `json:"timezone"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
CreatedBy *string `json:"created_by,omitempty"`
|
||||
UpdatedBy *string `json:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
// ToResponse converts Customer to CustomerResponse
|
||||
func (c *Customer) ToResponse() *CustomerResponse {
|
||||
companyID := ""
|
||||
if c.CompanyID != nil {
|
||||
companyID = c.CompanyID.String()
|
||||
}
|
||||
|
||||
createdBy := ""
|
||||
if c.CreatedBy != nil {
|
||||
createdBy = c.CreatedBy.String()
|
||||
}
|
||||
|
||||
updatedBy := ""
|
||||
if c.UpdatedBy != nil {
|
||||
updatedBy = c.UpdatedBy.String()
|
||||
}
|
||||
|
||||
return &CustomerResponse{
|
||||
ID: c.ID.String(),
|
||||
Type: string(c.Type),
|
||||
Status: string(c.Status),
|
||||
CompanyID: &companyID,
|
||||
FirstName: c.FirstName,
|
||||
LastName: c.LastName,
|
||||
FullName: c.FullName,
|
||||
Username: c.Username,
|
||||
Email: c.Email,
|
||||
Phone: c.Phone,
|
||||
Mobile: c.Mobile,
|
||||
CompanyName: c.CompanyName,
|
||||
RegistrationNumber: c.RegistrationNumber,
|
||||
TaxID: c.TaxID,
|
||||
Industry: c.Industry,
|
||||
Address: c.Address,
|
||||
BusinessType: c.BusinessType,
|
||||
EmployeeCount: c.EmployeeCount,
|
||||
AnnualRevenue: c.AnnualRevenue,
|
||||
FoundedYear: c.FoundedYear,
|
||||
ContactPerson: c.ContactPerson,
|
||||
IsVerified: c.IsVerified,
|
||||
IsCompliant: c.IsCompliant,
|
||||
ComplianceNotes: c.ComplianceNotes,
|
||||
Language: c.Language,
|
||||
Currency: c.Currency,
|
||||
Timezone: c.Timezone,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
CreatedBy: &createdBy,
|
||||
UpdatedBy: &updatedBy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package customer
|
||||
|
||||
// CreateCustomerForm represents the form for creating a new customer
|
||||
type CreateCustomerForm struct {
|
||||
Type string `json:"type" valid:"required,in(individual|company|government)"`
|
||||
CompanyID *string `json:"company_id,omitempty" valid:"optional,uuid"`
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company customer fields
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"`
|
||||
TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
|
||||
// Address information
|
||||
Address *AddressForm `json:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"`
|
||||
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"`
|
||||
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerForm represents the form for updating a customer
|
||||
type UpdateCustomerForm struct {
|
||||
Type *string `json:"type,omitempty" valid:"optional,in(individual|company|government)"`
|
||||
CompanyID *string `json:"company_id,omitempty" valid:"optional,uuid"`
|
||||
|
||||
// Individual customer fields
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"`
|
||||
Email *string `json:"email,omitempty" valid:"optional,email"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
|
||||
// Company customer fields
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
RegistrationNumber *string `json:"registration_number,omitempty" valid:"optional,length(5|50)"`
|
||||
TaxID *string `json:"tax_id,omitempty" valid:"optional,length(5|50)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
|
||||
// Address information
|
||||
Address *AddressForm `json:"address,omitempty"`
|
||||
|
||||
// Business information
|
||||
BusinessType *string `json:"business_type,omitempty" valid:"optional,length(2|100)"`
|
||||
EmployeeCount *int `json:"employee_count,omitempty" valid:"optional,range(1|100000)"`
|
||||
AnnualRevenue *float64 `json:"annual_revenue,omitempty" valid:"optional,range(0|999999999999)"`
|
||||
FoundedYear *int `json:"founded_year,omitempty" valid:"optional,range(1800|2100)"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPersonForm `json:"contact_person,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
}
|
||||
|
||||
// ListCustomersForm represents the form for listing customers with filters
|
||||
type ListCustomersForm struct {
|
||||
Search *string `query:"search" valid:"optional"`
|
||||
Type *string `query:"type" valid:"optional,in(individual|company|government)"`
|
||||
Status *string `query:"status" valid:"optional,in(active|inactive|suspended|pending)"`
|
||||
CompanyID *string `query:"company_id" valid:"optional,uuid"`
|
||||
Industry *string `query:"industry" valid:"optional"`
|
||||
IsVerified *bool `query:"is_verified" valid:"optional"`
|
||||
IsCompliant *bool `query:"is_compliant" valid:"optional"`
|
||||
Language *string `query:"language" valid:"optional"`
|
||||
Currency *string `query:"currency" valid:"optional"`
|
||||
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
||||
Offset *int `query:"offset" valid:"optional,min(0)"`
|
||||
SortBy *string `query:"sort_by" valid:"optional,in(email|company_name|created_at|updated_at|status)"`
|
||||
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerStatusForm represents the form for updating customer status
|
||||
type UpdateCustomerStatusForm struct {
|
||||
Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerVerificationForm represents the form for updating customer verification
|
||||
type UpdateCustomerVerificationForm struct {
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsCompliant bool `json:"is_compliant"`
|
||||
ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"`
|
||||
}
|
||||
|
||||
// AddressForm represents the form for customer address
|
||||
type AddressForm struct {
|
||||
Street string `json:"street" valid:"required,length(5|200)"`
|
||||
City string `json:"city" valid:"required,length(2|100)"`
|
||||
State string `json:"state" valid:"required,length(2|100)"`
|
||||
PostalCode string `json:"postal_code" valid:"required,length(3|20)"`
|
||||
Country string `json:"country" valid:"required,length(2|100)"`
|
||||
AddressType string `json:"address_type" valid:"required,in(billing|shipping|both)"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
}
|
||||
|
||||
// ContactPersonForm represents the form for contact person
|
||||
type ContactPersonForm struct {
|
||||
FirstName string `json:"first_name" valid:"required,length(2|50)"`
|
||||
LastName string `json:"last_name" valid:"required,length(2|50)"`
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)"`
|
||||
Position string `json:"position" valid:"required,length(2|100)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Phone string `json:"phone" valid:"required,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
IsPrimary bool `json:"is_primary"`
|
||||
}
|
||||
|
||||
// CustomerListResponse represents the response for listing customers
|
||||
type CustomerListResponse struct {
|
||||
Customers []*CustomerResponse `json:"customers"`
|
||||
Total int64 `json:"total"`
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// Mobile-specific forms (simplified for mobile app)
|
||||
type CreateCustomerMobileForm struct {
|
||||
Type string `json:"type" valid:"required,in(individual|company)"`
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username string `json:"username" valid:"required,alphanum,length(3|30)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
}
|
||||
|
||||
type UpdateCustomerMobileForm struct {
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
}
|
||||
|
||||
// Customer Authentication DTOs
|
||||
type LoginForm struct {
|
||||
Username string `json:"username" valid:"required"`
|
||||
Password string `json:"password" valid:"required"`
|
||||
}
|
||||
|
||||
type RefreshTokenForm struct {
|
||||
RefreshToken string `json:"refresh_token" valid:"required"`
|
||||
}
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
OldPassword string `json:"old_password" valid:"required"`
|
||||
NewPassword string `json:"new_password" valid:"required,length(8|128)"`
|
||||
}
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
FirstName *string `json:"first_name,omitempty" valid:"optional,length(2|50)"`
|
||||
LastName *string `json:"last_name,omitempty" valid:"optional,length(2|50)"`
|
||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)"`
|
||||
Username *string `json:"username,omitempty" valid:"optional,alphanum,length(3|30)"`
|
||||
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)"`
|
||||
Mobile *string `json:"mobile,omitempty" valid:"optional,length(10|20)"`
|
||||
CompanyName *string `json:"company_name,omitempty" valid:"optional,length(2|200)"`
|
||||
Industry *string `json:"industry,omitempty" valid:"optional,length(2|100)"`
|
||||
Language *string `json:"language,omitempty" valid:"optional,in(en|ar|fr|es|de|zh|ja|ko)"`
|
||||
Currency *string `json:"currency,omitempty" valid:"optional,in(USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR|BRL)"`
|
||||
Timezone *string `json:"timezone,omitempty" valid:"optional,length(3|50)"`
|
||||
}
|
||||
|
||||
// Customer Authentication Response DTOs
|
||||
type AuthResponse struct {
|
||||
Customer *CustomerResponse `json:"customer"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
@@ -0,0 +1,741 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"tm/internal/user"
|
||||
"tm/pkg/authorization"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"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,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes registers customer routes
|
||||
func (h *Handler) RegisterRoutes(e *echo.Echo) {
|
||||
|
||||
adminV1 := e.Group("/admin/v1/customers")
|
||||
adminV1.Use(h.userHandler.AuthMiddleware())
|
||||
adminV1.POST("", h.CreateCustomer)
|
||||
adminV1.GET("", h.ListCustomers)
|
||||
adminV1.GET("/:id", h.GetCustomerByID)
|
||||
adminV1.PUT("/:id", h.UpdateCustomer)
|
||||
adminV1.DELETE("/:id", h.DeleteCustomer)
|
||||
adminV1.PATCH("/:id/status", h.UpdateCustomerStatus)
|
||||
adminV1.PATCH("/:id/verification", h.UpdateCustomerVerification)
|
||||
adminV1.POST("/:id/verify", h.VerifyCustomer)
|
||||
adminV1.POST("/:id/suspend", h.SuspendCustomer)
|
||||
adminV1.POST("/:id/activate", h.ActivateCustomer)
|
||||
adminV1.GET("/company/:companyId", h.GetCustomersByCompanyID)
|
||||
adminV1.GET("/type/:type", h.GetCustomersByType)
|
||||
adminV1.GET("/status/:status", h.GetCustomersByStatus)
|
||||
|
||||
// Mobile-specific endpoints
|
||||
v1 := e.Group("/api/v1")
|
||||
authorizationGP := v1.Group("")
|
||||
authorizationGP.POST("/login", h.Login)
|
||||
authorizationGP.POST("/refresh-token", h.RefreshToken)
|
||||
|
||||
profileGP := v1.Group("")
|
||||
profileGP.Use(h.AuthMiddleware())
|
||||
profileGP.GET("/profile", h.GetProfile)
|
||||
profileGP.DELETE("/logout", h.Logout)
|
||||
|
||||
}
|
||||
|
||||
// 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 Customers-Admin
|
||||
// @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())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
createdBy := uuid.New() // This should come from JWT token
|
||||
|
||||
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
|
||||
// @Summary Get customer by ID
|
||||
// @Description Retrieve detailed customer information by unique customer ID. Returns comprehensive customer data including personal details, company information, address, verification status, and audit fields.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID format"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
customer, err := h.service.GetCustomerByID(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 retrieve customer")
|
||||
}
|
||||
|
||||
return response.Success(c, customer.ToResponse(), "Customer retrieved successfully")
|
||||
}
|
||||
|
||||
// UpdateCustomer updates a customer (Web Panel)
|
||||
// @Summary Update customer information
|
||||
// @Description Update existing customer information with comprehensive details. All fields are optional and only provided fields will be updated. This endpoint is used by the web panel for full customer management.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Param customer body UpdateCustomerForm true "Customer update information - all fields are optional"
|
||||
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
form, err := response.Parse[UpdateCustomerForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
updatedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
customer, err := h.service.UpdateCustomer(c.Request().Context(), id, 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 (soft delete)
|
||||
// @Summary Delete customer
|
||||
// @Description Soft delete a customer by setting status to inactive. The customer record is preserved but marked as deleted. This is a reversible operation.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Success 200 {object} response.APIResponse "Customer deleted successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID format"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
deletedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.DeleteCustomer(c.Request().Context(), id, &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, nil, "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 Customers-Admin
|
||||
// @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.ListCustomers(c.Request().Context(), form)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to list customers")
|
||||
}
|
||||
|
||||
return response.Success(c, result, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// UpdateCustomerStatus updates customer status
|
||||
// @Summary Update customer status
|
||||
// @Description Update the status of a customer account. Valid statuses include active, inactive, suspended, and pending. This affects the customer's ability to access the system.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Param status body UpdateCustomerStatusForm true "Status update information"
|
||||
// @Success 200 {object} response.APIResponse "Customer status updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or status"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid status value"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
form, err := response.Parse[UpdateCustomerStatusForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
updatedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.UpdateCustomerStatus(c.Request().Context(), id, 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, nil, "Customer status updated successfully")
|
||||
}
|
||||
|
||||
// UpdateCustomerVerification updates customer verification status
|
||||
// @Summary Update customer verification and compliance status
|
||||
// @Description Update the verification and compliance status of a customer. This includes marking customers as verified and compliant, with optional compliance notes for audit purposes.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Param verification body UpdateCustomerVerificationForm true "Verification and compliance update information"
|
||||
// @Success 200 {object} response.APIResponse "Customer verification updated successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid verification 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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
form, err := response.Parse[UpdateCustomerVerificationForm](c)
|
||||
if err != nil {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
updatedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.UpdateCustomerVerification(c.Request().Context(), id, 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, nil, "Customer verification updated successfully")
|
||||
}
|
||||
|
||||
// VerifyCustomer verifies a customer
|
||||
// @Summary Verify customer
|
||||
// @Description Mark a customer as verified. This is a simple verification action that sets the customer's verification status to true.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
verifiedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.VerifyCustomer(c.Request().Context(), id, &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, nil, "Customer verified successfully")
|
||||
}
|
||||
|
||||
// SuspendCustomer suspends a customer
|
||||
// @Summary Suspend customer account
|
||||
// @Description Suspend a customer account by setting their status to suspended. Suspended customers cannot access the system. A reason for suspension must be provided.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Param reason body object true "Suspension reason" SchemaExample({"reason": "Violation of terms of service"})
|
||||
// @Success 200 {object} response.APIResponse "Customer suspended successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or missing reason"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
var requestBody map[string]string
|
||||
if err := c.Bind(&requestBody); err != nil {
|
||||
return response.BadRequest(c, "Invalid request body", err.Error())
|
||||
}
|
||||
|
||||
reason := requestBody["reason"]
|
||||
if reason == "" {
|
||||
return response.BadRequest(c, "Suspension reason is required", "")
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
suspendedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.SuspendCustomer(c.Request().Context(), id, 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, nil, "Customer suspended successfully")
|
||||
}
|
||||
|
||||
// ActivateCustomer activates a customer
|
||||
// @Summary Activate suspended customer account
|
||||
// @Description Reactivate a suspended customer account by setting their status back to active. This reverses the suspension action.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
}
|
||||
|
||||
// TODO: Get user ID from JWT token
|
||||
activatedBy := uuid.New() // This should come from JWT token
|
||||
|
||||
err = h.service.ActivateCustomer(c.Request().Context(), id, &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, nil, "Customer activated successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByCompanyID retrieves customers by company ID
|
||||
// @Summary Get customers by company ID
|
||||
// @Description Retrieve all customers associated with a specific company. This is useful for company administrators to see all users within their organization.
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param companyId path string true "Company UUID" 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)
|
||||
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully"
|
||||
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID format"
|
||||
// @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")
|
||||
companyID, err := uuid.Parse(companyIDStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid company ID", err.Error())
|
||||
}
|
||||
|
||||
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.GetCustomersByCompanyID(c.Request().Context(), companyID, limit, offset)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve customers by company")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// 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 Customers-Admin
|
||||
// @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 Customers-Admin
|
||||
// @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")
|
||||
}
|
||||
|
||||
// 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 Customers-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/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 Customers-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/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
|
||||
// @Summary Get customer profile
|
||||
// @Description Retrieve authenticated customer's profile information. This endpoint requires a valid access token and returns the customer's own profile data.
|
||||
// @Tags Customers-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 access token"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer profile not found"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Router /api/v1/profile [get]
|
||||
func (h *Handler) GetProfile(c echo.Context) error {
|
||||
// Get customer ID from context (set by AuthMiddleware)
|
||||
customerID, err := GetCustomerIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Customer ID not found in context")
|
||||
}
|
||||
|
||||
// Call service
|
||||
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 retrieve customer profile")
|
||||
}
|
||||
|
||||
return response.Success(c, customer.ToResponse(), "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
// Logout handles customer logout
|
||||
// @Summary Customer logout
|
||||
// @Description Logout customer and invalidate access token. This ensures the token cannot be used for future requests.
|
||||
// @Tags Customers-Authorization
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} response.APIResponse "Logout successful"
|
||||
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing access token"
|
||||
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||
// @Router /api/v1/logout [delete]
|
||||
func (h *Handler) Logout(c echo.Context) error {
|
||||
// Get customer ID from context (set by AuthMiddleware)
|
||||
customerID, err := GetCustomerIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Customer ID not found in context")
|
||||
}
|
||||
|
||||
// Get access token from context
|
||||
accessToken, err := GetAccessTokenFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Access token not found in context")
|
||||
}
|
||||
|
||||
// Call service
|
||||
err = h.service.Logout(c.Request().Context(), customerID, accessToken)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to logout customer")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Logout successful")
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// AuthMiddleware validates JWT access tokens and extracts customer information
|
||||
func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// Extract token from Authorization header
|
||||
authHeader := c.Request().Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return response.Unauthorized(c, "Authorization header required")
|
||||
}
|
||||
|
||||
// Check if it's a Bearer token
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return response.Unauthorized(c, "Invalid authorization format. Use 'Bearer <token>'")
|
||||
}
|
||||
|
||||
// Extract the token
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
// Validate the access token using authorization service
|
||||
validationResult, err := h.authService.ValidateAccessToken(tokenString)
|
||||
if err != nil {
|
||||
h.logger.Error("Token validation error", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return response.Unauthorized(c, "Invalid token")
|
||||
}
|
||||
|
||||
if !validationResult.Valid {
|
||||
if validationResult.Expired {
|
||||
return response.Unauthorized(c, "Token expired")
|
||||
}
|
||||
return response.Unauthorized(c, validationResult.Error)
|
||||
}
|
||||
|
||||
// Extract customer information from token
|
||||
customerID, err := uuid.Parse(validationResult.Claims.UserID)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to parse customer ID from token", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return response.Unauthorized(c, "Invalid token format")
|
||||
}
|
||||
|
||||
// Store customer information in context for handlers to use
|
||||
c.Set("customer_id", customerID)
|
||||
c.Set("customer_email", validationResult.Claims.Email)
|
||||
c.Set("customer_role", validationResult.Claims.Role)
|
||||
c.Set("company_id", validationResult.Claims.CompanyID)
|
||||
c.Set("access_token", tokenString)
|
||||
|
||||
// Continue to next handler
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetCustomerIDFromContext extracts customer ID from Echo context
|
||||
func GetCustomerIDFromContext(c echo.Context) (uuid.UUID, error) {
|
||||
customerID, ok := c.Get("customer_id").(uuid.UUID)
|
||||
if !ok {
|
||||
return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Customer ID not found in context")
|
||||
}
|
||||
return customerID, nil
|
||||
}
|
||||
|
||||
// GetAccessTokenFromContext extracts access token from Echo context
|
||||
func GetAccessTokenFromContext(c echo.Context) (string, error) {
|
||||
accessToken, ok := c.Get("access_token").(string)
|
||||
if !ok {
|
||||
return "", echo.NewHTTPError(http.StatusUnauthorized, "Access token not found in context")
|
||||
}
|
||||
return accessToken, nil
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
mongopkg "tm/pkg/mongo"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// Repository defines the interface for customer data operations
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, customer *Customer) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*Customer, error)
|
||||
GetByEmail(ctx context.Context, email string) (*Customer, error)
|
||||
GetByUsername(ctx context.Context, username string) (*Customer, error)
|
||||
GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
|
||||
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
|
||||
GetByTaxID(ctx context.Context, taxID string) (*Customer, error)
|
||||
GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error)
|
||||
Update(ctx context.Context, customer *Customer) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
List(ctx context.Context, limit, offset int) ([]*Customer, error)
|
||||
Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
|
||||
CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error)
|
||||
CountByType(ctx context.Context, customerType CustomerType) (int64, error)
|
||||
CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
|
||||
UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error
|
||||
UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error
|
||||
}
|
||||
|
||||
// customerRepository implements the Repository interface
|
||||
type customerRepository struct {
|
||||
collection *mongo.Collection
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCustomerRepository creates a new customer repository
|
||||
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
||||
collection := mongoManager.GetCollection("customers")
|
||||
|
||||
// Create indexes
|
||||
repo := &customerRepository{
|
||||
collection: collection,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
repo.createIndexes()
|
||||
return repo
|
||||
}
|
||||
|
||||
// createIndexes creates necessary database indexes
|
||||
func (r *customerRepository) createIndexes() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Create indexes
|
||||
indexes := []mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "email", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "username", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetUnique(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "company_name", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetSparse(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "registration_number", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetSparse(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "tax_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetSparse(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "company_id", Value: 1},
|
||||
},
|
||||
Options: options.Index().SetSparse(true),
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "type", Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "status", Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "industry", Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "created_at", Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "updated_at", Value: -1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := r.collection.Indexes().CreateMany(ctx, indexes)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create customer indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
} else {
|
||||
r.logger.Info("Customer indexes created successfully", map[string]interface{}{})
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new customer
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
|
||||
// Set timestamps
|
||||
now := time.Now().Unix()
|
||||
customer.CreatedAt = now
|
||||
customer.UpdatedAt = now
|
||||
|
||||
// Set defaults if not provided
|
||||
if customer.Language == "" {
|
||||
customer.Language = "en"
|
||||
}
|
||||
if customer.Currency == "" {
|
||||
customer.Currency = "USD"
|
||||
}
|
||||
if customer.Timezone == "" {
|
||||
customer.Timezone = "UTC"
|
||||
}
|
||||
|
||||
_, err := r.collection.InsertOne(ctx, customer)
|
||||
if err != nil {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
return errors.New("customer with this email already exists")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer created successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a customer by ID
|
||||
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a customer by email
|
||||
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByUsername retrieves a customer by username
|
||||
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"username": username}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByCompanyName retrieves a customer by company name
|
||||
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"company_name": companyName}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByRegistrationNumber retrieves a customer by registration number
|
||||
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"registration_number": registrationNumber}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByTaxID retrieves a customer by tax ID
|
||||
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
|
||||
var customer Customer
|
||||
err := r.collection.FindOne(ctx, bson.M{"tax_id": taxID}).Decode(&customer)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
}
|
||||
|
||||
// GetByCompanyID retrieves customers by company ID with pagination
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) {
|
||||
filter := bson.M{"company_id": companyID}
|
||||
|
||||
opts := options.Find().
|
||||
SetLimit(int64(limit)).
|
||||
SetSkip(int64(offset)).
|
||||
SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
|
||||
cursor, err := r.collection.Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
// Update updates a customer
|
||||
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error {
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
filter := bson.M{"_id": customer.ID}
|
||||
update := bson.M{"$set": customer}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
r.logger.Info("Customer updated successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a customer (soft delete by setting status to inactive)
|
||||
func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"status": CustomerStatusInactive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
r.logger.Info("Customer deleted successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves customers with pagination
|
||||
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
|
||||
opts := options.Find().
|
||||
SetLimit(int64(limit)).
|
||||
SetSkip(int64(offset)).
|
||||
SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
|
||||
cursor, err := r.collection.Find(ctx, bson.M{}, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
// Search searches customers with filters
|
||||
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
|
||||
filter := bson.M{}
|
||||
|
||||
// Add search filter
|
||||
if search != "" {
|
||||
filter["$or"] = []bson.M{
|
||||
{"email": primitive.Regex{Pattern: search, Options: "i"}},
|
||||
{"company_name": primitive.Regex{Pattern: search, Options: "i"}},
|
||||
{"first_name": primitive.Regex{Pattern: search, Options: "i"}},
|
||||
{"last_name": primitive.Regex{Pattern: search, Options: "i"}},
|
||||
{"full_name": primitive.Regex{Pattern: search, Options: "i"}},
|
||||
}
|
||||
}
|
||||
|
||||
// Add type filter
|
||||
if customerType != nil {
|
||||
filter["type"] = *customerType
|
||||
}
|
||||
|
||||
// Add status filter
|
||||
if status != nil {
|
||||
filter["status"] = *status
|
||||
}
|
||||
|
||||
// Add company ID filter
|
||||
if companyID != nil {
|
||||
filter["company_id"] = *companyID
|
||||
}
|
||||
|
||||
// Add industry filter
|
||||
if industry != nil {
|
||||
filter["industry"] = *industry
|
||||
}
|
||||
|
||||
// Add verification filter
|
||||
if isVerified != nil {
|
||||
filter["is_verified"] = *isVerified
|
||||
}
|
||||
|
||||
// Add compliance filter
|
||||
if isCompliant != nil {
|
||||
filter["is_compliant"] = *isCompliant
|
||||
}
|
||||
|
||||
// Add language filter
|
||||
if language != nil {
|
||||
filter["language"] = *language
|
||||
}
|
||||
|
||||
// Add currency filter
|
||||
if currency != nil {
|
||||
filter["currency"] = *currency
|
||||
}
|
||||
|
||||
// Set sort options
|
||||
sortDirection := 1
|
||||
if sortOrder == "desc" {
|
||||
sortDirection = -1
|
||||
}
|
||||
|
||||
var sortField string
|
||||
switch sortBy {
|
||||
case "email":
|
||||
sortField = "email"
|
||||
case "company_name":
|
||||
sortField = "company_name"
|
||||
case "updated_at":
|
||||
sortField = "updated_at"
|
||||
case "status":
|
||||
sortField = "status"
|
||||
default:
|
||||
sortField = "created_at"
|
||||
}
|
||||
|
||||
opts := options.Find().
|
||||
SetLimit(int64(limit)).
|
||||
SetSkip(int64(offset)).
|
||||
SetSort(bson.D{{Key: sortField, Value: sortDirection}})
|
||||
|
||||
cursor, err := r.collection.Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
}
|
||||
|
||||
// CountByCompanyID counts customers by company ID
|
||||
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error) {
|
||||
filter := bson.M{"company_id": companyID}
|
||||
count, err := r.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByType counts customers by type
|
||||
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
|
||||
filter := bson.M{"type": customerType}
|
||||
count, err := r.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByStatus counts customers by status
|
||||
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
|
||||
filter := bson.M{"status": status}
|
||||
count, err := r.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates customer status
|
||||
func (r *customerRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error {
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"status": status,
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateVerification updates customer verification status
|
||||
func (r *customerRepository) UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error {
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"is_verified": isVerified,
|
||||
"is_compliant": isCompliant,
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
if complianceNotes != nil {
|
||||
update["$set"].(bson.M)["compliance_notes"] = *complianceNotes
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,214 +0,0 @@
|
||||
# User Management Service
|
||||
|
||||
This service provides comprehensive user management functionality for the Tender Management system, following Clean Architecture principles and Domain-Driven Design patterns.
|
||||
|
||||
## Features
|
||||
|
||||
### User Authentication
|
||||
- **Login**: Authenticate users with email/username and password
|
||||
- **Refresh Token**: Refresh access tokens
|
||||
- **Logout**: Securely log out users
|
||||
- **Password Management**: Change password and reset password functionality
|
||||
|
||||
### User Management
|
||||
- **Create User**: Create new users with roles and permissions
|
||||
- **Update User**: Update user information and profile
|
||||
- **Delete User**: Soft delete users (set status to inactive)
|
||||
- **Get User**: Retrieve user information by ID
|
||||
- **List Users**: Search and filter users with pagination
|
||||
|
||||
### Role-Based Access Control
|
||||
- **User Roles**: admin, manager, operator, viewer
|
||||
- **Role Management**: Update user roles
|
||||
- **Status Management**: Activate, deactivate, or suspend users
|
||||
|
||||
### Company Management
|
||||
- **Company Users**: Get users by company ID
|
||||
- **User Count**: Count users per company
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints
|
||||
|
||||
#### Authentication
|
||||
```
|
||||
POST /api/v1/users/login
|
||||
POST /api/v1/users/refresh-token
|
||||
POST /api/v1/users/reset-password
|
||||
```
|
||||
|
||||
### Protected Endpoints (Require Authentication)
|
||||
|
||||
#### User Profile
|
||||
```
|
||||
GET /api/v1/users/profile
|
||||
PUT /api/v1/users/profile
|
||||
PUT /api/v1/users/change-password
|
||||
POST /api/v1/users/logout
|
||||
```
|
||||
|
||||
### Admin Endpoints (Require Admin Role)
|
||||
|
||||
#### User Management
|
||||
```
|
||||
POST /api/v1/users/admin/
|
||||
GET /api/v1/users/admin/
|
||||
GET /api/v1/users/admin/:id
|
||||
PUT /api/v1/users/admin/:id
|
||||
DELETE /api/v1/users/admin/:id
|
||||
```
|
||||
|
||||
#### User Status & Role Management
|
||||
```
|
||||
PUT /api/v1/users/admin/:id/status
|
||||
PUT /api/v1/users/admin/:id/role
|
||||
```
|
||||
|
||||
#### Company & Role Queries
|
||||
```
|
||||
GET /api/v1/users/admin/company/:company_id
|
||||
GET /api/v1/users/admin/role/:role
|
||||
```
|
||||
|
||||
## Request/Response Examples
|
||||
|
||||
### Create User
|
||||
```json
|
||||
POST /api/v1/users/admin/
|
||||
{
|
||||
"full_name": "John Doe",
|
||||
"username": "johndoe",
|
||||
"email": "john.doe@example.com",
|
||||
"password": "securepassword123",
|
||||
"role": "manager",
|
||||
"company_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"department": "Engineering",
|
||||
"position": "Senior Manager",
|
||||
"phone": "+1234567890",
|
||||
"profile_image": "https://example.com/avatar.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
### Login
|
||||
```json
|
||||
POST /api/v1/users/login
|
||||
{
|
||||
"email_or_username": "john.doe@example.com",
|
||||
"password": "securepassword123"
|
||||
}
|
||||
```
|
||||
|
||||
### Update User
|
||||
```json
|
||||
PUT /api/v1/users/admin/:id
|
||||
{
|
||||
"full_name": "John Smith",
|
||||
"department": "Product Management",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
### List Users with Filters
|
||||
```
|
||||
GET /api/v1/users/admin/?search=john&role=manager&status=active&limit=20&offset=0&sort_by=created_at&sort_order=desc
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
### User Entity
|
||||
```go
|
||||
type User struct {
|
||||
ID uuid.UUID `bson:"_id"`
|
||||
FullName string `bson:"full_name"`
|
||||
Username string `bson:"username"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"`
|
||||
Role UserRole `bson:"role"`
|
||||
Status UserStatus `bson:"status"`
|
||||
CompanyID *uuid.UUID `bson:"company_id,omitempty"`
|
||||
Department *string `bson:"department,omitempty"`
|
||||
Position *string `bson:"position,omitempty"`
|
||||
Phone *string `bson:"phone,omitempty"`
|
||||
ProfileImage *string `bson:"profile_image,omitempty"`
|
||||
IsVerified bool `bson:"is_verified"`
|
||||
LastLoginAt *int64 `bson:"last_login_at,omitempty"`
|
||||
CreatedAt int64 `bson:"created_at"`
|
||||
UpdatedAt int64 `bson:"updated_at"`
|
||||
CreatedBy *uuid.UUID `bson:"created_by,omitempty"`
|
||||
UpdatedBy *uuid.UUID `bson:"updated_by,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### User Roles
|
||||
- `admin`: Full system access
|
||||
- `manager`: Company and team management
|
||||
- `operator`: Operational tasks
|
||||
- `viewer`: Read-only access
|
||||
|
||||
### User Status
|
||||
- `active`: User can access the system
|
||||
- `inactive`: User account is disabled
|
||||
- `suspended`: User account is temporarily suspended
|
||||
|
||||
## Security Features
|
||||
|
||||
### Password Security
|
||||
- Passwords are hashed using bcrypt
|
||||
- Minimum password length: 8 characters
|
||||
- Maximum password length: 128 characters
|
||||
|
||||
### Authentication
|
||||
- JWT-based authentication (to be implemented)
|
||||
- Token refresh mechanism
|
||||
- Secure logout with token invalidation
|
||||
|
||||
### Authorization
|
||||
- Role-based access control
|
||||
- Admin-only endpoints for user management
|
||||
- Company-scoped access for multi-tenant support
|
||||
|
||||
## Database Indexes
|
||||
|
||||
The service creates the following MongoDB indexes for optimal performance:
|
||||
|
||||
- **Email Index**: Unique index on email field
|
||||
- **Username Index**: Unique index on username field
|
||||
- **Company ID Index**: For company-based queries
|
||||
- **Role Index**: For role-based filtering
|
||||
- **Status Index**: For status-based filtering
|
||||
- **Created At Index**: For sorting by creation date
|
||||
- **Text Search Index**: For full-text search across name, email, username, department, and position
|
||||
|
||||
## Error Handling
|
||||
|
||||
The service provides comprehensive error handling with:
|
||||
|
||||
- **Validation Errors**: Detailed validation messages using govalidator
|
||||
- **Business Logic Errors**: Clear error messages for business rule violations
|
||||
- **Database Errors**: Proper handling of database constraints and connection issues
|
||||
- **Security Errors**: Secure error messages that don't leak sensitive information
|
||||
|
||||
## Logging
|
||||
|
||||
All operations are logged with structured logging including:
|
||||
|
||||
- **Operation Context**: User ID, company ID, operation type
|
||||
- **Error Details**: Full error context for debugging
|
||||
- **Security Events**: Login attempts, password changes, role updates
|
||||
- **Performance Metrics**: Operation timing and resource usage
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
- **JWT Token Management**: Complete JWT implementation with refresh tokens
|
||||
- **Email Verification**: Email verification for new user accounts
|
||||
- **Password Reset**: Complete password reset flow with email
|
||||
- **Audit Trail**: Comprehensive audit logging for all user operations
|
||||
- **Bulk Operations**: Bulk user import/export functionality
|
||||
- **Advanced Search**: Elasticsearch integration for advanced search capabilities
|
||||
|
||||
### Integration Points
|
||||
- **Email Service**: For password reset and verification emails
|
||||
- **Notification Service**: For user activity notifications
|
||||
- **Audit Service**: For comprehensive audit logging
|
||||
- **Permission Service**: For fine-grained permission management
|
||||
+16
-16
@@ -72,7 +72,7 @@ func (h *Handler) RegisterRoutes(e *echo.Echo) {
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /login [post]
|
||||
// @Router /admin/v1/login [post]
|
||||
func (h *Handler) Login(c echo.Context) error {
|
||||
form, err := response.Parse[LoginForm](c)
|
||||
if err != nil {
|
||||
@@ -99,7 +99,7 @@ func (h *Handler) Login(c echo.Context) error {
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /refresh-token [post]
|
||||
// @Router /admin/v1/refresh-token [post]
|
||||
func (h *Handler) RefreshToken(c echo.Context) error {
|
||||
form, err := response.Parse[RefreshTokenForm](c)
|
||||
if err != nil {
|
||||
@@ -124,7 +124,7 @@ func (h *Handler) RefreshToken(c echo.Context) error {
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /reset-password [post]
|
||||
// @Router /admin/v1/reset-password [post]
|
||||
func (h *Handler) ResetPassword(c echo.Context) error {
|
||||
form, err := response.Parse[ResetPasswordForm](c)
|
||||
if err != nil {
|
||||
@@ -152,7 +152,7 @@ func (h *Handler) ResetPassword(c echo.Context) error {
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /profile [get]
|
||||
// @Router /admin/v1/profile [get]
|
||||
func (h *Handler) GetProfile(c echo.Context) error {
|
||||
// Extract user ID from JWT token context
|
||||
userID, err := GetUserIDFromContext(c)
|
||||
@@ -180,7 +180,7 @@ func (h *Handler) GetProfile(c echo.Context) error {
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /profile [put]
|
||||
// @Router /admin/v1/profile [put]
|
||||
func (h *Handler) UpdateProfile(c echo.Context) error {
|
||||
// Extract user ID from JWT token context
|
||||
userID, err := GetUserIDFromContext(c)
|
||||
@@ -213,7 +213,7 @@ func (h *Handler) UpdateProfile(c echo.Context) error {
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /change-password [put]
|
||||
// @Router /admin/v1/change-password [put]
|
||||
func (h *Handler) ChangePassword(c echo.Context) error {
|
||||
userID, err := GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
@@ -245,7 +245,7 @@ func (h *Handler) ChangePassword(c echo.Context) error {
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /logout [delete]
|
||||
// @Router /admin/v1/logout [delete]
|
||||
func (h *Handler) Logout(c echo.Context) error {
|
||||
// Extract user ID and access token from context
|
||||
userID, err := GetUserIDFromContext(c)
|
||||
@@ -281,7 +281,7 @@ func (h *Handler) Logout(c echo.Context) error {
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users [post]
|
||||
// @Router /admin/v1/users [post]
|
||||
func (h *Handler) CreateUser(c echo.Context) error {
|
||||
// Get current user ID from JWT token
|
||||
currentUserID, err := GetUserIDFromContext(c)
|
||||
@@ -328,7 +328,7 @@ func (h *Handler) CreateUser(c echo.Context) error {
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users [get]
|
||||
// @Router /admin/v1/users [get]
|
||||
func (h *Handler) ListUsers(c echo.Context) error {
|
||||
var form ListUsersForm
|
||||
if err := c.Bind(&form); err != nil {
|
||||
@@ -363,7 +363,7 @@ func (h *Handler) ListUsers(c echo.Context) error {
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/{id} [get]
|
||||
// @Router /admin/v1/users/{id} [get]
|
||||
func (h *Handler) GetUserByID(c echo.Context) error {
|
||||
idStr := c.Param("id")
|
||||
userID, err := uuid.Parse(idStr)
|
||||
@@ -394,7 +394,7 @@ func (h *Handler) GetUserByID(c echo.Context) error {
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/{id} [put]
|
||||
// @Router /admin/v1/users/{id} [put]
|
||||
func (h *Handler) UpdateUser(c echo.Context) error {
|
||||
// Extract user ID from JWT token context
|
||||
currentUserID, err := GetUserIDFromContext(c)
|
||||
@@ -441,7 +441,7 @@ func (h *Handler) UpdateUser(c echo.Context) error {
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/{id} [delete]
|
||||
// @Router /admin/v1/users/{id} [delete]
|
||||
func (h *Handler) DeleteUser(c echo.Context) error {
|
||||
// Extract user ID from JWT token context
|
||||
currentUserID, err := GetUserIDFromContext(c)
|
||||
@@ -480,7 +480,7 @@ func (h *Handler) DeleteUser(c echo.Context) error {
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/{id}/status [put]
|
||||
// @Router /admin/v1/users/{id}/status [put]
|
||||
func (h *Handler) UpdateUserStatus(c echo.Context) error {
|
||||
currentUserID, err := GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
@@ -529,7 +529,7 @@ func (h *Handler) UpdateUserStatus(c echo.Context) error {
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/{id}/role [put]
|
||||
// @Router /admin/v1/users/{id}/role [put]
|
||||
func (h *Handler) UpdateUserRole(c echo.Context) error {
|
||||
// Extract user ID from JWT token context
|
||||
currentUserID, err := GetUserIDFromContext(c)
|
||||
@@ -579,7 +579,7 @@ func (h *Handler) UpdateUserRole(c echo.Context) error {
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/company/{company_id} [get]
|
||||
// @Router /admin/v1/users/company/{company_id} [get]
|
||||
func (h *Handler) GetUsersByCompanyID(c echo.Context) error {
|
||||
companyIDStr := c.Param("company_id")
|
||||
companyID, err := uuid.Parse(companyIDStr)
|
||||
@@ -643,7 +643,7 @@ func (h *Handler) GetUsersByCompanyID(c echo.Context) error {
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /users/role/{role} [get]
|
||||
// @Router /admin/v1/users/role/{role} [get]
|
||||
func (h *Handler) GetUsersByRole(c echo.Context) error {
|
||||
roleStr := c.Param("role")
|
||||
role := UserRole(roleStr)
|
||||
|
||||
Reference in New Issue
Block a user