package contact import ( "context" "fmt" "time" "tm/pkg/logger" orm "tm/pkg/mongo" "tm/pkg/response" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) // ContactRepository defines the interface for contact data operations type ContactRepository interface { // Create creates a new contact message Create(ctx context.Context, contact *Contact) error // GetByID retrieves a contact by ID GetByID(ctx context.Context, id string) (*Contact, error) // Search searches contacts with filters and pagination Search(ctx context.Context, filters SearchContactsForm, pagination *response.Pagination) (*orm.PaginatedResult[Contact], error) // UpdateStatus updates the status of a contact UpdateStatus(ctx context.Context, id string, status ContactStatus, adminNotes *string, resolvedBy *string) error // GetStats returns contact statistics GetStats(ctx context.Context) (*ContactStats, error) // Delete deletes a contact by ID Delete(ctx context.Context, id string) error } // ContactStats represents contact statistics type ContactStats struct { Total int64 `json:"total"` Pending int64 `json:"pending"` InProgress int64 `json:"in_progress"` Resolved int64 `json:"resolved"` Closed int64 `json:"closed"` } // contactRepository implements ContactRepository interface type contactRepository struct { repo orm.Repository[Contact] logger logger.Logger } // NewContactRepository creates a new contact repository func NewContactRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) ContactRepository { // Create indexes for contact collection contactIndexes := []orm.Index{ *orm.NewIndex("email_idx", bson.D{{Key: "email", Value: 1}}), *orm.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}), *orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}), *orm.CreateTextIndex("contact_text_idx", "full_name", "email", "message"), } // Create indexes if err := mongoManager.CreateIndexes("contacts", contactIndexes); err != nil { logger.Error("Failed to create contact indexes", map[string]interface{}{ "error": err.Error(), }) } collection := mongoManager.GetCollection("contacts") return &contactRepository{ repo: orm.NewRepository[Contact](collection, logger), logger: logger, } } // Create creates a new contact message func (r *contactRepository) Create(ctx context.Context, contact *Contact) error { // Set default status if not provided if contact.Status == "" { contact.Status = ContactStatusPending } return r.repo.Create(ctx, contact) } // GetByID retrieves a contact by ID func (r *contactRepository) GetByID(ctx context.Context, id string) (*Contact, error) { return r.repo.FindByID(ctx, id) } // Search searches contacts with filters and pagination func (r *contactRepository) Search(ctx context.Context, filters SearchContactsForm, pagination *response.Pagination) (*orm.PaginatedResult[Contact], error) { filter := bson.M{} if filters.Search != nil && *filters.Search != "" { filter["$text"] = bson.M{"$search": *filters.Search} } if filters.Status != nil && *filters.Status != "" { filter["status"] = *filters.Status } if filters.DateFrom != nil || filters.DateTo != nil { dateFilter := bson.M{} if filters.DateFrom != nil { dateFilter["$gte"] = *filters.DateFrom } if filters.DateTo != nil { dateFilter["$lte"] = *filters.DateTo } filter["created_at"] = dateFilter } mongoPagination, err := orm.BuildListPagination( pagination.Limit, pagination.Offset, pagination.Cursor, pagination.SortBy, pagination.SortOrder, filter, orm.ListPaginationOptions{}, ) if err != nil { return nil, err } return r.repo.FindAll(ctx, filter, mongoPagination) } // UpdateStatus updates the status of a contact func (r *contactRepository) UpdateStatus(ctx context.Context, id string, status ContactStatus, adminNotes *string, resolvedBy *string) error { // Get the existing contact contact, err := r.GetByID(ctx, id) if err != nil { return err } // Update the contact fields contact.Status = status contact.UpdatedAt = time.Now().Unix() if adminNotes != nil { contact.AdminNotes = adminNotes } // Set resolved fields if status is resolved or closed if status == ContactStatusResolved || status == ContactStatusClosed { now := time.Now().Unix() contact.ResolvedAt = &now if resolvedBy != nil { contact.ResolvedBy = resolvedBy } } else { // Unset resolved fields if status is changed back to pending or in_progress contact.ResolvedAt = nil contact.ResolvedBy = nil } err = r.repo.Update(ctx, contact) if err != nil { r.logger.Error("Failed to update contact status", map[string]interface{}{ "id": id, "status": status, "error": err.Error(), }) return fmt.Errorf("failed to update contact status: %w", err) } r.logger.Info("Contact status updated successfully", map[string]interface{}{ "id": id, "status": status, }) return nil } // GetStats returns contact statistics func (r *contactRepository) GetStats(ctx context.Context) (*ContactStats, error) { pipeline := mongo.Pipeline{ { {Key: "$group", Value: bson.D{ {Key: "_id", Value: "$status"}, {Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}, }}, }, } results, err := r.repo.Aggregate(ctx, pipeline) if err != nil { r.logger.Error("Failed to get contact stats", map[string]interface{}{ "error": err.Error(), }) return nil, fmt.Errorf("failed to get contact stats: %w", err) } stats := &ContactStats{} for _, result := range results { status, ok := result["_id"].(string) if !ok { continue } count, ok := result["count"].(int32) if !ok { if count64, ok := result["count"].(int64); ok { count = int32(count64) } else { continue } } switch ContactStatus(status) { case ContactStatusPending: stats.Pending = int64(count) case ContactStatusInProgress: stats.InProgress = int64(count) case ContactStatusResolved: stats.Resolved = int64(count) case ContactStatusClosed: stats.Closed = int64(count) } stats.Total += int64(count) } return stats, nil } // Delete deletes a contact by ID func (r *contactRepository) Delete(ctx context.Context, id string) error { return r.repo.Delete(ctx, id) }