Add Contact Management Functionality
- Introduced a new contact management feature, including the ability to create, retrieve, search, update, and delete contact messages. - Implemented the Contact entity, repository, service, and handler layers following Clean Architecture principles. - Added API endpoints for contact operations in Swagger documentation, ensuring comprehensive API specifications. - Enhanced error handling and validation for contact data, improving robustness and user experience. - Updated the main application to initialize the contact repository and service, integrating them into the existing system.
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package contact
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
orm "tm/pkg/mongo"
|
||||
|
||||
"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 orm.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 orm.Pagination) (*orm.PaginatedResult[Contact], error) {
|
||||
filter := bson.M{}
|
||||
|
||||
// Apply text search if provided
|
||||
if filters.Search != nil && *filters.Search != "" {
|
||||
filter["$text"] = bson.M{"$search": *filters.Search}
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if filters.Status != nil && *filters.Status != "" {
|
||||
filter["status"] = *filters.Status
|
||||
}
|
||||
|
||||
// Apply date range filters
|
||||
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
|
||||
}
|
||||
|
||||
return r.repo.FindAll(ctx, filter, pagination)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user