Refactor Customer API Endpoints and Update Documentation

- Changed customer-related API endpoints to improve clarity and consistency, including renaming and restructuring routes.
- Removed unused query parameters and handlers, streamlining the customer management functionality.
- Updated customer entity and forms to reflect new example values for enhanced API documentation clarity.
- Enhanced response structures to return customer entities directly, simplifying the response handling in API endpoints.
- Updated API documentation to reflect changes in endpoint structure and response formats, ensuring consistency for API consumers.
This commit is contained in:
n.nakhostin
2025-09-06 16:08:53 +03:30
parent 05e7b50ec4
commit e848b625ce
9 changed files with 432 additions and 4437 deletions
+112 -628
View File
@@ -7,6 +7,7 @@ import (
"tm/internal/company"
"tm/pkg/logger"
"tm/pkg/response"
"tm/pkg/authorization"
@@ -17,41 +18,18 @@ import (
// Service defines business logic for customer operations
type Service interface {
// Core customer operations
CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error)
GetCustomerByID(ctx context.Context, id string) (*Customer, error)
GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error)
UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error)
DeleteCustomer(ctx context.Context, id string, deletedBy *string) error
// Customer listing and search
ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error)
GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error)
GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error)
GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error)
// Customer management
UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error
UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error
// Company assignments
AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error
RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error
// Mobile-specific operations (simplified)
CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error)
UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error)
// Business operations
VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error
SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error
ActivateCustomer(ctx context.Context, id string, activatedBy *string) error
Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error)
GetByID(ctx context.Context, id string) (*CustomerResponse, error)
Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error)
Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error)
Delete(ctx context.Context, id string) error
UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error
// Authentication operations
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
GetProfile(ctx context.Context, customerID string) (*Customer, error)
GetProfileWithCompanies(ctx context.Context, customerID string) (*CustomerResponse, error)
Logout(ctx context.Context, customerID string, accessToken string) error
GetProfileWithCompanies(ctx context.Context, id string) (*CustomerResponse, error)
Logout(ctx context.Context, id string, accessToken string) error
}
// customerService implements the Service interface
@@ -72,34 +50,17 @@ func NewCustomerService(repository Repository, logger logger.Logger, authService
}
}
// CreateCustomer creates a new customer
func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustomerForm, createdBy *string) (*Customer, error) {
// Register creates a new customer
func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm) (*CustomerResponse, error) {
// Check if email already exists
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
if existingCustomer != nil {
return nil, errors.New("customer with this email already exists")
}
// Check if registration number already exists (for company customers)
if form.Type == string(CustomerTypeCompany) && form.RegistrationNumber != nil {
existingCustomer, _ = s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber)
if existingCustomer != nil {
return nil, errors.New("company with this registration number already exists")
}
}
// Check if tax ID already exists (for company customers)
if form.Type == string(CustomerTypeCompany) && form.TaxID != nil {
existingCustomer, _ = s.repository.GetByTaxID(ctx, *form.TaxID)
if existingCustomer != nil {
return nil, errors.New("company with this tax ID already exists")
}
}
// Prepare company assignments
// Verify that all company IDs exist
var companyIDs []string
if len(form.CompanyIDs) > 0 {
// Verify that all company IDs exist
for _, companyID := range form.CompanyIDs {
_, err := s.companyService.GetCompanyByID(ctx, companyID)
if err != nil {
@@ -124,36 +85,18 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
// Create customer
customer := &Customer{
Type: CustomerType(form.Type),
Status: CustomerStatusPending,
CompanyIDs: companyIDs,
FirstName: form.FirstName,
LastName: form.LastName,
FullName: form.FullName,
Username: form.Username,
Email: form.Email,
Password: string(hashedPassword),
Phone: form.Phone,
Mobile: form.Mobile,
RegistrationNumber: form.RegistrationNumber,
TaxID: form.TaxID,
Industry: form.Industry,
Address: s.convertAddressForm(form.Address),
BusinessType: form.BusinessType,
EmployeeCount: form.EmployeeCount,
AnnualRevenue: form.AnnualRevenue,
FoundedYear: form.FoundedYear,
ContactPerson: s.convertContactPersonForm(form.ContactPerson),
IsVerified: false,
IsCompliant: false,
Language: s.getDefaultLanguage(form.Language),
Currency: s.getDefaultCurrency(form.Currency),
Timezone: s.getDefaultTimezone(form.Timezone),
CreatedBy: createdBy,
Type: CustomerType(form.Type),
Status: CustomerStatusActive,
CompanyIDs: companyIDs,
FullName: form.FullName,
Username: form.Username,
Email: form.Email,
Password: string(hashedPassword),
Phone: form.Phone,
}
// Save to database
err = s.repository.Create(ctx, customer)
err = s.repository.Register(ctx, customer)
if err != nil {
s.logger.Error("Failed to create customer", map[string]interface{}{
"error": err.Error(),
@@ -168,14 +111,13 @@ func (s *customerService) CreateCustomer(ctx context.Context, form *CreateCustom
"email": customer.Email,
"type": customer.Type,
"company_ids": companyIDs,
"created_by": *createdBy,
})
return customer, nil
return customer.ToResponse(nil), nil
}
// GetCustomerByID retrieves a customer by ID
func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Customer, error) {
// GetByID retrieves a customer by ID
func (s *customerService) GetByID(ctx context.Context, id string) (*CustomerResponse, error) {
customer, err := s.repository.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get customer by ID", map[string]interface{}{
@@ -185,48 +127,56 @@ func (s *customerService) GetCustomerByID(ctx context.Context, id string) (*Cust
return nil, err
}
var companies []*CompanySummary
if len(customer.CompanyIDs) > 0 {
companies, err = s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
}
}
s.logger.Info("Customer retrieved successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
})
return customer, nil
return customer.ToResponse(companies), nil
}
// GetCustomerByIDWithCompanies retrieves a customer by ID and loads associated companies
func (s *customerService) GetCustomerByIDWithCompanies(ctx context.Context, id string) (*CustomerResponse, error) {
customer, err := s.repository.GetByID(ctx, id)
// Search searches for customers
func (s *customerService) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) (*CustomerListResponse, error) {
customers, total, err := s.repository.Search(ctx, form, pagination)
if err != nil {
s.logger.Error("Failed to get customer by ID with companies", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
s.logger.Error("Failed to search customers", map[string]interface{}{
"error": err.Error(),
})
return nil, err
return nil, errors.New("failed to search customers")
}
// Load companies for the customer
companies, err := s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
// Continue without companies if loading fails
companies = []*CompanySummary{}
var customerResponses []*CustomerResponse
for _, customer := range customers {
companies, err := s.loadCompaniesForCustomer(ctx, &customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
}
customerResponses = append(customerResponses, customer.ToResponse(companies))
}
customerResponse := customer.ToResponseWithCompanies(companies)
s.logger.Info("Customer retrieved with companies successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
})
return customerResponse, nil
return &CustomerListResponse{
Customers: customerResponses,
Meta: pagination.Response(total),
}, nil
}
// UpdateCustomer updates a customer
func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *UpdateCustomerForm, updatedBy *string) (*Customer, error) {
// Update updates a customer
func (s *customerService) Update(ctx context.Context, id string, form *UpdateCustomerForm) (*CustomerResponse, error) {
// Get existing customer
customer, err := s.repository.GetByID(ctx, id)
if err != nil {
@@ -238,44 +188,21 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
}
// Check if email already exists (if changing email)
if form.Email != nil && *form.Email != customer.Email {
existingCustomer, _ := s.repository.GetByEmail(ctx, *form.Email)
if form.Email != "" && form.Email != customer.Email {
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
if existingCustomer != nil {
return nil, errors.New("customer with this email already exists")
}
customer.Email = *form.Email
customer.Email = form.Email
}
// Check if username already exists (if changing username)
if form.Username != nil && *form.Username != customer.Username {
existingCustomer, _ := s.repository.GetByUsername(ctx, *form.Username)
if form.Username != "" && form.Username != customer.Username {
existingCustomer, _ := s.repository.GetByUsername(ctx, form.Username)
if existingCustomer != nil {
return nil, errors.New("customer with this username already exists")
}
customer.Username = *form.Username
}
// Check if registration number already exists (if changing)
if form.RegistrationNumber != nil && *form.RegistrationNumber != *customer.RegistrationNumber {
existingCustomer, _ := s.repository.GetByRegistrationNumber(ctx, *form.RegistrationNumber)
if existingCustomer != nil {
return nil, errors.New("company with this registration number already exists")
}
customer.RegistrationNumber = form.RegistrationNumber
}
// Check if tax ID already exists (if changing)
if form.TaxID != nil && *form.TaxID != *customer.TaxID {
existingCustomer, _ := s.repository.GetByTaxID(ctx, *form.TaxID)
if existingCustomer != nil {
return nil, errors.New("company with this tax ID already exists")
}
customer.TaxID = form.TaxID
}
// Update fields if provided
if form.Type != nil {
customer.Type = CustomerType(*form.Type)
customer.Username = form.Username
}
// Handle company assignments
@@ -296,14 +223,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
customer.CompanyIDs = validCompanyIDs
}
if form.FirstName != nil {
customer.FirstName = form.FirstName
}
if form.LastName != nil {
customer.LastName = form.LastName
}
if form.FullName != nil {
customer.FullName = form.FullName
}
@@ -312,54 +231,6 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
customer.Phone = form.Phone
}
if form.Mobile != nil {
customer.Mobile = form.Mobile
}
if form.Industry != nil {
customer.Industry = form.Industry
}
if form.Address != nil {
customer.Address = s.convertAddressForm(form.Address)
}
if form.BusinessType != nil {
customer.BusinessType = form.BusinessType
}
if form.EmployeeCount != nil {
customer.EmployeeCount = form.EmployeeCount
}
if form.AnnualRevenue != nil {
customer.AnnualRevenue = form.AnnualRevenue
}
if form.FoundedYear != nil {
customer.FoundedYear = form.FoundedYear
}
if form.ContactPerson != nil {
customer.ContactPerson = s.convertContactPersonForm(form.ContactPerson)
}
if form.Language != nil {
customer.Language = *form.Language
}
if form.Currency != nil {
customer.Currency = *form.Currency
}
if form.Timezone != nil {
customer.Timezone = *form.Timezone
}
// Set updated by
customer.UpdatedBy = updatedBy
customer.UpdatedAt = time.Now().Unix()
// Update in database
err = s.repository.Update(ctx, customer)
if err != nil {
@@ -373,23 +244,23 @@ func (s *customerService) UpdateCustomer(ctx context.Context, id string, form *U
s.logger.Info("Customer updated successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
"updated_by": *updatedBy,
})
return customer, nil
return customer.ToResponse(nil), nil
}
// DeleteCustomer deletes a customer (soft delete)
func (s *customerService) DeleteCustomer(ctx context.Context, id string, deletedBy *string) error {
// Delete deletes a customer (soft delete)
func (s *customerService) Delete(ctx context.Context, id string) error {
// Get customer to check if exists
customer, err := s.repository.GetByID(ctx, id)
_, err := s.repository.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get customer for delete", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return errors.New("customer not found")
}
// Set updated by before deletion
customer.UpdatedBy = deletedBy
// Delete customer (soft delete)
err = s.repository.Delete(ctx, id)
if err != nil {
@@ -402,154 +273,13 @@ func (s *customerService) DeleteCustomer(ctx context.Context, id string, deleted
s.logger.Info("Customer deleted successfully", map[string]interface{}{
"customer_id": id,
"deleted_by": deletedBy,
})
return nil
}
// ListCustomers lists customers with search and filters
func (s *customerService) ListCustomers(ctx context.Context, form *ListCustomersForm) (*CustomerListResponse, error) {
// Set defaults
limit := 20
if form.Limit != nil {
limit = *form.Limit
}
offset := 0
if form.Offset != nil {
offset = *form.Offset
}
search := ""
if form.Search != nil {
search = *form.Search
}
sortBy := "created_at"
if form.SortBy != nil {
sortBy = *form.SortBy
}
sortOrder := "desc"
if form.SortOrder != nil {
sortOrder = *form.SortOrder
}
// Parse company ID if provided
var companyID *string
if form.CompanyID != nil {
companyID = form.CompanyID
}
// Get customers
customers, err := s.repository.Search(ctx, search, form.Type, form.Status, companyID, form.Industry, form.IsVerified, form.IsCompliant, form.Language, form.Currency, limit, offset, sortBy, sortOrder)
if err != nil {
s.logger.Error("Failed to list customers", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to list customers")
}
// Convert to responses
var customerResponses []*CustomerResponse
for _, customer := range customers {
customerResponses = append(customerResponses, customer.ToResponse())
}
// TODO: Implement proper count for pagination metadata
total := int64(len(customers)) // This should be a separate count query
totalPages := (total + int64(limit) - 1) / int64(limit)
return &CustomerListResponse{
Customers: customerResponses,
Total: total,
Limit: limit,
Offset: offset,
TotalPages: int(totalPages),
}, nil
}
// GetCustomersByCompanyID retrieves customers by company ID with pagination
func (s *customerService) GetCustomersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, int64, error) {
customers, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset)
if err != nil {
s.logger.Error("Failed to get customers by company ID", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, 0, errors.New("failed to get customers by company")
}
// Get total count
total, err := s.repository.CountByCompanyID(ctx, companyID)
if err != nil {
s.logger.Error("Failed to count customers by company ID", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return customers, 0, nil // Return customers even if count fails
}
return customers, total, nil
}
// GetCustomersByType retrieves customers by type with pagination
func (s *customerService) GetCustomersByType(ctx context.Context, customerType CustomerType, limit, offset int) ([]*Customer, int64, error) {
// This would need to be implemented in the repository
// For now, we'll use the search method
customerTypeStr := string(customerType)
customers, err := s.repository.Search(ctx, "", &customerTypeStr, nil, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc")
if err != nil {
s.logger.Error("Failed to get customers by type", map[string]interface{}{
"error": err.Error(),
"customer_type": customerType,
})
return nil, 0, errors.New("failed to get customers by type")
}
// Get total count
total, err := s.repository.CountByType(ctx, customerType)
if err != nil {
s.logger.Error("Failed to count customers by type", map[string]interface{}{
"error": err.Error(),
"customer_type": customerType,
})
return customers, 0, nil
}
return customers, total, nil
}
// GetCustomersByStatus retrieves customers by status with pagination
func (s *customerService) GetCustomersByStatus(ctx context.Context, status CustomerStatus, limit, offset int) ([]*Customer, int64, error) {
// This would need to be implemented in the repository
// For now, we'll use the search method
statusStr := string(status)
customers, err := s.repository.Search(ctx, "", nil, &statusStr, nil, nil, nil, nil, nil, nil, limit, offset, "created_at", "desc")
if err != nil {
s.logger.Error("Failed to get customers by status", map[string]interface{}{
"error": err.Error(),
"status": status,
})
return nil, 0, errors.New("failed to get customers by status")
}
// Get total count
total, err := s.repository.CountByStatus(ctx, status)
if err != nil {
s.logger.Error("Failed to count customers by status", map[string]interface{}{
"error": err.Error(),
"status": status,
})
return customers, 0, nil
}
return customers, total, nil
}
// UpdateCustomerStatus updates customer status
func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, form *UpdateCustomerStatusForm, updatedBy *string) error {
// UpdateStatus updates customer status
func (s *customerService) UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error {
// Get customer to check if exists
_, err := s.repository.GetByID(ctx, id)
if err != nil {
@@ -571,42 +301,13 @@ func (s *customerService) UpdateCustomerStatus(ctx context.Context, id string, f
s.logger.Info("Customer status updated successfully", map[string]interface{}{
"customer_id": id,
"status": form.Status,
"updated_by": updatedBy,
})
return nil
}
// UpdateCustomerVerification updates customer verification status
func (s *customerService) UpdateCustomerVerification(ctx context.Context, id string, form *UpdateCustomerVerificationForm, updatedBy *string) error {
// Get customer to check if exists
_, err := s.repository.GetByID(ctx, id)
if err != nil {
return errors.New("customer not found")
}
// Update verification
err = s.repository.UpdateVerification(ctx, id, form.IsVerified, form.IsCompliant, form.ComplianceNotes)
if err != nil {
s.logger.Error("Failed to update customer verification", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return errors.New("failed to update customer verification")
}
s.logger.Info("Customer verification updated successfully", map[string]interface{}{
"customer_id": id,
"is_verified": form.IsVerified,
"is_compliant": form.IsCompliant,
"updated_by": updatedBy,
})
return nil
}
// AssignCompaniesToCustomer assigns companies to a customer
func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error {
// AssignCompanies assigns companies to a customer
func (s *customerService) AssignCompanies(ctx context.Context, customerID string, companyIDs []string) error {
// Get customer to check if exists
customer, err := s.repository.GetByID(ctx, customerID)
if err != nil {
@@ -630,7 +331,6 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome
// Update customer's company IDs
customer.CompanyIDs = append(customer.CompanyIDs, validCompanyIDs...)
customer.UpdatedBy = updatedBy
customer.UpdatedAt = time.Now().Unix()
err = s.repository.Update(ctx, customer)
@@ -645,14 +345,13 @@ func (s *customerService) AssignCompaniesToCustomer(ctx context.Context, custome
s.logger.Info("Companies assigned to customer successfully", map[string]interface{}{
"customer_id": customerID,
"company_ids": validCompanyIDs,
"updated_by": updatedBy,
})
return nil
}
// RemoveCompaniesFromCustomer removes companies from a customer
func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, customerID string, companyIDs []string, updatedBy *string) error {
func (s *customerService) RemoveCompanies(ctx context.Context, customerID string, companyIDs []string) error {
// Get customer to check if exists
customer, err := s.repository.GetByID(ctx, customerID)
if err != nil {
@@ -676,7 +375,6 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo
// Update customer's company IDs
customer.CompanyIDs = updatedCompanyIDs
customer.UpdatedBy = updatedBy
customer.UpdatedAt = time.Now().Unix()
err = s.repository.Update(ctx, customer)
@@ -691,194 +389,13 @@ func (s *customerService) RemoveCompaniesFromCustomer(ctx context.Context, custo
s.logger.Info("Companies removed from customer successfully", map[string]interface{}{
"customer_id": customerID,
"company_ids": companyIDs,
"updated_by": updatedBy,
})
return nil
}
// CreateCustomerMobile creates a new customer via mobile app (simplified)
func (s *customerService) CreateCustomerMobile(ctx context.Context, form *CreateCustomerMobileForm, createdBy *string) (*Customer, error) {
// Check if email already exists
existingCustomer, _ := s.repository.GetByEmail(ctx, form.Email)
if existingCustomer != nil {
return nil, errors.New("customer with this email already exists")
}
// Prepare company assignments
var companyIDs []string
if len(form.CompanyIDs) > 0 {
// Verify that all company IDs exist
for _, companyID := range form.CompanyIDs {
_, err := s.companyService.GetCompanyByID(ctx, companyID)
if err != nil {
s.logger.Error("Invalid company ID provided in mobile form", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, errors.New("invalid company ID: " + companyID)
}
}
companyIDs = form.CompanyIDs
}
// Create customer with mobile form
customer := &Customer{
Type: CustomerType(form.Type),
Status: CustomerStatusActive,
CompanyIDs: companyIDs,
FirstName: form.FirstName,
LastName: form.LastName,
FullName: form.FullName,
Username: form.Username,
Email: form.Email,
Password: "", // Will be set after hashing
Phone: form.Phone,
Mobile: form.Mobile,
Industry: form.Industry,
IsVerified: false,
IsCompliant: false,
Language: "en",
Currency: "USD",
Timezone: "UTC",
CreatedBy: createdBy,
}
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost)
if err != nil {
s.logger.Error("Failed to hash password for mobile customer", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to process password")
}
customer.Password = string(hashedPassword)
// Save to database
err = s.repository.Create(ctx, customer)
if err != nil {
s.logger.Error("Failed to create customer via mobile", map[string]interface{}{
"error": err.Error(),
"email": form.Email,
"type": form.Type,
"created_by": createdBy,
})
return nil, err
}
s.logger.Info("Customer created via mobile successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
"type": customer.Type,
"company_ids": companyIDs,
"created_by": *createdBy,
})
return customer, nil
}
// UpdateCustomerMobile updates a customer via mobile app (simplified)
func (s *customerService) UpdateCustomerMobile(ctx context.Context, id string, form *UpdateCustomerMobileForm, updatedBy *string) (*Customer, error) {
// Get existing customer
customer, err := s.repository.GetByID(ctx, id)
if err != nil {
return nil, errors.New("customer not found")
}
// Update fields if provided
if form.FirstName != nil {
customer.FirstName = form.FirstName
}
if form.LastName != nil {
customer.LastName = form.LastName
}
if form.FullName != nil {
customer.FullName = form.FullName
}
if form.Phone != nil {
customer.Phone = form.Phone
}
if form.Mobile != nil {
customer.Mobile = form.Mobile
}
// Handle company assignments
if len(form.CompanyIDs) > 0 {
// Verify that all company IDs exist
var validCompanyIDs []string
for _, companyID := range form.CompanyIDs {
_, err := s.companyService.GetCompanyByID(ctx, companyID)
if err != nil {
s.logger.Error("Invalid company ID provided in mobile update", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return nil, errors.New("invalid company ID: " + companyID)
}
validCompanyIDs = append(validCompanyIDs, companyID)
}
customer.CompanyIDs = validCompanyIDs
}
if form.Industry != nil {
customer.Industry = form.Industry
}
// Set updated by
customer.UpdatedBy = updatedBy
// Update in database
err = s.repository.Update(ctx, customer)
if err != nil {
s.logger.Error("Failed to update customer via mobile", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return nil, errors.New("failed to update customer")
}
s.logger.Info("Customer updated via mobile successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
"company_ids": customer.CompanyIDs,
"updated_by": *updatedBy,
})
return customer, nil
}
// VerifyCustomer verifies a customer
func (s *customerService) VerifyCustomer(ctx context.Context, id string, verifiedBy *string) error {
// Get customer to check if exists
customer, err := s.repository.GetByID(ctx, id)
if err != nil {
return errors.New("customer not found")
}
// Update verification
err = s.repository.UpdateVerification(ctx, id, true, customer.IsCompliant, customer.ComplianceNotes)
if err != nil {
s.logger.Error("Failed to verify customer", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return errors.New("failed to verify customer")
}
s.logger.Info("Customer verified successfully", map[string]interface{}{
"customer_id": id,
"verified_by": verifiedBy,
})
return nil
}
// SuspendCustomer suspends a customer
func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason string, suspendedBy *string) error {
// Suspend suspends a customer
func (s *customerService) Suspend(ctx context.Context, id string, reason string) error {
// Get customer to check if exists
_, err := s.repository.GetByID(ctx, id)
if err != nil {
@@ -896,16 +413,15 @@ func (s *customerService) SuspendCustomer(ctx context.Context, id string, reason
}
s.logger.Info("Customer suspended successfully", map[string]interface{}{
"customer_id": id,
"reason": reason,
"suspended_by": suspendedBy,
"customer_id": id,
"reason": reason,
})
return nil
}
// ActivateCustomer activates a customer
func (s *customerService) ActivateCustomer(ctx context.Context, id string, activatedBy *string) error {
// Activate activates a customer
func (s *customerService) Activate(ctx context.Context, id string) error {
// Get customer to check if exists
_, err := s.repository.GetByID(ctx, id)
if err != nil {
@@ -923,8 +439,7 @@ func (s *customerService) ActivateCustomer(ctx context.Context, id string, activ
}
s.logger.Info("Customer activated successfully", map[string]interface{}{
"customer_id": id,
"activated_by": activatedBy,
"customer_id": id,
})
return nil
@@ -985,6 +500,22 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
return nil, errors.New("failed to generate authentication tokens")
}
err = s.repository.Login(ctx, customer.ID.Hex())
if err != nil {
s.logger.Error("Failed to update customer last login", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
}
companies, err := s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
}
s.logger.Info("Customer login successful", map[string]interface{}{
"username": form.Username,
"customer_id": customer.ID,
@@ -992,7 +523,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
})
return &AuthResponse{
Customer: customer.ToResponse(),
Customer: customer.ToResponse(companies),
AccessToken: tokenPair.AccessToken,
RefreshToken: tokenPair.RefreshToken,
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
@@ -1053,13 +584,21 @@ func (s *customerService) RefreshToken(ctx context.Context, refreshToken string)
return nil, errors.New("customer account is inactive")
}
companies, err := s.loadCompaniesForCustomer(ctx, customer)
if err != nil {
s.logger.Error("Failed to load companies for customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
}
s.logger.Info("Access token refreshed successfully", map[string]interface{}{
"customer_id": customer.ID,
"customer_type": string(customer.Type),
})
return &AuthResponse{
Customer: customer.ToResponse(),
Customer: customer.ToResponse(companies),
AccessToken: newAccessToken,
RefreshToken: refreshToken, // Return the same refresh token
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(), // 15 minutes from now
@@ -1115,7 +654,7 @@ func (s *customerService) GetProfileWithCompanies(ctx context.Context, customerI
companies = []*CompanySummary{}
}
customerResponse := customer.ToResponseWithCompanies(companies)
customerResponse := customer.ToResponse(companies)
s.logger.Info("Customer profile retrieved with companies successfully", map[string]interface{}{
"customer_id": customerID,
@@ -1149,61 +688,6 @@ func (s *customerService) Logout(ctx context.Context, customerID string, accessT
return nil
}
// Helper methods for converting forms to domain objects
func (s *customerService) convertAddressForm(form *AddressForm) *Address {
if form == nil {
return nil
}
return &Address{
Street: form.Street,
City: form.City,
State: form.State,
PostalCode: form.PostalCode,
Country: form.Country,
AddressType: form.AddressType,
IsDefault: form.IsDefault,
}
}
func (s *customerService) convertContactPersonForm(form *ContactPersonForm) *ContactPerson {
if form == nil {
return nil
}
return &ContactPerson{
FirstName: form.FirstName,
LastName: form.LastName,
FullName: form.FullName,
Position: form.Position,
Email: form.Email,
Phone: form.Phone,
Mobile: form.Mobile,
IsPrimary: form.IsPrimary,
}
}
func (s *customerService) getDefaultLanguage(language *string) string {
if language != nil {
return *language
}
return "en"
}
func (s *customerService) getDefaultCurrency(currency *string) string {
if currency != nil {
return *currency
}
return "USD"
}
func (s *customerService) getDefaultTimezone(timezone *string) string {
if timezone != nil {
return *timezone
}
return "UTC"
}
// loadCompaniesForCustomer loads company summaries for a customer
func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer *Customer) ([]*CompanySummary, error) {
if len(customer.CompanyIDs) == 0 {