core/internal/app/service_player.go

222 lines
5.8 KiB
Go

package app
import (
"context"
"fmt"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
)
type PlayerRepository interface {
CreateOrUpdate(ctx context.Context, params ...domain.CreatePlayerParams) error
List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error)
ListWithRelations(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersWithRelationsResult, error)
// Delete marks players with the given serverKey and ids as deleted (sets deleted at to now).
// In addition, Delete sets TribeID to null.
//
// https://en.wiktionary.org/wiki/soft_deletion
Delete(ctx context.Context, serverKey string, ids ...int) error
}
type PlayerService struct {
repo PlayerRepository
tribeChangeSvc *TribeChangeService
twSvc TWService
pub PlayerPublisher
}
func NewPlayerService(
repo PlayerRepository,
tribeChangeSvc *TribeChangeService,
twSvc TWService,
pub PlayerPublisher,
) *PlayerService {
return &PlayerService{repo: repo, tribeChangeSvc: tribeChangeSvc, twSvc: twSvc, pub: pub}
}
func (svc *PlayerService) Sync(ctx context.Context, serverSyncedPayload domain.ServerSyncedEventPayload) error {
serverKey := serverSyncedPayload.Key()
serverURL := serverSyncedPayload.URL()
players, err := svc.twSvc.GetPlayers(ctx, serverURL)
if err != nil {
return fmt.Errorf("%s: couldn't get players: %w", serverKey, err)
}
if err = svc.createOrUpdate(ctx, serverKey, players); err != nil {
return fmt.Errorf("%s: couldn't create/update players: %w", serverKey, err)
}
if err = svc.delete(ctx, serverKey, players); err != nil {
return fmt.Errorf("%s: couldn't delete players: %w", serverKey, err)
}
payload, err := domain.NewPlayersSyncedEventPayload(
serverKey,
serverURL,
serverSyncedPayload.VersionCode(),
len(players),
)
if err != nil {
return fmt.Errorf("%s: couldn't construct domain.PlayersSyncedEventPayload: %w", serverKey, err)
}
if err = svc.pub.EventSynced(ctx, payload); err != nil {
return fmt.Errorf("%s: %w", serverKey, err)
}
return nil
}
const playerCreateOrUpdateChunkSize = domain.PlayerListMaxLimit
func (svc *PlayerService) createOrUpdate(ctx context.Context, serverKey string, players domain.BasePlayers) error {
for i := 0; i < len(players); i += playerCreateOrUpdateChunkSize {
end := i + playerCreateOrUpdateChunkSize
if end > len(players) {
end = len(players)
}
if err := svc.createOrUpdateChunk(ctx, serverKey, players[i:end]); err != nil {
return err
}
}
return nil
}
func (svc *PlayerService) createOrUpdateChunk(ctx context.Context, serverKey string, players domain.BasePlayers) error {
ids := make([]int, 0, len(players))
for _, p := range players {
ids = append(ids, p.ID())
}
listParams := domain.NewListPlayersParams()
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
return err
}
if err := listParams.SetIDs(ids); err != nil {
return err
}
if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
return err
}
res, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
storedPlayers := res.Players()
createParams, err := domain.NewCreatePlayerParams(serverKey, players, storedPlayers)
if err != nil {
return err
}
tribeChangesParams, err := domain.NewCreateTribeChangeParamsFromPlayers(serverKey, players, storedPlayers)
if err != nil {
return err
}
if err = svc.repo.CreateOrUpdate(ctx, createParams...); err != nil {
return err
}
return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
}
//nolint:gocyclo
func (svc *PlayerService) delete(ctx context.Context, serverKey string, players domain.BasePlayers) error {
listParams := domain.NewListPlayersParams()
if err := listParams.SetServerKeys([]string{serverKey}); err != nil {
return err
}
if err := listParams.SetDeleted(domain.NullBool{
V: false,
Valid: true,
}); err != nil {
return err
}
if err := listParams.SetSort([]domain.PlayerSort{domain.PlayerSortIDASC}); err != nil {
return err
}
if err := listParams.SetLimit(domain.PlayerListMaxLimit); err != nil {
return err
}
var toDelete []int
var tribeChangesParams []domain.CreateTribeChangeParams
for {
res, err := svc.repo.List(ctx, listParams)
if err != nil {
return err
}
ids, params, err := res.Players().Delete(serverKey, players)
if err != nil {
return err
}
toDelete = append(toDelete, ids...)
tribeChangesParams = append(tribeChangesParams, params...)
if res.Next().IsZero() {
break
}
if err = listParams.SetCursor(res.Next()); err != nil {
return err
}
}
if err := svc.repo.Delete(ctx, serverKey, toDelete...); err != nil {
return err
}
return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...)
}
func (svc *PlayerService) List(ctx context.Context, params domain.ListPlayersParams) (domain.ListPlayersResult, error) {
return svc.repo.List(ctx, params)
}
func (svc *PlayerService) ListWithRelations(
ctx context.Context,
params domain.ListPlayersParams,
) (domain.ListPlayersWithRelationsResult, error) {
return svc.repo.ListWithRelations(ctx, params)
}
func (svc *PlayerService) GetWithRelations(
ctx context.Context,
id int,
serverKey string,
) (domain.PlayerWithRelations, error) {
params := domain.NewListPlayersParams()
if err := params.SetIDs([]int{id}); err != nil {
return domain.PlayerWithRelations{}, err
}
if err := params.SetServerKeys([]string{serverKey}); err != nil {
return domain.PlayerWithRelations{}, err
}
res, err := svc.repo.ListWithRelations(ctx, params)
if err != nil {
return domain.PlayerWithRelations{}, err
}
players := res.Players()
if len(players) == 0 {
return domain.PlayerWithRelations{}, domain.PlayerNotFoundError{
ID: id,
ServerKey: serverKey,
}
}
return players[0], nil
}