Refactor customer domain to use string IDs and integrate MongoDB ORM
- Updated Customer entity to embed MongoDB model and replace uuid.UUID with string for ID fields. - Modified repository methods to accept string IDs instead of uuid.UUID, enhancing compatibility with MongoDB. - Refactored service and handler methods to align with the new ID type, ensuring consistent usage across customer management functionality. - Improved response handling in CustomerResponse to directly use string IDs. - Streamlined index creation in the customer repository using the MongoDB ORM for better maintainability. - Added new forms for customer suspension and updated validation rules. - Enhanced Swagger documentation for customer-related endpoints to reflect changes in ID handling and request structures.
This commit is contained in:
+80
-67
@@ -1,7 +1,7 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"tm/pkg/mongo"
|
||||
)
|
||||
|
||||
// CustomerStatus represents customer account status
|
||||
@@ -25,77 +25,105 @@ const (
|
||||
|
||||
// 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"`
|
||||
mongo.Model
|
||||
Type CustomerType `bson:"type" json:"type"`
|
||||
Status CustomerStatus `bson:"status" json:"status"`
|
||||
CompanyID *string `bson:"company_id,omitempty" json:"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"`
|
||||
FirstName *string `bson:"first_name,omitempty" json:"first_name,omitempty"`
|
||||
LastName *string `bson:"last_name,omitempty" json:"last_name,omitempty"`
|
||||
FullName *string `bson:"full_name,omitempty" json:"full_name,omitempty"`
|
||||
Username string `bson:"username" json:"username"` // Username for authentication
|
||||
Email string `bson:"email" json:"email"`
|
||||
Password string `bson:"password" json:"-"` // Hashed password for authentication
|
||||
Phone *string `bson:"phone,omitempty" json:"phone,omitempty"`
|
||||
Mobile *string `bson:"mobile,omitempty" json:"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"`
|
||||
CompanyName *string `bson:"company_name,omitempty" json:"company_name,omitempty"`
|
||||
RegistrationNumber *string `bson:"registration_number,omitempty" json:"registration_number,omitempty"`
|
||||
TaxID *string `bson:"tax_id,omitempty" json:"tax_id,omitempty"`
|
||||
Industry *string `bson:"industry,omitempty" json:"industry,omitempty"`
|
||||
|
||||
// Address information
|
||||
Address *Address `bson:"address,omitempty"`
|
||||
Address *Address `bson:"address,omitempty" json:"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"`
|
||||
BusinessType *string `bson:"business_type,omitempty" json:"business_type,omitempty"`
|
||||
EmployeeCount *int `bson:"employee_count,omitempty" json:"employee_count,omitempty"`
|
||||
AnnualRevenue *float64 `bson:"annual_revenue,omitempty" json:"annual_revenue,omitempty"`
|
||||
FoundedYear *int `bson:"founded_year,omitempty" json:"founded_year,omitempty"`
|
||||
|
||||
// Contact person (for company customers)
|
||||
ContactPerson *ContactPerson `bson:"contact_person,omitempty"`
|
||||
ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"`
|
||||
|
||||
// Verification and compliance
|
||||
IsVerified bool `bson:"is_verified"`
|
||||
IsCompliant bool `bson:"is_compliant"`
|
||||
ComplianceNotes *string `bson:"compliance_notes,omitempty"`
|
||||
IsVerified bool `bson:"is_verified" json:"is_verified"`
|
||||
IsCompliant bool `bson:"is_compliant" json:"is_compliant"`
|
||||
ComplianceNotes *string `bson:"compliance_notes,omitempty" json:"compliance_notes,omitempty"`
|
||||
|
||||
// Preferences and settings
|
||||
Language string `bson:"language"` // Default: "en"
|
||||
Currency string `bson:"currency"` // Default: "USD"
|
||||
Timezone string `bson:"timezone"` // Default: "UTC"
|
||||
Language string `bson:"language" json:"language"` // Default: "en"
|
||||
Currency string `bson:"currency" json:"currency"` // Default: "USD"
|
||||
Timezone string `bson:"timezone" json:"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"`
|
||||
CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"`
|
||||
UpdatedBy *string `bson:"updated_by,omitempty" json:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
// SetID sets the customer ID (implements IDSetter interface)
|
||||
func (c *Customer) SetID(id string) {
|
||||
c.ID = id
|
||||
}
|
||||
|
||||
// GetID returns the customer ID (implements IDGetter interface)
|
||||
func (c *Customer) GetID() string {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the created timestamp (implements Timestampable interface)
|
||||
func (c *Customer) SetCreatedAt(timestamp int64) {
|
||||
c.CreatedAt = timestamp
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the updated timestamp (implements Timestampable interface)
|
||||
func (c *Customer) SetUpdatedAt(timestamp int64) {
|
||||
c.UpdatedAt = timestamp
|
||||
}
|
||||
|
||||
// GetCreatedAt returns the created timestamp (implements Timestampable interface)
|
||||
func (c *Customer) GetCreatedAt() int64 {
|
||||
return c.CreatedAt
|
||||
}
|
||||
|
||||
// GetUpdatedAt returns the updated timestamp (implements Timestampable interface)
|
||||
func (c *Customer) GetUpdatedAt() int64 {
|
||||
return c.UpdatedAt
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Street string `bson:"street" json:"street"`
|
||||
City string `bson:"city" json:"city"`
|
||||
State string `bson:"state" json:"state"`
|
||||
PostalCode string `bson:"postal_code" json:"postal_code"`
|
||||
Country string `bson:"country" json:"country"`
|
||||
AddressType string `bson:"address_type" json:"address_type"` // billing, shipping, etc.
|
||||
IsDefault bool `bson:"is_default" json:"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"`
|
||||
FirstName string `bson:"first_name" json:"first_name"`
|
||||
LastName string `bson:"last_name" json:"last_name"`
|
||||
FullName string `bson:"full_name" json:"full_name"`
|
||||
Position string `bson:"position" json:"position"`
|
||||
Email string `bson:"email" json:"email"`
|
||||
Phone string `bson:"phone" json:"phone"`
|
||||
Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
|
||||
IsPrimary bool `bson:"is_primary" json:"is_primary"`
|
||||
}
|
||||
|
||||
// CustomerResponse represents the customer data sent in API responses
|
||||
@@ -135,26 +163,11 @@ type CustomerResponse struct {
|
||||
|
||||
// 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(),
|
||||
ID: c.ID,
|
||||
Type: string(c.Type),
|
||||
Status: string(c.Status),
|
||||
CompanyID: &companyID,
|
||||
CompanyID: c.CompanyID,
|
||||
FirstName: c.FirstName,
|
||||
LastName: c.LastName,
|
||||
FullName: c.FullName,
|
||||
@@ -180,7 +193,7 @@ func (c *Customer) ToResponse() *CustomerResponse {
|
||||
Timezone: c.Timezone,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
CreatedBy: &createdBy,
|
||||
UpdatedBy: &updatedBy,
|
||||
CreatedBy: c.CreatedBy,
|
||||
UpdatedBy: c.UpdatedBy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +99,18 @@ type UpdateCustomerStatusForm struct {
|
||||
Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"`
|
||||
}
|
||||
|
||||
// UpdateCustomerVerificationForm represents the form for updating customer verification
|
||||
// UpdateCustomerVerificationForm represents the form for updating customer verification status
|
||||
type UpdateCustomerVerificationForm struct {
|
||||
IsVerified bool `json:"is_verified"`
|
||||
IsCompliant bool `json:"is_compliant"`
|
||||
ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"`
|
||||
}
|
||||
|
||||
// SuspendCustomerForm represents the form for suspending a customer
|
||||
type SuspendCustomerForm struct {
|
||||
Reason string `json:"reason" valid:"required,length(10|500)"`
|
||||
}
|
||||
|
||||
// AddressForm represents the form for customer address
|
||||
type AddressForm struct {
|
||||
Street string `json:"street" valid:"required,length(5|200)"`
|
||||
|
||||
+155
-158
@@ -7,7 +7,6 @@ import (
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -81,8 +80,11 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
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
|
||||
// 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 {
|
||||
@@ -98,32 +100,28 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
|
||||
return response.Created(c, customer.ToResponse(), "Customer created successfully")
|
||||
}
|
||||
|
||||
// GetCustomerByID retrieves a customer by ID
|
||||
// GetCustomerByID retrieves a customer by ID (Web Panel)
|
||||
// @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.
|
||||
// @Description Retrieve detailed customer information by customer ID
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 format"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
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)
|
||||
customer, err := h.service.GetCustomerByID(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 retrieve customer")
|
||||
return response.InternalServerError(c, "Failed to get customer")
|
||||
}
|
||||
|
||||
return response.Success(c, customer.ToResponse(), "Customer retrieved successfully")
|
||||
@@ -131,15 +129,15 @@ func (h *Handler) GetCustomerByID(c echo.Context) error {
|
||||
|
||||
// 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.
|
||||
// @Description Update customer information including personal details, company information, address, and business details
|
||||
// @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"
|
||||
// @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 customer ID or input data"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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"
|
||||
@@ -147,20 +145,18 @@ func (h *Handler) GetCustomerByID(c echo.Context) error {
|
||||
// @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
|
||||
// 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(), id, form, &updatedBy)
|
||||
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")
|
||||
@@ -177,30 +173,29 @@ func (h *Handler) UpdateCustomer(c echo.Context) error {
|
||||
return response.Success(c, customer.ToResponse(), "Customer updated successfully")
|
||||
}
|
||||
|
||||
// DeleteCustomer deletes a customer (soft delete)
|
||||
// DeleteCustomer deletes a customer (Web Panel)
|
||||
// @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.
|
||||
// @Description Soft delete a customer by setting status to inactive
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 format"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
|
||||
// Get user ID from JWT token context
|
||||
deletedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
// 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)
|
||||
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")
|
||||
@@ -208,7 +203,9 @@ func (h *Handler) DeleteCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to delete customer")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer deleted successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer deleted successfully",
|
||||
}, "Customer deleted successfully")
|
||||
}
|
||||
|
||||
// ListCustomers lists customers with filters and pagination
|
||||
@@ -250,37 +247,35 @@ func (h *Handler) ListCustomers(c echo.Context) error {
|
||||
return response.Success(c, result, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// UpdateCustomerStatus updates customer status
|
||||
// UpdateCustomerStatus updates customer status (Web Panel)
|
||||
// @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.
|
||||
// @Description Update customer account status (active, inactive, suspended, pending)
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @Param status body UpdateCustomerStatusForm true "Status update information"
|
||||
// @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 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 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")
|
||||
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
|
||||
// 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(), id, form, &updatedBy)
|
||||
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")
|
||||
@@ -288,40 +283,40 @@ func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to update customer status")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer status updated successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer status updated successfully",
|
||||
}, "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.
|
||||
// UpdateCustomerVerification updates customer verification status (Web Panel)
|
||||
// @Summary Update customer verification status
|
||||
// @Description Update customer verification and compliance status
|
||||
// @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"
|
||||
// @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 customer ID"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @Failure 422 {object} response.APIResponse "Validation error - Invalid verification data"
|
||||
// @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")
|
||||
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
|
||||
// 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(), id, form, &updatedBy)
|
||||
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")
|
||||
@@ -329,33 +324,34 @@ func (h *Handler) UpdateCustomerVerification(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to update customer verification")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer verification updated successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer verification updated successfully",
|
||||
}, "Customer verification updated successfully")
|
||||
}
|
||||
|
||||
// VerifyCustomer verifies a customer
|
||||
// VerifyCustomer verifies a customer (Web Panel)
|
||||
// @Summary Verify customer
|
||||
// @Description Mark a customer as verified. This is a simple verification action that sets the customer's verification status to true.
|
||||
// @Description Mark a customer as verified
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
|
||||
// Get user ID from JWT token context
|
||||
verifiedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
// 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)
|
||||
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")
|
||||
@@ -363,44 +359,40 @@ func (h *Handler) VerifyCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to verify customer")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer verified successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer verified successfully",
|
||||
}, "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.
|
||||
// SuspendCustomer suspends a customer (Web Panel)
|
||||
// @Summary Suspend customer
|
||||
// @Description Suspend a customer account with optional reason
|
||||
// @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"})
|
||||
// @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 customer ID or missing reason"
|
||||
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
form, err := response.Parse[SuspendCustomerForm](c)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
var requestBody map[string]string
|
||||
if err := c.Bind(&requestBody); err != nil {
|
||||
return response.BadRequest(c, "Invalid request body", err.Error())
|
||||
// Get user ID from JWT token context
|
||||
suspendedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
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)
|
||||
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")
|
||||
@@ -408,33 +400,34 @@ func (h *Handler) SuspendCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to suspend customer")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer suspended successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer suspended successfully",
|
||||
}, "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.
|
||||
// ActivateCustomer activates a customer (Web Panel)
|
||||
// @Summary Activate customer
|
||||
// @Description Activate a suspended customer account
|
||||
// @Tags Customers-Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Customer UUID" format(uuid)
|
||||
// @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 does not exist"
|
||||
// @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")
|
||||
id, err := uuid.Parse(idStr)
|
||||
|
||||
// Get user ID from JWT token context
|
||||
activatedBy, err := user.GetUserIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
||||
return response.Unauthorized(c, "User not authenticated")
|
||||
}
|
||||
|
||||
// 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)
|
||||
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")
|
||||
@@ -442,61 +435,61 @@ func (h *Handler) ActivateCustomer(c echo.Context) error {
|
||||
return response.InternalServerError(c, "Failed to activate customer")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Customer activated successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Customer activated successfully",
|
||||
}, "Customer activated successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByCompanyID retrieves customers by company ID
|
||||
// GetCustomersByCompanyID retrieves customers by company ID (Web Panel)
|
||||
// @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.
|
||||
// @Description Retrieve all customers associated with a specific company
|
||||
// @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"
|
||||
// @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")
|
||||
companyID, err := uuid.Parse(companyIDStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid company ID", err.Error())
|
||||
}
|
||||
|
||||
limit := 20
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
customers, total, err := h.service.GetCustomersByCompanyID(c.Request().Context(), companyIDStr, limit, offset)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to retrieve customers by company")
|
||||
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())
|
||||
}
|
||||
|
||||
meta := &response.Meta{
|
||||
Total: int(total),
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
|
||||
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"customers": customerResponses,
|
||||
"total": total,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}, "Customers retrieved successfully")
|
||||
}
|
||||
|
||||
// GetCustomersByType retrieves customers by type
|
||||
@@ -676,66 +669,70 @@ func (h *Handler) RefreshToken(c echo.Context) error {
|
||||
return response.Success(c, authResponse, "Token refreshed successfully")
|
||||
}
|
||||
|
||||
// GetProfile retrieves customer profile information
|
||||
// GetProfile retrieves customer profile information (Mobile)
|
||||
// @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
|
||||
// @Description Retrieve current customer profile information
|
||||
// @Tags Customers-Mobile
|
||||
// @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 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 {
|
||||
// Get customer ID from context (set by AuthMiddleware)
|
||||
// Extract customer ID from JWT token context
|
||||
customerID, err := GetCustomerIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Customer ID not found in context")
|
||||
return response.Unauthorized(c, "Customer not authenticated")
|
||||
}
|
||||
|
||||
// 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.InternalServerError(c, "Failed to get customer profile")
|
||||
}
|
||||
|
||||
return response.Success(c, customer.ToResponse(), "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
// Logout handles customer logout
|
||||
// Logout handles customer logout (Mobile)
|
||||
// @Summary Customer logout
|
||||
// @Description Logout customer and invalidate access token. This ensures the token cannot be used for future requests.
|
||||
// @Tags Customers-Authorization
|
||||
// @Description Logout customer and invalidate access token
|
||||
// @Tags Customers-Mobile
|
||||
// @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 401 {object} response.APIResponse "Unauthorized - Invalid or missing 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)
|
||||
// Extract customer ID from JWT token context
|
||||
customerID, err := GetCustomerIDFromContext(c)
|
||||
if err != nil {
|
||||
return response.Unauthorized(c, "Customer ID not found in context")
|
||||
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 in context")
|
||||
return response.Unauthorized(c, "Access token not found")
|
||||
}
|
||||
|
||||
// Call service
|
||||
err = h.service.Logout(c.Request().Context(), customerID, accessToken)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Failed to logout customer")
|
||||
// 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, nil, "Logout successful")
|
||||
return response.Success(c, map[string]interface{}{
|
||||
"message": "Logout successful",
|
||||
}, "Logout successful")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -44,13 +43,7 @@ func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
customerID := validationResult.Claims.UserID
|
||||
|
||||
// Store customer information in context for handlers to use
|
||||
c.Set("customer_id", customerID)
|
||||
@@ -66,10 +59,10 @@ func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// GetCustomerIDFromContext extracts customer ID from Echo context
|
||||
func GetCustomerIDFromContext(c echo.Context) (uuid.UUID, error) {
|
||||
customerID, ok := c.Get("customer_id").(uuid.UUID)
|
||||
func GetCustomerIDFromContext(c echo.Context) (string, error) {
|
||||
customerID, ok := c.Get("customer_id").(string)
|
||||
if !ok {
|
||||
return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Customer ID not found in context")
|
||||
return "", echo.NewHTTPError(http.StatusUnauthorized, "Customer ID not found in context")
|
||||
}
|
||||
return customerID, nil
|
||||
}
|
||||
|
||||
+263
-264
@@ -7,162 +7,94 @@ import (
|
||||
"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)
|
||||
GetByID(ctx context.Context, id string) (*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)
|
||||
GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error)
|
||||
Update(ctx context.Context, customer *Customer) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
Delete(ctx context.Context, id string) 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)
|
||||
Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
|
||||
CountByCompanyID(ctx context.Context, companyID string) (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
|
||||
UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
|
||||
UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error
|
||||
}
|
||||
|
||||
// customerRepository implements the Repository interface
|
||||
// customerRepository implements the Repository interface using the MongoDB ORM
|
||||
type customerRepository struct {
|
||||
collection *mongo.Collection
|
||||
ormRepo mongopkg.Repository[Customer]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCustomerRepository creates a new customer repository
|
||||
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
||||
collection := mongoManager.GetCollection("customers")
|
||||
// Create indexes using the ORM's index management
|
||||
indexes := []mongopkg.Index{
|
||||
*mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
|
||||
*mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
||||
*mongopkg.NewIndex("company_name_idx", bson.D{{Key: "company_name", Value: 1}}),
|
||||
*mongopkg.NewIndex("registration_number_idx", bson.D{{Key: "registration_number", Value: 1}}),
|
||||
*mongopkg.NewIndex("tax_id_idx", bson.D{{Key: "tax_id", Value: 1}}),
|
||||
*mongopkg.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
|
||||
*mongopkg.NewIndex("type_idx", bson.D{{Key: "type", Value: 1}}),
|
||||
*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
||||
*mongopkg.NewIndex("industry_idx", bson.D{{Key: "industry", Value: 1}}),
|
||||
*mongopkg.NewIndex("is_verified_idx", bson.D{{Key: "is_verified", Value: 1}}),
|
||||
*mongopkg.NewIndex("is_compliant_idx", bson.D{{Key: "is_compliant", Value: 1}}),
|
||||
*mongopkg.NewIndex("language_idx", bson.D{{Key: "language", Value: 1}}),
|
||||
*mongopkg.NewIndex("currency_idx", bson.D{{Key: "currency", Value: 1}}),
|
||||
*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*mongopkg.CreateTextIndex("search_idx", "first_name", "last_name", "full_name", "company_name", "email", "username"),
|
||||
}
|
||||
|
||||
// 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)
|
||||
err := mongoManager.CreateIndexes("customers", indexes)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create customer indexes", map[string]interface{}{
|
||||
logger.Warn("Failed to create customer indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
} else {
|
||||
r.logger.Info("Customer indexes created successfully", map[string]interface{}{})
|
||||
}
|
||||
|
||||
// Create ORM repository
|
||||
ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger)
|
||||
|
||||
return &customerRepository{
|
||||
ormRepo: ormRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new customer
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
|
||||
// Set timestamps
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
customer.CreatedAt = now
|
||||
customer.UpdatedAt = now
|
||||
customer.SetCreatedAt(now)
|
||||
customer.SetUpdatedAt(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)
|
||||
// Use ORM to create customer
|
||||
err := r.ormRepo.Create(ctx, customer)
|
||||
if err != nil {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
return errors.New("customer with this email already exists")
|
||||
}
|
||||
r.logger.Error("Failed to create customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": customer.Email,
|
||||
"username": customer.Username,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer created successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
})
|
||||
@@ -171,107 +103,143 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) {
|
||||
customer, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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)
|
||||
filter := bson.M{"email": email}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by email", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": email,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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)
|
||||
filter := bson.M{"username": username}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by username", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"username": username,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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)
|
||||
filter := bson.M{"company_name": companyName}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by company name", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_name": companyName,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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)
|
||||
filter := bson.M{"registration_number": registrationNumber}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by registration number", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"registration_number": registrationNumber,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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)
|
||||
filter := bson.M{"tax_id": taxID}
|
||||
customer, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"tax_id": taxID,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &customer, nil
|
||||
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}
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
opts := options.Find().
|
||||
SetLimit(int64(limit)).
|
||||
SetSkip(int64(offset)).
|
||||
SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
// Filter by company ID and active status
|
||||
filter := bson.M{
|
||||
"company_id": companyID,
|
||||
"status": bson.M{"$ne": CustomerStatusInactive},
|
||||
}
|
||||
|
||||
cursor, err := r.collection.Find(ctx, filter, opts)
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
// Convert []Customer to []*Customer
|
||||
customers := make([]*Customer, len(result.Items))
|
||||
for i := range result.Items {
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return customers, nil
|
||||
@@ -279,22 +247,21 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.
|
||||
|
||||
// Update updates a customer
|
||||
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error {
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
// Set updated timestamp using Unix timestamp
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
filter := bson.M{"_id": customer.ID}
|
||||
update := bson.M{"$set": customer}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
// Use ORM to update customer
|
||||
err := r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customer.ID,
|
||||
})
|
||||
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(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
@@ -302,26 +269,29 @@ func (r *customerRepository) Update(ctx context.Context, customer *Customer) err
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (r *customerRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
// Update status to inactive
|
||||
customer.Status = CustomerStatusInactive
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer deleted successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -329,124 +299,125 @@ func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
|
||||
// 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}})
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
cursor, err := r.collection.Find(ctx, bson.M{}, opts)
|
||||
// Only active customers by default
|
||||
filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}}
|
||||
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to list customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
// Convert []Customer to []*Customer
|
||||
customers := make([]*Customer, len(result.Items))
|
||||
for i := range result.Items {
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
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) {
|
||||
// Search retrieves customers with search and filters
|
||||
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
|
||||
// Build filter
|
||||
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"}},
|
||||
}
|
||||
filter["$text"] = bson.M{"$search": search}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset)
|
||||
|
||||
// Set sort
|
||||
if sortBy != "" {
|
||||
if sortOrder == "desc" {
|
||||
sortDirection = -1
|
||||
pagination.SortDesc(sortBy)
|
||||
} else {
|
||||
pagination.SortAsc(sortBy)
|
||||
}
|
||||
} else {
|
||||
pagination.SortDesc("created_at") // Default sort
|
||||
}
|
||||
|
||||
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)
|
||||
// Use ORM to find customers
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to search customers", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"search": search,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var customers []*Customer
|
||||
if err = cursor.All(ctx, &customers); err != nil {
|
||||
return nil, err
|
||||
// Convert []Customer to []*Customer
|
||||
customers := make([]*Customer, len(result.Items))
|
||||
for i := range result.Items {
|
||||
customers[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
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)
|
||||
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
|
||||
filter := bson.M{
|
||||
"company_id": companyID,
|
||||
"status": bson.M{"$ne": CustomerStatusInactive},
|
||||
}
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -455,9 +426,17 @@ func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uui
|
||||
|
||||
// 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)
|
||||
filter := bson.M{
|
||||
"type": customerType,
|
||||
"status": bson.M{"$ne": CustomerStatusInactive},
|
||||
}
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by type", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_type": customerType,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -467,8 +446,13 @@ func (r *customerRepository) CountByType(ctx context.Context, customerType Custo
|
||||
// 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)
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count customers by status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"status": status,
|
||||
})
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -476,50 +460,65 @@ func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerS
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
// Update status
|
||||
customer.Status = status
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update customer status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
"status": status,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer status updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"status": status,
|
||||
})
|
||||
|
||||
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)
|
||||
func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error {
|
||||
// Get customer first
|
||||
customer, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
// Update verification fields
|
||||
customer.IsVerified = isVerified
|
||||
customer.IsCompliant = isCompliant
|
||||
customer.ComplianceNotes = complianceNotes
|
||||
customer.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, customer)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update customer verification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer verification updated successfully", map[string]interface{}{
|
||||
"customer_id": id,
|
||||
"is_verified": isVerified,
|
||||
"is_compliant": isCompliant,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+92
-113
@@ -16,35 +16,35 @@ import (
|
||||
// Service defines business logic for customer operations
|
||||
type Service interface {
|
||||
// Core customer operations
|
||||
CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *uuid.UUID) (*Customer, error)
|
||||
GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error)
|
||||
UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm, updatedBy *uuid.UUID) (*Customer, error)
|
||||
DeleteCustomer(ctx context.Context, id uuid.UUID, deletedBy *uuid.UUID) error
|
||||
CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error)
|
||||
GetCustomerByID(ctx context.Context, id string) (*Customer, error)
|
||||
UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error)
|
||||
DeleteCustomer(ctx context.Context, id string, deletedBy *string) error
|
||||
|
||||
// Customer listing and search
|
||||
ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error)
|
||||
GetCustomersByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, int64, error)
|
||||
GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error)
|
||||
GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error)
|
||||
GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error)
|
||||
|
||||
// Customer management
|
||||
UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm, updatedBy *uuid.UUID) error
|
||||
UpdateCustomerVerification(ctx context.Context, id uuid.UUID, form *UpdateCustomerVerificationForm, updatedBy *uuid.UUID) error
|
||||
UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error
|
||||
UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error
|
||||
|
||||
// Mobile-specific operations (simplified)
|
||||
CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *uuid.UUID) (*Customer, error)
|
||||
UpdateCustomerMobile(ctx context.Context, id uuid.UUID, form *UpdateCustomerMobileForm, updatedBy *uuid.UUID) (*Customer, error)
|
||||
CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error)
|
||||
UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error)
|
||||
|
||||
// Business operations
|
||||
VerifyCustomer(ctx context.Context, id uuid.UUID, verifiedBy *uuid.UUID) error
|
||||
SuspendCustomer(ctx context.Context, id uuid.UUID, reason string, suspendedBy *uuid.UUID) error
|
||||
ActivateCustomer(ctx context.Context, id uuid.UUID, activatedBy *uuid.UUID) error
|
||||
VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error
|
||||
SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error
|
||||
ActivateCustomer(ctx context.Context, id string, activatedBy *string) error
|
||||
|
||||
// Authentication operations
|
||||
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
|
||||
GetProfile(ctx context.Context, customerID uuid.UUID) (*Customer, error)
|
||||
Logout(ctx context.Context, customerID uuid.UUID, accessToken string) error
|
||||
GetProfile(ctx context.Context, customerID string) (*Customer, error)
|
||||
Logout(ctx context.Context, customerID string, accessToken string) error
|
||||
}
|
||||
|
||||
// customerService implements the Service interface
|
||||
@@ -64,7 +64,7 @@ func NewCustomerService(repository Repository, logger logger.Logger, authService
|
||||
}
|
||||
|
||||
// CreateCustomer creates a new customer
|
||||
func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error) {
|
||||
// Check if email already exists
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingCustomer != nil {
|
||||
@@ -96,27 +96,31 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
|
||||
}
|
||||
|
||||
// Parse company ID if provided
|
||||
var companyID *uuid.UUID
|
||||
var companyID *string
|
||||
if form.CompanyID != nil {
|
||||
parsedID, err := uuid.Parse(*form.CompanyID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid company ID")
|
||||
companyID = form.CompanyID
|
||||
}
|
||||
companyID = &parsedID
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to hash password", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to process password")
|
||||
}
|
||||
|
||||
// Create customer
|
||||
customer := &Customer{
|
||||
ID: uuid.New(),
|
||||
Type: CustomerType(form.Type),
|
||||
Status: CustomerStatusActive,
|
||||
Status: CustomerStatusPending,
|
||||
CompanyID: companyID,
|
||||
FirstName: form.FirstName,
|
||||
LastName: form.LastName,
|
||||
FullName: form.FullName,
|
||||
Username: form.Username,
|
||||
Email: form.Email,
|
||||
Password: "", // Will be set after hashing
|
||||
Password: string(hashedPassword),
|
||||
Phone: form.Phone,
|
||||
Mobile: form.Mobile,
|
||||
CompanyName: form.CompanyName,
|
||||
@@ -134,56 +138,43 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
|
||||
Language: s.getDefaultLanguage(form.Language),
|
||||
Currency: s.getDefaultCurrency(form.Currency),
|
||||
Timezone: s.getDefaultTimezone(form.Timezone),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
CreatedBy: createdBy,
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to hash password", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to process password")
|
||||
}
|
||||
customer.Password = string(hashedPassword)
|
||||
|
||||
// Save to database
|
||||
err = s.repository.Create(ctx, customer)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": form.Email,
|
||||
"type": form.Type,
|
||||
"created_by": createdBy.String(),
|
||||
"username": form.Username,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Customer created successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
"created_by": createdBy.String(),
|
||||
"created_by": *createdBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// GetCustomerByID retrieves a customer by ID
|
||||
func (s *customerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Customer, error) {
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Customer retrieved successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
@@ -191,13 +182,13 @@ func (s *customerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*C
|
||||
}
|
||||
|
||||
// UpdateCustomer updates a customer
|
||||
func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm, updatedBy *uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error) {
|
||||
// Get existing customer
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for update", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
@@ -253,11 +244,7 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form
|
||||
}
|
||||
|
||||
if form.CompanyID != nil {
|
||||
parsedID, err := uuid.Parse(*form.CompanyID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid company ID")
|
||||
}
|
||||
customer.CompanyID = &parsedID
|
||||
customer.CompanyID = form.CompanyID
|
||||
}
|
||||
|
||||
if form.FirstName != nil {
|
||||
@@ -329,22 +316,22 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer updated successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"updated_by": updatedBy.String(),
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// DeleteCustomer deletes a customer (soft delete)
|
||||
func (s *customerService) DeleteCustomer(ctx context.Context, id uuid.UUID, deletedBy *uuid.UUID) error {
|
||||
func (s *customerService) DeleteCustomer(ctx context.Context, id string, deletedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -359,14 +346,14 @@ func (s *customerService) DeleteCustomer(ctx context.Context, id uuid.UUID, dele
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to delete customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to delete customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer deleted successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"deleted_by": deletedBy.String(),
|
||||
"customer_id": id,
|
||||
"deleted_by": deletedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -401,13 +388,9 @@ func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomers
|
||||
}
|
||||
|
||||
// Parse company ID if provided
|
||||
var companyID *uuid.UUID
|
||||
var companyID *string
|
||||
if form.CompanyID != nil {
|
||||
parsedID, err := uuid.Parse(*form.CompanyID)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid company ID")
|
||||
}
|
||||
companyID = &parsedID
|
||||
companyID = form.CompanyID
|
||||
}
|
||||
|
||||
// Get customers
|
||||
@@ -439,12 +422,12 @@ func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomers
|
||||
}
|
||||
|
||||
// GetCustomersByCompanyID retrieves customers by company ID with pagination
|
||||
func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, int64, error) {
|
||||
func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error) {
|
||||
customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID.String(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return nil, 0, errors.New("failed to get customers by company")
|
||||
}
|
||||
@@ -454,7 +437,7 @@ func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to count customers by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID.String(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return customers, 0, nil // Return customers even if count fails
|
||||
}
|
||||
@@ -517,7 +500,7 @@ func (s *customerService) GetCustomersByStatus(ctx context.Context, status Custo
|
||||
}
|
||||
|
||||
// UpdateCustomerStatus updates customer status
|
||||
func (s *customerService) UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm, updatedBy *uuid.UUID) error {
|
||||
func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -530,23 +513,23 @@ func (s *customerService) UpdateCustomerStatus(ctx context.Context, id uuid.UUID
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
"status": form.Status,
|
||||
})
|
||||
return errors.New("failed to update customer status")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer status updated successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
"status": form.Status,
|
||||
"updated_by": updatedBy.String(),
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCustomerVerification updates customer verification status
|
||||
func (s *customerService) UpdateCustomerVerification(ctx context.Context, id uuid.UUID, form *UpdateCustomerVerificationForm, updatedBy *uuid.UUID) error {
|
||||
func (s *customerService) UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -558,23 +541,23 @@ func (s *customerService) UpdateCustomerVerification(ctx context.Context, id uui
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer verification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to update customer verification")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer verification updated successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
"is_verified": form.IsVerified,
|
||||
"is_compliant": form.IsCompliant,
|
||||
"updated_by": updatedBy.String(),
|
||||
"updated_by": updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateCustomerMobile creates a new customer via mobile app (simplified)
|
||||
func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error) {
|
||||
// Check if email already exists
|
||||
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingCustomer != nil {
|
||||
@@ -583,7 +566,6 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
|
||||
|
||||
// Create customer with mobile form
|
||||
customer := &Customer{
|
||||
ID: uuid.New(),
|
||||
Type: CustomerType(form.Type),
|
||||
Status: CustomerStatusActive,
|
||||
FirstName: form.FirstName,
|
||||
@@ -601,8 +583,6 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
|
||||
Language: "en",
|
||||
Currency: "USD",
|
||||
Timezone: "UTC",
|
||||
CreatedAt: time.Now().Unix(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
CreatedBy: createdBy,
|
||||
}
|
||||
|
||||
@@ -623,23 +603,23 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
|
||||
"error": err.Error(),
|
||||
"email": form.Email,
|
||||
"type": form.Type,
|
||||
"created_by": createdBy.String(),
|
||||
"created_by": createdBy,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("Customer created via mobile successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"type": customer.Type,
|
||||
"created_by": createdBy.String(),
|
||||
"created_by": *createdBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// UpdateCustomerMobile updates a customer via mobile app (simplified)
|
||||
func (s *customerService) UpdateCustomerMobile(ctx context.Context, id uuid.UUID, form *UpdateCustomerMobileForm, updatedBy *uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error) {
|
||||
// Get existing customer
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -683,22 +663,22 @@ func (s *customerService) UpdateCustomerMobile(ctx context.Context, id uuid.UUID
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update customer via mobile", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
"updated_by": updatedBy.String(),
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return customer, nil
|
||||
}
|
||||
|
||||
// VerifyCustomer verifies a customer
|
||||
func (s *customerService) VerifyCustomer(ctx context.Context, id uuid.UUID, verifiedBy *uuid.UUID) error {
|
||||
func (s *customerService) VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
customer, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -710,21 +690,21 @@ func (s *customerService) VerifyCustomer(ctx context.Context, id uuid.UUID, veri
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to verify customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to verify customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer verified successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"verified_by": verifiedBy.String(),
|
||||
"customer_id": id,
|
||||
"verified_by": verifiedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SuspendCustomer suspends a customer
|
||||
func (s *customerService) SuspendCustomer(ctx context.Context, id uuid.UUID, reason string, suspendedBy *uuid.UUID) error {
|
||||
func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -736,22 +716,22 @@ func (s *customerService) SuspendCustomer(ctx context.Context, id uuid.UUID, rea
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to suspend customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to suspend customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer suspended successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
"reason": reason,
|
||||
"suspended_by": suspendedBy.String(),
|
||||
"suspended_by": suspendedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActivateCustomer activates a customer
|
||||
func (s *customerService) ActivateCustomer(ctx context.Context, id uuid.UUID, activatedBy *uuid.UUID) error {
|
||||
func (s *customerService) ActivateCustomer(ctx context.Context, id string, activatedBy *string) error {
|
||||
// Get customer to check if exists
|
||||
_, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -763,14 +743,14 @@ func (s *customerService) ActivateCustomer(ctx context.Context, id uuid.UUID, ac
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to activate customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
"customer_id": id,
|
||||
})
|
||||
return errors.New("failed to activate customer")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer activated successfully", map[string]interface{}{
|
||||
"customer_id": id.String(),
|
||||
"activated_by": activatedBy.String(),
|
||||
"customer_id": id,
|
||||
"activated_by": activatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -795,7 +775,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
if customer.Status != CustomerStatusActive {
|
||||
s.logger.Warn("Customer login failed - account not active", map[string]interface{}{
|
||||
"username": form.Username,
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"status": string(customer.Status),
|
||||
})
|
||||
return nil, errors.New("account is not active")
|
||||
@@ -805,7 +785,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
err = bcrypt.CompareHashAndPassword([]byte(customer.Password), []byte(form.Password))
|
||||
if err != nil {
|
||||
s.logger.Warn("Customer login failed - wrong password", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"email": customer.Email,
|
||||
})
|
||||
return nil, errors.New("invalid credentials")
|
||||
@@ -814,11 +794,11 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
// Generate JWT tokens using authorization service
|
||||
companyID := ""
|
||||
if customer.CompanyID != nil {
|
||||
companyID = customer.CompanyID.String()
|
||||
companyID = *customer.CompanyID
|
||||
}
|
||||
|
||||
tokenPair, err := s.authService.GenerateTokenPair(
|
||||
customer.ID.String(),
|
||||
customer.ID,
|
||||
customer.Email,
|
||||
"customer", // Role for customers
|
||||
companyID,
|
||||
@@ -826,14 +806,14 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to generate JWT tokens", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
})
|
||||
return nil, errors.New("failed to generate authentication tokens")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer login successful", map[string]interface{}{
|
||||
"username": form.Username,
|
||||
"customer_id": customer.ID.String(),
|
||||
"customer_id": customer.ID,
|
||||
"customer_type": string(customer.Type),
|
||||
})
|
||||
|
||||
@@ -885,7 +865,7 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
return nil, errors.New("invalid token format")
|
||||
}
|
||||
|
||||
customer, err := s.repository.GetByID(ctx, customerID)
|
||||
customer, err := s.repository.GetByID(ctx, customerID.String())
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer for token refresh", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -900,8 +880,7 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
}
|
||||
|
||||
s.logger.Info("Access token refreshed successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
"customer_id": customer.ID,
|
||||
"customer_type": string(customer.Type),
|
||||
})
|
||||
|
||||
@@ -914,22 +893,22 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
}
|
||||
|
||||
// GetProfile retrieves customer profile information
|
||||
func (s *customerService) GetProfile(ctx context.Context, customerID uuid.UUID) (*Customer, error) {
|
||||
func (s *customerService) GetProfile(ctx context.Context, customerID string) (*Customer, error) {
|
||||
s.logger.Info("Getting customer profile", map[string]interface{}{
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
|
||||
customer, err := s.repository.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get customer profile", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
return nil, errors.New("customer not found")
|
||||
}
|
||||
|
||||
s.logger.Info("Customer profile retrieved successfully", map[string]interface{}{
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
"customer_type": string(customer.Type),
|
||||
})
|
||||
|
||||
@@ -937,9 +916,9 @@ func (s *customerService) GetProfile(ctx context.Context, customerID uuid.UUID)
|
||||
}
|
||||
|
||||
// Logout handles customer logout
|
||||
func (s *customerService) Logout(ctx context.Context, customerID uuid.UUID, accessToken string) error {
|
||||
func (s *customerService) Logout(ctx context.Context, customerID string, accessToken string) error {
|
||||
s.logger.Info("Customer logout", map[string]interface{}{
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
"access_token": accessToken[:10] + "...", // Log only first 10 chars for security
|
||||
})
|
||||
|
||||
@@ -948,13 +927,13 @@ func (s *customerService) Logout(ctx context.Context, customerID uuid.UUID, acce
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to invalidate access token", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
// Don't return error here as the customer should still be logged out
|
||||
}
|
||||
|
||||
s.logger.Info("Customer logout successful", map[string]interface{}{
|
||||
"customer_id": customerID.String(),
|
||||
"customer_id": customerID,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user