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/player.go
Dawid Wysokiński 0aed6f8fed
All checks were successful
continuous-integration/drone/push Build is passing
refactor: base models (#106)
Reviewed-on: twhelp/core#106
2022-10-20 04:16:37 +00:00

244 lines
6.3 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 (
playerChunkSize = 1000
playerMaxLimit = 200
playerSortMaxLen = 3
)
//counterfeiter:generate -o internal/mock/player_repository.gen.go . PlayerRepository
type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
DeleteByID(ctx context.Context, ids ...int64) error
List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error)
}
//counterfeiter:generate -o internal/mock/player_getter.gen.go . PlayerGetter
type PlayerGetter interface {
GetPlayers(ctx context.Context, baseURL string) ([]tw.Player, error)
}
type Player struct {
repo PlayerRepository
client PlayerGetter
}
func NewPlayer(repo PlayerRepository, client PlayerGetter) *Player {
return &Player{repo: repo, client: client}
}
func (p *Player) Refresh(ctx context.Context, key, url string) (int64, error) {
ctx, span := tracer.Start(ctx, "Player.Refresh", trace.WithAttributes(
attribute.String("server.key", key),
attribute.String("server.url", url),
))
defer span.End()
players, err := p.client.GetPlayers(ctx, url)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return 0, fmt.Errorf("TWClient.GetPlayers: %w", err)
}
if err := p.createOrUpdate(ctx, key, players); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return 0, err
}
if err := p.delete(ctx, key, players); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return 0, err
}
return int64(len(players)), nil
}
func (p *Player) createOrUpdate(ctx context.Context, key string, players []tw.Player) error {
ctx, span := tracer.Start(ctx, "Player.createOrUpdate", trace.WithAttributes(
attribute.String("server.key", key),
))
defer span.End()
for i := 0; i < len(players); i += playerChunkSize {
end := i + playerChunkSize
if end > len(players) {
end = len(players)
}
chunk := players[i:end]
params := make([]domain.CreatePlayerParams, 0, len(chunk))
for _, player := range chunk {
params = append(params, domain.CreatePlayerParams{
OpponentsDefeated: domain.OpponentsDefeated(player.OpponentsDefeated),
ID: player.ID,
Name: player.Name,
NumVillages: player.NumVillages,
Points: player.Points,
Rank: player.Rank,
TribeID: player.TribeID,
ProfileURL: player.ProfileURL,
ServerKey: key,
})
}
if err := p.repo.CreateOrUpdate(ctx, params...); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return fmt.Errorf("PlayerRepository.CreateOrUpdate: %w", err)
}
}
return nil
}
func (p *Player) delete(ctx context.Context, key string, existing []tw.Player) error {
ctx, span := tracer.Start(ctx, "Player.delete", trace.WithAttributes(
attribute.String("server.key", key),
))
defer span.End()
//nolint:prealloc
var playersToDelete []int64
var lastID int64
for {
players, _, err := p.repo.List(ctx, domain.ListPlayersParams{
ServerKeys: []string{key},
IDGT: domain.NullInt64{
Int64: lastID,
Valid: true,
},
Deleted: domain.NullBool{
Valid: true,
Bool: false,
},
Pagination: domain.Pagination{
Limit: playerChunkSize,
},
Sort: []domain.PlayerSort{
{By: domain.PlayerSortByID, Direction: domain.SortDirectionASC},
},
})
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return fmt.Errorf("PlayerRepository.List: %w", err)
}
for _, player := range players {
i := sort.Search(len(existing), func(i int) bool {
return existing[i].ID >= player.ID
})
if i < len(existing) && existing[i].ID == player.ID {
continue
}
playersToDelete = append(playersToDelete, player.ID)
}
if len(players) < playerChunkSize {
break
}
lastID = players[len(players)-1].ID
}
if err := p.repo.DeleteByID(ctx, playersToDelete...); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return fmt.Errorf("PlayerRepository.DeleteByID: %w", err)
}
return nil
}
func (p *Player) List(ctx context.Context, params domain.ListPlayersParams) ([]domain.Player, int64, error) {
ctx, span := tracer.Start(ctx, "Player.List")
defer span.End()
if len(params.Sort) == 0 {
params.Sort = []domain.PlayerSort{
{
By: domain.PlayerSortByID,
Direction: domain.SortDirectionASC,
},
}
}
if len(params.Sort) > playerSortMaxLen {
err := domain.ValidationError{
Field: "sort",
Err: domain.MaxLengthError{
Max: playerSortMaxLen,
},
}
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, 0, err
}
if params.Pagination.Limit == 0 {
params.Pagination.Limit = playerMaxLimit
}
if err := validatePagination(params.Pagination, playerMaxLimit); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, 0, fmt.Errorf("validatePagination: %w", err)
}
players, count, err := p.repo.List(ctx, params)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, 0, fmt.Errorf("PlayerRepository.List: %w", err)
}
return players, count, nil
}
func (p *Player) GetByServerKeyAndID(ctx context.Context, serverKey string, id int64, includeTribe bool) (domain.Player, error) {
ctx, span := tracer.Start(ctx, "Player.GetByServerKeyAndID", trace.WithAttributes(
attribute.String("server.key", serverKey),
attribute.Int64("player.id", id),
))
defer span.End()
players, _, err := p.repo.List(ctx, domain.ListPlayersParams{
IDs: []int64{id},
ServerKeys: []string{serverKey},
IncludeTribe: includeTribe,
Pagination: domain.Pagination{
Limit: 1,
},
})
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return domain.Player{}, fmt.Errorf("PlayerRepository.List: %w", err)
}
if len(players) == 0 {
err = domain.PlayerNotFoundError{
ID: id,
}
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return domain.Player{}, err
}
return players[0], nil
}