From 4b8218ec1426f104ea2bd53d3a20372b9c502dc8 Mon Sep 17 00:00:00 2001 From: lemintare Date: Sun, 19 Jul 2026 02:53:17 +0300 Subject: [PATCH] Enhance draft structures in backend models by adding new fields for banned actions and audit logs. Update frontend API interfaces to accommodate nullable properties for drafts and results, ensuring better handling of optional data. Improve series adaptation logic to manage null values effectively. --- backend/internal/domain/model.go | 4 ++-- backend/internal/domain/series_fsm.go | 3 ++- frontend/src/api/client.test.ts | 1 + frontend/src/api/client.ts | 27 ++++++++++++++------------- 4 files changed, 19 insertions(+), 16 deletions(-) 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 })), }