Add Inquiry Management Functionality and Update API Documentation
- Introduced a new inquiry management feature, including CRUD operations for inquiries in both admin and public API endpoints. - Implemented inquiry entity, service, repository, and handler layers following the clean architecture principles. - Added new API routes for searching, retrieving, updating, and deleting inquiries, enhancing the overall functionality of the system. - Updated Swagger documentation to include detailed descriptions and examples for the new inquiry endpoints, ensuring clarity for API consumers. - Enhanced error handling for duplicate inquiries and validation, improving the robustness of the inquiry management process. - Updated existing routes to integrate inquiry handling, ensuring a seamless user experience across the application.
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
package inquiry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
orm "tm/pkg/mongo"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Repository defines methods for inquiry data access
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, inquiry *Inquiry) error
|
||||
GetByID(ctx context.Context, id string) (*Inquiry, error)
|
||||
UpdateStatus(ctx context.Context, id string, status, reason, changedBy, description string) error
|
||||
Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) ([]Inquiry, int64, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
CheckPendingInquiry(ctx context.Context, email string) (*Inquiry, error)
|
||||
CheckPendingInquiryByPhone(ctx context.Context, phone string) (*Inquiry, error)
|
||||
}
|
||||
|
||||
// inquiryRepository implements the Repository interface using the MongoDB ORM
|
||||
type inquiryRepository struct {
|
||||
ormRepo orm.Repository[Inquiry]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewInquiryRepository creates a new inquiry repository
|
||||
func NewInquiryRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||
// Create indexes using the ORM's index management
|
||||
indexes := []orm.Index{
|
||||
*orm.CreateTextIndex("search_idx", "full_name", "company_name", "work_email"),
|
||||
*orm.NewIndex("company_name_idx", bson.D{bson.E{Key: "company_name", Value: 1}}),
|
||||
*orm.NewIndex("work_email_idx", bson.D{bson.E{Key: "work_email", Value: 1}}),
|
||||
*orm.NewIndex("phone_number_idx", bson.D{bson.E{Key: "phone_number", Value: 1}}),
|
||||
*orm.NewIndex("status_idx", bson.D{bson.E{Key: "status", Value: 1}}),
|
||||
*orm.NewIndex("created_at_idx", bson.D{bson.E{Key: "created_at", Value: -1}}),
|
||||
// Compound index for duplicate prevention
|
||||
*orm.NewIndex("email_status_idx", bson.D{
|
||||
bson.E{Key: "work_email", Value: 1},
|
||||
bson.E{Key: "status", Value: 1},
|
||||
}),
|
||||
*orm.NewIndex("phone_status_idx", bson.D{
|
||||
bson.E{Key: "phone_number", Value: 1},
|
||||
bson.E{Key: "status", Value: 1},
|
||||
}),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
err := mongoManager.CreateIndexes("inquiries", indexes)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to create inquiry indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Create ORM repository
|
||||
ormRepo := orm.NewRepository[Inquiry](mongoManager.GetCollection("inquiries"), logger)
|
||||
|
||||
return &inquiryRepository{
|
||||
ormRepo: ormRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new inquiry
|
||||
func (r *inquiryRepository) Create(ctx context.Context, inquiry *Inquiry) error {
|
||||
// Set created timestamp using Unix timestamp
|
||||
now := time.Now().Unix()
|
||||
inquiry.SetCreatedAt(now)
|
||||
inquiry.SetUpdatedAt(now)
|
||||
|
||||
// Set initial status to pending
|
||||
inquiry.Status = StatusPending
|
||||
|
||||
// Add initial status change to history
|
||||
inquiry.AddStatusChange(StatusPending, "Inquiry submitted", "system", "Initial inquiry submission")
|
||||
|
||||
// Use ORM to create inquiry
|
||||
err := r.ormRepo.Create(ctx, inquiry)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create inquiry", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"work_email": inquiry.WorkEmail,
|
||||
"company_name": inquiry.CompanyName,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Inquiry created successfully", map[string]interface{}{
|
||||
"inquiry_id": inquiry.ID,
|
||||
"work_email": inquiry.WorkEmail,
|
||||
"company_name": inquiry.CompanyName,
|
||||
"status": inquiry.Status,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves an inquiry by ID
|
||||
func (r *inquiryRepository) GetByID(ctx context.Context, id string) (*Inquiry, error) {
|
||||
inquiry, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("inquiry not found")
|
||||
}
|
||||
r.logger.Error("Failed to get inquiry by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"inquiry_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inquiry, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates the status of an inquiry
|
||||
func (r *inquiryRepository) UpdateStatus(ctx context.Context, id string, status, reason, changedBy, description string) error {
|
||||
// Get inquiry first
|
||||
inquiry, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add status change to history
|
||||
inquiry.AddStatusChange(status, reason, changedBy, description)
|
||||
|
||||
// Update in database - use the correct Update method signature
|
||||
err = r.ormRepo.Update(ctx, inquiry)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update inquiry status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"inquiry_id": id,
|
||||
"new_status": status,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Inquiry status updated successfully", map[string]interface{}{
|
||||
"inquiry_id": id,
|
||||
"old_status": inquiry.StatusHistory[len(inquiry.StatusHistory)-2].Status, // Previous status
|
||||
"new_status": status,
|
||||
"changed_by": changedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPendingInquiry checks if there's a pending inquiry for the given email
|
||||
func (r *inquiryRepository) CheckPendingInquiry(ctx context.Context, email string) (*Inquiry, error) {
|
||||
filter := bson.M{
|
||||
"work_email": email,
|
||||
"status": bson.M{"$in": []string{StatusPending, StatusReviewed}},
|
||||
}
|
||||
|
||||
pagination := orm.NewPaginationBuilder().
|
||||
Limit(1).
|
||||
SortBy("created_at", "desc").
|
||||
Build()
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to check pending inquiry by email", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"work_email": email,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Items) > 0 {
|
||||
return &result.Items[0], nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CheckPendingInquiryByPhone checks if there's a pending inquiry for the given phone number
|
||||
func (r *inquiryRepository) CheckPendingInquiryByPhone(ctx context.Context, phone string) (*Inquiry, error) {
|
||||
filter := bson.M{
|
||||
"phone_number": phone,
|
||||
"status": bson.M{"$in": []string{StatusPending, StatusReviewed}},
|
||||
}
|
||||
|
||||
pagination := orm.NewPaginationBuilder().
|
||||
Limit(1).
|
||||
SortBy("created_at", "desc").
|
||||
Build()
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to check pending inquiry by phone", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"phone_number": phone,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Items) > 0 {
|
||||
return &result.Items[0], nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Search retrieves inquiries with pagination and filters
|
||||
func (r *inquiryRepository) Search(ctx context.Context, form *SearchInquiriesForm, pagination *response.Pagination) ([]Inquiry, int64, error) {
|
||||
filter := bson.M{}
|
||||
|
||||
// Add search filter
|
||||
if form.Search != nil {
|
||||
filter["$or"] = bson.A{
|
||||
bson.M{"full_name": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
bson.M{"company_name": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
bson.M{"work_email": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
}
|
||||
}
|
||||
|
||||
// Add status filter
|
||||
if form.Status != nil {
|
||||
filter["status"] = *form.Status
|
||||
}
|
||||
|
||||
// Build sort
|
||||
sort := "created_at"
|
||||
if pagination.SortBy != "" {
|
||||
sort = pagination.SortBy
|
||||
}
|
||||
|
||||
sortOrder := "desc"
|
||||
if pagination.SortOrder != "" {
|
||||
sortOrder = pagination.SortOrder
|
||||
}
|
||||
|
||||
// Build pagination
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Limit(pagination.Limit).
|
||||
Skip(pagination.Offset).
|
||||
SortBy(sort, sortOrder).
|
||||
Build()
|
||||
|
||||
// Use ORM to find inquiries
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to search inquiries", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return result.Items, result.TotalCount, nil
|
||||
}
|
||||
|
||||
// Delete deletes an inquiry
|
||||
func (r *inquiryRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get inquiry first to ensure it exists
|
||||
_, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete inquiry", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"inquiry_id": id,
|
||||
})
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return errors.New("inquiry not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete in database
|
||||
err = r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete inquiry", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"inquiry_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Inquiry deleted successfully", map[string]interface{}{
|
||||
"inquiry_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user