Files
MatchLiveTv/native/ios/MatchLiveTv/Data/API/ApiDtos.swift
T
eminuxandCursor 5b723bf844 Aggiunge annunci in-app/sito, contatti e migliora UI copertina sul portale.
Include admin/API/banner nativi, form contatti e reset copertina standard con layout edit più ampio.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 15:30:49 +02:00

561 lines
16 KiB
Swift

import Foundation
struct LoginRequest: Encodable {
let email: String
let password: String
}
struct RefreshRequest: Encodable {
let refreshToken: String
enum CodingKeys: String, CodingKey {
case refreshToken = "refresh_token"
}
}
struct ForgotPasswordRequest: Encodable {
let email: String
}
struct ForgotPasswordResponse: Decodable {
let message: String
}
struct UpdateAccountRequest: Encodable {
let name: String
}
struct ChangePasswordRequest: Encodable {
let currentPassword: String
let password: String
let passwordConfirmation: String
enum CodingKeys: String, CodingKey {
case currentPassword = "current_password"
case password
case passwordConfirmation = "password_confirmation"
}
}
struct MessageResponse: Decodable {
let message: String
}
struct AnnouncementDto: Decodable, Identifiable, Equatable {
let id: String
let kind: String
let severity: String
let title: String
let body: String
let dismissible: Bool
let actionUrl: String?
let actionLabel: String?
}
struct LoginResponse: Decodable {
let user: UserDto
let accessToken: String
let refreshToken: String
func toDomain() -> (User, AuthTokens) {
(user.toDomain(), AuthTokens(accessToken: accessToken, refreshToken: refreshToken))
}
}
struct UserDto: Decodable {
let id: String
let email: String
let name: String
let role: String?
func toDomain() -> User {
User(id: id, email: email, name: name, role: role ?? "coach")
}
}
struct SportDto: Decodable {
let key: String
let label: String
let board: String
let overlay: String
let allowedOverlays: [String]?
let defaultRules: [String: Int]?
func toDomain() -> SportOption {
SportOption(
key: key,
label: label,
board: board,
overlay: overlay,
allowedOverlays: allowedOverlays ?? [],
defaultRules: defaultRules ?? [:]
)
}
}
struct TeamDto: Decodable {
let id: String
let name: String
let sport: String?
let sportKey: String?
let boardType: String?
let canStream: Bool?
let clubName: String?
let youtubeEnabled: Bool?
let youtubeConnected: Bool?
let youtubeSelectable: Bool?
let youtubeChannelTitle: String?
let youtubeUsesPlatformChannel: Bool?
let youtubeTeamChannelTitle: String?
let planName: String?
let recordingsEnabled: Bool?
let phoneDownloadEnabled: Bool?
let billingUrl: String?
let staffManageUrl: String?
let customCoverEnabled: Bool?
let logoUrl: String?
let primaryColor: String?
let secondaryColor: String?
func toDomain() -> Team {
Team(
id: id,
name: name,
sport: sportKey ?? sport ?? "pallavolo",
sportKey: sportKey ?? sport ?? "pallavolo",
boardType: boardType ?? "volley",
clubName: clubName,
logoUrl: logoUrl,
primaryColor: primaryColor,
secondaryColor: secondaryColor,
canStream: canStream ?? true,
youtubeEnabled: youtubeEnabled ?? false,
youtubeConnected: youtubeConnected ?? false,
youtubeSelectable: youtubeSelectable ?? false,
youtubeChannelTitle: youtubeChannelTitle,
youtubeUsesPlatformChannel: youtubeUsesPlatformChannel ?? false,
youtubeTeamChannelTitle: youtubeTeamChannelTitle,
planName: planName,
recordingsEnabled: recordingsEnabled ?? false,
phoneDownloadEnabled: phoneDownloadEnabled ?? false,
billingUrl: billingUrl,
staffManageUrl: staffManageUrl,
customCoverEnabled: customCoverEnabled ?? false
)
}
}
struct MatchDto: Decodable {
let id: String
let teamId: String
let teamName: String?
let opponentName: String
let location: String?
let scheduledAt: String?
let setsToWin: Int?
let sportKey: String?
let sport: String?
let sportLabel: String?
let boardType: String?
let overlayKind: String?
let effectiveOverlayKind: String?
let category: String?
let scoringRules: ScoringRulesBody?
let activeSessionId: String?
let activeSessionStatus: String?
let streamCompleted: Bool?
let coachHubVisible: Bool?
let homePrimaryColor: String?
let homeSecondaryColor: String?
let homeLogoUrl: String?
let opponentPrimaryColor: String?
let opponentLogoUrl: String?
let effectiveCoverUrl: String?
let coverSource: String?
let customCoverEnabled: Bool?
func toDomain() -> Match {
Match(
id: id,
teamId: teamId,
teamName: teamName ?? "",
opponentName: opponentName,
location: location,
scheduledAt: scheduledAt,
sportKey: sportKey ?? sport ?? "pallavolo",
sportLabel: sportLabel,
boardType: boardType ?? "volley",
overlayKind: overlayKind,
effectiveOverlayKind: effectiveOverlayKind ?? overlayKind ?? "volley",
setsToWin: setsToWin ?? 3,
category: category,
scoringRules: scoringRules?.toDomain(),
activeSessionId: activeSessionId,
activeSessionStatus: activeSessionStatus,
streamCompleted: streamCompleted ?? false,
coachHubVisible: coachHubVisible,
homePrimaryColor: homePrimaryColor,
homeSecondaryColor: homeSecondaryColor,
homeLogoUrl: homeLogoUrl,
opponentPrimaryColor: opponentPrimaryColor,
opponentLogoUrl: opponentLogoUrl,
effectiveCoverUrl: effectiveCoverUrl,
coverSource: coverSource ?? "default",
customCoverEnabled: customCoverEnabled ?? false
)
}
}
struct StreamSessionDto: Decodable {
let id: String
let matchId: String
let status: String?
let platform: String?
let rtmpIngestUrl: String?
let hlsPlaybackUrl: String?
let watchPageUrl: String?
let shareUrl: String?
let youtubeWatchUrl: String?
let youtubeReady: Bool?
let privacyStatus: String?
let targetBitrate: Int?
let targetFps: Int?
let qualityPreset: String?
let minQualityPreset: String?
let startedAt: String?
let audioMuted: Bool?
let score: ScoreStateDto?
func toDomain() -> StreamSession {
StreamSession(
id: id,
matchId: matchId,
status: status ?? "idle",
platform: platform ?? "matchlivetv",
rtmpIngestUrl: rtmpIngestUrl,
hlsPlaybackUrl: hlsPlaybackUrl,
watchPageUrl: watchPageUrl,
shareUrl: shareUrl,
youtubeWatchUrl: youtubeWatchUrl,
youtubeReady: youtubeReady ?? false,
privacyStatus: privacyStatus ?? "public",
targetBitrate: targetBitrate ?? 2_500_000,
targetFps: targetFps ?? 30,
qualityPreset: qualityPreset ?? "720p_30_2.5mbps",
minQualityPreset: minQualityPreset ?? "auto",
startedAt: startedAt,
score: score?.toDomain(),
audioMuted: audioMuted ?? false
)
}
}
struct SetPartialDto: Codable {
let set: Int?
let home: Int?
let away: Int?
init(set: Int?, home: Int?, away: Int?) {
self.set = set
self.home = home
self.away = away
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
set = Self.decodeInt(container, key: .set)
home = Self.decodeInt(container, key: .home)
away = Self.decodeInt(container, key: .away)
}
enum CodingKeys: String, CodingKey {
case set, home, away
}
private static func decodeInt(_ container: KeyedDecodingContainer<CodingKeys>, key: CodingKeys) -> Int? {
if let value = try? container.decode(Int.self, forKey: key) { return value }
if let text = try? container.decode(String.self, forKey: key), let value = Int(text) { return value }
return nil
}
}
struct ScoreStateDto: Decodable {
let type: String?
let boardType: String?
let homeSets: Int?
let awaySets: Int?
let homePoints: Int?
let awayPoints: Int?
let currentSet: Int?
let setPartials: [SetPartialDto]?
let timeoutHome: FlexibleBool?
let timeoutAway: FlexibleBool?
let period: FlexiblePeriod?
let clockSecs: Int?
let clockRunning: FlexibleBool?
let data: ScoreStateDataDto?
func toDomain() -> ScoreState {
let board = boardType ?? "volley"
let homePts = homePoints ?? data?.homeScore ?? 0
let awayPts = awayPoints ?? data?.awayScore ?? 0
let periodLabel = period?.label
return ScoreState(
homeSets: homeSets ?? 0,
awaySets: awaySets ?? 0,
homePoints: homePts,
awayPoints: awayPts,
currentSet: max(currentSet ?? 1, 1),
setPartials: (setPartials ?? []).map {
SetPartial(set: $0.set ?? 1, home: $0.home ?? 0, away: $0.away ?? 0)
},
timeoutHome: timeoutHome?.value ?? false,
timeoutAway: timeoutAway?.value ?? false,
boardType: board,
period: parsePeriodNumber(periodLabel, currentSet: currentSet),
periodLabel: periodLabel,
clockSecs: clockSecs ?? data?.clockSecs ?? 0,
clockRunning: clockRunning?.value ?? data?.clockRunning?.value ?? false,
overtime: data?.overtime?.value ?? false
)
}
private func parsePeriodNumber(_ label: String?, currentSet: Int?) -> Int {
if let text = label {
if let m = text.range(of: #"Q(\d+)"#, options: .regularExpression) {
let num = text[m].dropFirst()
if let v = Int(num) { return v }
}
if let v = Int(text), v > 0 { return v }
}
return max(currentSet ?? 1, 1)
}
}
/// `period` può essere stringa ("Q2"), intero o null.
enum FlexiblePeriod: Decodable {
case string(String)
case int(Int)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let v = try? container.decode(Int.self) { self = .int(v); return }
if let v = try? container.decode(String.self) { self = .string(v); return }
throw DecodingError.typeMismatch(
FlexiblePeriod.self,
DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "period non valido")
)
}
var label: String? {
switch self {
case .string(let s): return s
case .int(let v): return String(v)
}
}
}
enum FlexibleBool: Decodable {
case bool(Bool)
case int(Int)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let v = try? container.decode(Bool.self) { self = .bool(v); return }
if let v = try? container.decode(Int.self) { self = .int(v); return }
throw DecodingError.typeMismatch(
FlexibleBool.self,
DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "bool non valido")
)
}
var value: Bool {
switch self {
case .bool(let v): return v
case .int(let v): return v != 0
}
}
}
struct ScoreStateDataDto: Decodable {
let homeScore: Int?
let awayScore: Int?
let clockSecs: Int?
let clockRunning: FlexibleBool?
let period: Int?
let overtime: FlexibleBool?
enum CodingKeys: String, CodingKey {
case homeScore, awayScore, clockSecs, clockRunning, period, overtime
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
homeScore = Self.decodeInt(container, key: .homeScore)
awayScore = Self.decodeInt(container, key: .awayScore)
clockSecs = Self.decodeInt(container, key: .clockSecs)
clockRunning = try? container.decode(FlexibleBool.self, forKey: .clockRunning)
period = Self.decodeInt(container, key: .period)
overtime = try? container.decode(FlexibleBool.self, forKey: .overtime)
}
private static func decodeInt(
_ container: KeyedDecodingContainer<CodingKeys>,
key: CodingKeys
) -> Int? {
if let value = try? container.decode(Int.self, forKey: key) { return value }
if let text = try? container.decode(String.self, forKey: key), let value = Int(text) { return value }
if let value = try? container.decode(Double.self, forKey: key) { return Int(value) }
return nil
}
}
struct ScoreActionRequest: Encodable {
let scoreAction: String
}
struct ScoreSyncRequest: Encodable {
let homeSets: Int
let awaySets: Int
let homePoints: Int
let awayPoints: Int
let currentSet: Int
let setPartials: [SetPartialDto]
let clockSecs: Int?
let clockRunning: Bool?
let period: Int?
let homeScore: Int?
let awayScore: Int?
}
struct CreateSessionRequest: Encodable {
let platform: String
let privacyStatus: String
let qualityPreset: String
let targetBitrate: Int
let targetFps: Int
let youtubeChannel: String?
}
struct AudioMuteRequest: Encodable {
let muted: Bool
}
struct MinQualityRequest: Encodable {
let minQualityPreset: String
}
struct ScoringRulesBody: Codable {
let setsToWin: Int?
let pointsPerSet: Int?
let pointsDecidingSet: Int?
let minPointLead: Int?
let periods: Int?
let periodDurationSecs: Int?
let overtimeDurationSecs: Int?
func toDomain() -> ScoringRules {
ScoringRules(
pointsPerSet: pointsPerSet ?? 25,
pointsDecidingSet: pointsDecidingSet ?? 15,
minPointLead: minPointLead ?? 2,
periods: periods ?? 4,
periodDurationSecs: periodDurationSecs ?? 600,
overtimeDurationSecs: overtimeDurationSecs ?? 300
)
}
}
struct UpdateMatchBodyFull: Encodable {
let opponentName: String
let location: String?
let scheduledAt: String?
let setsToWin: Int
let sportKey: String?
let overlayKind: String?
let category: String?
let opponentPrimaryColor: String?
let scoringRules: [String: Int]?
}
struct UpdateMatchRequestFull: Encodable {
let match: UpdateMatchBodyFull
}
struct UpdateTeamBody: Encodable {
let primaryColor: String?
let secondaryColor: String?
}
struct UpdateTeamRequest: Encodable {
let team: UpdateTeamBody
}
struct NetworkTestResponse: Decodable {
let ready: Bool
let targetUploadMbps: Double?
let qualityPreset: String?
let targetBitrate: Int?
let targetFps: Int?
}
struct RegiaLinkResponse: Decodable {
let regiaUrl: String
}
struct CreateMatchBody: Encodable {
let opponentName: String
let sport: String
let setsToWin: Int
let scheduledAt: String?
let location: String?
}
struct CreateMatchRequest: Encodable {
let match: CreateMatchBody
}
struct NetworkTestRequest: Encodable {
let downloadMbps: Double
let uploadMbps: Double
let latencyMs: Int
let networkType: String
}
struct TelemetryRequest: Encodable {
let deviceRole: String
let batteryLevel: Int?
let networkType: String?
let signalStrength: Int?
let currentBitrate: Int?
let targetBitrate: Int?
let fps: Int?
let thermalState: String?
}
extension ScoringRules {
func toBody(setsToWin: Int? = nil) -> ScoringRulesBody {
ScoringRulesBody(
setsToWin: setsToWin,
pointsPerSet: pointsPerSet,
pointsDecidingSet: pointsDecidingSet,
minPointLead: minPointLead,
periods: periods,
periodDurationSecs: periodDurationSecs,
overtimeDurationSecs: overtimeDurationSecs
)
}
func toMap(setsToWin: Int? = nil) -> [String: Int] {
var map: [String: Int] = [:]
if let setsToWin { map["sets_to_win"] = setsToWin }
map["points_per_set"] = pointsPerSet
map["points_deciding_set"] = pointsDecidingSet
map["min_point_lead"] = minPointLead
map["periods"] = periods
map["period_duration_secs"] = periodDurationSecs
map["overtime_duration_secs"] = overtimeDurationSecs
return map
}
}