Refactor series handling in backend to include player context in Toss, Ban, Pick, and Record actions. Update service methods to accept player parameters, enhancing authorization checks and ensuring proper team actions. Modify integration tests to reflect new method signatures and improve tournament hydration logic in the store. Enhance frontend components to support new series features and improve user experience with live match navigation.
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / compose (push) Has been cancelled

This commit is contained in:
2026-07-19 03:22:32 +03:00
parent 4b8218ec14
commit 5d3e66d2f1
16 changed files with 449 additions and 62 deletions

View File

@@ -148,9 +148,7 @@ func (s *Service) CreateEvent(ctx context.Context, actor domain.Account, event d
if !actor.IsStaff() {
return domain.Event{}, domain.ErrForbidden
}
if event.RegistrationDeadline.IsZero() {
event.RegistrationDeadline = event.StartsAt
}
event.RegistrationDeadline = event.StartsAt
event.ID, event.CreatedBy, event.CreatedAt, event.UpdatedAt = NewID(), actor.ID, s.Now(), s.Now()
event.State, event.Version = domain.RegistrationOpen, 0
if event.RulesetID == "" {
@@ -174,9 +172,7 @@ func (s *Service) UpdateEvent(ctx context.Context, actor domain.Account, eventID
if err != nil {
return domain.Event{}, err
}
if changes.RegistrationDeadline.IsZero() {
changes.RegistrationDeadline = changes.StartsAt
}
changes.RegistrationDeadline = changes.StartsAt
current.Name = changes.Name
current.Description = changes.Description
current.StartsAt = changes.StartsAt

View File

@@ -518,32 +518,32 @@ func (s *Service) StartScrim(ctx context.Context, actor domain.Account, eventID
return result, err
}
func (s *Service) TossSeriesCoin(ctx context.Context, actor domain.Account, seriesID, seed string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.coin_tossed", func(series *domain.Series, rules domain.Ruleset) error {
func (s *Service) TossSeriesCoin(ctx context.Context, actor domain.Account, player domain.Player, seriesID, seed, actingTeamID string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, player, seriesID, actingTeamID, expectedVersion, "series.coin_tossed", func(series *domain.Series, rules domain.Ruleset) error {
return series.Toss(seed, actor.ID, s.Now(), rules)
})
}
func (s *Service) BanSeriesMap(ctx context.Context, actor domain.Account, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_banned", func(series *domain.Series, rules domain.Ruleset) error {
func (s *Service) BanSeriesMap(ctx context.Context, actor domain.Account, player domain.Player, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, player, seriesID, teamID, expectedVersion, "series.map_banned", func(series *domain.Series, rules domain.Ruleset) error {
return series.BanMap(teamID, name, actor.ID, s.Now(), rules)
})
}
func (s *Service) PickSeriesMap(ctx context.Context, actor domain.Account, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_picked", func(series *domain.Series, rules domain.Ruleset) error {
func (s *Service) PickSeriesMap(ctx context.Context, actor domain.Account, player domain.Player, seriesID, teamID, name string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, player, seriesID, teamID, expectedVersion, "series.map_picked", func(series *domain.Series, rules domain.Ruleset) error {
return series.PickMap(teamID, name, actor.ID, s.Now(), rules)
})
}
func (s *Service) BanSeriesHero(ctx context.Context, actor domain.Account, seriesID, teamID, hero string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.hero_banned", func(series *domain.Series, _ domain.Ruleset) error {
func (s *Service) BanSeriesHero(ctx context.Context, actor domain.Account, player domain.Player, seriesID, teamID, hero string, expectedVersion int) (domain.Series, error) {
return s.mutateSeries(ctx, actor, player, seriesID, teamID, expectedVersion, "series.hero_banned", func(series *domain.Series, _ domain.Ruleset) error {
return series.BanHero(teamID, hero, actor.ID, s.Now())
})
}
func (s *Service) RecordSeriesResult(ctx context.Context, actor domain.Account, seriesID string, outcome domain.MapOutcome, expectedVersion int) (domain.Series, error) {
out, err := s.mutateSeries(ctx, actor, seriesID, expectedVersion, "series.map_recorded", func(series *domain.Series, rules domain.Ruleset) error {
func (s *Service) RecordSeriesResult(ctx context.Context, actor domain.Account, player domain.Player, seriesID, actingTeamID string, outcome domain.MapOutcome, expectedVersion int) (domain.Series, error) {
out, err := s.mutateSeries(ctx, actor, player, seriesID, actingTeamID, expectedVersion, "series.map_recorded", func(series *domain.Series, rules domain.Ruleset) error {
return series.RecordCurrentMap(outcome, actor.ID, s.Now(), rules)
})
if err != nil || out.WinnerTeamID == "" {
@@ -610,7 +610,7 @@ func (s *Service) RecordSeriesResult(ctx context.Context, actor domain.Account,
return out, err
}
func (s *Service) mutateSeries(ctx context.Context, actor domain.Account, seriesID string, expectedVersion int, action string, mutation func(*domain.Series, domain.Ruleset) error) (domain.Series, error) {
func (s *Service) mutateSeries(ctx context.Context, actor domain.Account, player domain.Player, seriesID, actingTeamID string, expectedVersion int, action string, mutation func(*domain.Series, domain.Ruleset) error) (domain.Series, error) {
series, err := s.Store.GetSeries(ctx, seriesID)
if err != nil {
return series, err
@@ -622,6 +622,23 @@ func (s *Service) mutateSeries(ctx context.Context, actor domain.Account, series
if event.State != domain.Live {
return series, fmt.Errorf("%w: event is not live", domain.ErrConflict)
}
if actingTeamID != series.TeamAID && actingTeamID != series.TeamBID {
return series, domain.ErrForbidden
}
teams, err := s.Store.ListTeams(ctx, series.EventID)
if err != nil {
return series, err
}
authorized := actor.IsStaff()
for _, team := range teams {
if team.ID == actingTeamID && CanActForTeam(actor, player, team) {
authorized = true
break
}
}
if !authorized {
return series, domain.ErrForbidden
}
if series.Version != expectedVersion {
return series, fmt.Errorf("%w: stale series version", domain.ErrConflict)
}
@@ -636,6 +653,10 @@ func (s *Service) mutateSeries(ctx context.Context, actor domain.Account, series
if err == nil {
_ = s.Store.AppendAudit(ctx, actor.ID, action, seriesID, series)
s.Bus.Publish("series:"+seriesID, series)
if series.TournamentID != "" {
s.Bus.Publish("tournament:"+series.TournamentID, series)
}
s.Bus.Publish("event:"+series.EventID, series)
}
return series, err
}