feat: tribe persistence (#13)

Reviewed-on: twhelp/corev3#13
This commit is contained in:
Dawid Wysokiński 2023-12-29 08:23:05 +00:00
parent 9668c23cc8
commit ff2e578d0d
4 changed files with 213 additions and 28 deletions

View File

@ -28,38 +28,38 @@ func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCm
openServers, err := svc.twSvc.GetOpenServers(ctx, payload.URL())
if err != nil {
return fmt.Errorf("couldn't get open servers for version code %s: %w", versionCode, err)
return fmt.Errorf("%s: couldn't get open servers: %w", versionCode, err)
}
specialServers, err := svc.listAllSpecial(ctx, versionCode)
if err != nil {
return fmt.Errorf("couldn't list special servers with version code %s: %w", versionCode, err)
return fmt.Errorf("%s: couldn't list special servers: %w", versionCode, err)
}
currentlyStoredOpenServers, err := svc.listAllOpen(ctx, versionCode)
if err != nil {
return fmt.Errorf("couldn't list open servers with version code %s: %w", versionCode, err)
return fmt.Errorf("%s: couldn't list open servers: %w", versionCode, err)
}
openServersWithoutSpecial := openServers.FilterOutSpecial(specialServers)
serversToBeClosed, err := currentlyStoredOpenServers.Close(openServersWithoutSpecial)
if err != nil {
return fmt.Errorf("couldn't close servers: %w", err)
return fmt.Errorf("%s: couldn't close servers: %w", versionCode, err)
}
params, err := domain.NewCreateServerParams(append(openServersWithoutSpecial, serversToBeClosed...), versionCode)
if err != nil {
return err
return fmt.Errorf("%s: %w", versionCode, err)
}
if err = svc.repo.CreateOrUpdate(ctx, params...); err != nil {
return err
return fmt.Errorf("%s: couldn't create/update servers: %w", versionCode, err)
}
payloads, err := domain.NewServerSyncedEventPayloads(openServersWithoutSpecial, versionCode)
if err != nil {
return fmt.Errorf("couldn't construct server synced event payloads: %w", err)
return fmt.Errorf("%s: couldn't construct server synced event payloads: %w", versionCode, err)
}
return svc.publisher.EventSynced(ctx, payloads...)
@ -67,11 +67,12 @@ func (svc *ServerService) Sync(ctx context.Context, payload domain.SyncServersCm
func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string) (domain.Servers, error) {
params := domain.NewListServersParams()
if err := params.SetVersionCodes([]string{versionCode}); err != nil {
return nil, err
}
if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
return nil, err
}
if err := params.SetSpecial(domain.NullBool{
Value: true,
Valid: true,
@ -84,11 +85,12 @@ func (svc *ServerService) listAllSpecial(ctx context.Context, versionCode string
func (svc *ServerService) listAllOpen(ctx context.Context, versionCode string) (domain.Servers, error) {
params := domain.NewListServersParams()
if err := params.SetVersionCodes([]string{versionCode}); err != nil {
return nil, err
}
if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
return nil, err
}
if err := params.SetOpen(domain.NullBool{
Value: true,
Valid: true,
@ -105,11 +107,9 @@ func (svc *ServerService) ListAll(ctx context.Context, params domain.ListServers
if err := params.SetOffset(0); err != nil {
return nil, err
}
if err := params.SetLimit(domain.ServerListMaxLimit); err != nil {
return nil, err
}
if err := params.SetSort([]domain.ServerSort{domain.ServerSortKeyASC}); err != nil {
return nil, err
}
@ -156,21 +156,18 @@ func (svc *ServerService) SyncConfigAndInfo(ctx context.Context, payload domain.
}
var updateParams domain.UpdateServerParams
if err = updateParams.SetConfig(domain.NullServerConfig{
Value: cfg,
Valid: true,
}); err != nil {
return err
}
if err = updateParams.SetBuildingInfo(domain.NullBuildingInfo{
Value: buildingInfo,
Valid: true,
}); err != nil {
return err
}
if err = updateParams.SetUnitInfo(domain.NullUnitInfo{
Value: unitInfo,
Valid: true,

View File

@ -23,12 +23,113 @@ func NewTribeService(repo TribeRepository, twSvc TWService) *TribeService {
}
func (svc *TribeService) Sync(ctx context.Context, payload domain.ServerSyncedEventPayload) error {
serverKey := payload.Key()
tribes, err := svc.twSvc.GetTribes(ctx, payload.URL())
if err != nil {
return fmt.Errorf("couldn't get tribes for server %s: %w", payload.Key(), err)
return fmt.Errorf("%s: couldn't get tribes: %w", serverKey, err)
}
fmt.Println(payload.URL(), len(tribes))
if err = svc.createOrUpdate(ctx, serverKey, tribes); err != nil {
return fmt.Errorf("%s: couldn't create/update tribes: %w", serverKey, err)
}
if err = svc.delete(ctx, serverKey, tribes); err != nil {
return fmt.Errorf("%s: couldn't delete tribes: %w", serverKey, err)
}
return nil
}
const tribeCreateOrUpdateChunkSize = domain.TribeListMaxLimit
func (svc *TribeService) createOrUpdate(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
for i := 0; i < len(tribes); i += tribeCreateOrUpdateChunkSize {
end := i + tribeCreateOrUpdateChunkSize
if end > len(tribes) {
end = len(tribes)
}
if err := svc.createOrUpdateChunk(ctx, serverKey, tribes[i:end]); err != nil {
return err
}
}
return nil
}
func (svc *TribeService) createOrUpdateChunk(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
ids := make([]int, 0, len(tribes))
for _, t := range tribes {
ids = append(ids, t.ID())
}
listParams := domain.NewListTribesParams()
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
return err
}
if err := listParams.SetIDs(ids); err != nil {
return err
}
if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
return err
}
storedTribes, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
createParams, err := domain.NewCreateTribeParams(serverKey, tribes, storedTribes)
if err != nil {
return err
}
return svc.repo.CreateOrUpdate(ctx, createParams...)
}
func (svc *TribeService) delete(ctx context.Context, serverKey string, tribes domain.BaseTribes) error {
listParams := domain.NewListTribesParams()
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
return err
}
if err := listParams.SetDeleted(domain.NullBool{
Value: false,
Valid: true,
}); err != nil {
return err
}
if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil {
return err
}
var toDelete []int
for {
storedTribes, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
if len(storedTribes) == 0 {
break
}
toDelete = append(toDelete, storedTribes.Delete(tribes)...)
if err = listParams.SetIDGT(domain.NullInt{
Value: storedTribes[len(storedTribes)-1].ID(),
Valid: true,
}); err != nil {
return err
}
}
return svc.repo.Delete(ctx, serverKey, toDelete...)
}

View File

@ -1,6 +1,7 @@
package domain
import (
"cmp"
"fmt"
"math"
"net/url"
@ -208,6 +209,26 @@ func (t Tribe) Base() BaseTribe {
type Tribes []Tribe
// Delete finds all tribes that are not in the given slice with active tribes and returns their ids.
// Both slices must be sorted in ascending order by ID.
func (ts Tribes) Delete(active BaseTribes) []int {
//nolint:prealloc
var toDelete []int
for _, t := range ts {
_, found := slices.BinarySearchFunc(active, t, func(a BaseTribe, b Tribe) int {
return cmp.Compare(a.ID(), b.ID())
})
if found {
continue
}
toDelete = append(toDelete, t.ID())
}
return toDelete
}
type CreateTribeParams struct {
base BaseTribe
serverKey string
@ -221,6 +242,10 @@ type CreateTribeParams struct {
const createTribeParamsModelName = "CreateTribeParams"
// NewCreateTribeParams constructs a slice of CreateTribeParams based on the given parameters.
// Both slices must be sorted in ascending order by ID
// + if storedTribes contains tribes from different servers. they must be sorted in ascending order by server key.
//
//nolint:gocyclo
func NewCreateTribeParams(serverKey string, tribes BaseTribes, storedTribes Tribes) ([]CreateTribeParams, error) {
if err := validateServerKey(serverKey); err != nil {
@ -239,10 +264,14 @@ func NewCreateTribeParams(serverKey string, tribes BaseTribes, storedTribes Trib
}
var old Tribe
if oldIdx := slices.IndexFunc(storedTribes, func(old Tribe) bool {
return old.ID() == t.ID() && old.ServerKey() == serverKey
}); oldIdx >= 0 {
old = storedTribes[oldIdx]
idx, found := slices.BinarySearchFunc(storedTribes, t, func(a Tribe, b BaseTribe) int {
if res := cmp.Compare(a.ServerKey(), serverKey); res != 0 {
return res
}
return cmp.Compare(a.ID(), b.ID())
})
if found {
old = storedTribes[idx]
}
p := CreateTribeParams{

View File

@ -1,6 +1,7 @@
package domain_test
import (
"cmp"
"fmt"
"math"
"slices"
@ -14,6 +15,44 @@ import (
"github.com/stretchr/testify/require"
)
func TestTribes_Delete(t *testing.T) {
t.Parallel()
server := domaintest.NewServer(t)
active := domain.BaseTribes{
domaintest.NewBaseTribe(t),
domaintest.NewBaseTribe(t),
domaintest.NewBaseTribe(t),
}
slices.SortFunc(active, func(a, b domain.BaseTribe) int {
return cmp.Compare(a.ID(), b.ID())
})
tribes := domain.Tribes{
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
cfg.ID = active[0].ID()
cfg.ServerKey = server.Key()
}),
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
cfg.ServerKey = server.Key()
}),
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
cfg.ID = active[1].ID()
cfg.ServerKey = server.Key()
}),
}
expectedIDs := []int{tribes[1].ID()}
slices.SortFunc(tribes, func(a, b domain.Tribe) int {
if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
return res
}
return cmp.Compare(a.ID(), b.ID())
})
assert.Equal(t, expectedIDs, tribes.Delete(active))
}
func TestNewCreateTribeParams(t *testing.T) {
t.Parallel()
@ -26,8 +65,21 @@ func TestNewCreateTribeParams(t *testing.T) {
domaintest.NewBaseTribe(t),
domaintest.NewBaseTribe(t),
}
slices.SortFunc(tribes, func(a, b domain.BaseTribe) int {
return cmp.Compare(a.ID(), b.ID())
})
storedTribes := domain.Tribes{
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
cfg.ID = tribes[0].ID()
cfg.ServerKey = server.Key()[:len(server.Key())-1]
cfg.BestRank = tribes[0].Rank() + 1
cfg.BestRankAt = now.Add(-time.Hour)
cfg.MostPoints = tribes[0].AllPoints() - 1
cfg.MostPointsAt = now.Add(-time.Hour)
cfg.MostVillages = tribes[0].NumVillages() - 1
cfg.MostVillagesAt = now.Add(-time.Hour)
}),
domaintest.NewTribe(t, func(cfg *domaintest.TribeConfig) {
cfg.ID = tribes[0].ID()
cfg.ServerKey = server.Key()
@ -49,6 +101,12 @@ func TestNewCreateTribeParams(t *testing.T) {
cfg.MostVillagesAt = now.Add(-time.Hour)
}),
}
slices.SortFunc(storedTribes, func(a, b domain.Tribe) int {
if res := cmp.Compare(a.ServerKey(), b.ServerKey()); res != 0 {
return res
}
return cmp.Compare(a.ID(), b.ID())
})
expectedParams := []struct {
base domain.BaseTribe
@ -70,12 +128,12 @@ func TestNewCreateTribeParams(t *testing.T) {
},
{
base: tribes[1],
bestRank: storedTribes[1].BestRank(),
bestRankAt: storedTribes[1].BestRankAt(),
mostPoints: storedTribes[1].MostPoints(),
mostPointsAt: storedTribes[1].MostPointsAt(),
mostVillages: storedTribes[1].MostVillages(),
mostVillagesAt: storedTribes[1].MostVillagesAt(),
bestRank: storedTribes[2].BestRank(),
bestRankAt: storedTribes[2].BestRankAt(),
mostPoints: storedTribes[2].MostPoints(),
mostPointsAt: storedTribes[2].MostPointsAt(),
mostVillages: storedTribes[2].MostVillages(),
mostVillagesAt: storedTribes[2].MostVillagesAt(),
},
{
base: tribes[2],