package storage import ( "context" "database/sql" "time" "dashboard-cdc-worker/internal/dashboard" ) func (r *MySQLRepository) applyDailyUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, day time.Time, metricType string) error { if c.DeltaSign < 0 { return r.deleteDailyUserMetric(ctx, tx, c, day, metricType) } inserted, err := insertDailyUserMetric(ctx, tx, c, day, metricType) if err != nil || !inserted { return err } column := dailyMetricColumn(metricType) if column == "" { return nil } _, err = tx.ExecContext(ctx, ` INSERT INTO country_dashboard_daily_metric ( stat_date, date_number, sys_origin, country_code, country_name, refreshed_at , `+column+` ) VALUES (?, ?, ?, ?, ?, NOW(), 1) ON DUPLICATE KEY UPDATE `+column+` = `+column+` + VALUES(`+column+`), country_name = VALUES(country_name), refreshed_at = NOW() `, day, dashboard.DateNumber(day), c.SysOrigin, c.Country.Code, c.Country.Name) return err } func (r *MySQLRepository) applyPeriodUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, window dashboard.PeriodWindow, statTimezone string, metricType string) error { if c.DeltaSign < 0 { return r.deletePeriodUserMetric(ctx, tx, c, window, statTimezone, metricType) } inserted, err := insertPeriodUserMetric(ctx, tx, c, window, statTimezone, metricType) if err != nil || !inserted { return err } column := periodMetricColumn(metricType) if column == "" { return nil } _, err = tx.ExecContext(ctx, ` INSERT INTO country_dashboard_period_metric ( period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date, sys_origin, country_code, country_name, refreshed_at , `+column+` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), 1) ON DUPLICATE KEY UPDATE `+column+` = `+column+` + VALUES(`+column+`), country_name = VALUES(country_name), refreshed_at = NOW() `, window.Type, window.Key, statTimezone, window.Name, window.StartDate, window.EndDate, c.SysOrigin, c.Country.Code, c.Country.Name) return err } func (r *MySQLRepository) deleteDailyUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, day time.Time, metricType string) error { deleted, err := deleteDailyUserMetricRow(ctx, tx, c, day, metricType) if err != nil || !deleted { return err } column := dailyMetricColumn(metricType) if column == "" { return nil } _, err = tx.ExecContext(ctx, ` UPDATE country_dashboard_daily_metric SET `+column+` = GREATEST(`+column+` - 1, 0), refreshed_at = NOW() WHERE stat_date = ? AND sys_origin = ? AND country_code = ? `, day, c.SysOrigin, c.Country.Code) return err } func (r *MySQLRepository) deletePeriodUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, window dashboard.PeriodWindow, statTimezone string, metricType string) error { deleted, err := deletePeriodUserMetric(ctx, tx, c, window, statTimezone, metricType) if err != nil || !deleted { return err } column := periodMetricColumn(metricType) if column == "" { return nil } _, err = tx.ExecContext(ctx, ` UPDATE country_dashboard_period_metric SET `+column+` = GREATEST(`+column+` - 1, 0), refreshed_at = NOW() WHERE period_type = ? AND period_key = ? AND stat_timezone = ? AND sys_origin = ? AND country_code = ? `, window.Type, window.Key, statTimezone, c.SysOrigin, c.Country.Code) return err } func insertDailyUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, day time.Time, metricType string) (bool, error) { result, err := tx.ExecContext(ctx, ` INSERT IGNORE INTO country_dashboard_daily_user_metric ( stat_date, sys_origin, country_code, country_name, user_id, metric_type ) VALUES (?, ?, ?, ?, ?, ?) `, day, c.SysOrigin, c.Country.Code, c.Country.Name, c.UserID, metricType) if err != nil { return false, err } affected, err := result.RowsAffected() return affected > 0, err } func deleteDailyUserMetricRow(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, day time.Time, metricType string) (bool, error) { result, err := tx.ExecContext(ctx, ` DELETE FROM country_dashboard_daily_user_metric WHERE stat_date = ? AND sys_origin = ? AND country_code = ? AND user_id = ? AND metric_type = ? `, day, c.SysOrigin, c.Country.Code, c.UserID, metricType) if err != nil { return false, err } affected, err := result.RowsAffected() return affected > 0, err } func (r *MySQLRepository) applyGamePeriodUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, window dashboard.PeriodWindow, statTimezone string) error { result, err := tx.ExecContext(ctx, ` INSERT IGNORE INTO country_dashboard_game_period_user_metric ( period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date, sys_origin, country_code, country_name, game_provider, game_id, user_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, window.Type, window.Key, statTimezone, window.Name, window.StartDate, window.EndDate, c.SysOrigin, c.Country.Code, c.Country.Name, c.GameProvider, c.GameID, c.UserID) if err != nil { return err } affected, err := result.RowsAffected() if err != nil || affected == 0 { return err } _, err = tx.ExecContext(ctx, ` INSERT INTO country_dashboard_game_period_metric ( period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date, sys_origin, country_code, country_name, game_provider, game_id, game_name, user_count, refreshed_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NOW()) ON DUPLICATE KEY UPDATE user_count = user_count + VALUES(user_count), country_name = VALUES(country_name), game_name = VALUES(game_name), refreshed_at = NOW() `, window.Type, window.Key, statTimezone, window.Name, window.StartDate, window.EndDate, c.SysOrigin, c.Country.Code, c.Country.Name, c.GameProvider, c.GameID, c.GameName) return err } func insertPeriodUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, window dashboard.PeriodWindow, statTimezone string, metricType string) (bool, error) { result, err := tx.ExecContext(ctx, ` INSERT IGNORE INTO country_dashboard_period_user_metric ( period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date, sys_origin, country_code, country_name, user_id, metric_type ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, window.Type, window.Key, statTimezone, window.Name, window.StartDate, window.EndDate, c.SysOrigin, c.Country.Code, c.Country.Name, c.UserID, metricType) if err != nil { return false, err } affected, err := result.RowsAffected() return affected > 0, err } func deletePeriodUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, window dashboard.PeriodWindow, statTimezone string, metricType string) (bool, error) { result, err := tx.ExecContext(ctx, ` DELETE FROM country_dashboard_period_user_metric WHERE period_type = ? AND period_key = ? AND stat_timezone = ? AND sys_origin = ? AND country_code = ? AND user_id = ? AND metric_type = ? `, window.Type, window.Key, statTimezone, c.SysOrigin, c.Country.Code, c.UserID, metricType) if err != nil { return false, err } affected, err := result.RowsAffected() return affected > 0, err } func dailyMetricColumn(metricType string) string { switch metricType { case dashboard.MetricCountryNewUser: return "country_new_user" case dashboard.MetricLuckyGiftUser: return "lucky_gift_user" case dashboard.MetricGameUser: return "game_user" case dashboard.MetricRechargeUser: return "daily_recharge_user" default: return "" } } func periodMetricColumn(metricType string) string { return dailyMetricColumn(metricType) } func dashboardPtr(value time.Time) *time.Time { v := value return &v }