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.
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 02:53:17 +03:00
parent 2d7be8b9cd
commit 4b8218ec14
4 changed files with 19 additions and 16 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 })
})
})

View File

@@ -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 })),
}