feat: ennoblement sync (#28)

Reviewed-on: twhelp/corev3#28
This commit is contained in:
Dawid Wysokiński 2024-01-04 10:44:36 +00:00
parent 1c6624fa2d
commit 7a1e4dfb50
38 changed files with 1849 additions and 119 deletions

View File

@ -64,6 +64,7 @@ var cmdConsumer = &cli.Command{
c.String(rmqFlagTopicTribesSyncedEvent.Name), c.String(rmqFlagTopicTribesSyncedEvent.Name),
c.String(rmqFlagTopicPlayersSyncedEvent.Name), c.String(rmqFlagTopicPlayersSyncedEvent.Name),
c.String(rmqFlagTopicVillagesSyncedEvent.Name), c.String(rmqFlagTopicVillagesSyncedEvent.Name),
c.String(rmqFlagTopicEnnoblementsSyncedEvent.Name),
) )
consumer.Register(router) consumer.Register(router)
@ -194,6 +195,49 @@ var cmdConsumer = &cli.Command{
) )
consumer.Register(router) consumer.Register(router)
return nil
},
)
},
},
{
Name: "ennoblement",
Usage: "Run the worker responsible for consuming ennoblement-related messages",
Flags: concatSlices(dbFlags, rmqFlags, twSvcFlags),
Action: func(c *cli.Context) error {
return runConsumer(
c,
"EnnoblementConsumer",
func(
c *cli.Context,
router *message.Router,
logger watermill.LoggerAdapter,
publisher *amqp.Publisher,
subscriber *amqp.Subscriber,
marshaler watermillmsg.Marshaler,
db *bun.DB,
) error {
twSvc, err := newTWServiceFromFlags(c)
if err != nil {
return err
}
ennoblementPublisher := adapter.NewEnnoblementWatermillPublisher(
publisher,
marshaler,
c.String(rmqFlagTopicSyncEnnoblementsCmd.Name),
c.String(rmqFlagTopicEnnoblementsSyncedEvent.Name),
)
consumer := port.NewEnnoblementWatermillConsumer(
app.NewEnnoblementService(adapter.NewEnnoblementBunRepository(db), twSvc, ennoblementPublisher),
subscriber,
logger,
marshaler,
c.String(rmqFlagTopicSyncEnnoblementsCmd.Name),
)
consumer.Register(router)
return nil return nil
}, },
) )

View File

@ -22,6 +22,7 @@ func NewFixture(bunDB *bun.DB) *Fixture {
(*bunmodel.Tribe)(nil), (*bunmodel.Tribe)(nil),
(*bunmodel.Player)(nil), (*bunmodel.Player)(nil),
(*bunmodel.Village)(nil), (*bunmodel.Village)(nil),
(*bunmodel.Ennoblement)(nil),
) )
return &Fixture{ return &Fixture{
f: dbfixture.New(bunDB), f: dbfixture.New(bunDB),

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/tw" "gitea.dwysokinski.me/twhelp/corev3/internal/tw"
@ -282,3 +283,39 @@ func (t *TWHTTP) convertVillagesToDomain(villages []tw.Village) (domain.BaseVill
return res, nil return res, nil
} }
func (t *TWHTTP) GetEnnoblements(
ctx context.Context,
baseURL *url.URL,
since time.Time,
) (domain.BaseEnnoblements, error) {
ennoblements, err := t.client.GetEnnoblements(ctx, baseURL, since)
if err != nil {
return nil, err
}
return t.convertEnnoblementsToDomain(ennoblements)
}
func (t *TWHTTP) convertEnnoblementsToDomain(ennoblements []tw.Ennoblement) (domain.BaseEnnoblements, error) {
res := make(domain.BaseEnnoblements, 0, len(ennoblements))
for _, e := range ennoblements {
converted, err := domain.NewBaseEnnoblement(
e.VillageID,
e.NewOwnerID,
e.NewTribeID,
e.OldOwnerID,
e.OldTribeID,
e.Points,
e.CreatedAt,
)
if err != nil {
return nil, fmt.Errorf("couldn't construct domain.BaseEnnoblement: %w", err)
}
res = append(res, converted)
}
return res, nil
}

View File

@ -0,0 +1,69 @@
package bunmodel
import (
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/uptrace/bun"
)
type Ennoblement struct {
bun.BaseModel `bun:"table:ennoblements,alias:ennoblement"`
ID int `bun:"id,pk,autoincrement,identity"`
ServerKey string `bun:"server_key,nullzero"`
VillageID int `bun:"village_id,nullzero"`
Village Village `bun:"village,rel:belongs-to,join:village_id=id,join:server_key=server_key"`
NewOwnerID int `bun:"new_owner_id,nullzero"`
NewOwner Player `bun:"new_owner,rel:belongs-to,join:new_owner_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"`
OldOwnerID int `bun:"old_owner_id,nullzero"`
OldOwner Player `bun:"old_owner,rel:belongs-to,join:old_owner_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"`
Points int `bun:"points"`
CreatedAt time.Time `bun:"created_at,nullzero"`
}
func (e Ennoblement) ToDomain() (domain.Ennoblement, error) {
converted, err := domain.UnmarshalEnnoblementFromDatabase(
e.ID,
e.ServerKey,
e.VillageID,
e.NewOwnerID,
e.NewTribeID,
e.OldOwnerID,
e.OldTribeID,
e.Points,
e.CreatedAt,
)
if err != nil {
return domain.Ennoblement{}, fmt.Errorf(
"couldn't construct domain.Ennoblement (id=%d,serverKey=%s): %w",
e.ID,
e.ServerKey,
err,
)
}
return converted, nil
}
type Ennoblements []Ennoblement
func (es Ennoblements) ToDomain() (domain.Ennoblements, error) {
res := make(domain.Ennoblements, 0, len(es))
for _, e := range es {
converted, err := e.ToDomain()
if err != nil {
return nil, err
}
res = append(res, converted)
}
return res, nil
}

View File

@ -0,0 +1,96 @@
package adapter
import (
"context"
"database/sql"
"errors"
"fmt"
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/internal/bunmodel"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/uptrace/bun"
)
type EnnoblementBunRepository struct {
db bun.IDB
}
func NewEnnoblementBunRepository(db bun.IDB) *EnnoblementBunRepository {
return &EnnoblementBunRepository{db: db}
}
func (repo *EnnoblementBunRepository) Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error {
if len(params) == 0 {
return nil
}
ennoblements := make(bunmodel.Ennoblements, 0, len(params))
for _, p := range params {
base := p.Base()
ennoblements = append(ennoblements, bunmodel.Ennoblement{
ServerKey: p.ServerKey(),
VillageID: base.VillageID(),
NewOwnerID: base.NewOwnerID(),
NewTribeID: base.NewTribeID(),
OldOwnerID: base.OldOwnerID(),
OldTribeID: base.OldTribeID(),
Points: base.Points(),
CreatedAt: base.CreatedAt(),
})
}
if _, err := repo.db.NewInsert().
Model(&ennoblements).
Ignore().
Returning("").
Exec(ctx); err != nil {
return fmt.Errorf("something went wrong while inserting ennoblements into the db: %w", err)
}
return nil
}
func (repo *EnnoblementBunRepository) List(
ctx context.Context,
params domain.ListEnnoblementsParams,
) (domain.Ennoblements, error) {
var ennoblements bunmodel.Ennoblements
if err := repo.db.NewSelect().
Model(&ennoblements).
Apply(listEnnoblementsParamsApplier{params: params}.apply).
Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("couldn't select ennoblements from the db: %w", err)
}
return ennoblements.ToDomain()
}
type listEnnoblementsParamsApplier struct {
params domain.ListEnnoblementsParams
}
//nolint:gocyclo
func (a listEnnoblementsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 {
q = q.Where("ennoblement.server_key IN (?)", bun.In(serverKeys))
}
for _, s := range a.params.Sort() {
switch s {
case domain.EnnoblementSortCreatedAtASC:
q = q.Order("ennoblement.created_at ASC")
case domain.EnnoblementSortCreatedAtDESC:
q = q.Order("ennoblement.created_at DESC")
case domain.EnnoblementSortServerKeyASC:
q = q.Order("ennoblement.server_key ASC")
case domain.EnnoblementSortServerKeyDESC:
q = q.Order("ennoblement.server_key DESC")
default:
return q.Err(errors.New("unsupported sort value"))
}
}
return q.Limit(a.params.Limit()).Offset(a.params.Offset())
}

