package httpapi // 本文件是 REST 的 JSON 契约(camelCase,与 web/src/shared/types.ts 一一对应)。 // 数据库列是 snake_case,转换集中放在 repo.go 的扫描函数里。 type teamDTO struct { ID int64 `json:"id"` Code string `json:"code"` Name string `json:"name"` ShortName *string `json:"shortName"` LogoPath *string `json:"logoPath"` Color *string `json:"color"` SortOrder int `json:"sortOrder"` Active bool `json:"active"` // DeletedAt 非空表示「彻底删除」(对界面不可见),行仍在库里;停用是另一回事(active = false)。 DeletedAt *string `json:"deletedAt"` } type athleteDTO struct { ID int64 `json:"id"` Name string `json:"name"` Gender *string `json:"gender"` TeamID int64 `json:"teamId"` EmployeeNo *string `json:"employeeNo"` IDCard *string `json:"idCard"` Bib *string `json:"bib"` Age *int `json:"age"` Active bool `json:"active"` } type sportDTO struct { ID int64 `json:"id"` Code string `json:"code"` Name string `json:"name"` ShortName *string `json:"shortName"` DefaultProfileID *int64 `json:"defaultProfileId"` SortOrder int `json:"sortOrder"` Active bool `json:"active"` } type categoryDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` Name string `json:"name"` Multiplier float64 `json:"multiplier"` Gender *string `json:"gender"` AgeMin *int `json:"ageMin"` AgeMax *int `json:"ageMax"` SortOrder int `json:"sortOrder"` } type channelDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` Scope string `json:"scope"` Basis string `json:"basis"` ProfileID int64 `json:"profileId"` SortOrder int `json:"sortOrder"` // CountToTeam 决定该通道产出的流水是否计入单位总分;请求体里缺省(null)按 true 处理, // 「个人+团体」项目的个人通道传 false。响应里始终是具体值。 CountToTeam *bool `json:"countToTeam"` // CountToMedals 决定该通道产出的名次是否发奖牌;缺省(null)按 true 处理(与库中默认值一致)。 CountToMedals *bool `json:"countToMedals"` // CountToTeamMedals 决定该通道的个人奖牌是否并入单位奖牌榜;缺省按 true 处理。 // 只对 scope=athlete 的通道有意义:scope=team 的名次本来就记在单位头上。 CountToTeamMedals *bool `json:"countToTeamMedals"` } type eventDTO struct { ID int64 `json:"id"` SportID int64 `json:"sportId"` Code string `json:"code"` Name string `json:"name"` EventType string `json:"eventType"` ResultMode string `json:"resultMode"` ResultType string `json:"resultType"` Compare string `json:"compare"` RecordEligible bool `json:"recordEligible"` Scored bool `json:"scored"` Multiplier float64 `json:"multiplier"` Status string `json:"status"` ScheduledAt *string `json:"scheduledAt"` Venue *string `json:"venue"` SortOrder int `json:"sortOrder"` Remark *string `json:"remark"` AvgMode string `json:"avgMode"` // AwardMedals 决定该小项是否结算奖牌;请求体缺省按 true 处理(与库中默认值一致)。 // 关闭后小项的名次不产生任何奖牌,积分不受影响。 AwardMedals bool `json:"awardMedals"` Categories []categoryDTO `json:"categories,omitempty"` Channels []channelDTO `json:"channels,omitempty"` } type profileRuleDTO struct { Rank int `json:"rank"` Points float64 `json:"points"` } type profileDTO struct { ID int64 `json:"id"` Code string `json:"code"` Name string `json:"name"` TieMode string `json:"tieMode"` RecordBonusMode string `json:"recordBonusMode"` AwardRanks int `json:"awardRanks"` LastPlaceZero bool `json:"lastPlaceZero"` Rules []profileRuleDTO `json:"rules"` } type entryDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` CategoryID int64 `json:"categoryId"` TeamID int64 `json:"teamId"` AthleteID *int64 `json:"athleteId"` MemberNames []string `json:"memberNames"` Bib *string `json:"bib"` Seed *string `json:"seed"` // Heat 是录入 / 展示用的批次号,null 表示「未分批」;批次不参与算分。 Heat *int64 `json:"heat"` Status string `json:"status"` Remark *string `json:"remark"` AthleteName *string `json:"athleteName,omitempty"` TeamCode string `json:"teamCode,omitempty"` TeamName string `json:"teamName,omitempty"` ShortName *string `json:"shortName,omitempty"` CategoryName string `json:"categoryName,omitempty"` } type entryWithResultDTO struct { entryDTO Result *resultDTO `json:"result"` } // entryHeatResultDTO 是批量设置批次的结果:本批条目当前统一的批次号(null = 未分批)。 type entryHeatResultDTO struct { Updated int `json:"updated"` Heat *int64 `json:"heat"` EntryIDs []int64 `json:"entryIds"` } type resultDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` EntryID int64 `json:"entryId"` Rank *int `json:"rank"` ResultText string `json:"resultText"` ResultValue *float64 `json:"resultValue"` Unit string `json:"unit"` IsTie bool `json:"isTie"` PointsOverride *float64 `json:"pointsOverride"` Status string `json:"status"` State string `json:"state"` RecordedBy *int64 `json:"recordedBy"` RecordedAt *string `json:"recordedAt"` VerifiedBy *int64 `json:"verifiedBy"` VerifiedAt *string `json:"verifiedAt"` Note *string `json:"note"` Points *float64 `json:"points,omitempty"` } type athleteStandingDTO struct { Rank int `json:"rank"` AthleteID int64 `json:"athleteId"` AthleteName string `json:"athleteName"` Gender *string `json:"gender"` TeamID int64 `json:"teamId"` TeamCode string `json:"teamCode"` TeamName string `json:"teamName"` ShortName *string `json:"shortName"` LogoPath *string `json:"logoPath"` TotalPoints float64 `json:"totalPoints"` Gold int `json:"gold"` Silver int `json:"silver"` Bronze int `json:"bronze"` } type breakdownItemDTO struct { LedgerID int64 `json:"ledgerId"` SourceType string `json:"sourceType"` EventID *int64 `json:"eventId"` EventCode *string `json:"eventCode"` EventName *string `json:"eventName"` CategoryName *string `json:"categoryName"` AthleteID *int64 `json:"athleteId"` AthleteName *string `json:"athleteName"` Rank *int `json:"rank"` ResultText *string `json:"resultText"` Points float64 `json:"points"` CreatedAt string `json:"createdAt"` } type teamBreakdownDTO struct { TeamID int64 `json:"teamId"` TeamCode string `json:"teamCode"` TeamName string `json:"teamName"` TotalPoints float64 `json:"totalPoints"` Items []breakdownItemDTO `json:"items"` } type adjustmentDTO struct { ID int64 `json:"id"` TeamID int64 `json:"teamId"` Delta float64 `json:"delta"` Category string `json:"category"` Reason string `json:"reason"` EventID *int64 `json:"eventId"` Voided bool `json:"voided"` CreatedBy *int64 `json:"createdBy"` CreatedAt string `json:"createdAt"` } type recordEntryDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` Level string `json:"level"` ResultValue float64 `json:"resultValue"` ResultText string `json:"resultText"` HolderName *string `json:"holderName"` HolderTeamID *int64 `json:"holderTeamId"` AchievedAt *string `json:"achievedAt"` IsCurrent bool `json:"isCurrent"` Source string `json:"source"` } type recordBreakDTO struct { ID int64 `json:"id"` EventID int64 `json:"eventId"` EntryID int64 `json:"entryId"` Level string `json:"level"` ResultValue float64 `json:"resultValue"` ResultText string `json:"resultText"` BonusPoints float64 `json:"bonusPoints"` State string `json:"state"` ApprovedBy *int64 `json:"approvedBy"` ApprovedAt *string `json:"approvedAt"` // Note 是驳回 / 作废这条登记时写下的理由(0021 新增的列);从未被驳回的登记(含 // 老库里的历史行)为 null——空理由与「没有理由」是两回事,所以这里用指针。 Note *string `json:"note"` } type auditLogDTO struct { ID int64 `json:"id"` UserID *int64 `json:"userId"` Action string `json:"action"` EntityType string `json:"entityType"` EntityID *int64 `json:"entityId"` BeforeJSON *string `json:"beforeJson"` AfterJSON *string `json:"afterJson"` IP *string `json:"ip"` RequestID *string `json:"requestId"` CreatedAt string `json:"createdAt"` } type currentUserDTO struct { ID int64 `json:"id"` Username string `json:"username"` DisplayName string `json:"displayName"` Role string `json:"role"` LastLoginAt *string `json:"lastLoginAt"` MustChangePassword bool `json:"mustChangePassword"` // SessionExpiresAt 是当前会话的到期时间(RFC3339 UTC),供界面显示 // 「当前会话将于 … 过期」;未登录时为 null。 SessionExpiresAt *string `json:"sessionExpiresAt"` } type backupResultDTO struct { Path string `json:"path"` CreatedAt string `json:"createdAt"` } // GET/PUT /api/settings 的线上结构见 internal/dto.EventSettings(与 snapshot.settings 共用)。 // ---------- 用户管理 ---------- type userListItemDTO struct { ID int64 `json:"id"` Username string `json:"username"` DisplayName string `json:"displayName"` Role string `json:"role"` Active bool `json:"active"` MustChangePassword bool `json:"mustChangePassword"` LastLoginAt *string `json:"lastLoginAt"` } // resetPasswordResultDTO 里的 password 是唯一一次明文,落库只存哈希。 type resetPasswordResultDTO struct { ID int64 `json:"id"` Username string `json:"username"` Password string `json:"password"` Generated bool `json:"generated"` MustChangePassword bool `json:"mustChangePassword"` } // ---------- 变更历史 ---------- type revisionPageDTO struct { Items []revisionListItemDTO `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } type revisionListItemDTO struct { ID int64 `json:"id"` ParentID *int64 `json:"parentId"` EventID int64 `json:"eventId"` EventName string `json:"eventName"` Action string `json:"action"` ActorID *int64 `json:"actorId"` ActorName string `json:"actorName"` Message *string `json:"message"` CreatedAt string `json:"createdAt"` ChangedCount int `json:"changedCount"` } type revisionDetailDTO struct { revisionListItemDTO Snapshot []resultDTO `json:"snapshot"` Diff revisionDiffDTO `json:"diff"` } type revisionDiffDTO struct { Results []revisionResultChangeDTO `json:"results"` Teams []revisionTeamDeltaDTO `json:"teams"` } type revisionResultChangeDTO struct { ResultID int64 `json:"resultId"` EntryID int64 `json:"entryId"` TeamID *int64 `json:"teamId"` Change string `json:"change"` Fields []string `json:"fields,omitempty"` Before *resultDTO `json:"before"` After *resultDTO `json:"after"` } type revisionTeamDeltaDTO struct { TeamID int64 `json:"teamId"` TeamCode string `json:"teamCode"` TeamName string `json:"teamName"` PointsBefore float64 `json:"pointsBefore"` PointsAfter float64 `json:"pointsAfter"` PointsDelta float64 `json:"pointsDelta"` ChangedResults int `json:"changedResults"` } // ---------- domain → DTO ---------- func nilIfEmpty(s string) *string { if s == "" { return nil } return &s }