feat: server - update NumTribes & TribeDataSyncedAt (#18)

Reviewed-on: twhelp/corev3#18
This commit is contained in:
Dawid Wysokiński 2023-12-30 09:10:03 +00:00
parent 62832de065
commit d5973877a6
9 changed files with 229 additions and 34 deletions

View File

@ -61,6 +61,7 @@ var cmdConsumer = &cli.Command{
marshaler,
c.String(rmqFlagTopicSyncServersCmd.Name),
c.String(rmqFlagTopicServerSyncedEvent.Name),
c.String(rmqFlagTopicTribesSyncedEvent.Name),
)
consumer.Register(router)

View File

@ -9,28 +9,32 @@ import (
)
type Server struct {
bun.BaseModel `bun:"table:servers,alias:server"`
Key string `bun:"key,nullzero,pk"`
URL string `bun:"url,nullzero"`
Open bool `bun:"open"`
Special bool `bun:"special"`
NumPlayers int `bun:"num_players"`
NumTribes int `bun:"num_tribes"`
NumVillages int `bun:"num_villages"`
NumPlayerVillages int `bun:"num_player_villages"`
NumBarbarianVillages int `bun:"num_barbarian_villages"`
NumBonusVillages int `bun:"num_bonus_villages"`
Config ServerConfig `bun:"config"`
BuildingInfo BuildingInfo `bun:"building_info"`
UnitInfo UnitInfo `bun:"unit_info"`
CreatedAt time.Time `bun:"created_at,nullzero"`
PlayerDataUpdatedAt time.Time `bun:"player_data_updated_at,nullzero"`
PlayerSnapshotsCreatedAt time.Time `bun:"player_snapshots_created_at,nullzero"`
TribeDataUpdatedAt time.Time `bun:"tribe_data_updated_at,nullzero"`
TribeSnapshotsCreatedAt time.Time `bun:"tribe_snapshots_created_at,nullzero"`
VillageDataUpdatedAt time.Time `bun:"village_data_updated_at,nullzero"`
EnnoblementDataUpdatedAt time.Time `bun:"ennoblement_data_updated_at,nullzero"`
VersionCode string `bun:"version_code,nullzero"`
bun.BaseModel `bun:"table:servers,alias:server"`
Key string `bun:"key,nullzero,pk"`
URL string `bun:"url,nullzero"`
Open bool `bun:"open"`
Special bool `bun:"special"`
NumPlayers int `bun:"num_players"`
NumTribes int `bun:"num_tribes"`
NumVillages int `bun:"num_villages"`
NumPlayerVillages int `bun:"num_player_villages"`
NumBarbarianVillages int `bun:"num_barbarian_villages"`
NumBonusVillages int `bun:"num_bonus_villages"`
Config ServerConfig `bun:"config"`
BuildingInfo BuildingInfo `bun:"building_info"`
UnitInfo UnitInfo `bun:"unit_info"`
CreatedAt time.Time `bun:"created_at,nullzero"`
// TODO: rename this column to player_data_synced_at
PlayerDataUpdatedAt time.Time `bun:"player_data_updated_at,nullzero"`
PlayerSnapshotsCreatedAt time.Time `bun:"player_snapshots_created_at,nullzero"`
// TODO: rename this column to tribe_data_synced_at
TribeDataUpdatedAt time.Time `bun:"tribe_data_updated_at,nullzero"`
TribeSnapshotsCreatedAt time.Time `bun:"tribe_snapshots_created_at,nullzero"`
// TODO: rename this column to village_data_synced_at
VillageDataUpdatedAt time.Time `bun:"village_data_updated_at,nullzero"`
// TODO: rename this column to ennoblement_data_synced_at
EnnoblementDataUpdatedAt time.Time `bun:"ennoblement_data_updated_at,nullzero"`
VersionCode string `bun:"version_code,nullzero"`
}
func (s Server) ToDomain() (domain.Server, error) {

View File

@ -89,7 +89,7 @@ func (repo *ServerBunRepository) Update(ctx context.Context, key string, params
Returning("").
Exec(ctx)
if err != nil {
return fmt.Errorf("couldn't update server with key %s: %w", key, err)
return fmt.Errorf("%s: couldn't update server: %w", key, err)
}
if affected, _ := res.RowsAffected(); affected == 0 {
return domain.ServerNotFoundError{
@ -117,6 +117,15 @@ func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
q = q.Set("building_info = ?", bunmodel.NewBuildingInfo(buildingInfo.Value))
}
if numTribes := a.params.NumTribes(); numTribes.Valid {
q = q.Set("num_tribes = ?", numTribes.Value)
}
if tribeDataSyncedAt := a.params.TribeDataSyncedAt(); tribeDataSyncedAt.Valid {
// TODO: rename this column to tribe_data_synced_at
q = q.Set("tribe_data_updated_at = ?", tribeDataSyncedAt.Value)
}
return q
}

View File

@ -4,11 +4,14 @@ import (
"cmp"
"context"
"fmt"
"math"
"slices"
"testing"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -400,6 +403,14 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
Value: domaintest.NewBuildingInfo(t),
Valid: true,
}))
require.NoError(t, updateParams.SetNumTribes(domain.NullInt{
Value: gofakeit.IntRange(0, math.MaxInt),
Valid: true,
}))
require.NoError(t, updateParams.SetTribeDataSyncedAt(domain.NullTime{
Value: time.Now(),
Valid: true,
}))
require.NoError(t, repos.server.Update(ctx, serversBeforeUpdate[0].Key(), updateParams))
@ -413,6 +424,13 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories
assert.Equal(t, updateParams.Config().Value, serversAfterUpdate[0].Config())
assert.Equal(t, updateParams.UnitInfo().Value, serversAfterUpdate[0].UnitInfo())
assert.Equal(t, updateParams.BuildingInfo().Value, serversAfterUpdate[0].BuildingInfo())
assert.Equal(t, updateParams.NumTribes().Value, serversAfterUpdate[0].NumTribes())
assert.WithinDuration(
t,
updateParams.TribeDataSyncedAt().Value,
serversAfterUpdate[0].TribeDataSyncedAt(),
time.Minute,
)
})
t.Run("ERR: not found", func(t *testing.T) {

View File

@ -3,6 +3,7 @@ package app
import (
"context"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
)
@ -138,21 +139,22 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
}
func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
key := payload.Key()
u := payload.URL()
cfg, err := svc.twSvc.GetServerConfig(ctx, u)
if err != nil {
return fmt.Errorf("couldn't get server config for server %s: %w", payload.Key(), err)
return fmt.Errorf("%s: couldn't get server config: %w", key, err)
}
buildingInfo, err := svc.twSvc.GetBuildingInfo(ctx, u)
if err != nil {
return fmt.Errorf("couldn't get building info for server %s: %w", payload.Key(), err)
return fmt.Errorf("%s: couldn't get building info: %w", key, err)
}
unitInfo, err := svc.twSvc.GetUnitInfo(ctx, u)
if err != nil {
return fmt.Errorf("couldn't get unit info for server %s: %w", payload.Key(), err)
return fmt.Errorf("%s: couldn't get unit info: %w", key, err)
}
var updateParams domain.UpdateServerParams
@ -160,20 +162,40 @@ func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.
Value: cfg,
Valid: true,
}); err != nil {
return err
return fmt.Errorf("%s: %w", key, err)
}
if err = updateParams.SetBuildingInfo(domain.NullBuildingInfo{
Value: buildingInfo,
Valid: true,
}); err != nil {
return err
return fmt.Errorf("%s: %w", key, err)
}
if err = updateParams.SetUnitInfo(domain.NullUnitInfo{
Value: unitInfo,
Valid: true,
}); err != nil {
return err
return fmt.Errorf("%s: %w", key, err)
}
return svc.repo.Update(ctx, payload.Key(), updateParams)
return svc.repo.Update(ctx, key, updateParams)
}
func (svc *ServerService) UpdateNumTribes(ctx context.Context, payload domain.TribesSyncedEventPayload) error {
key := payload.ServerKey()
var updateParams domain.UpdateServerParams
if err := updateParams.SetNumTribes(domain.NullInt{
Value: payload.NumTribes(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
if err := updateParams.SetTribeDataSyncedAt(domain.NullTime{
Value: time.Now(),
Valid: true,
}); err != nil {
return fmt.Errorf("%s: %w", key, err)
}
return svc.repo.Update(ctx, key, updateParams)
}

View File

@ -1,5 +1,7 @@
package domain
import "time"
type NullValue[T any] struct {
Value T
Valid bool // Valid is true if Value is not NULL
@ -10,3 +12,5 @@ type NullInt = NullValue[int]
type NullString = NullValue[string]
type NullBool = NullValue[bool]
type NullTime = NullValue[time.Time]

View File

@ -274,11 +274,15 @@ func (params CreateServerParams) VersionCode() string {
}
type UpdateServerParams struct {
config NullServerConfig
buildingInfo NullBuildingInfo
unitInfo NullUnitInfo
config NullServerConfig
buildingInfo NullBuildingInfo
unitInfo NullUnitInfo
numTribes NullInt
tribeDataSyncedAt NullTime
}
const updateServerParamsModelName = "UpdateServerParams"
func (params *UpdateServerParams) Config() NullServerConfig {
return params.config
}
@ -306,10 +310,41 @@ func (params *UpdateServerParams) SetUnitInfo(unitInfo NullUnitInfo) error {
return nil
}
func (params *UpdateServerParams) NumTribes() NullInt {
return params.numTribes
}
func (params *UpdateServerParams) SetNumTribes(numTribes NullInt) error {
if numTribes.Valid {
if err := validateIntInRange(numTribes.Value, 0, math.MaxInt); err != nil {
return ValidationError{
Model: updateServerParamsModelName,
Field: "numTribes",
Err: err,
}
}
}
params.numTribes = numTribes
return nil
}
func (params *UpdateServerParams) TribeDataSyncedAt() NullTime {
return params.tribeDataSyncedAt
}
func (params *UpdateServerParams) SetTribeDataSyncedAt(tribeDataSyncedAt NullTime) error {
params.tribeDataSyncedAt = tribeDataSyncedAt
return nil
}
func (params *UpdateServerParams) IsZero() bool {
return !params.config.Valid &&
!params.buildingInfo.Valid &&
!params.unitInfo.Valid
!params.unitInfo.Valid &&
!params.numTribes.Valid &&
!params.tribeDataSyncedAt.Valid
}
type ServerSort uint8

View File

@ -2,11 +2,13 @@ package domain_test
import (
"fmt"
"math"
"slices"
"testing"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -120,6 +122,71 @@ 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{
Value: 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{
Value: -1,
Valid: true,
},
},
expectedErr: domain.ValidationError{
Model: "UpdateServerParams",
Field: "numTribes",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
tt := tt
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 TestListServersParams_SetSort(t *testing.T) {
t.Parallel()

View File

@ -15,6 +15,7 @@ type ServerWatermillConsumer struct {
marshaler watermillmsg.Marshaler
cmdSyncTopic string
eventServerSyncedTopic string
eventTribesSyncedTopic string
}
func NewServerWatermillConsumer(
@ -24,6 +25,7 @@ func NewServerWatermillConsumer(
marshaler watermillmsg.Marshaler,
cmdSyncTopic string,
eventServerSyncedTopic string,
eventTribesSyncedTopic string,
) *ServerWatermillConsumer {
return &ServerWatermillConsumer{
svc: svc,
@ -32,6 +34,7 @@ func NewServerWatermillConsumer(
marshaler: marshaler,
cmdSyncTopic: cmdSyncTopic,
eventServerSyncedTopic: eventServerSyncedTopic,
eventTribesSyncedTopic: eventTribesSyncedTopic,
}
}
@ -43,6 +46,12 @@ func (c *ServerWatermillConsumer) Register(router *message.Router) {
c.subscriber,
c.syncConfigAndInfo,
)
router.AddNoPublisherHandler(
"ServerConsumer.updateNumTribes",
c.eventTribesSyncedTopic,
c.subscriber,
c.updateNumTribes,
)
}
func (c *ServerWatermillConsumer) sync(msg *message.Message) error {
@ -86,3 +95,29 @@ func (c *ServerWatermillConsumer) syncConfigAndInfo(msg *message.Message) error
return c.svc.SyncConfigAndInfo(msg.Context(), payload)
}
func (c *ServerWatermillConsumer) updateNumTribes(msg *message.Message) error {
var rawPayload watermillmsg.TribesSyncedEventPayload
if err := c.marshaler.Unmarshal(msg, &rawPayload); err != nil {
c.logger.Error("couldn't unmarshal payload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
payload, err := domain.NewTribesSyncedEventPayload(
rawPayload.ServerKey,
rawPayload.ServerURL,
rawPayload.VersionCode,
rawPayload.NumTribes,
)
if err != nil {
c.logger.Error("couldn't construct domain.ServerSyncedEventPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
return c.svc.UpdateNumTribes(msg.Context(), payload)
}