View File

@ -0,0 +1,29 @@
package adapter_test
import (
"testing"
"gitea.dwysokinski.me/twhelp/corev3/internal/adapter/adaptertest"
)
func TestEnnoblementBunRepository_Postgres(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("skipping long-running test")
}
testEnnoblementRepository(t, func(t *testing.T) repositories {
t.Helper()
return newBunDBRepositories(t, postgres.NewBunDB(t))
})
}
func TestEnnoblementBunRepository_SQLite(t *testing.T) {
t.Parallel()
testEnnoblementRepository(t, func(t *testing.T) repositories {
t.Helper()
return newBunDBRepositories(t, adaptertest.NewBunDBSQLite(t))
})
}

View File

@ -0,0 +1,255 @@
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"
gocmp "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testEnnoblementRepository(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.CreateEnnoblementParams) {
t.Helper()
require.NotEmpty(t, params)
listParams := domain.NewListEnnoblementsParams()
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
ennoblements, err := repos.ennoblement.List(ctx, listParams)
require.NoError(t, err)
for i, p := range params {
idx := slices.IndexFunc(ennoblements, func(ennoblement domain.Ennoblement) bool {
return ennoblement.VillageID() == p.Base().VillageID() && ennoblement.ServerKey() == p.ServerKey()
})
require.GreaterOrEqualf(t, idx, 0, "params[%d] not found", i)
ennoblement := ennoblements[idx]
assert.Emptyf(t, gocmp.Diff(
p.Base(),
ennoblement.Base(),
cmpopts.EquateApproxTime(time.Minute),
gocmp.AllowUnexported(domain.BaseEnnoblement{}),
), "params[%d]", i)
assert.Equalf(t, p.ServerKey(), ennoblement.ServerKey(), "params[%d]", i)
}
}
assertNoDuplicates := func(t *testing.T, params []domain.CreateEnnoblementParams) {
t.Helper()
require.NotEmpty(t, params)
listParams := domain.NewListEnnoblementsParams()
require.NoError(t, listParams.SetServerKeys([]string{params[0].ServerKey()}))
ennoblements, err := repos.ennoblement.List(ctx, listParams)
require.NoError(t, err)
res := make(map[string][]int)
for _, p := range params {
key := fmt.Sprintf("%s-%d", p.ServerKey(), p.Base().VillageID())
for i, e := range ennoblements {
if e.ServerKey() == p.ServerKey() && gocmp.Equal(
p.Base(),
e.Base(),
cmpopts.EquateApproxTime(time.Minute),
gocmp.AllowUnexported(domain.BaseEnnoblement{}),
) {
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()
servers, err := repos.server.List(ctx, domain.NewListServersParams())
require.NoError(t, err)
require.NotEmpty(t, servers)
server := servers[0]
ennoblementsToCreate := domain.BaseEnnoblements{
domaintest.NewBaseEnnoblement(t),
domaintest.NewBaseEnnoblement(t),
}
createParams, err := domain.NewCreateEnnoblementParams(server.Key(), ennoblementsToCreate)
require.NoError(t, err)
require.NoError(t, repos.ennoblement.Create(ctx, createParams...))
assertCreated(t, createParams)
require.NoError(t, repos.ennoblement.Create(ctx, createParams...))
assertNoDuplicates(t, createParams)
})
t.Run("OK: len(params) == 0", func(t *testing.T) {
t.Parallel()
require.NoError(t, repos.ennoblement.Create(ctx))
})
})
t.Run("List & ListCount", func(t *testing.T) {
t.Parallel()
repos := newRepos(t)
ennoblements, listEnnoblementsErr := repos.ennoblement.List(ctx, domain.NewListEnnoblementsParams())
require.NoError(t, listEnnoblementsErr)
require.NotEmpty(t, ennoblements)
randEnnoblement := ennoblements[0]
tests := []struct {
name string
params func(t *testing.T) domain.ListEnnoblementsParams
assertEnnoblements func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements)
assertError func(t *testing.T, err error)
assertTotal func(t *testing.T, params domain.ListEnnoblementsParams, total int)
}{
{
name: "OK: default params",
params: func(t *testing.T) domain.ListEnnoblementsParams {
t.Helper()
return domain.NewListEnnoblementsParams()
},
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
t.Helper()
assert.NotEmpty(t, len(ennoblements))
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) 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.ListEnnoblementsParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: sort=[serverKey DESC, createdAt DESC]",
params: func(t *testing.T) domain.ListEnnoblementsParams {
t.Helper()
params := domain.NewListEnnoblementsParams()
require.NoError(t, params.SetSort([]domain.EnnoblementSort{
domain.EnnoblementSortServerKeyDESC,
domain.EnnoblementSortCreatedAtDESC,
}))
return params
},
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
t.Helper()
assert.NotEmpty(t, len(ennoblements))
assert.True(t, slices.IsSortedFunc(ennoblements, func(a, b domain.Ennoblement) 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.ListEnnoblementsParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: fmt.Sprintf("OK: serverKeys=[%s]", randEnnoblement.ServerKey()),
params: func(t *testing.T) domain.ListEnnoblementsParams {
t.Helper()
params := domain.NewListEnnoblementsParams()
require.NoError(t, params.SetServerKeys([]string{randEnnoblement.ServerKey()}))
return params
},
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
t.Helper()
serverKeys := params.ServerKeys()
for _, v := range ennoblements {
assert.True(t, slices.Contains(serverKeys, v.ServerKey()))
}
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, params domain.ListEnnoblementsParams, total int) {
t.Helper()
assert.NotEmpty(t, total)
},
},
{
name: "OK: offset=1 limit=2",
params: func(t *testing.T) domain.ListEnnoblementsParams {
t.Helper()
params := domain.NewListEnnoblementsParams()
require.NoError(t, params.SetOffset(1))
require.NoError(t, params.SetLimit(2))
return params
},
assertEnnoblements: func(t *testing.T, params domain.ListEnnoblementsParams, ennoblements domain.Ennoblements) {
t.Helper()
assert.Len(t, ennoblements, params.Limit())
},
assertError: func(t *testing.T, err error) {
t.Helper()
require.NoError(t, err)
},
assertTotal: func(t *testing.T, params domain.ListEnnoblementsParams, 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.ennoblement.List(ctx, params)
tt.assertError(t, err)
tt.assertEnnoblements(t, params, res)
})
}
})
}

