Dawid Wysokiński
ca895354cd
All checks were successful
continuous-integration/drone/push Build is passing
274 lines
7.1 KiB
Go
274 lines
7.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"gitea.dwysokinski.me/twhelp/core/internal/domain"
|
|
"gitea.dwysokinski.me/twhelp/core/internal/tw"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/trace"
|
|
)
|
|
|
|
const (
|
|
tribeChunkSize = 1000
|
|
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
|
|
DeleteByID(ctx context.Context, 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) {
|
|
ctx, span := tracer.Start(ctx, "Tribe.Refresh", trace.WithAttributes(
|
|
attribute.String("server.key", key),
|
|
attribute.String("server.url", url),
|
|
))
|
|
defer span.End()
|
|
|
|
tribes, err := t.client.GetTribes(ctx, url)
|
|
if err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return 0, fmt.Errorf("TWClient.GetTribes: %w", err)
|
|
}
|
|
|
|
if err = t.createOrUpdate(ctx, key, tribes); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return 0, err
|
|
}
|
|
|
|
if err = t.delete(ctx, key, tribes); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return 0, err
|
|
}
|
|
|
|
return int64(len(tribes)), nil
|
|
}
|
|
|
|
func (t *Tribe) createOrUpdate(ctx context.Context, key string, tribes []tw.Tribe) error {
|
|
ctx, span := tracer.Start(ctx, "Tribe.createOrUpdate", trace.WithAttributes(
|
|
attribute.String("server.key", key),
|
|
))
|
|
defer span.End()
|
|
|
|
for i := 0; i < len(tribes); i += tribeChunkSize {
|
|
end := i + tribeChunkSize
|
|
if end > len(tribes) {
|
|
end = len(tribes)
|
|
}
|
|
|
|
chunk := tribes[i:end]
|
|
|
|
params := make([]domain.CreateTribeParams, 0, len(chunk))
|
|
for _, tribe := range chunk {
|
|
params = append(params, domain.CreateTribeParams{
|
|
OpponentsDefeated: domain.OpponentsDefeated(tribe.OpponentsDefeated),
|
|
ID: tribe.ID,
|
|
Name: tribe.Name,
|
|
Tag: tribe.Tag,
|
|
NumMembers: tribe.NumMembers,
|
|
NumVillages: tribe.NumVillages,
|
|
Points: tribe.Points,
|
|
AllPoints: tribe.AllPoints,
|
|
Rank: tribe.Rank,
|
|
ProfileURL: tribe.ProfileURL,
|
|
ServerKey: key,
|
|
})
|
|
}
|
|
if err := t.repo.CreateOrUpdate(ctx, params...); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return fmt.Errorf("TribeRepository.CreateOrUpdate: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) delete(ctx context.Context, key string, existing []tw.Tribe) error {
|
|
ctx, span := tracer.Start(ctx, "Tribe.delete", trace.WithAttributes(
|
|
attribute.String("server.key", key),
|
|
))
|
|
defer span.End()
|
|
|
|
//nolint:prealloc
|
|
var tribesToDelete []int64
|
|
var lastID int64
|
|
for {
|
|
tribes, _, 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 {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return fmt.Errorf("TribeRepository.List: %w", err)
|
|
}
|
|
|
|
for _, tribe := range tribes {
|
|
i := sort.Search(len(existing), func(i int) bool {
|
|
return existing[i].ID >= tribe.ID
|
|
})
|
|
if i < len(existing) && existing[i].ID == tribe.ID {
|
|
continue
|
|
}
|
|
tribesToDelete = append(tribesToDelete, tribe.ID)
|
|
}
|
|
|
|
if len(tribes) < tribeChunkSize {
|
|
break
|
|
}
|
|
|
|
lastID = tribes[len(tribes)-1].ID
|
|
}
|
|
|
|
if err := t.repo.DeleteByID(ctx, tribesToDelete...); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return fmt.Errorf("TribeRepository.DeleteByID: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int64) error {
|
|
ctx, span := tracer.Start(ctx, "Tribe.UpdateDominance", trace.WithAttributes(
|
|
attribute.String("server.key", serverKey),
|
|
attribute.Int64("server.num_player_villages", numPlayerVillages),
|
|
))
|
|
defer span.End()
|
|
|
|
if err := t.repo.UpdateDominance(ctx, serverKey, numPlayerVillages); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return fmt.Errorf("TribeRepository.UpdateDominance: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *Tribe) List(ctx context.Context, params domain.ListTribesParams) ([]domain.Tribe, int64, error) {
|
|
ctx, span := tracer.Start(ctx, "Tribe.List")
|
|
defer span.End()
|
|
|
|
if len(params.Tags) > tribeTagMaxLen {
|
|
err := domain.ValidationError{
|
|
Field: "tag",
|
|
Err: domain.MaxLengthError{
|
|
Max: tribeTagMaxLen,
|
|
},
|
|
}
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return nil, 0, err
|
|
}
|
|
|
|
if len(params.Sort) == 0 {
|
|
params.Sort = []domain.TribeSort{
|
|
{
|
|
By: domain.TribeSortByID,
|
|
Direction: domain.SortDirectionASC,
|
|
},
|
|
}
|
|
}
|
|
|
|
if len(params.Sort) > tribeSortMaxLen {
|
|
err := domain.ValidationError{
|
|
Field: "sort",
|
|
Err: domain.MaxLengthError{
|
|
Max: tribeSortMaxLen,
|
|
},
|
|
}
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return nil, 0, err
|
|
}
|
|
|
|
if params.Pagination.Limit == 0 {
|
|
params.Pagination.Limit = tribeMaxLimit
|
|
}
|
|
|
|
if err := validatePagination(params.Pagination, tribeMaxLimit); err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return nil, 0, fmt.Errorf("validatePagination: %w", err)
|
|
}
|
|
|
|
tribes, count, err := t.repo.List(ctx, params)
|
|
if err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
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) {
|
|
ctx, span := tracer.Start(ctx, "Tribe.GetByServerKeyAndID", trace.WithAttributes(
|
|
attribute.String("server.key", serverKey),
|
|
attribute.Int64("tribe.id", id),
|
|
))
|
|
defer span.End()
|
|
|
|
tribes, _, err := t.repo.List(ctx, domain.ListTribesParams{
|
|
IDs: []int64{id},
|
|
ServerKeys: []string{serverKey},
|
|
Pagination: domain.Pagination{
|
|
Limit: 1,
|
|
},
|
|
})
|
|
if err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return domain.Tribe{}, fmt.Errorf("TribeRepository.List: %w", err)
|
|
}
|
|
if len(tribes) == 0 {
|
|
err = domain.TribeNotFoundError{
|
|
ID: id,
|
|
}
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
return domain.Tribe{}, err
|
|
}
|
|
|
|
return tribes[0], nil
|
|
}
|