diff --git a/cmds/core/client.go b/cmds/core/client.go index a5512bc4..cd1e9381 100644 --- a/cmds/core/client.go +++ b/cmds/core/client.go @@ -7,15 +7,32 @@ import ( "golang.org/x/text/language" ) -type Client struct { - Fetch fetch.Client - DB database.Client +var _ Client = &client{} + +type Client interface { + Render(locale language.Tag) stats.Renderer + + Database() database.Client + Fetch() fetch.Client +} + +type client struct { + fetch fetch.Client + db database.Client +} + +func (c *client) Database() database.Client { + return c.db +} + +func (c *client) Fetch() fetch.Client { + return c.fetch } -func (c *Client) Render(locale language.Tag) stats.Renderer { - return stats.NewRenderer(c.Fetch, locale) +func (c *client) Render(locale language.Tag) stats.Renderer { + return stats.NewRenderer(c.fetch, locale) } -func NewClient(fetch fetch.Client, database database.Client) Client { - return Client{Fetch: fetch, DB: database} +func NewClient(fetch fetch.Client, database database.Client) *client { + return &client{fetch: fetch, db: database} } diff --git a/cmds/core/scheduler/cron.go b/cmds/core/scheduler/cron.go new file mode 100644 index 00000000..23ea3de7 --- /dev/null +++ b/cmds/core/scheduler/cron.go @@ -0,0 +1,44 @@ +package scheduler + +import ( + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/go-co-op/gocron" + "github.com/rs/zerolog/log" +) + +func StartCronJobs(client core.Client) { + log.Info().Msg("starting cron jobs") + + c := gocron.NewScheduler(time.UTC) + // Tasks + c.Cron("* * * * *").Do(runTasksWorker(client)) + c.Cron("0 * * * *").Do(restartTasksWorker(client)) + + // Glossary - Do it around the same time WG releases game updates + c.Cron("0 10 * * *").Do(UpdateGlossaryWorker(client)) + c.Cron("0 12 * * *").Do(UpdateGlossaryWorker(client)) + // c.AddFunc("40 9 * * 0", updateAchievementsWorker) + + // Averages - Update averages shortly after session refreshes + c.Cron("0 10 * * *").Do(UpdateAveragesWorker(client)) + c.Cron("0 2 * * *").Do(UpdateAveragesWorker(client)) + c.Cron("0 19 * * *").Do(UpdateAveragesWorker(client)) + + // Sessions + c.Cron("0 9 * * *").Do(createSessionTasksWorker(client, "NA")) // NA + c.Cron("0 1 * * *").Do(createSessionTasksWorker(client, "EU")) // EU + c.Cron("0 18 * * *").Do(createSessionTasksWorker(client, "AS")) // Asia + + // Refresh WN8 + // "45 9 * * *" // NA + // "45 1 * * *" // EU + // "45 18 * * *" // Asia + + // Configurations + c.Cron("0 0 */7 * *").Do(rotateBackgroundPresetsWorker(client)) + + // Start the Cron job scheduler + c.StartAsync() +} diff --git a/cmds/core/scheduler/glossary.go b/cmds/core/scheduler/glossary.go new file mode 100644 index 00000000..a81cd930 --- /dev/null +++ b/cmds/core/scheduler/glossary.go @@ -0,0 +1,48 @@ +package scheduler + +import ( + "context" + "time" + + "github.com/cufee/aftermath/cmds/core" + "github.com/rs/zerolog/log" +) + +// CurrentTankAverages + +func UpdateAveragesWorker(client core.Client) func() { + return func() { + log.Info().Msg("updating tank averages cache") + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1) + defer cancel() + + // we just run the logic directly as it's not a heavy task and it doesn't matter if it fails + averages, err := client.Fetch().CurrentTankAverages(ctx) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + + err = client.Database().UpsertVehicleAverages(ctx, averages) + if err != nil { + log.Err(err).Msg("failed to update averages cache") + return + } + + log.Info().Msg("averages cache updated") + } +} + +func UpdateGlossaryWorker(client core.Client) func() { + return func() { + // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing + // log.Info().Msg("updating glossary cache") + // err := cache.UpdateGlossaryCache() + // if err != nil { + // log.Err(err).Msg("failed to update glossary cache") + // } else { + // log.Info().Msg("glossary cache updated") + // } + } +} diff --git a/cmds/core/scheduler/workers.go b/cmds/core/scheduler/workers.go new file mode 100644 index 00000000..d5037340 --- /dev/null +++ b/cmds/core/scheduler/workers.go @@ -0,0 +1,68 @@ +package scheduler + +import ( + "github.com/cufee/aftermath/cmds/core" +) + +func rotateBackgroundPresetsWorker(client core.Client) func() { + return func() { + // // We just run the logic directly as it's not a heavy task and it doesn't matter if it fails due to the app failing + // log.Info().Msg("rotating background presets") + // images, err := content.PickRandomBackgroundImages(3) + // if err != nil { + // log.Err(err).Msg("failed to pick random background images") + // return + // } + // err = database.UpdateAppConfiguration[[]string]("backgroundImagesSelection", images, nil, true) + // if err != nil { + // log.Err(err).Msg("failed to update background images selection") + // } + } +} + +func createSessionTasksWorker(client core.Client, realm string) func() { + return func() { + // err := tasks.CreateSessionUpdateTasks(realm) + // if err != nil { + // log.Err(err).Msg("failed to create session update tasks") + // } + } +} + +func runTasksWorker(client core.Client) func() { + return func() { + // if tasks.DefaultQueue.ActiveWorkers() > 0 { + // return + // } + + // activeTasks, err := tasks.StartScheduledTasks(nil, 50) + // if err != nil { + // log.Err(err).Msg("failed to start scheduled tasks") + // return + // } + // if len(activeTasks) == 0 { + // return + // } + + // tasks.DefaultQueue.Process(func(err error) { + // if err != nil { + // log.Err(err).Msg("failed to process tasks") + // return + // } + + // // If the queue is now empty, we can run the next batch of tasks right away + // runTasksWorker() + + // }, activeTasks...) + } +} + +func restartTasksWorker(client core.Client) func() { + return func() { + // _, err := tasks.RestartAbandonedTasks(nil) + // if err != nil { + // log.Err(err).Msg("failed to start scheduled tasks") + // return + // } + } +} diff --git a/cmds/discord/commands/link.go b/cmds/discord/commands/link.go index 7d735039..0eb0c5d2 100644 --- a/cmds/discord/commands/link.go +++ b/cmds/discord/commands/link.go @@ -41,7 +41,7 @@ func init() { return ctx.Reply(message) } - account, err := ctx.Core.Fetch.Search(ctx.Context, options.Nickname, options.Server) + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) @@ -59,7 +59,7 @@ func init() { currentConnection.Metadata["verified"] = false currentConnection.ReferenceID = fmt.Sprint(account.ID) - _, err = ctx.Core.DB.UpsertConnection(ctx.Context, currentConnection) + _, err = ctx.Core.Database().UpsertConnection(ctx.Context, currentConnection) if err != nil { return ctx.Err(err) } diff --git a/cmds/discord/commands/stats.go b/cmds/discord/commands/stats.go index 94b5f42d..9af35d25 100644 --- a/cmds/discord/commands/stats.go +++ b/cmds/discord/commands/stats.go @@ -30,7 +30,7 @@ func init() { switch { case options.UserID != "": // mentioned another user, check if the user has an account linked - mentionedUser, _ := ctx.Core.DB.GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) + mentionedUser, _ := ctx.Core.Database().GetUserByID(ctx.Context, options.UserID, database.WithConnections(), database.WithContent()) defaultAccount, hasDefaultAccount := mentionedUser.Connection(database.ConnectionTypeWargaming) if !hasDefaultAccount { return ctx.Reply("stats_error_connection_not_found_vague") @@ -40,7 +40,7 @@ func init() { case options.Nickname != "" && options.Server != "": // nickname provided and server selected - lookup the account - account, err := ctx.Core.Fetch.Search(ctx.Context, options.Nickname, options.Server) + account, err := ctx.Core.Fetch().Search(ctx.Context, options.Nickname, options.Server) if err != nil { if err.Error() == "no results found" { return ctx.ReplyFmt("stats_error_nickname_not_fount_fmt", options.Nickname, strings.ToUpper(options.Server)) diff --git a/cmds/discord/common/context.go b/cmds/discord/common/context.go index bdfa25fd..96018022 100644 --- a/cmds/discord/common/context.go +++ b/cmds/discord/common/context.go @@ -54,7 +54,7 @@ func NewContext(ctx context.Context, interaction discordgo.Interaction, respondC return nil, errors.New("failed to get a valid discord user id") } - user, err := client.DB.GetOrCreateUserByID(ctx, c.Member.ID, database.WithConnections(), database.WithSubscriptions()) + user, err := client.Database().GetOrCreateUserByID(ctx, c.Member.ID, database.WithConnections(), database.WithSubscriptions()) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index bdc7e794..e4c1c034 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/cufee/am-wg-proxy-next/v2 v2.1.2 github.com/disintegration/imaging v1.6.2 github.com/fogleman/gg v1.3.0 + github.com/go-co-op/gocron v1.37.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.33.0 @@ -22,11 +23,14 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 1a4a8bcb..fa0e39ca 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cufee/am-wg-proxy-next/v2 v2.1.2 h1:j45xbLPs5FqSsfEccls8AWXK1DIWjnIFEKoiB7M/TGY= github.com/cufee/am-wg-proxy-next/v2 v2.1.2/go.mod h1:+VxiIdbrdhHdRThASbVmZ5Fz4H6XPCzL3S2Ix6TO/84= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,24 +11,43 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= @@ -36,7 +56,12 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/steebchen/prisma-client-go v0.37.0 h1:CYfRxUnIsJRlCvPM4Yw2fElB7Y9rC4f2/PPmHliqyTc= github.com/steebchen/prisma-client-go v0.37.0/go.mod h1:wp2xU9HO5WIefc65vcl1HOiFUzaHKyOhHw5atrzs8hc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= @@ -48,6 +73,8 @@ go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRL go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11 h1:FTYVIEzY/bfl37lu3pR4lIj+F9Vp1jE8oh91VmxKgLo= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= @@ -73,7 +100,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/database/client.go b/internal/database/client.go index 575b6fd6..d4749a3f 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -8,8 +8,8 @@ import ( ) type Client interface { - SetVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) + UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) GetOrCreateUserByID(ctx context.Context, id string, opts ...userGetOption) (User, error) @@ -20,7 +20,7 @@ type Client interface { // var _ Client = &client{} // just a marker to see if it is implemented correctly type client struct { - prisma *db.PrismaClient + Raw *db.PrismaClient } func NewClient() (*client, error) { @@ -30,5 +30,5 @@ func NewClient() (*client, error) { return nil, err } - return &client{prisma: prisma}, nil + return &client{Raw: prisma}, nil } diff --git a/internal/database/external.go b/internal/database/external.go index f644c3f3..87117a4a 100644 --- a/internal/database/external.go +++ b/internal/database/external.go @@ -8,6 +8,7 @@ import ( "github.com/cufee/aftermath/internal/localization" "github.com/cufee/aftermath/internal/stats/frame" "github.com/rs/zerolog/log" + "github.com/steebchen/prisma-client-go/runtime/transaction" ) type GlossaryVehicle struct { @@ -18,12 +19,32 @@ func (v GlossaryVehicle) Name(printer localization.Printer) string { return printer("name") } -func (c *client) SetVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { +func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { if len(averages) < 1 { return nil } - return nil + var transactions []transaction.Transaction + for id, data := range averages { + encoded, err := data.Encode() + if err != nil { + log.Err(err).Str("id", id).Msg("failed to encode a stats frame for vehicle averages") + continue + } + + transactions = append(transactions, c.Raw.VehicleAverage. + UpsertOne(db.VehicleAverage.ID.Equals(id)). + Create( + db.VehicleAverage.ID.Set(id), + db.VehicleAverage.DataEncoded.Set(encoded), + ). + Update( + db.VehicleAverage.DataEncoded.Set(encoded), + ).Tx(), + ) + } + + return c.Raw.Prisma.Transaction(transactions...).Exec(ctx) } func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) { @@ -33,7 +54,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri qCtx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() - records, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) + records, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(ids)).Exec(qCtx) if err != nil { return nil, err } @@ -44,9 +65,9 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri for _, record := range records { parsed, err := frame.DecodeStatsFrame(record.DataEncoded) - if err != nil && !db.IsErrNotFound(err) { + lastErr = err + if err != nil { badRecords = append(badRecords, record.ID) - lastErr = err continue } averages[record.ID] = parsed @@ -61,7 +82,7 @@ func (c *client) GetVehicleAverages(ctx context.Context, ids []string) (map[stri // we can just delete the record and move on ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, err := c.prisma.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) + _, err := c.Raw.VehicleAverage.FindMany(db.VehicleAverage.ID.In(badRecords)).Delete().Exec(ctx) if err != nil { log.Err(err).Strs("ids", badRecords).Msg("failed to delete a bad vehicle average records") } diff --git a/internal/database/prisma/schema.prisma b/internal/database/prisma/schema.prisma index 1b54ac15..f4a0c95d 100644 --- a/internal/database/prisma/schema.prisma +++ b/internal/database/prisma/schema.prisma @@ -209,7 +209,7 @@ model Clan { } model VehicleAverage { - id String @id @default(uuid()) + id String @id createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/internal/database/users.go b/internal/database/users.go index c9458a51..eee8e046 100644 --- a/internal/database/users.go +++ b/internal/database/users.go @@ -176,7 +176,7 @@ func (c *client) GetOrCreateUserByID(ctx context.Context, id string, opts ...use user, err := c.GetUserByID(ctx, id, opts...) if err != nil { if db.IsErrNotFound(err) { - model, err := c.prisma.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) + model, err := c.Raw.User.CreateOne(db.User.ID.Set(id), db.User.Permissions.Set(permissions.User.Encode())).Exec(ctx) if err != nil { return User{}, err } @@ -210,7 +210,7 @@ func (c *client) GetUserByID(ctx context.Context, id string, opts ...userGetOpti fields = append(fields, db.User.Content.Fetch()) } - model, err := c.prisma.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) + model, err := c.Raw.User.FindUnique(db.User.ID.Equals(id)).With(fields...).Exec(ctx) if err != nil { return User{}, err } @@ -238,7 +238,7 @@ func (c *client) UpdateConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.prisma.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( + model, err := c.Raw.UserConnection.FindUnique(db.UserConnection.ID.Equals(connection.ID)).Update( db.UserConnection.ReferenceID.Set(connection.ReferenceID), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), db.UserConnection.MetadataEncoded.Set(string(encoded)), @@ -269,7 +269,7 @@ func (c *client) UpsertConnection(ctx context.Context, connection UserConnection return UserConnection{}, fmt.Errorf("failed to encode metadata: %w", err) } - model, err := c.prisma.UserConnection.CreateOne( + model, err := c.Raw.UserConnection.CreateOne( db.UserConnection.User.Link(db.User.ID.Equals(connection.UserID)), db.UserConnection.Type.Set(string(connection.Type)), db.UserConnection.Permissions.Set(connection.Permissions.Encode()), diff --git a/internal/external/blitzstars/averages.go b/internal/external/blitzstars/averages.go new file mode 100644 index 00000000..b7ddddb4 --- /dev/null +++ b/internal/external/blitzstars/averages.go @@ -0,0 +1,78 @@ +package blitzstars + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/cufee/aftermath/internal/stats/frame" +) + +// Response from https://www.blitzstars.com/ average stats endpoint +type VehicleAverages struct { + TankID int `json:"tank_id"` + Players int `json:"number_of_players"` + All struct { + AvgBattles float32 `json:"battles,omitempty"` + AvgDroppedCapturePoints float32 `json:"dropped_capture_points,omitempty"` + } `json:",omitempty"` + Special struct { + Winrate float32 `json:"winrate,omitempty"` + DamageRatio float32 `json:"damageRatio,omitempty"` + Kdr float32 `json:"kdr,omitempty"` + DamagePerBattle float32 `json:"damagePerBattle,omitempty"` + KillsPerBattle float32 `json:"killsPerBattle,omitempty"` + HitsPerBattle float32 `json:"hitsPerBattle,omitempty"` + SpotsPerBattle float32 `json:"spotsPerBattle,omitempty"` + Wpm float32 `json:"wpm,omitempty"` + Dpm float32 `json:"dpm,omitempty"` + Kpm float32 `json:"kpm,omitempty"` + HitRate float32 `json:"hitRate,omitempty"` + SurvivalRate float32 `json:"survivalRate,omitempty"` + } `json:"special,omitempty"` +} + +func (c *client) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { + req, err := http.NewRequest("GET", c.apiURL+"/tankaverages.json", nil) + if err != nil { + return nil, err + } + + res, err := c.http.Do(req.WithContext(ctx)) + if err != nil { + return nil, err + } + + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("bad status code: %d", res.StatusCode) + } + + var averages []VehicleAverages + err = json.NewDecoder(res.Body).Decode(&averages) + if err != nil { + return nil, err + } + + averagesMap := make(map[string]frame.StatsFrame) + for _, average := range averages { + battles := average.All.AvgBattles * float32(average.Players) + + averagesMap[fmt.Sprint(average.TankID)] = frame.StatsFrame{ + Battles: frame.ValueInt(battles), + BattlesWon: frame.ValueInt(average.Special.Winrate * battles / 100), + DamageDealt: frame.ValueInt(average.Special.DamagePerBattle * battles), + + ShotsHit: frame.ValueInt(average.Special.HitsPerBattle * battles), + ShotsFired: frame.ValueInt((average.Special.HitsPerBattle * battles) / (average.Special.HitRate / 100)), + + Frags: frame.ValueInt(average.Special.KillsPerBattle * battles), + EnemiesSpotted: frame.ValueInt(average.Special.SpotsPerBattle * battles), + DroppedCapturePoints: frame.ValueInt(average.All.AvgDroppedCapturePoints * battles), + } + + } + + return averagesMap, nil +} diff --git a/internal/external/blitzstars/client.go b/internal/external/blitzstars/client.go index 5c49b4e6..2015050e 100644 --- a/internal/external/blitzstars/client.go +++ b/internal/external/blitzstars/client.go @@ -4,9 +4,12 @@ import ( "context" "net/http" "time" + + "github.com/cufee/aftermath/internal/stats/frame" ) type Client interface { + CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) AccountTankHistories(ctx context.Context, accountId string) (map[int][]TankHistoryEntry, error) } @@ -18,10 +21,10 @@ type client struct { requestTimeout time.Duration } -func NewClient(apiURL string, requestTimeout time.Duration) *client { +func NewClient(apiURL string, requestTimeout time.Duration) (*client, error) { return &client{ apiURL: apiURL, requestTimeout: requestTimeout, http: http.Client{Timeout: requestTimeout}, - } + }, nil } diff --git a/internal/stats/fetch/client.go b/internal/stats/fetch/client.go index 18c82a8f..8c273edb 100644 --- a/internal/stats/fetch/client.go +++ b/internal/stats/fetch/client.go @@ -23,6 +23,24 @@ type AccountStatsOverPeriod struct { LastBattleTime time.Time `json:"lastBattleTime"` } +func (stats *AccountStatsOverPeriod) AddWN8(averages map[string]frame.StatsFrame) { + var weightedTotal, battlesTotal float32 + for id, data := range stats.RegularBattles.Vehicles { + tankAverages, ok := averages[id] + if !ok || data.Battles < 1 { + continue + } + weightedTotal += data.Battles.Float() * data.WN8(tankAverages).Float() + battlesTotal += data.Battles.Float() + } + if battlesTotal < 1 { + return + } + + wn8 := int(weightedTotal) / int(battlesTotal) + stats.RegularBattles.SetWN8(wn8) +} + type StatsWithVehicles struct { frame.StatsFrame Vehicles map[string]frame.VehicleStatsFrame @@ -30,6 +48,20 @@ type StatsWithVehicles struct { type Client interface { Search(ctx context.Context, nickname, realm string) (types.Account, error) - CurrentStats(ctx context.Context, id string) (AccountStatsOverPeriod, error) - PeriodStats(ctx context.Context, id string, from time.Time) (AccountStatsOverPeriod, error) + CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) + PeriodStats(ctx context.Context, id string, from time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) + + CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) +} + +type statsOptions struct { + withWN8 bool +} + +type statsOption func(*statsOptions) + +func WithWN8() statsOption { + return func(so *statsOptions) { + so.withWN8 = true + } } diff --git a/internal/stats/fetch/multisource.go b/internal/stats/fetch/multisource.go index 7a48dcf1..134c933f 100644 --- a/internal/stats/fetch/multisource.go +++ b/internal/stats/fetch/multisource.go @@ -3,12 +3,14 @@ package fetch import ( "context" "errors" + "fmt" "sync" "time" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" "github.com/cufee/aftermath/internal/external/wargaming" + "github.com/cufee/aftermath/internal/stats/frame" "github.com/cufee/am-wg-proxy-next/v2/types" ) @@ -51,7 +53,12 @@ Get live account stats from wargaming - Each request will be retried c.retriesPerRequest times - This function will assume the player ID is valid and optimistically run all request concurrently, returning the first error once all request finish */ -func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts ...statsOption) (AccountStatsOverPeriod, error) { + var options statsOptions + for _, apply := range opts { + apply(&options) + } + realm := c.wargaming.RealmFromAccountID(id) var group sync.WaitGroup @@ -60,6 +67,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun var clan types.ClanMember var account DataWithErr[types.ExtendedAccount] var vehicles DataWithErr[[]types.VehicleStatsFrame] + var averages DataWithErr[map[string]frame.StatsFrame] go func() { defer group.Done() @@ -79,6 +87,17 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun vehicles = withRetry(func() ([]types.VehicleStatsFrame, error) { return c.wargaming.AccountVehicles(ctx, realm, id) }, c.retriesPerRequest, c.retrySleepInterval) + + if vehicles.Err != nil || len(vehicles.Data) < 1 || !options.withWN8 { + return + } + + var ids []string + for _, v := range vehicles.Data { + ids = append(ids, fmt.Sprint(v.TankID)) + } + a, err := c.database.GetVehicleAverages(ctx, ids) + averages = DataWithErr[map[string]frame.StatsFrame]{a, err} }() group.Wait() @@ -90,11 +109,22 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string) (Accoun return AccountStatsOverPeriod{}, vehicles.Err } - return wargamingToStats(realm, account.Data, clan, vehicles.Data), nil + stats := wargamingToStats(realm, account.Data, clan, vehicles.Data) + if options.withWN8 { + stats.AddWN8(averages.Data) + } + + return stats, nil } -func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time) (AccountStatsOverPeriod, error) { +func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodStart time.Time, opts ...statsOption) (AccountStatsOverPeriod, error) { + var options statsOptions + for _, apply := range opts { + apply(&options) + } + var histories DataWithErr[map[int][]blitzstars.TankHistoryEntry] + var averages DataWithErr[map[string]frame.StatsFrame] var current DataWithErr[AccountStatsOverPeriod] var group sync.WaitGroup @@ -104,6 +134,17 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt stats, err := c.CurrentStats(ctx, id) current = DataWithErr[AccountStatsOverPeriod]{stats, err} + + if err != nil || stats.RegularBattles.Battles < 1 || !options.withWN8 { + return + } + + var ids []string + for id := range stats.RegularBattles.Vehicles { + ids = append(ids, id) + } + a, err := c.database.GetVehicleAverages(ctx, ids) + averages = DataWithErr[map[string]frame.StatsFrame]{a, err} }() // TODO: lookup a session from the database first @@ -116,7 +157,11 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt return AccountStatsOverPeriod{}, current.Err } - return current.Data, nil + stats := current.Data + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil } group.Add(1) @@ -139,5 +184,14 @@ func (c *multiSourceClient) PeriodStats(ctx context.Context, id string, periodSt current.Data.PeriodStart = periodStart current.Data.RatingBattles = StatsWithVehicles{} // blitzstars do not provide rating battles stats current.Data.RegularBattles = blitzstarsToStats(current.Data.RegularBattles.Vehicles, histories.Data, periodStart) - return current.Data, nil + + stats := current.Data + if options.withWN8 { + stats.AddWN8(averages.Data) + } + return stats, nil +} + +func (c *multiSourceClient) CurrentTankAverages(ctx context.Context) (map[string]frame.StatsFrame, error) { + return c.blitzstars.CurrentTankAverages(ctx) } diff --git a/internal/stats/frame/frame.go b/internal/stats/frame/frame.go index 2a64c61c..570f97bd 100644 --- a/internal/stats/frame/frame.go +++ b/internal/stats/frame/frame.go @@ -1,10 +1,9 @@ package frame import ( + "encoding/json" "math" "time" - - "go.dedis.ch/protobuf" ) /* @@ -42,7 +41,7 @@ type StatsFrame struct { func DecodeStatsFrame(encoded string) (StatsFrame, error) { var data StatsFrame - err := protobuf.Decode([]byte(encoded), &data) + err := json.Unmarshal([]byte(encoded), &data) if err != nil { return StatsFrame{}, err } @@ -53,7 +52,7 @@ func DecodeStatsFrame(encoded string) (StatsFrame, error) { Encode StatsFrame to string */ func (r *StatsFrame) Encode() (string, error) { - data, err := protobuf.Encode(r) + data, err := json.Marshal(r) if err != nil { return "", err } @@ -63,7 +62,7 @@ func (r *StatsFrame) Encode() (string, error) { /* Calculate and return average damage per battle */ -func (r *StatsFrame) AvgDamage() Value { +func (r *StatsFrame) AvgDamage(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -76,7 +75,7 @@ func (r *StatsFrame) AvgDamage() Value { /* Calculate and return damage dealt vs damage received ration */ -func (r *StatsFrame) DamageRatio() Value { +func (r *StatsFrame) DamageRatio(_ ...any) Value { if r.Battles == 0 || r.DamageReceived == 0 { return InvalidValue } @@ -89,7 +88,7 @@ func (r *StatsFrame) DamageRatio() Value { /* Calculate and return survival battles vs total battles ratio */ -func (r *StatsFrame) SurvivalRatio() Value { +func (r *StatsFrame) SurvivalRatio(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -102,7 +101,7 @@ func (r *StatsFrame) SurvivalRatio() Value { /* Calculate and return survival percentage */ -func (r *StatsFrame) Survival() Value { +func (r *StatsFrame) Survival(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -115,7 +114,7 @@ func (r *StatsFrame) Survival() Value { /* Calculate and return win percentage */ -func (r *StatsFrame) WinRate() Value { +func (r *StatsFrame) WinRate(_ ...any) Value { if r.Battles == 0 { return InvalidValue } @@ -128,7 +127,7 @@ func (r *StatsFrame) WinRate() Value { /* Calculate and return accuracy */ -func (r *StatsFrame) Accuracy() Value { +func (r *StatsFrame) Accuracy(_ ...any) Value { if r.Battles == 0 || r.ShotsFired == 0 { return InvalidValue } @@ -149,17 +148,17 @@ func (r *StatsFrame) SetWN8(wn8 int) { Calculate WN8 Rating for a tank using the following formula: (980*rDAMAGEc + 210*rDAMAGEc*rFRAGc + 155*rFRAGc*rSPOTc + 75*rDEFc*rFRAGc + 145*MIN(1.8,rWINc))/EXPc */ -func (r *StatsFrame) WN8(vehicleAverages ...StatsFrame) Value { +func (r *StatsFrame) WN8(averages ...any) Value { if r.wn8 > 0 { return r.wn8 } - if r.Battles == 0 || len(vehicleAverages) < 1 { + if r.Battles == 0 || len(averages) < 1 { return InvalidValue } - average := vehicleAverages[0] - if average.Battles == 0 { + average, ok := averages[0].(StatsFrame) + if !ok || average.Battles == 0 { return InvalidValue } diff --git a/internal/stats/frame/value.go b/internal/stats/frame/value.go index 86a7b9ee..6b15afbe 100644 --- a/internal/stats/frame/value.go +++ b/internal/stats/frame/value.go @@ -2,7 +2,7 @@ package frame import "fmt" -type ValueInt uint32 +type ValueInt int func (value ValueInt) String() string { return fmt.Sprintf("%d", value) diff --git a/internal/stats/period.go b/internal/stats/period.go index c36e3b99..3e61b421 100644 --- a/internal/stats/period.go +++ b/internal/stats/period.go @@ -26,7 +26,7 @@ func (r *renderer) Period(ctx context.Context, accountId string, from time.Time, } stop := meta.Timer("fetchClient#PeriodStats") - stats, err := r.fetchClient.PeriodStats(ctx, accountId, from) + stats, err := r.fetchClient.PeriodStats(ctx, accountId, from, fetch.WithWN8()) stop() if err != nil { return nil, meta, err diff --git a/internal/stats/prepare/common/card.go b/internal/stats/prepare/common/card.go index f3af6977..56e370ce 100644 --- a/internal/stats/prepare/common/card.go +++ b/internal/stats/prepare/common/card.go @@ -38,28 +38,28 @@ func (block *StatsBlock[D]) Localize(printer func(string) string) { block.Label = printer(block.Label) } -func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame) error { +func (block *StatsBlock[D]) FillValue(stats frame.StatsFrame, args ...any) error { switch block.Tag { case TagWN8: - block.Value = stats.WN8() + block.Value = stats.WN8(args...) case TagFrags: block.Value = stats.Frags case TagBattles: block.Value = stats.Battles case TagWinrate: - block.Value = stats.WinRate() + block.Value = stats.WinRate(args...) case TagAccuracy: - block.Value = stats.Accuracy() + block.Value = stats.Accuracy(args...) case TagRankedRating: block.Value = stats.Rating case TagAvgDamage: - block.Value = stats.AvgDamage() + block.Value = stats.AvgDamage(args...) case TagDamageRatio: - block.Value = stats.DamageRatio() + block.Value = stats.DamageRatio(args...) case TagSurvivalRatio: - block.Value = stats.SurvivalRatio() + block.Value = stats.SurvivalRatio(args...) case TagSurvivalPercent: - block.Value = stats.Survival() + block.Value = stats.Survival(args...) case TagDamageDealt: block.Value = stats.DamageDealt case TagDamageTaken: diff --git a/internal/stats/prepare/period/preset.go b/internal/stats/prepare/period/preset.go index 253e03b6..781aaaec 100644 --- a/internal/stats/prepare/period/preset.go +++ b/internal/stats/prepare/period/preset.go @@ -7,8 +7,8 @@ import ( func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[BlockData] { block := common.StatsBlock[BlockData](common.NewBlock(preset, BlockData{})) - block.FillValue(stats) + var args []any switch preset { case common.TagWN8: block.Data.Flavor = BlockFlavorSpecial @@ -32,5 +32,6 @@ func presetToBlock(preset common.Tag, stats frame.StatsFrame) common.StatsBlock[ block.Data.Flavor = BlockFlavorDefault } + block.FillValue(stats, args...) return block } diff --git a/main.go b/main.go index c3312e0b..2f8f7850 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "time" "github.com/cufee/aftermath/cmds/core" + "github.com/cufee/aftermath/cmds/core/scheduler" "github.com/cufee/aftermath/cmds/discord" "github.com/cufee/aftermath/internal/database" "github.com/cufee/aftermath/internal/external/blitzstars" @@ -35,11 +36,9 @@ func main() { loadStaticAssets(static) - dbClient, err := database.NewClient() - if err != nil { - panic(err) - } - coreClient := core.NewClient(fetchClientFromEnv(), dbClient) + coreClient := coreClientFromEnv() + + scheduler.UpdateAveragesWorker(coreClient)() discordHandler, err := discord.NewRouterHandler(coreClient, os.Getenv("DISCORD_TOKEN"), os.Getenv("DISCORD_PUBLIC_KEY")) if err != nil { @@ -50,24 +49,7 @@ func main() { panic(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) } -func loadStaticAssets(static fs.FS) { - // Assets for rendering - err := assets.LoadAssets(static, "static") - if err != nil { - log.Fatalf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) - } - err = render.InitLoadedAssets() - if err != nil { - log.Fatalf("render#InitLoadedAssets failed %s", err) - } - err = localization.LoadAssets(static, "static/localization") - if err != nil { - log.Fatalf("localization#LoadAssets failed %s", err) - } - -} - -func fetchClientFromEnv() fetch.Client { +func coreClientFromEnv() core.Client { // Dependencies wgClient, err := wargaming.NewClientFromEnv(os.Getenv("WG_PRIMARY_APP_ID"), os.Getenv("WG_PRIMARY_APP_RPS"), os.Getenv("WG_REQUEST_TIMEOUT_SEC"), os.Getenv("WG_PROXY_HOST_LIST")) if err != nil { @@ -77,7 +59,10 @@ func fetchClientFromEnv() fetch.Client { if err != nil { log.Fatalf("database#NewClient failed %s", err) } - bsClient := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*3) + bsClient, err := blitzstars.NewClient(os.Getenv("BLITZ_STARS_API_URL"), time.Second*10) + if err != nil { + log.Fatalf("failed to init a blitzstars client %s", err) + } // Fetch client client, err := fetch.NewMultiSourceClient(wgClient, bsClient, dbClient) @@ -85,5 +70,22 @@ func fetchClientFromEnv() fetch.Client { log.Fatalf("fetch#NewMultiSourceClient failed %s", err) } - return client + return core.NewClient(client, dbClient) +} + +func loadStaticAssets(static fs.FS) { + // Assets for rendering + err := assets.LoadAssets(static, "static") + if err != nil { + log.Fatalf("assets#LoadAssets failed to load assets from static/ embed.FS %s", err) + } + err = render.InitLoadedAssets() + if err != nil { + log.Fatalf("render#InitLoadedAssets failed %s", err) + } + err = localization.LoadAssets(static, "static/localization") + if err != nil { + log.Fatalf("localization#LoadAssets failed %s", err) + } + }