package httpapi // 本文件是各写接口的请求体契约;字段名与 web/src/shared/types.ts 对齐。 type loginInput struct { Username string `json:"username"` Password string `json:"password"` RequestID string `json:"requestId"` } type saveDraftPayload struct { EventID int64 `json:"eventId"` CategoryID *int64 `json:"categoryId"` Results []draftResultInput `json:"results"` RequestID string `json:"requestId"` } type draftResultInput struct { EntryID int64 `json:"entryId"` Rank *int `json:"rank"` ResultText *string `json:"resultText"` ResultValue *float64 `json:"resultValue"` Unit *string `json:"unit"` IsTie bool `json:"isTie"` Status string `json:"status"` PointsOverride *float64 `json:"pointsOverride"` Note *string `json:"note"` } type publishPayload struct { EventID int64 `json:"eventId"` CategoryID *int64 `json:"categoryId"` RequestID string `json:"requestId"` } // publishResultsPayload 是 POST /api/results/publish 的请求体:在发布请求体上多一个发布口径。 // // 单独一个类型(而不是把字段塞进 publishPayload)是因为「撤销发布 / 立即结算」没有 // 「按决赛发布」这回事:共用一个结构只会让它们静默吃下一个毫无作用的字段。 type publishResultsPayload struct { publishPayload // RankOnly 为 true 时本次发布只更新名次:写 results.rank,但不产生积分流水、 // 不重建通道名次、不发奖牌、不动榜单分数(名次照旧更新——预赛也要排名次)。 // // 缺省 / false = 计分发布,与改动前逐字节一致:老客户端(不带这个字段)与勾选了 // 「以决赛成绩发布」的新客户端走的是同一条路。 RankOnly bool `json:"rankOnly"` } type voidResultPayload struct { Reason string `json:"reason"` RequestID string `json:"requestId"` } // entryHeatPayload 是批量设置批次的请求体:heat 为 null / 缺省表示清除批次(未分批)。 type entryHeatPayload struct { EntryIDs []int64 `json:"entryIds"` Heat *int64 `json:"heat"` RequestID string `json:"requestId"` } // entryTeamSharesPayload 是 PUT /api/entries/{id}/team-shares 的请求体:整组替换一条报名的份额。 // 不传重量字段(weight 缺省 1)就是均分;medal 缺省 true = 这枚奖牌整枚记给该单位。 // 必须包含主单位(entries.team_id),且每个单位只能出现一次——校验见 validateEntryShares。 type entryTeamSharesPayload struct { TeamShares []entryTeamShareDTO `json:"teamShares"` RequestID string `json:"requestId"` } // entrySquadPayload 是 PUT /api/entries/{id}/squad 的请求体:改一条团体报名的队名。 // 队名是自由文本,服务端只 trim 首尾空白;空串(或只有空白)是**合法值**, // 表示「不区分队伍」,不是「清掉这条报名」(见迁移 0025 与 updateEntrySquad)。 type entrySquadPayload struct { Squad string `json:"squad"` RequestID string `json:"requestId"` } // entryAthletesPayload 是 PUT /api/entries/{id}/athletes 的请求体:整组替换一条报名的队员。 // 空数组是**合法值**(清空队员 = 只报单位,与升级前一致);只认 athleteId。 type entryAthletesPayload struct { Athletes []entryAthleteDTO `json:"athletes"` RequestID string `json:"requestId"` } // entryBatchPayload 是报名项批量操作的请求体: // action 决定语义(scratch 退赛可恢复 / restore 恢复 / remove 物理移出报名), // ids 是勾选的报名项(重复会自动去重),reason 可选、只进审计便于事后对账。 type entryBatchPayload struct { Action string `json:"action"` IDs []int64 `json:"ids"` Reason string `json:"reason"` RequestID string `json:"requestId"` } type adjustmentInput struct { TeamID int64 `json:"teamId"` Delta float64 `json:"delta"` Category string `json:"category"` Reason string `json:"reason"` EventID *int64 `json:"eventId"` RequestID string `json:"requestId"` } type recordBreakInput struct { EventID int64 `json:"eventId"` EntryID int64 `json:"entryId"` Level string `json:"level"` ResultValue float64 `json:"resultValue"` ResultText string `json:"resultText"` RequestID string `json:"requestId"` } // reopenResultPayload 是 POST /api/results/{id}/reopen 的请求体: // 把一条成绩退回草稿让录入员直接改,理由必填(与作废同口径)。 type reopenResultPayload struct { Reason string `json:"reason"` RequestID string `json:"requestId"` } // rejectRecordBreakPayload 是 POST /api/record-breaks/{id}/reject 的请求体: // 驳回 / 作废一条破纪录登记,理由必填并落到 record_breaks.note。 type rejectRecordBreakPayload struct { Reason string `json:"reason"` RequestID string `json:"requestId"` } // recordEntryInput 是 POST /api/records 的请求体:手工登记一条历史纪录。 // // source 只接受 history(缺省即 history):source='current' 是「确认破纪录」写出来的产物, // 客户端造不得——那会让一条没人批准过的成绩看起来像是本届刚破的纪录。 type recordEntryInput struct { EventID int64 `json:"eventId"` Level string `json:"level"` // ResultValue 与 ResultText 至少给一个:纪录可以只有数值(10.5)或只有文本('10秒5')。 ResultValue *float64 `json:"resultValue"` ResultText *string `json:"resultText"` HolderName *string `json:"holderName"` HolderTeamID *int64 `json:"holderTeamId"` AchievedAt *string `json:"achievedAt"` Source string `json:"source"` RequestID string `json:"requestId"` } // displayCommandInput 是大屏控制指令的请求体。 type displayCommandInput struct { Action string `json:"action"` Payload any `json:"payload"` // Targets 是要定向控制的大屏实例 id(GET /api/display/instances 的 id); // 留空表示广播给所有大屏——公告这类全屏指令走的就是广播。 Targets []string `json:"targets"` RequestID string `json:"requestId"` } type voidAdjustmentPayload struct { Reason string `json:"reason"` RequestID string `json:"requestId"` } type settingsPatch struct { Event *settingsEventPatch `json:"event"` Display *settingsDisplayPatch `json:"display"` Standings *settingsStandingsPatch `json:"standings"` DefaultProfileID *int64 `json:"defaultProfileId"` RequireApproval *bool `json:"requireApproval"` RequestID string `json:"requestId"` } type settingsEventPatch struct { Name *string `json:"name"` Subtitle *string `json:"subtitle"` Host *string `json:"host"` LogoPath *string `json:"logoPath"` PrimaryColor *string `json:"primaryColor"` } type settingsDisplayPatch struct { Background *string `json:"background"` Logo *string `json:"logo"` Title *string `json:"title"` Subtitle *string `json:"subtitle"` PrimaryColor *string `json:"primaryColor"` RotateSeconds *float64 `json:"rotateSeconds"` // FontTitle / FontBody 是字体 id;null 表示不改动(清空要传空串)。 FontTitle *string `json:"fontTitle"` FontBody *string `json:"fontBody"` // LiveEvents 是「实时赛况」人工指定的当前项目 id(最多 2 个)。 // nil 表示不改动;传 [] 表示清空人工指定。 LiveEvents *[]int64 `json:"liveEvents"` // LiveSource 是当前项目的判定方式(manual / auto);nil 表示不改动。 LiveSource *string `json:"liveSource"` } type settingsStandingsPatch struct { IndividualToTeamWeight *float64 `json:"individualToTeamWeight"` RankingMode *string `json:"rankingMode"` ShowIndividualBoard *bool `json:"showIndividualBoard"` ShowIndividualMedals *bool `json:"showIndividualMedals"` // LiveOnPublish 是「分批发布时是否实时更新各单位总分与奖牌榜」;nil 表示不改动。 LiveOnPublish *bool `json:"liveOnPublish"` } // sessionSettingsPatch 是 PUT /api/system/session 的请求体: // 只改「登录有效期(小时)」这一个系统级配置。 type sessionSettingsPatch struct { SessionHours *int `json:"sessionHours"` RequestID string `json:"requestId"` } type categoriesPayload struct { Categories []categoryDTO `json:"categories"` } // athletesBatchPayload 是运动员批量动作的请求体:action 决定停用还是彻底删除。 type athletesBatchPayload struct { Action string `json:"action"` IDs []int64 `json:"ids"` // DryRun 只统计不改库:前端用它先看清「会删掉多少、重算哪些小项」再确认。 DryRun bool `json:"dryRun"` } type channelsPayload struct { Channels []channelDTO `json:"channels"` } // teamRanksPayload 是 PUT /api/events/{id}/categories/{cid}/team-ranks 的请求体: // 一次提交某组别某类通道(当前只认 scope=team + basis=manual)的整组团体名次。 // ranks 里的 rank 为 0 表示清空该单位的记录;未出现在 ranks 里的单位保持原值, // 录入页只需提交改动过的行,误传部分数据不会把没提到的单位一并清掉。 type teamRanksPayload struct { Scope string `json:"scope"` Basis string `json:"basis"` Ranks []teamManualRankDTO `json:"ranks"` } // ---------- 用户管理 ---------- type createUserInput struct { Username string `json:"username"` DisplayName string `json:"displayName"` Role string `json:"role"` Password string `json:"password"` RequestID string `json:"requestId"` } // updateUserInput 只带需要改的字段:nil 表示保持原值。 type updateUserInput struct { DisplayName *string `json:"displayName"` Role *string `json:"role"` Active *bool `json:"active"` RequestID string `json:"requestId"` } // resetUserPasswordInput 的 password 留空时由服务端随机生成。 type resetUserPasswordInput struct { Password string `json:"password"` RequestID string `json:"requestId"` } // restoreBackupPayload 是 POST /api/backup/restore 的请求体。 // // File 只接受备份目录里的裸文件名(不接受路径);Kind 是**要覆盖的那份库** // (备份列表里的 dbKind)——安全副本的性质是 safety,但它同样属于某一份库, // 拿它退回上一个时间点是正当操作,所以这里仍只认 system / event。 // 服务端再按文件名校验一遍(对不上直接拒绝):恢复错库的代价是两份数据一起完蛋。 type restoreBackupPayload struct { File string `json:"file"` Kind string `json:"kind"` RequestID string `json:"requestId"` }