Dawid Wysokiński
d244dc69bf
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: twhelp/core#140
298 lines
7.4 KiB
Go
298 lines
7.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
|
"gitea.dwysokinski.me/twhelp/core/internal/tw"
|
|
)
|
|
|
|
const (
|
|
tribeChunkSize = 500
|
|
tribeMaxLimit = 200
|
|
tribeTagMaxLen = 20
|
|
tribeSortMaxLen = 3
|
|
)
|
|
|
|
//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
|
|
Delete(ctx context.Context, serverKey string, ids ...int64) error
|
|
List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error)
|
|
}
|
|
|
|
//counterfeiter:generate -o internal/mock/tribe_getter.gen.go . TribeGetter
|
|
type TribeGetter interface {
|
|
GetTribes(ctx context.Context, baseURL string) ([]tw.Tribe, error)
|
|
}
|
|
|
|
type Tribe struct {
|
|
repo TribeRepository
|
|
client TribeGetter
|
|
}
|
|
|
|
func NewTribe(repo TribeRepository, client TribeGetter) *Tribe {
|
|
return &Tribe{repo: repo, client: client}
|
|
}
|
|
|
|
func (t *Tribe) Refresh(ctx context.Context, key, url string) (int64, error) {
|
|
tribes, err := t.client.GetTribes(ctx, url)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("TWClient.GetTribes: %w", err)
|
|
}
|
|
|
|
if err = t.createOrUpdate(ctx, key, tribes); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if err = t.delete(ctx, key, tribes); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return int64(len(tribes)), nil
|
|
}
|
|
|
|
func (t *Tribe) createOrUpdate(ctx context.Context, key string, tribes []tw.Tribe) error {
|
|
for i := 0; i < len(tribes); i += tribeChunkSize {
|
|
end := i + tribeChunkSize
|
|
if end > len(tribes) {
|
|
end = len(tribes)
|
|
}
|
|
|
|
if err := t.createOrUpdateChunk(ctx, key, tribes[i:end]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) createOrUpdateChunk(ctx context.Context, key string, chunk []tw.Tribe) error {
|
|
ids := make([]int64, 0, len(chunk))
|
|
for _, player := range chunk {
|
|
ids = append(ids, player.ID)
|
|
}
|
|
|
|
tribesDB, _, err := t.repo.List(ctx, domain.ListTribesParams{
|
|
ServerKeys: []string{key},
|
|
IDs: ids,
|
|
Sort: []domain.TribeSort{
|
|
{By: domain.TribeSortByID, Direction: domain.SortDirectionASC},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("PlayerRepository.List: %w", err)
|
|
}
|
|
|
|
params := make([]domain.CreateTribeParams, 0, len(chunk))
|
|
for _, tribe := range chunk {
|
|
var tribeDB domain.Tribe
|
|
j := sort.Search(len(tribesDB), func(i int) bool {
|
|
return tribesDB[i].ID >= tribe.ID
|
|
})
|
|
if j < len(tribesDB) && tribesDB[j].ID == tribe.ID {
|
|
tribeDB = tribesDB[j]
|
|
}
|
|
|
|
params = append(params, createTribeParamsBuilder{tribe, tribeDB, key}.build())
|
|
}
|
|
|
|
if err = t.repo.CreateOrUpdate(ctx, params...); err != nil {
|
|
return fmt.Errorf("TribeRepository.CreateOrUpdate: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) delete(ctx context.Context, key string, tribes []tw.Tribe) error {
|
|
//nolint:prealloc
|
|
var tribesToDelete []int64
|
|
var lastID int64
|
|
for {
|
|
tribesDB, _, err := t.repo.List(ctx, domain.ListTribesParams{
|
|
ServerKeys: []string{key},
|
|
IDGT: domain.NullInt64{
|
|
Int64: lastID,
|
|
Valid: true,
|
|
},
|
|
Deleted: domain.NullBool{
|
|
Valid: true,
|
|
Bool: false,
|
|
},
|
|
Pagination: domain.Pagination{
|
|
Limit: tribeChunkSize,
|
|
},
|
|
Sort: []domain.TribeSort{
|
|
{By: domain.TribeSortByID, Direction: domain.SortDirectionASC},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("TribeRepository.List: %w", err)
|
|
}
|
|
|
|
for _, tribe := range tribesDB {
|
|
i := sort.Search(len(tribes), func(i int) bool {
|
|
return tribes[i].ID >= tribe.ID
|
|
})
|
|
if i < len(tribes) && tribes[i].ID == tribe.ID {
|
|
continue
|
|
}
|
|
tribesToDelete = append(tribesToDelete, tribe.ID)
|
|
}
|
|
|
|
if len(tribesDB) < tribeChunkSize {
|
|
break
|
|
}
|
|
|
|
lastID = tribesDB[len(tribesDB)-1].ID
|
|
}
|
|
|
|
if err := t.repo.Delete(ctx, key, tribesToDelete...); err != nil {
|
|
return fmt.Errorf("TribeRepository.Delete: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
|
|
if err := t.repo.UpdateDominance(ctx, serverKey, numPlayerVillages); err != nil {
|
|
return fmt.Errorf("TribeRepository.UpdateDominance: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error) {
|
|
if len(params.Tags) > tribeTagMaxLen {
|
|
return nil, 0, domain.ValidationError{
|
|
Field: "tag",
|
|
Err: domain.MaxLengthError{
|
|
Max: tribeTagMaxLen,
|
|
},
|
|
}
|
|
}
|
|
|
|
if len(params.Sort) == 0 {
|
|
params.Sort = []domain.TribeSort{
|
|
{
|
|
By: domain.TribeSortByID,
|
|
Direction: domain.SortDirectionASC,
|
|
},
|
|
}
|
|
}
|
|
|
|
if len(params.Sort) > tribeSortMaxLen {
|
|
return nil, 0, domain.ValidationError{
|
|
Field: "sort",
|
|
Err: domain.MaxLengthError{
|
|
Max: tribeSortMaxLen,
|
|
},
|
|
}
|
|
}
|
|
|
|
if params.Pagination.Limit == 0 {
|
|
params.Pagination.Limit = tribeMaxLimit
|
|
}
|
|
|
|
if err := validatePagination(params.Pagination, tribeMaxLimit); err != nil {
|
|
return nil, 0, fmt.Errorf("validatePagination: %w", err)
|
|
}
|
|
|
|
tribes, count, err := t.repo.List(ctx, params)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("TribeRepository.List: %w", err)
|
|
}
|
|
|
|
return tribes, count, nil
|
|
}
|
|
|
|
func (t *Tribe) GetByServerKeyAndID(ctx context.Context, serverKey string, id int64) (domain.Tribe, error) {
|
|
tribes, _, err := t.repo.List(ctx, domain.ListTribesParams{
|
|
IDs: []int64{id},
|
|
ServerKeys: []string{serverKey},
|
|
Pagination: domain.Pagination{
|
|
Limit: 1,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return domain.Tribe{}, fmt.Errorf("TribeRepository.List: %w", err)
|
|
}
|
|
if len(tribes) == 0 {
|
|
return domain.Tribe{}, domain.TribeNotFoundError{
|
|
ID: id,
|
|
}
|
|
}
|
|
|
|
return tribes[0], nil
|
|
}
|
|
|
|
type createTribeParamsBuilder struct {
|
|
tribe tw.Tribe
|
|
tribeDB domain.Tribe
|
|
key string
|
|
}
|
|
|
|
func (c createTribeParamsBuilder) build() domain.CreateTribeParams {
|
|
var p domain.CreateTribeParams
|
|
for _, fn := range [...]func(p domain.CreateTribeParams) domain.CreateTribeParams{
|
|
c.setBase,
|
|
c.setBestRank,
|
|
c.setMostVillages,
|
|
c.setMostPoints,
|
|
} {
|
|
p = fn(p)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func (c createTribeParamsBuilder) setBase(p domain.CreateTribeParams) domain.CreateTribeParams {
|
|
p.OpponentsDefeated = domain.OpponentsDefeated(c.tribe.OpponentsDefeated)
|
|
p.ID = c.tribe.ID
|
|
p.Name = c.tribe.Name
|
|
p.Tag = c.tribe.Tag
|
|
p.NumMembers = c.tribe.NumMembers
|
|
p.NumVillages = c.tribe.NumVillages
|
|
p.Points = c.tribe.Points
|
|
p.AllPoints = c.tribe.AllPoints
|
|
p.Rank = c.tribe.Rank
|
|
p.ProfileURL = c.tribe.ProfileURL
|
|
p.ServerKey = c.key
|
|
return p
|
|
}
|
|
|
|
func (c createTribeParamsBuilder) setBestRank(p domain.CreateTribeParams) domain.CreateTribeParams {
|
|
if c.tribe.Rank >= c.tribeDB.BestRank && c.tribeDB.ID > 0 {
|
|
p.BestRank = c.tribeDB.BestRank
|
|
p.BestRankAt = c.tribeDB.BestRankAt
|
|
return p
|
|
}
|
|
p.BestRank = c.tribe.Rank
|
|
p.BestRankAt = time.Now()
|
|
return p
|
|
}
|
|
|
|
func (c createTribeParamsBuilder) setMostPoints(p domain.CreateTribeParams) domain.CreateTribeParams {
|
|
if c.tribe.Points <= c.tribeDB.MostPoints && c.tribeDB.ID > 0 {
|
|
p.MostPoints = c.tribeDB.MostPoints
|
|
p.MostPointsAt = c.tribeDB.MostPointsAt
|
|
return p
|
|
}
|
|
p.MostPoints = c.tribe.Points
|
|
p.MostPointsAt = time.Now()
|
|
return p
|
|
}
|
|
|
|
func (c createTribeParamsBuilder) setMostVillages(p domain.CreateTribeParams) domain.CreateTribeParams {
|
|
if c.tribe.NumVillages <= c.tribeDB.MostVillages && c.tribeDB.ID > 0 {
|
|
p.MostVillages = c.tribeDB.MostVillages
|
|
p.MostVillagesAt = c.tribeDB.MostVillagesAt
|
|
return p
|
|
}
|
|
p.MostVillages = c.tribe.NumVillages
|
|
p.MostVillagesAt = time.Now()
|
|
return p
|
|
}
|