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:
n.nakhostin
2025-08-11 14:01:36 +03:30
parent 08cf927294
commit ce4f7e83d3
6 changed files with 604 additions and 618 deletions
+80 -67
View File
@@ -1,7 +1,7 @@
package customer package customer
import ( import (
"github.com/google/uuid" "tm/pkg/mongo"
) )
// CustomerStatus represents customer account status // CustomerStatus represents customer account status
@@ -25,77 +25,105 @@ const (
// Customer represents a customer in the tender management system // Customer represents a customer in the tender management system
type Customer struct { type Customer struct {
ID uuid.UUID `bson:"_id"` mongo.Model
Type CustomerType `bson:"type"` Type CustomerType `bson:"type" json:"type"`
Status CustomerStatus `bson:"status"` Status CustomerStatus `bson:"status" json:"status"`
CompanyID *uuid.UUID `bson:"company_id,omitempty"` CompanyID *string `bson:"company_id,omitempty" json:"company_id,omitempty"`
// Individual customer fields // Individual customer fields
FirstName *string `bson:"first_name,omitempty"` FirstName *string `bson:"first_name,omitempty" json:"first_name,omitempty"`
LastName *string `bson:"last_name,omitempty"` LastName *string `bson:"last_name,omitempty" json:"last_name,omitempty"`
FullName *string `bson:"full_name,omitempty"` FullName *string `bson:"full_name,omitempty" json:"full_name,omitempty"`
Username string `bson:"username"` // Username for authentication Username string `bson:"username" json:"username"` // Username for authentication
Email string `bson:"email"` Email string `bson:"email" json:"email"`
Password string `bson:"password"` // Hashed password for authentication Password string `bson:"password" json:"-"` // Hashed password for authentication
Phone *string `bson:"phone,omitempty"` Phone *string `bson:"phone,omitempty" json:"phone,omitempty"`
Mobile *string `bson:"mobile,omitempty"` Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
// Company customer fields // Company customer fields
CompanyName *string `bson:"company_name,omitempty"` CompanyName *string `bson:"company_name,omitempty" json:"company_name,omitempty"`
RegistrationNumber *string `bson:"registration_number,omitempty"` RegistrationNumber *string `bson:"registration_number,omitempty" json:"registration_number,omitempty"`
TaxID *string `bson:"tax_id,omitempty"` TaxID *string `bson:"tax_id,omitempty" json:"tax_id,omitempty"`
Industry *string `bson:"industry,omitempty"` Industry *string `bson:"industry,omitempty" json:"industry,omitempty"`
// Address information // Address information
Address *Address `bson:"address,omitempty"` Address *Address `bson:"address,omitempty" json:"address,omitempty"`
// Business information // Business information
BusinessType *string `bson:"business_type,omitempty"` BusinessType *string `bson:"business_type,omitempty" json:"business_type,omitempty"`
EmployeeCount *int `bson:"employee_count,omitempty"` EmployeeCount *int `bson:"employee_count,omitempty" json:"employee_count,omitempty"`
AnnualRevenue *float64 `bson:"annual_revenue,omitempty"` AnnualRevenue *float64 `bson:"annual_revenue,omitempty" json:"annual_revenue,omitempty"`
FoundedYear *int `bson:"founded_year,omitempty"` FoundedYear *int `bson:"founded_year,omitempty" json:"founded_year,omitempty"`
// Contact person (for company customers) // Contact person (for company customers)
ContactPerson *ContactPerson `bson:"contact_person,omitempty"` ContactPerson *ContactPerson `bson:"contact_person,omitempty" json:"contact_person,omitempty"`
// Verification and compliance // Verification and compliance
IsVerified bool `bson:"is_verified"` IsVerified bool `bson:"is_verified" json:"is_verified"`
IsCompliant bool `bson:"is_compliant"` IsCompliant bool `bson:"is_compliant" json:"is_compliant"`
ComplianceNotes *string `bson:"compliance_notes,omitempty"` ComplianceNotes *string `bson:"compliance_notes,omitempty" json:"compliance_notes,omitempty"`
// Preferences and settings // Preferences and settings
Language string `bson:"language"` // Default: "en" Language string `bson:"language" json:"language"` // Default: "en"
Currency string `bson:"currency"` // Default: "USD" Currency string `bson:"currency" json:"currency"` // Default: "USD"
Timezone string `bson:"timezone"` // Default: "UTC" Timezone string `bson:"timezone" json:"timezone"` // Default: "UTC"
// Audit fields // Audit fields
CreatedAt int64 `bson:"created_at"` // Unix timestamp CreatedBy *string `bson:"created_by,omitempty" json:"created_by,omitempty"`
UpdatedAt int64 `bson:"updated_at"` // Unix timestamp UpdatedBy *string `bson:"updated_by,omitempty" json:"updated_by,omitempty"`
CreatedBy *uuid.UUID `bson:"created_by,omitempty"` }
UpdatedBy *uuid.UUID `bson:"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 // Address represents a customer's address
type Address struct { type Address struct {
Street string `bson:"street"` Street string `bson:"street" json:"street"`
City string `bson:"city"` City string `bson:"city" json:"city"`
State string `bson:"state"` State string `bson:"state" json:"state"`
PostalCode string `bson:"postal_code"` PostalCode string `bson:"postal_code" json:"postal_code"`
Country string `bson:"country"` Country string `bson:"country" json:"country"`
AddressType string `bson:"address_type"` // billing, shipping, etc. AddressType string `bson:"address_type" json:"address_type"` // billing, shipping, etc.
IsDefault bool `bson:"is_default"` IsDefault bool `bson:"is_default" json:"is_default"`
} }
// ContactPerson represents a contact person for company customers // ContactPerson represents a contact person for company customers
type ContactPerson struct { type ContactPerson struct {
FirstName string `bson:"first_name"` FirstName string `bson:"first_name" json:"first_name"`
LastName string `bson:"last_name"` LastName string `bson:"last_name" json:"last_name"`
FullName string `bson:"full_name"` FullName string `bson:"full_name" json:"full_name"`
Position string `bson:"position"` Position string `bson:"position" json:"position"`
Email string `bson:"email"` Email string `bson:"email" json:"email"`
Phone string `bson:"phone"` Phone string `bson:"phone" json:"phone"`
Mobile *string `bson:"mobile,omitempty"` Mobile *string `bson:"mobile,omitempty" json:"mobile,omitempty"`
IsPrimary bool `bson:"is_primary"` IsPrimary bool `bson:"is_primary" json:"is_primary"`
} }
// CustomerResponse represents the customer data sent in API responses // CustomerResponse represents the customer data sent in API responses
@@ -135,26 +163,11 @@ type CustomerResponse struct {
// ToResponse converts Customer to CustomerResponse // ToResponse converts Customer to CustomerResponse
func (c *Customer) ToResponse() *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{ return &CustomerResponse{
ID: c.ID.String(), ID: c.ID,
Type: string(c.Type), Type: string(c.Type),
Status: string(c.Status), Status: string(c.Status),
CompanyID: &companyID, CompanyID: c.CompanyID,
FirstName: c.FirstName, FirstName: c.FirstName,
LastName: c.LastName, LastName: c.LastName,
FullName: c.FullName, FullName: c.FullName,
@@ -180,7 +193,7 @@ func (c *Customer) ToResponse() *CustomerResponse {
Timezone: c.Timezone, Timezone: c.Timezone,
CreatedAt: c.CreatedAt, CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt, UpdatedAt: c.UpdatedAt,
CreatedBy: &createdBy, CreatedBy: c.CreatedBy,
UpdatedBy: &updatedBy, UpdatedBy: c.UpdatedBy,
} }
} }
+6 -1
View File
@@ -99,13 +99,18 @@ type UpdateCustomerStatusForm struct {
Status string `json:"status" valid:"required,in(active|inactive|suspended|pending)"` 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 { type UpdateCustomerVerificationForm struct {
IsVerified bool `json:"is_verified"` IsVerified bool `json:"is_verified"`
IsCompliant bool `json:"is_compliant"` IsCompliant bool `json:"is_compliant"`
ComplianceNotes *string `json:"compliance_notes,omitempty" valid:"optional,length(0|1000)"` 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 // AddressForm represents the form for customer address
type AddressForm struct { type AddressForm struct {
Street string `json:"street" valid:"required,length(5|200)"` Street string `json:"street" valid:"required,length(5|200)"`
+155 -158
View File
@@ -7,7 +7,6 @@ import (
"tm/pkg/logger" "tm/pkg/logger"
"tm/pkg/response" "tm/pkg/response"
"github.com/google/uuid"
"github.com/labstack/echo/v4" "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()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// TODO: Get user ID from JWT token // Get user ID from JWT token context
createdBy := uuid.New() // This should come from JWT token createdBy, err := user.GetUserIDFromContext(c)
if err != nil {
return response.Unauthorized(c, "User not authenticated")
}
customer, err := h.service.CreateCustomer(c.Request().Context(), form, &createdBy) customer, err := h.service.CreateCustomer(c.Request().Context(), form, &createdBy)
if err != nil { if err != nil {
@@ -98,32 +100,28 @@ func (h *Handler) CreateCustomer(c echo.Context) error {
return response.Created(c, customer.ToResponse(), "Customer created successfully") 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 // @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 // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce 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" // @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID format" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id} [get] // @Router /admin/v1/customers/{id} [get]
func (h *Handler) GetCustomerByID(c echo.Context) error { func (h *Handler) GetCustomerByID(c echo.Context) error {
idStr := c.Param("id") 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 != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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") 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) // UpdateCustomer updates a customer (Web Panel)
// @Summary Update customer information // @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 // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Customer UUID" format(uuid) // @Param id path string true "Customer ID"
// @Param customer body UpdateCustomerForm true "Customer update information - all fields are optional" // @Param customer body UpdateCustomerForm true "Updated customer information"
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer updated successfully" // @Success 200 {object} response.APIResponse{data=CustomerResponse} "Customer updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or input data" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist" // @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 409 {object} response.APIResponse "Conflict - Customer with this email/company already exists" // @Failure 409 {object} response.APIResponse "Conflict - Customer with this email/company already exists"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data" // @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error" // @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] // @Router /admin/v1/customers/{id} [put]
func (h *Handler) UpdateCustomer(c echo.Context) error { func (h *Handler) UpdateCustomer(c echo.Context) error {
idStr := c.Param("id") 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) form, err := response.Parse[UpdateCustomerForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// TODO: Get user ID from JWT token // Get user ID from JWT token context
updatedBy := uuid.New() // This should come from JWT token 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 != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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") return response.Success(c, customer.ToResponse(), "Customer updated successfully")
} }
// DeleteCustomer deletes a customer (soft delete) // DeleteCustomer deletes a customer (Web Panel)
// @Summary Delete customer // @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 // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce 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" // @Success 200 {object} response.APIResponse "Customer deleted successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID format" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id} [delete] // @Router /admin/v1/customers/{id} [delete]
func (h *Handler) DeleteCustomer(c echo.Context) error { func (h *Handler) DeleteCustomer(c echo.Context) error {
idStr := c.Param("id") idStr := c.Param("id")
id, err := uuid.Parse(idStr)
// Get user ID from JWT token context
deletedBy, err := user.GetUserIDFromContext(c)
if err != nil { 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 err = h.service.DeleteCustomer(c.Request().Context(), idStr, &deletedBy)
deletedBy := uuid.New() // This should come from JWT token
err = h.service.DeleteCustomer(c.Request().Context(), id, &deletedBy)
if err != nil { if err != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // 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") return response.Success(c, result, "Customers retrieved successfully")
} }
// UpdateCustomerStatus updates customer status // UpdateCustomerStatus updates customer status (Web Panel)
// @Summary Update customer status // @Summary Update customer status
// @Description Update the status of a customer account. Valid statuses include active, inactive, suspended, and pending. This affects the customer's ability to access the system. // @Description Update customer account status (active, inactive, suspended, pending)
// @Tags Customers-Admin // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Customer UUID" format(uuid) // @Param id path string true "Customer ID"
// @Param status body UpdateCustomerStatusForm true "Status update information" // @Param status body UpdateCustomerStatusForm true "New customer status"
// @Success 200 {object} response.APIResponse "Customer status updated successfully" // @Success 200 {object} response.APIResponse "Customer status updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or status" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist" // @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid status value" // @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id}/status [patch] // @Router /admin/v1/customers/{id}/status [patch]
func (h *Handler) UpdateCustomerStatus(c echo.Context) error { func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
idStr := c.Param("id") 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) form, err := response.Parse[UpdateCustomerStatusForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// TODO: Get user ID from JWT token // Get user ID from JWT token context
updatedBy := uuid.New() // This should come from JWT token 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 != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // UpdateCustomerVerification updates customer verification status (Web Panel)
// @Summary Update customer verification and compliance status // @Summary Update customer verification 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. // @Description Update customer verification and compliance status
// @Tags Customers-Admin // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Customer UUID" format(uuid) // @Param id path string true "Customer ID"
// @Param verification body UpdateCustomerVerificationForm true "Verification and compliance update information" // @Param verification body UpdateCustomerVerificationForm true "Verification status update"
// @Success 200 {object} response.APIResponse "Customer verification updated successfully" // @Success 200 {object} response.APIResponse "Customer verification updated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist" // @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 422 {object} response.APIResponse "Validation error - Invalid verification data" // @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id}/verification [patch] // @Router /admin/v1/customers/{id}/verification [patch]
func (h *Handler) UpdateCustomerVerification(c echo.Context) error { func (h *Handler) UpdateCustomerVerification(c echo.Context) error {
idStr := c.Param("id") 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) form, err := response.Parse[UpdateCustomerVerificationForm](c)
if err != nil { if err != nil {
return response.ValidationError(c, "Invalid request data", err.Error()) return response.ValidationError(c, "Invalid request data", err.Error())
} }
// TODO: Get user ID from JWT token // Get user ID from JWT token context
updatedBy := uuid.New() // This should come from JWT token 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 != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // @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 // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce 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" // @Success 200 {object} response.APIResponse "Customer verified successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id}/verify [post] // @Router /admin/v1/customers/{id}/verify [post]
func (h *Handler) VerifyCustomer(c echo.Context) error { func (h *Handler) VerifyCustomer(c echo.Context) error {
idStr := c.Param("id") idStr := c.Param("id")
id, err := uuid.Parse(idStr)
// Get user ID from JWT token context
verifiedBy, err := user.GetUserIDFromContext(c)
if err != nil { 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 err = h.service.VerifyCustomer(c.Request().Context(), idStr, &verifiedBy)
verifiedBy := uuid.New() // This should come from JWT token
err = h.service.VerifyCustomer(c.Request().Context(), id, &verifiedBy)
if err != nil { if err != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // SuspendCustomer suspends a customer (Web Panel)
// @Summary Suspend customer account // @Summary Suspend customer
// @Description Suspend a customer account by setting their status to suspended. Suspended customers cannot access the system. A reason for suspension must be provided. // @Description Suspend a customer account with optional reason
// @Tags Customers-Admin // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path string true "Customer UUID" format(uuid) // @Param id path string true "Customer ID"
// @Param reason body object true "Suspension reason" SchemaExample({"reason": "Violation of terms of service"}) // @Param suspension body SuspendCustomerForm true "Suspension information"
// @Success 200 {object} response.APIResponse "Customer suspended successfully" // @Success 200 {object} response.APIResponse "Customer suspended successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID or missing reason" // @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
// @Failure 404 {object} response.APIResponse "Not found - Customer does not exist" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id}/suspend [post] // @Router /admin/v1/customers/{id}/suspend [post]
func (h *Handler) SuspendCustomer(c echo.Context) error { func (h *Handler) SuspendCustomer(c echo.Context) error {
idStr := c.Param("id") idStr := c.Param("id")
id, err := uuid.Parse(idStr) form, err := response.Parse[SuspendCustomerForm](c)
if err != nil { 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 // Get user ID from JWT token context
if err := c.Bind(&requestBody); err != nil { suspendedBy, err := user.GetUserIDFromContext(c)
return response.BadRequest(c, "Invalid request body", err.Error()) if err != nil {
return response.Unauthorized(c, "User not authenticated")
} }
reason := requestBody["reason"] err = h.service.SuspendCustomer(c.Request().Context(), idStr, form.Reason, &suspendedBy)
if reason == "" {
return response.BadRequest(c, "Suspension reason is required", "")
}
// TODO: Get user ID from JWT token
suspendedBy := uuid.New() // This should come from JWT token
err = h.service.SuspendCustomer(c.Request().Context(), id, reason, &suspendedBy)
if err != nil { if err != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // ActivateCustomer activates a customer (Web Panel)
// @Summary Activate suspended customer account // @Summary Activate customer
// @Description Reactivate a suspended customer account by setting their status back to active. This reverses the suspension action. // @Description Activate a suspended customer account
// @Tags Customers-Admin // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce 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" // @Success 200 {object} response.APIResponse "Customer activated successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid customer ID" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/{id}/activate [post] // @Router /admin/v1/customers/{id}/activate [post]
func (h *Handler) ActivateCustomer(c echo.Context) error { func (h *Handler) ActivateCustomer(c echo.Context) error {
idStr := c.Param("id") idStr := c.Param("id")
id, err := uuid.Parse(idStr)
// Get user ID from JWT token context
activatedBy, err := user.GetUserIDFromContext(c)
if err != nil { 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 err = h.service.ActivateCustomer(c.Request().Context(), idStr, &activatedBy)
activatedBy := uuid.New() // This should come from JWT token
err = h.service.ActivateCustomer(c.Request().Context(), id, &activatedBy)
if err != nil { if err != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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.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 // @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 // @Tags Customers-Admin
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param companyId path string true "Company UUID" format(uuid) // @Param companyId path string true "Company ID"
// @Param limit query integer false "Number of customers per page (1-100)" minimum(1) maximum(100) default(20) // @Param limit query int false "Number of customers to return (default: 10, max: 100)"
// @Param offset query integer false "Number of customers to skip for pagination" minimum(0) default(0) // @Param offset query int false "Number of customers to skip (default: 0)"
// @Success 200 {object} response.APIResponse{data=[]CustomerResponse,meta=response.Meta} "Customers retrieved successfully" // @Success 200 {object} response.APIResponse{data=[]CustomerResponse} "Customers retrieved successfully"
// @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID format" // @Failure 400 {object} response.APIResponse "Bad request - Invalid company ID"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Security BearerAuth // @Security BearerAuth
// @Router /admin/v1/customers/company/{companyId} [get] // @Router /admin/v1/customers/company/{companyId} [get]
func (h *Handler) GetCustomersByCompanyID(c echo.Context) error { func (h *Handler) GetCustomersByCompanyID(c echo.Context) error {
companyIDStr := c.Param("companyId") 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 limitStr := c.QueryParam("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 { if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit limit = parsedLimit
} }
} }
offset := 0
if offsetStr := c.QueryParam("offset"); offsetStr != "" { if offsetStr := c.QueryParam("offset"); offsetStr != "" {
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 { if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
offset = parsedOffset 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 { 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 var customerResponses []*CustomerResponse
for _, customer := range customers { for _, customer := range customers {
customerResponses = append(customerResponses, customer.ToResponse()) customerResponses = append(customerResponses, customer.ToResponse())
} }
meta := &response.Meta{ return response.Success(c, map[string]interface{}{
Total: int(total), "customers": customerResponses,
Limit: limit, "total": total,
Offset: offset, "limit": limit,
} "offset": offset,
}, "Customers retrieved successfully")
return response.SuccessWithMeta(c, customerResponses, meta, "Customers retrieved successfully")
} }
// GetCustomersByType retrieves customers by type // 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") return response.Success(c, authResponse, "Token refreshed successfully")
} }
// GetProfile retrieves customer profile information // GetProfile retrieves customer profile information (Mobile)
// @Summary Get customer profile // @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. // @Description Retrieve current customer profile information
// @Tags Customers-Authorization // @Tags Customers-Mobile
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security BearerAuth // @Security BearerAuth
// @Success 200 {object} response.APIResponse{data=CustomerResponse} "Profile retrieved successfully" // @Success 200 {object} response.APIResponse{data=CustomerResponse} "Profile retrieved successfully"
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing access token" // @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or missing token"
// @Failure 404 {object} response.APIResponse "Not found - Customer profile not found" // @Failure 404 {object} response.APIResponse "Not found - Customer not found"
// @Failure 500 {object} response.APIResponse "Internal server error" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Router /api/v1/profile [get] // @Router /api/v1/profile [get]
func (h *Handler) GetProfile(c echo.Context) error { 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) customerID, err := GetCustomerIDFromContext(c)
if err != nil { 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) customer, err := h.service.GetProfile(c.Request().Context(), customerID)
if err != nil { if err != nil {
if err.Error() == "customer not found" { if err.Error() == "customer not found" {
return response.NotFound(c, "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") return response.Success(c, customer.ToResponse(), "Profile retrieved successfully")
} }
// Logout handles customer logout // Logout handles customer logout (Mobile)
// @Summary Customer logout // @Summary Customer logout
// @Description Logout customer and invalidate access token. This ensures the token cannot be used for future requests. // @Description Logout customer and invalidate access token
// @Tags Customers-Authorization // @Tags Customers-Mobile
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security BearerAuth // @Security BearerAuth
// @Success 200 {object} response.APIResponse "Logout successful" // @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" // @Failure 500 {object} response.APIResponse "Internal server error"
// @Router /api/v1/logout [delete] // @Router /api/v1/logout [delete]
func (h *Handler) Logout(c echo.Context) error { 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) customerID, err := GetCustomerIDFromContext(c)
if err != nil { 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 // Get access token from context
accessToken, err := GetAccessTokenFromContext(c) accessToken, err := GetAccessTokenFromContext(c)
if err != nil { 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) err = h.service.Logout(c.Request().Context(), customerID, accessToken)
if err != nil { 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")
} }
+4 -11
View File
@@ -5,7 +5,6 @@ import (
"strings" "strings"
"tm/pkg/response" "tm/pkg/response"
"github.com/google/uuid"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@@ -44,13 +43,7 @@ func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
} }
// Extract customer information from token // Extract customer information from token
customerID, err := uuid.Parse(validationResult.Claims.UserID) customerID := validationResult.Claims.UserID
if err != nil {
h.logger.Error("Failed to parse customer ID from token", map[string]interface{}{
"error": err.Error(),
})
return response.Unauthorized(c, "Invalid token format")
}
// Store customer information in context for handlers to use // Store customer information in context for handlers to use
c.Set("customer_id", customerID) c.Set("customer_id", customerID)
@@ -66,10 +59,10 @@ func (h *Handler) AuthMiddleware() echo.MiddlewareFunc {
} }
// GetCustomerIDFromContext extracts customer ID from Echo context // GetCustomerIDFromContext extracts customer ID from Echo context
func GetCustomerIDFromContext(c echo.Context) (uuid.UUID, error) { func GetCustomerIDFromContext(c echo.Context) (string, error) {
customerID, ok := c.Get("customer_id").(uuid.UUID) customerID, ok := c.Get("customer_id").(string)
if !ok { 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 return customerID, nil
} }
+263 -264
View File
@@ -7,162 +7,94 @@ import (
"tm/pkg/logger" "tm/pkg/logger"
mongopkg "tm/pkg/mongo" mongopkg "tm/pkg/mongo"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson" "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 // Repository defines the interface for customer data operations
type Repository interface { type Repository interface {
Create(ctx context.Context, customer *Customer) error 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) GetByEmail(ctx context.Context, email string) (*Customer, error)
GetByUsername(ctx context.Context, username string) (*Customer, error) GetByUsername(ctx context.Context, username string) (*Customer, error)
GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
GetByTaxID(ctx context.Context, taxID 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 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) 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) 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 uuid.UUID) (int64, error) CountByCompanyID(ctx context.Context, companyID string) (int64, error)
CountByType(ctx context.Context, customerType CustomerType) (int64, error) CountByType(ctx context.Context, customerType CustomerType) (int64, error)
CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) 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 { type customerRepository struct {
collection *mongo.Collection ormRepo mongopkg.Repository[Customer]
logger logger.Logger logger logger.Logger
} }
// NewCustomerRepository creates a new customer repository // NewCustomerRepository creates a new customer repository
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) 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 // Create indexes
repo := &customerRepository{ err := mongoManager.CreateIndexes("customers", indexes)
collection: collection,
logger: logger,
}
repo.createIndexes()
return repo
}
// createIndexes creates necessary database indexes
func (r *customerRepository) createIndexes() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Create indexes
indexes := []mongo.IndexModel{
{
Keys: bson.D{
{Key: "email", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "username", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "company_name", Value: 1},
},
Options: options.Index().SetSparse(true),
},
{
Keys: bson.D{
{Key: "registration_number", Value: 1},
},
Options: options.Index().SetSparse(true),
},
{
Keys: bson.D{
{Key: "tax_id", Value: 1},
},
Options: options.Index().SetSparse(true),
},
{
Keys: bson.D{
{Key: "company_id", Value: 1},
},
Options: options.Index().SetSparse(true),
},
{
Keys: bson.D{
{Key: "type", Value: 1},
},
},
{
Keys: bson.D{
{Key: "status", Value: 1},
},
},
{
Keys: bson.D{
{Key: "industry", Value: 1},
},
},
{
Keys: bson.D{
{Key: "created_at", Value: -1},
},
},
{
Keys: bson.D{
{Key: "updated_at", Value: -1},
},
},
}
_, err := r.collection.Indexes().CreateMany(ctx, indexes)
if err != nil { 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(), "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 // Create creates a new customer
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error { func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
// Set timestamps // Set created/updated timestamps using Unix timestamps
now := time.Now().Unix() now := time.Now().Unix()
customer.CreatedAt = now customer.SetCreatedAt(now)
customer.UpdatedAt = now customer.SetUpdatedAt(now)
// Set defaults if not provided // Use ORM to create customer
if customer.Language == "" { err := r.ormRepo.Create(ctx, customer)
customer.Language = "en"
}
if customer.Currency == "" {
customer.Currency = "USD"
}
if customer.Timezone == "" {
customer.Timezone = "UTC"
}
_, err := r.collection.InsertOne(ctx, customer)
if err != nil { if err != nil {
if mongo.IsDuplicateKeyError(err) { r.logger.Error("Failed to create customer", map[string]interface{}{
return errors.New("customer with this email already exists") "error": err.Error(),
} "email": customer.Email,
"username": customer.Username,
})
return err return err
} }
r.logger.Info("Customer created successfully", map[string]interface{}{ r.logger.Info("Customer created successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
"type": customer.Type, "type": customer.Type,
}) })
@@ -171,107 +103,143 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
} }
// GetByID retrieves a customer by ID // GetByID retrieves a customer by ID
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) { func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) {
var customer Customer customer, err := r.ormRepo.FindByID(ctx, id)
err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&customer)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByEmail retrieves a customer by email // GetByEmail retrieves a customer by email
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) { func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
var customer Customer filter := bson.M{"email": email}
err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&customer) customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByUsername retrieves a customer by username // GetByUsername retrieves a customer by username
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) { func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
var customer Customer filter := bson.M{"username": username}
err := r.collection.FindOne(ctx, bson.M{"username": username}).Decode(&customer) customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByCompanyName retrieves a customer by company name // GetByCompanyName retrieves a customer by company name
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) { func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
var customer Customer filter := bson.M{"company_name": companyName}
err := r.collection.FindOne(ctx, bson.M{"company_name": companyName}).Decode(&customer) customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByRegistrationNumber retrieves a customer by registration number // GetByRegistrationNumber retrieves a customer by registration number
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) { func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
var customer Customer filter := bson.M{"registration_number": registrationNumber}
err := r.collection.FindOne(ctx, bson.M{"registration_number": registrationNumber}).Decode(&customer) customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByTaxID retrieves a customer by tax ID // GetByTaxID retrieves a customer by tax ID
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) { func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
var customer Customer filter := bson.M{"tax_id": taxID}
err := r.collection.FindOne(ctx, bson.M{"tax_id": taxID}).Decode(&customer) customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil { if err != nil {
if err == mongo.ErrNoDocuments { if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found") 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 nil, err
} }
return &customer, nil return customer, nil
} }
// GetByCompanyID retrieves customers by company ID with pagination // GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) { func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) {
filter := bson.M{"company_id": companyID} // Build pagination
pagination := mongopkg.NewPaginationBuilder().
Limit(limit).
Skip(offset).
SortDesc("created_at").
Build()
opts := options.Find(). // Filter by company ID and active status
SetLimit(int64(limit)). filter := bson.M{
SetSkip(int64(offset)). "company_id": companyID,
SetSort(bson.D{{Key: "created_at", Value: -1}}) "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 { 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 return nil, err
} }
defer cursor.Close(ctx)
var customers []*Customer // Convert []Customer to []*Customer
if err = cursor.All(ctx, &customers); err != nil { customers := make([]*Customer, len(result.Items))
return nil, err for i := range result.Items {
customers[i] = &result.Items[i]
} }
return customers, nil return customers, nil
@@ -279,22 +247,21 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.
// Update updates a customer // Update updates a customer
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error { 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} // Use ORM to update customer
update := bson.M{"$set": customer} err := r.ormRepo.Update(ctx, customer)
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil { if err != nil {
r.logger.Error("Failed to update customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
return err return err
} }
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Customer updated successfully", map[string]interface{}{ r.logger.Info("Customer updated successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "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) // Delete deletes a customer (soft delete by setting status to inactive)
func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error { func (r *customerRepository) Delete(ctx context.Context, id string) error {
filter := bson.M{"_id": id} // Get customer first
update := bson.M{ customer, err := r.GetByID(ctx, id)
"$set": bson.M{
"status": CustomerStatusInactive,
"updated_at": time.Now().Unix(),
},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil { if err != nil {
return err return err
} }
if result.MatchedCount == 0 { // Update status to inactive
return errors.New("customer not found") 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{}{ r.logger.Info("Customer deleted successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
}) })
return nil return nil
@@ -329,124 +299,125 @@ func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
// List retrieves customers with pagination // List retrieves customers with pagination
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) { func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
opts := options.Find(). // Build pagination
SetLimit(int64(limit)). pagination := mongopkg.NewPaginationBuilder().
SetSkip(int64(offset)). Limit(limit).
SetSort(bson.D{{Key: "created_at", Value: -1}}) 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 { if err != nil {
r.logger.Error("Failed to list customers", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, err return nil, err
} }
defer cursor.Close(ctx)
var customers []*Customer // Convert []Customer to []*Customer
if err = cursor.All(ctx, &customers); err != nil { customers := make([]*Customer, len(result.Items))
return nil, err for i := range result.Items {
customers[i] = &result.Items[i]
} }
return customers, nil return customers, nil
} }
// Search searches customers with filters // Search retrieves customers with search and 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) { 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{} filter := bson.M{}
// Add search filter
if search != "" { if search != "" {
filter["$or"] = []bson.M{ filter["$text"] = bson.M{"$search": search}
{"email": primitive.Regex{Pattern: search, Options: "i"}},
{"company_name": primitive.Regex{Pattern: search, Options: "i"}},
{"first_name": primitive.Regex{Pattern: search, Options: "i"}},
{"last_name": primitive.Regex{Pattern: search, Options: "i"}},
{"full_name": primitive.Regex{Pattern: search, Options: "i"}},
}
} }
// Add type filter
if customerType != nil { if customerType != nil {
filter["type"] = *customerType filter["type"] = *customerType
} }
// Add status filter
if status != nil { if status != nil {
filter["status"] = *status filter["status"] = *status
} }
// Add company ID filter
if companyID != nil { if companyID != nil {
filter["company_id"] = *companyID filter["company_id"] = *companyID
} }
// Add industry filter
if industry != nil { if industry != nil {
filter["industry"] = *industry filter["industry"] = *industry
} }
// Add verification filter
if isVerified != nil { if isVerified != nil {
filter["is_verified"] = *isVerified filter["is_verified"] = *isVerified
} }
// Add compliance filter
if isCompliant != nil { if isCompliant != nil {
filter["is_compliant"] = *isCompliant filter["is_compliant"] = *isCompliant
} }
// Add language filter
if language != nil { if language != nil {
filter["language"] = *language filter["language"] = *language
} }
// Add currency filter
if currency != nil { if currency != nil {
filter["currency"] = *currency filter["currency"] = *currency
} }
// Set sort options // Build pagination
sortDirection := 1 pagination := mongopkg.NewPaginationBuilder().
Limit(limit).
Skip(offset)
// Set sort
if sortBy != "" {
if sortOrder == "desc" { if sortOrder == "desc" {
sortDirection = -1 pagination.SortDesc(sortBy)
} else {
pagination.SortAsc(sortBy)
}
} else {
pagination.SortDesc("created_at") // Default sort
} }
var sortField string // Use ORM to find customers
switch sortBy { result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
case "email":
sortField = "email"
case "company_name":
sortField = "company_name"
case "updated_at":
sortField = "updated_at"
case "status":
sortField = "status"
default:
sortField = "created_at"
}
opts := options.Find().
SetLimit(int64(limit)).
SetSkip(int64(offset)).
SetSort(bson.D{{Key: sortField, Value: sortDirection}})
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil { if err != nil {
r.logger.Error("Failed to search customers", map[string]interface{}{
"error": err.Error(),
"search": search,
})
return nil, err return nil, err
} }
defer cursor.Close(ctx)
var customers []*Customer // Convert []Customer to []*Customer
if err = cursor.All(ctx, &customers); err != nil { customers := make([]*Customer, len(result.Items))
return nil, err for i := range result.Items {
customers[i] = &result.Items[i]
} }
return customers, nil return customers, nil
} }
// CountByCompanyID counts customers by company ID // CountByCompanyID counts customers by company ID
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error) { func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
filter := bson.M{"company_id": companyID} filter := bson.M{
count, err := r.collection.CountDocuments(ctx, filter) "company_id": companyID,
"status": bson.M{"$ne": CustomerStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil { 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 return 0, err
} }
@@ -455,9 +426,17 @@ func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uui
// CountByType counts customers by type // CountByType counts customers by type
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) { func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
filter := bson.M{"type": customerType} filter := bson.M{
count, err := r.collection.CountDocuments(ctx, filter) "type": customerType,
"status": bson.M{"$ne": CustomerStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil { if err != nil {
r.logger.Error("Failed to count customers by type", map[string]interface{}{
"error": err.Error(),
"customer_type": customerType,
})
return 0, err return 0, err
} }
@@ -467,8 +446,13 @@ func (r *customerRepository) CountByType(ctx context.Context, customerType Custo
// CountByStatus counts customers by status // CountByStatus counts customers by status
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) { func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
filter := bson.M{"status": status} filter := bson.M{"status": status}
count, err := r.collection.CountDocuments(ctx, filter)
count, err := r.ormRepo.Count(ctx, filter)
if err != nil { if err != nil {
r.logger.Error("Failed to count customers by status", map[string]interface{}{
"error": err.Error(),
"status": status,
})
return 0, err return 0, err
} }
@@ -476,50 +460,65 @@ func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerS
} }
// UpdateStatus updates customer status // UpdateStatus updates customer status
func (r *customerRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error { func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error {
filter := bson.M{"_id": id} // Get customer first
update := bson.M{ customer, err := r.GetByID(ctx, id)
"$set": bson.M{
"status": status,
"updated_at": time.Now().Unix(),
},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil { if err != nil {
return err return err
} }
if result.MatchedCount == 0 { // Update status
return errors.New("customer not found") 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 return nil
} }
// UpdateVerification updates customer verification status // UpdateVerification updates customer verification status
func (r *customerRepository) UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error { func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error {
filter := bson.M{"_id": id} // Get customer first
update := bson.M{ customer, err := r.GetByID(ctx, id)
"$set": bson.M{
"is_verified": isVerified,
"is_compliant": isCompliant,
"updated_at": time.Now().Unix(),
},
}
if complianceNotes != nil {
update["$set"].(bson.M)["compliance_notes"] = *complianceNotes
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil { if err != nil {
return err return err
} }
if result.MatchedCount == 0 { // Update verification fields
return errors.New("customer not found") 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 return nil
} }
+92 -113
View File
@@ -16,35 +16,35 @@ import (
// Service defines business logic for customer operations // Service defines business logic for customer operations
type Service interface { type Service interface {
// Core customer operations // Core customer operations
CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *uuid.UUID) (*Customer, error) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error)
GetCustomerByID(ctx context.Context, id uuid.UUID) (*Customer, error) GetCustomerByID(ctx context.Context, id string) (*Customer, error)
UpdateCustomer(ctx context.Context, id uuid.UUID, form *UpdateCustomerForm, updatedBy *uuid.UUID) (*Customer, error) UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error)
DeleteCustomer(ctx context.Context, id uuid.UUID, deletedBy *uuid.UUID) error DeleteCustomer(ctx context.Context, id string, deletedBy *string) error
// Customer listing and search // Customer listing and search
ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) 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) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error)
GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error)
// Customer management // Customer management
UpdateCustomerStatus(ctx context.Context, id uuid.UUID, form *UpdateCustomerStatusForm, updatedBy *uuid.UUID) error UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error
UpdateCustomerVerification(ctx context.Context, id uuid.UUID, form *UpdateCustomerVerificationForm, updatedBy *uuid.UUID) error UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error
// Mobile-specific operations (simplified) // Mobile-specific operations (simplified)
CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *uuid.UUID) (*Customer, error) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error)
UpdateCustomerMobile(ctx context.Context, id uuid.UUID, form *UpdateCustomerMobileForm, updatedBy *uuid.UUID) (*Customer, error) UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error)
// Business operations // Business operations
VerifyCustomer(ctx context.Context, id uuid.UUID, verifiedBy *uuid.UUID) error VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error
SuspendCustomer(ctx context.Context, id uuid.UUID, reason string, suspendedBy *uuid.UUID) error SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error
ActivateCustomer(ctx context.Context, id uuid.UUID, activatedBy *uuid.UUID) error ActivateCustomer(ctx context.Context, id string, activatedBy *string) error
// Authentication operations // Authentication operations
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error) RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
GetProfile(ctx context.Context, customerID uuid.UUID) (*Customer, error) GetProfile(ctx context.Context, customerID string) (*Customer, error)
Logout(ctx context.Context, customerID uuid.UUID, accessToken string) error Logout(ctx context.Context, customerID string, accessToken string) error
} }
// customerService implements the Service interface // customerService implements the Service interface
@@ -64,7 +64,7 @@ func NewCustomerService(repository Repository, logger logger.Logger, authService
} }
// CreateCustomer creates a new customer // 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 // Check if email already exists
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
if existingCustomer != nil { if existingCustomer != nil {
@@ -96,27 +96,31 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
} }
// Parse company ID if provided // Parse company ID if provided
var companyID *uuid.UUID var companyID *string
if form.CompanyID != nil { if form.CompanyID != nil {
parsedID, err := uuid.Parse(*form.CompanyID) companyID = form.CompanyID
if err != nil {
return nil, errors.New("invalid company ID")
} }
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 // Create customer
customer := &Customer{ customer := &Customer{
ID: uuid.New(),
Type: CustomerType(form.Type), Type: CustomerType(form.Type),
Status: CustomerStatusActive, Status: CustomerStatusPending,
CompanyID: companyID, CompanyID: companyID,
FirstName: form.FirstName, FirstName: form.FirstName,
LastName: form.LastName, LastName: form.LastName,
FullName: form.FullName, FullName: form.FullName,
Username: form.Username, Username: form.Username,
Email: form.Email, Email: form.Email,
Password: "", // Will be set after hashing Password: string(hashedPassword),
Phone: form.Phone, Phone: form.Phone,
Mobile: form.Mobile, Mobile: form.Mobile,
CompanyName: form.CompanyName, CompanyName: form.CompanyName,
@@ -134,56 +138,43 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
Language: s.getDefaultLanguage(form.Language), Language: s.getDefaultLanguage(form.Language),
Currency: s.getDefaultCurrency(form.Currency), Currency: s.getDefaultCurrency(form.Currency),
Timezone: s.getDefaultTimezone(form.Timezone), Timezone: s.getDefaultTimezone(form.Timezone),
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
CreatedBy: createdBy, 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 // Save to database
err = s.repository.Create(ctx, customer) err = s.repository.Create(ctx, customer)
if err != nil { if err != nil {
s.logger.Error("Failed to create customer", map[string]interface{}{ s.logger.Error("Failed to create customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"email": form.Email, "email": form.Email,
"type": form.Type, "username": form.Username,
"created_by": createdBy.String(),
}) })
return nil, err return nil, err
} }
s.logger.Info("Customer created successfully", map[string]interface{}{ s.logger.Info("Customer created successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
"type": customer.Type, "type": customer.Type,
"created_by": createdBy.String(), "created_by": *createdBy,
}) })
return customer, nil return customer, nil
} }
// GetCustomerByID retrieves a customer by ID // 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) customer, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to get customer by ID", map[string]interface{}{ s.logger.Error("Failed to get customer by ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return nil, err return nil, err
} }
s.logger.Info("Customer retrieved successfully", map[string]interface{}{ s.logger.Info("Customer retrieved successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
}) })
@@ -191,13 +182,13 @@ func (s *customerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*C
} }
// UpdateCustomer updates a customer // 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 // Get existing customer
customer, err := s.repository.GetByID(ctx, id) customer, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
s.logger.Error("Failed to get customer for update", map[string]interface{}{ s.logger.Error("Failed to get customer for update", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return nil, errors.New("customer not found") 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 { if form.CompanyID != nil {
parsedID, err := uuid.Parse(*form.CompanyID) customer.CompanyID = form.CompanyID
if err != nil {
return nil, errors.New("invalid company ID")
}
customer.CompanyID = &parsedID
} }
if form.FirstName != nil { if form.FirstName != nil {
@@ -329,22 +316,22 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id uuid.UUID, form
if err != nil { if err != nil {
s.logger.Error("Failed to update customer", map[string]interface{}{ s.logger.Error("Failed to update customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return nil, errors.New("failed to update customer") return nil, errors.New("failed to update customer")
} }
s.logger.Info("Customer updated successfully", map[string]interface{}{ s.logger.Info("Customer updated successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
"updated_by": updatedBy.String(), "updated_by": *updatedBy,
}) })
return customer, nil return customer, nil
} }
// DeleteCustomer deletes a customer (soft delete) // 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 // Get customer to check if exists
customer, err := s.repository.GetByID(ctx, id) customer, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -359,14 +346,14 @@ func (s *customerService) DeleteCustomer(ctx context.Context, id uuid.UUID, dele
if err != nil { if err != nil {
s.logger.Error("Failed to delete customer", map[string]interface{}{ s.logger.Error("Failed to delete customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return errors.New("failed to delete customer") return errors.New("failed to delete customer")
} }
s.logger.Info("Customer deleted successfully", map[string]interface{}{ s.logger.Info("Customer deleted successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"deleted_by": deletedBy.String(), "deleted_by": deletedBy,
}) })
return nil return nil
@@ -401,13 +388,9 @@ func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomers
} }
// Parse company ID if provided // Parse company ID if provided
var companyID *uuid.UUID var companyID *string
if form.CompanyID != nil { if form.CompanyID != nil {
parsedID, err := uuid.Parse(*form.CompanyID) companyID = form.CompanyID
if err != nil {
return nil, errors.New("invalid company ID")
}
companyID = &parsedID
} }
// Get customers // Get customers
@@ -439,12 +422,12 @@ func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomers
} }
// GetCustomersByCompanyID retrieves customers by company ID with pagination // 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) customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset)
if err != nil { if err != nil {
s.logger.Error("Failed to get customers by company ID", map[string]interface{}{ s.logger.Error("Failed to get customers by company ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"company_id": companyID.String(), "company_id": companyID,
}) })
return nil, 0, errors.New("failed to get customers by company") 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 { if err != nil {
s.logger.Error("Failed to count customers by company ID", map[string]interface{}{ s.logger.Error("Failed to count customers by company ID", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"company_id": companyID.String(), "company_id": companyID,
}) })
return customers, 0, nil // Return customers even if count fails 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 // 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 // Get customer to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -530,23 +513,23 @@ func (s *customerService) UpdateCustomerStatus(ctx context.Context, id uuid.UUID
if err != nil { if err != nil {
s.logger.Error("Failed to update customer status", map[string]interface{}{ s.logger.Error("Failed to update customer status", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
"status": form.Status, "status": form.Status,
}) })
return errors.New("failed to update customer status") return errors.New("failed to update customer status")
} }
s.logger.Info("Customer status updated successfully", map[string]interface{}{ s.logger.Info("Customer status updated successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"status": form.Status, "status": form.Status,
"updated_by": updatedBy.String(), "updated_by": updatedBy,
}) })
return nil return nil
} }
// UpdateCustomerVerification updates customer verification status // 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 // Get customer to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -558,23 +541,23 @@ func (s *customerService) UpdateCustomerVerification(ctx context.Context, id uui
if err != nil { if err != nil {
s.logger.Error("Failed to update customer verification", map[string]interface{}{ s.logger.Error("Failed to update customer verification", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return errors.New("failed to update customer verification") return errors.New("failed to update customer verification")
} }
s.logger.Info("Customer verification updated successfully", map[string]interface{}{ s.logger.Info("Customer verification updated successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"is_verified": form.IsVerified, "is_verified": form.IsVerified,
"is_compliant": form.IsCompliant, "is_compliant": form.IsCompliant,
"updated_by": updatedBy.String(), "updated_by": updatedBy,
}) })
return nil return nil
} }
// CreateCustomerMobile creates a new customer via mobile app (simplified) // 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 // Check if email already exists
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email) existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
if existingCustomer != nil { if existingCustomer != nil {
@@ -583,7 +566,6 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
// Create customer with mobile form // Create customer with mobile form
customer := &Customer{ customer := &Customer{
ID: uuid.New(),
Type: CustomerType(form.Type), Type: CustomerType(form.Type),
Status: CustomerStatusActive, Status: CustomerStatusActive,
FirstName: form.FirstName, FirstName: form.FirstName,
@@ -601,8 +583,6 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
Language: "en", Language: "en",
Currency: "USD", Currency: "USD",
Timezone: "UTC", Timezone: "UTC",
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
CreatedBy: createdBy, CreatedBy: createdBy,
} }
@@ -623,23 +603,23 @@ func (s *customerService) CreateCustomerMobile(ctx context.Context, form *Create
"error": err.Error(), "error": err.Error(),
"email": form.Email, "email": form.Email,
"type": form.Type, "type": form.Type,
"created_by": createdBy.String(), "created_by": createdBy,
}) })
return nil, err return nil, err
} }
s.logger.Info("Customer created via mobile successfully", map[string]interface{}{ s.logger.Info("Customer created via mobile successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
"type": customer.Type, "type": customer.Type,
"created_by": createdBy.String(), "created_by": *createdBy,
}) })
return customer, nil return customer, nil
} }
// UpdateCustomerMobile updates a customer via mobile app (simplified) // 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 // Get existing customer
customer, err := s.repository.GetByID(ctx, id) customer, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -683,22 +663,22 @@ func (s *customerService) UpdateCustomerMobile(ctx context.Context, id uuid.UUID
if err != nil { if err != nil {
s.logger.Error("Failed to update customer via mobile", map[string]interface{}{ s.logger.Error("Failed to update customer via mobile", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return nil, errors.New("failed to update customer") return nil, errors.New("failed to update customer")
} }
s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{ s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
"updated_by": updatedBy.String(), "updated_by": *updatedBy,
}) })
return customer, nil return customer, nil
} }
// VerifyCustomer verifies a customer // 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 // Get customer to check if exists
customer, err := s.repository.GetByID(ctx, id) customer, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -710,21 +690,21 @@ func (s *customerService) VerifyCustomer(ctx context.Context, id uuid.UUID, veri
if err != nil { if err != nil {
s.logger.Error("Failed to verify customer", map[string]interface{}{ s.logger.Error("Failed to verify customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return errors.New("failed to verify customer") return errors.New("failed to verify customer")
} }
s.logger.Info("Customer verified successfully", map[string]interface{}{ s.logger.Info("Customer verified successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"verified_by": verifiedBy.String(), "verified_by": verifiedBy,
}) })
return nil return nil
} }
// SuspendCustomer suspends a customer // 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 // Get customer to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -736,22 +716,22 @@ func (s *customerService) SuspendCustomer(ctx context.Context, id uuid.UUID, rea
if err != nil { if err != nil {
s.logger.Error("Failed to suspend customer", map[string]interface{}{ s.logger.Error("Failed to suspend customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return errors.New("failed to suspend customer") return errors.New("failed to suspend customer")
} }
s.logger.Info("Customer suspended successfully", map[string]interface{}{ s.logger.Info("Customer suspended successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"reason": reason, "reason": reason,
"suspended_by": suspendedBy.String(), "suspended_by": suspendedBy,
}) })
return nil return nil
} }
// ActivateCustomer activates a customer // 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 // Get customer to check if exists
_, err := s.repository.GetByID(ctx, id) _, err := s.repository.GetByID(ctx, id)
if err != nil { if err != nil {
@@ -763,14 +743,14 @@ func (s *customerService) ActivateCustomer(ctx context.Context, id uuid.UUID, ac
if err != nil { if err != nil {
s.logger.Error("Failed to activate customer", map[string]interface{}{ s.logger.Error("Failed to activate customer", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": id.String(), "customer_id": id,
}) })
return errors.New("failed to activate customer") return errors.New("failed to activate customer")
} }
s.logger.Info("Customer activated successfully", map[string]interface{}{ s.logger.Info("Customer activated successfully", map[string]interface{}{
"customer_id": id.String(), "customer_id": id,
"activated_by": activatedBy.String(), "activated_by": activatedBy,
}) })
return nil return nil
@@ -795,7 +775,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
if customer.Status != CustomerStatusActive { if customer.Status != CustomerStatusActive {
s.logger.Warn("Customer login failed - account not active", map[string]interface{}{ s.logger.Warn("Customer login failed - account not active", map[string]interface{}{
"username": form.Username, "username": form.Username,
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"status": string(customer.Status), "status": string(customer.Status),
}) })
return nil, errors.New("account is not active") 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)) err = bcrypt.CompareHashAndPassword([]byte(customer.Password), []byte(form.Password))
if err != nil { if err != nil {
s.logger.Warn("Customer login failed - wrong password", map[string]interface{}{ s.logger.Warn("Customer login failed - wrong password", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email, "email": customer.Email,
}) })
return nil, errors.New("invalid credentials") 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 // Generate JWT tokens using authorization service
companyID := "" companyID := ""
if customer.CompanyID != nil { if customer.CompanyID != nil {
companyID = customer.CompanyID.String() companyID = *customer.CompanyID
} }
tokenPair, err := s.authService.GenerateTokenPair( tokenPair, err := s.authService.GenerateTokenPair(
customer.ID.String(), customer.ID,
customer.Email, customer.Email,
"customer", // Role for customers "customer", // Role for customers
companyID, companyID,
@@ -826,14 +806,14 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
if err != nil { if err != nil {
s.logger.Error("Failed to generate JWT tokens", map[string]interface{}{ s.logger.Error("Failed to generate JWT tokens", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": customer.ID.String(), "customer_id": customer.ID,
}) })
return nil, errors.New("failed to generate authentication tokens") return nil, errors.New("failed to generate authentication tokens")
} }
s.logger.Info("Customer login successful", map[string]interface{}{ s.logger.Info("Customer login successful", map[string]interface{}{
"username": form.Username, "username": form.Username,
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"customer_type": string(customer.Type), "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") 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 { if err != nil {
s.logger.Error("Failed to get customer for token refresh", map[string]interface{}{ s.logger.Error("Failed to get customer for token refresh", map[string]interface{}{
"error": err.Error(), "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{}{ s.logger.Info("Access token refreshed successfully", map[string]interface{}{
"customer_id": customer.ID.String(), "customer_id": customer.ID,
"email": customer.Email,
"customer_type": string(customer.Type), "customer_type": string(customer.Type),
}) })
@@ -914,22 +893,22 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
} }
// GetProfile retrieves customer profile information // 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{}{ s.logger.Info("Getting customer profile", map[string]interface{}{
"customer_id": customerID.String(), "customer_id": customerID,
}) })
customer, err := s.repository.GetByID(ctx, customerID) customer, err := s.repository.GetByID(ctx, customerID)
if err != nil { if err != nil {
s.logger.Error("Failed to get customer profile", map[string]interface{}{ s.logger.Error("Failed to get customer profile", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": customerID.String(), "customer_id": customerID,
}) })
return nil, errors.New("customer not found") return nil, errors.New("customer not found")
} }
s.logger.Info("Customer profile retrieved successfully", map[string]interface{}{ s.logger.Info("Customer profile retrieved successfully", map[string]interface{}{
"customer_id": customerID.String(), "customer_id": customerID,
"customer_type": string(customer.Type), "customer_type": string(customer.Type),
}) })
@@ -937,9 +916,9 @@ func (s *customerService) GetProfile(ctx context.Context, customerID uuid.UUID)
} }
// Logout handles customer logout // 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{}{ 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 "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 { if err != nil {
s.logger.Error("Failed to invalidate access token", map[string]interface{}{ s.logger.Error("Failed to invalidate access token", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
"customer_id": customerID.String(), "customer_id": customerID,
}) })
// Don't return error here as the customer should still be logged out // Don't return error here as the customer should still be logged out
} }
s.logger.Info("Customer logout successful", map[string]interface{}{ s.logger.Info("Customer logout successful", map[string]interface{}{
"customer_id": customerID.String(), "customer_id": customerID,
}) })
return nil return nil