From 7d7c44e33838cdd1f748ed7c5a2e2dac59fdfd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Wed, 6 Mar 2024 06:35:26 +0000 Subject: [PATCH] feat: api - extend village schema (#18) Reviewed-on: https://gitea.dwysokinski.me/twhelp/corev3/pulls/18 --- api/openapi3.yml | 20 ++- internal/adapter/repository_bun_village.go | 27 ++++ internal/adapter/repository_test.go | 4 + internal/adapter/repository_village_test.go | 11 +- internal/app/service_village.go | 11 ++ internal/bun/bunmodel/player.go | 72 +++++++-- internal/bun/bunmodel/village.go | 34 ++++ internal/domain/domaintest/player.go | 10 +- internal/domain/domaintest/village.go | 26 ++++ internal/domain/player.go | 163 ++++++++++++++------ internal/domain/server.go | 12 +- internal/domain/tribe.go | 41 ++--- internal/domain/version.go | 8 +- internal/domain/village.go | 88 ++++++++++- internal/domain/village_test.go | 44 ++++++ internal/port/handler_http_api_village.go | 2 +- internal/port/internal/apimodel/player.go | 20 +++ internal/port/internal/apimodel/village.go | 6 +- 18 files changed, 493 insertions(+), 106 deletions(-) diff --git a/api/openapi3.yml b/api/openapi3.yml index b97a629..47d01fa 100644 --- a/api/openapi3.yml +++ b/api/openapi3.yml @@ -1017,7 +1017,7 @@ components: - profileUrl properties: id: - type: integer + $ref: "#/components/schemas/IntId" name: type: string tag: @@ -1097,6 +1097,22 @@ components: deletedAt: type: string format: date-time + PlayerMeta: + type: object + required: + - id + - name + - profileUrl + properties: + id: + $ref: "#/components/schemas/IntId" + name: + type: string + profileUrl: + type: string + format: uri + tribe: + $ref: "#/components/schemas/TribeMeta" Village: type: object required: @@ -1149,6 +1165,8 @@ components: createdAt: type: string format: date-time + player: + $ref: "#/components/schemas/PlayerMeta" Cursor: type: object x-go-type-skip-optional-pointer: true diff --git a/internal/adapter/repository_bun_village.go b/internal/adapter/repository_bun_village.go index 2921745..9d5544f 100644 --- a/internal/adapter/repository_bun_village.go +++ b/internal/adapter/repository_bun_village.go @@ -97,6 +97,33 @@ func (repo *VillageBunRepository) List( return domain.NewListVillagesResult(separateListResultAndNext(converted, params.Limit())) } +func (repo *VillageBunRepository) ListWithRelations( + ctx context.Context, + params domain.ListVillagesParams, +) (domain.ListVillagesWithRelationsResult, error) { + var villages bunmodel.Villages + + if err := repo.db.NewSelect(). + Model(&villages). + Apply(listVillagesParamsApplier{params: params}.apply). + Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.PlayerMetaColumns...) + }). + Relation("Player.Tribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). + Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return domain.ListVillagesWithRelationsResult{}, fmt.Errorf("couldn't select players from the db: %w", err) + } + + converted, err := villages.ToDomainWithRelations() + if err != nil { + return domain.ListVillagesWithRelationsResult{}, err + } + + return domain.NewListVillagesWithRelationsResult(separateListResultAndNext(converted, params.Limit())) +} + func (repo *VillageBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error { if len(ids) == 0 { return nil diff --git a/internal/adapter/repository_test.go b/internal/adapter/repository_test.go index 86ba068..a23ddf5 100644 --- a/internal/adapter/repository_test.go +++ b/internal/adapter/repository_test.go @@ -38,6 +38,10 @@ type playerRepository interface { type villageRepository interface { CreateOrUpdate(ctx context.Context, params ...domain.CreateVillageParams) error List(ctx context.Context, params domain.ListVillagesParams) (domain.ListVillagesResult, error) + ListWithRelations( + ctx context.Context, + params domain.ListVillagesParams, + ) (domain.ListVillagesWithRelationsResult, error) Delete(ctx context.Context, serverKey string, ids ...int) error } diff --git a/internal/adapter/repository_village_test.go b/internal/adapter/repository_village_test.go index b7fac47..3e0c12a 100644 --- a/internal/adapter/repository_village_test.go +++ b/internal/adapter/repository_village_test.go @@ -92,7 +92,7 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie }) }) - t.Run("List", func(t *testing.T) { + t.Run("List & ListWithRelations", func(t *testing.T) { t.Parallel() repos := newRepos(t) @@ -339,6 +339,15 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie res, err := repos.village.List(ctx, params) tt.assertError(t, err) tt.assertResult(t, params, res) + + resWithRelations, err := repos.village.ListWithRelations(ctx, params) + tt.assertError(t, err) + require.Len(t, resWithRelations.Villages(), len(res.Villages())) + for i, v := range resWithRelations.Villages() { + assert.Equal(t, res.Villages()[i], v.Village()) + assert.Equal(t, v.Village().PlayerID(), v.Player().V.Player().ID()) + assert.Equal(t, v.Village().PlayerID() != 0, v.Player().Valid) + } }) } }) diff --git a/internal/app/service_village.go b/internal/app/service_village.go index 08dd278..7fedfa7 100644 --- a/internal/app/service_village.go +++ b/internal/app/service_village.go @@ -10,6 +10,10 @@ import ( type VillageRepository interface { CreateOrUpdate(ctx context.Context, params ...domain.CreateVillageParams) error List(ctx context.Context, params domain.ListVillagesParams) (domain.ListVillagesResult, error) + ListWithRelations( + ctx context.Context, + params domain.ListVillagesParams, + ) (domain.ListVillagesWithRelationsResult, error) Delete(ctx context.Context, serverKey string, ids ...int) error } @@ -119,3 +123,10 @@ func (svc *VillageService) List( ) (domain.ListVillagesResult, error) { return svc.repo.List(ctx, params) } + +func (svc *VillageService) ListWithRelations( + ctx context.Context, + params domain.ListVillagesParams, +) (domain.ListVillagesWithRelationsResult, error) { + return svc.repo.ListWithRelations(ctx, params) +} diff --git a/internal/bun/bunmodel/player.go b/internal/bun/bunmodel/player.go index 9321d45..bee86dc 100644 --- a/internal/bun/bunmodel/player.go +++ b/internal/bun/bunmodel/player.go @@ -8,6 +8,8 @@ import ( "github.com/uptrace/bun" ) +var PlayerMetaColumns = []string{"id", "name", "profile_url"} + type Player struct { bun.BaseModel `bun:"table:players,alias:player"` @@ -77,6 +79,61 @@ func (p Player) ToDomain() (domain.Player, error) { return converted, nil } +func (p Player) ToDomainWithRelations() (domain.PlayerWithRelations, error) { + converted, err := p.ToDomain() + if err != nil { + return domain.PlayerWithRelations{}, err + } + + var tribe domain.NullTribeMeta + + if p.Tribe.ID > 0 { + tribe.Valid = true + tribe.V, err = p.Tribe.ToMeta() + if err != nil { + return domain.PlayerWithRelations{}, err + } + } + + return converted.WithRelations(tribe), nil +} + +func (p Player) ToMeta() (domain.PlayerMeta, error) { + converted, err := domain.UnmarshalPlayerMetaFromDatabase( + p.ID, + p.Name, + p.ProfileURL, + ) + if err != nil { + return domain.PlayerMeta{}, fmt.Errorf( + "couldn't construct domain.TribeMeta (id=%d,serverKey=%s): %w", + p.ID, + p.ServerKey, + err, + ) + } + return converted, nil +} + +func (p Player) ToMetaWithRelations() (domain.PlayerMetaWithRelations, error) { + converted, err := p.ToMeta() + if err != nil { + return domain.PlayerMetaWithRelations{}, err + } + + var tribe domain.NullTribeMeta + + if p.Tribe.ID > 0 { + tribe.Valid = true + tribe.V, err = p.Tribe.ToMeta() + if err != nil { + return domain.PlayerMetaWithRelations{}, err + } + } + + return converted.WithRelations(tribe), nil +} + type Players []Player func (ps Players) ToDomain() (domain.Players, error) { @@ -98,23 +155,12 @@ func (ps Players) ToDomainWithRelations() (domain.PlayersWithRelations, error) { res := make(domain.PlayersWithRelations, 0, len(ps)) for _, p := range ps { - var err error - var tribe domain.NullTribeMeta - - if p.Tribe.ID > 0 { - tribe.Valid = true - tribe.V, err = p.Tribe.ToMeta() - if err != nil { - return nil, err - } - } - - converted, err := p.ToDomain() + converted, err := p.ToDomainWithRelations() if err != nil { return nil, err } - res = append(res, converted.WithRelations(tribe)) + res = append(res, converted) } return res, nil diff --git a/internal/bun/bunmodel/village.go b/internal/bun/bunmodel/village.go index e749dc7..ae570fb 100644 --- a/internal/bun/bunmodel/village.go +++ b/internal/bun/bunmodel/village.go @@ -51,6 +51,25 @@ func (v Village) ToDomain() (domain.Village, error) { return converted, nil } +func (v Village) ToDomainWithRelations() (domain.VillageWithRelations, error) { + converted, err := v.ToDomain() + if err != nil { + return domain.VillageWithRelations{}, err + } + + var tribe domain.NullPlayerMetaWithRelations + + if v.Player.ID > 0 { + tribe.Valid = true + tribe.V, err = v.Player.ToMetaWithRelations() + if err != nil { + return domain.VillageWithRelations{}, err + } + } + + return converted.WithRelations(tribe), nil +} + type Villages []Village func (vs Villages) ToDomain() (domain.Villages, error) { @@ -67,3 +86,18 @@ func (vs Villages) ToDomain() (domain.Villages, error) { return res, nil } + +func (vs Villages) ToDomainWithRelations() (domain.VillagesWithRelations, error) { + res := make(domain.VillagesWithRelations, 0, len(vs)) + + for _, v := range vs { + converted, err := v.ToDomainWithRelations() + if err != nil { + return nil, err + } + + res = append(res, converted) + } + + return res, nil +} diff --git a/internal/domain/domaintest/player.go b/internal/domain/domaintest/player.go index f9414ff..ed638f7 100644 --- a/internal/domain/domaintest/player.go +++ b/internal/domain/domaintest/player.go @@ -122,23 +122,19 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player { } type PlayerWithRelationsConfig struct { - TribeID int + PlayerOptions []func(cfg *PlayerConfig) } func NewPlayerWithRelations(tb TestingTB, opts ...func(cfg *PlayerWithRelationsConfig)) domain.PlayerWithRelations { tb.Helper() - cfg := &PlayerWithRelationsConfig{ - TribeID: RandID(), - } + cfg := &PlayerWithRelationsConfig{} for _, opt := range opts { opt(cfg) } - p := NewPlayer(tb, func(playerCfg *PlayerConfig) { - playerCfg.TribeID = cfg.TribeID - }) + p := NewPlayer(tb, cfg.PlayerOptions...) var tribeMeta domain.TribeMeta if p.TribeID() > 0 { diff --git a/internal/domain/domaintest/village.go b/internal/domain/domaintest/village.go index 1f99e5f..51ac823 100644 --- a/internal/domain/domaintest/village.go +++ b/internal/domain/domaintest/village.go @@ -70,3 +70,29 @@ func NewVillage(tb TestingTB, opts ...func(cfg *VillageConfig)) domain.Village { return v } + +type VillageWithRelationsConfig struct { + PlayerOptions []func(cfg *PlayerWithRelationsConfig) +} + +func NewVillageWithRelations(tb TestingTB, opts ...func(cfg *VillageWithRelationsConfig)) domain.VillageWithRelations { + tb.Helper() + + cfg := &VillageWithRelationsConfig{} + + for _, opt := range opts { + opt(cfg) + } + + v := NewVillage(tb) + + var playerMeta domain.PlayerMetaWithRelations + if v.PlayerID() > 0 { + playerMeta = NewPlayerWithRelations(tb, cfg.PlayerOptions...).Meta() + } + + return v.WithRelations(domain.NullPlayerMetaWithRelations{ + V: playerMeta, + Valid: !playerMeta.IsZero(), + }) +} diff --git a/internal/domain/player.go b/internal/domain/player.go index 5727cd9..4cca029 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -188,6 +188,27 @@ func (p Player) WithRelations(tribe NullTribeMeta) PlayerWithRelations { } } +func (p Player) ToCursor() (PlayerCursor, error) { + return NewPlayerCursor( + p.id, + p.serverKey, + p.od.scoreAtt, + p.od.scoreDef, + p.od.scoreSup, + p.od.scoreTotal, + p.points, + p.deletedAt, + ) +} + +func (p Player) Meta() PlayerMeta { + return PlayerMeta{ + id: p.id, + name: p.name, + profileURL: p.profileURL, + } +} + func (p Player) Base() BasePlayer { return BasePlayer{ id: p.id, @@ -261,8 +282,96 @@ func (p PlayerWithRelations) Tribe() NullTribeMeta { return p.tribe } +func (p PlayerWithRelations) Meta() PlayerMetaWithRelations { + return PlayerMetaWithRelations{ + player: p.player.Meta(), + tribe: p.tribe, + } +} + func (p PlayerWithRelations) IsZero() bool { - return p.player.IsZero() && p.tribe.IsZero() + return p.player.IsZero() +} + +type PlayersWithRelations []PlayerWithRelations + +type PlayerMeta struct { + id int + name string + profileURL *url.URL +} + +const playerMetaModelName = "PlayerMeta" + +// UnmarshalPlayerMetaFromDatabase unmarshals PlayerMeta from the database. +// +// It should be used only for unmarshalling from the database! +// You can't use UnmarshalPlayerMetaFromDatabase as constructor - It may put domain into the invalid state! +func UnmarshalPlayerMetaFromDatabase(id int, name string, rawProfileURL string) (PlayerMeta, error) { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { + return PlayerMeta{}, ValidationError{ + Model: playerMetaModelName, + Field: "id", + Err: err, + } + } + + profileURL, err := parseURL(rawProfileURL) + if err != nil { + return PlayerMeta{}, ValidationError{ + Model: playerMetaModelName, + Field: "profileURL", + Err: err, + } + } + + return PlayerMeta{id: id, name: name, profileURL: profileURL}, nil +} + +func (p PlayerMeta) ID() int { + return p.id +} + +func (p PlayerMeta) Name() string { + return p.name +} + +func (p PlayerMeta) ProfileURL() *url.URL { + return p.profileURL +} + +func (p PlayerMeta) IsZero() bool { + return p == PlayerMeta{} +} + +func (p PlayerMeta) WithRelations(tribe NullTribeMeta) PlayerMetaWithRelations { + return PlayerMetaWithRelations{ + player: p, + tribe: tribe, + } +} + +type PlayerMetaWithRelations struct { + player PlayerMeta + tribe NullTribeMeta +} + +func (p PlayerMetaWithRelations) Player() PlayerMeta { + return p.player +} + +func (p PlayerMetaWithRelations) Tribe() NullTribeMeta { + return p.tribe +} + +func (p PlayerMetaWithRelations) IsZero() bool { + return p.player.IsZero() +} + +type NullPlayerMetaWithRelations NullValue[PlayerMetaWithRelations] + +func (p NullPlayerMetaWithRelations) IsZero() bool { + return !p.Valid } type CreatePlayerParams struct { @@ -277,8 +386,6 @@ type CreatePlayerParams struct { lastActivityAt time.Time } -type PlayersWithRelations []PlayerWithRelations - const createPlayerParamsModelName = "CreatePlayerParams" // NewCreatePlayerParams constructs a slice of CreatePlayerParams based on the given parameters. @@ -934,17 +1041,7 @@ func NewListPlayersResult(players Players, next Player) (ListPlayersResult, erro } if len(players) > 0 { - od := players[0].OD() - res.self, err = NewPlayerCursor( - players[0].ID(), - players[0].ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreSup(), - od.ScoreTotal(), - players[0].Points(), - players[0].DeletedAt(), - ) + res.self, err = players[0].ToCursor() if err != nil { return ListPlayersResult{}, ValidationError{ Model: listPlayersResultModelName, @@ -955,17 +1052,7 @@ func NewListPlayersResult(players Players, next Player) (ListPlayersResult, erro } if !next.IsZero() { - od := next.OD() - res.next, err = NewPlayerCursor( - next.ID(), - next.ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreSup(), - od.ScoreTotal(), - next.Points(), - next.DeletedAt(), - ) + res.next, err = next.ToCursor() if err != nil { return ListPlayersResult{}, ValidationError{ Model: listPlayersResultModelName, @@ -1008,18 +1095,7 @@ func NewListPlayersWithRelationsResult( } if len(players) > 0 { - player := players[0].Player() - od := player.OD() - res.self, err = NewPlayerCursor( - player.ID(), - player.ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreSup(), - od.ScoreTotal(), - player.Points(), - player.DeletedAt(), - ) + res.self, err = players[0].Player().ToCursor() if err != nil { return ListPlayersWithRelationsResult{}, ValidationError{ Model: listPlayersWithRelationsResultModelName, @@ -1030,18 +1106,7 @@ func NewListPlayersWithRelationsResult( } if !next.IsZero() { - player := next.Player() - od := player.OD() - res.next, err = NewPlayerCursor( - player.ID(), - player.ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreSup(), - od.ScoreTotal(), - player.Points(), - player.DeletedAt(), - ) + res.next, err = next.Player().ToCursor() if err != nil { return ListPlayersWithRelationsResult{}, ValidationError{ Model: listPlayersWithRelationsResultModelName, diff --git a/internal/domain/server.go b/internal/domain/server.go index 28d714f..0246bae 100644 --- a/internal/domain/server.go +++ b/internal/domain/server.go @@ -195,8 +195,8 @@ func (s Server) EnnoblementDataSyncedAt() time.Time { return s.ennoblementDataSyncedAt } -func (s Server) IsZero() bool { - return s == Server{} +func (s Server) ToCursor() (ServerCursor, error) { + return NewServerCursor(s.key, s.open) } func (s Server) Base() BaseServer { @@ -207,6 +207,10 @@ func (s Server) Base() BaseServer { } } +func (s Server) IsZero() bool { + return s == Server{} +} + type Servers []Server // Close finds all servers with Server.Open returning true that are not in the given slice with open servers @@ -796,7 +800,7 @@ func NewListServersResult(servers Servers, next Server) (ListServersResult, erro } if len(servers) > 0 { - res.self, err = NewServerCursor(servers[0].Key(), servers[0].Open()) + res.self, err = servers[0].ToCursor() if err != nil { return ListServersResult{}, ValidationError{ Model: listServersResultModelName, @@ -807,7 +811,7 @@ func NewListServersResult(servers Servers, next Server) (ListServersResult, erro } if !next.IsZero() { - res.next, err = NewServerCursor(next.Key(), next.Open()) + res.next, err = next.ToCursor() if err != nil { return ListServersResult{}, ValidationError{ Model: listServersResultModelName, diff --git a/internal/domain/tribe.go b/internal/domain/tribe.go index b41d95a..c51d184 100644 --- a/internal/domain/tribe.go +++ b/internal/domain/tribe.go @@ -197,8 +197,17 @@ func (t Tribe) DeletedAt() time.Time { return t.deletedAt } -func (t Tribe) IsDeleted() bool { - return !t.deletedAt.IsZero() +func (t Tribe) ToCursor() (TribeCursor, error) { + return NewTribeCursor( + t.id, + t.serverKey, + t.od.scoreAtt, + t.od.scoreDef, + t.od.scoreTotal, + t.points, + t.dominance, + t.deletedAt, + ) } func (t Tribe) Base() BaseTribe { @@ -225,6 +234,10 @@ func (t Tribe) Meta() TribeMeta { } } +func (t Tribe) IsDeleted() bool { + return !t.deletedAt.IsZero() +} + func (t Tribe) IsZero() bool { return t == Tribe{} } @@ -931,17 +944,7 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) { } if len(tribes) > 0 { - od := tribes[0].OD() - res.self, err = NewTribeCursor( - tribes[0].ID(), - tribes[0].ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreTotal(), - tribes[0].Points(), - tribes[0].Dominance(), - tribes[0].DeletedAt(), - ) + res.self, err = tribes[0].ToCursor() if err != nil { return ListTribesResult{}, ValidationError{ Model: listTribesResultModelName, @@ -952,17 +955,7 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) { } if !next.IsZero() { - od := next.OD() - res.next, err = NewTribeCursor( - next.ID(), - next.ServerKey(), - od.ScoreAtt(), - od.ScoreDef(), - od.ScoreTotal(), - next.Points(), - next.Dominance(), - next.DeletedAt(), - ) + res.next, err = next.ToCursor() if err != nil { return ListTribesResult{}, ValidationError{ Model: listTribesResultModelName, diff --git a/internal/domain/version.go b/internal/domain/version.go index 4cd537b..98fead7 100644 --- a/internal/domain/version.go +++ b/internal/domain/version.go @@ -88,6 +88,10 @@ func (v Version) URL() *url.URL { } } +func (v Version) ToCursor() (VersionCursor, error) { + return NewVersionCursor(v.code) +} + func (v Version) IsZero() bool { return v == Version{} } @@ -297,7 +301,7 @@ func NewListVersionsResult(versions Versions, next Version) (ListVersionsResult, } if len(versions) > 0 { - res.self, err = NewVersionCursor(versions[0].Code()) + res.self, err = versions[0].ToCursor() if err != nil { return ListVersionsResult{}, ValidationError{ Model: listVersionsResultModelName, @@ -308,7 +312,7 @@ func NewListVersionsResult(versions Versions, next Version) (ListVersionsResult, } if !next.IsZero() { - res.next, err = NewVersionCursor(next.Code()) + res.next, err = next.ToCursor() if err != nil { return ListVersionsResult{}, ValidationError{ Model: listVersionsResultModelName, diff --git a/internal/domain/village.go b/internal/domain/village.go index ae41e1c..6d5038f 100644 --- a/internal/domain/village.go +++ b/internal/domain/village.go @@ -137,6 +137,17 @@ func (v Village) CreatedAt() time.Time { return v.createdAt } +func (v Village) WithRelations(player NullPlayerMetaWithRelations) VillageWithRelations { + return VillageWithRelations{ + village: v, + player: player, + } +} + +func (v Village) ToCursor() (VillageCursor, error) { + return NewVillageCursor(v.id, v.serverKey) +} + func (v Village) Base() BaseVillage { return BaseVillage{ id: v.id, @@ -183,6 +194,25 @@ func (vs Villages) Delete(serverKey string, active BaseVillages) []int { return toDelete } +type VillageWithRelations struct { + village Village + player NullPlayerMetaWithRelations +} + +func (v VillageWithRelations) Village() Village { + return v.village +} + +func (v VillageWithRelations) Player() NullPlayerMetaWithRelations { + return v.player +} + +func (v VillageWithRelations) IsZero() bool { + return v.village.IsZero() +} + +type VillagesWithRelations []VillageWithRelations + type CreateVillageParams struct { base BaseVillage serverKey string @@ -482,7 +512,7 @@ func NewListVillagesResult(villages Villages, next Village) (ListVillagesResult, } if len(villages) > 0 { - res.self, err = NewVillageCursor(villages[0].ID(), villages[0].ServerKey()) + res.self, err = villages[0].ToCursor() if err != nil { return ListVillagesResult{}, ValidationError{ Model: listVillagesResultModelName, @@ -493,7 +523,7 @@ func NewListVillagesResult(villages Villages, next Village) (ListVillagesResult, } if !next.IsZero() { - res.next, err = NewVillageCursor(next.ID(), next.ServerKey()) + res.next, err = next.ToCursor() if err != nil { return ListVillagesResult{}, ValidationError{ Model: listVillagesResultModelName, @@ -517,3 +547,57 @@ func (res ListVillagesResult) Self() VillageCursor { func (res ListVillagesResult) Next() VillageCursor { return res.next } + +type ListVillagesWithRelationsResult struct { + villages VillagesWithRelations + self VillageCursor + next VillageCursor +} + +const listVillagesWithRelationsResultModelName = "ListVillagesWithRelationsResult" + +func NewListVillagesWithRelationsResult( + villages VillagesWithRelations, + next VillageWithRelations, +) (ListVillagesWithRelationsResult, error) { + var err error + res := ListVillagesWithRelationsResult{ + villages: villages, + } + + if len(villages) > 0 { + res.self, err = villages[0].Village().ToCursor() + if err != nil { + return ListVillagesWithRelationsResult{}, ValidationError{ + Model: listVillagesWithRelationsResultModelName, + Field: "self", + Err: err, + } + } + } + + if !next.IsZero() { + res.next, err = next.Village().ToCursor() + if err != nil { + return ListVillagesWithRelationsResult{}, ValidationError{ + Model: listVillagesWithRelationsResultModelName, + Field: "next", + Err: err, + } + } + } + + return res, nil +} + +func (res ListVillagesWithRelationsResult) Villages() VillagesWithRelations { + return res.villages +} + +func (res ListVillagesWithRelationsResult) Self() VillageCursor { + return res.self +} + +func (res ListVillagesWithRelationsResult) Next() VillageCursor { + return res.next +} diff --git a/internal/domain/village_test.go b/internal/domain/village_test.go index 0502a2e..ecd98b2 100644 --- a/internal/domain/village_test.go +++ b/internal/domain/village_test.go @@ -612,3 +612,47 @@ func TestNewListVillagesResult(t *testing.T) { assert.True(t, res.Next().IsZero()) }) } + +func TestNewListVillagesWithRelationsResult(t *testing.T) { + t.Parallel() + + villages := domain.VillagesWithRelations{ + domaintest.NewVillageWithRelations(t), + domaintest.NewVillageWithRelations(t), + domaintest.NewVillageWithRelations(t), + } + next := domaintest.NewVillageWithRelations(t) + + t.Run("OK: with next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListVillagesWithRelationsResult(villages, next) + require.NoError(t, err) + assert.Equal(t, villages, res.Villages()) + assert.Equal(t, villages[0].Village().ID(), res.Self().ID()) + assert.Equal(t, villages[0].Village().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, next.Village().ID(), res.Next().ID()) + assert.Equal(t, next.Village().ServerKey(), res.Next().ServerKey()) + }) + + t.Run("OK: without next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListVillagesWithRelationsResult(villages, domain.VillageWithRelations{}) + require.NoError(t, err) + assert.Equal(t, villages, res.Villages()) + assert.Equal(t, villages[0].Village().ID(), res.Self().ID()) + assert.Equal(t, villages[0].Village().ServerKey(), res.Self().ServerKey()) + assert.True(t, res.Next().IsZero()) + }) + + t.Run("OK: 0 villages", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListVillagesWithRelationsResult(nil, domain.VillageWithRelations{}) + require.NoError(t, err) + assert.Zero(t, res.Villages()) + assert.True(t, res.Self().IsZero()) + assert.True(t, res.Next().IsZero()) + }) +} diff --git a/internal/port/handler_http_api_village.go b/internal/port/handler_http_api_village.go index b5af161..915a43a 100644 --- a/internal/port/handler_http_api_village.go +++ b/internal/port/handler_http_api_village.go @@ -40,7 +40,7 @@ func (h *apiHTTPHandler) ListVillages( } } - res, err := h.villageSvc.List(r.Context(), domainParams) + res, err := h.villageSvc.ListWithRelations(r.Context(), domainParams) if err != nil { h.errorRenderer.render(w, r, err) return diff --git a/internal/port/internal/apimodel/player.go b/internal/port/internal/apimodel/player.go index acea881..cb53244 100644 --- a/internal/port/internal/apimodel/player.go +++ b/internal/port/internal/apimodel/player.go @@ -46,6 +46,26 @@ func NewPlayer(withRelations domain.PlayerWithRelations) Player { return converted } +func NewPlayerMeta(withRelations domain.PlayerMetaWithRelations) PlayerMeta { + p := withRelations.Player() + return PlayerMeta{ + Id: p.ID(), + Name: p.Name(), + ProfileUrl: p.ProfileURL().String(), + Tribe: NewNullTribeMeta(withRelations.Tribe()), + } +} + +func NewNullPlayerMeta(p domain.NullPlayerMetaWithRelations) *PlayerMeta { + if !p.Valid { + return nil + } + + converted := NewPlayerMeta(p.V) + + return &converted +} + func NewListPlayersResponse(res domain.ListPlayersWithRelationsResult) ListPlayersResponse { players := res.Players() diff --git a/internal/port/internal/apimodel/village.go b/internal/port/internal/apimodel/village.go index bd3884f..1a3f5d3 100644 --- a/internal/port/internal/apimodel/village.go +++ b/internal/port/internal/apimodel/village.go @@ -4,7 +4,8 @@ import ( "gitea.dwysokinski.me/twhelp/corev3/internal/domain" ) -func NewVillage(v domain.Village) Village { +func NewVillage(withRelations domain.VillageWithRelations) Village { + v := withRelations.Village() return Village{ Bonus: v.Bonus(), Continent: v.Continent(), @@ -16,10 +17,11 @@ func NewVillage(v domain.Village) Village { ProfileUrl: v.ProfileURL().String(), X: v.X(), Y: v.Y(), + Player: NewNullPlayerMeta(withRelations.Player()), } } -func NewListVillagesResponse(res domain.ListVillagesResult) ListVillagesResponse { +func NewListVillagesResponse(res domain.ListVillagesWithRelationsResult) ListVillagesResponse { versions := res.Villages() resp := ListVillagesResponse{