package sql import ( "context" "fmt" "tercul/internal/domain" "time" "gorm.io/gorm" ) type analyticsRepository struct { db *gorm.DB } func NewAnalyticsRepository(db *gorm.DB) domain.AnalyticsRepository { return &analyticsRepository{db: db} } var allowedWorkCounterFields = map[string]bool{ "views": true, "likes": true, "comments": true, "bookmarks": true, "shares": true, "translation_count": true, } var allowedTranslationCounterFields = map[string]bool{ "views": true, "likes": true, "comments": true, "shares": true, } func (r *analyticsRepository) IncrementWorkCounter(ctx context.Context, workID uint, field string, value int) error { if !allowedWorkCounterFields[field] { return fmt.Errorf("invalid work counter field: %s", field) } // Using a transaction to ensure atomicity return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // First, try to update the existing record result := tx.Model(&domain.WorkStats{}).Where("work_id = ?", workID).UpdateColumn(field, gorm.Expr(fmt.Sprintf("%s + ?", field), value)) if result.Error != nil { return result.Error } // If no rows were affected, the record does not exist, so create it if result.RowsAffected == 0 { initialData := map[string]interface{}{"work_id": workID, field: value} return tx.Model(&domain.WorkStats{}).Create(initialData).Error } return nil }) } func (r *analyticsRepository) IncrementTranslationCounter(ctx context.Context, translationID uint, field string, value int) error { if !allowedTranslationCounterFields[field] { return fmt.Errorf("invalid translation counter field: %s", field) } return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { result := tx.Model(&domain.TranslationStats{}).Where("translation_id = ?", translationID).UpdateColumn(field, gorm.Expr(fmt.Sprintf("%s + ?", field), value)) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { initialData := map[string]interface{}{"translation_id": translationID, field: value} return tx.Model(&domain.TranslationStats{}).Create(initialData).Error } return nil }) } func (r *analyticsRepository) UpdateWorkStats(ctx context.Context, workID uint, stats domain.WorkStats) error { return r.db.WithContext(ctx).Model(&domain.WorkStats{}).Where("work_id = ?", workID).Updates(stats).Error } func (r *analyticsRepository) UpdateTranslationStats(ctx context.Context, translationID uint, stats domain.TranslationStats) error { return r.db.WithContext(ctx).Model(&domain.TranslationStats{}).Where("translation_id = ?", translationID).Updates(stats).Error } func (r *analyticsRepository) GetOrCreateWorkStats(ctx context.Context, workID uint) (*domain.WorkStats, error) { var stats domain.WorkStats err := r.db.WithContext(ctx).Where(domain.WorkStats{WorkID: workID}).FirstOrCreate(&stats).Error return &stats, err } func (r *analyticsRepository) GetOrCreateTranslationStats(ctx context.Context, translationID uint) (*domain.TranslationStats, error) { var stats domain.TranslationStats err := r.db.WithContext(ctx).Where(domain.TranslationStats{TranslationID: translationID}).FirstOrCreate(&stats).Error return &stats, err } func (r *analyticsRepository) GetOrCreateUserEngagement(ctx context.Context, userID uint, date time.Time) (*domain.UserEngagement, error) { var engagement domain.UserEngagement err := r.db.WithContext(ctx).Where(domain.UserEngagement{UserID: userID, Date: date}).FirstOrCreate(&engagement).Error return &engagement, err } func (r *analyticsRepository) UpdateUserEngagement(ctx context.Context, userEngagement *domain.UserEngagement) error { return r.db.WithContext(ctx).Save(userEngagement).Error } func (r *analyticsRepository) UpdateTrending(ctx context.Context, trending []domain.Trending) error { if len(trending) == 0 { return nil } return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { timePeriod := trending[0].TimePeriod date := trending[0].Date if err := tx.Where("time_period = ? AND date = ?", timePeriod, date).Delete(&domain.Trending{}).Error; err != nil { return err } return tx.Create(&trending).Error }) }