package httpapi import "git.yeij.top/AskaEth/Scoreboard/server/internal/domain" // 本文件是 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"` } type athleteDTO struct { ID int64 `json:"id"` Name string `json:"name"` Gender *string `json:"gender"` TeamID int64 `json:"teamId"` EmployeeNo *string `json:"employeeNo"` 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"` } 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"` 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"` 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"` } 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 standingRowDTO struct { TeamID int64 `json:"teamId"` TeamCode string `json:"teamCode"` TeamName string `json:"teamName"` ShortName *string `json:"shortName"` Color *string `json:"color"` LogoPath *string `json:"logoPath"` Rank int `json:"rank"` Gold int `json:"gold"` Silver int `json:"silver"` Bronze int `json:"bronze"` MedalTotal int `json:"medalTotal"` TotalPoints float64 `json:"totalPoints"` RankChange *int `json:"rankChange"` } 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"` 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"` } 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"` } type backupResultDTO struct { Path string `json:"path"` CreatedAt string `json:"createdAt"` } // eventSettingsDTO 是 GET/PUT /api/settings 的响应;服务端把 settings 表的点号键聚合成嵌套对象。 type eventSettingsDTO struct { Event eventSettingsEvent `json:"event"` Display eventSettingsDisplay `json:"display"` Standings eventSettingsStandings `json:"standings"` DefaultProfileID *int64 `json:"defaultProfileId"` RequireApproval bool `json:"requireApproval"` } type eventSettingsEvent struct { Name string `json:"name"` Subtitle *string `json:"subtitle"` Host *string `json:"host"` LogoPath *string `json:"logoPath"` PrimaryColor *string `json:"primaryColor"` } type eventSettingsDisplay struct { Background *string `json:"background"` Logo *string `json:"logo"` Title string `json:"title"` Subtitle *string `json:"subtitle"` PrimaryColor *string `json:"primaryColor"` RotateSecond int `json:"rotateSeconds"` } type eventSettingsStandings struct { IndividualToTeamWeight float64 `json:"individualToTeamWeight"` RankingMode string `json:"rankingMode"` ShowIndividualBoard bool `json:"showIndividualBoard"` } // ---------- 用户管理 ---------- 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 standingRowsDTO(rows []domain.StandingsRow, teams map[int64]teamDTO) []standingRowDTO { out := make([]standingRowDTO, 0, len(rows)) for _, r := range rows { t := teams[r.TeamID] dto := standingRowDTO{ TeamID: r.TeamID, TeamCode: r.Code, TeamName: r.Name, ShortName: nilIfEmpty(r.ShortName), Rank: r.Rank, Gold: r.Gold, Silver: r.Silver, Bronze: r.Bronze, MedalTotal: r.Medals, TotalPoints: r.Points, RankChange: nil, } if t.ID != 0 { dto.Color = t.Color dto.LogoPath = t.LogoPath if dto.TeamCode == "" { dto.TeamCode = t.Code } if dto.TeamName == "" { dto.TeamName = t.Name } if dto.ShortName == nil { dto.ShortName = t.ShortName } } out = append(out, dto) } return out } func nilIfEmpty(s string) *string { if s == "" { return nil } return &s }