103 lines
2.8 KiB
Go
103 lines
2.8 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
"dashboard-cdc-worker/internal/config"
|
|
)
|
|
|
|
type Projector struct {
|
|
cfg config.Config
|
|
store Store
|
|
redisClient *redis.Client
|
|
storageZone *time.Location
|
|
statZones map[string]*time.Location
|
|
}
|
|
|
|
func NewProjector(cfg config.Config, store Store, redisClient *redis.Client) *Projector {
|
|
storageZone := mustLocation(cfg.Dashboard.StorageTimezone)
|
|
statZones := make(map[string]*time.Location, len(cfg.Dashboard.StatTimezones))
|
|
for _, zone := range cfg.Dashboard.StatTimezones {
|
|
statZones[zone] = mustLocation(zone)
|
|
}
|
|
return &Projector{
|
|
cfg: cfg,
|
|
store: store,
|
|
redisClient: redisClient,
|
|
storageZone: storageZone,
|
|
statZones: statZones,
|
|
}
|
|
}
|
|
|
|
func (p *Projector) Apply(ctx context.Context, eventKey string, source string, contributions []Contribution) error {
|
|
return p.apply(ctx, eventKey, source, nil, contributions)
|
|
}
|
|
|
|
func (p *Projector) ApplyUser(ctx context.Context, eventKey string, source string, user UserCountry, contributions []Contribution) error {
|
|
return p.apply(ctx, eventKey, source, &user, contributions)
|
|
}
|
|
|
|
func (p *Projector) apply(ctx context.Context, eventKey string, source string, user *UserCountry, contributions []Contribution) error {
|
|
if user == nil && len(contributions) == 0 {
|
|
return nil
|
|
}
|
|
if len(contributions) == 0 {
|
|
contributions = nil
|
|
}
|
|
if p.cfg.CDC.DryRun {
|
|
slog.Info("dashboard cdc dry run", "eventKey", eventKey, "source", source, "user", user != nil, "contributions", len(contributions))
|
|
return nil
|
|
}
|
|
if err := p.store.WithTx(ctx, func(tx *sql.Tx) error {
|
|
applied, err := p.store.MarkEventApplied(ctx, tx, eventKey, source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !applied {
|
|
return nil
|
|
}
|
|
if user != nil {
|
|
if err := p.store.UpsertUserCountry(ctx, tx, *user); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, contribution := range contributions {
|
|
if contribution.Empty() {
|
|
continue
|
|
}
|
|
if contribution.DeltaSign == 0 {
|
|
contribution.DeltaSign = 1
|
|
}
|
|
if err := p.store.ApplyDailyContribution(ctx, tx, contribution, p.cfg.Dashboard.StorageTimezone); err != nil {
|
|
return err
|
|
}
|
|
for statTimezone := range p.statZones {
|
|
if err := p.store.ApplyPeriodContribution(ctx, tx, contribution, statTimezone); err != nil {
|
|
return err
|
|
}
|
|
if err := p.store.ApplyGamePeriodContribution(ctx, tx, contribution, statTimezone); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return p.writeRealtimeRedis(ctx, contributions)
|
|
}
|
|
|
|
func mustLocation(name string) *time.Location {
|
|
location, err := time.LoadLocation(name)
|
|
if err != nil {
|
|
slog.Warn("invalid timezone, fallback to UTC", "timezone", name, "error", err)
|
|
return time.UTC
|
|
}
|
|
return location
|
|
}
|