From d9a529167ac5f4dd4d51abc51d0b90461567f5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Sun, 10 Mar 2024 09:37:44 +0000 Subject: [PATCH] feat: add a new model - EnnoblementWithRelations (#21) Reviewed-on: https://gitea.dwysokinski.me/twhelp/corev3/pulls/21 --- .../adapter/repository_bun_ennoblement.go | 36 ++++++ .../adapter/repository_ennoblement_test.go | 18 ++- internal/adapter/repository_test.go | 4 + internal/app/service_ennoblement.go | 11 ++ internal/bun/bunmodel/ennoblement.go | 66 +++++++++++ internal/bun/bunmodel/player.go | 2 +- internal/bun/bunmodel/village.go | 22 ++++ internal/domain/domaintest/ennoblement.go | 110 ++++++++++++++++++ internal/domain/domaintest/player.go | 19 ++- internal/domain/domaintest/tribe.go | 23 ---- internal/domain/domaintest/village.go | 23 +++- internal/domain/ennoblement.go | 110 ++++++++++++++++++ internal/domain/ennoblement_test.go | 50 ++++++++ internal/domain/player.go | 6 + internal/domain/village.go | 104 ++++++++++++++++- 15 files changed, 566 insertions(+), 38 deletions(-) diff --git a/internal/adapter/repository_bun_ennoblement.go b/internal/adapter/repository_bun_ennoblement.go index 21818b5..8bb2a96 100644 --- a/internal/adapter/repository_bun_ennoblement.go +++ b/internal/adapter/repository_bun_ennoblement.go @@ -72,6 +72,42 @@ func (repo *EnnoblementBunRepository) List( return domain.NewListEnnoblementsResult(separateListResultAndNext(converted, params.Limit())) } +func (repo *EnnoblementBunRepository) ListWithRelations( + ctx context.Context, + params domain.ListEnnoblementsParams, +) (domain.ListEnnoblementsWithRelationsResult, error) { + var ennoblements bunmodel.Ennoblements + + if err := repo.db.NewSelect(). + Model(&ennoblements). + Apply(listEnnoblementsParamsApplier{params: params}.apply). + Relation("Village", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.VillageMetaColumns...) + }). + Relation("NewOwner", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.PlayerMetaColumns...) + }). + Relation("NewTribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). + Relation("OldOwner", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.PlayerMetaColumns...) + }). + Relation("OldTribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). + Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return domain.ListEnnoblementsWithRelationsResult{}, fmt.Errorf("couldn't select ennoblements from the db: %w", err) + } + + converted, err := ennoblements.ToDomainWithRelations() + if err != nil { + return domain.ListEnnoblementsWithRelationsResult{}, err + } + + return domain.NewListEnnoblementsWithRelationsResult(separateListResultAndNext(converted, params.Limit())) +} + type listEnnoblementsParamsApplier struct { params domain.ListEnnoblementsParams } diff --git a/internal/adapter/repository_ennoblement_test.go b/internal/adapter/repository_ennoblement_test.go index 0454a3a..aa3060c 100644 --- a/internal/adapter/repository_ennoblement_test.go +++ b/internal/adapter/repository_ennoblement_test.go @@ -118,7 +118,7 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit }) }) - t.Run("List", func(t *testing.T) { + t.Run("List & ListWithRelations", func(t *testing.T) { t.Parallel() repos := newRepos(t) @@ -448,6 +448,22 @@ func testEnnoblementRepository(t *testing.T, newRepos func(t *testing.T) reposit res, err := repos.ennoblement.List(ctx, params) tt.assertError(t, err) tt.assertResult(t, params, res) + + resWithRelations, err := repos.ennoblement.ListWithRelations(ctx, params) + tt.assertError(t, err) + require.Len(t, resWithRelations.Ennoblements(), len(res.Ennoblements())) + for i, e := range resWithRelations.Ennoblements() { + assert.Equal(t, res.Ennoblements()[i], e.Ennoblement()) + assert.Equal(t, e.Ennoblement().VillageID(), e.Village().ID()) + assert.Equal(t, e.Ennoblement().NewOwnerID(), e.NewOwner().V.ID()) + assert.Equal(t, e.Ennoblement().NewOwnerID() != 0, e.NewOwner().Valid) + assert.Equal(t, e.Ennoblement().NewTribeID(), e.NewTribe().V.ID()) + assert.Equal(t, e.Ennoblement().NewTribeID() != 0, e.NewTribe().Valid) + assert.Equal(t, e.Ennoblement().OldOwnerID(), e.OldOwner().V.ID()) + assert.Equal(t, e.Ennoblement().OldOwnerID() != 0, e.OldOwner().Valid) + assert.Equal(t, e.Ennoblement().OldTribeID(), e.OldTribe().V.ID()) + assert.Equal(t, e.Ennoblement().OldTribeID() != 0, e.OldTribe().Valid) + } }) } }) diff --git a/internal/adapter/repository_test.go b/internal/adapter/repository_test.go index 23954e5..d47dadf 100644 --- a/internal/adapter/repository_test.go +++ b/internal/adapter/repository_test.go @@ -48,6 +48,10 @@ type villageRepository interface { type ennoblementRepository interface { Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.ListEnnoblementsResult, error) + ListWithRelations( + ctx context.Context, + params domain.ListEnnoblementsParams, + ) (domain.ListEnnoblementsWithRelationsResult, error) } type tribeChangeRepository interface { diff --git a/internal/app/service_ennoblement.go b/internal/app/service_ennoblement.go index 0b613df..4a46d47 100644 --- a/internal/app/service_ennoblement.go +++ b/internal/app/service_ennoblement.go @@ -12,6 +12,10 @@ import ( type EnnoblementRepository interface { Create(ctx context.Context, params ...domain.CreateEnnoblementParams) error List(ctx context.Context, params domain.ListEnnoblementsParams) (domain.ListEnnoblementsResult, error) + ListWithRelations( + ctx context.Context, + params domain.ListEnnoblementsParams, + ) (domain.ListEnnoblementsWithRelationsResult, error) } type EnnoblementService struct { @@ -125,3 +129,10 @@ func (svc *EnnoblementService) List( ) (domain.ListEnnoblementsResult, error) { return svc.repo.List(ctx, params) } + +func (svc *EnnoblementService) ListWithRelations( + ctx context.Context, + params domain.ListEnnoblementsParams, +) (domain.ListEnnoblementsWithRelationsResult, error) { + return svc.repo.ListWithRelations(ctx, params) +} diff --git a/internal/bun/bunmodel/ennoblement.go b/internal/bun/bunmodel/ennoblement.go index b8f9c6a..98746dc 100644 --- a/internal/bun/bunmodel/ennoblement.go +++ b/internal/bun/bunmodel/ennoblement.go @@ -51,6 +51,57 @@ func (e Ennoblement) ToDomain() (domain.Ennoblement, error) { return converted, nil } +//nolint:gocyclo +func (e Ennoblement) ToDomainWithRelations() (domain.EnnoblementWithRelations, error) { + converted, err := e.ToDomain() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + + village, err := e.Village.ToMeta() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + + var newOwner domain.NullPlayerMeta + if e.NewOwner.ID > 0 { + newOwner.Valid = true + newOwner.V, err = e.NewOwner.ToMeta() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + } + + var newTribe domain.NullTribeMeta + if e.NewTribe.ID > 0 { + newTribe.Valid = true + newTribe.V, err = e.NewTribe.ToMeta() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + } + + var oldOwner domain.NullPlayerMeta + if e.OldOwner.ID > 0 { + oldOwner.Valid = true + oldOwner.V, err = e.OldOwner.ToMeta() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + } + + var oldTribe domain.NullTribeMeta + if e.OldTribe.ID > 0 { + oldTribe.Valid = true + oldTribe.V, err = e.OldTribe.ToMeta() + if err != nil { + return domain.EnnoblementWithRelations{}, err + } + } + + return converted.WithRelations(village, newOwner, newTribe, oldOwner, oldTribe), nil +} + type Ennoblements []Ennoblement func (es Ennoblements) ToDomain() (domain.Ennoblements, error) { @@ -67,3 +118,18 @@ func (es Ennoblements) ToDomain() (domain.Ennoblements, error) { return res, nil } + +func (es Ennoblements) ToDomainWithRelations() (domain.EnnoblementsWithRelations, error) { + res := make(domain.EnnoblementsWithRelations, 0, len(es)) + + for _, e := range es { + converted, err := e.ToDomainWithRelations() + if err != nil { + return nil, err + } + + res = append(res, converted) + } + + return res, nil +} diff --git a/internal/bun/bunmodel/player.go b/internal/bun/bunmodel/player.go index bee86dc..44052fb 100644 --- a/internal/bun/bunmodel/player.go +++ b/internal/bun/bunmodel/player.go @@ -106,7 +106,7 @@ func (p Player) ToMeta() (domain.PlayerMeta, error) { ) if err != nil { return domain.PlayerMeta{}, fmt.Errorf( - "couldn't construct domain.TribeMeta (id=%d,serverKey=%s): %w", + "couldn't construct domain.PlayerMeta (id=%d,serverKey=%s): %w", p.ID, p.ServerKey, err, diff --git a/internal/bun/bunmodel/village.go b/internal/bun/bunmodel/village.go index ae570fb..3c46e94 100644 --- a/internal/bun/bunmodel/village.go +++ b/internal/bun/bunmodel/village.go @@ -8,6 +8,8 @@ import ( "github.com/uptrace/bun" ) +var VillageMetaColumns = []string{"id", "name", "profile_url", "x", "y", "continent"} + type Village struct { bun.BaseModel `bun:"table:villages,alias:village"` @@ -70,6 +72,26 @@ func (v Village) ToDomainWithRelations() (domain.VillageWithRelations, error) { return converted.WithRelations(tribe), nil } +func (v Village) ToMeta() (domain.VillageMeta, error) { + converted, err := domain.UnmarshalVillageMetaFromDatabase( + v.ID, + v.Name, + v.X, + v.Y, + v.Continent, + v.ProfileURL, + ) + if err != nil { + return domain.VillageMeta{}, fmt.Errorf( + "couldn't construct domain.VillageMeta (id=%d,serverKey=%s): %w", + v.ID, + v.ServerKey, + err, + ) + } + return converted, nil +} + type Villages []Village func (vs Villages) ToDomain() (domain.Villages, error) { diff --git a/internal/domain/domaintest/ennoblement.go b/internal/domain/domaintest/ennoblement.go index 086a82c..a8a66da 100644 --- a/internal/domain/domaintest/ennoblement.go +++ b/internal/domain/domaintest/ennoblement.go @@ -71,3 +71,113 @@ func NewEnnoblement(tb TestingTB, opts ...func(cfg *EnnoblementConfig)) domain.E return e } + +type EnnoblementWithRelationsConfig struct { + EnnoblementOptions []func(cfg *EnnoblementConfig) + VillageOptions []func(cfg *VillageConfig) + NewOwnerOptions []func(cfg *PlayerConfig) + NewTribeOptions []func(cfg *TribeConfig) + OldOwnerOptions []func(cfg *PlayerConfig) + OldTribeOptions []func(cfg *TribeConfig) +} + +//nolint:gocyclo +func NewEnnoblementWithRelations( + tb TestingTB, + opts ...func(cfg *EnnoblementWithRelationsConfig), +) domain.EnnoblementWithRelations { + tb.Helper() + + cfg := &EnnoblementWithRelationsConfig{} + + for _, opt := range opts { + opt(cfg) + } + + e := NewEnnoblement(tb, cfg.EnnoblementOptions...) + + if e.VillageID() > 0 { + cfg.VillageOptions = append([]func(cfg *VillageConfig){ + func(cfg *VillageConfig) { + cfg.ID = e.VillageID() + }, + }, cfg.VillageOptions...) + } + + if e.NewOwnerID() > 0 { + cfg.NewOwnerOptions = append([]func(cfg *PlayerConfig){ + func(cfg *PlayerConfig) { + cfg.ID = e.NewOwnerID() + }, + }, cfg.NewOwnerOptions...) + } + + if e.NewTribeID() > 0 { + cfg.NewTribeOptions = append([]func(cfg *TribeConfig){ + func(cfg *TribeConfig) { + cfg.ID = e.NewTribeID() + }, + }, cfg.NewTribeOptions...) + } + + if e.OldOwnerID() > 0 { + cfg.OldOwnerOptions = append([]func(cfg *PlayerConfig){ + func(cfg *PlayerConfig) { + cfg.ID = e.OldOwnerID() + }, + }, cfg.OldOwnerOptions...) + } + + if e.OldTribeID() > 0 { + cfg.OldTribeOptions = append([]func(cfg *TribeConfig){ + func(cfg *TribeConfig) { + cfg.ID = e.OldTribeID() + }, + }, cfg.OldTribeOptions...) + } + + var village domain.VillageMeta + if len(cfg.VillageOptions) > 0 { + village = NewVillage(tb, cfg.VillageOptions...).Meta() + } + + var newOwner domain.PlayerMeta + if len(cfg.NewOwnerOptions) > 0 { + newOwner = NewPlayer(tb, cfg.NewOwnerOptions...).Meta() + } + + var newTribe domain.TribeMeta + if len(cfg.NewTribeOptions) > 0 { + newTribe = NewTribe(tb, cfg.NewTribeOptions...).Meta() + } + + var oldOwner domain.PlayerMeta + if len(cfg.OldOwnerOptions) > 0 { + oldOwner = NewPlayer(tb, cfg.OldOwnerOptions...).Meta() + } + + var oldTribe domain.TribeMeta + if len(cfg.OldTribeOptions) > 0 { + newTribe = NewTribe(tb, cfg.OldTribeOptions...).Meta() + } + + return e.WithRelations( + village, + domain.NullPlayerMeta{ + V: newOwner, + Valid: !newOwner.IsZero(), + }, + domain.NullTribeMeta{ + V: newTribe, + Valid: !newTribe.IsZero(), + }, + domain.NullPlayerMeta{ + V: oldOwner, + Valid: !oldOwner.IsZero(), + }, + domain.NullTribeMeta{ + V: oldTribe, + Valid: !oldTribe.IsZero(), + }, + ) +} diff --git a/internal/domain/domaintest/player.go b/internal/domain/domaintest/player.go index ed638f7..234f9c9 100644 --- a/internal/domain/domaintest/player.go +++ b/internal/domain/domaintest/player.go @@ -123,6 +123,7 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player { type PlayerWithRelationsConfig struct { PlayerOptions []func(cfg *PlayerConfig) + TribeOptions []func(cfg *TribeConfig) } func NewPlayerWithRelations(tb TestingTB, opts ...func(cfg *PlayerWithRelationsConfig)) domain.PlayerWithRelations { @@ -136,15 +137,21 @@ func NewPlayerWithRelations(tb TestingTB, opts ...func(cfg *PlayerWithRelationsC p := NewPlayer(tb, cfg.PlayerOptions...) - var tribeMeta domain.TribeMeta if p.TribeID() > 0 { - tribeMeta = NewTribeMeta(tb, func(cfg *TribeMetaConfig) { - cfg.ID = p.TribeID() - }) + cfg.TribeOptions = append([]func(cfg *TribeConfig){ + func(cfg *TribeConfig) { + cfg.ID = p.ID() + }, + }, cfg.TribeOptions...) + } + + var tribe domain.TribeMeta + if len(cfg.TribeOptions) > 0 { + tribe = NewTribe(tb, cfg.TribeOptions...).Meta() } return p.WithRelations(domain.NullTribeMeta{ - V: tribeMeta, - Valid: !tribeMeta.IsZero(), + V: tribe, + Valid: !tribe.IsZero(), }) } diff --git a/internal/domain/domaintest/tribe.go b/internal/domain/domaintest/tribe.go index a57646e..ed0cc17 100644 --- a/internal/domain/domaintest/tribe.go +++ b/internal/domain/domaintest/tribe.go @@ -120,26 +120,3 @@ func NewTribe(tb TestingTB, opts ...func(cfg *TribeConfig)) domain.Tribe { return t } - -type TribeMetaConfig struct { - ID int - Tag string -} - -func NewTribeMeta(tb TestingTB, opts ...func(cfg *TribeMetaConfig)) domain.TribeMeta { - tb.Helper() - - cfg := &TribeMetaConfig{ - ID: RandID(), - Tag: RandTribeTag(), - } - - for _, opt := range opts { - opt(cfg) - } - - return NewTribe(tb, func(tribeCfg *TribeConfig) { - tribeCfg.ID = cfg.ID - tribeCfg.Tag = cfg.Tag - }).Meta() -} diff --git a/internal/domain/domaintest/village.go b/internal/domain/domaintest/village.go index 0836344..91a865e 100644 --- a/internal/domain/domaintest/village.go +++ b/internal/domain/domaintest/village.go @@ -72,7 +72,8 @@ func NewVillage(tb TestingTB, opts ...func(cfg *VillageConfig)) domain.Village { } type VillageWithRelationsConfig struct { - PlayerOptions []func(cfg *PlayerWithRelationsConfig) + VillageOptions []func(cfg *VillageConfig) + PlayerOptions []func(cfg *PlayerWithRelationsConfig) } func NewVillageWithRelations(tb TestingTB, opts ...func(cfg *VillageWithRelationsConfig)) domain.VillageWithRelations { @@ -84,15 +85,25 @@ func NewVillageWithRelations(tb TestingTB, opts ...func(cfg *VillageWithRelation opt(cfg) } - v := NewVillage(tb) + v := NewVillage(tb, cfg.VillageOptions...) - var playerMeta domain.PlayerMetaWithRelations if v.PlayerID() > 0 { - playerMeta = NewPlayerWithRelations(tb, cfg.PlayerOptions...).Meta() + cfg.PlayerOptions = append([]func(cfg *PlayerWithRelationsConfig){ + func(cfg *PlayerWithRelationsConfig) { + cfg.PlayerOptions = append(cfg.PlayerOptions, func(cfg *PlayerConfig) { + cfg.ID = v.PlayerID() + }) + }, + }, cfg.PlayerOptions...) + } + + var player domain.PlayerMetaWithRelations + if len(cfg.PlayerOptions) > 0 { + player = NewPlayerWithRelations(tb, cfg.PlayerOptions...).Meta() } return v.WithRelations(domain.NullPlayerMetaWithRelations{ - V: playerMeta, - Valid: !playerMeta.IsZero(), + V: player, + Valid: !player.IsZero(), }) } diff --git a/internal/domain/ennoblement.go b/internal/domain/ennoblement.go index ceaebee..1f8b6b1 100644 --- a/internal/domain/ennoblement.go +++ b/internal/domain/ennoblement.go @@ -101,6 +101,23 @@ func (e Ennoblement) CreatedAt() time.Time { return e.createdAt } +func (e Ennoblement) WithRelations( + village VillageMeta, + newOwner NullPlayerMeta, + newTribe NullTribeMeta, + oldOwner NullPlayerMeta, + oldTribe NullTribeMeta, +) EnnoblementWithRelations { + return EnnoblementWithRelations{ + ennoblement: e, + village: village, + newOwner: newOwner, + newTribe: newTribe, + oldOwner: oldOwner, + oldTribe: oldTribe, + } +} + func (e Ennoblement) ToCursor() (EnnoblementCursor, error) { return NewEnnoblementCursor(e.id, e.serverKey, e.createdAt) } @@ -123,6 +140,45 @@ func (e Ennoblement) IsZero() bool { type Ennoblements []Ennoblement +type EnnoblementWithRelations struct { + ennoblement Ennoblement + village VillageMeta + newOwner NullPlayerMeta + newTribe NullTribeMeta + oldOwner NullPlayerMeta + oldTribe NullTribeMeta +} + +func (e EnnoblementWithRelations) Ennoblement() Ennoblement { + return e.ennoblement +} + +func (e EnnoblementWithRelations) Village() VillageMeta { + return e.village +} + +func (e EnnoblementWithRelations) NewOwner() NullPlayerMeta { + return e.newOwner +} + +func (e EnnoblementWithRelations) NewTribe() NullTribeMeta { + return e.newTribe +} + +func (e EnnoblementWithRelations) OldOwner() NullPlayerMeta { + return e.oldOwner +} + +func (e EnnoblementWithRelations) OldTribe() NullTribeMeta { + return e.oldTribe +} + +func (e EnnoblementWithRelations) IsZero() bool { + return e.ennoblement.IsZero() +} + +type EnnoblementsWithRelations []EnnoblementWithRelations + type CreateEnnoblementParams struct { base BaseEnnoblement serverKey string @@ -455,3 +511,57 @@ func (res ListEnnoblementsResult) Self() EnnoblementCursor { func (res ListEnnoblementsResult) Next() EnnoblementCursor { return res.next } + +type ListEnnoblementsWithRelationsResult struct { + ennoblements EnnoblementsWithRelations + self EnnoblementCursor + next EnnoblementCursor +} + +const listEnnoblementsWithRelationsResultModelName = "ListEnnoblementsWithRelationsResult" + +func NewListEnnoblementsWithRelationsResult( + ennoblements EnnoblementsWithRelations, + next EnnoblementWithRelations, +) (ListEnnoblementsWithRelationsResult, error) { + var err error + res := ListEnnoblementsWithRelationsResult{ + ennoblements: ennoblements, + } + + if len(ennoblements) > 0 { + res.self, err = ennoblements[0].Ennoblement().ToCursor() + if err != nil { + return ListEnnoblementsWithRelationsResult{}, ValidationError{ + Model: listEnnoblementsWithRelationsResultModelName, + Field: "self", + Err: err, + } + } + } + + if !next.IsZero() { + res.next, err = next.Ennoblement().ToCursor() + if err != nil { + return ListEnnoblementsWithRelationsResult{}, ValidationError{ + Model: listEnnoblementsWithRelationsResultModelName, + Field: "next", + Err: err, + } + } + } + + return res, nil +} + +func (res ListEnnoblementsWithRelationsResult) Ennoblements() EnnoblementsWithRelations { + return res.ennoblements +} + +func (res ListEnnoblementsWithRelationsResult) Self() EnnoblementCursor { + return res.self +} + +func (res ListEnnoblementsWithRelationsResult) Next() EnnoblementCursor { + return res.next +} diff --git a/internal/domain/ennoblement_test.go b/internal/domain/ennoblement_test.go index 3b96abf..7c7c1bf 100644 --- a/internal/domain/ennoblement_test.go +++ b/internal/domain/ennoblement_test.go @@ -490,8 +490,10 @@ func TestNewListEnnoblementsResult(t *testing.T) { assert.Equal(t, ennoblements, res.Ennoblements()) assert.Equal(t, ennoblements[0].ID(), res.Self().ID()) assert.Equal(t, ennoblements[0].ServerKey(), res.Self().ServerKey()) + assert.Equal(t, ennoblements[0].CreatedAt(), res.Self().CreatedAt()) assert.Equal(t, next.ID(), res.Next().ID()) assert.Equal(t, next.ServerKey(), res.Next().ServerKey()) + assert.Equal(t, next.CreatedAt(), res.Next().CreatedAt()) }) t.Run("OK: without next", func(t *testing.T) { @@ -502,6 +504,7 @@ func TestNewListEnnoblementsResult(t *testing.T) { assert.Equal(t, ennoblements, res.Ennoblements()) assert.Equal(t, ennoblements[0].ID(), res.Self().ID()) assert.Equal(t, ennoblements[0].ServerKey(), res.Self().ServerKey()) + assert.Equal(t, ennoblements[0].CreatedAt(), res.Self().CreatedAt()) assert.True(t, res.Next().IsZero()) }) @@ -515,3 +518,50 @@ func TestNewListEnnoblementsResult(t *testing.T) { assert.True(t, res.Next().IsZero()) }) } + +func TestNewListEnnoblementsWithRelationsResult(t *testing.T) { + t.Parallel() + + ennoblements := domain.EnnoblementsWithRelations{ + domaintest.NewEnnoblementWithRelations(t), + domaintest.NewEnnoblementWithRelations(t), + domaintest.NewEnnoblementWithRelations(t), + } + next := domaintest.NewEnnoblementWithRelations(t) + + t.Run("OK: with next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListEnnoblementsWithRelationsResult(ennoblements, next) + require.NoError(t, err) + assert.Equal(t, ennoblements, res.Ennoblements()) + assert.Equal(t, ennoblements[0].Ennoblement().ID(), res.Self().ID()) + assert.Equal(t, ennoblements[0].Ennoblement().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, ennoblements[0].Ennoblement().CreatedAt(), res.Self().CreatedAt()) + assert.Equal(t, next.Ennoblement().ID(), res.Next().ID()) + assert.Equal(t, next.Ennoblement().ServerKey(), res.Next().ServerKey()) + assert.Equal(t, next.Ennoblement().CreatedAt(), res.Next().CreatedAt()) + }) + + t.Run("OK: without next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListEnnoblementsWithRelationsResult(ennoblements, domain.EnnoblementWithRelations{}) + require.NoError(t, err) + assert.Equal(t, ennoblements, res.Ennoblements()) + assert.Equal(t, ennoblements[0].Ennoblement().ID(), res.Self().ID()) + assert.Equal(t, ennoblements[0].Ennoblement().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, ennoblements[0].Ennoblement().CreatedAt(), res.Self().CreatedAt()) + assert.True(t, res.Next().IsZero()) + }) + + t.Run("OK: 0 ennoblements", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayersWithRelationsResult(nil, domain.PlayerWithRelations{}) + require.NoError(t, err) + assert.Zero(t, res.Players()) + assert.True(t, res.Self().IsZero()) + assert.True(t, res.Next().IsZero()) + }) +} diff --git a/internal/domain/player.go b/internal/domain/player.go index 3636f8d..c37b19e 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -351,6 +351,12 @@ func (p PlayerMeta) WithRelations(tribe NullTribeMeta) PlayerMetaWithRelations { } } +type NullPlayerMeta NullValue[PlayerMeta] + +func (p NullPlayerMeta) IsZero() bool { + return !p.Valid +} + type PlayerMetaWithRelations struct { player PlayerMeta tribe NullTribeMeta diff --git a/internal/domain/village.go b/internal/domain/village.go index 5f356a1..98c3a5b 100644 --- a/internal/domain/village.go +++ b/internal/domain/village.go @@ -102,7 +102,7 @@ func (v Village) Name() string { } func (v Village) FullName() string { - return fmt.Sprintf("%s (%d|%d) %s", v.name, v.x, v.y, v.continent) + return formatVillageFullName(v) } func (v Village) Points() int { @@ -155,6 +155,17 @@ func (v Village) ToCursor() (VillageCursor, error) { return NewVillageCursor(v.id, v.serverKey) } +func (v Village) Meta() VillageMeta { + return VillageMeta{ + id: v.id, + name: v.name, + x: v.x, + y: v.y, + continent: v.continent, + profileURL: v.profileURL, + } +} + func (v Village) Base() BaseVillage { return BaseVillage{ id: v.id, @@ -220,6 +231,88 @@ func (v VillageWithRelations) IsZero() bool { type VillagesWithRelations []VillageWithRelations +type VillageMeta struct { + id int + name string + x int + y int + continent string + profileURL *url.URL +} + +const villageMetaModelName = "VillageMeta" + +// UnmarshalVillageMetaFromDatabase unmarshals VillageMeta from the database. +// +// It should be used only for unmarshalling from the database! +// You can't use UnmarshalVillageMetaFromDatabase as constructor - It may put domain into the invalid state! +func UnmarshalVillageMetaFromDatabase( + id int, + name string, + x int, + y int, + continent string, + rawProfileURL string, +) (VillageMeta, error) { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { + return VillageMeta{}, ValidationError{ + Model: villageMetaModelName, + Field: "id", + Err: err, + } + } + + profileURL, err := parseURL(rawProfileURL) + if err != nil { + return VillageMeta{}, ValidationError{ + Model: villageMetaModelName, + Field: "profileURL", + Err: err, + } + } + + return VillageMeta{ + id: id, + name: name, + x: x, + y: y, + continent: continent, + profileURL: profileURL, + }, nil +} + +func (v VillageMeta) ID() int { + return v.id +} + +func (v VillageMeta) Name() string { + return v.name +} + +func (v VillageMeta) FullName() string { + return formatVillageFullName(v) +} + +func (v VillageMeta) X() int { + return v.x +} + +func (v VillageMeta) Y() int { + return v.y +} + +func (v VillageMeta) Continent() string { + return v.continent +} + +func (v VillageMeta) ProfileURL() *url.URL { + return v.profileURL +} + +func (v VillageMeta) IsZero() bool { + return v == VillageMeta{} +} + type CreateVillageParams struct { base BaseVillage serverKey string @@ -732,3 +825,12 @@ func (e VillageNotFoundError) Params() map[string]any { "ServerKey": e.ServerKey, } } + +func formatVillageFullName(v interface { + Name() string + X() int + Y() int + Continent() string +}) string { + return fmt.Sprintf("%s (%d|%d) %s", v.Name(), v.X(), v.Y(), v.Continent()) +}