Files
tm_back/internal/customer/domain/customer.go
T

332 lines
8.3 KiB
Go

package customer
import (
"context"
"errors"
"time"
"tm/internal/customer/domain/entity"
"tm/pkg/logger"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// customerRepository implements the Repository interface
type customerRepository struct {
collection *mongo.Collection
logger logger.Logger
}
// NewCustomerRepository creates a new customer repository
func NewCustomerRepository(db *mongo.Database, logger logger.Logger) Repository {
collection := db.Collection("customers")
// Create indexes
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Email index (unique)
emailIndex := mongo.IndexModel{
Keys: bson.D{{Key: "email", Value: 1}},
Options: options.Index().SetUnique(true),
}
// Company ID index
companyIndex := mongo.IndexModel{
Keys: bson.D{{Key: "company_id", Value: 1}},
}
// Active status index
activeIndex := mongo.IndexModel{
Keys: bson.D{{Key: "is_active", Value: 1}},
}
// Created at index
createdAtIndex := mongo.IndexModel{
Keys: bson.D{{Key: "created_at", Value: -1}},
}
_, err := collection.Indexes().CreateMany(ctx, []mongo.IndexModel{
emailIndex,
companyIndex,
activeIndex,
createdAtIndex,
})
if err != nil {
logger.Warn("Failed to create customer indexes", map[string]interface{}{
"error": err.Error(),
})
}
return &customerRepository{
collection: collection,
logger: logger,
}
}
// Create creates a new customer
func (r *customerRepository) Create(ctx context.Context, customer *entity.Customer) error {
// Set created/updated timestamps
now := time.Now()
customer.CreatedAt = now
customer.UpdatedAt = now
// Insert customer
_, err := r.collection.InsertOne(ctx, customer)
if err != nil {
if mongo.IsDuplicateKeyError(err) {
return errors.New("customer already exists")
}
r.logger.Error("Failed to create customer", map[string]interface{}{
"error": err.Error(),
"email": customer.Email,
"customer_id": customer.ID.String(),
})
return err
}
r.logger.Info("Customer created successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
return nil
}
// GetByID retrieves a customer by ID
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*entity.Customer, error) {
var customer entity.Customer
filter := bson.M{"_id": id}
err := r.collection.FindOne(ctx, filter).Decode(&customer)
if err != nil {
if err == mongo.ErrNoDocuments {
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.String(),
})
return nil, err
}
return &customer, nil
}
// GetByEmail retrieves a customer by email
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*entity.Customer, error) {
var customer entity.Customer
filter := bson.M{"email": email}
err := r.collection.FindOne(ctx, filter).Decode(&customer)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, errors.New("customer not found")
}
r.logger.Error("Failed to get customer by email", map[string]interface{}{
"error": err.Error(),
"email": email,
})
return nil, err
}
return &customer, nil
}
// Update updates a customer
func (r *customerRepository) Update(ctx context.Context, customer *entity.Customer) error {
// Set updated timestamp
customer.UpdatedAt = time.Now()
filter := bson.M{"_id": customer.ID}
update := bson.M{"$set": customer}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to update customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
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 is_active to false)
func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"is_active": false,
"updated_at": time.Now(),
},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to delete customer", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
})
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) ([]*entity.Customer, error) {
var customers []*entity.Customer
// Build options
opts := options.Find()
opts.SetLimit(int64(limit))
opts.SetSkip(int64(offset))
opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc
// Only active customers by default
filter := bson.M{"is_active": true}
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil {
r.logger.Error("Failed to list customers", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &customers); err != nil {
r.logger.Error("Failed to decode customers", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return customers, nil
}
// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.Customer, error) {
var customers []*entity.Customer
// Build options
opts := options.Find()
opts.SetLimit(int64(limit))
opts.SetSkip(int64(offset))
opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // Sort by created_at desc
// Filter by company ID and active status
filter := bson.M{
"company_id": companyID,
"is_active": true,
}
cursor, err := r.collection.Find(ctx, filter, opts)
if err != nil {
r.logger.Error("Failed to get customers by company ID", map[string]interface{}{
"error": err.Error(),
"company_id": companyID.String(),
"limit": limit,
"offset": offset,
})
return nil, err
}
defer cursor.Close(ctx)
if err = cursor.All(ctx, &customers); err != nil {
r.logger.Error("Failed to decode customers by company", map[string]interface{}{
"error": err.Error(),
"company_id": companyID.String(),
})
return nil, err
}
return customers, nil
}
// AddDeviceToken adds a device token for push notifications
func (r *customerRepository) AddDeviceToken(ctx context.Context, id uuid.UUID, token string) error {
filter := bson.M{"_id": id}
update := bson.M{
"$addToSet": bson.M{"device_tokens": token}, // Use $addToSet to avoid duplicates
"$set": bson.M{"updated_at": time.Now()},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to add device token", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
"token": token,
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Device token added successfully", map[string]interface{}{
"customer_id": id.String(),
"token": token,
})
return nil
}
// RemoveDeviceToken removes a device token
func (r *customerRepository) RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error {
filter := bson.M{"_id": id}
update := bson.M{
"$pull": bson.M{"device_tokens": token}, // Use $pull to remove the token
"$set": bson.M{"updated_at": time.Now()},
}
result, err := r.collection.UpdateOne(ctx, filter, update)
if err != nil {
r.logger.Error("Failed to remove device token", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
"token": token,
})
return err
}
if result.MatchedCount == 0 {
return errors.New("customer not found")
}
r.logger.Info("Device token removed successfully", map[string]interface{}{
"customer_id": id.String(),
"token": token,
})
return nil
}