diff --git a/backend/internal/domain/model.go b/backend/internal/domain/model.go index 5873e88..56f09b0 100644 --- a/backend/internal/domain/model.go +++ b/backend/internal/domain/model.go @@ -264,7 +264,7 @@ func NewMapDraft(pool []string, firstTeam string, teams [2]string) (*MapDraft, e if len(pool) < 1 || teams[0] == teams[1] || (firstTeam != teams[0] && firstTeam != teams[1]) { return nil, fmt.Errorf("%w: invalid map draft", ErrInvalid) } - return &MapDraft{Pool: slices.Clone(pool), FirstTeamID: firstTeam, TeamIDs: teams}, nil + return &MapDraft{Pool: slices.Clone(pool), Banned: []string{}, Actions: []DraftAction{}, FirstTeamID: firstTeam, TeamIDs: teams}, nil } func (d *MapDraft) NextTeam() string { @@ -321,7 +321,7 @@ func NewHeroDraft(heroes []Hero, teams [2]string, first string, count int, previ if previous == nil { previous = map[string][]string{} } - return &HeroDraft{Heroes: slices.Clone(heroes), TeamIDs: teams, FirstTeamID: first, BansPerTeam: count, SeriesBans: previous, CurrentRoles: map[string][]Role{}}, nil + return &HeroDraft{Heroes: slices.Clone(heroes), TeamIDs: teams, FirstTeamID: first, BansPerTeam: count, SeriesBans: previous, CurrentBans: []DraftAction{}, CurrentRoles: map[string][]Role{}}, nil } func (d *HeroDraft) NextTeam() string { diff --git a/backend/internal/domain/series_fsm.go b/backend/internal/domain/series_fsm.go index 794428b..7050d8b 100644 --- a/backend/internal/domain/series_fsm.go +++ b/backend/internal/domain/series_fsm.go @@ -43,7 +43,8 @@ func NewSeries(id, eventID, tournamentID string, teams [2]string, rules Ruleset) ID: id, EventID: eventID, TournamentID: tournamentID, TeamAID: teams[0], TeamBID: teams[1], BestOf: rules.BestOf, RulesetID: rules.ID, Phase: CoinTossPending, - SeriesBans: map[string][]string{}, + PlayedMaps: []string{}, AvailableMaps: []string{}, Maps: []SeriesMap{}, + SeriesBans: map[string][]string{}, Audit: []SeriesAction{}, Results: []MapResult{}, }, nil } diff --git a/frontend/src/api/client.test.ts b/frontend/src/api/client.test.ts index 9b8f8f9..63542e2 100644 --- a/frontend/src/api/client.test.ts +++ b/frontend/src/api/client.test.ts @@ -78,6 +78,7 @@ describe('backend DTO adapters', () => { ], } expect(adaptSeries(series).score).toEqual({ alpha: 0, beta: 1 }) + expect(adaptSeries({ ...series, phase: 'CoinTossPending', results: null, maps: null, audit: null }).score).toEqual({ alpha: 0, beta: 0 }) }) }) diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index bf3895e..0bfb496 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -161,12 +161,12 @@ export interface RawSeries { winnerTeamId: string; bestOf: number; rulesetId: string phase: 'CoinTossPending' | 'MapBan' | 'MapPick' | 'HeroBan' | 'Playing' | 'Completed' coinToss?: { seed: string; winnerTeamId: string; performedAt: string } - mapDraft?: { pool: string[]; banned: string[]; firstTeamId: string; teamIds: [string, string]; actions: RawDraftAction[] } - heroDraft?: { heroes: Array<{ name: string; role: 'Tank' | 'Damage' | 'Support' }>; teamIds: [string, string]; firstTeamId: string; bansPerTeam: number; currentBans: RawDraftAction[] } - currentMap: string; playedMaps: string[]; nextMapPickerId: string; availableMaps: string[] - maps: Array<{ number: number; name: string; heroBans: RawDraftAction[]; result?: RawMapResult }> - audit: Array<{ kind: DraftAction['kind']; summary: string; teamId?: string; actorAccountId: string; at: string }> - results: RawMapResult[]; version: number + mapDraft?: { pool: string[] | null; banned: string[] | null; firstTeamId: string; teamIds: [string, string]; actions: RawDraftAction[] | null } + heroDraft?: { heroes: Array<{ name: string; role: 'Tank' | 'Damage' | 'Support' }> | null; teamIds: [string, string]; firstTeamId: string; bansPerTeam: number; currentBans: RawDraftAction[] | null } + currentMap: string; playedMaps: string[] | null; nextMapPickerId: string; availableMaps: string[] | null + maps: Array<{ number: number; name: string; heroBans: RawDraftAction[] | null; result?: RawMapResult }> | null + audit: Array<{ kind: DraftAction['kind']; summary: string; teamId?: string; actorAccountId: string; at: string }> | null + results: RawMapResult[] | null; version: number } export interface RawDraftAction { teamId: string; value: string; actorAccountId: string; at: string } export interface RawTournament { @@ -317,8 +317,9 @@ export function adaptBalance(raw: RawBalanceCandidate, players: Player[], index: export function adaptSeries(raw: RawSeries, teams: Team[] = []): Series { const alpha = teams.find((team) => team.id === raw.teamAId) ?? emptyTeam(raw.teamAId, 'alpha') const beta = teams.find((team) => team.id === raw.teamBId) ?? emptyTeam(raw.teamBId, 'beta') - const activeResults = raw.results.filter((_, index) => - !raw.results.some((correction) => correction.correctionOf === index + 1), + const results = raw.results ?? [] + const activeResults = results.filter((_, index) => + !results.some((correction) => correction.correctionOf === index + 1), ) const score = activeResults.reduce((total, result) => ({ alpha: total.alpha + (result.outcome === 'TeamAWin' ? 1 : 0), @@ -331,20 +332,20 @@ export function adaptSeries(raw: RawSeries, teams: Team[] = []): Series { : phase === 'HeroBan' ? 'hero_draft' : phase === 'Completed' ? 'completed' : 'playing' const nextMapTeam = raw.mapDraft - ? (raw.mapDraft.actions.length % 2 === 0 ? raw.mapDraft.firstTeamId : raw.mapDraft.teamIds.find((id) => id !== raw.mapDraft?.firstTeamId)) + ? ((raw.mapDraft.actions ?? []).length % 2 === 0 ? raw.mapDraft.firstTeamId : raw.mapDraft.teamIds.find((id) => id !== raw.mapDraft?.firstTeamId)) : undefined const nextHeroTeam = raw.heroDraft - ? (raw.heroDraft.currentBans.length % 2 === 0 ? raw.heroDraft.firstTeamId : raw.heroDraft.teamIds.find((id) => id !== raw.heroDraft?.firstTeamId)) + ? ((raw.heroDraft.currentBans ?? []).length % 2 === 0 ? raw.heroDraft.firstTeamId : raw.heroDraft.teamIds.find((id) => id !== raw.heroDraft?.firstTeamId)) : undefined const activeTeamId = phase === 'MapBan' ? nextMapTeam : phase === 'MapPick' ? raw.nextMapPickerId : phase === 'HeroBan' ? nextHeroTeam : undefined const kind: Series['currentStep']['kind'] = phase === 'CoinTossPending' ? 'coin_toss' : phase === 'MapBan' ? 'map_ban' : phase === 'MapPick' ? 'map_pick' : phase === 'HeroBan' ? 'hero_ban' : phase === 'Completed' ? 'complete' : 'result' const options: DraftOption[] = phase === 'MapBan' && raw.mapDraft - ? raw.mapDraft.pool.filter((name) => !raw.mapDraft?.banned.includes(name)).map((name) => ({ id: name, name })) + ? (raw.mapDraft.pool ?? []).filter((name) => !(raw.mapDraft?.banned ?? []).includes(name)).map((name) => ({ id: name, name })) : phase === 'MapPick' ? (raw.availableMaps ?? []).map((name) => ({ id: name, name })) : phase === 'HeroBan' && raw.heroDraft - ? raw.heroDraft.heroes.filter((hero) => !raw.heroDraft?.currentBans.some((ban) => ban.value === hero.name)).map((hero) => ({ id: hero.name, name: hero.name, role: roleFromRaw(hero.role) })) + ? (raw.heroDraft.heroes ?? []).filter((hero) => !(raw.heroDraft?.currentBans ?? []).some((ban) => ban.value === hero.name)).map((hero) => ({ id: hero.name, name: hero.name, role: roleFromRaw(hero.role) })) : [] return { id: raw.id, @@ -369,7 +370,7 @@ export function adaptSeries(raw: RawSeries, teams: Team[] = []): Series { mode: 'Map', status: map.result ? 'completed' : phase === 'Playing' ? 'playing' : phase === 'HeroBan' ? 'ready' : 'drafting', winner: map.result ? (map.result.outcome === 'TeamAWin' ? 'alpha' : map.result.outcome === 'TeamBWin' ? 'beta' : 'draw') : undefined, - heroBans: map.heroBans.map((ban) => ({ hero: ban.value, team: ban.teamId === raw.teamAId ? 'alpha' : 'beta', role: 'damage' as PlayerRole })), + heroBans: (map.heroBans ?? []).map((ban) => ({ hero: ban.value, team: ban.teamId === raw.teamAId ? 'alpha' : 'beta', role: 'damage' as PlayerRole })), })), audit: (raw.audit ?? []).map((action, index) => ({ id: `action-${index}`, kind: action.kind, summary: action.summary, actorName: action.actorAccountId, createdAt: action.at })), }