diff --git a/cmd/errorcodes/main.go b/cmd/errorcodes/main.go index 4ab5d52..8160339 100644 --- a/cmd/errorcodes/main.go +++ b/cmd/errorcodes/main.go @@ -114,7 +114,7 @@ func getErrorCodesFromFolder(root string) (map[string]string, error) { break } - if strings.Contains(desc, "ignorecode") { + if strings.Contains(desc, "errorcode:ignore") { continue } diff --git a/internal/adapter/repository_bun_player_snapshot.go b/internal/adapter/repository_bun_player_snapshot.go index c0371ec..66f526b 100644 --- a/internal/adapter/repository_bun_player_snapshot.go +++ b/internal/adapter/repository_bun_player_snapshot.go @@ -77,6 +77,36 @@ func (repo *PlayerSnapshotBunRepository) List( return domain.NewListPlayerSnapshotsResult(separateListResultAndNext(converted, params.Limit())) } +func (repo *PlayerSnapshotBunRepository) ListWithRelations( + ctx context.Context, + params domain.ListPlayerSnapshotsParams, +) (domain.ListPlayerSnapshotsWithRelationsResult, error) { + var playerSnapshots bunmodel.PlayerSnapshots + + if err := repo.db.NewSelect(). + Model(&playerSnapshots). + Apply(listPlayerSnapshotsParamsApplier{params: params}.apply). + Relation("Player", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.PlayerMetaColumns...) + }). + Relation("Tribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). + Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { + return domain.ListPlayerSnapshotsWithRelationsResult{}, fmt.Errorf( + "couldn't select player snapshots from the db: %w", + err, + ) + } + + converted, err := playerSnapshots.ToDomainWithRelations() + if err != nil { + return domain.ListPlayerSnapshotsWithRelationsResult{}, err + } + + return domain.NewListPlayerSnapshotsWithRelationsResult(separateListResultAndNext(converted, params.Limit())) +} + type listPlayerSnapshotsParamsApplier struct { params domain.ListPlayerSnapshotsParams } diff --git a/internal/adapter/repository_bun_tribe_snapshot.go b/internal/adapter/repository_bun_tribe_snapshot.go index e7dce5f..94b0251 100644 --- a/internal/adapter/repository_bun_tribe_snapshot.go +++ b/internal/adapter/repository_bun_tribe_snapshot.go @@ -85,7 +85,9 @@ func (repo *TribeSnapshotBunRepository) ListWithRelations( if err := repo.db.NewSelect(). Model(&tribeSnapshots). Apply(listTribeSnapshotsParamsApplier{params: params}.apply). - Relation("Tribe"). + Relation("Tribe", func(q *bun.SelectQuery) *bun.SelectQuery { + return q.Column(bunmodel.TribeMetaColumns...) + }). Scan(ctx); err != nil && !errors.Is(err, sql.ErrNoRows) { return domain.ListTribeSnapshotsWithRelationsResult{}, fmt.Errorf( "couldn't select tribe snapshots from the db: %w", diff --git a/internal/adapter/repository_player_snapshot_test.go b/internal/adapter/repository_player_snapshot_test.go index d99e893..ad1d495 100644 --- a/internal/adapter/repository_player_snapshot_test.go +++ b/internal/adapter/repository_player_snapshot_test.go @@ -116,7 +116,7 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo }) }) - t.Run("List", func(t *testing.T) { + t.Run("List & ListWithRelations", func(t *testing.T) { t.Parallel() repos := newRepos(t) @@ -443,6 +443,16 @@ func testPlayerSnapshotRepository(t *testing.T, newRepos func(t *testing.T) repo res, err := repos.playerSnapshot.List(ctx, params) assertError(t, err) tt.assertResult(t, params, res) + + resWithRelations, err := repos.playerSnapshot.ListWithRelations(ctx, params) + assertError(t, err) + require.Len(t, resWithRelations.PlayerSnapshots(), len(res.PlayerSnapshots())) + for i, ps := range resWithRelations.PlayerSnapshots() { + assert.Equal(t, res.PlayerSnapshots()[i], ps.PlayerSnapshot()) + assert.Equal(t, ps.PlayerSnapshot().PlayerID(), ps.Player().ID()) + assert.Equal(t, ps.PlayerSnapshot().TribeID(), ps.Tribe().V.ID()) + assert.Equal(t, ps.PlayerSnapshot().TribeID() != 0, ps.Tribe().Valid) + } }) } }) diff --git a/internal/adapter/repository_test.go b/internal/adapter/repository_test.go index fafa75c..aacde29 100644 --- a/internal/adapter/repository_test.go +++ b/internal/adapter/repository_test.go @@ -75,6 +75,10 @@ type tribeSnapshotRepository interface { type playerSnapshotRepository interface { Create(ctx context.Context, params ...domain.CreatePlayerSnapshotParams) error List(ctx context.Context, params domain.ListPlayerSnapshotsParams) (domain.ListPlayerSnapshotsResult, error) + ListWithRelations( + ctx context.Context, + params domain.ListPlayerSnapshotsParams, + ) (domain.ListPlayerSnapshotsWithRelationsResult, error) } type repositories struct { diff --git a/internal/bun/bunmodel/ennoblement.go b/internal/bun/bunmodel/ennoblement.go index d1ab812..9df9ec2 100644 --- a/internal/bun/bunmodel/ennoblement.go +++ b/internal/bun/bunmodel/ennoblement.go @@ -58,7 +58,7 @@ func (e Ennoblement) ToDomainWithRelations() (domain.EnnoblementWithRelations, e return domain.EnnoblementWithRelations{}, err } - // in some cases there is no corresponding village in the db (for example, for old ennoblements) + // in some cases there is no corresponding village in the db (old ennoblements) var village domain.VillageMeta if e.Village.ID > 0 { village, err = e.Village.ToMeta() @@ -67,7 +67,7 @@ func (e Ennoblement) ToDomainWithRelations() (domain.EnnoblementWithRelations, e } } - // in some cases there is no corresponding player in the db (for example, for old ennoblements) + // in some cases there is no corresponding player in the db (old ennoblements) var newOwner domain.PlayerMeta if e.NewOwner.ID > 0 { newOwner, err = e.NewOwner.ToMeta() diff --git a/internal/bun/bunmodel/player.go b/internal/bun/bunmodel/player.go index c46e2d5..4a6bb5a 100644 --- a/internal/bun/bunmodel/player.go +++ b/internal/bun/bunmodel/player.go @@ -86,7 +86,6 @@ func (p Player) ToDomainWithRelations() (domain.PlayerWithRelations, error) { } var tribe domain.NullTribeMeta - if p.Tribe.ID > 0 { tribe.Valid = true tribe.V, err = p.Tribe.ToMeta() diff --git a/internal/bun/bunmodel/player_snapshot.go b/internal/bun/bunmodel/player_snapshot.go index f0c8fd8..6696649 100644 --- a/internal/bun/bunmodel/player_snapshot.go +++ b/internal/bun/bunmodel/player_snapshot.go @@ -13,12 +13,13 @@ type PlayerSnapshot struct { ID int `bun:"id,pk,autoincrement,identity"` PlayerID int `bun:"player_id,nullzero"` + TribeID int `bun:"tribe_id,nullzero"` + ServerKey string `bun:"server_key,nullzero"` + Tribe Tribe `bun:"tribe,rel:belongs-to,join:tribe_id=id,join:server_key=server_key"` + Player Player `bun:"player,rel:belongs-to,join:player_id=id,join:server_key=server_key"` NumVillages int `bun:"num_villages"` Points int `bun:"points"` Rank int `bun:"rank"` - TribeID int `bun:"tribe_id,nullzero"` - Tribe Tribe `bun:"tribe,rel:belongs-to,join:tribe_id=id,join:server_key=server_key"` - ServerKey string `bun:"server_key,nullzero"` Date time.Time `bun:"date,nullzero"` CreatedAt time.Time `bun:"created_at,nullzero"` @@ -58,8 +59,35 @@ func (ps PlayerSnapshot) ToDomain() (domain.PlayerSnapshot, error) { return converted, nil } +func (ps PlayerSnapshot) ToDomainWithRelations() (domain.PlayerSnapshotWithRelations, error) { + converted, err := ps.ToDomain() + if err != nil { + return domain.PlayerSnapshotWithRelations{}, err + } + + player, err := ps.Player.ToMeta() + if err != nil { + return domain.PlayerSnapshotWithRelations{}, err + } + + var tribe domain.NullTribeMeta + if ps.Tribe.ID > 0 { + tribe.Valid = true + tribe.V, err = ps.Tribe.ToMeta() + if err != nil { + return domain.PlayerSnapshotWithRelations{}, err + } + } + + return converted.WithRelations(player, tribe), nil +} + type PlayerSnapshots []PlayerSnapshot func (pss PlayerSnapshots) ToDomain() (domain.PlayerSnapshots, error) { return sliceToDomain(pss) } + +func (pss PlayerSnapshots) ToDomainWithRelations() (domain.PlayerSnapshotsWithRelations, error) { + return sliceToDomainWithRelations(pss) +} diff --git a/internal/bun/bunmodel/village.go b/internal/bun/bunmodel/village.go index a00bde2..67f3cb3 100644 --- a/internal/bun/bunmodel/village.go +++ b/internal/bun/bunmodel/village.go @@ -59,17 +59,16 @@ func (v Village) ToDomainWithRelations() (domain.VillageWithRelations, error) { return domain.VillageWithRelations{}, err } - var tribe domain.NullPlayerMetaWithRelations - + var player domain.NullPlayerMetaWithRelations if v.Player.ID > 0 { - tribe.Valid = true - tribe.V, err = v.Player.ToMetaWithRelations() + player.Valid = true + player.V, err = v.Player.ToMetaWithRelations() if err != nil { return domain.VillageWithRelations{}, err } } - return converted.WithRelations(tribe), nil + return converted.WithRelations(player), nil } func (v Village) ToMeta() (domain.VillageMeta, error) { diff --git a/internal/domain/domaintest/player_snapshot.go b/internal/domain/domaintest/player_snapshot.go index f261930..ba3fcaf 100644 --- a/internal/domain/domaintest/player_snapshot.go +++ b/internal/domain/domaintest/player_snapshot.go @@ -88,3 +88,55 @@ func NewPlayerSnapshot(tb TestingTB, opts ...func(cfg *PlayerSnapshotConfig)) do return ps } + +type PlayerSnapshotWithRelationsConfig struct { + PlayerSnapshotOptions []func(cfg *PlayerSnapshotConfig) + PlayerOptions []func(cfg *PlayerConfig) + TribeOptions []func(cfg *TribeConfig) +} + +func NewPlayerSnapshotWithRelations( + tb TestingTB, + opts ...func(cfg *PlayerSnapshotWithRelationsConfig), +) domain.PlayerSnapshotWithRelations { + tb.Helper() + + cfg := &PlayerSnapshotWithRelationsConfig{} + + for _, opt := range opts { + opt(cfg) + } + + ps := NewPlayerSnapshot(tb, cfg.PlayerSnapshotOptions...) + + if ps.PlayerID() > 0 { + cfg.PlayerOptions = append([]func(cfg *PlayerConfig){ + func(cfg *PlayerConfig) { + cfg.ID = ps.ID() + }, + }, cfg.PlayerOptions...) + } + + if ps.TribeID() > 0 { + cfg.TribeOptions = append([]func(cfg *TribeConfig){ + func(cfg *TribeConfig) { + cfg.ID = ps.ID() + }, + }, cfg.TribeOptions...) + } + + var player domain.PlayerMeta + if len(cfg.PlayerOptions) > 0 { + player = NewPlayer(tb, cfg.PlayerOptions...).Meta() + } + + var tribe domain.TribeMeta + if len(cfg.TribeOptions) > 0 { + tribe = NewTribe(tb, cfg.TribeOptions...).Meta() + } + + return ps.WithRelations(player, domain.NullTribeMeta{ + V: tribe, + Valid: tribe.IsZero(), + }) +} diff --git a/internal/domain/player_snapshot.go b/internal/domain/player_snapshot.go index a47932b..a25e1f2 100644 --- a/internal/domain/player_snapshot.go +++ b/internal/domain/player_snapshot.go @@ -115,6 +115,14 @@ func (ps PlayerSnapshot) CreatedAt() time.Time { return ps.createdAt } +func (ps PlayerSnapshot) WithRelations(player PlayerMeta, tribe NullTribeMeta) PlayerSnapshotWithRelations { + return PlayerSnapshotWithRelations{ + snapshot: ps, + player: player, + tribe: tribe, + } +} + func (ps PlayerSnapshot) ToCursor() (PlayerSnapshotCursor, error) { return NewPlayerSnapshotCursor(ps.id, ps.serverKey, ps.date) } @@ -125,6 +133,30 @@ func (ps PlayerSnapshot) IsZero() bool { type PlayerSnapshots []PlayerSnapshot +type PlayerSnapshotWithRelations struct { + snapshot PlayerSnapshot + player PlayerMeta + tribe NullTribeMeta +} + +func (ps PlayerSnapshotWithRelations) PlayerSnapshot() PlayerSnapshot { + return ps.snapshot +} + +func (ps PlayerSnapshotWithRelations) Player() PlayerMeta { + return ps.player +} + +func (ps PlayerSnapshotWithRelations) Tribe() NullTribeMeta { + return ps.tribe +} + +func (ps PlayerSnapshotWithRelations) IsZero() bool { + return ps.snapshot.IsZero() +} + +type PlayerSnapshotsWithRelations []PlayerSnapshotWithRelations + type CreatePlayerSnapshotParams struct { playerID int serverKey string @@ -545,3 +577,57 @@ func (res ListPlayerSnapshotsResult) Self() PlayerSnapshotCursor { func (res ListPlayerSnapshotsResult) Next() PlayerSnapshotCursor { return res.next } + +type ListPlayerSnapshotsWithRelationsResult struct { + snapshots PlayerSnapshotsWithRelations + self PlayerSnapshotCursor + next PlayerSnapshotCursor +} + +const listPlayerSnapshotsWithRelationsResultModelName = "ListPlayerSnapshotsWithRelationsResult" + +func NewListPlayerSnapshotsWithRelationsResult( + snapshots PlayerSnapshotsWithRelations, + next PlayerSnapshotWithRelations, +) (ListPlayerSnapshotsWithRelationsResult, error) { + var err error + res := ListPlayerSnapshotsWithRelationsResult{ + snapshots: snapshots, + } + + if len(snapshots) > 0 { + res.self, err = snapshots[0].PlayerSnapshot().ToCursor() + if err != nil { + return ListPlayerSnapshotsWithRelationsResult{}, ValidationError{ + Model: listPlayerSnapshotsWithRelationsResultModelName, + Field: "self", + Err: err, + } + } + } + + if !next.IsZero() { + res.next, err = next.PlayerSnapshot().ToCursor() + if err != nil { + return ListPlayerSnapshotsWithRelationsResult{}, ValidationError{ + Model: listPlayerSnapshotsWithRelationsResultModelName, + Field: "next", + Err: err, + } + } + } + + return res, nil +} + +func (res ListPlayerSnapshotsWithRelationsResult) PlayerSnapshots() PlayerSnapshotsWithRelations { + return res.snapshots +} + +func (res ListPlayerSnapshotsWithRelationsResult) Self() PlayerSnapshotCursor { + return res.self +} + +func (res ListPlayerSnapshotsWithRelationsResult) Next() PlayerSnapshotCursor { + return res.next +} diff --git a/internal/domain/player_snapshot_test.go b/internal/domain/player_snapshot_test.go index dd93552..c46c2e7 100644 --- a/internal/domain/player_snapshot_test.go +++ b/internal/domain/player_snapshot_test.go @@ -720,3 +720,50 @@ func TestNewListPlayerSnapshotsResult(t *testing.T) { assert.True(t, res.Next().IsZero()) }) } + +func TestNewListPlayerSnapshotsWithRelationsResult(t *testing.T) { + t.Parallel() + + snapshots := domain.PlayerSnapshotsWithRelations{ + domaintest.NewPlayerSnapshotWithRelations(t), + domaintest.NewPlayerSnapshotWithRelations(t), + domaintest.NewPlayerSnapshotWithRelations(t), + } + next := domaintest.NewPlayerSnapshotWithRelations(t) + + t.Run("OK: with next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayerSnapshotsWithRelationsResult(snapshots, next) + require.NoError(t, err) + assert.Equal(t, snapshots, res.PlayerSnapshots()) + assert.Equal(t, snapshots[0].PlayerSnapshot().ID(), res.Self().ID()) + assert.Equal(t, snapshots[0].PlayerSnapshot().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, snapshots[0].PlayerSnapshot().Date(), res.Self().Date()) + assert.Equal(t, next.PlayerSnapshot().ID(), res.Next().ID()) + assert.Equal(t, next.PlayerSnapshot().ServerKey(), res.Next().ServerKey()) + assert.Equal(t, next.PlayerSnapshot().Date(), res.Next().Date()) + }) + + t.Run("OK: without next", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayerSnapshotsWithRelationsResult(snapshots, domain.PlayerSnapshotWithRelations{}) + require.NoError(t, err) + assert.Equal(t, snapshots, res.PlayerSnapshots()) + assert.Equal(t, snapshots[0].PlayerSnapshot().ID(), res.Self().ID()) + assert.Equal(t, snapshots[0].PlayerSnapshot().ServerKey(), res.Self().ServerKey()) + assert.Equal(t, snapshots[0].PlayerSnapshot().Date(), res.Self().Date()) + assert.True(t, res.Next().IsZero()) + }) + + t.Run("OK: 0 snapshots", func(t *testing.T) { + t.Parallel() + + res, err := domain.NewListPlayerSnapshotsWithRelationsResult(nil, domain.PlayerSnapshotWithRelations{}) + require.NoError(t, err) + assert.Zero(t, res.PlayerSnapshots()) + assert.True(t, res.Self().IsZero()) + assert.True(t, res.Next().IsZero()) + }) +} diff --git a/internal/domain/validation.go b/internal/domain/validation.go index eae7799..63780cc 100644 --- a/internal/domain/validation.go +++ b/internal/domain/validation.go @@ -40,7 +40,7 @@ func (e ValidationError) Type() ErrorType { return ErrorTypeIncorrectInput } -// ignorecode +// errorcode:ignore const errorCodeValidationFailed = "validation-failed" func (e ValidationError) Code() string { @@ -99,7 +99,7 @@ func (e SliceElementValidationError) Type() ErrorType { return ErrorTypeIncorrectInput } -// ignorecode +// errorcode:ignore const errorCodeSliceElementValidationFailed = "slice-element-validation-failed" func (e SliceElementValidationError) Code() string {