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 }