View File

@ -44,12 +44,18 @@ type villageRepository interface {
Delete(ctx context.Context, serverKey string, ids ...int) error Delete(ctx context.Context, serverKey string, ids ...int) error
} }
type ennoblementRepository interface {
Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
}
type repositories struct { type repositories struct {
version versionRepository version versionRepository
server serverRepository server serverRepository
tribe tribeRepository tribe tribeRepository
player playerRepository player playerRepository
village villageRepository village villageRepository
ennoblement ennoblementRepository
} }
func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories { func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
@ -58,10 +64,11 @@ func newBunDBRepositories(tb testing.TB, bunDB *bun.DB) repositories {
adaptertest.NewFixture(bunDB).Load(tb, context.Background(), os.DirFS("testdata"), "fixture.yml") adaptertest.NewFixture(bunDB).Load(tb, context.Background(), os.DirFS("testdata"), "fixture.yml")
return repositories{ return repositories{
version: adapter.NewVersionBunRepository(bunDB), version: adapter.NewVersionBunRepository(bunDB),
server: adapter.NewServerBunRepository(bunDB), server: adapter.NewServerBunRepository(bunDB),
tribe: adapter.NewTribeBunRepository(bunDB), tribe: adapter.NewTribeBunRepository(bunDB),
player: adapter.NewPlayerBunRepository(bunDB), player: adapter.NewPlayerBunRepository(bunDB),
village: adapter.NewVillageBunRepository(bunDB), village: adapter.NewVillageBunRepository(bunDB),
ennoblement: adapter.NewEnnoblementBunRepository(bunDB),
} }
} }

View File

@ -7288,3 +7288,45 @@
player_id: 0 player_id: 0
created_at: 2022-02-25T15:00:10.000Z created_at: 2022-02-25T15:00:10.000Z
profile_url: https://it70.tribals.it/game.php?screen=info_village&id=10024 profile_url: https://it70.tribals.it/game.php?screen=info_village&id=10024
- model: Ennoblement
rows:
- _id: pl169-village-2-1
id: 10000
server_key: pl169
village_id: 1112
new_owner_id: 699513260
new_tribe_id: 27
old_owner_id: 0
old_tribe_id: 0
points: 2500
created_at: 2021-09-30T09:01:00.000Z
- _id: pl169-village-2-2
id: 10001
server_key: pl169
village_id: 1112
new_owner_id: 699783765
new_tribe_id: 28
old_owner_id: 699513260
old_tribe_id: 27
points: 5000
created_at: 2021-10-30T09:01:00.000Z
- _id: it70-village-2-1
id: 20000
server_key: it70
village_id: 10023
new_owner_id: 848881282
new_tribe_id: 31
old_owner_id: 0
old_tribe_id: 0
points: 2500
created_at: 2022-03-15T15:00:10.000Z
- _id: it70-village-2-2
id: 20001
server_key: it70
village_id: 10023
new_owner_id: 578014
new_tribe_id: 1
old_owner_id: 848881282
old_tribe_id: 31
points: 5123
created_at: 2022-04-22T15:00:10.000Z

View File

@ -0,0 +1,118 @@
package app
import (
"context"
"errors"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
)
type EnnoblementRepository interface {
Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error
List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.Ennoblements, error)
}
type EnnoblementService struct {
repo EnnoblementRepository
twSvc TWService
pub EnnoblementPublisher
}
func NewEnnoblementService(repo EnnoblementRepository, twSvc TWService, pub EnnoblementPublisher) *EnnoblementService {
return &EnnoblementService{repo: repo, twSvc: twSvc, pub: pub}
}
func (svc *EnnoblementService) Sync(
ctx context.Context,
syncEnnoblementsCmdPayload domain.SyncEnnoblementsCmdPayload,
) error {
serverURL := syncEnnoblementsCmdPayload.ServerURL()
serverKey := syncEnnoblementsCmdPayload.ServerKey()
latestEnnoblement, err := svc.getLatestEnnoblement(ctx, serverKey)
if err != nil && !errors.Is(err, errEnnoblementNotFound) {
return fmt.Errorf("%s: %w", serverKey, err)
}
var since time.Time
if err == nil {
since = latestEnnoblement.CreatedAt()
}
ennoblements, err := svc.twSvc.GetEnnoblements(ctx, serverURL, since)
if err != nil {
return fmt.Errorf("%s: couldn't get ennoblements: %w", serverKey, err)
}
if err = svc.create(ctx, serverKey, ennoblements); err != nil {
return fmt.Errorf("%s: couldn't create ennoblements: %w", serverKey, err)
}
payload, err := domain.NewEnnoblementsSyncedEventPayload(
serverKey,
serverURL,
syncEnnoblementsCmdPayload.VersionCode(),
)
if err != nil {
return fmt.Errorf("%s: couldn't construct domain.EnnoblementsSyncedEventPayload %w", serverKey, err)
}
if err = svc.pub.EventSynced(ctx, payload); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
return nil
}
var errEnnoblementNotFound = errors.New("ennoblement not found")
func (svc *EnnoblementService) getLatestEnnoblement(ctx context.Context, serverKey string) (domain.Ennoblement, error) {
params := domain.NewListEnnoblementsParams()
if err := params.SetLimit(1); err != nil {
return domain.Ennoblement{}, err
}
if err := params.SetServerKeys([]string{serverKey}); err != nil {
return domain.Ennoblement{}, err
}
if err := params.SetSort([]domain.EnnoblementSort{domain.EnnoblementSortCreatedAtDESC}); err != nil {
return domain.Ennoblement{}, err
}
ennoblements, err := svc.repo.List(ctx, params)
if err != nil {
return domain.Ennoblement{}, err
}
if len(ennoblements) == 0 {
return domain.Ennoblement{}, errEnnoblementNotFound
}
return ennoblements[0], nil
}
const ennoblementCreateChunkSize = 500
func (svc *EnnoblementService) create(
ctx context.Context,
serverKey string,
ennoblements domain.BaseEnnoblements,
) error {
for i := 0; i < len(ennoblements); i += ennoblementCreateChunkSize {
end := i + ennoblementCreateChunkSize
if end > len(ennoblements) {
end = len(ennoblements)
}
createParams, err := domain.NewCreateEnnoblementParams(serverKey, ennoblements[i:end])
if err != nil {
return err
}
if err = svc.repo.Create(ctx, createParams...); err != nil {
return err
}
}
return nil
}

