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.Players, 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) } playersSyncedPayload, 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, playersSyncedPayload); 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 } storedPlayers, err := svc.repo.List(ctx, listParams) if err != nil { return err } 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{ Value: 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 { storedPlayers, err := svc.repo.List(ctx, listParams) if err != nil { return err } if len(storedPlayers) == 0 { break } ids, params, err := storedPlayers.Delete(serverKey, players) if err != nil { return err } toDelete = append(toDelete, ids...) tribeChangesParams = append(tribeChangesParams, params...) if err = listParams.SetIDGT(domain.NullInt{ Value: storedPlayers[len(storedPlayers)-1].ID(), Valid: true, }); err != nil { return err } } if err := svc.repo.Delete(ctx, serverKey, toDelete...); err != nil { return err } return svc.tribeChangeSvc.Create(ctx, tribeChangesParams...) }