Files
tm_back/internal/customer/repository.go
T
n.nakhostin 20aee91467 Refactor Customer and User Repository Methods for ObjectID Handling
- Updated GetByIDs methods in customer and user repositories to convert string IDs to bson.ObjectID, ensuring proper handling of MongoDB object IDs.
- Enhanced error handling for ID conversion to provide clearer feedback when invalid IDs are provided.
- Modified notification service calls in customer and user services to use context.Background() for asynchronous email sending, improving concurrency management.
- Ensured consistent handling of company IDs in the customer repository for retrieving customers by companies.
2025-10-01 13:19:34 +03:30

401 lines
11 KiB
Go

package customer
import (
"context"
"errors"
"time"
"tm/pkg/logger"
orm "tm/pkg/mongo"
"tm/pkg/response"
"go.mongodb.org/mongo-driver/v2/bson"
)
// Repository defines the interface for customer data operations
type Repository interface {
Login(ctx context.Context, id, deviceToken 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)
GetByIDs(ctx context.Context, ids []string) ([]Customer, error)
GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error)
GetByCompanies(ctx context.Context, companies []string) ([]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
}
// customerRepository implements the Repository interface using the MongoDB ORM
type customerRepository struct {
ormRepo orm.Repository[Customer]
logger logger.Logger
}
func collection() string {
return "customers"
}
// NewCustomerRepository creates a new customer repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// Create indexes using the ORM's index management
indexes := []orm.Index{
*orm.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
*orm.CreateTextIndex("search_idx", "email", "username"),
}
// Create indexes
err := mongoManager.CreateIndexes(collection(), indexes)
if err != nil {
logger.Warn("Failed to create customer indexes", map[string]interface{}{
"error": err.Error(),
})
}
// Create ORM repository
ormRepo := orm.NewRepository[Customer](mongoManager.GetCollection(collection()), logger)
return &customerRepository{
ormRepo: ormRepo,
logger: logger,
}
}
// 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)
// Use ORM to create customer
err := r.ormRepo.Create(ctx, customer)
if err != nil {
r.logger.Error("Failed to register customer", map[string]interface{}{
"error": err.Error(),
"email": customer.Email,
"username": customer.Username,
})
return err
}
r.logger.Info("Customer registered successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
"type": customer.Type,
})
return nil
}
// Login updates the last login timestamp
func (r *customerRepository) Login(ctx context.Context, id, deviceToken string) error {
// Get customer first
customer, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// append device token to customer if not exists
// if !slices.Contains(customer.DeviceToken, deviceToken) {
customer.DeviceToken = []string{deviceToken}
// }
// 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, orm.ErrDocumentNotFound) {
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 customer, nil
}
// GetByEmail retrieves a customer by email
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
filter := bson.M{"email": email}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
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{}{
"error": err.Error(),
"email": email,
})
return nil, err
}
return customer, nil
}
// GetByUsername retrieves a customer by username
func (r *customerRepository) GetByUsername(ctx context.Context, username string) (*Customer, error) {
filter := bson.M{"username": username}
customer, err := r.ormRepo.FindOne(ctx, filter)
if err != nil {
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{}{
"error": err.Error(),
"username": username,
})
return nil, err
}
return customer, nil
}
// GetByIDs retrieves customers by IDs
func (r *customerRepository) GetByIDs(ctx context.Context, ids []string) ([]Customer, error) {
objectIDs := make([]bson.ObjectID, 0)
for _, id := range ids {
objectID, err := bson.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
objectIDs = append(objectIDs, objectID)
}
customers, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(ids)).Build())
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("customers not found")
}
r.logger.Error("Failed to get customers by IDs", map[string]interface{}{
"error": err.Error(),
"ids": ids,
})
return nil, err
}
return customers.Items, nil
}
// GetByRole retrieves customers by role
func (r *customerRepository) GetByRole(ctx context.Context, role CustomerRole) ([]Customer, error) {
customers, err := r.ormRepo.FindAll(ctx, bson.M{"role": role}, orm.NewPaginationBuilder().Build())
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, errors.New("customers not found")
}
r.logger.Error("Failed to get customers by role", map[string]interface{}{
"error": err.Error(),
"role": role,
})
return nil, err
}
return customers.Items, nil
}
// GetByCompanies retrieves customers by companies
func (r *customerRepository) GetByCompanies(ctx context.Context, companies []string) ([]Customer, error) {
companyIDs := make([]bson.ObjectID, 0)
for _, company := range companies {
id, err := bson.ObjectIDFromHex(company)
if err != nil {
return nil, err
}
companyIDs = append(companyIDs, id)
}
customers, err := r.ormRepo.FindAll(ctx, bson.M{"companies": bson.M{"$in": companyIDs}}, orm.NewPaginationBuilder().Build())
if err != nil {
return nil, err
}
return customers.Items, nil
}
// GetByCompanyID retrieves customers by company ID with pagination
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID string, pagination *response.Pagination) ([]*Customer, int64, error) {
// Build pagination
paginationBuilder := orm.NewPaginationBuilder().
Limit(pagination.Limit).
Skip(pagination.Offset).
SortBy(pagination.SortBy, pagination.SortOrder).
Build()
// Filter by company ID and active status
filter := bson.M{
"company_id": companyID,
"status": bson.M{"$ne": CustomerStatusInactive},
}
// Use ORM to find customers
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": pagination.Limit,
"offset": pagination.Offset,
})
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, result.TotalCount, nil
}
// Update updates a customer
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error {
// Set updated timestamp using Unix timestamp
customer.SetUpdatedAt(time.Now().Unix())
// Use ORM to update customer
err := r.ormRepo.Update(ctx, customer)
if err != nil {
r.logger.Error("Failed to update customer", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID,
})
return err
}
r.logger.Info("Customer updated successfully", map[string]interface{}{
"customer_id": customer.ID,
"email": customer.Email,
})
return nil
}
// Delete deletes a customer (hard delete)
func (r *customerRepository) Delete(ctx context.Context, id string) error {
// Get customer first
_, 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 in database
err = r.ormRepo.Delete(ctx, id)
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{}{
"customer_id": id,
})
return nil
}
// Search retrieves customers with search and filters
func (r *customerRepository) Search(ctx context.Context, form *SearchCustomersForm, pagination *response.Pagination) ([]Customer, int64, error) {
// Build filter
filter := bson.M{}
if form.Search != nil {
filter["$text"] = bson.M{"$search": *form.Search}
}
if form.Type != nil {
filter["type"] = *form.Type
}
if form.Status != nil {
filter["status"] = *form.Status
}
if form.CompanyID != nil {
filter["company_id"] = *form.CompanyID
}
// Build pagination
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, paginationBuilder.Build())
if err != nil {
r.logger.Error("Failed to search customers", map[string]interface{}{
"error": err.Error(),
"search": form.Search,
})
return nil, 0, err
}
return result.Items, result.TotalCount, nil
}
// UpdateStatus updates customer status
func (r *customerRepository) UpdateStatus(ctx context.Context, id string, status CustomerStatus) error {
// Get customer first
customer, err := r.GetByID(ctx, id)
if err != nil {
return err
}
// Update status
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
}