View File

@ -60,10 +60,14 @@ func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCm
payloads, err := domain.NewServerSyncedEventPayloads(openServersWithoutSpecial, versionCode) payloads, err := domain.NewServerSyncedEventPayloads(openServersWithoutSpecial, versionCode)
if err != nil { if err != nil {
return fmt.Errorf("%s: couldn't construct server synced event payloads: %w", versionCode, err) return fmt.Errorf("%s: couldn't construct domain.ServerSyncedEventPayload: %w", versionCode, err)
} }
return svc.pub.EventSynced(ctx, payloads...) if err = svc.pub.EventSynced(ctx, payloads...); err != nil {
return fmt.Errorf("%s: %w", versionCode, err)
}
return nil
} }
func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string) (domain.Servers, error) { func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string) (domain.Servers, error) {
@ -257,3 +261,20 @@ func (svc *ServerService) UpdateNumVillages(ctx context.Context, payload domain.
return svc.repo.Update(ctx, key, updateParams) return svc.repo.Update(ctx, key, updateParams)
} }
func (svc *ServerService) UpdateEnnoblementDataSyncedAt(
ctx context.Context,
payload domain.EnnoblementsSyncedEventPayload,
) error {
key := payload.ServerKey()
var updateParams domain.UpdateServerParams
if err := updateParams.SetEnnoblementDataSyncedAt(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

@ -3,6 +3,7 @@ package app
import ( import (
"context" "context"
"net/url" "net/url"
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
) )
@ -15,4 +16,5 @@ type TWService interface {
GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error) GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error)
GetPlayers(ctx context.Context, baseURL *url.URL) (domain.BasePlayers, error) GetPlayers(ctx context.Context, baseURL *url.URL) (domain.BasePlayers, error)
GetVillages(ctx context.Context, baseURL *url.URL) (domain.BaseVillages, error) GetVillages(ctx context.Context, baseURL *url.URL) (domain.BaseVillages, error)
GetEnnoblements(ctx context.Context, baseURL *url.URL, since time.Time) (domain.BaseEnnoblements, error)
} }

View File

@ -66,7 +66,12 @@ func (svc *VillageService) createOrUpdate(ctx context.Context, serverKey string,
end = len(villages) end = len(villages)
} }
if err := svc.createOrUpdateChunk(ctx, serverKey, villages[i:end]); err != nil { createParams, err := domain.NewCreateVillageParams(serverKey, villages[i:end])
if err != nil {
return err
}
if err = svc.repo.CreateOrUpdate(ctx, createParams...); err != nil {
return err return err
} }
} }
@ -74,19 +79,6 @@ func (svc *VillageService) createOrUpdate(ctx context.Context, serverKey string,
return nil return nil
} }
func (svc *VillageService) createOrUpdateChunk(
ctx context.Context,
serverKey string,
villages domain.BaseVillages,
) error {
createParams, err := domain.NewCreateVillageParams(serverKey, villages)
if err != nil {
return err
}
return svc.repo.CreateOrUpdate(ctx, createParams...)
}
func (svc *VillageService) delete(ctx context.Context, serverKey string, villages domain.BaseVillages) error { func (svc *VillageService) delete(ctx context.Context, serverKey string, villages domain.BaseVillages) error {
listParams := domain.NewListVillagesParams() listParams := domain.NewListVillagesParams()
if err := listParams.SetServerKeys([]string{serverKey}); err != nil { if err := listParams.SetServerKeys([]string{serverKey}); err != nil {

View File

@ -0,0 +1,120 @@
package domain
import (
"math"
"time"
)
type BaseEnnoblement struct {
villageID int
newOwnerID int
newTribeID int
oldOwnerID int
oldTribeID int
points int
createdAt time.Time
}
const baseEnnoblementModelName = "BaseEnnoblement"
func NewBaseEnnoblement(
villageID int,
newOwnerID int,
newTribeID int,
oldOwnerID int,
oldTribeID int,
points int,
createdAt time.Time,
) (BaseEnnoblement, error) {
if err := validateIntInRange(villageID, 1, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "villageID",
Err: err,
}
}
if err := validateIntInRange(newOwnerID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "newOwnerID",
Err: err,
}
}
if err := validateIntInRange(newTribeID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "newTribeID",
Err: err,
}
}
if err := validateIntInRange(oldOwnerID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "oldOwnerID",
Err: err,
}
}
if err := validateIntInRange(oldTribeID, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "oldTribeID",
Err: err,
}
}
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
return BaseEnnoblement{}, ValidationError{
Model: baseEnnoblementModelName,
Field: "points",
Err: err,
}
}
return BaseEnnoblement{
villageID: villageID,
newOwnerID: newOwnerID,
newTribeID: newTribeID,
oldOwnerID: oldOwnerID,
oldTribeID: oldTribeID,
points: points,
createdAt: createdAt,
}, nil
}
func (e BaseEnnoblement) VillageID() int {
return e.villageID
}
func (e BaseEnnoblement) NewOwnerID() int {
return e.newOwnerID
}
func (e BaseEnnoblement) NewTribeID() int {
return e.newTribeID
}
func (e BaseEnnoblement) OldOwnerID() int {
return e.oldOwnerID
}
func (e BaseEnnoblement) OldTribeID() int {
return e.oldTribeID
}
func (e BaseEnnoblement) Points() int {
return e.points
}
func (e BaseEnnoblement) CreatedAt() time.Time {
return e.createdAt
}
func (e BaseEnnoblement) IsZero() bool {
return e == BaseEnnoblement{}
}
type BaseEnnoblements []BaseEnnoblement

View File

