feat: add more stats to the Server model (#47)
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

Reviewed-on: #47
This commit is contained in:
Dawid Wysokiński 2024-05-07 06:15:37 +00:00
parent 4d6c1c8982
commit f8c9bdb321
32 changed files with 1000 additions and 14 deletions

View File

@ -583,8 +583,12 @@ components:
- key
- open
- url
- numPlayers
- numActivePlayers
- numInactivePlayers
- numTribes
- numActiveTribes
- numInactiveTribes
- numVillages
- numBarbarianVillages
- numBonusVillages
@ -599,13 +603,23 @@ components:
type: string
format: uri
example: https://en138.tribalwars.net
numPlayers:
type: integer
description: numActivePlayers+numInactivePlayers
numActivePlayers:
type: integer
numInactivePlayers:
type: integer
playerDataSyncedAt:
type: string
format: date-time
numTribes:
type: integer
description: numActiveTribes+numInactiveTribes
numActiveTribes:
type: integer
numInactiveTribes:
type: integer
tribeDataSyncedAt:
type: string
format: date-time

View File

@ -38,6 +38,7 @@ func (pub *PlayerWatermillPublisher) EventSynced(
ServerKey: p.ServerKey(),
VersionCode: p.VersionCode(),
ServerURL: p.ServerURL(),
NumPlayers: p.NumPlayers(),
NumActivePlayers: p.NumActivePlayers(),
})
if err != nil {

View File

@ -38,6 +38,7 @@ func (pub *TribeWatermillPublisher) EventSynced(
ServerKey: p.ServerKey(),
VersionCode: p.VersionCode(),
ServerURL: p.ServerURL(),
NumTribes: p.NumTribes(),
NumActiveTribes: p.NumActiveTribes(),
})
if err != nil {

View File

@ -131,6 +131,17 @@ func (repo *PlayerBunRepository) ListWithRelations(
return domain.NewListPlayersWithRelationsResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *PlayerBunRepository) Count(ctx context.Context, params domain.CountPlayersParams) (int, error) {
cnt, err := repo.db.NewSelect().
Model((*bunmodel.Player)(nil)).
Apply(countPlayersParamsApplier{params: params}.apply).
Count(ctx)
if err != nil {
return 0, fmt.Errorf("couldnt count players: %w", err)
}
return cnt, nil
}
func (repo *PlayerBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
if len(ids) == 0 {
return nil
@ -323,3 +334,15 @@ func (a listPlayersParamsApplier) sortToColumnAndDirection(
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
}
}
type countPlayersParamsApplier struct {
params domain.CountPlayersParams
}
func (a countPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("player.server_key IN (?)", bun.In(serverKeys))
}
return q
}

View File

@ -126,18 +126,34 @@ func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
q = q.Set("building_info = ?", bunmodel.NewBuildingInfo(buildingInfo.V))
}
if numTribes := a.params.NumTribes(); numTribes.Valid {
q = q.Set("num_tribes = ?", numTribes.V)
}
if numActiveTribes := a.params.NumActiveTribes(); numActiveTribes.Valid {
q = q.Set("num_active_tribes = ?", numActiveTribes.V)
}
if numInactiveTribes := a.params.NumInactiveTribes(); numInactiveTribes.Valid {
q = q.Set("num_inactive_tribes = ?", numInactiveTribes.V)
}
if tribeDataSyncedAt := a.params.TribeDataSyncedAt(); tribeDataSyncedAt.Valid {
q = q.Set("tribe_data_synced_at = ?", tribeDataSyncedAt.V)
}
if numPlayers := a.params.NumPlayers(); numPlayers.Valid {
q = q.Set("num_players = ?", numPlayers.V)
}
if numActivePlayers := a.params.NumActivePlayers(); numActivePlayers.Valid {
q = q.Set("num_active_players = ?", numActivePlayers.V)
}
if numInactivePlayers := a.params.NumInactivePlayers(); numInactivePlayers.Valid {
q = q.Set("num_inactive_players = ?", numInactivePlayers.V)
}
if playerDataSyncedAt := a.params.PlayerDataSyncedAt(); playerDataSyncedAt.Valid {
q = q.Set("player_data_synced_at = ?", playerDataSyncedAt.V)
}

View File

