Files
tm_back/internal/contact/repository.go
T
2026-05-17 17:21:36 +03:30

232 lines
6.2 KiB
Go

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)
}