package app import ( "context" "fmt" "gitea.dwysokinski.me/twhelp/corev3/internal/domain" ) type TribeRepository interface { CreateOrUpdate(ctx context.Context, params ...domain.CreateTribeParams) error UpdateDominance(ctx context.Context, serverKey string, numPlayerVillages int) error List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error) // Delete marks players with the given serverKey and ids as deleted (sets deleted at to now). // // https://en.wiktionary.org/wiki/soft_deletion Delete(ctx context.Context, serverKey string, ids ...int) error } type TribeService struct { repo TribeRepository twSvc TWService pub TribePublisher } func NewTribeService(repo TribeRepository, twSvc TWService, pub TribePublisher) *TribeService { return &TribeService{repo: repo, twSvc: twSvc, pub: pub} } func (svc *TribeService) Sync(ctx context.Context, serverSyncedPayload domain.ServerSyncedEventPayload) error { serverKey := serverSyncedPayload.Key() serverURL := serverSyncedPayload.URL() tribes, err := svc.twSvc.GetTribes(ctx, serverURL) if err != nil { return fmt.Errorf("%s: couldn't get tribes: %w", serverKey, err) } if err = svc.createOrUpdate(ctx, serverKey, tribes); err != nil { return fmt.Errorf("%s: couldn't create/update tribes: %w", serverKey, err) } if err = svc.delete(ctx, serverKey, tribes); err != nil { return fmt.Errorf("%s: couldn't delete tribes: %w", serverKey, err) } payload, err := domain.NewTribesSyncedEventPayload( serverKey, serverURL, serverSyncedPayload.VersionCode(), len(tribes), ) if err != nil { return fmt.Errorf("%s: couldn't construct domain.TribesSyncedEventPayload: %w", serverKey, err) } if err = svc.pub.EventSynced(ctx, payload); err != nil { return fmt.Errorf("%s: %w", serverKey, err) } return nil } const tribeCreateOrUpdateChunkSize = domain.TribeListMaxLimit func (svc *TribeService) createOrUpdate(ctx context.Context, serverKey string, tribes domain.BaseTribes) error { for i := 0; i < len(tribes); i += tribeCreateOrUpdateChunkSize { end := i + tribeCreateOrUpdateChunkSize if end > len(tribes) { end = len(tribes) } if err := svc.createOrUpdateChunk(ctx, serverKey, tribes[i:end]); err != nil { return err } } return nil } func (svc *TribeService) createOrUpdateChunk(ctx context.Context, serverKey string, tribes domain.BaseTribes) error { ids := make([]int, 0, len(tribes)) for _, t := range tribes { ids = append(ids, t.ID()) } listParams := domain.NewListTribesParams() if err := listParams.SetServerKeys([]string{serverKey}); err != nil { return err } if err := listParams.SetIDs(ids); err != nil { return err } if err := listParams.SetSort([]domain.TribeSort{domain.TribeSortIDASC}); err != nil { return err } if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil { return err } res, err := svc.repo.List(ctx, listParams) if err != nil { return err } createParams, err := domain.NewCreateTribeParams(serverKey, tribes, res.Tribes()) if err != nil { return err } return svc.repo.CreateOrUpdate(ctx, createParams...) } func (svc *TribeService) delete(ctx context.Context, serverKey string, tribes domain.BaseTribes) error { listParams := domain.NewListTribesParams() 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.TribeSort{domain.TribeSortIDASC}); err != nil { return err } if err := listParams.SetLimit(domain.TribeListMaxLimit); err != nil { return err } var toDelete []int for { res, err := svc.repo.List(ctx, listParams) if err != nil { return err } toDelete = append(toDelete, res.Tribes().Delete(serverKey, tribes)...) if res.Next().IsZero() { break } if err = listParams.SetCursor(res.Next()); err != nil { return err } } return svc.repo.Delete(ctx, serverKey, toDelete...) } func (svc *TribeService) UpdateDominance(ctx context.Context, payload domain.VillagesSyncedEventPayload) error { return svc.repo.UpdateDominance(ctx, payload.ServerKey(), payload.NumPlayerVillages()) } func (svc *TribeService) List(ctx context.Context, params domain.ListTribesParams) (domain.ListTribesResult, error) { return svc.repo.List(ctx, params) }