Files
tm_back/internal/feedback/repository.go
T

402 lines
11 KiB
Go

package feedback
import (
"context"
"errors"
"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"
)
// 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
Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*orm.PaginatedResult[Feedback], error)
GetByID(ctx context.Context, id string) (*Feedback, error)
GetByCompanyID(ctx context.Context, companyID string, tender string) (*Feedback, error)
GetByCustomerID(ctx context.Context, customerID string, tenderID string) (*Feedback, error)
GetByTenderID(ctx context.Context, tenderID, companyID string) (*Feedback, error)
GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*Feedback, error)
Delete(ctx context.Context, id string) error
CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error)
CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, error)
}
// feedbackRepository implements FeedbackRepository interface using MongoDB ORM
type feedbackRepository struct {
repo orm.Repository[Feedback]
logger logger.Logger
}
// NewFeedbackRepository creates a new feedback repository
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
// Create indexes for feedback collection
feedbackIndexes := []orm.Index{
*orm.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
*orm.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
*orm.NewIndex("feedback_type_idx", bson.D{{Key: "feedback_type", Value: 1}}),
// Compound indexes for common queries
*orm.NewIndex("tender_company_idx", bson.D{
{Key: "tender_id", Value: 1},
{Key: "company_id", Value: 1},
}),
*orm.NewIndex("customer_id_idx", bson.D{{Key: "customer_id", Value: 1}}),
*orm.NewIndex("tender_customer_idx", bson.D{
{Key: "tender_id", Value: 1},
{Key: "customer_id", Value: 1},
}),
*orm.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 := orm.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
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 {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrFeedbackNotFound
}
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 {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrFeedbackNotFound
}
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
}
// GetByCustomerID retrieves feedback by customer ID and tender ID.
func (r *feedbackRepository) GetByCustomerID(ctx context.Context, customerID string, tenderID string) (*Feedback, error) {
filter := bson.M{"customer_id": customerID, "tender_id": tenderID}
result, err := r.repo.FindOne(ctx, filter)
if err != nil {
r.logger.Error("Failed to get feedback by customer ID and tender ID", map[string]interface{}{
"customer_id": customerID,
"tender_id": tenderID,
"error": err.Error(),
})
return nil, err
}
return result, nil
}
// GetByTenderID retrieves feedback by tender ID and company ID
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 {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrFeedbackNotFound
}
r.logger.Error("Failed to get feedback by tender ID and company ID", map[string]interface{}{
"tender_id": tenderID,
"company_id": companyID,
"error": err.Error(),
})
return nil, err
}
return result, nil
}
// GetByTenderIDForCustomer retrieves feedback by tender ID and customer ID.
func (r *feedbackRepository) GetByTenderIDForCustomer(ctx context.Context, tenderID, customerID string) (*Feedback, error) {
filter := bson.M{"tender_id": tenderID, "customer_id": customerID}
result, err := r.repo.FindOne(ctx, filter)
if err != nil {
if errors.Is(err, orm.ErrDocumentNotFound) {
return nil, ErrFeedbackNotFound
}
r.logger.Error("Failed to get feedback by tender ID and customer ID", map[string]interface{}{
"tender_id": tenderID,
"customer_id": customerID,
"error": err.Error(),
})
return nil, err
}
return result, nil
}
// Delete deletes a 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
}
return nil
}
// Search searches feedback based on criteria
func (r *feedbackRepository) Search(ctx context.Context, criteria SearchForm, pagination *response.Pagination) (*orm.PaginatedResult[Feedback], error) {
filter := r.buildSearchFilter(criteria)
mongoPagination, err := orm.BuildListPagination(
pagination.Limit,
pagination.Offset,
pagination.Cursor,
pagination.SortBy,
pagination.SortOrder,
filter,
orm.ListPaginationOptions{},
)
if err != nil {
return nil, err
}
result, err := r.repo.FindAll(ctx, filter, mongoPagination)
if err != nil {
r.logger.Error("Failed to search feedback", map[string]interface{}{
"error": err.Error(),
})
return nil, err
}
return result, nil
}
// CompanyStats calculates feedback statistics for a company.
func (r *feedbackRepository) CompanyStats(ctx context.Context, companyID string) (*StatsResponse, error) {
counts, lastUpdated, err := r.aggregateFeedbackCounts(ctx, bson.M{"company_id": companyID})
if err != nil {
r.logger.Error("Failed to get feedback stats", map[string]interface{}{
"company_id": companyID,
"error": err.Error(),
})
return nil, err
}
return &StatsResponse{
CompanyID: companyID,
TotalFeedback: counts.totalFeedback,
TotalLikes: counts.totalLikes,
TotalDislikes: counts.totalDislikes,
LastUpdated: lastUpdated,
}, nil
}
// CustomerStats calculates feedback statistics for a customer.
func (r *feedbackRepository) CustomerStats(ctx context.Context, customerID string) (*CustomerStatsResponse, error) {
counts, lastUpdated, err := r.aggregateFeedbackCounts(ctx, bson.M{"customer_id": customerID})
if err != nil {
r.logger.Error("Failed to get customer feedback stats", map[string]interface{}{
"customer_id": customerID,
"error": err.Error(),
})
return nil, err
}
return &CustomerStatsResponse{
CustomerID: customerID,
TotalFeedback: counts.totalFeedback,
TotalLikes: counts.totalLikes,
TotalDislikes: counts.totalDislikes,
LastUpdated: lastUpdated,
}, nil
}
type feedbackCountAggregate struct {
totalFeedback int64
totalLikes int64
totalDislikes int64
}
func (r *feedbackRepository) aggregateFeedbackCounts(ctx context.Context, match bson.M) (feedbackCountAggregate, int64, error) {
pipeline := mongo.Pipeline{
{{Key: "$match", Value: match}},
{
{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 {
return feedbackCountAggregate{}, time.Now().Unix(), err
}
counts := feedbackCountAggregate{}
lastUpdated := time.Now().Unix()
if len(results) == 0 {
return counts, lastUpdated, nil
}
result := results[0]
counts.totalFeedback = aggregateInt64(result["total_feedback"])
counts.totalLikes = aggregateInt64(result["total_likes"])
counts.totalDislikes = aggregateInt64(result["total_dislikes"])
if v, ok := result["last_updated"].(int64); ok && v > 0 {
lastUpdated = v
}
return counts, lastUpdated, nil
}
func aggregateInt64(value interface{}) int64 {
switch v := value.(type) {
case int32:
return int64(v)
case int64:
return v
case float64:
return int64(v)
default:
return 0
}
}
// buildSearchFilter builds MongoDB filter from search criteria
func (r *feedbackRepository) buildSearchFilter(criteria SearchForm) bson.M {
filter := bson.M{}
// Always exclude deleted feedback unless specifically requested
if criteria.Tender != nil {
filter["tender_id"] = *criteria.Tender
}
if criteria.Customer != nil {
filter["customer_id"] = *criteria.Customer
}
if criteria.Company != nil {
filter["company_id"] = *criteria.Company
}
if criteria.Type != nil {
filter["feedback_type"] = *criteria.Type
}
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
}