Almost done

This commit is contained in:
Daniel Arantes Loverde
2026-06-08 10:46:52 -03:00
parent aec1193944
commit 23d9752bfd
25 changed files with 476 additions and 257 deletions

View File

@@ -82,7 +82,6 @@ final class ApiClient {
}
func send<T: Decodable>(_ request: ApiRequest) async throws -> T {
#if canImport(LCEssentials) && os(iOS)
return try await sendWithLCEssentials(request)
#else
@@ -119,7 +118,7 @@ private extension ApiClient {
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw NetworkError.decodeError(String(data: data, encoding: .utf8))
throw NetworkError.decodeError(sanitizedBody(data))
}
} catch {
let mapped = mapError(error)
@@ -156,6 +155,7 @@ private extension ApiClient {
case "POST": return .post
case "PUT": return .put
case "DELETE": return .delete
case "PATCH": return .patch
default: return .get
}
}
@@ -274,7 +274,7 @@ private extension ApiClient {
do {
return try JSONDecoder().decode(type, from: data)
} catch {
throw NetworkError.decodeError(String(data: data, encoding: .utf8))
throw NetworkError.decodeError(sanitizedBody(data))
}
}
@@ -328,7 +328,7 @@ private extension ApiClient {
return msg
}
}
return String(data: data, encoding: .utf8)
return sanitizedBody(data)
}
func serverPayload(from data: Data) -> ApiErrorDescriptor? {
@@ -364,48 +364,41 @@ private extension ApiClient {
#if canImport(LCEssentials) && os(iOS)
func serverMessage(from error: NSError) -> String? {
// Tenta buscar no userInfo por chaves conhecidas
for key in [NSLocalizedFailureReasonErrorKey, "body", "responseBody", "data", "message"] {
if let value = error.userInfo[key] as? String,
!value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let data = value.data(using: .utf8),
let parsed = serverMessage(from: data),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
continue
}
return parsed
}
if !value.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return value
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if t.hasPrefix("{") { continue }
if let safe = sanitizedMessage(t) { return safe }
}
let t = value.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
} else if let valueData = error.userInfo[key] as? Data {
if let parsed = serverMessage(from: valueData),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return parsed
if let parsed = serverMessage(from: valueData) {
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
}
}
}
// Tenta varrer qualquer chave do userInfo por strings que possam ser JSON ou mensagens
for (_, value) in error.userInfo {
if let str = value as? String, !str.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
if let data = str.data(using: .utf8),
let parsed = serverMessage(from: data),
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!parsed.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{") {
return parsed
}
}
if let str = value as? String,
str.trimmingCharacters(in: .whitespacesAndNewlines).hasPrefix("{"),
let data = str.data(using: .utf8),
let parsed = serverMessage(from: data) {
let t = parsed.trimmingCharacters(in: .whitespacesAndNewlines)
if !t.hasPrefix("{"), let safe = sanitizedMessage(t) { return safe }
}
}
if let description = error.userInfo[NSLocalizedDescriptionKey] as? String,
!description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
!description.lowercased().contains("nsurlerrordomain") {
return description
!description.lowercased().contains("nsurlerrordomain"),
let safe = sanitizedMessage(description) {
return safe
}
return nil
@@ -435,6 +428,25 @@ private extension ApiClient {
}
#endif
func sanitizedBody(_ data: Data) -> String? {
guard let raw = String(data: data, encoding: .utf8) else { return nil }
return sanitizedMessage(raw)
}
func sanitizedMessage(_ raw: String) -> String? {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.isEmpty == false else { return nil }
// Drop data URLs (base64 images)
if trimmed.lowercased().hasPrefix("data:image") { return "Erro ao processar imagem." }
// Drop fields containing base64,
if trimmed.contains("base64,") { return "Resposta do servidor inválida." }
// Truncate long strings (raw JSON bodies, etc.)
if trimmed.count > 300 {
return String(trimmed.prefix(300)) + ""
}
return trimmed
}
func buildURL(path: String, query: [URLQueryItem]) throws -> URL {
guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else {
throw NetworkError.invalidURL

View File

@@ -53,18 +53,23 @@ struct CustomerAddress: Decodable {
let state: String?
let zipCode: String?
let latLong: [Double]?
let isDefault: Bool?
init(id: String?, label: String?, address: String?, number: String?,
complement: String?, neighborhood: String?, city: String?,
state: String?, zipCode: String?, latLong: [Double]?, isDefault: Bool?) {
self.id = id; self.label = label; self.address = address
self.number = number; self.complement = complement
self.neighborhood = neighborhood; self.city = city
self.state = state; self.zipCode = zipCode
self.latLong = latLong; self.isDefault = isDefault
}
enum CodingKeys: String, CodingKey {
case id
case label
case address
case number
case complement
case neighborhood
case city
case state
case zipCode
case id, label, address, number, complement
case neighborhood, city, state, zipCode
case latLong = "lat_long"
case isDefault
}
}

View File

@@ -28,6 +28,7 @@ struct CreateOrderItemPayload: Codable {
let qty: Int
let price: Double
let addons: [CreateOrderAddonPayload]
let choices: [String]?
}
struct CreateOrderAddonPayload: Codable {

View File

@@ -212,6 +212,13 @@ final class ApiService {
return try await updateCustomerAddressBook(customerId: customer.id, addressBook: updatedAddressBook)
}
func setDefaultAddress(addressId: String) async throws -> ApiEnvelope<EmptyResult> {
let req = ApiRequest(path: "/api/customer/addresses/\(addressId)/default", method: "PATCH", module: .customer, requiresAuth: true)
let envelope: ApiEnvelope<EmptyResult> = try await sendEnvelope(req)
AppContentCache.shared.invalidate(prefix: profileCachePrefix)
return envelope
}
func lookupZipCode(_ zipCode: String) async throws -> ApiEnvelope<CepLookupResult> {
let digits = zipCode.filter(\.isNumber)
let normalized = String(digits.prefix(8))

View File

@@ -258,7 +258,7 @@ final class FeatureControlService {
}
private func featureKeys() -> [String] {
let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "at.city.aguai,at.promo,at.ios.only,at.android.only"
let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "at.city.aguai,at.promo,at.ios.only,at.android.only,at.cupons"
let items = raw
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }