parent
7a1e4dfb50
commit
57de175c66
|
@ -42,11 +42,6 @@ var cmdConsumer = &cli.Command{
|
||||||
marshaler watermillmsg.Marshaler,
|
marshaler watermillmsg.Marshaler,
|
||||||
db *bun.DB,
|
db *bun.DB,
|
||||||
) error {
|
) error {
|
||||||
twSvc, err := newTWServiceFromFlags(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
serverPublisher := adapter.NewServerWatermillPublisher(
|
serverPublisher := adapter.NewServerWatermillPublisher(
|
||||||
publisher,
|
publisher,
|
||||||
marshaler,
|
marshaler,
|
||||||
|
@ -54,6 +49,11 @@ var cmdConsumer = &cli.Command{
|
||||||
c.String(rmqFlagTopicServerSyncedEvent.Name),
|
c.String(rmqFlagTopicServerSyncedEvent.Name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twSvc, err := newTWServiceFromFlags(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
consumer := port.NewServerWatermillConsumer(
|
consumer := port.NewServerWatermillConsumer(
|
||||||
app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher),
|
app.NewServerService(adapter.NewServerBunRepository(db), twSvc, serverPublisher),
|
||||||
subscriber,
|
subscriber,
|
||||||
|
@ -90,17 +90,17 @@ var cmdConsumer = &cli.Command{
|
||||||
marshaler watermillmsg.Marshaler,
|
marshaler watermillmsg.Marshaler,
|
||||||
db *bun.DB,
|
db *bun.DB,
|
||||||
) error {
|
) error {
|
||||||
twSvc, err := newTWServiceFromFlags(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tribePublisher := adapter.NewTribeWatermillPublisher(
|
tribePublisher := adapter.NewTribeWatermillPublisher(
|
||||||
publisher,
|
publisher,
|
||||||
marshaler,
|
marshaler,
|
||||||
c.String(rmqFlagTopicTribesSyncedEvent.Name),
|
c.String(rmqFlagTopicTribesSyncedEvent.Name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twSvc, err := newTWServiceFromFlags(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
consumer := port.NewTribeWatermillConsumer(
|
consumer := port.NewTribeWatermillConsumer(
|
||||||
app.NewTribeService(adapter.NewTribeBunRepository(db), twSvc, tribePublisher),
|
app.NewTribeService(adapter.NewTribeBunRepository(db), twSvc, tribePublisher),
|
||||||
subscriber,
|
subscriber,
|
||||||
|
@ -133,19 +133,26 @@ var cmdConsumer = &cli.Command{
|
||||||
marshaler watermillmsg.Marshaler,
|
marshaler watermillmsg.Marshaler,
|
||||||
db *bun.DB,
|
db *bun.DB,
|
||||||
) error {
|
) error {
|
||||||
twSvc, err := newTWServiceFromFlags(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
playerPublisher := adapter.NewPlayerWatermillPublisher(
|
playerPublisher := adapter.NewPlayerWatermillPublisher(
|
||||||
publisher,
|
publisher,
|
||||||
marshaler,
|
marshaler,
|
||||||
c.String(rmqFlagTopicPlayersSyncedEvent.Name),
|
c.String(rmqFlagTopicPlayersSyncedEvent.Name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twSvc, err := newTWServiceFromFlags(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tribeChangeSvc := app.NewTribeChangeService(adapter.NewTribeChangeBunRepository(db))
|
||||||
|
|
||||||
consumer := port.NewPlayerWatermillConsumer(
|
consumer := port.NewPlayerWatermillConsumer(
|
||||||
app.NewPlayerService(adapter.NewPlayerBunRepository(db), twSvc, playerPublisher),
|
app.NewPlayerService(
|
||||||
|
adapter.NewPlayerBunRepository(db),
|
||||||
|
tribeChangeSvc,
|
||||||
|
twSvc,
|
||||||
|
playerPublisher,
|
||||||
|
),
|
||||||
subscriber,
|
subscriber,
|
||||||
logger,
|
logger,
|
||||||
marshaler,
|
marshaler,
|
||||||
|
@ -175,17 +182,17 @@ var cmdConsumer = &cli.Command{
|
||||||
marshaler watermillmsg.Marshaler,
|
marshaler watermillmsg.Marshaler,
|
||||||
db *bun.DB,
|
db *bun.DB,
|
||||||
) error {
|
) error {
|
||||||
twSvc, err := newTWServiceFromFlags(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
villagePublisher := adapter.NewVillageWatermillPublisher(
|
villagePublisher := adapter.NewVillageWatermillPublisher(
|
||||||
publisher,
|
publisher,
|
||||||
marshaler,
|
marshaler,
|
||||||
c.String(rmqFlagTopicVillagesSyncedEvent.Name),
|
c.String(rmqFlagTopicVillagesSyncedEvent.Name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twSvc, err := newTWServiceFromFlags(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
consumer := port.NewVillageWatermillConsumer(
|
consumer := port.NewVillageWatermillConsumer(
|
||||||
app.NewVillageService(adapter.NewVillageBunRepository(db), twSvc, villagePublisher),
|
app.NewVillageService(adapter.NewVillageBunRepository(db), twSvc, villagePublisher),
|
||||||
subscriber,
|
subscriber,
|
||||||
|
@ -217,11 +224,6 @@ var cmdConsumer = &cli.Command{
|
||||||
marshaler watermillmsg.Marshaler,
|
marshaler watermillmsg.Marshaler,
|
||||||
db *bun.DB,
|
db *bun.DB,
|
||||||
) error {
|
) error {
|
||||||
twSvc, err := newTWServiceFromFlags(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ennoblementPublisher := adapter.NewEnnoblementWatermillPublisher(
|
ennoblementPublisher := adapter.NewEnnoblementWatermillPublisher(
|
||||||
publisher,
|
publisher,
|
||||||
marshaler,
|
marshaler,
|
||||||
|
@ -229,6 +231,11 @@ var cmdConsumer = &cli.Command{
|
||||||
c.String(rmqFlagTopicEnnoblementsSyncedEvent.Name),
|
c.String(rmqFlagTopicEnnoblementsSyncedEvent.Name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twSvc, err := newTWServiceFromFlags(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
consumer := port.NewEnnoblementWatermillConsumer(
|
consumer := port.NewEnnoblementWatermillConsumer(
|
||||||
app.NewEnnoblementService(adapter.NewEnnoblementBunRepository(db), twSvc, ennoblementPublisher),
|
app.NewEnnoblementService(adapter.NewEnnoblementBunRepository(db), twSvc, ennoblementPublisher),
|
||||||
subscriber,
|
subscriber,
|
||||||
|
|
|
@ -23,6 +23,7 @@ func NewFixture(bunDB *bun.DB) *Fixture {
|
||||||
(*bunmodel.Player)(nil),
|
(*bunmodel.Player)(nil),
|
||||||
(*bunmodel.Village)(nil),
|
(*bunmodel.Village)(nil),
|
||||||
(*bunmodel.Ennoblement)(nil),
|
(*bunmodel.Ennoblement)(nil),
|
||||||
|
(*bunmodel.TribeChange)(nil),
|
||||||
)
|
)
|
||||||
return &Fixture{
|
return &Fixture{
|
||||||
f: dbfixture.New(bunDB),
|
f: dbfixture.New(bunDB),
|
||||||
|
|
|
@ -19,6 +19,9 @@ func NewBunDBSQLite(tb TestingTB) *bun.DB {
|
||||||
sqlDB.SetConnMaxLifetime(0)
|
sqlDB.SetConnMaxLifetime(0)
|
||||||
|
|
||||||
bunDB := bun.NewDB(sqlDB, sqlitedialect.New())
|
bunDB := bun.NewDB(sqlDB, sqlitedialect.New())
|
||||||
|
tb.Cleanup(func() {
|
||||||
|
_ = bunDB.Close()
|
||||||
|
})
|
||||||
|
|
||||||
bunDB.AddQueryHook(newBunDebugHook())
|
bunDB.AddQueryHook(newBunDebugHook())
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package bunmodel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TribeChange struct {
|
||||||
|
bun.BaseModel `bun:"table:tribe_changes,alias:tc"`
|
||||||
|
|
||||||
|
ID int `bun:"id,pk,autoincrement,identity"`
|
||||||
|
ServerKey string `bun:"server_key,nullzero"`
|
||||||
|
PlayerID int `bun:"player_id,nullzero"`
|
||||||
|
Player Player `bun:"player,rel:belongs-to,join:player_id=id,join:server_key=server_key"`
|
||||||
|
NewTribeID int `bun:"new_tribe_id,nullzero"`
|
||||||
|
NewTribe Tribe `bun:"new_tribe,rel:belongs-to,join:new_tribe_id=id,join:server_key=server_key"`
|
||||||
|
OldTribeID int `bun:"old_tribe_id,nullzero"`
|
||||||
|
OldTribe Tribe `bun:"old_tribe,rel:belongs-to,join:old_tribe_id=id,join:server_key=server_key"`
|
||||||
|
CreatedAt time.Time `bun:"created_at,nullzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) ToDomain() (domain.TribeChange, error) {
|
||||||
|
converted, err := domain.UnmarshalTribeChangeFromDatabase(
|
||||||
|
tc.ID,
|
||||||
|
tc.ServerKey,
|
||||||
|
tc.PlayerID,
|
||||||
|
tc.OldTribeID,
|
||||||
|
tc.NewTribeID,
|
||||||
|
tc.CreatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.TribeChange{}, fmt.Errorf(
|
||||||
|
"couldn't construct domain.TribeChange (id=%d): %w",
|
||||||
|
tc.ID,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TribeChanges []TribeChange
|
||||||
|
|
||||||
|
func (tcs TribeChanges) ToDomain() (domain.TribeChanges, error) {
|
||||||
|
res := make(domain.TribeChanges, 0, len(tcs))
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
converted, err := tc.ToDomain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, converted)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ func (repo *PlayerBunRepository) CreateOrUpdate(ctx context.Context, params ...d
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
players := make(bunmodel.Players, 0, len(params))
|
players := make(bunmodel.Players, 0, len(params))
|
||||||
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
|
@ -46,7 +47,7 @@ func (repo *PlayerBunRepository) CreateOrUpdate(ctx context.Context, params ...d
|
||||||
MostVillages: p.MostVillages(),
|
MostVillages: p.MostVillages(),
|
||||||
MostVillagesAt: p.MostVillagesAt(),
|
MostVillagesAt: p.MostVillagesAt(),
|
||||||
LastActivityAt: p.LastActivityAt(),
|
LastActivityAt: p.LastActivityAt(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: now,
|
||||||
OpponentsDefeated: bunmodel.NewOpponentsDefeated(base.OD()),
|
OpponentsDefeated: bunmodel.NewOpponentsDefeated(base.OD()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ func (repo *ServerBunRepository) CreateOrUpdate(ctx context.Context, params ...d
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
servers := make(bunmodel.Servers, 0, len(params))
|
servers := make(bunmodel.Servers, 0, len(params))
|
||||||
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
|
@ -35,7 +36,7 @@ func (repo *ServerBunRepository) CreateOrUpdate(ctx context.Context, params ...d
|
||||||
URL: base.URL().String(),
|
URL: base.URL().String(),
|
||||||
Open: base.Open(),
|
Open: base.Open(),
|
||||||
VersionCode: p.VersionCode(),
|
VersionCode: p.VersionCode(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: now,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ func (repo *TribeBunRepository) CreateOrUpdate(ctx context.Context, params ...do
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
tribes := make(bunmodel.Tribes, 0, len(params))
|
tribes := make(bunmodel.Tribes, 0, len(params))
|
||||||
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
|
@ -47,7 +48,7 @@ func (repo *TribeBunRepository) CreateOrUpdate(ctx context.Context, params ...do
|
||||||
MostPointsAt: p.MostPointsAt(),
|
MostPointsAt: p.MostPointsAt(),
|
||||||
MostVillages: p.MostVillages(),
|
MostVillages: p.MostVillages(),
|
||||||
MostVillagesAt: p.MostVillagesAt(),
|
MostVillagesAt: p.MostVillagesAt(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: now,
|
||||||
OpponentsDefeated: bunmodel.NewOpponentsDefeated(base.OD()),
|
OpponentsDefeated: bunmodel.NewOpponentsDefeated(base.OD()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/internal/bunmodel"
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TribeChangeBunRepository struct {
|
||||||
|
db bun.IDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTribeChangeBunRepository(db bun.IDB) *TribeChangeBunRepository {
|
||||||
|
return &TribeChangeBunRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *TribeChangeBunRepository) Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error {
|
||||||
|
if len(params) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
tribeChanges := make(bunmodel.TribeChanges, 0, len(params))
|
||||||
|
|
||||||
|
for _, p := range params {
|
||||||
|
tribeChanges = append(tribeChanges, bunmodel.TribeChange{
|
||||||
|
ServerKey: p.ServerKey(),
|
||||||
|
PlayerID: p.PlayerID(),
|
||||||
|
NewTribeID: p.NewTribeID(),
|
||||||
|
OldTribeID: p.OldTribeID(),
|
||||||
|
CreatedAt: now,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := repo.db.NewInsert().
|
||||||
|
Model(&tribeChanges).
|
||||||
|
Ignore().
|
||||||
|
Returning("").
|
||||||
|
Exec(ctx); err != nil {
|
||||||
|
return fmt.Errorf("something went wrong while inserting tribe changes into the db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *TribeChangeBunRepository) List(
|
||||||
|
ctx context.Context,
|
||||||
|
params domain.ListTribeChangesParams,
|
||||||
|
) (domain.TribeChanges, error) {
|
||||||
|
var tribeChanges bunmodel.TribeChanges
|
||||||
|
|
||||||
|
if err := repo.db.NewSelect().
|
||||||
|
Model(&tribeChanges).
|
||||||
|
Apply(listTribeChangesParamsApplier{params: params}.apply).
|
||||||
|
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, fmt.Errorf("couldn't select tribe changes from the db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tribeChanges.ToDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
type listTribeChangesParamsApplier struct {
|
||||||
|
params domain.ListTribeChangesParams
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func (a listTribeChangesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
||||||
|
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
|
||||||
|
q = q.Where("tc.server_key IN (?)", bun.In(serverKeys))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range a.params.Sort() {
|
||||||
|
switch s {
|
||||||
|
case domain.TribeChangeSortCreatedAtASC:
|
||||||
|
q = q.Order("tc.created_at ASC")
|
||||||
|
case domain.TribeChangeSortCreatedAtDESC:
|
||||||
|
q = q.Order("tc.created_at DESC")
|
||||||
|
case domain.TribeChangeSortServerKeyASC:
|
||||||
|
q = q.Order("tc.server_key ASC")
|
||||||
|
case domain.TribeChangeSortServerKeyDESC:
|
||||||
|
q = q.Order("tc.server_key DESC")
|
||||||
|
default:
|
||||||
|
return q.Err(errors.New("unsupported sort value"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package adapter_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTribeChangeBunRepository_Postgres(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping long-running test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testTribeChangeRepository(t, func(t *testing.T) repositories {
|
||||||
|
t.Helper()
|
||||||
|
return newBunDBRepositories(t, postgres.NewBunDB(t))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTribeChangeBunRepository_SQLite(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testTribeChangeRepository(t, func(t *testing.T) repositories {
|
||||||
|
t.Helper()
|
||||||
|
return newBunDBRepositories(t, adaptertest.NewBunDBSQLite(t))
|
||||||
|
})
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ func (repo *VillageBunRepository) CreateOrUpdate(ctx context.Context, params ...
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
villages := make(bunmodel.Villages, 0, len(params))
|
villages := make(bunmodel.Villages, 0, len(params))
|
||||||
|
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
|
@ -41,7 +42,7 @@ func (repo *VillageBunRepository) CreateOrUpdate(ctx context.Context, params ...
|
||||||
Bonus: base.Bonus(),
|
Bonus: base.Bonus(),
|
||||||
PlayerID: base.PlayerID(),
|
PlayerID: base.PlayerID(),
|
||||||
ProfileURL: base.ProfileURL().String(),
|
ProfileURL: base.ProfileURL().String(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: now,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,8 +201,8 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit
|
||||||
|
|
||||||
serverKeys := params.ServerKeys()
|
serverKeys := params.ServerKeys()
|
||||||
|
|
||||||
for _, v := range ennoblements {
|
for _, e := range ennoblements {
|
||||||
assert.True(t, slices.Contains(serverKeys, v.ServerKey()))
|
assert.True(t, slices.Contains(serverKeys, e.ServerKey()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
assertError: func(t *testing.T, err error) {
|
assertError: func(t *testing.T, err error) {
|
||||||
|
|
|
@ -49,6 +49,11 @@ type ennoblementRepository interface {
|
||||||
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
|
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tribeChangeRepository interface {
|
||||||
|
Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error
|
||||||
|
List(ctx context.Context, params domain.ListTribeChangesParams) (domain.TribeChanges, error)
|
||||||
|
}
|
||||||
|
|
||||||
type repositories struct {
|
type repositories struct {
|
||||||
version versionRepository
|
version versionRepository
|
||||||
server serverRepository
|
server serverRepository
|
||||||
|
@ -56,6 +61,7 @@ type repositories struct {
|
||||||
player playerRepository
|
player playerRepository
|
||||||
village villageRepository
|
village villageRepository
|
||||||
ennoblement ennoblementRepository
|
ennoblement ennoblementRepository
|
||||||
|
tribeChange tribeChangeRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
||||||
|
@ -70,5 +76,6 @@ func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
|
||||||
player: adapter.NewPlayerBunRepository(bunDB),
|
player: adapter.NewPlayerBunRepository(bunDB),
|
||||||
village: adapter.NewVillageBunRepository(bunDB),
|
village: adapter.NewVillageBunRepository(bunDB),
|
||||||
ennoblement: adapter.NewEnnoblementBunRepository(bunDB),
|
ennoblement: adapter.NewEnnoblementBunRepository(bunDB),
|
||||||
|
tribeChange: adapter.NewTribeChangeBunRepository(bunDB),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,272 @@
|
||||||
|
package adapter_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testTribeChangeRepository(t *testing.T, newRepos func(t *testing.T) repositories) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
t.Run("Create", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
repos := newRepos(t)
|
||||||
|
|
||||||
|
assertCreated := func(t *testing.T, params []domain.CreateTribeChangeParams) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.NotEmpty(t, params)
|
||||||
|
|
||||||
|
listParams := domain.NewListTribeChangesParams()
|
||||||
|
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
|
||||||
|
|
||||||
|
tribeChanges, err := repos.tribeChange.List(ctx, listParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for i, p := range params {
|
||||||
|
idx := slices.IndexFunc(tribeChanges, func(tc domain.TribeChange) bool {
|
||||||
|
return tc.ServerKey() == p.ServerKey() &&
|
||||||
|
tc.PlayerID() == p.PlayerID() &&
|
||||||
|
tc.OldTribeID() == p.OldTribeID() &&
|
||||||
|
tc.NewTribeID() == p.NewTribeID()
|
||||||
|
})
|
||||||
|
require.GreaterOrEqualf(t, idx, 0, "params[%d] not found", i)
|
||||||
|
tribeChange := tribeChanges[idx]
|
||||||
|
|
||||||
|
assert.Equalf(t, p.ServerKey(), tribeChange.ServerKey(), "params[%d]", i)
|
||||||
|
assert.Equalf(t, p.PlayerID(), tribeChange.PlayerID(), "params[%d]", i)
|
||||||
|
assert.Equalf(t, p.OldTribeID(), tribeChange.OldTribeID(), "params[%d]", i)
|
||||||
|
assert.Equalf(t, p.NewTribeID(), tribeChange.NewTribeID(), "params[%d]", i)
|
||||||
|
assert.WithinDurationf(t, time.Now(), tribeChange.CreatedAt(), time.Minute, "params[%d]", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoDuplicates := func(t *testing.T, params []domain.CreateTribeChangeParams) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.NotEmpty(t, params)
|
||||||
|
|
||||||
|
listParams := domain.NewListTribeChangesParams()
|
||||||
|
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
|
||||||
|
|
||||||
|
tribeChanges, err := repos.tribeChange.List(ctx, listParams)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res := make(map[string][]int)
|
||||||
|
|
||||||
|
for _, p := range params {
|
||||||
|
key := fmt.Sprintf("%s-%d-%d-%d", p.ServerKey(), p.PlayerID(), p.OldTribeID(), p.NewTribeID())
|
||||||
|
|
||||||
|
for i, tc := range tribeChanges {
|
||||||
|
//nolint:lll
|
||||||
|
if tc.ServerKey() == p.ServerKey() && tc.PlayerID() == p.PlayerID() && tc.OldTribeID() == p.OldTribeID() && tc.NewTribeID() == p.NewTribeID() {
|
||||||
|
res[key] = append(res[key], i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, indexes := range res {
|
||||||
|
assert.Len(t, indexes, 1, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
players, err := repos.player.List(ctx, domain.NewListPlayersParams())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, players)
|
||||||
|
player := players[0]
|
||||||
|
|
||||||
|
p1, err := domain.NewCreateTribeChangeParams(
|
||||||
|
player.ServerKey(),
|
||||||
|
player.ID(),
|
||||||
|
0,
|
||||||
|
domaintest.RandID(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p2, err := domain.NewCreateTribeChangeParams(
|
||||||
|
player.ServerKey(),
|
||||||
|
player.ID(),
|
||||||
|
p1.OldTribeID(),
|
||||||
|
player.TribeID(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p3, err := domain.NewCreateTribeChangeParams(
|
||||||
|
player.ServerKey(),
|
||||||
|
player.ID(),
|
||||||
|
p2.NewTribeID(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
createParams := []domain.CreateTribeChangeParams{
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
p3,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, repos.tribeChange.Create(ctx, createParams...))
|
||||||
|
assertCreated(t, createParams)
|
||||||
|
|
||||||
|
require.NoError(t, repos.tribeChange.Create(ctx, createParams...))
|
||||||
|
assertNoDuplicates(t, createParams)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OK: len(params) == 0", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
require.NoError(t, repos.tribeChange.Create(ctx))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("List & ListCount", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
repos := newRepos(t)
|
||||||
|
|
||||||
|
tribeChanges, listTribeChangesErr := repos.tribeChange.List(ctx, domain.NewListTribeChangesParams())
|
||||||
|
require.NoError(t, listTribeChangesErr)
|
||||||
|
require.NotEmpty(t, tribeChanges)
|
||||||
|
randTribeChange := tribeChanges[0]
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
params func(t *testing.T) domain.ListTribeChangesParams
|
||||||
|
assertTribeChanges func(t *testing.T, params domain.ListTribeChangesParams, tribeChanges domain.TribeChanges)
|
||||||
|
assertError func(t *testing.T, err error)
|
||||||
|
assertTotal func(t *testing.T, params domain.ListTribeChangesParams, total int)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK: default params",
|
||||||
|
params: func(t *testing.T) domain.ListTribeChangesParams {
|
||||||
|
t.Helper()
|
||||||
|
return domain.NewListTribeChangesParams()
|
||||||
|
},
|
||||||
|
assertTribeChanges: func(t *testing.T, params domain.ListTribeChangesParams, tribeChanges domain.TribeChanges) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, len(tribeChanges))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribeChanges, func(a, b domain.TribeChange) int {
|
||||||
|
if x := cmp.Compare(a.ServerKey(), b.ServerKey()); x != 0 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return a.CreatedAt().Compare(b.CreatedAt())
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
assertTotal: func(t *testing.T, params domain.ListTribeChangesParams, total int) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, total)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: sort=[serverKey DESC, createdAt DESC]",
|
||||||
|
params: func(t *testing.T) domain.ListTribeChangesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
require.NoError(t, params.SetSort([]domain.TribeChangeSort{
|
||||||
|
domain.TribeChangeSortServerKeyDESC,
|
||||||
|
domain.TribeChangeSortCreatedAtDESC,
|
||||||
|
}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertTribeChanges: func(t *testing.T, params domain.ListTribeChangesParams, tribeChanges domain.TribeChanges) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, len(tribeChanges))
|
||||||
|
assert.True(t, slices.IsSortedFunc(tribeChanges, func(a, b domain.TribeChange) int {
|
||||||
|
if x := cmp.Compare(a.ServerKey(), b.ServerKey()) * -1; x != 0 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return a.CreatedAt().Compare(b.CreatedAt()) * -1
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
assertTotal: func(t *testing.T, params domain.ListTribeChangesParams, total int) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, total)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fmt.Sprintf("OK: serverKeys=[%s]", randTribeChange.ServerKey()),
|
||||||
|
params: func(t *testing.T) domain.ListTribeChangesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
require.NoError(t, params.SetServerKeys([]string{randTribeChange.ServerKey()}))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertTribeChanges: func(t *testing.T, params domain.ListTribeChangesParams, tribeChanges domain.TribeChanges) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
serverKeys := params.ServerKeys()
|
||||||
|
|
||||||
|
for _, tc := range tribeChanges {
|
||||||
|
assert.True(t, slices.Contains(serverKeys, tc.ServerKey()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
assertTotal: func(t *testing.T, params domain.ListTribeChangesParams, total int) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, total)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OK: offset=1 limit=2",
|
||||||
|
params: func(t *testing.T) domain.ListTribeChangesParams {
|
||||||
|
t.Helper()
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
require.NoError(t, params.SetOffset(1))
|
||||||
|
require.NoError(t, params.SetLimit(2))
|
||||||
|
return params
|
||||||
|
},
|
||||||
|
assertTribeChanges: func(t *testing.T, params domain.ListTribeChangesParams, tribeChanges domain.TribeChanges) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Len(t, tribeChanges, params.Limit())
|
||||||
|
},
|
||||||
|
assertError: func(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
|
require.NoError(t, err)
|
||||||
|
},
|
||||||
|
assertTotal: func(t *testing.T, params domain.ListTribeChangesParams, total int) {
|
||||||
|
t.Helper()
|
||||||
|
assert.NotEmpty(t, total)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
params := tt.params(t)
|
||||||
|
|
||||||
|
res, err := repos.tribeChange.List(ctx, params)
|
||||||
|
tt.assertError(t, err)
|
||||||
|
tt.assertTribeChanges(t, params, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -7330,3 +7330,47 @@
|
||||||
old_tribe_id: 31
|
old_tribe_id: 31
|
||||||
points: 5123
|
points: 5123
|
||||||
created_at: 2022-04-22T15:00:10.000Z
|
created_at: 2022-04-22T15:00:10.000Z
|
||||||
|
- model: TribeChange
|
||||||
|
rows:
|
||||||
|
- _id: de188-1577279214-0-772-1
|
||||||
|
id: 10000
|
||||||
|
player_id: 1577279214
|
||||||
|
old_tribe_id: 0
|
||||||
|
new_tribe_id: 772
|
||||||
|
server_key: de188
|
||||||
|
created_at: 2021-06-25T12:00:53.000Z
|
||||||
|
- _id: de188-1577279214-772-0
|
||||||
|
id: 10001
|
||||||
|
player_id: 1577279214
|
||||||
|
old_tribe_id: 772
|
||||||
|
new_tribe_id: 0
|
||||||
|
server_key: de188
|
||||||
|
created_at: 2021-06-30T12:00:53.000Z
|
||||||
|
- _id: de188-1577279214-0-772-2
|
||||||
|
id: 10002
|
||||||
|
player_id: 1577279214
|
||||||
|
old_tribe_id: 0
|
||||||
|
new_tribe_id: 772
|
||||||
|
server_key: de188
|
||||||
|
created_at: 2021-07-01T12:00:53.000Z
|
||||||
|
- _id: de188-2572835-0-772
|
||||||
|
id: 10010
|
||||||
|
player_id: 2572835
|
||||||
|
old_tribe_id: 0
|
||||||
|
new_tribe_id: 772
|
||||||
|
server_key: de188
|
||||||
|
created_at: 2021-02-25T18:01:02.000Z
|
||||||
|
- _id: pl169-6180190-0-27
|
||||||
|
id: 10100
|
||||||
|
player_id: 6180190
|
||||||
|
old_tribe_id: 0
|
||||||
|
new_tribe_id: 27
|
||||||
|
server_key: pl169
|
||||||
|
created_at: 2021-09-04T21:01:03.000Z
|
||||||
|
- _id: pl169-8419570-0-2
|
||||||
|
id: 10110
|
||||||
|
player_id: 8419570
|
||||||
|
old_tribe_id: 0
|
||||||
|
new_tribe_id: 2
|
||||||
|
server_key: pl169
|
||||||
|
created_at: 2021-09-10T20:01:11.000Z
|
||||||
|
|
|
@ -18,13 +18,19 @@ type PlayerRepository interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerService struct {
|
type PlayerService struct {
|
||||||
repo PlayerRepository
|
repo PlayerRepository
|
||||||
twSvc TWService
|
tribeChangeSvc *TribeChangeService
|
||||||
pub PlayerPublisher
|
twSvc TWService
|
||||||
|
pub PlayerPublisher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayerService(repo PlayerRepository, twSvc TWService, pub PlayerPublisher) *PlayerService {
|
func NewPlayerService(
|
||||||
return &PlayerService{repo: repo, twSvc: twSvc, pub: pub}
|
repo PlayerRepository,
|
||||||
|
tribeChangeSvc *TribeChangeService,
|
||||||
|
twSvc TWService,
|
||||||
|
pub PlayerPublisher,
|
||||||
|
) *PlayerService {
|
||||||
|
return &PlayerService{repo: repo, tribeChangeSvc: tribeChangeSvc, twSvc: twSvc, pub: pub}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.ServerSyncedEventPayload) error {
|
func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.ServerSyncedEventPayload) error {
|
||||||
|
@ -108,9 +114,19 @@ func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey str
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.repo.CreateOrUpdate(ctx, createParams...)
|
tribeChangesParams, err := domain.NewCreateTribeChangeParamsFromPlayers(serverKey, players, storedPlayers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.repo.CreateOrUpdate(ctx, createParams...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
func (svc *PlayerService) delete(ctx context.Context, serverKey string, players domain.BasePlayers) error {
|
func (svc *PlayerService) delete(ctx context.Context, serverKey string, players domain.BasePlayers) error {
|
||||||
listParams := domain.NewListPlayersParams()
|
listParams := domain.NewListPlayersParams()
|
||||||
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
|
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
|
||||||
|
@ -130,6 +146,7 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
|
||||||
}
|
}
|
||||||
|
|
||||||
var toDelete []int
|
var toDelete []int
|
||||||
|
var tribeChangesParams []domain.CreateTribeChangeParams
|
||||||
|
|
||||||
for {
|
for {
|
||||||
storedPlayers, err := svc.repo.List(ctx, listParams)
|
storedPlayers, err := svc.repo.List(ctx, listParams)
|
||||||
|
@ -141,7 +158,13 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
toDelete = append(toDelete, storedPlayers.Delete(players)...)
|
ids, params, err := storedPlayers.Delete(serverKey, players)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
toDelete = append(toDelete, ids...)
|
||||||
|
tribeChangesParams = append(tribeChangesParams, params...)
|
||||||
|
|
||||||
if err = listParams.SetIDGT(domain.NullInt{
|
if err = listParams.SetIDGT(domain.NullInt{
|
||||||
Value: storedPlayers[len(storedPlayers)-1].ID(),
|
Value: storedPlayers[len(storedPlayers)-1].ID(),
|
||||||
|
@ -151,5 +174,9 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.repo.Delete(ctx, serverKey, toDelete...)
|
if err := svc.repo.Delete(ctx, serverKey, toDelete...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TribeChangeRepository interface {
|
||||||
|
// Create persists tribe changes in a store (e.g. Postgres).
|
||||||
|
// If there is a similar tribe change, such changes are ignored.
|
||||||
|
// Similar means that there is a tribe change with the same player id,
|
||||||
|
// new tribe id and old tribe id and this tribe change was created within the same hour.
|
||||||
|
Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TribeChangeService struct {
|
||||||
|
repo TribeChangeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTribeChangeService(repo TribeChangeRepository) *TribeChangeService {
|
||||||
|
return &TribeChangeService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tribeChangeChunkSize = 500
|
||||||
|
)
|
||||||
|
|
||||||
|
func (svc *TribeChangeService) Create(ctx context.Context, params ...domain.CreateTribeChangeParams) error {
|
||||||
|
for i := 0; i < len(params); i += tribeChangeChunkSize {
|
||||||
|
end := i + tribeChangeChunkSize
|
||||||
|
if end > len(params) {
|
||||||
|
end = len(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.repo.Create(ctx, params[i:end]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ type PlayerConfig struct {
|
||||||
Points int
|
Points int
|
||||||
NumVillages int
|
NumVillages int
|
||||||
ServerKey string
|
ServerKey string
|
||||||
|
TribeID int
|
||||||
OD domain.OpponentsDefeated
|
OD domain.OpponentsDefeated
|
||||||
BestRank int
|
BestRank int
|
||||||
BestRankAt time.Time
|
BestRankAt time.Time
|
||||||
|
@ -21,6 +22,7 @@ type PlayerConfig struct {
|
||||||
MostVillages int
|
MostVillages int
|
||||||
MostVillagesAt time.Time
|
MostVillagesAt time.Time
|
||||||
LastActivityAt time.Time
|
LastActivityAt time.Time
|
||||||
|
DeletedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
||||||
|
@ -33,6 +35,7 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
||||||
ServerKey: RandServerKey(),
|
ServerKey: RandServerKey(),
|
||||||
Points: gofakeit.IntRange(1, 10000),
|
Points: gofakeit.IntRange(1, 10000),
|
||||||
NumVillages: gofakeit.IntRange(1, 10000),
|
NumVillages: gofakeit.IntRange(1, 10000),
|
||||||
|
TribeID: RandID(),
|
||||||
OD: NewOpponentsDefeated(tb),
|
OD: NewOpponentsDefeated(tb),
|
||||||
BestRank: gofakeit.IntRange(1, 10000),
|
BestRank: gofakeit.IntRange(1, 10000),
|
||||||
BestRankAt: now,
|
BestRankAt: now,
|
||||||
|
@ -41,6 +44,7 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
||||||
MostVillages: gofakeit.IntRange(1, 10000),
|
MostVillages: gofakeit.IntRange(1, 10000),
|
||||||
MostVillagesAt: now,
|
MostVillagesAt: now,
|
||||||
LastActivityAt: now,
|
LastActivityAt: now,
|
||||||
|
DeletedAt: time.Time{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
@ -54,7 +58,7 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
||||||
cfg.NumVillages,
|
cfg.NumVillages,
|
||||||
cfg.Points,
|
cfg.Points,
|
||||||
gofakeit.IntRange(1, 10000),
|
gofakeit.IntRange(1, 10000),
|
||||||
gofakeit.IntRange(1, 10000),
|
cfg.TribeID,
|
||||||
cfg.OD,
|
cfg.OD,
|
||||||
gofakeit.URL(),
|
gofakeit.URL(),
|
||||||
cfg.BestRank,
|
cfg.BestRank,
|
||||||
|
@ -65,7 +69,7 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player {
|
||||||
cfg.MostVillagesAt,
|
cfg.MostVillagesAt,
|
||||||
cfg.LastActivityAt,
|
cfg.LastActivityAt,
|
||||||
now,
|
now,
|
||||||
time.Time{},
|
cfg.DeletedAt,
|
||||||
)
|
)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
|
|
@ -28,17 +28,19 @@ type TribeConfig struct {
|
||||||
func NewTribe(tb TestingTB, opts ...func(cfg *TribeConfig)) domain.Tribe {
|
func NewTribe(tb TestingTB, opts ...func(cfg *TribeConfig)) domain.Tribe {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
cfg := &TribeConfig{
|
cfg := &TribeConfig{
|
||||||
ID: RandID(),
|
ID: RandID(),
|
||||||
ServerKey: RandServerKey(),
|
ServerKey: RandServerKey(),
|
||||||
Tag: RandTribeTag(),
|
Tag: RandTribeTag(),
|
||||||
OD: NewOpponentsDefeated(tb),
|
OD: NewOpponentsDefeated(tb),
|
||||||
BestRank: gofakeit.IntRange(1, 10000),
|
BestRank: gofakeit.IntRange(1, 10000),
|
||||||
BestRankAt: time.Now(),
|
BestRankAt: now,
|
||||||
MostPoints: gofakeit.IntRange(1, 10000),
|
MostPoints: gofakeit.IntRange(1, 10000),
|
||||||
MostPointsAt: time.Now(),
|
MostPointsAt: now,
|
||||||
MostVillages: gofakeit.IntRange(1, 10000),
|
MostVillages: gofakeit.IntRange(1, 10000),
|
||||||
MostVillagesAt: time.Now(),
|
MostVillagesAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
@ -64,7 +66,7 @@ func NewTribe(tb TestingTB, opts ...func(cfg *TribeConfig)) domain.Tribe {
|
||||||
cfg.MostPointsAt,
|
cfg.MostPointsAt,
|
||||||
cfg.MostVillages,
|
cfg.MostVillages,
|
||||||
cfg.MostVillagesAt,
|
cfg.MostVillagesAt,
|
||||||
time.Now(),
|
now,
|
||||||
time.Time{},
|
time.Time{},
|
||||||
)
|
)
|
||||||
require.NoError(tb, err)
|
require.NoError(tb, err)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package domaintest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TribeChangeConfig struct {
|
||||||
|
ID int
|
||||||
|
ServerKey string
|
||||||
|
PlayerID int
|
||||||
|
OldTribeID int
|
||||||
|
NewTribeID int
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTribeChange(tb TestingTB, opts ...func(cfg *TribeChangeConfig)) domain.TribeChange {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
cfg := &TribeChangeConfig{
|
||||||
|
ID: RandID(),
|
||||||
|
ServerKey: RandServerKey(),
|
||||||
|
PlayerID: RandID(),
|
||||||
|
OldTribeID: RandID(),
|
||||||
|
NewTribeID: RandID(),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
tc, err := domain.UnmarshalTribeChangeFromDatabase(
|
||||||
|
cfg.ID,
|
||||||
|
cfg.ServerKey,
|
||||||
|
cfg.PlayerID,
|
||||||
|
cfg.OldTribeID,
|
||||||
|
cfg.NewTribeID,
|
||||||
|
cfg.CreatedAt,
|
||||||
|
)
|
||||||
|
require.NoError(tb, err)
|
||||||
|
|
||||||
|
return tc
|
||||||
|
}
|
|
@ -199,13 +199,21 @@ func (p Player) IsDeleted() bool {
|
||||||
|
|
||||||
type Players []Player
|
type Players []Player
|
||||||
|
|
||||||
// Delete finds all players that are not in the given slice with active players and returns their ids.
|
// Delete finds all players with the given serverKey that are not in the given slice with active players
|
||||||
|
// and returns their ids + tribe changes that must be created.
|
||||||
// Both slices must be sorted in ascending order by ID.
|
// Both slices must be sorted in ascending order by ID.
|
||||||
func (ps Players) Delete(active BasePlayers) []int {
|
// + if ps contains players from different servers. they must be sorted in ascending order by server key.
|
||||||
|
func (ps Players) Delete(serverKey string, active BasePlayers) ([]int, []CreateTribeChangeParams, error) {
|
||||||
|
// players are deleted now and then, there is no point in prereallocating these slices
|
||||||
//nolint:prealloc
|
//nolint:prealloc
|
||||||
var toDelete []int
|
var toDelete []int
|
||||||
|
var params []CreateTribeChangeParams
|
||||||
|
|
||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
|
if p.IsDeleted() || p.ServerKey() != serverKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
_, found := slices.BinarySearchFunc(active, p, func(a BasePlayer, b Player) int {
|
_, found := slices.BinarySearchFunc(active, p, func(a BasePlayer, b Player) int {
|
||||||
return cmp.Compare(a.ID(), b.ID())
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
})
|
})
|
||||||
|
@ -214,9 +222,18 @@ func (ps Players) Delete(active BasePlayers) []int {
|
||||||
}
|
}
|
||||||
|
|
||||||
toDelete = append(toDelete, p.ID())
|
toDelete = append(toDelete, p.ID())
|
||||||
|
|
||||||
|
if p.TribeID() > 0 {
|
||||||
|
p, err := NewCreateTribeChangeParams(serverKey, p.ID(), p.TribeID(), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params = append(params, p)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return toDelete
|
return toDelete, params, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePlayerParams struct {
|
type CreatePlayerParams struct {
|
||||||
|
|
|
@ -23,24 +23,54 @@ func TestPlayers_Delete(t *testing.T) {
|
||||||
domaintest.NewBasePlayer(t),
|
domaintest.NewBasePlayer(t),
|
||||||
domaintest.NewBasePlayer(t),
|
domaintest.NewBasePlayer(t),
|
||||||
}
|
}
|
||||||
slices.SortFunc(active, func(a, b domain.BasePlayer) int {
|
|
||||||
return cmp.Compare(a.ID(), b.ID())
|
|
||||||
})
|
|
||||||
|
|
||||||
players := domain.Players{
|
players := domain.Players{
|
||||||
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
cfg.ID = active[0].ID()
|
cfg.ID = active[0].ID()
|
||||||
cfg.ServerKey = server.Key()
|
cfg.ServerKey = server.Key()
|
||||||
}),
|
}),
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) { // should be deleted
|
||||||
|
cfg.ServerKey = server.Key()
|
||||||
|
cfg.TribeID = 0
|
||||||
|
}),
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) { // should be deleted + tribe change
|
||||||
|
cfg.ServerKey = server.Key()
|
||||||
|
}),
|
||||||
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
cfg.ServerKey = server.Key()
|
cfg.ServerKey = server.Key()
|
||||||
|
cfg.DeletedAt = time.Now()
|
||||||
}),
|
}),
|
||||||
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
cfg.ID = active[1].ID()
|
cfg.ID = active[1].ID()
|
||||||
cfg.ServerKey = server.Key()
|
cfg.ServerKey = server.Key()
|
||||||
}),
|
}),
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
|
cfg.ID = active[0].ID()
|
||||||
|
cfg.ServerKey = domaintest.RandServerKey()
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
expectedIDs := []int{players[1].ID()}
|
|
||||||
|
expectedIDs := []int{players[1].ID(), players[2].ID()}
|
||||||
|
expectedCreateTribeChangeParams := []struct {
|
||||||
|
serverKey string
|
||||||
|
playerID int
|
||||||
|
oldTribeID int
|
||||||
|
newTribeID int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
serverKey: server.Key(),
|
||||||
|
playerID: players[2].ID(),
|
||||||
|
oldTribeID: players[2].TribeID(),
|
||||||
|
newTribeID: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(expectedIDs)
|
||||||
|
|
||||||
|
slices.SortFunc(active, func(a, b domain.BasePlayer) int {
|
||||||
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
|
})
|
||||||
|
|
||||||
slices.SortFunc(players, func(a, b domain.Player) int {
|
slices.SortFunc(players, func(a, b domain.Player) int {
|
||||||
if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
|
if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
|
||||||
return res
|
return res
|
||||||
|
@ -48,7 +78,23 @@ func TestPlayers_Delete(t *testing.T) {
|
||||||
return cmp.Compare(a.ID(), b.ID())
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, expectedIDs, players.Delete(active))
|
ids, tribeChangesParams, err := players.Delete(server.Key(), active)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedIDs, ids)
|
||||||
|
assert.Len(t, tribeChangesParams, len(expectedCreateTribeChangeParams))
|
||||||
|
for i, expected := range expectedCreateTribeChangeParams {
|
||||||
|
idx := slices.IndexFunc(tribeChangesParams, func(params domain.CreateTribeChangeParams) bool {
|
||||||
|
return params.PlayerID() == expected.playerID && params.ServerKey() == expected.serverKey
|
||||||
|
})
|
||||||
|
require.GreaterOrEqualf(t, idx, 0, "expectedCreateTribeChangeParams[%d] not found", i)
|
||||||
|
|
||||||
|
params := tribeChangesParams[idx]
|
||||||
|
|
||||||
|
assert.Equalf(t, expected.serverKey, params.ServerKey(), "expectedCreateTribeChangeParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.playerID, params.PlayerID(), "expectedCreateTribeChangeParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.newTribeID, params.NewTribeID(), "expectedCreateTribeChangeParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.oldTribeID, params.OldTribeID(), "expectedCreateTribeChangeParams[%d]", i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCreatePlayerParams(t *testing.T) {
|
func TestNewCreatePlayerParams(t *testing.T) {
|
||||||
|
@ -125,6 +171,7 @@ func TestNewCreatePlayerParams(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
base: players[0],
|
base: players[0],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: players[0].Rank(),
|
bestRank: players[0].Rank(),
|
||||||
bestRankAt: now,
|
bestRankAt: now,
|
||||||
mostPoints: players[0].Points(),
|
mostPoints: players[0].Points(),
|
||||||
|
@ -135,6 +182,7 @@ func TestNewCreatePlayerParams(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
base: players[1],
|
base: players[1],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: storedPlayers[2].BestRank(),
|
bestRank: storedPlayers[2].BestRank(),
|
||||||
bestRankAt: storedPlayers[2].BestRankAt(),
|
bestRankAt: storedPlayers[2].BestRankAt(),
|
||||||
mostPoints: storedPlayers[2].MostPoints(),
|
mostPoints: storedPlayers[2].MostPoints(),
|
||||||
|
@ -145,6 +193,7 @@ func TestNewCreatePlayerParams(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
base: players[2],
|
base: players[2],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: players[2].Rank(),
|
bestRank: players[2].Rank(),
|
||||||
bestRankAt: now,
|
bestRankAt: now,
|
||||||
mostPoints: players[2].Points(),
|
mostPoints: players[2].Points(),
|
||||||
|
@ -160,13 +209,14 @@ func TestNewCreatePlayerParams(t *testing.T) {
|
||||||
assert.Len(t, res, len(expectedParams))
|
assert.Len(t, res, len(expectedParams))
|
||||||
for i, expected := range expectedParams {
|
for i, expected := range expectedParams {
|
||||||
idx := slices.IndexFunc(res, func(params domain.CreatePlayerParams) bool {
|
idx := slices.IndexFunc(res, func(params domain.CreatePlayerParams) bool {
|
||||||
return params.Base().ID() == expected.base.ID() && params.ServerKey() == server.Key()
|
return params.Base().ID() == expected.base.ID() && params.ServerKey() == expected.serverKey
|
||||||
})
|
})
|
||||||
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
||||||
|
|
||||||
params := res[idx]
|
params := res[idx]
|
||||||
|
|
||||||
assert.Equalf(t, expected.base, params.Base(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.base, params.Base(), "expectedParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.serverKey, params.ServerKey(), "expectedParams[%d]", i)
|
||||||
assert.Equalf(t, expected.bestRank, params.BestRank(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.bestRank, params.BestRank(), "expectedParams[%d]", i)
|
||||||
assert.WithinDurationf(t, expected.bestRankAt, params.BestRankAt(), time.Minute, "expectedParams[%d]", i)
|
assert.WithinDurationf(t, expected.bestRankAt, params.BestRankAt(), time.Minute, "expectedParams[%d]", i)
|
||||||
assert.Equalf(t, expected.mostPoints, params.MostPoints(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.mostPoints, params.MostPoints(), "expectedParams[%d]", i)
|
||||||
|
|
|
@ -0,0 +1,301 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TribeChange struct {
|
||||||
|
id int
|
||||||
|
serverKey string
|
||||||
|
playerID int
|
||||||
|
oldTribeID int
|
||||||
|
newTribeID int
|
||||||
|
createdAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
const tribeChangeModelName = "TribeChange"
|
||||||
|
|
||||||
|
// UnmarshalTribeChangeFromDatabase unmarshals TribeChange from the database.
|
||||||
|
//
|
||||||
|
// It should be used only for unmarshalling from the database!
|
||||||
|
// You can't use UnmarshalTribeChangeFromDatabase as constructor - It may put domain into the invalid state!
|
||||||
|
func UnmarshalTribeChangeFromDatabase(
|
||||||
|
id int,
|
||||||
|
serverKey string,
|
||||||
|
playerID int,
|
||||||
|
oldTribeID int,
|
||||||
|
newTribeID int,
|
||||||
|
createdAt time.Time,
|
||||||
|
) (TribeChange, error) {
|
||||||
|
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||||
|
return TribeChange{}, ValidationError{
|
||||||
|
Model: tribeChangeModelName,
|
||||||
|
Field: "id",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateServerKey(serverKey); err != nil {
|
||||||
|
return TribeChange{}, ValidationError{
|
||||||
|
Model: tribeChangeModelName,
|
||||||
|
Field: "serverKey",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TribeChange{
|
||||||
|
id: id,
|
||||||
|
playerID: playerID,
|
||||||
|
oldTribeID: oldTribeID,
|
||||||
|
newTribeID: newTribeID,
|
||||||
|
serverKey: serverKey,
|
||||||
|
createdAt: createdAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) ID() int {
|
||||||
|
return tc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) PlayerID() int {
|
||||||
|
return tc.playerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) OldTribeID() int {
|
||||||
|
return tc.oldTribeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) NewTribeID() int {
|
||||||
|
return tc.newTribeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) ServerKey() string {
|
||||||
|
return tc.serverKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc TribeChange) CreatedAt() time.Time {
|
||||||
|
return tc.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
type TribeChanges []TribeChange
|
||||||
|
|
||||||
|
type CreateTribeChangeParams struct {
|
||||||
|
serverKey string
|
||||||
|
playerID int
|
||||||
|
newTribeID int
|
||||||
|
oldTribeID int
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTribeChangeParamsModelName = "CreateTribeChangeParams"
|
||||||
|
|
||||||
|
func NewCreateTribeChangeParams(
|
||||||
|
serverKey string,
|
||||||
|
playerID int,
|
||||||
|
oldTribeID int,
|
||||||
|
newTribeID int,
|
||||||
|
) (CreateTribeChangeParams, error) {
|
||||||
|
if err := validateServerKey(serverKey); err != nil {
|
||||||
|
return CreateTribeChangeParams{}, ValidationError{
|
||||||
|
Model: createTribeChangeParamsModelName,
|
||||||
|
Field: "serverKey",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(playerID, 1, math.MaxInt); err != nil {
|
||||||
|
return CreateTribeChangeParams{}, ValidationError{
|
||||||
|
Model: createTribeChangeParamsModelName,
|
||||||
|
Field: "playerID",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
|
||||||
|
return CreateTribeChangeParams{}, ValidationError{
|
||||||
|
Model: createTribeChangeParamsModelName,
|
||||||
|
Field: "oldTribeID",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
|
||||||
|
return CreateTribeChangeParams{}, ValidationError{
|
||||||
|
Model: createTribeChangeParamsModelName,
|
||||||
|
Field: "newTribeID",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateTribeChangeParams{
|
||||||
|
serverKey: serverKey,
|
||||||
|
playerID: playerID,
|
||||||
|
oldTribeID: oldTribeID,
|
||||||
|
newTribeID: newTribeID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateTribeChangeParamsFromPlayers constructs a slice of CreatePlayerParams based on the given parameters.
|
||||||
|
// Both slices must be sorted in ascending order by ID
|
||||||
|
// + if storedPlayers contains players from different servers. they must be sorted in ascending order by server key.
|
||||||
|
func NewCreateTribeChangeParamsFromPlayers(
|
||||||
|
serverKey string,
|
||||||
|
players BasePlayers,
|
||||||
|
storedPlayers Players,
|
||||||
|
) ([]CreateTribeChangeParams, error) {
|
||||||
|
// tribe changes happens now and then, there is no point in prereallocating this slice
|
||||||
|
//nolint:prealloc
|
||||||
|
var params []CreateTribeChangeParams
|
||||||
|
|
||||||
|
for i, player := range players {
|
||||||
|
if player.IsZero() {
|
||||||
|
return nil, fmt.Errorf("players[%d] is an empty struct", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var old Player
|
||||||
|
idx, found := slices.BinarySearchFunc(storedPlayers, player, func(a Player, b BasePlayer) int {
|
||||||
|
if res := cmp.Compare(a.ServerKey(), serverKey); res != 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
old = storedPlayers[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old.ID() > 0 && old.TribeID() == player.TribeID()) || (old.ID() == 0 && player.TribeID() == 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := NewCreateTribeChangeParams(
|
||||||
|
serverKey,
|
||||||
|
player.ID(),
|
||||||
|
old.TribeID(),
|
||||||
|
player.TribeID(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params = append(params, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params CreateTribeChangeParams) ServerKey() string {
|
||||||
|
return params.serverKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params CreateTribeChangeParams) PlayerID() int {
|
||||||
|
return params.playerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params CreateTribeChangeParams) OldTribeID() int {
|
||||||
|
return params.oldTribeID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params CreateTribeChangeParams) NewTribeID() int {
|
||||||
|
return params.newTribeID
|
||||||
|
}
|
||||||
|
|
||||||
|
type TribeChangeSort uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TribeChangeSortCreatedAtASC TribeChangeSort = iota + 1
|
||||||
|
TribeChangeSortCreatedAtDESC
|
||||||
|
TribeChangeSortServerKeyASC
|
||||||
|
TribeChangeSortServerKeyDESC
|
||||||
|
)
|
||||||
|
|
||||||
|
const TribeChangeListMaxLimit = 200
|
||||||
|
|
||||||
|
type ListTribeChangesParams struct {
|
||||||
|
serverKeys []string
|
||||||
|
sort []TribeChangeSort
|
||||||
|
limit int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
const listTribeChangesParamsModelName = "ListTribeChangesParams"
|
||||||
|
|
||||||
|
func NewListTribeChangesParams() ListTribeChangesParams {
|
||||||
|
return ListTribeChangesParams{
|
||||||
|
sort: []TribeChangeSort{
|
||||||
|
TribeChangeSortServerKeyASC,
|
||||||
|
TribeChangeSortCreatedAtASC,
|
||||||
|
},
|
||||||
|
limit: TribeChangeListMaxLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) ServerKeys() []string {
|
||||||
|
return params.serverKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) SetServerKeys(serverKeys []string) error {
|
||||||
|
params.serverKeys = serverKeys
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) Sort() []TribeChangeSort {
|
||||||
|
return params.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tribeChangeSortMinLength = 1
|
||||||
|
tribeChangeSortMaxLength = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) SetSort(sort []TribeChangeSort) error {
|
||||||
|
if err := validateSliceLen(sort, tribeChangeSortMinLength, tribeChangeSortMaxLength); err != nil {
|
||||||
|
return ValidationError{
|
||||||
|
Model: listTribeChangesParamsModelName,
|
||||||
|
Field: "sort",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.sort = sort
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) Limit() int {
|
||||||
|
return params.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) SetLimit(limit int) error {
|
||||||
|
if err := validateIntInRange(limit, 1, TribeChangeListMaxLimit); err != nil {
|
||||||
|
return ValidationError{
|
||||||
|
Model: listTribeChangesParamsModelName,
|
||||||
|
Field: "limit",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.limit = limit
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) Offset() int {
|
||||||
|
return params.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (params *ListTribeChangesParams) SetOffset(offset int) error {
|
||||||
|
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
|
||||||
|
return ValidationError{
|
||||||
|
Model: listTribeChangesParamsModelName,
|
||||||
|
Field: "offset",
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params.offset = offset
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,407 @@
|
||||||
|
package domain_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmp"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||||
|
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCreateTribeChangeParams(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
validTribeChange := domaintest.NewTribeChange(t)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
serverKey string
|
||||||
|
playerID int
|
||||||
|
oldTribeID int
|
||||||
|
newTribeID int
|
||||||
|
}
|
||||||
|
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectedErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []test{
|
||||||
|
{
|
||||||
|
name: "OK",
|
||||||
|
args: args{
|
||||||
|
serverKey: validTribeChange.ServerKey(),
|
||||||
|
playerID: validTribeChange.PlayerID(),
|
||||||
|
oldTribeID: validTribeChange.OldTribeID(),
|
||||||
|
newTribeID: validTribeChange.NewTribeID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: playerID < 1",
|
||||||
|
args: args{
|
||||||
|
serverKey: validTribeChange.ServerKey(),
|
||||||
|
playerID: 0,
|
||||||
|
oldTribeID: validTribeChange.OldTribeID(),
|
||||||
|
newTribeID: validTribeChange.NewTribeID(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "CreateTribeChangeParams",
|
||||||
|
Field: "playerID",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 1,
|
||||||
|
Current: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: oldTribeID < 0",
|
||||||
|
args: args{
|
||||||
|
serverKey: validTribeChange.ServerKey(),
|
||||||
|
playerID: validTribeChange.PlayerID(),
|
||||||
|
oldTribeID: -1,
|
||||||
|
newTribeID: validTribeChange.NewTribeID(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "CreateTribeChangeParams",
|
||||||
|
Field: "oldTribeID",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: newTribeID < 0",
|
||||||
|
args: args{
|
||||||
|
serverKey: validTribeChange.ServerKey(),
|
||||||
|
playerID: validTribeChange.PlayerID(),
|
||||||
|
oldTribeID: validTribeChange.OldTribeID(),
|
||||||
|
newTribeID: -1,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "CreateTribeChangeParams",
|
||||||
|
Field: "newTribeID",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||||
|
tests = append(tests, test{
|
||||||
|
name: serverKeyTest.name,
|
||||||
|
args: args{
|
||||||
|
serverKey: serverKeyTest.key,
|
||||||
|
playerID: validTribeChange.PlayerID(),
|
||||||
|
oldTribeID: validTribeChange.OldTribeID(),
|
||||||
|
newTribeID: validTribeChange.NewTribeID(),
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "CreateTribeChangeParams",
|
||||||
|
Field: "serverKey",
|
||||||
|
Err: serverKeyTest.expectedErr,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
res, err := domain.NewCreateTribeChangeParams(
|
||||||
|
tt.args.serverKey,
|
||||||
|
tt.args.playerID,
|
||||||
|
tt.args.oldTribeID,
|
||||||
|
tt.args.newTribeID,
|
||||||
|
)
|
||||||
|
require.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
if tt.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.args.serverKey, res.ServerKey())
|
||||||
|
assert.Equal(t, tt.args.playerID, res.PlayerID())
|
||||||
|
assert.Equal(t, tt.args.oldTribeID, res.OldTribeID())
|
||||||
|
assert.Equal(t, tt.args.newTribeID, res.NewTribeID())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCreateTribeChangeParamsFromPlayers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
server := domaintest.NewServer(t)
|
||||||
|
|
||||||
|
players := domain.BasePlayers{
|
||||||
|
domaintest.NewBasePlayer(t),
|
||||||
|
domaintest.NewBasePlayer(t),
|
||||||
|
domaintest.NewBasePlayer(t),
|
||||||
|
domaintest.NewBasePlayer(t, func(cfg *domaintest.BasePlayerConfig) {
|
||||||
|
cfg.TribeID = 0
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
storedPlayers := domain.Players{
|
||||||
|
// server with random server key to verify that slice order matters
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
|
cfg.ID = players[0].ID()
|
||||||
|
cfg.ServerKey = domaintest.RandServerKey()
|
||||||
|
}),
|
||||||
|
// new tribe id
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
|
cfg.ID = players[0].ID()
|
||||||
|
cfg.ServerKey = server.Key()
|
||||||
|
}),
|
||||||
|
// the same tribe id
|
||||||
|
domaintest.NewPlayer(t, func(cfg *domaintest.PlayerConfig) {
|
||||||
|
cfg.ID = players[1].ID()
|
||||||
|
cfg.ServerKey = server.Key()
|
||||||
|
cfg.TribeID = players[1].TribeID()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedParams := []struct {
|
||||||
|
serverKey string
|
||||||
|
playerID int
|
||||||
|
oldTribeID int
|
||||||
|
newTribeID int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
serverKey: server.Key(),
|
||||||
|
playerID: players[0].ID(),
|
||||||
|
oldTribeID: storedPlayers[1].TribeID(),
|
||||||
|
newTribeID: players[0].TribeID(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serverKey: server.Key(),
|
||||||
|
playerID: players[2].ID(),
|
||||||
|
oldTribeID: 0,
|
||||||
|
newTribeID: players[2].TribeID(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(players, func(a, b domain.BasePlayer) int {
|
||||||
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
|
})
|
||||||
|
|
||||||
|
slices.SortFunc(storedPlayers, func(a, b domain.Player) int {
|
||||||
|
if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
return cmp.Compare(a.ID(), b.ID())
|
||||||
|
})
|
||||||
|
|
||||||
|
res, err := domain.NewCreateTribeChangeParamsFromPlayers(server.Key(), players, storedPlayers)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, res, len(expectedParams))
|
||||||
|
for i, expected := range expectedParams {
|
||||||
|
idx := slices.IndexFunc(res, func(params domain.CreateTribeChangeParams) bool {
|
||||||
|
return params.PlayerID() == expected.playerID && params.ServerKey() == expected.serverKey
|
||||||
|
})
|
||||||
|
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
||||||
|
|
||||||
|
params := res[idx]
|
||||||
|
|
||||||
|
assert.Equalf(t, expected.serverKey, params.ServerKey(), "expectedParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.playerID, params.PlayerID(), "expectedParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.newTribeID, params.NewTribeID(), "expectedParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.oldTribeID, params.OldTribeID(), "expectedParams[%d]", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListTribeChangesParams_SetSort(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
sort []domain.TribeChangeSort
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK",
|
||||||
|
args: args{
|
||||||
|
sort: []domain.TribeChangeSort{
|
||||||
|
domain.TribeChangeSortCreatedAtASC,
|
||||||
|
domain.TribeChangeSortServerKeyASC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) < 1",
|
||||||
|
args: args{
|
||||||
|
sort: nil,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListTribeChangesParams",
|
||||||
|
Field: "sort",
|
||||||
|
Err: domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 2,
|
||||||
|
Current: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: len(sort) > 2",
|
||||||
|
args: args{
|
||||||
|
sort: []domain.TribeChangeSort{
|
||||||
|
domain.TribeChangeSortCreatedAtASC,
|
||||||
|
domain.TribeChangeSortServerKeyASC,
|
||||||
|
domain.TribeChangeSortServerKeyDESC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListTribeChangesParams",
|
||||||
|
Field: "sort",
|
||||||
|
Err: domain.LenOutOfRangeError{
|
||||||
|
Min: 1,
|
||||||
|
Max: 2,
|
||||||
|
Current: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
|
||||||
|
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
|
||||||
|
if tt.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.args.sort, params.Sort())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListTribeChangesParams_SetLimit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK",
|
||||||
|
args: args{
|
||||||
|
limit: domain.TribeChangeListMaxLimit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: limit < 1",
|
||||||
|
args: args{
|
||||||
|
limit: 0,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListTribeChangesParams",
|
||||||
|
Field: "limit",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 1,
|
||||||
|
Current: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: fmt.Sprintf("ERR: limit > %d", domain.TribeChangeListMaxLimit),
|
||||||
|
args: args{
|
||||||
|
limit: domain.TribeChangeListMaxLimit + 1,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListTribeChangesParams",
|
||||||
|
Field: "limit",
|
||||||
|
Err: domain.MaxLessEqualError{
|
||||||
|
Max: domain.TribeChangeListMaxLimit,
|
||||||
|
Current: domain.TribeChangeListMaxLimit + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
|
||||||
|
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
|
||||||
|
if tt.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.args.limit, params.Limit())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListTribeChangesParams_SetOffset(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK",
|
||||||
|
args: args{
|
||||||
|
offset: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ERR: offset < 0",
|
||||||
|
args: args{
|
||||||
|
offset: -1,
|
||||||
|
},
|
||||||
|
expectedErr: domain.ValidationError{
|
||||||
|
Model: "ListTribeChangesParams",
|
||||||
|
Field: "offset",
|
||||||
|
Err: domain.MinGreaterEqualError{
|
||||||
|
Min: 0,
|
||||||
|
Current: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
params := domain.NewListTribeChangesParams()
|
||||||
|
|
||||||
|
require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
|
||||||
|
if tt.expectedErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.args.offset, params.Offset())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,7 @@ func TestNewCreateTribeParams(t *testing.T) {
|
||||||
|
|
||||||
expectedParams := []struct {
|
expectedParams := []struct {
|
||||||
base domain.BaseTribe
|
base domain.BaseTribe
|
||||||
|
serverKey string
|
||||||
bestRank int
|
bestRank int
|
||||||
bestRankAt time.Time
|
bestRankAt time.Time
|
||||||
mostPoints int
|
mostPoints int
|
||||||
|
@ -112,6 +113,7 @@ func TestNewCreateTribeParams(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
base: tribes[0],
|
base: tribes[0],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: tribes[0].Rank(),
|
bestRank: tribes[0].Rank(),
|
||||||
bestRankAt: now,
|
bestRankAt: now,
|
||||||
mostPoints: tribes[0].AllPoints(),
|
mostPoints: tribes[0].AllPoints(),
|
||||||
|
@ -121,6 +123,7 @@ func TestNewCreateTribeParams(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
base: tribes[1],
|
base: tribes[1],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: storedTribes[2].BestRank(),
|
bestRank: storedTribes[2].BestRank(),
|
||||||
bestRankAt: storedTribes[2].BestRankAt(),
|
bestRankAt: storedTribes[2].BestRankAt(),
|
||||||
mostPoints: storedTribes[2].MostPoints(),
|
mostPoints: storedTribes[2].MostPoints(),
|
||||||
|
@ -130,6 +133,7 @@ func TestNewCreateTribeParams(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
base: tribes[2],
|
base: tribes[2],
|
||||||
|
serverKey: server.Key(),
|
||||||
bestRank: tribes[2].Rank(),
|
bestRank: tribes[2].Rank(),
|
||||||
bestRankAt: now,
|
bestRankAt: now,
|
||||||
mostPoints: tribes[2].AllPoints(),
|
mostPoints: tribes[2].AllPoints(),
|
||||||
|
@ -144,13 +148,14 @@ func TestNewCreateTribeParams(t *testing.T) {
|
||||||
assert.Len(t, res, len(expectedParams))
|
assert.Len(t, res, len(expectedParams))
|
||||||
for i, expected := range expectedParams {
|
for i, expected := range expectedParams {
|
||||||
idx := slices.IndexFunc(res, func(params domain.CreateTribeParams) bool {
|
idx := slices.IndexFunc(res, func(params domain.CreateTribeParams) bool {
|
||||||
return params.Base().ID() == expected.base.ID() && params.ServerKey() == server.Key()
|
return params.Base().ID() == expected.base.ID() && params.ServerKey() == expected.serverKey
|
||||||
})
|
})
|
||||||
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
require.GreaterOrEqualf(t, idx, 0, "expectedParams[%d] not found", i)
|
||||||
|
|
||||||
params := res[idx]
|
params := res[idx]
|
||||||
|
|
||||||
assert.Equalf(t, expected.base, params.Base(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.base, params.Base(), "expectedParams[%d]", i)
|
||||||
|
assert.Equalf(t, expected.serverKey, params.ServerKey(), "expectedParams[%d]", i)
|
||||||
assert.Equalf(t, expected.bestRank, params.BestRank(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.bestRank, params.BestRank(), "expectedParams[%d]", i)
|
||||||
assert.WithinDurationf(t, expected.bestRankAt, params.BestRankAt(), time.Minute, "expectedParams[%d]", i)
|
assert.WithinDurationf(t, expected.bestRankAt, params.BestRankAt(), time.Minute, "expectedParams[%d]", i)
|
||||||
assert.Equalf(t, expected.mostPoints, params.MostPoints(), "expectedParams[%d]", i)
|
assert.Equalf(t, expected.mostPoints, params.MostPoints(), "expectedParams[%d]", i)
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
_, err := db.ExecContext(ctx, `
|
_, err := db.ExecContext(ctx, `
|
||||||
create table if not exists ennoblements
|
create table if not exists ennoblements
|
||||||
(
|
(
|
||||||
?,
|
?ID_COL,
|
||||||
server_key varchar(100) not null
|
server_key varchar(100) not null
|
||||||
references servers,
|
references servers,
|
||||||
village_id bigint not null,
|
village_id bigint not null,
|
||||||
|
@ -40,7 +40,7 @@ create index if not exists ennoblements_server_key_new_tribe_id_idx
|
||||||
|
|
||||||
create index if not exists ennoblements_server_key_old_tribe_id_idx
|
create index if not exists ennoblements_server_key_old_tribe_id_idx
|
||||||
on ennoblements (server_key, old_tribe_id);
|
on ennoblements (server_key, old_tribe_id);
|
||||||
`, bun.Safe(autoincrementIDColumn(db)))
|
`)
|
||||||
return err
|
return err
|
||||||
}, func(ctx context.Context, db *bun.DB) error {
|
}, func(ctx context.Context, db *bun.DB) error {
|
||||||
_, err := db.ExecContext(ctx, "drop table if exists ennoblements cascade;")
|
_, err := db.ExecContext(ctx, "drop table if exists ennoblements cascade;")
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
_, err := db.ExecContext(ctx, `
|
_, err := db.ExecContext(ctx, `
|
||||||
create table if not exists player_snapshots
|
create table if not exists player_snapshots
|
||||||
(
|
(
|
||||||
?,
|
?ID_COL,
|
||||||
player_id bigint not null,
|
player_id bigint not null,
|
||||||
num_villages bigint default 0,
|
num_villages bigint default 0,
|
||||||
points bigint default 0,
|
points bigint default 0,
|
||||||
|
@ -32,7 +32,7 @@ create table if not exists player_snapshots
|
||||||
unique (player_id, server_key, date),
|
unique (player_id, server_key, date),
|
||||||
foreign key (player_id, server_key) references players
|
foreign key (player_id, server_key) references players
|
||||||
);
|
);
|
||||||
`, bun.Safe(autoincrementIDColumn(db)))
|
`)
|
||||||
return err
|
return err
|
||||||
}, func(ctx context.Context, db *bun.DB) error {
|
}, func(ctx context.Context, db *bun.DB) error {
|
||||||
_, err := db.ExecContext(ctx, "drop table if exists player_snapshots cascade;")
|
_, err := db.ExecContext(ctx, "drop table if exists player_snapshots cascade;")
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
_, err := db.ExecContext(ctx, `
|
_, err := db.ExecContext(ctx, `
|
||||||
create table if not exists tribe_snapshots
|
create table if not exists tribe_snapshots
|
||||||
(
|
(
|
||||||
?,
|
?ID_COL,
|
||||||
tribe_id bigint not null,
|
tribe_id bigint not null,
|
||||||
server_key varchar(100) not null
|
server_key varchar(100) not null
|
||||||
references servers,
|
references servers,
|
||||||
|
@ -34,7 +34,7 @@ create table if not exists tribe_snapshots
|
||||||
unique (tribe_id, server_key, date),
|
unique (tribe_id, server_key, date),
|
||||||
foreign key (tribe_id, server_key) references tribes
|
foreign key (tribe_id, server_key) references tribes
|
||||||
);
|
);
|
||||||
`, bun.Safe(autoincrementIDColumn(db)))
|
`)
|
||||||
return err
|
return err
|
||||||
}, func(ctx context.Context, db *bun.DB) error {
|
}, func(ctx context.Context, db *bun.DB) error {
|
||||||
_, err := db.ExecContext(ctx, "drop table if exists tribe_snapshots cascade;")
|
_, err := db.ExecContext(ctx, "drop table if exists tribe_snapshots cascade;")
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
_, err := db.ExecContext(ctx, `
|
_, err := db.ExecContext(ctx, `
|
||||||
create table if not exists tribe_changes
|
create table if not exists tribe_changes
|
||||||
(
|
(
|
||||||
?,
|
?ID_COL,
|
||||||
player_id bigint not null,
|
player_id bigint not null,
|
||||||
new_tribe_id bigint,
|
new_tribe_id bigint,
|
||||||
old_tribe_id bigint,
|
old_tribe_id bigint,
|
||||||
|
@ -29,7 +29,7 @@ create index if not exists tribe_changes_server_key_new_tribe_id_idx
|
||||||
|
|
||||||
create index if not exists tribe_changes_server_key_old_tribe_id_idx
|
create index if not exists tribe_changes_server_key_old_tribe_id_idx
|
||||||
on tribe_changes (server_key, old_tribe_id);
|
on tribe_changes (server_key, old_tribe_id);
|
||||||
`, bun.Safe(autoincrementIDColumn(db)))
|
`)
|
||||||
return err
|
return err
|
||||||
}, func(ctx context.Context, db *bun.DB) error {
|
}, func(ctx context.Context, db *bun.DB) error {
|
||||||
_, err := db.ExecContext(ctx, "drop table if exists tribe_changes cascade;")
|
_, err := db.ExecContext(ctx, "drop table if exists tribe_changes cascade;")
|
||||||
|
|
|
@ -9,19 +9,29 @@ import (
|
||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
func init() {
|
func init() {
|
||||||
// this index is for Postgres only
|
|
||||||
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
||||||
if db.Dialect().Name() != dialect.PG {
|
var err error
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := db.ExecContext(
|
//nolint:exhaustive
|
||||||
ctx,
|
switch db.Dialect().Name() {
|
||||||
`create unique index concurrently if not exists tribe_changes_hash_key
|
case dialect.PG:
|
||||||
|
// hash_record_extended is Postgres specific
|
||||||
|
// https://dba.stackexchange.com/questions/299098/why-doesnt-my-unique-constraint-trigger/299107#299107
|
||||||
|
_, err = db.ExecContext(
|
||||||
|
ctx,
|
||||||
|
`create unique index concurrently if not exists tribe_changes_hash_key
|
||||||
on tribe_changes (hash_record_extended(
|
on tribe_changes (hash_record_extended(
|
||||||
ROW (player_id, new_tribe_id, old_tribe_id, server_key, date_trunc('hours'::text, (created_at AT TIME ZONE 'UTC'::text))),
|
ROW (player_id, new_tribe_id, old_tribe_id, server_key, date_trunc('hours'::text, (created_at AT TIME ZONE 'UTC'::text))),
|
||||||
0::bigint));`,
|
0::bigint));`,
|
||||||
)
|
)
|
||||||
|
case dialect.SQLite:
|
||||||
|
_, err = db.ExecContext(
|
||||||
|
ctx,
|
||||||
|
`create unique index if not exists tribe_changes_hash_key
|
||||||
|
on tribe_changes (server_key, coalesce(player_id, 0), coalesce(new_tribe_id, 0), coalesce(old_tribe_id, 0), strftime('%Y-%m-%d-%H', created_at));`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}, func(ctx context.Context, db *bun.DB) error {
|
}, func(ctx context.Context, db *bun.DB) error {
|
||||||
_, err := db.NewDropIndex().IfExists().Index("tribe_changes_hash_key").Concurrently().Exec(ctx)
|
_, err := db.NewDropIndex().IfExists().Index("tribe_changes_hash_key").Concurrently().Exec(ctx)
|
||||||
|
|
|
@ -2,11 +2,31 @@ package migrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
|
"github.com/uptrace/bun/dialect/feature"
|
||||||
"github.com/uptrace/bun/migrate"
|
"github.com/uptrace/bun/migrate"
|
||||||
|
"github.com/uptrace/bun/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var migrations = migrate.NewMigrations()
|
var migrations = migrate.NewMigrations()
|
||||||
|
|
||||||
func NewMigrator(db *bun.DB, opts ...migrate.MigratorOption) *migrate.Migrator {
|
func NewMigrator(db *bun.DB, opts ...migrate.MigratorOption) *migrate.Migrator {
|
||||||
return migrate.NewMigrator(db, migrations, opts...)
|
return migrate.NewMigrator(
|
||||||
|
db.WithNamedArg("ID_COL", autoincrementIDColumn(db)),
|
||||||
|
migrations,
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type hasFeaturer interface {
|
||||||
|
HasFeature(feat feature.Feature) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoincrementIDColumn(f hasFeaturer) schema.QueryAppender {
|
||||||
|
if f.HasFeature(feature.GeneratedIdentity) {
|
||||||
|
// postgres
|
||||||
|
return bun.Safe("id bigint GENERATED BY DEFAULT AS IDENTITY primary key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqlite
|
||||||
|
return bun.Safe("id INTEGER PRIMARY KEY")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package migrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/uptrace/bun/dialect/feature"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hasFeaturer interface {
|
|
||||||
HasFeature(feat feature.Feature) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func autoincrementIDColumn(f hasFeaturer) string {
|
|
||||||
if f.HasFeature(feature.GeneratedIdentity) {
|
|
||||||
// postgres
|
|
||||||
return "id bigint GENERATED BY DEFAULT AS IDENTITY primary key"
|
|
||||||
}
|
|
||||||
|
|
||||||
// sqlite
|
|
||||||
return "id INTEGER PRIMARY KEY"
|
|
||||||
}
|
|
Loading…
Reference in New Issue