import Foundation enum APIError: Error, LocalizedError { case unauthorized case networkError(String) case decodingError(String) case serverError(Int, String) var errorDescription: String? { switch self { case .unauthorized: return "Неверный email или пароль" case .networkError(let m): return "Ошибка сети: \(m)" case .decodingError(let m): return "Ошибка данных: \(m)" case .serverError(let c, let m): return "Ошибка \(c): \(m)" } } } class APIService { static let shared = APIService() let baseURL = "https://api.digital-home.site" private func makeRequest(_ path: String, method: String = "GET", token: String? = nil, body: Data? = nil) -> URLRequest { var req = URLRequest(url: URL(string: "\(baseURL)\(path)")!) req.httpMethod = method req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.timeoutInterval = 15 if let t = token { req.setValue("Bearer \(t)", forHTTPHeaderField: "Authorization") } req.httpBody = body return req } private func fetch(_ path: String, method: String = "GET", token: String? = nil, body: Data? = nil) async throws -> T { let req = makeRequest(path, method: method, token: token, body: body) let (data, response) = try await URLSession.shared.data(for: req) guard let http = response as? HTTPURLResponse else { throw APIError.networkError("Нет ответа") } if http.statusCode == 401 { throw APIError.unauthorized } if http.statusCode >= 400 { let msg = String(data: data, encoding: .utf8) ?? "Unknown" throw APIError.serverError(http.statusCode, msg) } do { return try JSONDecoder().decode(T.self, from: data) } catch { throw APIError.decodingError(error.localizedDescription) } } // MARK: - Auth func login(email: String, password: String) async throws -> AuthResponse { let body = try JSONEncoder().encode(LoginRequest(email: email, password: password)) return try await fetch("/auth/login", method: "POST", body: body) } func register(email: String, password: String, name: String) async throws -> AuthResponse { let body = try JSONEncoder().encode(RegisterRequest(email: email, password: password, name: name)) return try await fetch("/auth/register", method: "POST", body: body) } func me(token: String) async throws -> UserInfo { return try await fetch("/auth/me", token: token) } // MARK: - Tasks func getTasks(token: String) async throws -> [PulseTask] { return try await fetch("/tasks", token: token) } func getTodayTasks(token: String) async throws -> [PulseTask] { return try await fetch("/tasks/today", token: token) } @discardableResult func createTask(token: String, request: CreateTaskRequest) async throws -> PulseTask { let body = try JSONEncoder().encode(request) return try await fetch("/tasks", method: "POST", token: token, body: body) } func completeTask(token: String, id: Int) async throws { let _: EmptyResponse = try await fetch("/tasks/\(id)/complete", method: "POST", token: token) } func deleteTask(token: String, id: Int) async throws { let _: EmptyResponse = try await fetch("/tasks/\(id)", method: "DELETE", token: token) } // MARK: - Habits func getHabits(token: String) async throws -> [Habit] { return try await fetch("/habits", token: token) } func logHabit(token: String, id: Int) async throws { let body = try JSONEncoder().encode(HabitLogRequest()) let _: EmptyResponse = try await fetch("/habits/\(id)/log", method: "POST", token: token, body: body) } // MARK: - Finance func getFinanceSummary(token: String) async throws -> FinanceSummary { return try await fetch("/finance/summary", token: token) } func getTransactions(token: String) async throws -> [FinanceTransaction] { return try await fetch("/finance/transactions", token: token) } @discardableResult func createTransaction(token: String, request: CreateTransactionRequest) async throws -> FinanceTransaction { let body = try JSONEncoder().encode(request) return try await fetch("/finance/transactions", method: "POST", token: token, body: body) } // MARK: - Savings func getSavingsCategories(token: String) async throws -> [SavingsCategory] { return try await fetch("/savings/categories", token: token) } func getSavingsStats(token: String) async throws -> SavingsStats { return try await fetch("/savings/stats", token: token) } func getSavingsTransactions(token: String, limit: Int = 50) async throws -> [SavingsTransaction] { return try await fetch("/savings/transactions?limit=\(limit)", token: token) } func getFinanceCategories(token: String) async throws -> [FinanceCategory] { return try await fetch("/finance/categories", token: token) } } struct EmptyResponse: Codable {}