@ -0,0 +1,195 @@
package domain_test
import (
"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 TestNewBaseEnnoblement(t *testing.T) {
t.Parallel()
validBaseEnnoblement := domaintest.NewBaseEnnoblement(t)
type args struct {
villageID int
newOwnerID int
newTribeID int
oldOwnerID int
oldTribeID int
points int
createdAt time.Time
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
},
{
name: "ERR: villageID < 1",
args: args{
villageID: 0,
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "villageID",
Err: domain.MinGreaterEqualError{
Min: 1,
Current: 0,
},
},
},
{
name: "ERR: newOwnerID < 0",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: -1,
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "newOwnerID",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: newTribeID < 0",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: -1,
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "newTribeID",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: oldOwnerID < 0",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: -1,
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "oldOwnerID",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: oldTribeID < 0",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: -1,
points: validBaseEnnoblement.Points(),
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "oldTribeID",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
{
name: "ERR: points < 0",
args: args{
villageID: validBaseEnnoblement.VillageID(),
newOwnerID: validBaseEnnoblement.NewOwnerID(),
newTribeID: validBaseEnnoblement.NewTribeID(),
oldOwnerID: validBaseEnnoblement.OldOwnerID(),
oldTribeID: validBaseEnnoblement.OldTribeID(),
points: -1,
createdAt: validBaseEnnoblement.CreatedAt(),
},
expectedErr: domain.ValidationError{
Model: "BaseEnnoblement",
Field: "points",
Err: domain.MinGreaterEqualError{
Min: 0,
Current: -1,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
res, err := domain.NewBaseEnnoblement(
tt.args.villageID,
tt.args.newOwnerID,
tt.args.newTribeID,
tt.args.oldOwnerID,
tt.args.oldTribeID,
tt.args.points,
tt.args.createdAt,
)
require.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.villageID, res.VillageID())
assert.Equal(t, tt.args.newOwnerID, res.NewOwnerID())
assert.Equal(t, tt.args.newTribeID, res.NewTribeID())
assert.Equal(t, tt.args.oldOwnerID, res.OldOwnerID())
assert.Equal(t, tt.args.oldTribeID, res.OldTribeID())
assert.Equal(t, tt.args.points, res.Points())
assert.Equal(t, tt.args.createdAt, res.CreatedAt())
})
}
}

View File

