SPB Git

spb/vrai-prix-ios Public

App iOS native de Vrai-Prix — estimation immobilière transparente pour le Québec (SwiftUI, MapKit, Swift Charts)

Swift 100%
4.0 KB · 97 lines swift
Raw Blame History
1// Auteur : Simon-Pierre Boucher — contact@spboucher.ai2// Client de l'API de production Vrai-Prix (https://www.vrai-prix.com).3import Foundation45enum APIError: LocalizedError {6    case server(String)7    case http(Int)89    var errorDescription: String? {10        switch self {11        case .server(let message): return message12        case .http(let code): return "Erreur réseau (\(code)). Réessayez."13        }14    }15}1617struct VraiPrixAPI {18    static let shared = VraiPrixAPI()19    let base = URL(string: "https://www.vrai-prix.com")!2021    private func decode<T: Decodable>(_ type: T.Type, from data: Data, status: Int) throws -> T {22        guard (200...299).contains(status) else {23            if let payload = try? JSONDecoder().decode(APIErrorPayload.self, from: data) {24                throw APIError.server(payload.error)25            }26            throw APIError.http(status)27        }28        return try JSONDecoder().decode(type, from: data)29    }3031    private func get<T: Decodable>(_ type: T.Type, path: String, query: [URLQueryItem]) async throws -> T {32        var comps = URLComponents(url: base.appending(path: path), resolvingAgainstBaseURL: false)!33        comps.queryItems = query34        let (data, response) = try await URLSession.shared.data(from: comps.url!)35        let status = (response as? HTTPURLResponse)?.statusCode ?? 036        return try decode(type, from: data, status: status)37    }3839    // MARK: Endpoints4041    func search(_ q: String) async throws -> [SearchResult] {42        try await get(SearchResponse.self, path: "/api/search", query: [.init(name: "q", value: q)]).results43    }4445    func nearby(lat: Double, lng: Double, halfLat: Double, halfLng: Double, limit: Int = 250) async throws -> [NearbyUnit] {46        try await get(NearbyResponse.self, path: "/api/nearby", query: [47            .init(name: "lat", value: String(lat)),48            .init(name: "lng", value: String(lng)),49            .init(name: "halfLat", value: String(halfLat)),50            .init(name: "halfLng", value: String(halfLng)),51            .init(name: "limit", value: String(limit)),52        ]).results53    }5455    func estimate(id: String) async throws -> UnitEstimate {56        try await get(UnitEstimate.self, path: "/api/estimate", query: [.init(name: "id", value: id)])57    }5859    func estimateManual(60        municipality: String,61        typeProp: String,62        floorArea: Double?,63        yearBuilt: Int?,64        landArea: Double?65    ) async throws -> UnitEstimate {66        var body: [String: Any] = ["municipality": municipality, "typeProp": typeProp]67        if let floorArea { body["floorArea"] = floorArea }68        if let yearBuilt { body["yearBuilt"] = yearBuilt }69        if let landArea { body["landArea"] = landArea }7071        var request = URLRequest(url: base.appending(path: "/api/estimate"))72        request.httpMethod = "POST"73        request.setValue("application/json", forHTTPHeaderField: "Content-Type")74        request.httpBody = try JSONSerialization.data(withJSONObject: body)75        let (data, response) = try await URLSession.shared.data(for: request)76        let status = (response as? HTTPURLResponse)?.statusCode ?? 077        return try decode(UnitEstimate.self, from: data, status: status)78    }7980    /// Télécharge un rapport PDF dans un fichier temporaire et retourne son URL locale.81    func downloadReport(path: String, query: [URLQueryItem], fileName: String) async throws -> URL {82        var comps = URLComponents(url: base.appending(path: path), resolvingAgainstBaseURL: false)!83        comps.queryItems = query84        let (data, response) = try await URLSession.shared.data(from: comps.url!)85        let status = (response as? HTTPURLResponse)?.statusCode ?? 086        guard (200...299).contains(status) else {87            if let payload = try? JSONDecoder().decode(APIErrorPayload.self, from: data) {88                throw APIError.server(payload.error)89            }90            throw APIError.http(status)91        }92        let url = FileManager.default.temporaryDirectory.appending(path: fileName)93        try data.write(to: url)94        return url95    }96}97