feat: add DeletedAt field to Player and Tribe (#33)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: twhelp/core#33
This commit is contained in:
parent
f246e205b4
commit
631c080376
|
@ -7,7 +7,6 @@ linters:
|
|||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocritic
|
||||
- gofmt
|
||||
|
@ -32,6 +31,10 @@ linters:
|
|||
- gomnd
|
||||
- tenv
|
||||
- testpackage
|
||||
- noctx
|
||||
- tparallel
|
||||
- usestdlibvars
|
||||
- unconvert
|
||||
|
||||
linters-settings:
|
||||
tagliatelle:
|
||||
|
|
|
@ -6,6 +6,6 @@ repos:
|
|||
stages: [commit-msg]
|
||||
additional_dependencies: ['@commitlint/config-conventional']
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.47.2
|
||||
rev: v1.48.0
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
|
|
|
@ -2,6 +2,7 @@ package serve
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -131,7 +132,7 @@ func newCORSMiddlewareForDevMode() func(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
func runServer(srv *http.Server, logger *zap.Logger) {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
logger.Fatal("srv.ListenAndServe:" + err.Error())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ type Player struct {
|
|||
Rank int64 `bun:"rank,default:0"`
|
||||
TribeID int64 `bun:"tribe_id,nullzero"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
|
||||
DeletedAt time.Time `bun:"deleted_at,nullzero"`
|
||||
|
||||
OpponentsDefeated
|
||||
}
|
||||
|
@ -49,5 +50,6 @@ func (p Player) ToDomain() domain.Player {
|
|||
},
|
||||
ServerKey: p.ServerKey,
|
||||
CreatedAt: p.CreatedAt,
|
||||
DeletedAt: p.DeletedAt,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ type Tribe struct {
|
|||
Rank int64 `bun:"rank,default:0"`
|
||||
Dominance float64 `bun:"dominance,default:0"`
|
||||
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"`
|
||||
DeletedAt time.Time `bun:"deleted_at,nullzero"`
|
||||
|
||||
OpponentsDefeated
|
||||
}
|
||||
|
@ -58,5 +59,6 @@ func (t Tribe) ToDomain() domain.Tribe {
|
|||
ServerKey: t.ServerKey,
|
||||
Dominance: t.Dominance,
|
||||
CreatedAt: t.CreatedAt,
|
||||
DeletedAt: t.DeletedAt,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
|
||||
models := []interface{}{
|
||||
&model.Player{},
|
||||
&model.Tribe{},
|
||||
}
|
||||
for _, m := range models {
|
||||
if _, err := tx.NewAddColumn().
|
||||
Model(m).
|
||||
ColumnExpr("deleted_at timestamp with time zone").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't add the deleted_at column (model=%T): %w", m, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}, func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
|
||||
models := []interface{}{
|
||||
&model.Player{},
|
||||
&model.Tribe{},
|
||||
}
|
||||
for _, m := range models {
|
||||
if _, err := tx.NewDropColumn().
|
||||
Model(m).
|
||||
Column("deleted_at").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't drop the deleted_at column (model=%T): %w", m, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
|
@ -3,7 +3,9 @@ package bundb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
|
||||
|
||||
|
@ -19,7 +21,7 @@ func NewPlayer(db *bun.DB) *Player {
|
|||
return &Player{db: db}
|
||||
}
|
||||
|
||||
func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error {
|
||||
func (p *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error {
|
||||
if len(params) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -28,7 +30,7 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
|
|||
for _, p := range params {
|
||||
players = append(players, model.NewPlayer(p))
|
||||
}
|
||||
if _, err := t.db.NewInsert().
|
||||
if _, err := p.db.NewInsert().
|
||||
Model(&players).
|
||||
On("CONFLICT ON CONSTRAINT players_pkey DO UPDATE").
|
||||
Set("name = EXCLUDED.name").
|
||||
|
@ -36,6 +38,7 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
|
|||
Set("points = EXCLUDED.points").
|
||||
Set("rank = EXCLUDED.rank").
|
||||
Set("tribe_id = EXCLUDED.tribe_id").
|
||||
Set("deleted_at = null").
|
||||
Apply(appendODSetClauses).
|
||||
Returning("NULL").
|
||||
Exec(ctx); err != nil {
|
||||
|
@ -45,12 +48,31 @@ func (t *Player) CreateOrUpdate(ctx context.Context, params ...domain.CreatePlay
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error) {
|
||||
func (p *Player) DeleteByID(ctx context.Context, ids ...int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := p.db.NewUpdate().
|
||||
Model(&model.Player{}).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("id IN (?)", bun.In(ids)).
|
||||
Set("deleted_at = ?", time.Now()).
|
||||
Set("tribe_id = NULL").
|
||||
Returning("NULL").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't delete players: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error) {
|
||||
var players []model.Player
|
||||
var count int
|
||||
var err error
|
||||
|
||||
q := t.db.NewSelect().
|
||||
q := p.db.NewSelect().
|
||||
Model(&players).
|
||||
Order("server_key ASC", "id ASC").
|
||||
Apply((listPlayersParamsApplier{params}).Apply)
|
||||
|
@ -59,7 +81,7 @@ func (t *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]d
|
|||
} else {
|
||||
err = q.Scan(ctx)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, 0, fmt.Errorf("couldn't select players from the db: %w", err)
|
||||
}
|
||||
|
||||
|
@ -83,5 +105,13 @@ func (l listPlayersParamsApplier) Apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
|
||||
}
|
||||
|
||||
if l.params.Deleted.Valid {
|
||||
if l.params.Deleted.Bool {
|
||||
q = q.Where("deleted_at IS NOT NULL")
|
||||
} else {
|
||||
q = q.Where("deleted_at IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3,6 +3,7 @@ package bundb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -68,7 +69,7 @@ func (s *Server) List(ctx context.Context, params domain.ListServersParams) ([]d
|
|||
} else {
|
||||
err = q.Scan(ctx)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, 0, fmt.Errorf("couldn't select servers from the db: %w", err)
|
||||
}
|
||||
|
||||
|
|
38
internal/bundb/testdata/fixture.yml
vendored
38
internal/bundb/testdata/fixture.yml
vendored
|
@ -214,6 +214,7 @@
|
|||
rank: 30
|
||||
dominance: 0
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
deleted_at: 2020-12-03T18:01:07Z
|
||||
- rank_att: 3
|
||||
score_att: 231146512
|
||||
rank_def: 5
|
||||
|
@ -254,6 +255,7 @@
|
|||
rank: 40
|
||||
dominance: 0
|
||||
created_at: 2020-06-22T13:45:46.000Z
|
||||
deleted_at: 2020-12-07T04:00:56Z
|
||||
- rank_att: 1
|
||||
score_att: 132897
|
||||
rank_def: 3
|
||||
|
@ -1085,6 +1087,24 @@
|
|||
rank: 3
|
||||
tribe_id: 122
|
||||
created_at: 2020-04-08T02:10:08.000Z
|
||||
- rank_att: 16
|
||||
score_att: 43305549
|
||||
rank_def: 5
|
||||
score_def: 88199345
|
||||
rank_sup: 1
|
||||
score_sup: 64715292
|
||||
rank_total: 4
|
||||
score_total: 196220186
|
||||
_id: en113-deleted-1
|
||||
id: 1111
|
||||
server_key: en113
|
||||
name: Deleted 1
|
||||
num_villages: 1421
|
||||
points: 17185328
|
||||
rank: 3
|
||||
tribe_id: 0
|
||||
created_at: 2020-04-08T02:10:08.000Z
|
||||
deleted_at: 2020-12-03T18:01:07Z
|
||||
- rank_att: 8
|
||||
score_att: 78782855
|
||||
rank_def: 27
|
||||
|
@ -1102,6 +1122,24 @@
|
|||
rank: 4
|
||||
tribe_id: 122
|
||||
created_at: 2020-04-13T06:15:34.000Z
|
||||
- rank_att: 8
|
||||
score_att: 78782855
|
||||
rank_def: 27
|
||||
score_def: 32539992
|
||||
rank_sup: 28
|
||||
score_sup: 9291867
|
||||
rank_total: 17
|
||||
score_total: 120614714
|
||||
_id: en113-deleted-2
|
||||
id: 1112
|
||||
server_key: en113
|
||||
name: Deleted 2
|
||||
num_villages: 1358
|
||||
points: 16287830
|
||||
rank: 4
|
||||
tribe_id: 0
|
||||
created_at: 2020-04-13T06:15:34.000Z
|
||||
deleted_at: 2020-06-22T13:45:46Z
|
||||
- rank_att: 6
|
||||
score_att: 95156312
|
||||
rank_def: 23
|
||||
|
|
|
@ -3,7 +3,9 @@ package bundb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
|
||||
|
||||
|
@ -45,6 +47,7 @@ func (t *Tribe) CreateOrUpdate(ctx context.Context, params ...domain.CreateTribe
|
|||
Set("points = EXCLUDED.points").
|
||||
Set("all_points = EXCLUDED.all_points").
|
||||
Set("rank = EXCLUDED.rank").
|
||||
Set("deleted_at = null").
|
||||
Apply(appendODSetClauses).
|
||||
Returning("NULL").
|
||||
Exec(ctx); err != nil {
|
||||
|
@ -54,6 +57,46 @@ func (t *Tribe) CreateOrUpdate(ctx context.Context, params ...domain.CreateTribe
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *Tribe) DeleteByID(ctx context.Context, ids ...int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := t.db.NewUpdate().
|
||||
Model(&model.Tribe{}).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("id IN (?)", bun.In(ids)).
|
||||
Set("deleted_at = ?", time.Now()).
|
||||
Returning("NULL").
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't delete tribes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
|
||||
if numPlayerVillages < 0 {
|
||||
return ErrNumPlayerVillagesGTE
|
||||
}
|
||||
|
||||
q := t.db.NewUpdate().
|
||||
Model(&model.Tribe{}).
|
||||
Returning("NULL").
|
||||
Where("server_key = ?", serverKey).
|
||||
Where("deleted_at IS NULL")
|
||||
if numPlayerVillages > 0 {
|
||||
q = q.Set("dominance = num_villages::double precision / ? * 100", numPlayerVillages)
|
||||
} else {
|
||||
q = q.Set("dominance = 0")
|
||||
}
|
||||
if _, err := q.Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't update dominance (server=%s): %w", serverKey, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error) {
|
||||
var tribes []model.Tribe
|
||||
var count int
|
||||
|
@ -68,7 +111,7 @@ func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]dom
|
|||
} else {
|
||||
err = q.Scan(ctx)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, 0, fmt.Errorf("couldn't select tribes from the db: %w", err)
|
||||
}
|
||||
|
||||
|
@ -79,27 +122,6 @@ func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]dom
|
|||
return result, int64(count), nil
|
||||
}
|
||||
|
||||
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
|
||||
if numPlayerVillages < 0 {
|
||||
return ErrNumPlayerVillagesGTE
|
||||
}
|
||||
|
||||
q := t.db.NewUpdate().
|
||||
Model(&model.Tribe{}).
|
||||
Returning("NULL").
|
||||
Where("server_key = ?", serverKey)
|
||||
if numPlayerVillages > 0 {
|
||||
q = q.Set("dominance = num_villages::double precision / ? * 100", numPlayerVillages)
|
||||
} else {
|
||||
q = q.Set("dominance = 0")
|
||||
}
|
||||
if _, err := q.Exec(ctx); err != nil {
|
||||
return fmt.Errorf("couldn't update dominance (server=%s): %w", serverKey, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type listTribesParamsApplier struct {
|
||||
params domain.ListTribesParams
|
||||
}
|
||||
|
@ -113,5 +135,13 @@ func (l listTribesParamsApplier) Apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
q = q.Where("server_key IN (?)", bun.In(l.params.ServerKeys))
|
||||
}
|
||||
|
||||
if l.params.Deleted.Valid {
|
||||
if l.params.Deleted.Bool {
|
||||
q = q.Where("deleted_at IS NOT NULL")
|
||||
} else {
|
||||
q = q.Where("deleted_at IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
|
|
@ -164,6 +164,40 @@ func TestTribe_CreateOrUpdate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestTribe_DeleteByID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := newDB(t)
|
||||
fixture := loadFixtures(t, db)
|
||||
repo := bundb.NewTribe(db)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tribeCSA := getTribeFromFixture(t, fixture, "pl169-csa")
|
||||
require.Zero(t, tribeCSA.DeletedAt)
|
||||
tribeCSAJR := getTribeFromFixture(t, fixture, "pl169-csa-jr")
|
||||
require.Zero(t, tribeCSAJR.DeletedAt)
|
||||
|
||||
assert.NoError(t, repo.DeleteByID(context.Background(), tribeCSA.ID, tribeCSAJR.ID))
|
||||
tribes, _, err := repo.List(context.Background(), domain.ListTribesParams{
|
||||
IDs: []int64{tribeCSA.ID, tribeCSAJR.ID},
|
||||
ServerKeys: []string{tribeCSA.ServerKey},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tribes, 2)
|
||||
for _, tribe := range tribes {
|
||||
assert.WithinDuration(t, time.Now(), tribe.DeletedAt, 1*time.Second)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("OK: len(ids) == 0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.NoError(t, repo.DeleteByID(context.Background()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTribe_List(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -244,6 +278,25 @@ func TestTribe_List(t *testing.T) {
|
|||
},
|
||||
expectedCount: 1,
|
||||
},
|
||||
{
|
||||
name: "Deleted=true,ServerKeys=[en113],Count=true",
|
||||
params: domain.ListTribesParams{
|
||||
Deleted: domain.NullBool{Bool: true, Valid: true},
|
||||
ServerKeys: []string{"en113"},
|
||||
Count: true,
|
||||
},
|
||||
expectedTribes: []expectedTribe{
|
||||
{
|
||||
id: 1337,
|
||||
serverKey: "en113",
|
||||
},
|
||||
{
|
||||
id: 1115,
|
||||
serverKey: "en113",
|
||||
},
|
||||
},
|
||||
expectedCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -3,6 +3,7 @@ package bundb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
|
||||
|
@ -32,7 +33,7 @@ func (v *Version) List(ctx context.Context, params domain.ListVersionsParams) ([
|
|||
} else {
|
||||
err = q.Scan(ctx)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, 0, fmt.Errorf("couldn't select versions from the db: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package bundb
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
|
||||
|
@ -60,7 +61,7 @@ func (v *Village) List(ctx context.Context, params domain.ListVillagesParams) ([
|
|||
} else {
|
||||
err = q.Scan(ctx)
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, 0, fmt.Errorf("couldn't select villages from the db: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ type Player struct {
|
|||
|
||||
ServerKey string
|
||||
CreatedAt time.Time
|
||||
DeletedAt time.Time
|
||||
}
|
||||
|
||||
type CreatePlayerParams struct {
|
||||
|
@ -29,6 +30,7 @@ type CreatePlayerParams struct {
|
|||
type ListPlayersParams struct {
|
||||
IDs []int64
|
||||
ServerKeys []string
|
||||
Deleted NullBool
|
||||
Count bool
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ type Tribe struct {
|
|||
ServerKey string
|
||||
Dominance float64
|
||||
CreatedAt time.Time
|
||||
DeletedAt time.Time
|
||||
}
|
||||
|
||||
type CreateTribeParams struct {
|
||||
|
@ -32,6 +33,7 @@ type CreateTribeParams struct {
|
|||
type ListTribesParams struct {
|
||||
IDs []int64
|
||||
ServerKeys []string
|
||||
Deleted NullBool
|
||||
Count bool
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,19 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
)
|
||||
|
||||
//counterfeiter:generate -o internal/mock/player_repository.gen.go . PlayerRepository
|
||||
type PlayerRepository interface {
|
||||
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
|
||||
DeleteByID(ctx context.Context, ids ...int64) error
|
||||
List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error)
|
||||
}
|
||||
|
||||
//counterfeiter:generate -o internal/mock/players_getter.gen.go . PlayersGetter
|
||||
type PlayersGetter interface {
|
||||
GetPlayers(ctx context.Context, baseURL string) ([]domain.BasePlayer, error)
|
||||
}
|
||||
|
@ -30,6 +35,17 @@ func (p *Player) Refresh(ctx context.Context, key, url string) (int64, error) {
|
|||
return 0, fmt.Errorf("TWClient.GetPlayers: %w", err)
|
||||
}
|
||||
|
||||
existingPlayers, _, err := p.repo.List(ctx, domain.ListPlayersParams{
|
||||
ServerKeys: []string{key},
|
||||
Deleted: domain.NullBool{
|
||||
Valid: true,
|
||||
Bool: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("PlayerRepository.List: %w", err)
|
||||
}
|
||||
|
||||
params := make([]domain.CreatePlayerParams, 0, len(players))
|
||||
for _, player := range players {
|
||||
params = append(params, domain.CreatePlayerParams{
|
||||
|
@ -42,5 +58,21 @@ func (p *Player) Refresh(ctx context.Context, key, url string) (int64, error) {
|
|||
return 0, fmt.Errorf("PlayerRepository.CreateOrUpdate: %w", err)
|
||||
}
|
||||
|
||||
//nolint:prealloc
|
||||
var playersToDelete []int64
|
||||
for _, existing := range existingPlayers {
|
||||
i := sort.Search(len(players), func(i int) bool {
|
||||
return players[i].ID >= existing.ID
|
||||
})
|
||||
if i < len(players) && players[i].ID == existing.ID {
|
||||
continue
|
||||
}
|
||||
playersToDelete = append(playersToDelete, existing.ID)
|
||||
}
|
||||
err = p.repo.DeleteByID(ctx, playersToDelete...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("PlayerRepository.DeleteByID: %w", err)
|
||||
}
|
||||
|
||||
return int64(len(players)), nil
|
||||
}
|
||||
|
|
115
internal/service/player_test.go
Normal file
115
internal/service/player_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/service"
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/service/internal/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlayer_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serverKey, serverUrl := "pl169", "https://pl169.plemiona.pl"
|
||||
|
||||
players := []domain.BasePlayer{
|
||||
{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 8,
|
||||
ScoreAtt: 7,
|
||||
RankDef: 6,
|
||||
ScoreDef: 5,
|
||||
RankSup: 4,
|
||||
ScoreSup: 3,
|
||||
RankTotal: 2,
|
||||
ScoreTotal: 1,
|
||||
},
|
||||
ID: 998,
|
||||
Name: "name 998",
|
||||
NumVillages: 5,
|
||||
Points: 4,
|
||||
Rank: 2,
|
||||
},
|
||||
{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 1,
|
||||
ScoreAtt: 2,
|
||||
RankDef: 3,
|
||||
ScoreDef: 4,
|
||||
RankSup: 5,
|
||||
ScoreSup: 6,
|
||||
RankTotal: 7,
|
||||
ScoreTotal: 8,
|
||||
},
|
||||
ID: 999,
|
||||
Name: "name 999",
|
||||
NumVillages: 3,
|
||||
Points: 4,
|
||||
Rank: 6,
|
||||
TribeID: 1234,
|
||||
},
|
||||
}
|
||||
client := &mock.FakePlayersGetter{}
|
||||
client.GetPlayersReturns(players, nil)
|
||||
|
||||
existingPlayers := []domain.Player{
|
||||
{
|
||||
BasePlayer: domain.BasePlayer{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 8,
|
||||
ScoreAtt: 7,
|
||||
RankDef: 6,
|
||||
ScoreDef: 5,
|
||||
RankSup: 4,
|
||||
ScoreSup: 3,
|
||||
RankTotal: 2,
|
||||
ScoreTotal: 1,
|
||||
},
|
||||
ID: 997,
|
||||
Name: "name 997",
|
||||
NumVillages: 5,
|
||||
Points: 4,
|
||||
Rank: 2,
|
||||
},
|
||||
ServerKey: serverKey,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
BasePlayer: players[0],
|
||||
ServerKey: serverKey,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
repo := &mock.FakePlayerRepository{}
|
||||
repo.ListReturnsOnCall(0, existingPlayers, 0, nil)
|
||||
repo.CreateOrUpdateReturns(nil)
|
||||
repo.DeleteByIDReturns(nil)
|
||||
|
||||
numPlayers, err := service.NewPlayer(repo, client).Refresh(context.Background(), serverKey, serverUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(players), numPlayers)
|
||||
|
||||
require.Equal(t, 1, client.GetPlayersCallCount())
|
||||
_, passedServerUrl := client.GetPlayersArgsForCall(0)
|
||||
assert.Equal(t, passedServerUrl, serverUrl)
|
||||
|
||||
require.Equal(t, 1, repo.CreateOrUpdateCallCount())
|
||||
_, params := repo.CreateOrUpdateArgsForCall(0)
|
||||
assert.Len(t, params, len(players))
|
||||
for i, p := range params {
|
||||
assert.Equal(t, players[i], p.BasePlayer)
|
||||
assert.Equal(t, serverKey, p.ServerKey)
|
||||
}
|
||||
|
||||
require.Equal(t, 1, repo.DeleteByIDCallCount())
|
||||
_, playersToDelete := repo.DeleteByIDArgsForCall(0)
|
||||
require.Len(t, playersToDelete, 1)
|
||||
assert.Equal(t, existingPlayers[0].ID, playersToDelete[0])
|
||||
|
||||
require.Equal(t, 1, repo.ListCallCount())
|
||||
}
|
|
@ -3,15 +3,20 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
)
|
||||
|
||||
//counterfeiter:generate -o internal/mock/tribe_repository.gen.go . TribeRepository
|
||||
type TribeRepository interface {
|
||||
CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error
|
||||
UpdateDominance(ctx context.Context, serverKey string, numVillages int64) error
|
||||
DeleteByID(ctx context.Context, ids ...int64) error
|
||||
List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error)
|
||||
}
|
||||
|
||||
//counterfeiter:generate -o internal/mock/tribes_getter.gen.go . TribesGetter
|
||||
type TribesGetter interface {
|
||||
GetTribes(ctx context.Context, baseURL string) ([]domain.BaseTribe, error)
|
||||
}
|
||||
|
@ -31,6 +36,17 @@ func (t *Tribe) Refresh(ctx context.Context, key, url string) (int64, error) {
|
|||
return 0, fmt.Errorf("TWClient.GetTribes: %w", err)
|
||||
}
|
||||
|
||||
existingTribes, _, err := t.repo.List(ctx, domain.ListTribesParams{
|
||||
ServerKeys: []string{key},
|
||||
Deleted: domain.NullBool{
|
||||
Valid: true,
|
||||
Bool: false,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("TribeRepository.List: %w", err)
|
||||
}
|
||||
|
||||
params := make([]domain.CreateTribeParams, 0, len(tribes))
|
||||
for _, tribe := range tribes {
|
||||
params = append(params, domain.CreateTribeParams{
|
||||
|
@ -43,6 +59,22 @@ func (t *Tribe) Refresh(ctx context.Context, key, url string) (int64, error) {
|
|||
return 0, fmt.Errorf("TribeRepository.CreateOrUpdate: %w", err)
|
||||
}
|
||||
|
||||
//nolint:prealloc
|
||||
var tribesToDelete []int64
|
||||
for _, existing := range existingTribes {
|
||||
i := sort.Search(len(tribes), func(i int) bool {
|
||||
return tribes[i].ID >= existing.ID
|
||||
})
|
||||
if i < len(tribes) && tribes[i].ID == existing.ID {
|
||||
continue
|
||||
}
|
||||
tribesToDelete = append(tribesToDelete, existing.ID)
|
||||
}
|
||||
err = t.repo.DeleteByID(ctx, tribesToDelete...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("TribeRepository.DeleteByID: %w", err)
|
||||
}
|
||||
|
||||
return int64(len(tribes)), nil
|
||||
}
|
||||
|
||||
|
|
128
internal/service/tribe_test.go
Normal file
128
internal/service/tribe_test.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/service"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/service/internal/mock"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
)
|
||||
|
||||
func TestTribe_Refresh(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serverKey, serverUrl := "pl169", "https://pl169.plemiona.pl"
|
||||
|
||||
tribes := []domain.BaseTribe{
|
||||
{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 8,
|
||||
ScoreAtt: 7,
|
||||
RankDef: 6,
|
||||
ScoreDef: 5,
|
||||
RankSup: 4,
|
||||
ScoreSup: 3,
|
||||
RankTotal: 2,
|
||||
ScoreTotal: 1,
|
||||
},
|
||||
ID: 998,
|
||||
Name: "name 998",
|
||||
Tag: "tag 998",
|
||||
NumMembers: 6,
|
||||
NumVillages: 5,
|
||||
Points: 4,
|
||||
AllPoints: 3,
|
||||
Rank: 2,
|
||||
},
|
||||
{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 1,
|
||||
ScoreAtt: 2,
|
||||
RankDef: 3,
|
||||
ScoreDef: 4,
|
||||
RankSup: 5,
|
||||
ScoreSup: 6,
|
||||
RankTotal: 7,
|
||||
ScoreTotal: 8,
|
||||
},
|
||||
ID: 999,
|
||||
Name: "name 999",
|
||||
Tag: "tag 999",
|
||||
NumMembers: 2,
|
||||
NumVillages: 3,
|
||||
Points: 4,
|
||||
AllPoints: 5,
|
||||
Rank: 6,
|
||||
},
|
||||
}
|
||||
client := &mock.FakeTribesGetter{}
|
||||
client.GetTribesReturns(tribes, nil)
|
||||
|
||||
existingTribes := []domain.Tribe{
|
||||
{
|
||||
BaseTribe: domain.BaseTribe{
|
||||
OpponentsDefeated: domain.OpponentsDefeated{
|
||||
RankAtt: 8,
|
||||
ScoreAtt: 7,
|
||||
RankDef: 6,
|
||||
ScoreDef: 5,
|
||||
RankSup: 4,
|
||||
ScoreSup: 3,
|
||||
RankTotal: 2,
|
||||
ScoreTotal: 1,
|
||||
},
|
||||
ID: 997,
|
||||
Name: "name 997",
|
||||
Tag: "tag 997",
|
||||
NumMembers: 6,
|
||||
NumVillages: 5,
|
||||
Points: 4,
|
||||
AllPoints: 3,
|
||||
Rank: 2,
|
||||
},
|
||||
ServerKey: serverKey,
|
||||
Dominance: 12.5,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
BaseTribe: tribes[0],
|
||||
ServerKey: serverKey,
|
||||
Dominance: 11.5,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
repo := &mock.FakeTribeRepository{}
|
||||
repo.ListReturnsOnCall(0, existingTribes, 0, nil)
|
||||
repo.CreateOrUpdateReturns(nil)
|
||||
repo.DeleteByIDReturns(nil)
|
||||
|
||||
numTribes, err := service.NewTribe(repo, client).Refresh(context.Background(), serverKey, serverUrl)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, len(tribes), numTribes)
|
||||
|
||||
require.Equal(t, 1, client.GetTribesCallCount())
|
||||
_, passedServerUrl := client.GetTribesArgsForCall(0)
|
||||
assert.Equal(t, passedServerUrl, serverUrl)
|
||||
|
||||
require.Equal(t, 1, repo.CreateOrUpdateCallCount())
|
||||
_, params := repo.CreateOrUpdateArgsForCall(0)
|
||||
assert.Len(t, params, len(tribes))
|
||||
for i, p := range params {
|
||||
assert.Equal(t, tribes[i], p.BaseTribe)
|
||||
assert.Equal(t, serverKey, p.ServerKey)
|
||||
}
|
||||
|
||||
require.Equal(t, 1, repo.DeleteByIDCallCount())
|
||||
_, tribesToDelete := repo.DeleteByIDArgsForCall(0)
|
||||
require.Len(t, tribesToDelete, 1)
|
||||
assert.Equal(t, existingTribes[0].ID, tribesToDelete[0])
|
||||
|
||||
require.Equal(t, 1, repo.ListCallCount())
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -177,6 +178,10 @@ func (c *Client) GetTribes(ctx context.Context, baseURL string) ([]domain.BaseTr
|
|||
tribes[i] = tribe
|
||||
}
|
||||
|
||||
sort.SliceStable(tribes, func(i, j int) bool {
|
||||
return tribes[i].ID < tribes[j].ID
|
||||
})
|
||||
|
||||
return tribes, nil
|
||||
}
|
||||
|
||||
|
@ -250,6 +255,10 @@ func (c *Client) GetPlayers(ctx context.Context, baseURL string) ([]domain.BaseP
|
|||
players[i] = player
|
||||
}
|
||||
|
||||
sort.SliceStable(players, func(i, j int) bool {
|
||||
return players[i].ID < players[j].ID
|
||||
})
|
||||
|
||||
return players, nil
|
||||
}
|
||||
|
||||
|
@ -380,6 +389,10 @@ func (c *Client) GetVillages(ctx context.Context, baseURL string) ([]domain.Base
|
|||
villages[i] = village
|
||||
}
|
||||
|
||||
sort.SliceStable(villages, func(i, j int) bool {
|
||||
return villages[i].ID < villages[j].ID
|
||||
})
|
||||
|
||||
return villages, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/core/internal/tw"
|
||||
|
@ -814,19 +816,9 @@ func TestClient_GetTribes(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
assert.Len(t, tribes, len(tt.expectedTribes))
|
||||
|
||||
for _, tribe := range tt.expectedTribes {
|
||||
var expectedTribe domain.BaseTribe
|
||||
|
||||
for _, tribe2 := range tribes {
|
||||
if tribe.ID == tribe2.ID {
|
||||
expectedTribe = tribe2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedTribe, tribe)
|
||||
require.Len(t, tribes, len(tt.expectedTribes))
|
||||
for i, tribe := range tt.expectedTribes {
|
||||
assert.Equal(t, tribe, tt.expectedTribes[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1096,19 +1088,9 @@ func TestClient_GetPlayers(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
assert.Len(t, players, len(tt.expectedPlayers))
|
||||
|
||||
for _, player1 := range tt.expectedPlayers {
|
||||
var expectedPlayer domain.BasePlayer
|
||||
|
||||
for _, player2 := range players {
|
||||
if player1.ID == player2.ID {
|
||||
expectedPlayer = player2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedPlayer, player1)
|
||||
require.Len(t, players, len(tt.expectedPlayers))
|
||||
for i, player := range tt.expectedPlayers {
|
||||
assert.Equal(t, player, tt.expectedPlayers[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1235,19 +1217,9 @@ func TestClient_GetVillages(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.True(t, checkErr(err))
|
||||
assert.Len(t, villages, len(tt.expectedVillages))
|
||||
|
||||
for _, village := range tt.expectedVillages {
|
||||
var expectedVillage domain.BaseVillage
|
||||
|
||||
for _, village2 := range villages {
|
||||
if village.ID == village2.ID {
|
||||
expectedVillage = village2
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedVillage, village)
|
||||
require.Len(t, villages, len(tt.expectedVillages))
|
||||
for i, village := range tt.expectedVillages {
|
||||
assert.Equal(t, village, tt.expectedVillages[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Reference in New Issue
Block a user