package storage import ( "context" "crypto/sha1" "database/sql" "encoding/hex" "strings" "time" "github.com/shopspring/decimal" "dashboard-cdc-worker/internal/dashboard" ) func (r *MySQLRepository) upsertGameEventFact(ctx context.Context, tx *sql.Tx, c dashboard.Contribution, storageZone *time.Location) error { if !isGameFact(c) { return nil } if storageZone == nil { storageZone = time.UTC } deltaSign := c.DeltaSign if deltaSign == 0 { deltaSign = 1 } sign := decimal.NewFromInt(int64(deltaSign)) consume := c.GameConsume.Mul(sign) payout := c.GamePayoutBy.Mul(sign) profit := c.GameProfit.Mul(sign) orderCount := c.GameOrder * int64(deltaSign) _, err := tx.ExecContext(ctx, ` INSERT INTO dashboard_game_event_fact ( fact_key, source_schema, source_table, source_pk, source_action, sys_origin, stat_date, event_time, user_id, country_code, country_name, game_provider, game_id, game_name, consume_amount, payout_amount, profit_amount, order_count ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE source_action = VALUES(source_action), sys_origin = VALUES(sys_origin), stat_date = VALUES(stat_date), event_time = VALUES(event_time), user_id = VALUES(user_id), country_code = VALUES(country_code), country_name = VALUES(country_name), game_provider = VALUES(game_provider), game_id = VALUES(game_id), game_name = VALUES(game_name), consume_amount = VALUES(consume_amount), payout_amount = VALUES(payout_amount), profit_amount = VALUES(profit_amount), order_count = VALUES(order_count), updated_at = NOW() `, gameFactKey(c), c.SourceSchema, c.SourceTable, c.SourcePK, c.SourceAction, c.SysOrigin, dashboard.DailyDate(c.EventTime, storageZone), c.EventTime, c.UserID, c.Country.Code, c.Country.Name, c.GameProvider, c.GameID, c.GameName, consume, payout, profit, orderCount) return err } func isGameFact(c dashboard.Contribution) bool { return !c.EventTime.IsZero() && c.SysOrigin != "" && c.UserID != 0 && c.GameProvider != "" && c.GameID != "" && (!c.GameConsume.IsZero() || !c.GamePayoutBy.IsZero() || !c.GameProfit.IsZero() || c.GameOrder != 0 || c.GameUser) } func gameFactKey(c dashboard.Contribution) string { parts := []string{c.EventKey, c.SourceSchema, c.SourceTable, c.SourcePK, c.SourceAction} raw := strings.Join(parts, "|") if len(raw) <= 255 && strings.Trim(raw, "|") != "" { return raw } sum := sha1.Sum([]byte(raw)) return "game-fact:" + hex.EncodeToString(sum[:]) }