feat: add a new domain model - PlayerSnapshotWithRelations
ci/woodpecker/push/govulncheck Pipeline was successful Details
ci/woodpecker/push/test Pipeline was successful Details

This commit is contained in:
Dawid Wysokiński 2024-03-20 06:49:24 +01:00
parent 267953ea61
commit 387fe30b82
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
13 changed files with 273 additions and 16 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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",

View File

@ -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)
}
})
}
})

View File

@ -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 {

View File

@ -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()

View File

@ -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()

View File

@ -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)
}

View File

@ -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) {

View File

@ -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(),
})
}

View File

@ -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
}

View File

@ -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())
})
}

View File

@ -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 {