@ -132,6 +132,17 @@ func (repo *TribeBunRepository) List(
return domain.NewListTribesResult(separateListResultAndNext(converted, params.Limit()))
}
func (repo *TribeBunRepository) Count(ctx context.Context, params domain.CountTribesParams) (int, error) {
cnt, err := repo.db.NewSelect().
Model((*bunmodel.Tribe)(nil)).
Apply(countTribesParamsApplier{params: params}.apply).
Count(ctx)
if err != nil {
return 0, fmt.Errorf("couldnt count tribes: %w", err)
}
return cnt, nil
}
func (repo *TribeBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error {
if len(ids) == 0 {
return nil
@ -287,3 +298,15 @@ func (a listTribesParamsApplier) sortToColumnAndDirection(
return "", 0, fmt.Errorf("%s: %w", s.String(), errInvalidSortValue)
}
}
type countTribesParamsApplier struct {
params domain.CountTribesParams
}
func (a countTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("tribe.server_key IN (?)", bun.In(serverKeys))
}
return q
}

View File

@ -664,6 +664,78 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories
}
})
t.Run("Count", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
tests := []struct {
name string
params func(t *testing.T) domain.CountPlayersParams
assertResult func(t *testing.T, params domain.CountPlayersParams, count int)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.CountPlayersParams {
t.Helper()
return domain.NewCountPlayersParams()
},
assertResult: func(t *testing.T, _ domain.CountPlayersParams, count int) {
t.Helper()
res, err := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, err)
assert.Len(t, res.Players(), count)
},
},
{
name: "OK: serverKeys",
params: func(t *testing.T) domain.CountPlayersParams {
t.Helper()
res, err := repos.player.List(ctx, domain.NewListPlayersParams())
require.NoError(t, err)
params := domain.NewCountPlayersParams()
require.NoError(t, params.SetServerKeys([]string{res.Players()[0].ServerKey()}))
return params
},
assertResult: func(t *testing.T, params domain.CountPlayersParams, count int) {
t.Helper()
listParams := domain.NewListPlayersParams()
require.NoError(t, listParams.SetServerKeys(params.ServerKeys()))
res, err := repos.player.List(ctx, listParams)
require.NoError(t, err)
assert.Len(t, res.Players(), count)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assertError := tt.assertError
if assertError == nil {
assertError = func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
}
}
params := tt.params(t)
cnt, err := repos.player.Count(ctx, params)
assertError(t, err)
tt.assertResult(t, params, cnt)
})
}
})
t.Run("Delete", func(t *testing.T) {
t.Parallel()

View File

@ -494,18 +494,34 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
V: domaintest.NewBuildingInfo(t),
Valid: true,
}))
require.NoError(t, updateParams.SetNumTribes(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumActiveTribes(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumInactiveTribes(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetTribeDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
}))
require.NoError(t, updateParams.SetNumPlayers(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumActivePlayers(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetNumInactivePlayers(domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetPlayerDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -556,14 +572,18 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
assert.Equal(t, updateParams.Config().V, serverAfterUpdate.Config())
assert.Equal(t, updateParams.UnitInfo().V, serverAfterUpdate.UnitInfo())
assert.Equal(t, updateParams.BuildingInfo().V, serverAfterUpdate.BuildingInfo())
assert.Equal(t, updateParams.NumTribes().V, serverAfterUpdate.NumTribes())
assert.Equal(t, updateParams.NumActiveTribes().V, serverAfterUpdate.NumActiveTribes())
assert.Equal(t, updateParams.NumInactiveTribes().V, serverAfterUpdate.NumInactiveTribes())
assert.WithinDuration(
t,
updateParams.TribeDataSyncedAt().V,
serverAfterUpdate.TribeDataSyncedAt(),
time.Minute,
)
assert.Equal(t, updateParams.NumPlayers().V, serverAfterUpdate.NumPlayers())
assert.Equal(t, updateParams.NumActivePlayers().V, serverAfterUpdate.NumActivePlayers())
assert.Equal(t, updateParams.NumInactivePlayers().V, serverAfterUpdate.NumInactivePlayers())
assert.WithinDuration(
t,
updateParams.PlayerDataSyncedAt().V,

View File

@ -26,6 +26,7 @@ type tribeRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error
List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error)
Count(ctx context.Context, params domain.CountTribesParams) (int, error)
Delete(ctx context.Context, serverKey string, ids ...int) error
}
@ -33,6 +34,7 @@ type playerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error)
Count(ctx context.Context, params domain.CountPlayersParams) (int, error)
Delete(ctx context.Context, serverKey string, ids ...int) error
}

View File

@ -718,6 +718,78 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
}
})
t.Run("Count", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
tests := []struct {
name string
params func(t *testing.T) domain.CountTribesParams
assertResult func(t *testing.T, params domain.CountTribesParams, count int)
assertError func(t *testing.T, err error)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.CountTribesParams {
t.Helper()
return domain.NewCountTribesParams()
},
assertResult: func(t *testing.T, _ domain.CountTribesParams, count int) {
t.Helper()
res, err := repos.tribe.List(ctx, domain.NewListTribesParams())
require.NoError(t, err)
assert.Len(t, res.Tribes(), count)
},
},
{
name: "OK: serverKeys",
params: func(t *testing.T) domain.CountTribesParams {
t.Helper()
res, err := repos.tribe.List(ctx, domain.NewListTribesParams())
require.NoError(t, err)
params := domain.NewCountTribesParams()
require.NoError(t, params.SetServerKeys([]string{res.Tribes()[0].ServerKey()}))
return params
},
assertResult: func(t *testing.T, params domain.CountTribesParams, count int) {
t.Helper()
listParams := domain.NewListTribesParams()
require.NoError(t, listParams.SetServerKeys(params.ServerKeys()))
res, err := repos.tribe.List(ctx, listParams)
require.NoError(t, err)
assert.Len(t, res.Tribes(), count)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assertError := tt.assertError
if assertError == nil {
assertError = func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
}
}
params := tt.params(t)
cnt, err := repos.tribe.Count(ctx, params)
assertError(t, err)
tt.assertResult(t, params, cnt)
})
}
})
t.Run("Delete", func(t *testing.T) {
t.Parallel()

View File

@ -11,6 +11,7 @@ type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error)
Count(ctx context.Context, params domain.CountPlayersParams) (int, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
// In addition, Delete sets TribeID to null.
//
@ -51,10 +52,21 @@ func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.S
return fmt.Errorf("%s: couldn't delete players: %w", serverKey, err)
}
countParams := domain.NewCountPlayersParams()
if err = countParams.SetServerKeys([]string{serverKey}); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
numPlayers, err := svc.repo.Count(ctx, countParams)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
payload, err := domain.NewPlayersSyncedEventPayload(
serverKey,
serverURL,
serverSyncedPayload.VersionCode(),
numPlayers,
len(players),
)
if err != nil {
@ -101,7 +113,7 @@ func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey str
if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
if err := listParams.SetLimit(len(ids)); err != nil {
return err
}

View File

@ -183,12 +183,24 @@ func (svc *ServerService) UpdateNumTribes(ctx context.Context, payload domain.Tr
key := payload.ServerKey()
var updateParams domain.UpdateServerParams
if err := updateParams.SetNumTribes(domain.NullInt{
V: payload.NumTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumActiveTribes(domain.NullInt{
V: payload.NumActiveTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumInactiveTribes(domain.NullInt{
V: payload.NumInactiveTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetTribeDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,
@ -203,12 +215,24 @@ func (svc *ServerService) UpdateNumPlayers(ctx context.Context, payload domain.P
key := payload.ServerKey()
var updateParams domain.UpdateServerParams
if err := updateParams.SetNumPlayers(domain.NullInt{
V: payload.NumPlayers(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumActivePlayers(domain.NullInt{
V: payload.NumActivePlayers(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetNumInactivePlayers(domain.NullInt{
V: payload.NumInactivePlayers(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetPlayerDataSyncedAt(domain.NullTime{
V: time.Now(),
Valid: true,

View File

@ -11,6 +11,7 @@ type TribeRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error
List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error)
Count(ctx context.Context, params domain.CountTribesParams) (int, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
//
// https://en.wiktionary.org/wiki/soft_deletion
@ -44,10 +45,21 @@ func (svc *TribeService) Sync(ctx context.Context, serverSyncedPayload domain.Se
return fmt.Errorf("%s: couldn't delete tribes: %w", serverKey, err)
}
countParams := domain.NewCountTribesParams()
if err = countParams.SetServerKeys([]string{serverKey}); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
numTribes, err := svc.repo.Count(ctx, countParams)
if err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
payload, err := domain.NewTribesSyncedEventPayload(
serverKey,
serverURL,
serverSyncedPayload.VersionCode(),
numTribes,
len(tribes),
)
if err != nil {
@ -94,7 +106,7 @@ func (svc *TribeService) createOrUpdateChunk(ctx context.Context, serverKey stri
if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
if err := listParams.SetLimit(len(ids)); err != nil {
return err
}

View File

@ -16,8 +16,12 @@ type Server struct {
URL string `bun:"url,nullzero"`
Open bool `bun:"open"`
Special bool `bun:"special"`
NumPlayers int `bun:"num_players"`
NumActivePlayers int `bun:"num_active_players"`
NumInactivePlayers int `bun:"num_inactive_players"`
NumTribes int `bun:"num_tribes"`
NumActiveTribes int `bun:"num_active_tribes"`
NumInactiveTribes int `bun:"num_inactive_tribes"`
NumVillages int `bun:"num_villages"`
NumPlayerVillages int `bun:"num_player_villages"`
NumBarbarianVillages int `bun:"num_barbarian_villages"`
@ -57,8 +61,12 @@ func (s Server) ToDomain() (domain.Server, error) {
s.URL,
s.Open,
s.Special,
s.NumPlayers,
s.NumActivePlayers,
s.NumInactivePlayers,
s.NumTribes,
s.NumActiveTribes,
s.NumInactiveTribes,
s.NumVillages,
s.NumPlayerVillages,
s.NumBarbarianVillages,
@ -84,7 +92,7 @@ func (s Server) ToDomain() (domain.Server, error) {
func (s Server) ToMeta() (domain.ServerMeta, error) {
converted, err := domain.UnmarshalServerMetaFromDatabase(s.Key, s.URL, s.Open)
if err != nil {
return domain.ServerMeta{}, fmt.Errorf("couldn't construct domain.Server (key=%s): %w", s.Key, err)
return domain.ServerMeta{}, fmt.Errorf("couldn't construct domain.ServerMeta (key=%s): %w", s.Key, err)
}
return converted, nil
}

View File

@ -0,0 +1,44 @@
package migrations
import (
"context"
"database/sql"
"fmt"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for _, column := range getColumnsToAdd20240506051128() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers ADD ? bigint DEFAULT 0", bun.Safe(column))
if err != nil {
return fmt.Errorf("%s: %w", column, err)
}
}
return nil
})
}, func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
for _, column := range getColumnsToAdd20240506051128() {
_, err := tx.ExecContext(ctx, "ALTER TABLE servers DROP COLUMN ?", bun.Safe(column))
if err != nil {
return fmt.Errorf("%s: %w", column, err)
}
}
return nil
})
})
}
func getColumnsToAdd20240506051128() []string {
return []string{
"num_players",
"num_inactive_players",
"num_tribes",
"num_inactive_tribes",
}
}

View File

@ -0,0 +1,48 @@
package migrations
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/core/internal/bun/bunmodel"
"github.com/uptrace/bun"
)
func init() {
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
var servers bunmodel.Servers
if err := db.NewSelect().Model(&servers).Where("special = false").Scan(ctx); err != nil {
return fmt.Errorf("couldn't select servers from the db: %w", err)
}
for _, s := range servers {
numPlayers, err := db.NewSelect().Model((*bunmodel.Player)(nil)).Where("server_key = ?", s.Key).Count(ctx)
if err != nil {
return fmt.Errorf("%s: numPlayers: %w", s.Key, err)
}
numTribes, err := db.NewSelect().Model((*bunmodel.Tribe)(nil)).Where("server_key = ?", s.Key).Count(ctx)
if err != nil {
return fmt.Errorf("%s: numTribes: %w", s.Key, err)
}
_, err = db.NewUpdate().
Model((*bunmodel.Server)(nil)).
Returning("NULL").
Where("key = ?", s.Key).
Set("num_players = ?", numPlayers).
Set("num_inactive_players = ?", numPlayers-s.NumActivePlayers).
Set("num_tribes = ?", numTribes).
Set("num_inactive_tribes = ?", numTribes-s.NumActiveTribes).
Exec(ctx)
if err != nil {
return fmt.Errorf("%s: %w", s.Key, err)
}
}
return nil
}, func(_ context.Context, _ *bun.DB) error {
return nil
})
}

View File

@ -89,6 +89,10 @@ func NewServer(tb TestingTB, opts ...func(cfg *ServerConfig)) domain.Server {
0,
0,
0,
0,
0,
0,
0,
NewServerConfig(tb),
NewBuildingInfo(tb),
NewUnitInfo(tb),

View File

@ -112,6 +112,7 @@ type TribesSyncedEventPayload struct {
serverKey string
serverURL *url.URL
versionCode string
numTribes int
numActiveTribes int
}
@ -121,6 +122,7 @@ func NewTribesSyncedEventPayload(
serverKey string,
serverURL *url.URL,
versionCode string,
numTribes int,
numActiveTribes int,
) (TribesSyncedEventPayload, error) {
if serverKey == "" {
@ -147,6 +149,14 @@ func NewTribesSyncedEventPayload(
}
}
if err := validateIntInRange(numTribes, 0, math.MaxInt); err != nil {
return TribesSyncedEventPayload{}, ValidationError{
Model: tribesSyncedEventPayloadModelName,
Field: "numTribes",
Err: err,
}
}
if err := validateIntInRange(numActiveTribes, 0, math.MaxInt); err != nil {
return TribesSyncedEventPayload{}, ValidationError{
Model: tribesSyncedEventPayloadModelName,
@ -159,6 +169,7 @@ func NewTribesSyncedEventPayload(
serverKey: serverKey,
serverURL: serverURL,
versionCode: versionCode,
numTribes: numTribes,
numActiveTribes: numActiveTribes,
}, nil
}
@ -175,14 +186,23 @@ func (p TribesSyncedEventPayload) VersionCode() string {
return p.versionCode
}
func (p TribesSyncedEventPayload) NumTribes() int {
return p.numTribes
}
func (p TribesSyncedEventPayload) NumActiveTribes() int {
return p.numActiveTribes
}
func (p TribesSyncedEventPayload) NumInactiveTribes() int {
return p.numTribes - p.numActiveTribes
}
type PlayersSyncedEventPayload struct {
serverKey string
serverURL *url.URL
versionCode string
numPlayers int
numActivePlayers int
}
@ -192,6 +212,7 @@ func NewPlayersSyncedEventPayload(
serverKey string,
serverURL *url.URL,
versionCode string,
numPlayers int,
numActivePlayers int,
) (PlayersSyncedEventPayload, error) {
if serverKey == "" {
@ -218,6 +239,14 @@ func NewPlayersSyncedEventPayload(
}
}
if err := validateIntInRange(numPlayers, 0, math.MaxInt); err != nil {
return PlayersSyncedEventPayload{}, ValidationError{
Model: playersSyncedEventPayloadModelName,
Field: "numPlayers",
Err: err,
}
}
if err := validateIntInRange(numActivePlayers, 0, math.MaxInt); err != nil {
return PlayersSyncedEventPayload{}, ValidationError{
Model: playersSyncedEventPayloadModelName,
@ -230,6 +259,7 @@ func NewPlayersSyncedEventPayload(
serverKey: serverKey,
serverURL: serverURL,
versionCode: versionCode,
numPlayers: numPlayers,
numActivePlayers: numActivePlayers,
}, nil
}
@ -246,10 +276,18 @@ func (p PlayersSyncedEventPayload) VersionCode() string {
return p.versionCode
}
func (p PlayersSyncedEventPayload) NumPlayers() int {
return p.numPlayers
}
func (p PlayersSyncedEventPayload) NumActivePlayers() int {
return p.numActivePlayers
}
func (p PlayersSyncedEventPayload) NumInactivePlayers() int {
return p.numPlayers - p.numActivePlayers
}
type VillagesSyncedEventPayload struct {
serverKey string
serverURL *url.URL

View File

@ -58,38 +58,46 @@ func TestNewTribesSyncedEventPayload(t *testing.T) {
t.Parallel()
server := domaintest.NewServer(t)
numTribes := gofakeit.IntRange(0, math.MaxInt)
numActiveTribes := gofakeit.IntRange(0, math.MaxInt)
payload, err := domain.NewTribesSyncedEventPayload(
server.Key(),
server.URL(),
server.VersionCode(),
numTribes,
numActiveTribes,
)
require.NoError(t, err)
assert.Equal(t, server.Key(), payload.ServerKey())
assert.Equal(t, server.URL(), payload.ServerURL())
assert.Equal(t, server.VersionCode(), payload.VersionCode())
assert.Equal(t, numTribes, payload.NumTribes())
assert.Equal(t, numActiveTribes, payload.NumActiveTribes())
assert.Equal(t, numTribes-numActiveTribes, payload.NumInactiveTribes())
}
func TestNewPlayersSyncedEventPayload(t *testing.T) {
t.Parallel()
server := domaintest.NewServer(t)
numPlayers := gofakeit.IntRange(0, math.MaxInt)
numActivePlayers := gofakeit.IntRange(0, math.MaxInt)
payload, err := domain.NewPlayersSyncedEventPayload(
server.Key(),
server.URL(),
server.VersionCode(),
numPlayers,
numActivePlayers,
)
require.NoError(t, err)
assert.Equal(t, server.Key(), payload.ServerKey())
assert.Equal(t, server.URL(), payload.ServerURL())
assert.Equal(t, server.VersionCode(), payload.VersionCode())
assert.Equal(t, numPlayers, payload.NumPlayers())
assert.Equal(t, numActivePlayers, payload.NumActivePlayers())
assert.Equal(t, numPlayers-numActivePlayers, payload.NumInactivePlayers())
}
func TestNewVillagesSyncedEventPayload(t *testing.T) {

View File

@ -1180,6 +1180,37 @@ func (res ListPlayersWithRelationsResult) Next() PlayerCursor {
return res.next
}
type CountPlayersParams struct {
serverKeys []string
}
const countPlayersParamsModelName = "CountPlayersParams"
func NewCountPlayersParams() CountPlayersParams {
return CountPlayersParams{}
}
func (params *CountPlayersParams) ServerKeys() []string {
return params.serverKeys
}
func (params *CountPlayersParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: countPlayersParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
type PlayerNotFoundError struct {
ID int
ServerKey string

View File

@ -1614,3 +1614,57 @@ func TestNewListPlayersWithRelationsResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
}
func TestCountPlayersParams_SetServerKeys(t *testing.T) {
t.Parallel()
type args struct {
serverKeys []string
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
serverKeys: []string{
domaintest.RandServerKey(),
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
serverKeys: []string{serverKeyTest.key},
},
expectedErr: domain.SliceElementValidationError{
Model: "CountPlayersParams",
Field: "serverKeys",
Index: 0,
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewCountPlayersParams()
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
})
}
}

View File

@ -14,8 +14,12 @@ type Server struct {
url *url.URL
open bool
special bool
numPlayers int
numActivePlayers int
numInactivePlayers int
numTribes int
numActiveTribes int
numInactiveTribes int
numVillages int
numPlayerVillages int
numBarbarianVillages int
@ -44,8 +48,12 @@ func UnmarshalServerFromDatabase(
rawURL string,
open bool,
special bool,
numPlayers int,
numActivePlayers int,
numInactivePlayers int,
numTribes int,
numActiveTribes int,
numInactiveTribes int,
numVillages int,
numPlayerVillages int,
numBarbarianVillages int,
@ -88,11 +96,16 @@ func UnmarshalServerFromDatabase(
return Server{
key: key,
versionCode: versionCode,
url: u,
open: open,
special: special,
numPlayers: numPlayers,
numActivePlayers: numActivePlayers,
numInactivePlayers: numInactivePlayers,
numTribes: numTribes,
numActiveTribes: numActiveTribes,
numInactiveTribes: numInactiveTribes,
numVillages: numVillages,
numPlayerVillages: numPlayerVillages,
numBarbarianVillages: numBarbarianVillages,
@ -107,7 +120,6 @@ func UnmarshalServerFromDatabase(
tribeSnapshotsCreatedAt: tribeSnapshotsCreatedAt,
villageDataSyncedAt: villageDataSyncedAt,
ennoblementDataSyncedAt: ennoblementDataSyncedAt,
versionCode: versionCode,
}, nil
}
@ -131,14 +143,30 @@ func (s Server) Special() bool {
return s.special
}
func (s Server) NumPlayers() int {
return s.numPlayers
}
func (s Server) NumActivePlayers() int {
return s.numActivePlayers
}
func (s Server) NumInactivePlayers() int {
return s.numInactivePlayers
}
func (s Server) NumTribes() int {
return s.numTribes
}
func (s Server) NumActiveTribes() int {
return s.numActiveTribes
}
func (s Server) NumInactiveTribes() int {
return s.numInactiveTribes
}
func (s Server) NumVillages() int {
return s.numVillages
}
@ -377,9 +405,13 @@ type UpdateServerParams struct {
config NullServerConfig
buildingInfo NullBuildingInfo
unitInfo NullUnitInfo
numTribes NullInt
numActiveTribes NullInt
numInactiveTribes NullInt
tribeDataSyncedAt NullTime
numPlayers NullInt
numActivePlayers NullInt
numInactivePlayers NullInt
playerDataSyncedAt NullTime
numVillages NullInt
numPlayerVillages NullInt
@ -420,6 +452,26 @@ func (params *UpdateServerParams) SetUnitInfo(unitInfo NullUnitInfo) error {
return nil
}
func (params *UpdateServerParams) NumTribes() NullInt {
return params.numTribes
}
func (params *UpdateServerParams) SetNumTribes(num NullInt) error {
if num.Valid {
if err := validateIntInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numTribes",
Err: err,
}
}
}
params.numTribes = num
return nil
}
func (params *UpdateServerParams) NumActiveTribes() NullInt {
return params.numActiveTribes
}
@ -440,6 +492,26 @@ func (params *UpdateServerParams) SetNumActiveTribes(num NullInt) error {
return nil
}
func (params *UpdateServerParams) NumInactiveTribes() NullInt {
return params.numInactiveTribes
}
func (params *UpdateServerParams) SetNumInactiveTribes(num NullInt) error {
if num.Valid {
if err := validateIntInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numInactiveTribes",
Err: err,
}
}
}
params.numInactiveTribes = num
return nil
}
func (params *UpdateServerParams) TribeDataSyncedAt() NullTime {
return params.tribeDataSyncedAt
}
@ -449,6 +521,26 @@ func (params *UpdateServerParams) SetTribeDataSyncedAt(tribeDataSyncedAt NullTim
return nil
}
func (params *UpdateServerParams) NumPlayers() NullInt {
return params.numPlayers
}
func (params *UpdateServerParams) SetNumPlayers(num NullInt) error {
if num.Valid {
if err := validateIntInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numPlayers",
Err: err,
}
}
}
params.numPlayers = num
return nil
}
func (params *UpdateServerParams) NumActivePlayers() NullInt {
return params.numActivePlayers
}
@ -469,6 +561,26 @@ func (params *UpdateServerParams) SetNumActivePlayers(num NullInt) error {
return nil
}
func (params *UpdateServerParams) NumInactivePlayers() NullInt {
return params.numInactivePlayers
}
func (params *UpdateServerParams) SetNumInactivePlayers(num NullInt) error {
if num.Valid {
if err := validateIntInRange(num.V, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numInactivePlayers",
Err: err,
}
}
}
params.numInactivePlayers = num
return nil
}
func (params *UpdateServerParams) PlayerDataSyncedAt() NullTime {
return params.playerDataSyncedAt
}

View File

@ -161,6 +161,69 @@ func TestNewCreateServerParams(t *testing.T) {
}
}
func TestUpdateServerParams_SetNumTribes(t *testing.T) {
t.Parallel()
type args struct {
numTribes domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numTribes: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numTribes: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numTribes < 0",
args: args{
numTribes: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numTribes",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumTribes(tt.args.numTribes), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numTribes, params.NumTribes())
})
}
}
func TestUpdateServerParams_SetNumActiveTribes(t *testing.T) {
t.Parallel()
@ -224,6 +287,132 @@ func TestUpdateServerParams_SetNumActiveTribes(t *testing.T) {
}
}
func TestUpdateServerParams_SetNumInactiveTribes(t *testing.T) {
t.Parallel()
type args struct {
numInactiveTribes domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numInactiveTribes: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numInactiveTribes: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numInactiveTribes < 0",
args: args{
numInactiveTribes: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numInactiveTribes",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumInactiveTribes(tt.args.numInactiveTribes), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numInactiveTribes, params.NumInactiveTribes())
})
}
}
func TestUpdateServerParams_SetNumPlayers(t *testing.T) {
t.Parallel()
type args struct {
numPlayers domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numPlayers: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numPlayers: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numPlayers < 0",
args: args{
numPlayers: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numPlayers",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumPlayers(tt.args.numPlayers), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numPlayers, params.NumPlayers())
})
}
}
func TestUpdateServerParams_SetNumActivePlayers(t *testing.T) {
t.Parallel()
@ -287,6 +476,69 @@ func TestUpdateServerParams_SetNumActivePlayers(t *testing.T) {
}
}
func TestUpdateServerParams_SetNumInactivePlayers(t *testing.T) {
t.Parallel()
type args struct {
numInactivePlayers domain.NullInt
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
numInactivePlayers: domain.NullInt{
V: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
},
},
},
{
name: "OK: null value",
args: args{
numInactivePlayers: domain.NullInt{
Valid: false,
},
},
},
{
name: "ERR: numInactivePlayers < 0",
args: args{
numInactivePlayers: domain.NullInt{
V: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numInactivePlayers",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var params domain.UpdateServerParams
require.ErrorIs(t, params.SetNumInactivePlayers(tt.args.numInactivePlayers), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.numInactivePlayers, params.NumInactivePlayers())
})
}
}
func TestUpdateServerParams_SetNumVillages(t *testing.T) {
t.Parallel()

View File

@ -969,6 +969,37 @@ func (res ListTribesResult) Next() TribeCursor {
return res.next
}
type CountTribesParams struct {
serverKeys []string
}
const countTribesParamsModelName = "CountTribesParams"
func NewCountTribesParams() CountTribesParams {
return CountTribesParams{}
}
func (params *CountTribesParams) ServerKeys() []string {
return params.serverKeys
}
func (params *CountTribesParams) SetServerKeys(serverKeys []string) error {
for i, sk := range serverKeys {
if err := validateServerKey(sk); err != nil {
return SliceElementValidationError{
Model: countTribesParamsModelName,
Field: "serverKeys",
Index: i,
Err: err,
}
}
}
params.serverKeys = serverKeys
return nil
}
type TribeNotFoundError struct {
ID int
ServerKey string

View File

@ -1205,3 +1205,57 @@ func TestNewListTribesResult(t *testing.T) {
assert.True(t, res.Next().IsZero())
})
}
func TestCountTribesParams_SetServerKeys(t *testing.T) {
t.Parallel()
type args struct {
serverKeys []string
}
type test struct {
name string
args args
expectedErr error
}
tests := []test{
{
name: "OK",
args: args{
serverKeys: []string{
domaintest.RandServerKey(),
},
},
},
}
for _, serverKeyTest := range newServerKeyValidationTests() {
tests = append(tests, test{
name: serverKeyTest.name,
args: args{
serverKeys: []string{serverKeyTest.key},
},
expectedErr: domain.SliceElementValidationError{
Model: "CountTribesParams",
Field: "serverKeys",
Index: 0,
Err: serverKeyTest.expectedErr,
},
})
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewCountTribesParams()
require.ErrorIs(t, params.SetServerKeys(tt.args.serverKeys), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.serverKeys, params.ServerKeys())
})
}
}

View File

@ -248,8 +248,12 @@ func TestDataSync(t *testing.T) {
assert.Equal(collect, expected["URL"], actual.URL().String(), msg)
assert.Equal(collect, expected["Open"], actual.Open(), msg)
assert.Equal(collect, expected["VersionCode"], actual.VersionCode(), msg)
assert.EqualValues(collect, expected["NumPlayers"], actual.NumPlayers(), msg)
assert.EqualValues(collect, expected["NumActivePlayers"], actual.NumActivePlayers(), msg)
assert.EqualValues(collect, expected["NumInactivePlayers"], actual.NumInactivePlayers(), msg)
assert.EqualValues(collect, expected["NumTribes"], actual.NumTribes(), msg)
assert.EqualValues(collect, expected["NumActiveTribes"], actual.NumActiveTribes(), msg)
assert.EqualValues(collect, expected["NumInactiveTribes"], actual.NumInactiveTribes(), msg)
assert.EqualValues(collect, expected["NumVillages"], actual.NumVillages(), msg)
assert.EqualValues(collect, expected["NumPlayerVillages"], actual.NumPlayerVillages(), msg)
assert.EqualValues(collect, expected["NumBonusVillages"], actual.NumBonusVillages(), msg)
@ -257,8 +261,7 @@ func TestDataSync(t *testing.T) {
collect,
expected["NumBarbarianVillages"],
actual.NumBarbarianVillages(),
"Key=%s",
expected["Key"],
msg,
)
assert.WithinDuration(collect, time.Now(), actual.PlayerDataSyncedAt(), time.Minute, msg)
assert.WithinDuration(collect, time.Now(), actual.TribeDataSyncedAt(), time.Minute, msg)
@ -267,8 +270,7 @@ func TestDataSync(t *testing.T) {
collect,
string(marshalJSON(collect, expected["Config"])),
string(marshalJSON(collect, serverConfigToMap(actual.Config()))),
"Key=%s",
expected["Key"],
msg,
)
assert.JSONEq(
collect,

View File

@ -155,6 +155,7 @@ func (c *ServerWatermillConsumer) updateNumTribes(msg *message.Message) error {
rawPayload.ServerKey,
rawPayload.ServerURL,
rawPayload.VersionCode,
rawPayload.NumTribes,
rawPayload.NumActiveTribes,
)
if err != nil {
@ -181,6 +182,7 @@ func (c *ServerWatermillConsumer) updateNumPlayers(msg *message.Message) error {
rawPayload.ServerKey,
rawPayload.ServerURL,
rawPayload.VersionCode,
rawPayload.NumPlayers,
rawPayload.NumActivePlayers,
)
if err != nil {

View File

@ -6,13 +6,17 @@ import (
func NewServer(s domain.Server) Server {
converted := Server{
Key: s.Key(),
CreatedAt: s.CreatedAt(),
NumBarbarianVillages: s.NumBarbarianVillages(),
NumBonusVillages: s.NumBonusVillages(),
NumPlayerVillages: s.NumPlayerVillages(),
Key: s.Key(),
NumActivePlayers: s.NumActivePlayers(),
NumActiveTribes: s.NumActiveTribes(),
NumBarbarianVillages: s.NumBarbarianVillages(),
NumBonusVillages: s.NumBonusVillages(),
NumInactivePlayers: s.NumInactivePlayers(),
NumInactiveTribes: s.NumInactiveTribes(),
NumPlayerVillages: s.NumPlayerVillages(),
NumPlayers: s.NumPlayers(),
NumTribes: s.NumTribes(),
NumVillages: s.NumVillages(),
Open: s.Open(),
Url: s.URL().String(),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,5 +6,6 @@ type PlayersSyncedEventPayload struct {
ServerKey string `json:"serverKey"`
ServerURL *url.URL `json:"serverUrl"`
VersionCode string `json:"versionCode"`
NumPlayers int `json:"numPlayers"`
NumActivePlayers int `json:"numActivePlayers"`
}

View File

@ -6,5 +6,6 @@ type TribesSyncedEventPayload struct {
ServerKey string `json:"serverKey"`
ServerURL *url.URL `json:"serverUrl"`
VersionCode string `json:"versionCode"`
NumTribes int `json:"numTribes"`
NumActiveTribes int `json:"numActiveTribes"`
}