package feedback import ( "context" "time" "tm/pkg/logger" "tm/pkg/mongo" "go.mongodb.org/mongo-driver/v2/bson" driver "go.mongodb.org/mongo-driver/v2/mongo" ) // FeedbackRepository interface defines feedback data access methods type Repository interface { // Basic CRUD operations Create(ctx context.Context, feedback *Feedback) error Update(ctx context.Context, feedback *Feedback) error GetByID(ctx context.Context, id string) (*Feedback, error) GetByCompanyID(ctx context.Context, companyID string, tender string) (*Feedback, error) Delete(ctx context.Context, id string) error List(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) ([]*Feedback, int64, error) Search(ctx context.Context, criteria FeedbackSearchCriteria) ([]*Feedback, error) GetFeedbackSummary(ctx context.Context, tenderID string) (*FeedbackSummary, error) GetFeedbackCountByType(ctx context.Context) (map[FeedbackType]int64, error) GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error) GetCompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error) } // feedbackRepository implements FeedbackRepository interface using MongoDB ORM type feedbackRepository struct { repo mongo.Repository[Feedback] logger logger.Logger } // NewFeedbackRepository creates a new feedback repository func NewFeedbackRepository(mongoManager *mongo.ConnectionManager, logger logger.Logger) Repository { // Create indexes for feedback collection feedbackIndexes := []mongo.Index{ *mongo.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}), *mongo.NewIndex("customer_id_idx", bson.D{{Key: "customer_id", Value: 1}}), *mongo.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}), *mongo.NewIndex("feedback_type_idx", bson.D{{Key: "feedback_type", Value: 1}}), // Compound indexes for common queries *mongo.NewIndex("tender_customer_idx", bson.D{ {Key: "tender_id", Value: 1}, {Key: "customer_id", Value: 1}, }), *mongo.NewIndex("tender_type_idx", bson.D{ {Key: "tender_id", Value: 1}, {Key: "feedback_type", Value: 1}, }), } // Create indexes if err := mongoManager.CreateIndexes("feedback", feedbackIndexes); err != nil { logger.Warn("Failed to create feedback indexes", map[string]interface{}{ "error": err.Error(), }) } // Create ORM repository repo := mongo.NewRepository[Feedback](mongoManager.GetCollection("feedback"), logger) return &feedbackRepository{ repo: repo, logger: logger, } } // Create creates a new feedback func (r *feedbackRepository) Create(ctx context.Context, feedback *Feedback) error { // Set timestamps now := time.Now().Unix() feedback.CreatedAt = now feedback.UpdatedAt = now err := r.repo.Create(ctx, feedback) if err != nil { r.logger.Error("Failed to create feedback", map[string]interface{}{ "error": err.Error(), "tender_id": feedback.TenderID, }) return err } r.logger.Info("Feedback created successfully", map[string]interface{}{ "feedback_id": feedback.GetID(), "tender_id": feedback.TenderID, "type": feedback.FeedbackType, }) return nil } // Update updates an existing feedback func (r *feedbackRepository) Update(ctx context.Context, feedback *Feedback) error { feedback.UpdatedAt = time.Now().Unix() err := r.repo.Update(ctx, feedback) if err != nil { r.logger.Error("Failed to update feedback", map[string]interface{}{ "feedback_id": feedback.GetID(), "error": err.Error(), }) return err } r.logger.Info("Feedback updated successfully", map[string]interface{}{ "feedback_id": feedback.GetID(), "tender_id": feedback.TenderID, }) return nil } // GetByID retrieves feedback by ID func (r *feedbackRepository) GetByID(ctx context.Context, id string) (*Feedback, error) { feedback, err := r.repo.FindByID(ctx, id) if err != nil { r.logger.Error("Failed to get feedback by ID", map[string]interface{}{ "feedback_id": id, "error": err.Error(), }) return nil, err } return feedback, nil } // GetByCompanyID retrieves feedback by company ID and tender ID func (r *feedbackRepository) GetByCompanyID(ctx context.Context, companyID string, tenderID string) (*Feedback, error) { filter := bson.M{"company_id": companyID, "tender_id": tenderID} result, err := r.repo.FindOne(ctx, filter) if err != nil { r.logger.Error("Failed to get feedback by company ID and tender ID", map[string]interface{}{ "company_id": companyID, "tender_id": tenderID, "error": err.Error(), }) return nil, err } return result, nil } // Delete permanently deletes feedback by ID func (r *feedbackRepository) Delete(ctx context.Context, id string) error { err := r.repo.Delete(ctx, id) if err != nil { r.logger.Error("Failed to delete feedback", map[string]interface{}{ "feedback_id": id, "error": err.Error(), }) return err } r.logger.Info("Feedback deleted successfully", map[string]interface{}{ "feedback_id": id, }) return nil } // List retrieves feedback with pagination and filtering func (r *feedbackRepository) List(ctx context.Context, criteria FeedbackSearchCriteria, limit, offset int) ([]*Feedback, int64, error) { filter := r.buildSearchFilter(criteria) pagination := mongo.Pagination{ Limit: limit, Skip: offset, SortField: "created_at", SortOrder: -1, } result, err := r.repo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to list feedback", map[string]interface{}{ "error": err.Error(), }) return nil, 0, err } // Convert []TenderFeedback to []*TenderFeedback feedback := make([]*Feedback, len(result.Items)) for i := range result.Items { feedback[i] = &result.Items[i] } return feedback, result.TotalCount, nil } // GetByFeedbackType retrieves feedback by type func (r *feedbackRepository) GetByFeedbackType(ctx context.Context, feedbackType FeedbackType, limit, offset int) ([]*Feedback, int64, error) { filter := bson.M{"feedback_type": feedbackType} pagination := mongo.Pagination{ Limit: limit, Skip: offset, SortField: "created_at", SortOrder: -1, } result, err := r.repo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to get feedback by type", map[string]interface{}{ "feedback_type": feedbackType, "error": err.Error(), }) return nil, 0, err } // Convert []TenderFeedback to []*TenderFeedback feedback := make([]*Feedback, len(result.Items)) for i := range result.Items { feedback[i] = &result.Items[i] } return feedback, result.TotalCount, nil } // Search searches feedback based on criteria func (r *feedbackRepository) Search(ctx context.Context, criteria FeedbackSearchCriteria) ([]*Feedback, error) { filter := r.buildSearchFilter(criteria) pagination := mongo.Pagination{ Limit: 100, SortField: "created_at", SortOrder: -1, } result, err := r.repo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to search feedback", map[string]interface{}{ "error": err.Error(), }) return nil, err } // Convert []TenderFeedback to []*TenderFeedback feedback := make([]*Feedback, len(result.Items)) for i := range result.Items { feedback[i] = &result.Items[i] } return feedback, nil } // GetFeedbackSummary calculates feedback summary for a tender func (r *feedbackRepository) GetFeedbackSummary(ctx context.Context, tenderID string) (*FeedbackSummary, error) { pipeline := driver.Pipeline{ { {Key: "$match", Value: bson.M{ "tender_id": tenderID, }}, }, { {Key: "$group", Value: bson.M{ "_id": nil, "total_likes": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeLike)}}, 1, 0, }, }, }, "total_dislikes": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeDislike)}}, 1, 0, }, }, }, "total_comments": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$ne": []interface{}{"$comment", nil}}, 1, 0, }, }, }, "total_ratings": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$gt": []interface{}{"$rating", 0}}, 1, 0, }, }, }, "rating_sum": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$gt": []interface{}{"$rating", 0}}, "$rating", 0, }, }, }, "last_updated": bson.M{"$max": "$updated_at"}, }}, }, } results, err := r.repo.Aggregate(ctx, pipeline) if err != nil { r.logger.Error("Failed to get feedback summary", map[string]interface{}{ "tender_id": tenderID, "error": err.Error(), }) return nil, err } summary := &FeedbackSummary{ TenderID: tenderID, TotalLikes: 0, TotalDislikes: 0, LastUpdated: time.Now().Unix(), } if len(results) > 0 { result := results[0] if totalLikes, ok := result["total_likes"].(int32); ok { summary.TotalLikes = int64(totalLikes) } if totalDislikes, ok := result["total_dislikes"].(int32); ok { summary.TotalDislikes = int64(totalDislikes) } if lastUpdated, ok := result["last_updated"].(int64); ok { summary.LastUpdated = lastUpdated } } return summary, nil } // GetFeedbackCountByType gets feedback count grouped by type func (r *feedbackRepository) GetFeedbackCountByType(ctx context.Context) (map[FeedbackType]int64, error) { pipeline := driver.Pipeline{ { {Key: "$group", Value: bson.M{ "_id": "$feedback_type", "count": bson.M{"$sum": 1}, }}, }, } results, err := r.repo.Aggregate(ctx, pipeline) if err != nil { return nil, err } counts := make(map[FeedbackType]int64) for _, result := range results { if feedbackType, ok := result["_id"].(string); ok { if count, ok := result["count"].(int32); ok { counts[FeedbackType(feedbackType)] = int64(count) } } } return counts, nil } // GetCustomerFeedbackHistory gets the feedback history for a specific customer func (r *feedbackRepository) GetCustomerFeedbackHistory(ctx context.Context, customerID string, limit, offset int) ([]*Feedback, int64, error) { filter := bson.M{ "customer_id": customerID, } pagination := mongo.Pagination{ Limit: limit, Skip: offset, SortField: "created_at", SortOrder: -1, } result, err := r.repo.FindAll(ctx, filter, pagination) if err != nil { r.logger.Error("Failed to get customer feedback history", map[string]interface{}{ "customer_id": customerID, "error": err.Error(), }) return nil, 0, err } // Convert []TenderFeedback to []*TenderFeedback feedback := make([]*Feedback, len(result.Items)) for i := range result.Items { feedback[i] = &result.Items[i] } return feedback, result.TotalCount, nil } func (r *feedbackRepository) GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error) { filter := bson.M{"tender_id": tenderID, "company_id": companyID} result, err := r.repo.FindOne(ctx, filter) if err != nil { r.logger.Error("Failed to get feedback by tender ID", map[string]interface{}{ "tender_id": tenderID, "error": err.Error(), }) return nil, err } return result, nil } // GetCompanyFeedbackStats calculates feedback statistics for a company func (r *feedbackRepository) GetCompanyFeedbackStats(ctx context.Context, companyID string) (*CompanyFeedbackStatsResponse, error) { pipeline := driver.Pipeline{ { {Key: "$match", Value: bson.M{ "company_id": companyID, }}, }, { {Key: "$group", Value: bson.M{ "_id": nil, "total_feedback": bson.M{ "$sum": 1, }, "total_likes": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeLike)}}, 1, 0, }, }, }, "total_dislikes": bson.M{ "$sum": bson.M{ "$cond": []interface{}{ bson.M{"$eq": []string{"$feedback_type", string(FeedbackTypeDislike)}}, 1, 0, }, }, }, "last_updated": bson.M{"$max": "$updated_at"}, }}, }, } results, err := r.repo.Aggregate(ctx, pipeline) if err != nil { r.logger.Error("Failed to get company feedback stats", map[string]interface{}{ "company_id": companyID, "error": err.Error(), }) return nil, err } stats := &CompanyFeedbackStatsResponse{ CompanyID: companyID, TotalFeedback: 0, TotalLikes: 0, TotalDislikes: 0, LastUpdated: time.Now().Unix(), } if len(results) > 0 { result := results[0] if totalFeedback, ok := result["total_feedback"].(int32); ok { stats.TotalFeedback = int64(totalFeedback) } if totalLikes, ok := result["total_likes"].(int32); ok { stats.TotalLikes = int64(totalLikes) } if totalDislikes, ok := result["total_dislikes"].(int32); ok { stats.TotalDislikes = int64(totalDislikes) } if lastUpdated, ok := result["last_updated"].(int64); ok { stats.LastUpdated = lastUpdated } } return stats, nil } // buildSearchFilter builds MongoDB filter from search criteria func (r *feedbackRepository) buildSearchFilter(criteria FeedbackSearchCriteria) bson.M { filter := bson.M{} // Always exclude deleted feedback unless specifically requested if criteria.TenderID != nil { filter["tender_id"] = *criteria.TenderID } if criteria.CustomerID != nil { filter["customer_id"] = *criteria.CustomerID } if criteria.CompanyID != nil { filter["company_id"] = *criteria.CompanyID } if criteria.FeedbackType != nil { filter["feedback_type"] = *criteria.FeedbackType } if criteria.DateFrom != nil || criteria.DateTo != nil { dateFilter := bson.M{} if criteria.DateFrom != nil { dateFilter["$gte"] = *criteria.DateFrom } if criteria.DateTo != nil { dateFilter["$lte"] = *criteria.DateTo } filter["created_at"] = dateFilter } return filter }