@ -45,7 +45,7 @@ func NewBaseVillage(
} }
} }
if err := validateIntInRange(points, 1, math.MaxInt); err != nil { if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
return BaseVillage{}, ValidationError{ return BaseVillage{}, ValidationError{
Model: baseVillageModelName, Model: baseVillageModelName,
Field: "points", Field: "points",

View File

@ -116,11 +116,11 @@ func TestNewBaseVillage(t *testing.T) {
}, },
}, },
{ {
name: "ERR: points < 1", name: "ERR: points < 0",
args: args{ args: args{
id: validBaseVillage.ID(), id: validBaseVillage.ID(),
name: validBaseVillage.Name(), name: validBaseVillage.Name(),
points: 0, points: -1,
x: validBaseVillage.X(), x: validBaseVillage.X(),
y: validBaseVillage.Y(), y: validBaseVillage.Y(),
continent: validBaseVillage.Continent(), continent: validBaseVillage.Continent(),
@ -132,8 +132,8 @@ func TestNewBaseVillage(t *testing.T) {
Model: "BaseVillage", Model: "BaseVillage",
Field: "points", Field: "points",
Err: domain.MinGreaterEqualError{ Err: domain.MinGreaterEqualError{
Min: 1, Min: 0,
Current: 0, Current: -1,
}, },
}, },
}, },

View File

@ -0,0 +1,38 @@
package domaintest
import (
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/require"
)
type BaseEnnoblementConfig struct {
VillageID int
}
func NewBaseEnnoblement(tb TestingTB, opts ...func(cfg *BaseEnnoblementConfig)) domain.BaseEnnoblement {
tb.Helper()
cfg := &BaseEnnoblementConfig{
VillageID: RandID(),
}
for _, opt := range opts {
opt(cfg)
}
e, err := domain.NewBaseEnnoblement(
cfg.VillageID,
RandID(),
RandID(),
RandID(),
RandID(),
gofakeit.IntRange(1, 10000),
time.Now(),
)
require.NoError(tb, err)
return e
}

View File

@ -26,7 +26,7 @@ func NewBasePlayer(tb TestingTB, opts ...func(cfg *BasePlayerConfig)) domain.Bas
NumVillages: gofakeit.IntRange(1, 10000), NumVillages: gofakeit.IntRange(1, 10000),
AllPoints: gofakeit.IntRange(1, 10000), AllPoints: gofakeit.IntRange(1, 10000),
Rank: gofakeit.IntRange(1, 10000), Rank: gofakeit.IntRange(1, 10000),
TribeID: gofakeit.IntRange(0, 10000), TribeID: RandID(),
} }
for _, opt := range opts { for _, opt := range opts {

View File

@ -19,7 +19,7 @@ func NewBaseVillage(tb TestingTB, opts ...func(cfg *BaseVillageConfig)) domain.B
cfg := &BaseVillageConfig{ cfg := &BaseVillageConfig{
ID: RandID(), ID: RandID(),
PlayerID: gofakeit.IntRange(0, 10000), PlayerID: RandID(),
Bonus: 0, Bonus: 0,
} }

View File

@ -0,0 +1,44 @@
package domaintest
import (
"time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/require"
)
type EnnoblementConfig struct {
ID int
ServerKey string
VillageID int
}
func NewEnnoblement(tb TestingTB, opts ...func(cfg *EnnoblementConfig)) domain.Ennoblement {
tb.Helper()
cfg := &EnnoblementConfig{
ID: RandID(),
ServerKey: RandServerKey(),
VillageID: RandID(),
}
for _, opt := range opts {
opt(cfg)
}
e, err := domain.UnmarshalEnnoblementFromDatabase(
cfg.ID,
cfg.ServerKey,
cfg.VillageID,
RandID(),
RandID(),
RandID(),
RandID(),
gofakeit.IntRange(1, 10000),
time.Now(),
)
require.NoError(tb, err)
return e
}

View File

@ -0,0 +1,253 @@
package domain
import (
"fmt"
"math"
"time"
)
type Ennoblement struct {
id int
serverKey string
villageID int
newOwnerID int
newTribeID int
oldOwnerID int
oldTribeID int
points int
createdAt time.Time
}
const ennoblementModelName = "Ennoblement"
// UnmarshalEnnoblementFromDatabase unmarshals Ennoblement from the database.
//
// It should be used only for unmarshalling from the database!
// You can't use UnmarshalEnnoblementFromDatabase as constructor - It may put domain into the invalid state!
func UnmarshalEnnoblementFromDatabase(
id int,
serverKey string,
villageID int,
newOwnerID int,
newTribeID int,
oldOwnerID int,
oldTribeID int,
points int,
createdAt time.Time,
) (Ennoblement, error) {
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
return Ennoblement{}, ValidationError{
Model: ennoblementModelName,
Field: "id",
Err: err,
}
}
if err := validateServerKey(serverKey); err != nil {
return Ennoblement{}, ValidationError{
Model: ennoblementModelName,
Field: "serverKey",
Err: err,
}
}
return Ennoblement{
id: id,
serverKey: serverKey,
villageID: villageID,
newOwnerID: newOwnerID,
newTribeID: newTribeID,
oldOwnerID: oldOwnerID,
oldTribeID: oldTribeID,
points: points,
createdAt: createdAt,
}, nil
}
func (e Ennoblement) ID() int {
return e.id
}
func (e Ennoblement) ServerKey() string {
return e.serverKey
}
func (e Ennoblement) VillageID() int {
return e.villageID
}
func (e Ennoblement) NewOwnerID() int {
return e.newOwnerID
}
func (e Ennoblement) NewTribeID() int {
return e.newTribeID
}
func (e Ennoblement) OldOwnerID() int {
return e.oldOwnerID
}
func (e Ennoblement) OldTribeID() int {
return e.oldTribeID
}
func (e Ennoblement) Points() int {
return e.points
}
func (e Ennoblement) CreatedAt() time.Time {
return e.createdAt
}
func (e Ennoblement) Base() BaseEnnoblement {
return BaseEnnoblement{
villageID: e.villageID,
newOwnerID: e.newOwnerID,
newTribeID: e.newTribeID,
oldOwnerID: e.oldOwnerID,
oldTribeID: e.oldTribeID,
points: e.points,
createdAt: e.createdAt,
}
}
type Ennoblements []Ennoblement
type CreateEnnoblementParams struct {
base BaseEnnoblement
serverKey string
}
const createEnnoblementParamsModelName = "CreateEnnoblementParams"
func NewCreateEnnoblementParams(serverKey string, ennoblements BaseEnnoblements) ([]CreateEnnoblementParams, error) {
if err := validateServerKey(serverKey); err != nil {
return nil, ValidationError{
Model: createEnnoblementParamsModelName,
Field: "serverKey",
Err: err,
}
}
params := make([]CreateEnnoblementParams, 0, len(ennoblements))
for i, e := range ennoblements {
if e.IsZero() {
return nil, fmt.Errorf("ennoblements[%d] is an empty struct", i)
}
params = append(params, CreateEnnoblementParams{
base: e,
serverKey: serverKey,
})
}
return params, nil
}
func (params CreateEnnoblementParams) Base() BaseEnnoblement {
return params.base
}
func (params CreateEnnoblementParams) ServerKey() string {
return params.serverKey
}
type EnnoblementSort uint8
const (
EnnoblementSortCreatedAtASC EnnoblementSort = iota + 1
EnnoblementSortCreatedAtDESC
EnnoblementSortServerKeyASC
EnnoblementSortServerKeyDESC
)
const EnnoblementListMaxLimit = 200
type ListEnnoblementsParams struct {
serverKeys []string
sort []EnnoblementSort
limit int
offset int
}
const listEnnoblementsParamsModelName = "ListEnnoblementsParams"
func NewListEnnoblementsParams() ListEnnoblementsParams {
return ListEnnoblementsParams{
sort: []EnnoblementSort{
EnnoblementSortServerKeyASC,
EnnoblementSortCreatedAtASC,
},
limit: EnnoblementListMaxLimit,
}
}
func (params *ListEnnoblementsParams) ServerKeys() []string {
return params.serverKeys
}
func (params *ListEnnoblementsParams) SetServerKeys(serverKeys []string) error {
params.serverKeys = serverKeys
return nil
}
func (params *ListEnnoblementsParams) Sort() []EnnoblementSort {
return params.sort
}
const (
ennoblementSortMinLength = 1
ennoblementSortMaxLength = 2
)
func (params *ListEnnoblementsParams) SetSort(sort []EnnoblementSort) error {
if err := validateSliceLen(sort, ennoblementSortMinLength, ennoblementSortMaxLength); err != nil {
return ValidationError{
Model: listEnnoblementsParamsModelName,
Field: "sort",
Err: err,
}
}
params.sort = sort
return nil
}
func (params *ListEnnoblementsParams) Limit() int {
return params.limit
}
func (params *ListEnnoblementsParams) SetLimit(limit int) error {
if err := validateIntInRange(limit, 1, EnnoblementListMaxLimit); err != nil {
return ValidationError{
Model: listEnnoblementsParamsModelName,
Field: "limit",
Err: err,
}
}
params.limit = limit
return nil
}
func (params *ListEnnoblementsParams) Offset() int {
return params.offset
}
func (params *ListEnnoblementsParams) SetOffset(offset int) error {
if err := validateIntInRange(offset, 0, math.MaxInt); err != nil {
return ValidationError{
Model: listEnnoblementsParamsModelName,
Field: "offset",
Err: err,
}
}
params.offset = offset
return nil
}

View File

@ -0,0 +1,227 @@
package domain_test
import (
"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 TestNewCreateEnnoblementParams(t *testing.T) {
t.Parallel()
server := domaintest.NewServer(t)
ennoblements := domain.BaseEnnoblements{
domaintest.NewBaseEnnoblement(t),
domaintest.NewBaseEnnoblement(t),
domaintest.NewBaseEnnoblement(t),
}
res, err := domain.NewCreateEnnoblementParams(server.Key(), ennoblements)
require.NoError(t, err)
for i, e := range ennoblements {
idx := slices.IndexFunc(res, func(params domain.CreateEnnoblementParams) bool {
return params.Base().VillageID() == e.VillageID() && params.ServerKey() == server.Key()
})
require.GreaterOrEqualf(t, idx, 0, "ennoblements[%d] not found", i)
params := res[idx]
assert.Equalf(t, e, params.Base(), "ennoblements[%d]", i)
}
}
func TestListEnnoblementsParams_SetSort(t *testing.T) {
t.Parallel()
type args struct {
sort []domain.EnnoblementSort
}
tests := []struct {
name string
args args
expectedErr error
}{
{
name: "OK",
args: args{
sort: []domain.EnnoblementSort{
domain.EnnoblementSortCreatedAtASC,
domain.EnnoblementSortServerKeyASC,
},
},
},
{
name: "ERR: len(sort) < 1",
args: args{
sort: nil,
},
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 2,
Current: 0,
},
},
},
{
name: "ERR: len(sort) > 2",
args: args{
sort: []domain.EnnoblementSort{
domain.EnnoblementSortCreatedAtASC,
domain.EnnoblementSortServerKeyASC,
domain.EnnoblementSortServerKeyDESC,
},
},
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
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.NewListEnnoblementsParams()
require.ErrorIs(t, params.SetSort(tt.args.sort), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.sort, params.Sort())
})
}
}
func TestListEnnoblementsParams_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.EnnoblementListMaxLimit,
},
},
{
name: "ERR: limit < 1",
args: args{
limit: 0,
},
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
Field: "limit",
Err: domain.MinGreaterEqualError{
Min: 1,
Current: 0,
},
},
},
{
name: fmt.Sprintf("ERR: limit > %d", domain.EnnoblementListMaxLimit),
args: args{
limit: domain.EnnoblementListMaxLimit + 1,
},
expectedErr: domain.ValidationError{
Model: "ListEnnoblementsParams",
Field: "limit",
Err: domain.MaxLessEqualError{
Max: domain.EnnoblementListMaxLimit,
Current: domain.EnnoblementListMaxLimit + 1,
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
params := domain.NewListEnnoblementsParams()
require.ErrorIs(t, params.SetLimit(tt.args.limit), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.limit, params.Limit())
})
}
}
func TestListEnnoblementsParams_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: "ListEnnoblementsParams",
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.NewListEnnoblementsParams()
require.ErrorIs(t, params.SetOffset(tt.args.offset), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.args.offset, params.Offset())
})
}
}

View File

