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
2022-11-10 12:43:33 +01:00

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
}