From 0be010ab5084cf2079f537d5674001913ede3bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Wed, 28 Feb 2024 06:59:10 +0000 Subject: [PATCH] feat: api - extend player schema (#8) Reviewed-on: https://gitea.dwysokinski.me/twhelp/corev3/pulls/8 --- api/openapi3.yml | 21 +++- internal/adapter/repository_bun_player.go | 26 ++++- internal/adapter/repository_bun_server.go | 38 +++---- internal/adapter/repository_bun_tribe.go | 6 +- internal/adapter/repository_bun_village.go | 2 +- .../repository_player_snapshot_test.go | 2 +- internal/adapter/repository_player_test.go | 19 +++- internal/adapter/repository_server_test.go | 70 ++++++------ internal/adapter/repository_test.go | 1 + .../adapter/repository_tribe_snapshot_test.go | 2 +- internal/adapter/repository_tribe_test.go | 14 +-- internal/adapter/repository_village_test.go | 6 +- internal/app/service_player.go | 10 +- internal/app/service_player_snapshot.go | 2 +- internal/app/service_server.go | 36 +++--- internal/app/service_snapshot.go | 8 +- internal/app/service_tribe.go | 2 +- internal/app/service_tribe_snapshot.go | 2 +- internal/app/service_village.go | 2 +- internal/bun/bunmodel/player.go | 26 +++++ internal/bun/bunmodel/tribe.go | 20 ++++ internal/domain/domaintest/player.go | 32 ++++++ internal/domain/domaintest/tribe.go | 23 ++++ internal/domain/null.go | 4 +- internal/domain/player.go | 103 +++++++++++++++++- internal/domain/player_test.go | 52 ++++++++- internal/domain/server.go | 4 +- internal/domain/server_test.go | 4 +- internal/domain/tribe.go | 71 +++++++++++- internal/domain/tribe_test.go | 8 +- internal/domain/village.go | 4 +- internal/domain/village_test.go | 12 +- internal/port/consumer_data_sync_test.go | 4 +- .../port/consumer_ennoblement_sync_test.go | 2 +- .../port/consumer_snapshot_creation_test.go | 6 +- internal/port/handler_http_api_player.go | 4 +- internal/port/handler_http_api_server.go | 2 +- internal/port/handler_http_api_tribe.go | 2 +- internal/port/handler_http_api_tribe_test.go | 4 +- internal/port/internal/apimodel/player.go | 7 +- internal/port/internal/apimodel/tribe.go | 19 ++++ 41 files changed, 538 insertions(+), 144 deletions(-) diff --git a/api/openapi3.yml b/api/openapi3.yml index 1d54946..8f614c7 100644 --- a/api/openapi3.yml +++ b/api/openapi3.yml @@ -941,6 +941,23 @@ components: deletedAt: type: string format: date-time + TribeMeta: + type: object + required: + - id + - name + - tag + - profileUrl + properties: + id: + type: integer + name: + type: string + tag: + type: string + profileUrl: + type: string + format: uri PlayerOpponentsDefeated: allOf: - $ref: "#/components/schemas/TribeOpponentsDefeated" @@ -985,6 +1002,8 @@ components: profileUrl: type: string format: uri + tribe: + $ref: "#/components/schemas/TribeMeta" lastActivityAt: type: string format: date-time @@ -1140,7 +1159,7 @@ components: required: true schema: type: integer - minimum: 0 + minimum: 1 responses: ListVersionsResponse: description: "" diff --git a/internal/adapter/repository_bun_player.go b/internal/adapter/repository_bun_player.go index 8191a1c..19bdced 100644 --- a/internal/adapter/repository_bun_player.go +++ b/internal/adapter/repository_bun_player.go @@ -110,6 +110,30 @@ func (repo *PlayerBunRepository) List( return domain.NewListPlayersResult(separateListResultAndNext(converted, params.Limit())) } +func (repo *PlayerBunRepository) ListWithRelations( + ctx context.Context, + params domain.ListPlayersParams, +) (domain.ListPlayersWithRelationsResult, error) { + var players bunmodel.Players + + if err := repo.db.NewSelect(). + Model(&players). + Apply(listPlayersParamsApplier{params: params}.apply). + Relation("Tribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). + Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return domain.ListPlayersWithRelationsResult{}, fmt.Errorf("couldn't select players from the db: %w", err) + } + + converted, err := players.ToDomainWithRelations() + if err != nil { + return domain.ListPlayersWithRelationsResult{}, err + } + + return domain.NewListPlayersWithRelationsResult(separateListResultAndNext(converted, params.Limit())) +} + func (repo *PlayerBunRepository) Delete(ctx context.Context, serverKey string, ids ...int) error { if len(ids) == 0 { return nil @@ -145,7 +169,7 @@ func (a listPlayersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { } if deleted := a.params.Deleted(); deleted.Valid { - if deleted.Value { + if deleted.V { q = q.Where("player.deleted_at IS NOT NULL") } else { q = q.Where("player.deleted_at IS NULL") diff --git a/internal/adapter/repository_bun_server.go b/internal/adapter/repository_bun_server.go index c3d71e8..0f73149 100644 --- a/internal/adapter/repository_bun_server.go +++ b/internal/adapter/repository_bun_server.go @@ -115,67 +115,67 @@ type updateServerParamsApplier struct { //nolint:gocyclo func (a updateServerParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery { if config := a.params.Config(); config.Valid { - q = q.Set("config = ?", bunmodel.NewServerConfig(config.Value)) + q = q.Set("config = ?", bunmodel.NewServerConfig(config.V)) } if unitInfo := a.params.UnitInfo(); unitInfo.Valid { - q = q.Set("unit_info = ?", bunmodel.NewUnitInfo(unitInfo.Value)) + q = q.Set("unit_info = ?", bunmodel.NewUnitInfo(unitInfo.V)) } if buildingInfo := a.params.BuildingInfo(); buildingInfo.Valid { - q = q.Set("building_info = ?", bunmodel.NewBuildingInfo(buildingInfo.Value)) + q = q.Set("building_info = ?", bunmodel.NewBuildingInfo(buildingInfo.V)) } if numTribes := a.params.NumTribes(); numTribes.Valid { - q = q.Set("num_tribes = ?", numTribes.Value) + q = q.Set("num_tribes = ?", numTribes.V) } if tribeDataSyncedAt := a.params.TribeDataSyncedAt(); tribeDataSyncedAt.Valid { // TODO: rename this column to tribe_data_synced_at - q = q.Set("tribe_data_updated_at = ?", tribeDataSyncedAt.Value) + q = q.Set("tribe_data_updated_at = ?", tribeDataSyncedAt.V) } if numPlayers := a.params.NumPlayers(); numPlayers.Valid { - q = q.Set("num_players = ?", numPlayers.Value) + q = q.Set("num_players = ?", numPlayers.V) } if playerDataSyncedAt := a.params.PlayerDataSyncedAt(); playerDataSyncedAt.Valid { // TODO: rename this column to player_data_synced_at - q = q.Set("player_data_updated_at = ?", playerDataSyncedAt.Value) + q = q.Set("player_data_updated_at = ?", playerDataSyncedAt.V) } if numVillages := a.params.NumVillages(); numVillages.Valid { - q = q.Set("num_villages = ?", numVillages.Value) + q = q.Set("num_villages = ?", numVillages.V) } if numPlayerVillages := a.params.NumPlayerVillages(); numPlayerVillages.Valid { - q = q.Set("num_player_villages = ?", numPlayerVillages.Value) + q = q.Set("num_player_villages = ?", numPlayerVillages.V) } if numBarbarianVillages := a.params.NumBarbarianVillages(); numBarbarianVillages.Valid { - q = q.Set("num_barbarian_villages = ?", numBarbarianVillages.Value) + q = q.Set("num_barbarian_villages = ?", numBarbarianVillages.V) } if numBonusVillages := a.params.NumBonusVillages(); numBonusVillages.Valid { - q = q.Set("num_bonus_villages = ?", numBonusVillages.Value) + q = q.Set("num_bonus_villages = ?", numBonusVillages.V) } if villageDataSyncedAt := a.params.VillageDataSyncedAt(); villageDataSyncedAt.Valid { // TODO: rename this column to village_data_synced_at - q = q.Set("village_data_updated_at = ?", villageDataSyncedAt.Value) + q = q.Set("village_data_updated_at = ?", villageDataSyncedAt.V) } if ennoblementDataSyncedAt := a.params.EnnoblementDataSyncedAt(); ennoblementDataSyncedAt.Valid { // TODO: rename this column to ennoblement_data_synced_at - q = q.Set("ennoblement_data_updated_at = ?", ennoblementDataSyncedAt.Value) + q = q.Set("ennoblement_data_updated_at = ?", ennoblementDataSyncedAt.V) } if tribeSnapshotsCreatedAt := a.params.TribeSnapshotsCreatedAt(); tribeSnapshotsCreatedAt.Valid { - q = q.Set("tribe_snapshots_created_at = ?", tribeSnapshotsCreatedAt.Value) + q = q.Set("tribe_snapshots_created_at = ?", tribeSnapshotsCreatedAt.V) } if playerSnapshotsCreatedAt := a.params.PlayerSnapshotsCreatedAt(); playerSnapshotsCreatedAt.Valid { - q = q.Set("player_snapshots_created_at = ?", playerSnapshotsCreatedAt.Value) + q = q.Set("player_snapshots_created_at = ?", playerSnapshotsCreatedAt.V) } return q @@ -196,24 +196,24 @@ func (a listServersParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { } if open := a.params.Open(); open.Valid { - q = q.Where("server.open = ?", open.Value) + q = q.Where("server.open = ?", open.V) } if special := a.params.Special(); special.Valid { - q = q.Where("server.special = ?", special.Value) + q = q.Where("server.special = ?", special.V) } if tribeSnapshotsCreatedAtLT := a.params.TribeSnapshotsCreatedAtLT(); tribeSnapshotsCreatedAtLT.Valid { q = q.Where( "server.tribe_snapshots_created_at < ? OR server.tribe_snapshots_created_at is null", - tribeSnapshotsCreatedAtLT.Value, + tribeSnapshotsCreatedAtLT.V, ) } if playerSnapshotsCreatedAtLT := a.params.PlayerSnapshotsCreatedAtLT(); playerSnapshotsCreatedAtLT.Valid { q = q.Where( "server.player_snapshots_created_at < ? OR server.player_snapshots_created_at is null", - playerSnapshotsCreatedAtLT.Value, + playerSnapshotsCreatedAtLT.V, ) } diff --git a/internal/adapter/repository_bun_tribe.go b/internal/adapter/repository_bun_tribe.go index 4b36562..ab5f403 100644 --- a/internal/adapter/repository_bun_tribe.go +++ b/internal/adapter/repository_bun_tribe.go @@ -117,10 +117,6 @@ func (repo *TribeBunRepository) List( ) (domain.ListTribesResult, error) { var tribes bunmodel.Tribes - fmt.Println(repo.db.NewSelect(). - Model(&tribes). - Apply(listTribesParamsApplier{params: params}.apply).String()) - if err := repo.db.NewSelect(). Model(&tribes). Apply(listTribesParamsApplier{params: params}.apply). @@ -174,7 +170,7 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { } if deleted := a.params.Deleted(); deleted.Valid { - if deleted.Value { + if deleted.V { q = q.Where("tribe.deleted_at IS NOT NULL") } else { q = q.Where("tribe.deleted_at IS NULL") diff --git a/internal/adapter/repository_bun_village.go b/internal/adapter/repository_bun_village.go index 2b83ed0..145d65d 100644 --- a/internal/adapter/repository_bun_village.go +++ b/internal/adapter/repository_bun_village.go @@ -117,7 +117,7 @@ func (a listVillagesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { } if idGT := a.params.IDGT(); idGT.Valid { - q = q.Where("village.id > ?", idGT.Value) + q = q.Where("village.id > ?", idGT.V) } if serverKeys := a.params.ServerKeys(); len(serverKeys) > 0 { diff --git a/internal/adapter/repository_player_snapshot_test.go b/internal/adapter/repository_player_snapshot_test.go index fe58e9a..a9d2e31 100644 --- a/internal/adapter/repository_player_snapshot_test.go +++ b/internal/adapter/repository_player_snapshot_test.go @@ -85,7 +85,7 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo listPlayersParams := domain.NewListPlayersParams() require.NoError(t, listPlayersParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(t, listPlayersParams.SetLimit(domain.PlayerSnapshotListMaxLimit/2)) diff --git a/internal/adapter/repository_player_test.go b/internal/adapter/repository_player_test.go index 4490ec9..efd5235 100644 --- a/internal/adapter/repository_player_test.go +++ b/internal/adapter/repository_player_test.go @@ -100,7 +100,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories }) }) - t.Run("List", func(t *testing.T) { + t.Run("List & ListWithRelations", func(t *testing.T) { t.Parallel() repos := newRepos(t) @@ -374,7 +374,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListPlayersParams() require.NoError(t, params.SetDeleted(domain.NullBool{ - Value: true, + V: true, Valid: true, })) return params @@ -398,7 +398,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListPlayersParams() require.NoError(t, params.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) return params @@ -567,6 +567,15 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories res, err := repos.player.List(ctx, params) tt.assertError(t, err) tt.assertResult(t, params, res) + + resWithRelations, err := repos.player.ListWithRelations(ctx, params) + tt.assertError(t, err) + require.Len(t, resWithRelations.Players(), len(res.Players())) + for i, p := range resWithRelations.Players() { + assert.Equal(t, res.Players()[i], p.Player()) + assert.Equal(t, p.Player().TribeID(), p.Tribe().V.ID()) + assert.Equal(t, p.Player().TribeID() != 0, p.Tribe().Valid) + } }) } }) @@ -577,7 +586,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories repos := newRepos(t) listServersParams := domain.NewListServersParams() - require.NoError(t, listServersParams.SetSpecial(domain.NullBool{Value: false, Valid: true})) + require.NoError(t, listServersParams.SetSpecial(domain.NullBool{V: false, Valid: true})) listServersRes, listServersErr := repos.server.List(ctx, listServersParams) require.NoError(t, listServersErr) require.NotEmpty(t, listServersRes) @@ -591,7 +600,7 @@ func testPlayerRepository(t *testing.T, newRepos func(t *testing.T) repositories } listPlayersParams := domain.NewListPlayersParams() - require.NoError(t, listPlayersParams.SetDeleted(domain.NullBool{Value: false, Valid: true})) + require.NoError(t, listPlayersParams.SetDeleted(domain.NullBool{V: false, Valid: true})) require.NoError(t, listPlayersParams.SetServerKeys(serverKeys)) res, err := repos.player.List(ctx, listPlayersParams) diff --git a/internal/adapter/repository_server_test.go b/internal/adapter/repository_server_test.go index fdd35de..7dc5b39 100644 --- a/internal/adapter/repository_server_test.go +++ b/internal/adapter/repository_server_test.go @@ -267,7 +267,7 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListServersParams() require.NoError(t, params.SetSpecial(domain.NullBool{ - Value: true, + V: true, Valid: true, })) return params @@ -291,7 +291,7 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListServersParams() require.NoError(t, params.SetOpen(domain.NullBool{ - Value: false, + V: false, Valid: true, })) return params @@ -315,7 +315,7 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListServersParams() require.NoError(t, params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{ - Value: snapshotsCreatedAtLT, + V: snapshotsCreatedAtLT, Valid: true, })) return params @@ -339,7 +339,7 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories t.Helper() params := domain.NewListServersParams() require.NoError(t, params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{ - Value: snapshotsCreatedAtLT, + V: snapshotsCreatedAtLT, Valid: true, })) return params @@ -528,63 +528,63 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories var updateParams domain.UpdateServerParams require.NoError(t, updateParams.SetConfig(domain.NullServerConfig{ - Value: domaintest.NewServerConfig(t), + V: domaintest.NewServerConfig(t), Valid: true, })) require.NoError(t, updateParams.SetUnitInfo(domain.NullUnitInfo{ - Value: domaintest.NewUnitInfo(t), + V: domaintest.NewUnitInfo(t), Valid: true, })) require.NoError(t, updateParams.SetBuildingInfo(domain.NullBuildingInfo{ - Value: domaintest.NewBuildingInfo(t), + V: domaintest.NewBuildingInfo(t), Valid: true, })) require.NoError(t, updateParams.SetNumTribes(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetTribeDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) require.NoError(t, updateParams.SetNumPlayers(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetPlayerDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) require.NoError(t, updateParams.SetNumVillages(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetNumPlayerVillages(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetNumBonusVillages(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetNumBarbarianVillages(domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, })) require.NoError(t, updateParams.SetVillageDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) require.NoError(t, updateParams.SetEnnoblementDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) require.NoError(t, updateParams.SetTribeSnapshotsCreatedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) require.NoError(t, updateParams.SetPlayerSnapshotsCreatedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, })) @@ -598,48 +598,48 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories require.NoError(t, err) require.NotEmpty(t, serversAfterUpdate) serverAfterUpdate := serversAfterUpdate.Servers()[0] - assert.Equal(t, updateParams.Config().Value, serverAfterUpdate.Config()) - assert.Equal(t, updateParams.UnitInfo().Value, serverAfterUpdate.UnitInfo()) - assert.Equal(t, updateParams.BuildingInfo().Value, serverAfterUpdate.BuildingInfo()) - assert.Equal(t, updateParams.NumTribes().Value, serverAfterUpdate.NumTribes()) + assert.Equal(t, updateParams.Config().V, serverAfterUpdate.Config()) + assert.Equal(t, updateParams.UnitInfo().V, serverAfterUpdate.UnitInfo()) + assert.Equal(t, updateParams.BuildingInfo().V, serverAfterUpdate.BuildingInfo()) + assert.Equal(t, updateParams.NumTribes().V, serverAfterUpdate.NumTribes()) assert.WithinDuration( t, - updateParams.TribeDataSyncedAt().Value, + updateParams.TribeDataSyncedAt().V, serverAfterUpdate.TribeDataSyncedAt(), time.Minute, ) - assert.Equal(t, updateParams.NumPlayers().Value, serverAfterUpdate.NumPlayers()) + assert.Equal(t, updateParams.NumPlayers().V, serverAfterUpdate.NumPlayers()) assert.WithinDuration( t, - updateParams.PlayerDataSyncedAt().Value, + updateParams.PlayerDataSyncedAt().V, serverAfterUpdate.PlayerDataSyncedAt(), time.Minute, ) - assert.Equal(t, updateParams.NumVillages().Value, serverAfterUpdate.NumVillages()) - assert.Equal(t, updateParams.NumPlayerVillages().Value, serverAfterUpdate.NumPlayerVillages()) - assert.Equal(t, updateParams.NumBarbarianVillages().Value, serverAfterUpdate.NumBarbarianVillages()) - assert.Equal(t, updateParams.NumBonusVillages().Value, serverAfterUpdate.NumBonusVillages()) + assert.Equal(t, updateParams.NumVillages().V, serverAfterUpdate.NumVillages()) + assert.Equal(t, updateParams.NumPlayerVillages().V, serverAfterUpdate.NumPlayerVillages()) + assert.Equal(t, updateParams.NumBarbarianVillages().V, serverAfterUpdate.NumBarbarianVillages()) + assert.Equal(t, updateParams.NumBonusVillages().V, serverAfterUpdate.NumBonusVillages()) assert.WithinDuration( t, - updateParams.VillageDataSyncedAt().Value, + updateParams.VillageDataSyncedAt().V, serverAfterUpdate.VillageDataSyncedAt(), time.Minute, ) assert.WithinDuration( t, - updateParams.EnnoblementDataSyncedAt().Value, + updateParams.EnnoblementDataSyncedAt().V, serverAfterUpdate.EnnoblementDataSyncedAt(), time.Minute, ) assert.WithinDuration( t, - updateParams.TribeSnapshotsCreatedAt().Value, + updateParams.TribeSnapshotsCreatedAt().V, serverAfterUpdate.TribeSnapshotsCreatedAt(), time.Minute, ) assert.WithinDuration( t, - updateParams.PlayerSnapshotsCreatedAt().Value, + updateParams.PlayerSnapshotsCreatedAt().V, serverAfterUpdate.PlayerSnapshotsCreatedAt(), time.Minute, ) @@ -650,7 +650,7 @@ func testServerRepository(t *testing.T, newRepos func(t *testing.T) repositories var updateParams domain.UpdateServerParams require.NoError(t, updateParams.SetConfig(domain.NullServerConfig{ - Value: domaintest.NewServerConfig(t), + V: domaintest.NewServerConfig(t), Valid: true, })) diff --git a/internal/adapter/repository_test.go b/internal/adapter/repository_test.go index 1a75c8c..337c160 100644 --- a/internal/adapter/repository_test.go +++ b/internal/adapter/repository_test.go @@ -31,6 +31,7 @@ type tribeRepository interface { type playerRepository interface { CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error) + ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error) Delete(ctx context.Context, serverKey string, ids ...int) error } diff --git a/internal/adapter/repository_tribe_snapshot_test.go b/internal/adapter/repository_tribe_snapshot_test.go index a958080..898bcae 100644 --- a/internal/adapter/repository_tribe_snapshot_test.go +++ b/internal/adapter/repository_tribe_snapshot_test.go @@ -87,7 +87,7 @@ func testTribeSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repos listTribesParams := domain.NewListTribesParams() require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) diff --git a/internal/adapter/repository_tribe_test.go b/internal/adapter/repository_tribe_test.go index e8b9c0f..44f6ed0 100644 --- a/internal/adapter/repository_tribe_test.go +++ b/internal/adapter/repository_tribe_test.go @@ -106,11 +106,11 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) listServersParams := domain.NewListServersParams() require.NoError(t, listServersParams.SetOpen(domain.NullBool{ - Value: true, + V: true, Valid: true, })) require.NoError(t, listServersParams.SetSpecial(domain.NullBool{ - Value: false, + V: false, Valid: true, })) @@ -143,7 +143,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) listTribesParams := domain.NewListTribesParams() require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(t, listTribesParams.SetServerKeys([]string{tt.serverKey})) @@ -501,7 +501,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) t.Helper() params := domain.NewListTribesParams() require.NoError(t, params.SetDeleted(domain.NullBool{ - Value: true, + V: true, Valid: true, })) return params @@ -525,7 +525,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) t.Helper() params := domain.NewListTribesParams() require.NoError(t, params.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) return params @@ -797,7 +797,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) repos := newRepos(t) listServersParams := domain.NewListServersParams() - require.NoError(t, listServersParams.SetSpecial(domain.NullBool{Value: false, Valid: true})) + require.NoError(t, listServersParams.SetSpecial(domain.NullBool{V: false, Valid: true})) listServersRes, listServersErr := repos.server.List(ctx, listServersParams) require.NoError(t, listServersErr) require.NotEmpty(t, listServersRes) @@ -811,7 +811,7 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories) } listTribesParams := domain.NewListTribesParams() - require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{Value: false, Valid: true})) + require.NoError(t, listTribesParams.SetDeleted(domain.NullBool{V: false, Valid: true})) require.NoError(t, listTribesParams.SetServerKeys(serverKeys)) res, err := repos.tribe.List(ctx, listTribesParams) diff --git a/internal/adapter/repository_village_test.go b/internal/adapter/repository_village_test.go index eff7bc7..7e7c3ea 100644 --- a/internal/adapter/repository_village_test.go +++ b/internal/adapter/repository_village_test.go @@ -196,7 +196,7 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie t.Helper() params := domain.NewListVillagesParams() require.NoError(t, params.SetIDGT(domain.NullInt{ - Value: randVillage.ID(), + V: randVillage.ID(), Valid: true, })) return params @@ -205,7 +205,7 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie t.Helper() assert.NotEmpty(t, villages) for _, v := range villages { - assert.Greater(t, v.ID(), params.IDGT().Value, v.ID()) + assert.Greater(t, v.ID(), params.IDGT().V, v.ID()) } }, assertError: func(t *testing.T, err error) { @@ -260,7 +260,7 @@ func testVillageRepository(t *testing.T, newRepos func(t *testing.T) repositorie repos := newRepos(t) listServersParams := domain.NewListServersParams() - require.NoError(t, listServersParams.SetSpecial(domain.NullBool{Value: false, Valid: true})) + require.NoError(t, listServersParams.SetSpecial(domain.NullBool{V: false, Valid: true})) listServersRes, listServersErr := repos.server.List(ctx, listServersParams) require.NoError(t, listServersErr) require.NotEmpty(t, listServersRes) diff --git a/internal/app/service_player.go b/internal/app/service_player.go index 9751f7f..6d30a03 100644 --- a/internal/app/service_player.go +++ b/internal/app/service_player.go @@ -10,6 +10,7 @@ import ( type PlayerRepository interface { CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error) + ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error) // Delete marks players with the given serverKey and ids as deleted (sets deleted at to now). // In addition, Delete sets TribeID to null. // @@ -134,7 +135,7 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players return err } if err := listParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, }); err != nil { return err @@ -182,3 +183,10 @@ func (svc *PlayerService) delete(ctx context.Context, serverKey string, players func (svc *PlayerService) List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error) { return svc.repo.List(ctx, params) } + +func (svc *PlayerService) ListWithRelations( + ctx context.Context, + params domain.ListPlayersParams, +) (domain.ListPlayersWithRelationsResult, error) { + return svc.repo.ListWithRelations(ctx, params) +} diff --git a/internal/app/service_player_snapshot.go b/internal/app/service_player_snapshot.go index 95c54bb..64114f2 100644 --- a/internal/app/service_player_snapshot.go +++ b/internal/app/service_player_snapshot.go @@ -40,7 +40,7 @@ func (svc *PlayerSnapshotService) Create( return fmt.Errorf("%s: %w", serverKey, err) } if err := listPlayersParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, }); err != nil { return fmt.Errorf("%s: %w", serverKey, err) diff --git a/internal/app/service_server.go b/internal/app/service_server.go index 9ea135e..2ac10a4 100644 --- a/internal/app/service_server.go +++ b/internal/app/service_server.go @@ -76,7 +76,7 @@ func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string return nil, err } if err := params.SetSpecial(domain.NullBool{ - Value: true, + V: true, Valid: true, }); err != nil { return nil, err @@ -91,7 +91,7 @@ func (svc *ServerService) ListAllOpen(ctx context.Context, versionCode string) ( return nil, err } if err := params.SetOpen(domain.NullBool{ - Value: true, + V: true, Valid: true, }); err != nil { return nil, err @@ -158,19 +158,19 @@ func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain. var updateParams domain.UpdateServerParams if err = updateParams.SetConfig(domain.NullServerConfig{ - Value: cfg, + V: cfg, Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err = updateParams.SetBuildingInfo(domain.NullBuildingInfo{ - Value: buildingInfo, + V: buildingInfo, Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err = updateParams.SetUnitInfo(domain.NullUnitInfo{ - Value: unitInfo, + V: unitInfo, Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -184,13 +184,13 @@ func (svc *ServerService) UpdateNumTribes(ctx context.Context, payload domain.Tr var updateParams domain.UpdateServerParams if err := updateParams.SetNumTribes(domain.NullInt{ - Value: payload.NumTribes(), + V: payload.NumTribes(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetTribeDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -204,13 +204,13 @@ func (svc *ServerService) UpdateNumPlayers(ctx context.Context, payload domain.P var updateParams domain.UpdateServerParams if err := updateParams.SetNumPlayers(domain.NullInt{ - Value: payload.NumPlayers(), + V: payload.NumPlayers(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetPlayerDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -224,31 +224,31 @@ func (svc *ServerService) UpdateNumVillages(ctx context.Context, payload domain. var updateParams domain.UpdateServerParams if err := updateParams.SetNumVillages(domain.NullInt{ - Value: payload.NumVillages(), + V: payload.NumVillages(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetNumPlayerVillages(domain.NullInt{ - Value: payload.NumPlayerVillages(), + V: payload.NumPlayerVillages(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetNumBarbarianVillages(domain.NullInt{ - Value: payload.NumBarbarianVillages(), + V: payload.NumBarbarianVillages(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetNumBonusVillages(domain.NullInt{ - Value: payload.NumBonusVillages(), + V: payload.NumBonusVillages(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) } if err := updateParams.SetVillageDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -265,7 +265,7 @@ func (svc *ServerService) UpdateEnnoblementDataSyncedAt( var updateParams domain.UpdateServerParams if err := updateParams.SetEnnoblementDataSyncedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -282,7 +282,7 @@ func (svc *ServerService) UpdateTribeSnapshotsCreatedAt( var updateParams domain.UpdateServerParams if err := updateParams.SetTribeSnapshotsCreatedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -299,7 +299,7 @@ func (svc *ServerService) UpdatePlayerSnapshotsCreatedAt( var updateParams domain.UpdateServerParams if err := updateParams.SetPlayerSnapshotsCreatedAt(domain.NullTime{ - Value: time.Now(), + V: time.Now(), Valid: true, }); err != nil { return fmt.Errorf("%s: %w", key, err) @@ -320,7 +320,7 @@ func (svc *ServerService) GetNormalByVersionCodeAndServerKey( return domain.Server{}, err } if err := params.SetSpecial(domain.NullBool{ - Value: false, + V: false, Valid: true, }); err != nil { return domain.Server{}, err diff --git a/internal/app/service_snapshot.go b/internal/app/service_snapshot.go index f186010..0f7377d 100644 --- a/internal/app/service_snapshot.go +++ b/internal/app/service_snapshot.go @@ -70,13 +70,13 @@ func (svc *SnapshotService) publishTribe( return err } if err := params.SetOpen(domain.NullBool{ - Value: true, + V: true, Valid: true, }); err != nil { return err } if err := params.SetTribeSnapshotsCreatedAtLT(domain.NullTime{ - Value: snapshotsCreatedAtLT, + V: snapshotsCreatedAtLT, Valid: true, }); err != nil { return err @@ -106,13 +106,13 @@ func (svc *SnapshotService) publishPlayer( return err } if err := params.SetOpen(domain.NullBool{ - Value: true, + V: true, Valid: true, }); err != nil { return err } if err := params.SetPlayerSnapshotsCreatedAtLT(domain.NullTime{ - Value: snapshotsCreatedAtLT, + V: snapshotsCreatedAtLT, Valid: true, }); err != nil { return err diff --git a/internal/app/service_tribe.go b/internal/app/service_tribe.go index d9d280a..4c667d3 100644 --- a/internal/app/service_tribe.go +++ b/internal/app/service_tribe.go @@ -117,7 +117,7 @@ func (svc *TribeService) delete(ctx context.Context, serverKey string, tribes do return err } if err := listParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, }); err != nil { return err diff --git a/internal/app/service_tribe_snapshot.go b/internal/app/service_tribe_snapshot.go index c35aafd..2073486 100644 --- a/internal/app/service_tribe_snapshot.go +++ b/internal/app/service_tribe_snapshot.go @@ -40,7 +40,7 @@ func (svc *TribeSnapshotService) Create( return fmt.Errorf("%s: %w", serverKey, err) } if err := listTribesParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, }); err != nil { return fmt.Errorf("%s: %w", serverKey, err) diff --git a/internal/app/service_village.go b/internal/app/service_village.go index af7eb99..05128a0 100644 --- a/internal/app/service_village.go +++ b/internal/app/service_village.go @@ -106,7 +106,7 @@ func (svc *VillageService) delete(ctx context.Context, serverKey string, village toDelete = append(toDelete, storedVillages.Delete(serverKey, villages)...) if err = listParams.SetIDGT(domain.NullInt{ - Value: storedVillages[len(storedVillages)-1].ID(), + V: storedVillages[len(storedVillages)-1].ID(), Valid: true, }); err != nil { return err diff --git a/internal/bun/bunmodel/player.go b/internal/bun/bunmodel/player.go index 606fc75..9321d45 100644 --- a/internal/bun/bunmodel/player.go +++ b/internal/bun/bunmodel/player.go @@ -93,3 +93,29 @@ func (ps Players) ToDomain() (domain.Players, error) { return res, nil } + +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() + if err != nil { + return nil, err + } + + res = append(res, converted.WithRelations(tribe)) + } + + return res, nil +} diff --git a/internal/bun/bunmodel/tribe.go b/internal/bun/bunmodel/tribe.go index a09bf0b..6d4e2bf 100644 --- a/internal/bun/bunmodel/tribe.go +++ b/internal/bun/bunmodel/tribe.go @@ -8,6 +8,8 @@ import ( "github.com/uptrace/bun" ) +var TribeMetaColumns = []string{"id", "name", "tag", "profile_url"} + type Tribe struct { bun.BaseModel `bun:"table:tribes,alias:tribe"` @@ -34,6 +36,24 @@ type Tribe struct { OpponentsDefeated } +func (t Tribe) ToMeta() (domain.TribeMeta, error) { + converted, err := domain.UnmarshalTribeMetaFromDatabase( + t.ID, + t.Name, + t.Tag, + t.ProfileURL, + ) + if err != nil { + return domain.TribeMeta{}, fmt.Errorf( + "couldn't construct domain.TribeMeta (id=%d,serverKey=%s): %w", + t.ID, + t.ServerKey, + err, + ) + } + return converted, nil +} + func (t Tribe) ToDomain() (domain.Tribe, error) { od, err := t.OpponentsDefeated.ToDomain() if err != nil { diff --git a/internal/domain/domaintest/player.go b/internal/domain/domaintest/player.go index 4d7ebd2..0cb32ef 100644 --- a/internal/domain/domaintest/player.go +++ b/internal/domain/domaintest/player.go @@ -117,3 +117,35 @@ func NewPlayer(tb TestingTB, opts ...func(cfg *PlayerConfig)) domain.Player { return p } + +type PlayerWithRelationsConfig struct { + TribeID int +} + +func NewPlayerWithRelations(tb TestingTB, opts ...func(cfg *PlayerWithRelationsConfig)) domain.PlayerWithRelations { + tb.Helper() + + cfg := &PlayerWithRelationsConfig{ + TribeID: RandID(), + } + + for _, opt := range opts { + opt(cfg) + } + + p := NewPlayer(tb, func(playerCfg *PlayerConfig) { + playerCfg.TribeID = cfg.TribeID + }) + + var tribeMeta domain.TribeMeta + if p.TribeID() > 0 { + tribeMeta = NewTribeMeta(tb, func(cfg *TribeMetaConfig) { + cfg.ID = p.TribeID() + }) + } + + return p.WithRelations(domain.NullTribeMeta{ + V: tribeMeta, + Valid: !tribeMeta.IsZero(), + }) +} diff --git a/internal/domain/domaintest/tribe.go b/internal/domain/domaintest/tribe.go index 8ca284e..d9994ff 100644 --- a/internal/domain/domaintest/tribe.go +++ b/internal/domain/domaintest/tribe.go @@ -120,3 +120,26 @@ 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/null.go b/internal/domain/null.go index 0fde982..3dcb2d3 100644 --- a/internal/domain/null.go +++ b/internal/domain/null.go @@ -3,8 +3,8 @@ package domain import "time" type NullValue[T any] struct { - Value T - Valid bool // Valid is true if Value is not NULL + V T + Valid bool // Valid is true if V is not NULL } type NullInt = NullValue[int] diff --git a/internal/domain/player.go b/internal/domain/player.go index 870111d..a942b3c 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -181,6 +181,13 @@ func (p Player) DeletedAt() time.Time { return p.deletedAt } +func (p Player) WithRelations(tribe NullTribeMeta) PlayerWithRelations { + return PlayerWithRelations{ + player: p, + tribe: tribe, + } +} + func (p Player) Base() BasePlayer { return BasePlayer{ id: p.id, @@ -241,6 +248,23 @@ func (ps Players) Delete(serverKey string, active BasePlayers) ([]int, []CreateT return toDelete, params, nil } +type PlayerWithRelations struct { + player Player + tribe NullTribeMeta +} + +func (p PlayerWithRelations) Player() Player { + return p.player +} + +func (p PlayerWithRelations) Tribe() NullTribeMeta { + return p.tribe +} + +func (p PlayerWithRelations) IsZero() bool { + return p.player.IsZero() && p.tribe.IsZero() +} + type CreatePlayerParams struct { base BasePlayer serverKey string @@ -253,6 +277,8 @@ type CreatePlayerParams struct { lastActivityAt time.Time } +type PlayersWithRelations []PlayerWithRelations + const createPlayerParamsModelName = "CreatePlayerParams" // NewCreatePlayerParams constructs a slice of CreatePlayerParams based on the given parameters. @@ -630,7 +656,7 @@ func (params *ListPlayersParams) IDs() []int { func (params *ListPlayersParams) SetIDs(ids []int) error { for i, id := range ids { - if err := validateIntInRange(id, 0, math.MaxInt); err != nil { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { return SliceElementValidationError{ Model: listPlayersParamsModelName, Field: "ids", @@ -821,3 +847,78 @@ func (res ListPlayersResult) Self() PlayerCursor { func (res ListPlayersResult) Next() PlayerCursor { return res.next } + +type ListPlayersWithRelationsResult struct { + players PlayersWithRelations + self PlayerCursor + next PlayerCursor +} + +const listPlayersWithRelationsResultModelName = "ListPlayersWithRelationsResult" + +func NewListPlayersWithRelationsResult( + players PlayersWithRelations, + next PlayerWithRelations, +) (ListPlayersWithRelationsResult, error) { + var err error + res := ListPlayersWithRelationsResult{ + players: players, + } + + if len(players) > 0 { + player := players[0].Player() + od := player.OD() + res.self, err = NewPlayerCursor( + player.ID(), + player.ServerKey(), + od.ScoreAtt(), + od.ScoreDef(), + od.ScoreTotal(), + player.Points(), + player.DeletedAt(), + ) + if err != nil { + return ListPlayersWithRelationsResult{}, ValidationError{ + Model: listPlayersWithRelationsResultModelName, + Field: "self", + Err: err, + } + } + } + + if !next.IsZero() { + fmt.Println(next.IsZero()) + player := next.Player() + od := player.OD() + res.next, err = NewPlayerCursor( + player.ID(), + player.ServerKey(), + od.ScoreAtt(), + od.ScoreDef(), + od.ScoreTotal(), + player.Points(), + player.DeletedAt(), + ) + if err != nil { + return ListPlayersWithRelationsResult{}, ValidationError{ + Model: listPlayersWithRelationsResultModelName, + Field: "next", + Err: err, + } + } + } + + return res, nil +} + +func (res ListPlayersWithRelationsResult) Players() PlayersWithRelations { + return res.players +} + +func (res ListPlayersWithRelationsResult) Self() PlayerCursor { + return res.self +} + +func (res ListPlayersWithRelationsResult) Next() PlayerCursor { + return res.next +} diff --git a/internal/domain/player_test.go b/internal/domain/player_test.go index 83ca62a..bdba988 100644 --- a/internal/domain/player_test.go +++ b/internal/domain/player_test.go @@ -437,13 +437,13 @@ func TestListPlayersParams_SetIDs(t *testing.T) { }, }, { - name: "ERR: value < 0", + name: "ERR: value < 1", args: args{ ids: []int{ domaintest.RandID(), domaintest.RandID(), domaintest.RandID(), - -1, + 0, domaintest.RandID(), }, }, @@ -452,8 +452,8 @@ func TestListPlayersParams_SetIDs(t *testing.T) { Field: "ids", Index: 3, Err: domain.MinGreaterEqualError{ - Min: 0, - Current: -1, + Min: 1, + Current: 0, }, }, }, @@ -910,3 +910,47 @@ func TestNewListPlayersResult(t *testing.T) { assert.True(t, res.Next().IsZero()) }) } + +func TestNewListPlayersWithRelationsResult(t *testing.T) { + t.Parallel() + + players := domain.PlayersWithRelations{ + domaintest.NewPlayerWithRelations(t), + domaintest.NewPlayerWithRelations(t), + domaintest.NewPlayerWithRelations(t), + } + next := domaintest.NewPlayerWithRelations(t) + + t.Run("OK: with next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayersWithRelationsResult(players, next) + require.NoError(t, err) + assert.Equal(t, players, res.Players()) + assert.Equal(t, players[0].Player().ID(), res.Self().ID()) + assert.Equal(t, players[0].Player().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, next.Player().ID(), res.Next().ID()) + assert.Equal(t, next.Player().ServerKey(), res.Next().ServerKey()) + }) + + t.Run("OK: without next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayersWithRelationsResult(players, domain.PlayerWithRelations{}) + require.NoError(t, err) + assert.Equal(t, players, res.Players()) + assert.Equal(t, players[0].Player().ID(), res.Self().ID()) + assert.Equal(t, players[0].Player().ServerKey(), res.Self().ServerKey()) + assert.True(t, res.Next().IsZero()) + }) + + t.Run("OK: 0 players", 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/server.go b/internal/domain/server.go index 0c9e787..dea6be2 100644 --- a/internal/domain/server.go +++ b/internal/domain/server.go @@ -330,7 +330,7 @@ func (params *UpdateServerParams) NumTribes() NullInt { func (params *UpdateServerParams) SetNumTribes(numTribes NullInt) error { if numTribes.Valid { - if err := validateIntInRange(numTribes.Value, 0, math.MaxInt); err != nil { + if err := validateIntInRange(numTribes.V, 0, math.MaxInt); err != nil { return ValidationError{ Model: updateServerParamsModelName, Field: "numTribes", @@ -556,7 +556,7 @@ func NewListServersParams() ListServersParams { sort: []ServerSort{ServerSortKeyASC}, limit: ServerListMaxLimit, special: NullBool{ - Value: false, + V: false, Valid: true, }, } diff --git a/internal/domain/server_test.go b/internal/domain/server_test.go index 980a647..ed624e5 100644 --- a/internal/domain/server_test.go +++ b/internal/domain/server_test.go @@ -136,7 +136,7 @@ func TestUpdateServerParams_SetNumTribes(t *testing.T) { name: "OK", args: args{ numTribes: domain.NullInt{ - Value: gofakeit.IntRange(0, math.MaxInt), + V: gofakeit.IntRange(0, math.MaxInt), Valid: true, }, }, @@ -153,7 +153,7 @@ func TestUpdateServerParams_SetNumTribes(t *testing.T) { name: "ERR: numTribes < 0", args: args{ numTribes: domain.NullInt{ - Value: -1, + V: -1, Valid: true, }, }, diff --git a/internal/domain/tribe.go b/internal/domain/tribe.go index 708d95e..5ee448d 100644 --- a/internal/domain/tribe.go +++ b/internal/domain/tribe.go @@ -216,6 +216,15 @@ func (t Tribe) Base() BaseTribe { } } +func (t Tribe) Meta() TribeMeta { + return TribeMeta{ + id: t.id, + name: t.name, + tag: t.tag, + profileURL: t.profileURL, + } +} + func (t Tribe) IsZero() bool { return t == Tribe{} } @@ -247,6 +256,66 @@ func (ts Tribes) Delete(serverKey string, active BaseTribes) []int { return toDelete } +type TribeMeta struct { + id int + name string + tag string + profileURL *url.URL +} + +const tribeMetaModelName = "TribeMeta" + +// UnmarshalTribeMetaFromDatabase unmarshals TribeMeta from the database. +// +// It should be used only for unmarshalling from the database! +// You can't use UnmarshalTribeMetaFromDatabase as constructor - It may put domain into the invalid state! +func UnmarshalTribeMetaFromDatabase(id int, name string, tag string, rawProfileURL string) (TribeMeta, error) { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { + return TribeMeta{}, ValidationError{ + Model: tribeMetaModelName, + Field: "id", + Err: err, + } + } + + profileURL, err := parseURL(rawProfileURL) + if err != nil { + return TribeMeta{}, ValidationError{ + Model: tribeMetaModelName, + Field: "profileURL", + Err: err, + } + } + + return TribeMeta{id: id, name: name, tag: tag, profileURL: profileURL}, nil +} + +func (t TribeMeta) ID() int { + return t.id +} + +func (t TribeMeta) Name() string { + return t.name +} + +func (t TribeMeta) Tag() string { + return t.tag +} + +func (t TribeMeta) ProfileURL() *url.URL { + return t.profileURL +} + +func (t TribeMeta) IsZero() bool { + return t == TribeMeta{} +} + +type NullTribeMeta NullValue[TribeMeta] + +func (t NullTribeMeta) IsZero() bool { + return !t.Valid +} + type CreateTribeParams struct { base BaseTribe serverKey string @@ -646,7 +715,7 @@ func (params *ListTribesParams) IDs() []int { func (params *ListTribesParams) SetIDs(ids []int) error { for i, id := range ids { - if err := validateIntInRange(id, 0, math.MaxInt); err != nil { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { return SliceElementValidationError{ Model: listTribesParamsModelName, Field: "ids", diff --git a/internal/domain/tribe_test.go b/internal/domain/tribe_test.go index 5c8821d..2fc853d 100644 --- a/internal/domain/tribe_test.go +++ b/internal/domain/tribe_test.go @@ -399,13 +399,13 @@ func TestListTribesParams_SetIDs(t *testing.T) { }, }, { - name: "ERR: value < 0", + name: "ERR: value < 1", args: args{ ids: []int{ domaintest.RandID(), domaintest.RandID(), domaintest.RandID(), - -1, + 0, domaintest.RandID(), }, }, @@ -414,8 +414,8 @@ func TestListTribesParams_SetIDs(t *testing.T) { Field: "ids", Index: 3, Err: domain.MinGreaterEqualError{ - Min: 0, - Current: -1, + Min: 1, + Current: 0, }, }, }, diff --git a/internal/domain/village.go b/internal/domain/village.go index 0f304ea..4a66200 100644 --- a/internal/domain/village.go +++ b/internal/domain/village.go @@ -254,7 +254,7 @@ func (params *ListVillagesParams) IDs() []int { func (params *ListVillagesParams) SetIDs(ids []int) error { for i, id := range ids { - if err := validateIntInRange(id, 0, math.MaxInt); err != nil { + if err := validateIntInRange(id, 1, math.MaxInt); err != nil { return SliceElementValidationError{ Model: listVillagesParamsModelName, Field: "ids", @@ -275,7 +275,7 @@ func (params *ListVillagesParams) IDGT() NullInt { func (params *ListVillagesParams) SetIDGT(idGT NullInt) error { if idGT.Valid { - if err := validateIntInRange(idGT.Value, 0, math.MaxInt); err != nil { + if err := validateIntInRange(idGT.V, 0, math.MaxInt); err != nil { return ValidationError{ Model: listVillagesParamsModelName, Field: "idGT", diff --git a/internal/domain/village_test.go b/internal/domain/village_test.go index 1b91563..4b8ea5f 100644 --- a/internal/domain/village_test.go +++ b/internal/domain/village_test.go @@ -111,13 +111,13 @@ func TestListVillagesParams_SetIDs(t *testing.T) { }, }, { - name: "ERR: value < 0", + name: "ERR: value < 1", args: args{ ids: []int{ domaintest.RandID(), domaintest.RandID(), domaintest.RandID(), - -1, + 0, domaintest.RandID(), }, }, @@ -126,8 +126,8 @@ func TestListVillagesParams_SetIDs(t *testing.T) { Field: "ids", Index: 3, Err: domain.MinGreaterEqualError{ - Min: 0, - Current: -1, + Min: 1, + Current: 0, }, }, }, @@ -164,7 +164,7 @@ func TestListVillagesParams_SetIDGT(t *testing.T) { name: "OK", args: args{ idGT: domain.NullInt{ - Value: domaintest.RandID(), + V: domaintest.RandID(), Valid: true, }, }, @@ -173,7 +173,7 @@ func TestListVillagesParams_SetIDGT(t *testing.T) { name: "ERR: value < 0", args: args{ idGT: domain.NullInt{ - Value: -1, + V: -1, Valid: true, }, }, diff --git a/internal/port/consumer_data_sync_test.go b/internal/port/consumer_data_sync_test.go index 60bcfde..607babc 100644 --- a/internal/port/consumer_data_sync_test.go +++ b/internal/port/consumer_data_sync_test.go @@ -207,7 +207,7 @@ func TestDataSync(t *testing.T) { domain.ServerSortKeyASC, })) require.NoError(collect, listParams.SetSpecial(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(collect, listParams.SetLimit(domain.ServerListMaxLimit)) @@ -431,7 +431,7 @@ func TestDataSync(t *testing.T) { allVillages = append(allVillages, villages...) require.NoError(collect, listParams.SetIDGT(domain.NullInt{ - Value: villages[len(villages)-1].ID(), + V: villages[len(villages)-1].ID(), Valid: true, })) } diff --git a/internal/port/consumer_ennoblement_sync_test.go b/internal/port/consumer_ennoblement_sync_test.go index e9e903b..935d0cd 100644 --- a/internal/port/consumer_ennoblement_sync_test.go +++ b/internal/port/consumer_ennoblement_sync_test.go @@ -167,7 +167,7 @@ func TestEnnoblementSync(t *testing.T) { domain.ServerSortKeyASC, })) require.NoError(collect, listParams.SetSpecial(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(collect, listParams.SetLimit(domain.ServerListMaxLimit)) diff --git a/internal/port/consumer_snapshot_creation_test.go b/internal/port/consumer_snapshot_creation_test.go index 61b6c98..20df161 100644 --- a/internal/port/consumer_snapshot_creation_test.go +++ b/internal/port/consumer_snapshot_creation_test.go @@ -151,7 +151,7 @@ func TestSnapshotCreation(t *testing.T) { domain.ServerSortKeyASC, })) require.NoError(collect, listParams.SetSpecial(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(collect, listParams.SetLimit(domain.ServerListMaxLimit)) @@ -182,7 +182,7 @@ func TestSnapshotCreation(t *testing.T) { domain.TribeSortIDASC, })) require.NoError(collect, listTribesParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(collect, listTribesParams.SetLimit(domain.TribeListMaxLimit)) @@ -268,7 +268,7 @@ func TestSnapshotCreation(t *testing.T) { domain.PlayerSortIDASC, })) require.NoError(collect, listPlayersParams.SetDeleted(domain.NullBool{ - Value: false, + V: false, Valid: true, })) require.NoError(collect, listPlayersParams.SetLimit(domain.PlayerListMaxLimit)) diff --git a/internal/port/handler_http_api_player.go b/internal/port/handler_http_api_player.go index fa59579..b33f494 100644 --- a/internal/port/handler_http_api_player.go +++ b/internal/port/handler_http_api_player.go @@ -37,7 +37,7 @@ func (h *apiHTTPHandler) ListPlayers( if params.Deleted != nil { if err := domainParams.SetDeleted(domain.NullBool{ - Value: *params.Deleted, + V: *params.Deleted, Valid: true, }); err != nil { h.errorRenderer.withErrorPathFormatter(formatListPlayersErrorPath).render(w, r, err) @@ -59,7 +59,7 @@ func (h *apiHTTPHandler) ListPlayers( } } - res, err := h.playerSvc.List(r.Context(), domainParams) + res, err := h.playerSvc.ListWithRelations(r.Context(), domainParams) if err != nil { h.errorRenderer.render(w, r, err) return diff --git a/internal/port/handler_http_api_server.go b/internal/port/handler_http_api_server.go index 6fb64bf..a86ff5f 100644 --- a/internal/port/handler_http_api_server.go +++ b/internal/port/handler_http_api_server.go @@ -30,7 +30,7 @@ func (h *apiHTTPHandler) ListServers( if params.Open != nil { if err := domainParams.SetOpen(domain.NullBool{ - Value: *params.Open, + V: *params.Open, Valid: true, }); err != nil { h.errorRenderer.withErrorPathFormatter(formatListServersErrorPath).render(w, r, err) diff --git a/internal/port/handler_http_api_tribe.go b/internal/port/handler_http_api_tribe.go index e3ba513..a14d5b6 100644 --- a/internal/port/handler_http_api_tribe.go +++ b/internal/port/handler_http_api_tribe.go @@ -47,7 +47,7 @@ func (h *apiHTTPHandler) ListTribes( if params.Deleted != nil { if err := domainParams.SetDeleted(domain.NullBool{ - Value: *params.Deleted, + V: *params.Deleted, Valid: true, }); err != nil { h.errorRenderer.withErrorPathFormatter(formatListTribesErrorPath).render(w, r, err) diff --git a/internal/port/handler_http_api_tribe_test.go b/internal/port/handler_http_api_tribe_test.go index 7289c90..d701cac 100644 --- a/internal/port/handler_http_api_tribe_test.go +++ b/internal/port/handler_http_api_tribe_test.go @@ -704,7 +704,7 @@ func TestGetTribe(t *testing.T) { }, }, { - name: "ERR: id < 0", + name: "ERR: id < 1", reqModifier: func(t *testing.T, req *http.Request) { t.Helper() req.URL.Path = fmt.Sprintf( @@ -726,7 +726,7 @@ func TestGetTribe(t *testing.T) { id, err := strconv.Atoi(pathSegments[7]) require.NoError(t, err) domainErr := domain.MinGreaterEqualError{ - Min: 0, + Min: 1, Current: id, } assert.Equal(t, apimodel.ErrorResponse{ diff --git a/internal/port/internal/apimodel/player.go b/internal/port/internal/apimodel/player.go index df70c71..007f3fd 100644 --- a/internal/port/internal/apimodel/player.go +++ b/internal/port/internal/apimodel/player.go @@ -17,7 +17,9 @@ func NewPlayerOpponentsDefeated(od domain.OpponentsDefeated) PlayerOpponentsDefe } } -func NewPlayer(p domain.Player) Player { +func NewPlayer(withRelations domain.PlayerWithRelations) Player { + p := withRelations.Player() + converted := Player{ BestRank: p.BestRank(), BestRankAt: p.BestRankAt(), @@ -34,6 +36,7 @@ func NewPlayer(p domain.Player) Player { Points: p.Points(), ProfileUrl: p.ProfileURL().String(), Rank: p.Rank(), + Tribe: NewNullTribeMeta(withRelations.Tribe()), } if deletedAt := p.DeletedAt(); !deletedAt.IsZero() { @@ -43,7 +46,7 @@ func NewPlayer(p domain.Player) Player { return converted } -func NewListPlayersResponse(res domain.ListPlayersResult) ListPlayersResponse { +func NewListPlayersResponse(res domain.ListPlayersWithRelationsResult) ListPlayersResponse { players := res.Players() resp := ListPlayersResponse{ diff --git a/internal/port/internal/apimodel/tribe.go b/internal/port/internal/apimodel/tribe.go index dfe8f93..aa7d05d 100644 --- a/internal/port/internal/apimodel/tribe.go +++ b/internal/port/internal/apimodel/tribe.go @@ -44,6 +44,25 @@ func NewTribe(t domain.Tribe) Tribe { return converted } +func NewTribeMeta(t domain.TribeMeta) TribeMeta { + return TribeMeta{ + Id: t.ID(), + Name: t.Name(), + ProfileUrl: t.ProfileURL().String(), + Tag: t.Tag(), + } +} + +func NewNullTribeMeta(t domain.NullTribeMeta) *TribeMeta { + if !t.Valid { + return nil + } + + converted := NewTribeMeta(t.V) + + return &converted +} + func NewListTribesResponse(res domain.ListTribesResult) ListTribesResponse { tribes := res.Tribes()