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
+93 -285
View File
@@ -5,61 +5,46 @@ import (
"errors"
"time"
"tm/pkg/logger"
mongopkg "tm/pkg/mongo"
orm "tm/pkg/mongo"
"tm/pkg/response"
"go.mongodb.org/mongo-driver/bson"
)
// Repository defines the interface for customer data operations
type Repository interface {
Create(ctx context.Context, customer *Customer) error
Login(ctx context.Context, id string) error
Register(ctx context.Context, customer *Customer) error
Update(ctx context.Context, customer *Customer) error
UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
GetByID(ctx context.Context, id string) (*Customer, error)
GetByEmail(ctx context.Context, email string) (*Customer, error)
GetByUsername(ctx context.Context, username string) (*Customer, error)
GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
GetByTaxID(ctx context.Context, taxID string) (*Customer, error)
GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error)
Update(ctx context.Context, customer *Customer) error
GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error)
Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error)
Delete(ctx context.Context, id string) error
List(ctx context.Context, limit, offset int) ([]*Customer, error)
Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
CountByCompanyID(ctx context.Context, companyID string) (int64, error)
CountByType(ctx context.Context, customerType CustomerType) (int64, error)
CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
UpdateStatus(ctx context.Context, id string, status CustomerStatus) error
UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error
}
// customerRepository implements the Repository interface using the MongoDB ORM
type customerRepository struct {
ormRepo mongopkg.Repository[Customer]
ormRepo orm.Repository[Customer]
logger logger.Logger
}
func collection() string {
return "customers"
}
// NewCustomerRepository creates a new customer repository
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
func NewCustomerRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// 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_ids_idx", bson.D{{Key: "company_ids", Value: 1}}), // Index for CompanyIDs array
*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"),
indexes := []orm.Index{
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
*orm.CreateTextIndex("search_idx", "full_name", "email", "username", "phone"),
}
// Create indexes
err := mongoManager.CreateIndexes("customers", indexes)
err := mongoManager.CreateIndexes(collection(), indexes)
if err != nil {
logger.Warn("Failed to create customer indexes", map[string]interface{}{
"error": err.Error(),
@@ -67,7 +52,7 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg
}
// Create ORM repository
ormRepo := mongopkg.NewRepository[Customer](mongoManager.GetCollection("customers"), logger)
ormRepo := orm.NewRepository[Customer](mongoManager.GetCollection(collection()), logger)
return &customerRepository{
ormRepo: ormRepo,
@@ -75,17 +60,16 @@ func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logg
}
}
// Create creates a new customer
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
// Register creates a new customer
func (r *customerRepository) Register(ctx context.Context, customer *Customer) error {
// Set created/updated timestamps using Unix timestamps
now := time.Now().Unix()
customer.SetCreatedAt(now)
customer.SetUpdatedAt(now)
// Use ORM to create customer
err := r.ormRepo.Create(ctx, customer)
if err != nil {
r.logger.Error("Failed to create customer", map[string]interface{}{
r.logger.Error("Failed to register customer", map[string]interface{}{
"error": err.Error(),
"email": customer.Email,
"username": customer.Username,
@@ -93,7 +77,7 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
return err
}
r.logger.Info("Customer created successfully", map[string]interface{}{
r.logger.Info("Customer registered successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
"type": customer.Type,
@@ -102,11 +86,40 @@ func (r *customerRepository) Create(ctx context.Context, customer *Customer) err
return nil
}
// Login updates the last login timestamp
func (r *customerRepository) Login(ctx context.Context, id string) error {
// Get customer first
customer, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Update last login timestamp
customer.LastLoginAt = &[]int64{time.Now().Unix()}[0]
customer.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, customer)
if err != nil {
r.logger.Error("Failed to update last login", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return err
}
r.logger.Info("Last login updated successfully", map[string]interface{}{
"customer_id": id,
})
return nil
}
// GetByID retrieves a customer by ID
func (r *customerRepository) GetByID(ctx context.Context, id string) (*Customer, error) {
customer, err := r.ormRepo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by ID", map[string]interface{}{
@@ -124,7 +137,7 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Cus
filter := bson.M{"email": email}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by email", map[string]interface{}{
@@ -142,7 +155,7 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string)
filter := bson.M{"username": username}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by username", map[string]interface{}{
@@ -155,67 +168,13 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string)
return customer, nil
}
// GetByCompanyName retrieves a customer by company name
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
filter := bson.M{"company_name": companyName}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by company name", map[string]interface{}{
"error": err.Error(),
"company_name": companyName,
})
return nil, err
}
return customer, nil
}
// GetByRegistrationNumber retrieves a customer by registration number
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
filter := bson.M{"registration_number": registrationNumber}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by registration number", map[string]interface{}{
"error": err.Error(),
"registration_number": registrationNumber,
})
return nil, err
}
return customer, nil
}
// GetByTaxID retrieves a customer by tax ID
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
filter := bson.M{"tax_id": taxID}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by tax ID", map[string]interface{}{
"error": err.Error(),
"tax_id": taxID,
})
return nil, err
}
return customer, nil
}
// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*Customer, error) {
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) {
// Build pagination
pagination := mongopkg.NewPaginationBuilder().
Limit(limit).
Skip(offset).
SortDesc("created_at").
paginationBuilder := orm.NewPaginationBuilder().
Limit(pagination.Limit).
Skip(pagination.Offset).
SortBy(pagination.SortBy, pagination.SortOrder).
Build()
// Filter by company ID and active status
@@ -225,15 +184,15 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin
}
// Use ORM to find customers
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
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,
"limit": pagination.Limit,
"offset": pagination.Offset,
})
return nil, err
return nil, 0, err
}
// Convert []Customer to []*Customer
@@ -242,7 +201,7 @@ func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID strin
customers[i] = &result.Items[i]
}
return customers, nil
return customers, result.TotalCount, nil
}
// Update updates a customer
@@ -268,20 +227,24 @@ func (r *customerRepository) Update(ctx context.Context, customer *Customer) err
return nil
}
// Delete deletes a customer (soft delete by setting status to inactive)
// Delete deletes a customer (hard delete)
func (r *customerRepository) Delete(ctx context.Context, id string) error {
// Get customer first
customer, err := r.GetByID(ctx, id)
_, err := r.GetByID(ctx, id)
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return errors.New("customer not found")
}
r.logger.Error("Failed to delete customer", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return err
}
// Update status to inactive
customer.Status = CustomerStatusInactive
customer.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, customer)
err = r.ormRepo.Delete(ctx, id)
if err != nil {
r.logger.Error("Failed to delete customer", map[string]interface{}{
"error": err.Error(),
@@ -297,166 +260,44 @@ func (r *customerRepository) Delete(ctx context.Context, id string) error {
return nil
}
// List retrieves customers with pagination
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
// Build pagination
pagination := mongopkg.NewPaginationBuilder().
Limit(limit).
Skip(offset).
SortDesc("created_at").
Build()
// Only active customers by default
filter := bson.M{"status": bson.M{"$ne": CustomerStatusInactive}}
// Use ORM to find customers
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
if err != nil {
r.logger.Error("Failed to list customers", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, err
}
// Convert []Customer to []*Customer
customers := make([]*Customer, len(result.Items))
for i := range result.Items {
customers[i] = &result.Items[i]
}
return customers, nil
}
// Search retrieves customers with search and filters
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *string, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
func (r *customerRepository) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error) {
// Build filter
filter := bson.M{}
if search != "" {
filter["$text"] = bson.M{"$search": search}
if form.Search != nil {
filter["$text"] = bson.M{"$search": *form.Search}
}
if customerType != nil {
filter["type"] = *customerType
if form.Type != nil {
filter["type"] = *form.Type
}
if status != nil {
filter["status"] = *status
if form.Status != nil {
filter["status"] = *form.Status
}
if companyID != nil {
filter["company_id"] = *companyID
}
if industry != nil {
filter["industry"] = *industry
}
if isVerified != nil {
filter["is_verified"] = *isVerified
}
if isCompliant != nil {
filter["is_compliant"] = *isCompliant
}
if language != nil {
filter["language"] = *language
}
if currency != nil {
filter["currency"] = *currency
if form.CompanyID != nil {
filter["company_id"] = *form.CompanyID
}
// Build pagination
pagination := mongopkg.NewPaginationBuilder().
Limit(limit).
Skip(offset)
// Set sort
if sortBy != "" {
if sortOrder == "desc" {
pagination.SortDesc(sortBy)
} else {
pagination.SortAsc(sortBy)
}
} else {
pagination.SortDesc("created_at") // Default sort
}
paginationBuilder := orm.NewPaginationBuilder().
Limit(pagination.Limit).
Skip(pagination.Offset).
SortBy(pagination.SortBy, pagination.SortOrder)
// Use ORM to find customers
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder.Build())
if err != nil {
r.logger.Error("Failed to search customers", map[string]interface{}{
"error": err.Error(),
"search": search,
"search": form.Search,
})
return nil, err
return nil, 0, err
}
// Convert []Customer to []*Customer
customers := make([]*Customer, len(result.Items))
for i := range result.Items {
customers[i] = &result.Items[i]
}
return customers, nil
}
// CountByCompanyID counts customers by company ID
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
filter := bson.M{
"company_id": companyID,
"status": bson.M{"$ne": CustomerStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count customers by company ID", map[string]interface{}{
"error": err.Error(),
"company_id": companyID,
})
return 0, err
}
return count, nil
}
// CountByType counts customers by type
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
filter := bson.M{
"type": customerType,
"status": bson.M{"$ne": CustomerStatusInactive},
}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count customers by type", map[string]interface{}{
"error": err.Error(),
"customer_type": customerType,
})
return 0, err
}
return count, nil
}
// CountByStatus counts customers by status
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
filter := bson.M{"status": status}
count, err := r.ormRepo.Count(ctx, filter)
if err != nil {
r.logger.Error("Failed to count customers by status", map[string]interface{}{
"error": err.Error(),
"status": status,
})
return 0, err
}
return count, nil
return result.Items, result.TotalCount, nil
}
// UpdateStatus updates customer status
@@ -489,36 +330,3 @@ func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status
return nil
}
// UpdateVerification updates customer verification status
func (r *customerRepository) UpdateVerification(ctx context.Context, id string, isVerified, isCompliant bool, complianceNotes *string) error {
// Get customer first
customer, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Update verification fields
customer.IsVerified = isVerified
customer.IsCompliant = isCompliant
customer.ComplianceNotes = complianceNotes
customer.SetUpdatedAt(time.Now().Unix())
// Update in database
err = r.ormRepo.Update(ctx, customer)
if err != nil {
r.logger.Error("Failed to update customer verification", map[string]interface{}{
"error": err.Error(),
"customer_id": id,
})
return err
}
r.logger.Info("Customer verification updated successfully", map[string]interface{}{
"customer_id": id,
"is_verified": isVerified,
"is_compliant": isCompliant,
})
return nil
}