Files
tm_back/internal/feedback/repository.go
T
n.nakhostin 21b736b55b Implement Feedback Management System with CRUD Operations and API Enhancements
- Introduced a new feedback management system, including the creation of feedback entities, services, and repositories.
- Implemented CRUD operations for feedback, allowing users to submit likes and dislikes on tenders.
- Developed administrative endpoints for listing and retrieving feedback with comprehensive filtering options.
- Enhanced public API to allow users to view feedback related to tenders.
- Updated Swagger documentation to reflect new feedback endpoints and their functionalities.
- Removed obsolete configuration file `config.yaml` as part of the transition to a modular configuration system.
- Ensured adherence to Clean Architecture principles throughout the implementation.
2025-08-16 16:00:03 +03:30

433 lines
11 KiB
Go

package feedback
import (
"context"
"time"
"tm/pkg/logger"
"tm/pkg/mongo"
"go.mongodb.org/mongo-driver/bson"
driver "go.mongodb.org/mongo-driver/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)
}
// 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
}
// 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
}