@ -3,14 +3,12 @@ package domain_test
import ( import (
"cmp" "cmp"
"fmt" "fmt"
"math"
"slices" "slices"
"testing" "testing"
"time" "time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest" "gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -195,9 +193,9 @@ func TestListPlayersParams_SetIDs(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
}, },
@ -205,11 +203,11 @@ func TestListPlayersParams_SetIDs(t *testing.T) {
name: "ERR: value < 0", name: "ERR: value < 0",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
-1, -1,
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
expectedErr: domain.SliceElementValidationError{ expectedErr: domain.SliceElementValidationError{
@ -257,7 +255,7 @@ func TestListPlayersParams_SetIDGT(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
idGT: domain.NullInt{ idGT: domain.NullInt{
Value: gofakeit.IntRange(0, math.MaxInt), Value: domaintest.RandID(),
Valid: true, Valid: true,
}, },
}, },

View File

@ -3,14 +3,12 @@ package domain_test
import ( import (
"cmp" "cmp"
"fmt" "fmt"
"math"
"slices" "slices"
"testing" "testing"
"time" "time"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest" "gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -178,9 +176,9 @@ func TestListTribesParams_SetIDs(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
}, },
@ -188,11 +186,11 @@ func TestListTribesParams_SetIDs(t *testing.T) {
name: "ERR: value < 0", name: "ERR: value < 0",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
-1, -1,
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
expectedErr: domain.SliceElementValidationError{ expectedErr: domain.SliceElementValidationError{
@ -240,7 +238,7 @@ func TestListTribesParams_SetIDGT(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
idGT: domain.NullInt{ idGT: domain.NullInt{
Value: gofakeit.IntRange(0, math.MaxInt), Value: domaintest.RandID(),
Valid: true, Valid: true,
}, },
}, },

View File

@ -57,11 +57,11 @@ func TestNewVillagesSyncedEventPayloadFromVillages(t *testing.T) {
cfg.Bonus = 0 cfg.Bonus = 0
}), }),
domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) { domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
cfg.PlayerID = gofakeit.IntRange(1, 10000) cfg.PlayerID = domaintest.RandID()
cfg.Bonus = 0 cfg.Bonus = 0
}), }),
domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) { domaintest.NewBaseVillage(t, func(cfg *domaintest.BaseVillageConfig) {
cfg.PlayerID = gofakeit.IntRange(1, 10000) cfg.PlayerID = domaintest.RandID()
cfg.Bonus = 1 cfg.Bonus = 1
}), }),
} }

View File

