import Foundation struct ApiEnvelope: Decodable { let error: Bool let code: String? let message: String? let result: T? } final class ApiService { private let client: ApiClient private var tokenStore: TokenStore init(client: ApiClient = ApiClient(), tokenStore: TokenStore = DefaultTokenStore()) { self.client = client self.tokenStore = tokenStore } // MARK: - Auth func requestOtp(email: String) async throws -> ApiEnvelope { let body = try JSONEncoder().encode(["email": email]) let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body) return try await client.send(req) } func validateOtp(email: String, otp: String) async throws -> ApiEnvelope { let body = try JSONEncoder().encode(["email": email, "otp": otp]) let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body) let response: ApiEnvelope = try await client.send(req) if let token = response.result?.token { tokenStore.jwt = token } return response } func profile() async throws -> ApiEnvelope { let req = ApiRequest(path: "/api/customer/profile", method: "GET", module: .customer, requiresAuth: true) return try await client.send(req) } // MARK: - Stores func listStores(lat: Double, lng: Double, category: String? = nil, search: String? = nil) async throws -> ApiEnvelope<[StoreSummary]> { var items = [ URLQueryItem(name: "lat", value: String(lat)), URLQueryItem(name: "lng", value: String(lng)) ] if let category { items.append(URLQueryItem(name: "category", value: category)) } if let search { items.append(URLQueryItem(name: "search", value: search)) } let req = ApiRequest(path: "/api/app/stores", method: "GET", module: .app, requiresAuth: true, queryItems: items) return try await client.send(req) } } // MARK: - DTOs struct EmptyResult: Decodable {} struct LoginResult: Decodable { let token: String let customer: CustomerProfile? } struct CustomerProfile: Decodable { let id: String let name: String let email: String let phoneNumber: String? } struct StoreSummary: Decodable { let id: String let name: String let logo: String? let cover: String? let category: String? let rating: Double? let deliveryTime: String? let deliveryFee: Double? let distance: Double? let isOpen: Bool? let statusLabel: String? }