feat: add the DeletedAt field to Player and Tribe

This commit is contained in:
Dawid Wysokiński 2022-08-14 09:38:51 +02:00
parent f246e205b4
commit 06e8706115
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
9 changed files with 188 additions and 25 deletions

View File

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

View File

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

View File

@ -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 := db.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 := db.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
})
})
}

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -19,7 +20,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 +29,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 +37,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 +47,30 @@ 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()).
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)

View File

@ -159,6 +159,40 @@ func TestPlayer_CreateOrUpdate(t *testing.T) {
})
}
func TestPlayer_DeleteByID(t *testing.T) {
t.Parallel()
db := newDB(t)
fixture := loadFixtures(t, db)
repo := bundb.NewPlayer(db)
t.Run("OK", func(t *testing.T) {
t.Parallel()
playerChceszRemont := getPlayerFromFixture(t, fixture, "pl169-chcesz-remont")
require.Zero(t, playerChceszRemont.DeletedAt)
playerMaddov := getPlayerFromFixture(t, fixture, "pl169-maddov")
require.Zero(t, playerMaddov.DeletedAt)
assert.NoError(t, repo.DeleteByID(context.Background(), playerChceszRemont.ID, playerMaddov.ID))
players, _, err := repo.List(context.Background(), domain.ListPlayersParams{
IDs: []int64{playerChceszRemont.ID, playerMaddov.ID},
ServerKeys: []string{playerChceszRemont.ServerKey},
})
assert.NoError(t, err)
assert.Len(t, players, 2)
for _, player := range players {
assert.WithinDuration(t, time.Now(), player.DeletedAt, 1*time.Second)
}
})
t.Run("OK: len(ids) == 0", func(t *testing.T) {
t.Parallel()
assert.NoError(t, repo.DeleteByID(context.Background()))
})
}
func TestPlayer_List(t *testing.T) {
t.Parallel()

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/core/internal/bundb/internal/model"
@ -45,6 +46,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 +56,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
@ -79,27 +121,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
}

View File

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

View File

@ -18,6 +18,7 @@ type Player struct {
ServerKey string
CreatedAt time.Time
DeletedAt time.Time
}
type CreatePlayerParams struct {

View File

@ -21,6 +21,7 @@ type Tribe struct {
ServerKey string
Dominance float64
CreatedAt time.Time
DeletedAt time.Time
}
type CreateTribeParams struct {