diff --git a/internal/storage/schema.go b/internal/storage/schema.go index a7d4d69..b7b42ce 100644 --- a/internal/storage/schema.go +++ b/internal/storage/schema.go @@ -31,6 +31,7 @@ func (r *MySQLRepository) EnsureSchema(ctx context.Context) error { KEY idx_dashboard_cdc_user_country_origin_country (sys_origin, country_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`, createDailyMetricTable, + createDailyUserMetricTable, createPeriodMetricTable, createPeriodUserMetricTable, createGamePeriodMetricTable, @@ -64,6 +65,20 @@ const createDailyMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_dai KEY idx_daily_origin_date (sys_origin, stat_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci` +const createDailyUserMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_daily_user_metric ( + id bigint NOT NULL AUTO_INCREMENT, + stat_date date NOT NULL, + sys_origin varchar(50) NOT NULL, + country_code varchar(64) NOT NULL, + country_name varchar(128) NOT NULL, + user_id bigint NOT NULL, + metric_type varchar(32) NOT NULL, + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY uk_daily_user_metric (stat_date, sys_origin, country_code, user_id, metric_type), + KEY idx_daily_user_metric (sys_origin, user_id, metric_type) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci` + const createPeriodMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_period_metric ( id bigint NOT NULL AUTO_INCREMENT, period_type varchar(16) NOT NULL, diff --git a/internal/storage/user_metrics.go b/internal/storage/user_metrics.go index f5ab07b..fff8e8b 100644 --- a/internal/storage/user_metrics.go +++ b/internal/storage/user_metrics.go @@ -12,13 +12,7 @@ func (r *MySQLRepository) applyDailyUserMetric(ctx context.Context, tx *sql.Tx, if c.DeltaSign < 0 { return r.deleteDailyUserMetric(ctx, tx, c, day, metricType) } - inserted, err := insertPeriodUserMetric(ctx, tx, c, dashboard.PeriodWindow{ - Type: dashboard.PeriodDay, - Key: day.Format("2006-01-02"), - Name: day.Format("2006-01-02"), - StartDate: dashboardPtr(day), - EndDate: dashboardPtr(day.AddDate(0, 0, 1)), - }, r.cfg.Dashboard.StorageTimezone, metricType) + inserted, err := insertDailyUserMetric(ctx, tx, c, day, metricType) if err != nil || !inserted { return err } @@ -59,14 +53,7 @@ ON DUPLICATE KEY UPDATE `+column+` = `+column+` + 1, country_name = VALUES(count } func (r *MySQLRepository) deleteDailyUserMetric(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, day time.Time, metricType string) error { - window := dashboard.PeriodWindow{ - Type: dashboard.PeriodDay, - Key: day.Format("2006-01-02"), - Name: day.Format("2006-01-02"), - StartDate: dashboardPtr(day), - EndDate: dashboardPtr(day.AddDate(0, 0, 1)), - } - deleted, err := deletePeriodUserMetric(ctx, tx, c, window, r.cfg.Dashboard.StorageTimezone, metricType) + deleted, err := deleteDailyUserMetricRow(ctx, tx, c, day, metricType) if err != nil || !deleted { return err } @@ -99,6 +86,31 @@ WHERE period_type = ? AND period_key = ? AND stat_timezone = ? AND sys_origin = 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 ( diff --git a/migrations/001_dashboard_cdc_worker.sql b/migrations/001_dashboard_cdc_worker.sql index a1eb4b6..45bd057 100644 --- a/migrations/001_dashboard_cdc_worker.sql +++ b/migrations/001_dashboard_cdc_worker.sql @@ -26,3 +26,17 @@ CREATE TABLE IF NOT EXISTS dashboard_cdc_user_country ( PRIMARY KEY (user_id), KEY idx_dashboard_cdc_user_country_origin_country (sys_origin, country_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS country_dashboard_daily_user_metric ( + id bigint NOT NULL AUTO_INCREMENT, + stat_date date NOT NULL, + sys_origin varchar(50) NOT NULL, + country_code varchar(64) NOT NULL, + country_name varchar(128) NOT NULL, + user_id bigint NOT NULL, + metric_type varchar(32) NOT NULL, + created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY uk_daily_user_metric (stat_date, sys_origin, country_code, user_id, metric_type), + KEY idx_daily_user_metric (sys_origin, user_id, metric_type) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;