import Foundation /// Liest Daten von einem **bestimmten** Server, ohne den aktiven zu wechseln. /// /// `APIClient` ist fest auf das aktive Profil verdrahtet (Session.shared). /// Fuer die Benachrichtigungen muss die App aber jeden eingerichteten Server /// abfragen - deshalb dieser parallele Pfad, der baseURL und Token ausdruecklich /// bekommt. Modelle und Fehlerbedeutung sind dieselben wie im `APIClient`. enum ServerFetch { /// Alles, was der Planer je Server braucht: ablaufende Chargen im Horizont, /// die Produkte (fuer die Kategoriezuordnung) und der Kategorie-Baum. struct Snapshot { let expiring: [ExpiringItem] let products: [Product] let categories: [CategoryItem] } static func snapshot(baseURL: URL, token: String, horizonDays: Int) async throws -> Snapshot { async let expiring: [ExpiringItem] = get( "/expiring?days=\(horizonDays)", baseURL: baseURL, token: token) async let products: [Product] = get("/products", baseURL: baseURL, token: token) async let categories: [CategoryItem] = get("/categories", baseURL: baseURL, token: token) return try await Snapshot(expiring: expiring, products: products, categories: categories) } private static func get( _ path: String, baseURL: URL, token: String ) async throws -> T { guard let url = URL(string: "api" + path, relativeTo: baseURL) else { throw APIError.notConfigured } var request = URLRequest(url: url) request.httpMethod = "GET" // Etwas grosszuegiger als der interaktive Client: Der Abruf laeuft im // Hintergrund, ein langsamer Heimserver soll nicht sofort scheitern. request.timeoutInterval = 20 request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) { if http.statusCode == 401 { throw APIError.unauthorized } throw APIError.server("Serverfehler (\(http.statusCode))") } return try JSONDecoder().decode(T.self, from: data) } }