8c1e593686
- Introduced a new customer management domain, including entities, services, handlers, and forms for customer operations. - Added JWT-based user authorization settings in the configuration file for both user and customer management. - Updated API endpoints to reflect the new structure, including changes to health check and user management routes. - Enhanced Swagger documentation to include new customer-related endpoints and authorization details. - Refactored the Makefile to include a target for generating API documentation. - Removed obsolete documentation files to streamline the project structure.
526 lines
13 KiB
Go
526 lines
13 KiB
Go
package customer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
"tm/pkg/logger"
|
|
mongopkg "tm/pkg/mongo"
|
|
|
|
"github.com/google/uuid"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
// Repository defines the interface for customer data operations
|
|
type Repository interface {
|
|
Create(ctx context.Context, customer *Customer) error
|
|
GetByID(ctx context.Context, id uuid.UUID) (*Customer, error)
|
|
GetByEmail(ctx context.Context, email string) (*Customer, error)
|
|
GetByUsername(ctx context.Context, username string) (*Customer, error)
|
|
GetByCompanyName(ctx context.Context, companyName string) (*Customer, error)
|
|
GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error)
|
|
GetByTaxID(ctx context.Context, taxID string) (*Customer, error)
|
|
GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error)
|
|
Update(ctx context.Context, customer *Customer) error
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
List(ctx context.Context, limit, offset int) ([]*Customer, error)
|
|
Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error)
|
|
CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error)
|
|
CountByType(ctx context.Context, customerType CustomerType) (int64, error)
|
|
CountByStatus(ctx context.Context, status CustomerStatus) (int64, error)
|
|
UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error
|
|
UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error
|
|
}
|
|
|
|
// customerRepository implements the Repository interface
|
|
type customerRepository struct {
|
|
collection *mongo.Collection
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewCustomerRepository creates a new customer repository
|
|
func NewCustomerRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
|
collection := mongoManager.GetCollection("customers")
|
|
|
|
// Create indexes
|
|
repo := &customerRepository{
|
|
collection: collection,
|
|
logger: logger,
|
|
}
|
|
|
|
repo.createIndexes()
|
|
return repo
|
|
}
|
|
|
|
// createIndexes creates necessary database indexes
|
|
func (r *customerRepository) createIndexes() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Create indexes
|
|
indexes := []mongo.IndexModel{
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "email", Value: 1},
|
|
},
|
|
Options: options.Index().SetUnique(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "username", Value: 1},
|
|
},
|
|
Options: options.Index().SetUnique(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "company_name", Value: 1},
|
|
},
|
|
Options: options.Index().SetSparse(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "registration_number", Value: 1},
|
|
},
|
|
Options: options.Index().SetSparse(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "tax_id", Value: 1},
|
|
},
|
|
Options: options.Index().SetSparse(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "company_id", Value: 1},
|
|
},
|
|
Options: options.Index().SetSparse(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "type", Value: 1},
|
|
},
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "status", Value: 1},
|
|
},
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "industry", Value: 1},
|
|
},
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "created_at", Value: -1},
|
|
},
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "updated_at", Value: -1},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := r.collection.Indexes().CreateMany(ctx, indexes)
|
|
if err != nil {
|
|
r.logger.Error("Failed to create customer indexes", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
} else {
|
|
r.logger.Info("Customer indexes created successfully", map[string]interface{}{})
|
|
}
|
|
}
|
|
|
|
// Create creates a new customer
|
|
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
|
|
// Set timestamps
|
|
now := time.Now().Unix()
|
|
customer.CreatedAt = now
|
|
customer.UpdatedAt = now
|
|
|
|
// Set defaults if not provided
|
|
if customer.Language == "" {
|
|
customer.Language = "en"
|
|
}
|
|
if customer.Currency == "" {
|
|
customer.Currency = "USD"
|
|
}
|
|
if customer.Timezone == "" {
|
|
customer.Timezone = "UTC"
|
|
}
|
|
|
|
_, err := r.collection.InsertOne(ctx, customer)
|
|
if err != nil {
|
|
if mongo.IsDuplicateKeyError(err) {
|
|
return errors.New("customer with this email already exists")
|
|
}
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Customer created successfully", map[string]interface{}{
|
|
"customer_id": customer.ID.String(),
|
|
"email": customer.Email,
|
|
"type": customer.Type,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetByID retrieves a customer by ID
|
|
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByEmail retrieves a customer by email
|
|
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"email": email}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByUsername retrieves a customer by username
|
|
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"username": username}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByCompanyName retrieves a customer by company name
|
|
func (r *customerRepository) GetByCompanyName(ctx context.Context, companyName string) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"company_name": companyName}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByRegistrationNumber retrieves a customer by registration number
|
|
func (r *customerRepository) GetByRegistrationNumber(ctx context.Context, registrationNumber string) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"registration_number": registrationNumber}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByTaxID retrieves a customer by tax ID
|
|
func (r *customerRepository) GetByTaxID(ctx context.Context, taxID string) (*Customer, error) {
|
|
var customer Customer
|
|
err := r.collection.FindOne(ctx, bson.M{"tax_id": taxID}).Decode(&customer)
|
|
if err != nil {
|
|
if err == mongo.ErrNoDocuments {
|
|
return nil, errors.New("customer not found")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return &customer, nil
|
|
}
|
|
|
|
// GetByCompanyID retrieves customers by company ID with pagination
|
|
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) {
|
|
filter := bson.M{"company_id": companyID}
|
|
|
|
opts := options.Find().
|
|
SetLimit(int64(limit)).
|
|
SetSkip(int64(offset)).
|
|
SetSort(bson.D{{Key: "created_at", Value: -1}})
|
|
|
|
cursor, err := r.collection.Find(ctx, filter, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
var customers []*Customer
|
|
if err = cursor.All(ctx, &customers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return customers, nil
|
|
}
|
|
|
|
// Update updates a customer
|
|
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error {
|
|
customer.UpdatedAt = time.Now().Unix()
|
|
|
|
filter := bson.M{"_id": customer.ID}
|
|
update := bson.M{"$set": customer}
|
|
|
|
result, err := r.collection.UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.MatchedCount == 0 {
|
|
return errors.New("customer not found")
|
|
}
|
|
|
|
r.logger.Info("Customer updated successfully", map[string]interface{}{
|
|
"customer_id": customer.ID.String(),
|
|
"email": customer.Email,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes a customer (soft delete by setting status to inactive)
|
|
func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
|
filter := bson.M{"_id": id}
|
|
update := bson.M{
|
|
"$set": bson.M{
|
|
"status": CustomerStatusInactive,
|
|
"updated_at": time.Now().Unix(),
|
|
},
|
|
}
|
|
|
|
result, err := r.collection.UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.MatchedCount == 0 {
|
|
return errors.New("customer not found")
|
|
}
|
|
|
|
r.logger.Info("Customer deleted successfully", map[string]interface{}{
|
|
"customer_id": id.String(),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// List retrieves customers with pagination
|
|
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
|
|
opts := options.Find().
|
|
SetLimit(int64(limit)).
|
|
SetSkip(int64(offset)).
|
|
SetSort(bson.D{{Key: "created_at", Value: -1}})
|
|
|
|
cursor, err := r.collection.Find(ctx, bson.M{}, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
var customers []*Customer
|
|
if err = cursor.All(ctx, &customers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return customers, nil
|
|
}
|
|
|
|
// Search searches customers with filters
|
|
func (r *customerRepository) Search(ctx context.Context, search string, customerType *string, status *string, companyID *uuid.UUID, industry *string, isVerified *bool, isCompliant *bool, language *string, currency *string, limit, offset int, sortBy, sortOrder string) ([]*Customer, error) {
|
|
filter := bson.M{}
|
|
|
|
// Add search filter
|
|
if search != "" {
|
|
filter["$or"] = []bson.M{
|
|
{"email": primitive.Regex{Pattern: search, Options: "i"}},
|
|
{"company_name": primitive.Regex{Pattern: search, Options: "i"}},
|
|
{"first_name": primitive.Regex{Pattern: search, Options: "i"}},
|
|
{"last_name": primitive.Regex{Pattern: search, Options: "i"}},
|
|
{"full_name": primitive.Regex{Pattern: search, Options: "i"}},
|
|
}
|
|
}
|
|
|
|
// Add type filter
|
|
if customerType != nil {
|
|
filter["type"] = *customerType
|
|
}
|
|
|
|
// Add status filter
|
|
if status != nil {
|
|
filter["status"] = *status
|
|
}
|
|
|
|
// Add company ID filter
|
|
if companyID != nil {
|
|
filter["company_id"] = *companyID
|
|
}
|
|
|
|
// Add industry filter
|
|
if industry != nil {
|
|
filter["industry"] = *industry
|
|
}
|
|
|
|
// Add verification filter
|
|
if isVerified != nil {
|
|
filter["is_verified"] = *isVerified
|
|
}
|
|
|
|
// Add compliance filter
|
|
if isCompliant != nil {
|
|
filter["is_compliant"] = *isCompliant
|
|
}
|
|
|
|
// Add language filter
|
|
if language != nil {
|
|
filter["language"] = *language
|
|
}
|
|
|
|
// Add currency filter
|
|
if currency != nil {
|
|
filter["currency"] = *currency
|
|
}
|
|
|
|
// Set sort options
|
|
sortDirection := 1
|
|
if sortOrder == "desc" {
|
|
sortDirection = -1
|
|
}
|
|
|
|
var sortField string
|
|
switch sortBy {
|
|
case "email":
|
|
sortField = "email"
|
|
case "company_name":
|
|
sortField = "company_name"
|
|
case "updated_at":
|
|
sortField = "updated_at"
|
|
case "status":
|
|
sortField = "status"
|
|
default:
|
|
sortField = "created_at"
|
|
}
|
|
|
|
opts := options.Find().
|
|
SetLimit(int64(limit)).
|
|
SetSkip(int64(offset)).
|
|
SetSort(bson.D{{Key: sortField, Value: sortDirection}})
|
|
|
|
cursor, err := r.collection.Find(ctx, filter, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
var customers []*Customer
|
|
if err = cursor.All(ctx, &customers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return customers, nil
|
|
}
|
|
|
|
// CountByCompanyID counts customers by company ID
|
|
func (r *customerRepository) CountByCompanyID(ctx context.Context, companyID uuid.UUID) (int64, error) {
|
|
filter := bson.M{"company_id": companyID}
|
|
count, err := r.collection.CountDocuments(ctx, filter)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountByType counts customers by type
|
|
func (r *customerRepository) CountByType(ctx context.Context, customerType CustomerType) (int64, error) {
|
|
filter := bson.M{"type": customerType}
|
|
count, err := r.collection.CountDocuments(ctx, filter)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// CountByStatus counts customers by status
|
|
func (r *customerRepository) CountByStatus(ctx context.Context, status CustomerStatus) (int64, error) {
|
|
filter := bson.M{"status": status}
|
|
count, err := r.collection.CountDocuments(ctx, filter)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// UpdateStatus updates customer status
|
|
func (r *customerRepository) UpdateStatus(ctx context.Context, id uuid.UUID, status CustomerStatus) error {
|
|
filter := bson.M{"_id": id}
|
|
update := bson.M{
|
|
"$set": bson.M{
|
|
"status": status,
|
|
"updated_at": time.Now().Unix(),
|
|
},
|
|
}
|
|
|
|
result, err := r.collection.UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.MatchedCount == 0 {
|
|
return errors.New("customer not found")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateVerification updates customer verification status
|
|
func (r *customerRepository) UpdateVerification(ctx context.Context, id uuid.UUID, isVerified, isCompliant bool, complianceNotes *string) error {
|
|
filter := bson.M{"_id": id}
|
|
update := bson.M{
|
|
"$set": bson.M{
|
|
"is_verified": isVerified,
|
|
"is_compliant": isCompliant,
|
|
"updated_at": time.Now().Unix(),
|
|
},
|
|
}
|
|
|
|
if complianceNotes != nil {
|
|
update["$set"].(bson.M)["compliance_notes"] = *complianceNotes
|
|
}
|
|
|
|
result, err := r.collection.UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.MatchedCount == 0 {
|
|
return errors.New("customer not found")
|
|
}
|
|
|
|
return nil
|
|
}
|