This repository has been archived on 2024-04-06. You can view files and clone it, but cannot push or open issues or pull requests.
core-old/internal/service/tribe.go
Dawid Wysokiński 42f95d6dd1
All checks were successful
continuous-integration/drone/push Build is passing
fix: tribe - fix incorrect most points logic
2023-02-26 09:51:52 +01:00

326 lines
8.1 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, error)
ListCount(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, error) {
params, err := listTribesParamsBuilder{params}.build()
if err != nil {
return nil, err
}
tribes, err := t.repo.List(ctx, params)
if err != nil {
return nil, fmt.Errorf("TribeRepository.List: %w", err)
}
return tribes, nil
}
func (t *Tribe) ListCount(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error) {
params, err := listTribesParamsBuilder{params}.build()
if err != nil {
return nil, 0, err
}
tribes, count, err := t.repo.ListCount(ctx, params)
if err != nil {
return nil, 0, fmt.Errorf("TribeRepository.ListCount: %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 _, f := range [...]func(p domain.CreateTribeParams) domain.CreateTribeParams{
c.setBase,
c.setBestRank,
c.setMostVillages,
c.setMostPoints,
} {
p = f(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.AllPoints <= c.tribeDB.MostPoints && c.tribeDB.ID > 0 {
p.MostPoints = c.tribeDB.MostPoints
p.MostPointsAt = c.tribeDB.MostPointsAt
return p
}
p.MostPoints = c.tribe.AllPoints
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
}
type listTribesParamsBuilder struct {
domain.ListTribesParams
}
func (l listTribesParamsBuilder) build() (domain.ListTribesParams, error) {
if len(l.Sort) == 0 {
l.Sort = []domain.TribeSort{
{
By: domain.TribeSortByID,
Direction: domain.SortDirectionASC,
},
}
}
if l.Pagination.Limit == 0 {
l.Pagination.Limit = tribeMaxLimit
}
if len(l.Tags) > tribeTagMaxLen {
return domain.ListTribesParams{}, domain.ValidationError{
Field: "tag",
Err: domain.MaxLengthError{
Max: tribeTagMaxLen,
},
}
}
if len(l.Sort) > tribeSortMaxLen {
return domain.ListTribesParams{}, domain.ValidationError{
Field: "sort",
Err: domain.MaxLengthError{
Max: tribeSortMaxLen,
},
}
}
if err := validatePagination(l.Pagination, tribeMaxLimit); err != nil {
return domain.ListTribesParams{}, fmt.Errorf("validatePagination: %w", err)
}
return l.ListTribesParams, nil
}