import Foundation class HealthAPIService { static let shared = HealthAPIService() let baseURL = "https://health.digital-home.site" private var cachedToken: String? { get { UserDefaults.standard.string(forKey: "healthJWTToken") } set { UserDefaults.standard.set(newValue, forKey: "healthJWTToken") } } // Логин в health сервис (отдельный JWT) func ensureToken() async throws -> String { if let t = cachedToken { return t } return try await refreshToken() } func refreshToken() async throws -> String { var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/login")!) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = try? JSONEncoder().encode(["email": "daniilklimov25@gmail.com", "password": "cosmo-health-2026"]) req.timeoutInterval = 15 let (data, _) = try await URLSession.shared.data(for: req) struct LoginResp: Decodable { let token: String } let resp = try JSONDecoder().decode(LoginResp.self, from: data) cachedToken = resp.token return resp.token } private func fetch(_ path: String) async throws -> T { let token = try await ensureToken() var req = URLRequest(url: URL(string: "\(baseURL)\(path)")!) req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.timeoutInterval = 15 let (data, response) = try await URLSession.shared.data(for: req) guard let http = response as? HTTPURLResponse else { throw APIError.networkError("No response") } if http.statusCode == 401 { // Token expired, retry once cachedToken = nil let newToken = try await refreshToken() var req2 = URLRequest(url: URL(string: "\(baseURL)\(path)")!) req2.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization") req2.setValue("application/json", forHTTPHeaderField: "Content-Type") req2.timeoutInterval = 15 let (data2, _) = try await URLSession.shared.data(for: req2) return try JSONDecoder().decode(T.self, from: data2) } if http.statusCode >= 400 { throw APIError.serverError(http.statusCode, String(data: data, encoding: .utf8) ?? "") } return try JSONDecoder().decode(T.self, from: data) } func getLatest() async throws -> LatestHealthResponse { return try await fetch("/api/health/latest") } func getReadiness() async throws -> ReadinessResponse { return try await fetch("/api/health/readiness") } func getHeatmap(days: Int = 30) async throws -> [HeatmapEntry] { let token = try await ensureToken() var req = URLRequest(url: URL(string: "\(baseURL)/api/health/heatmap?days=\(days)")!) req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") req.timeoutInterval = 15 let (data, _) = try await URLSession.shared.data(for: req) if let entries = try? JSONDecoder().decode([HeatmapEntry].self, from: data) { return entries } struct HeatmapResponse: Decodable { let data: [HeatmapEntry] } let wrapped = try JSONDecoder().decode(HeatmapResponse.self, from: data) return wrapped.data } func sendHealthData(apiKey: String, payload: Data) async throws { let url = URL(string: "\(baseURL)/api/health?key=\(apiKey)")! var req = URLRequest(url: url) req.httpMethod = "POST" req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = payload req.timeoutInterval = 30 let (_, response) = try await URLSession.shared.data(for: req) guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else { throw APIError.serverError((response as? HTTPURLResponse)?.statusCode ?? 0, "Ошибка отправки") } } }