@ -3,13 +3,11 @@ package domain_test
import ( import (
"cmp" "cmp"
"fmt" "fmt"
"math"
"slices" "slices"
"testing" "testing"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain" "gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest" "gitea.dwysokinski.me/twhelp/corev3/internal/domain/domaintest"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -72,7 +70,7 @@ func TestNewCreateVillageParams(t *testing.T) {
idx := slices.IndexFunc(res, func(params domain.CreateVillageParams) bool { idx := slices.IndexFunc(res, func(params domain.CreateVillageParams) bool {
return params.Base().ID() == v.ID() && params.ServerKey() == server.Key() return params.Base().ID() == v.ID() && params.ServerKey() == server.Key()
}) })
require.GreaterOrEqualf(t, idx, 0, "village[%d] not found", i) require.GreaterOrEqualf(t, idx, 0, "villages[%d] not found", i)
params := res[idx] params := res[idx]
@ -96,9 +94,9 @@ func TestListVillagesParams_SetIDs(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
}, },
@ -106,11 +104,11 @@ func TestListVillagesParams_SetIDs(t *testing.T) {
name: "ERR: value < 0", name: "ERR: value < 0",
args: args{ args: args{
ids: []int{ ids: []int{
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
-1, -1,
gofakeit.IntRange(0, math.MaxInt), domaintest.RandID(),
}, },
}, },
expectedErr: domain.SliceElementValidationError{ expectedErr: domain.SliceElementValidationError{
@ -158,7 +156,7 @@ func TestListVillagesParams_SetIDGT(t *testing.T) {
name: "OK", name: "OK",
args: args{ args: args{
idGT: domain.NullInt{ idGT: domain.NullInt{
Value: gofakeit.IntRange(0, math.MaxInt), Value: domaintest.RandID(),
Valid: true, Valid: true,
}, },
}, },

View File

@ -11,8 +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 bigint ? ?,
primary key,
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,
@ -41,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(autoIncrement(db))) `, 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;")

View File

@ -11,8 +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 bigint ? ?,
primary key,
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,
@ -33,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(autoIncrement(db))) `, 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;")

View File

@ -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 bigint ? primary key, ?,
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(autoIncrement(db))) `, 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;")

View File

@ -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 bigint ? primary key, ?,
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(autoIncrement(db))) `, 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;")

View File

@ -9,26 +9,30 @@ 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( if db.Dialect().Name() == dialect.PG {
ctx, // hash_record_extended is Postgres specific
`create unique index concurrently if not exists ennoblements_hash_key // 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 ennoblements_hash_key
on ennoblements (hash_record_extended( on ennoblements (hash_record_extended(
ROW (server_key, village_id, new_owner_id, new_tribe_id, old_owner_id, old_tribe_id, points, created_at), ROW (server_key, village_id, new_owner_id, new_tribe_id, old_owner_id, old_tribe_id, points, created_at),
0::bigint));`, 0::bigint));`,
) )
return err } else {
}, func(ctx context.Context, db *bun.DB) error { _, err = db.ExecContext(
if db.Dialect().Name() != dialect.PG { ctx,
return nil `create unique index if not exists ennoblements_hash_key
on ennoblements (server_key, coalesce(village_id, 0), coalesce(new_owner_id, 0), coalesce(new_tribe_id, 0), coalesce(old_owner_id, 0), coalesce(old_tribe_id, 0), points, created_at);`,
)
} }
_, err := db.ExecContext(ctx, "DROP INDEX CONCURRENTLY ennoblements_hash_key") return err
}, func(ctx context.Context, db *bun.DB) error {
_, err := db.NewDropIndex().IfExists().Index("ennoblements_hash_key").Concurrently().Exec(ctx)
return err return err
}) })
} }

View File

@ -24,11 +24,7 @@ func init() {
) )
return err return err
}, func(ctx context.Context, db *bun.DB) error { }, func(ctx context.Context, db *bun.DB) error {
if db.Dialect().Name() != dialect.PG { _, err := db.NewDropIndex().IfExists().Index("tribe_changes_hash_key").Concurrently().Exec(ctx)
return nil
}
_, err := db.ExecContext(ctx, "DROP INDEX CONCURRENTLY tribe_changes_hash_key")
return err return err
}) })
} }

View File

@ -1,17 +1,19 @@
package migrations package migrations
import "github.com/uptrace/bun/dialect/feature" import (
"github.com/uptrace/bun/dialect/feature"
)
type hasFeaturer interface { type hasFeaturer interface {
HasFeature(feat feature.Feature) bool HasFeature(feat feature.Feature) bool
} }
func autoIncrement(f hasFeaturer) string { func autoincrementIDColumn(f hasFeaturer) string {
if f.HasFeature(feature.GeneratedIdentity) { if f.HasFeature(feature.GeneratedIdentity) {
return "GENERATED BY DEFAULT AS IDENTITY" // postgres
return "id bigint GENERATED BY DEFAULT AS IDENTITY primary key"
} }
if f.HasFeature(feature.AutoIncrement) {
return "AUTO_INCREMENT" // sqlite
} return "id INTEGER PRIMARY KEY"
return ""
} }

View File

@ -0,0 +1,67 @@
package port
import (
"gitea.dwysokinski.me/twhelp/corev3/internal/app"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/watermillmsg"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
)
type EnnoblementWatermillConsumer struct {
svc *app.EnnoblementService
subscriber message.Subscriber
logger watermill.LoggerAdapter
marshaler watermillmsg.Marshaler
cmdSyncTopic string
}
func NewEnnoblementWatermillConsumer(
svc *app.EnnoblementService,
subscriber message.Subscriber,
logger watermill.LoggerAdapter,
marshaler watermillmsg.Marshaler,
cmdSyncTopic string,
) *EnnoblementWatermillConsumer {
return &EnnoblementWatermillConsumer{
svc: svc,
subscriber: subscriber,
logger: logger,
marshaler: marshaler,
cmdSyncTopic: cmdSyncTopic,
}
}
func (c *EnnoblementWatermillConsumer) Register(router *message.Router) {
router.AddNoPublisherHandler(
"EnnoblementConsumer.sync",
c.cmdSyncTopic,
c.subscriber,
c.sync,
)
}
func (c *EnnoblementWatermillConsumer) sync(msg *message.Message) error {
var rawPayload watermillmsg.SyncEnnoblementsCmdPayload
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.NewSyncEnnoblementsCmdPayload(
rawPayload.ServerKey,
rawPayload.ServerURL,
rawPayload.VersionCode,
)
if err != nil {
c.logger.Error("couldn't construct domain.SyncEnnoblementsCmdPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
return c.svc.Sync(msg.Context(), payload)
}

View File

@ -9,15 +9,16 @@ import (
) )
type ServerWatermillConsumer struct { type ServerWatermillConsumer struct {
svc *app.ServerService svc *app.ServerService
subscriber message.Subscriber subscriber message.Subscriber
logger watermill.LoggerAdapter logger watermill.LoggerAdapter
marshaler watermillmsg.Marshaler marshaler watermillmsg.Marshaler
cmdSyncTopic string cmdSyncTopic string
eventServerSyncedTopic string eventServerSyncedTopic string
eventTribesSyncedTopic string eventTribesSyncedTopic string
eventPlayersSyncedTopic string eventPlayersSyncedTopic string
eventVillagesSyncedTopic string eventVillagesSyncedTopic string
eventEnnoblementsSyncedTopic string
} }
func NewServerWatermillConsumer( func NewServerWatermillConsumer(
@ -30,17 +31,19 @@ func NewServerWatermillConsumer(
eventTribesSyncedTopic string, eventTribesSyncedTopic string,
eventPlayersSyncedTopic string, eventPlayersSyncedTopic string,
eventVillagesSyncedTopic string, eventVillagesSyncedTopic string,
eventEnnoblementsSyncedTopic string,
) *ServerWatermillConsumer { ) *ServerWatermillConsumer {
return &ServerWatermillConsumer{ return &ServerWatermillConsumer{
svc: svc, svc: svc,
subscriber: subscriber, subscriber: subscriber,
logger: logger, logger: logger,
marshaler: marshaler, marshaler: marshaler,
cmdSyncTopic: cmdSyncTopic, cmdSyncTopic: cmdSyncTopic,
eventServerSyncedTopic: eventServerSyncedTopic, eventServerSyncedTopic: eventServerSyncedTopic,
eventTribesSyncedTopic: eventTribesSyncedTopic, eventTribesSyncedTopic: eventTribesSyncedTopic,
eventPlayersSyncedTopic: eventPlayersSyncedTopic, eventPlayersSyncedTopic: eventPlayersSyncedTopic,
eventVillagesSyncedTopic: eventVillagesSyncedTopic, eventVillagesSyncedTopic: eventVillagesSyncedTopic,
eventEnnoblementsSyncedTopic: eventEnnoblementsSyncedTopic,
} }
} }
@ -70,6 +73,12 @@ func (c *ServerWatermillConsumer) Register(router *message.Router) {
c.subscriber, c.subscriber,
c.updateNumVillages, c.updateNumVillages,
) )
router.AddNoPublisherHandler(
"ServerConsumer.updateEnnoblementDataSyncedAt",
c.eventEnnoblementsSyncedTopic,
c.subscriber,
c.updateEnnoblementDataSyncedAt,
)
} }
func (c *ServerWatermillConsumer) sync(msg *message.Message) error { func (c *ServerWatermillConsumer) sync(msg *message.Message) error {
@ -194,3 +203,28 @@ func (c *ServerWatermillConsumer) updateNumVillages(msg *message.Message) error
return c.svc.UpdateNumVillages(msg.Context(), payload) return c.svc.UpdateNumVillages(msg.Context(), payload)
} }
func (c *ServerWatermillConsumer) updateEnnoblementDataSyncedAt(msg *message.Message) error {
var rawPayload watermillmsg.EnnoblementsSyncedEventPayload
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.NewEnnoblementsSyncedEventPayload(
rawPayload.ServerKey,
rawPayload.ServerURL,
rawPayload.VersionCode,
)
if err != nil {
c.logger.Error("couldn't construct domain.EnnoblementsSyncedEventPayload", err, watermill.LogFields{
"handler": message.HandlerNameFromCtx(msg.Context()),
})
return nil
}
return c.svc.UpdateEnnoblementDataSyncedAt(msg.Context(), payload)
}

View File

@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: twhelp-ennoblement-consumer-deployment
spec:
selector:
matchLabels:
app: twhelp-ennoblement-consumer
template:
metadata:
labels:
app: twhelp-ennoblement-consumer
spec:
containers:
- name: twhelp-ennoblement-consumer
image: twhelp
args: [consumer, ennoblement]
env:
- name: APP_MODE
value: development
- name: LOG_LEVEL
value: debug
- name: DB_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: twhelp-secret
key: db-connection-string
- name: RABBITMQ_CONNECTION_STRING
valueFrom:
secretKeyRef:
name: twhelp-secret
key: rabbitmq-connection-string
livenessProbe:
exec:
command: [cat, /tmp/live]
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 300m
memory: 300Mi

View File

@ -6,6 +6,7 @@ resources:
- tribe-consumer.yml - tribe-consumer.yml
- player-consumer.yml - player-consumer.yml
- village-consumer.yml - village-consumer.yml
- ennoblement-consumer.yml
images: images:
- name: twhelp - name: twhelp
newName: twhelp newName: twhelp