login flow

This commit is contained in:
Daniel Arantes Loverde
2026-02-12 09:44:40 -03:00
parent 3176b67914
commit 38fc71718b
12 changed files with 710 additions and 111 deletions

View File

@@ -32,14 +32,40 @@ final class ApiService {
return try await client.send(req)
} catch let error as NetworkError {
if case .unauthorized(let message) = error {
tokenStore.clear()
NotificationCenter.default.post(name: .sessionExpired, object: message)
expireSession(message)
throw ApiServiceError.sessionExpired(message)
}
throw error
}
}
private func sendEnvelope<T: Decodable>(_ req: ApiRequest) async throws -> ApiEnvelope<T> {
let envelope: ApiEnvelope<T> = try await send(req)
if isSessionExpiredEnvelope(envelope) {
expireSession(envelope.message)
throw ApiServiceError.sessionExpired(envelope.message)
}
return envelope
}
private func isSessionExpiredEnvelope<T>(_ envelope: ApiEnvelope<T>) -> Bool {
guard envelope.error else { return false }
let code = (envelope.code ?? "").lowercased()
let message = (envelope.message ?? "").lowercased()
if code.contains("auth") || code.contains("token") || code.contains("unauthorized") {
return true
}
if message.contains("token") && (message.contains("expir") || message.contains("invalid") || message.contains("sess")) {
return true
}
return false
}
private func expireSession(_ message: String?) {
tokenStore.clear()
NotificationCenter.default.post(name: .sessionExpired, object: message)
}
// MARK: - Auth
func registerCustomer(name: String, email: String, phoneNumber: String, birthDate: String? = nil) async throws -> ApiEnvelope<RegistrationResult> {
@@ -53,19 +79,19 @@ final class ApiService {
}
let body = try JSONEncoder().encode(payload)
let req = ApiRequest(path: "/api/customer", method: "POST", module: .customer, requiresAuth: false, body: body)
return try await send(req)
return try await sendEnvelope(req)
}
func requestOtp(email: String, phoneNumber: String) async throws -> ApiEnvelope<EmptyResult> {
let body = try makeLoginBody(email: email, phoneNumber: phoneNumber, otp: nil)
let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body)
return try await send(req)
return try await sendEnvelope(req)
}
func validateOtp(email: String, phoneNumber: String, otp: String) async throws -> ApiEnvelope<LoginResult> {
let body = try makeLoginBody(email: email, phoneNumber: phoneNumber, otp: otp)
let req = ApiRequest(path: "/api/customer/login", method: "POST", module: .customer, requiresAuth: false, body: body)
let response: ApiEnvelope<LoginResult> = try await send(req)
let response: ApiEnvelope<LoginResult> = try await sendEnvelope(req)
if let token = response.result?.token {
tokenStore.jwt = token
}
@@ -97,7 +123,7 @@ final class ApiService {
func profile() async throws -> ApiEnvelope<CustomerProfile> {
let req = ApiRequest(path: "/api/customer/profile", method: "GET", module: .customer, requiresAuth: true)
return try await send(req)
return try await sendEnvelope(req)
}
func addCustomerAddress(_ address: CustomerAddress) async throws -> ApiEnvelope<CustomerProfile> {
@@ -112,7 +138,7 @@ final class ApiService {
let payload = CustomerProfileUpdatePayload(addressBook: addressBook)
let body = try JSONEncoder().encode(payload)
let req = ApiRequest(path: "/api/customer/\(customer.id)", method: "POST", module: .customer, requiresAuth: true, body: body)
return try await send(req)
return try await sendEnvelope(req)
}
func lookupZipCode(_ zipCode: String) async throws -> ApiEnvelope<CepLookupResult> {
@@ -128,16 +154,22 @@ final class ApiService {
}
let req = ApiRequest(path: "/api/public/cep/\(formatted)", method: "GET", module: .none, requiresAuth: true)
return try await send(req)
return try await sendEnvelope(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))
]
func listPublicCategories() async throws -> ApiEnvelope<[PublicCategory]> {
let req = ApiRequest(path: "/api/public/categories", method: "GET", module: .none, requiresAuth: false)
return try await sendEnvelope(req)
}
func listStores(lat: Double? = nil, lng: Double? = nil, category: String? = nil, search: String? = nil) async throws -> ApiEnvelope<[StoreSummary]> {
var items: [URLQueryItem] = []
if let lat, let lng {
items.append(URLQueryItem(name: "lat", value: String(lat)))
items.append(URLQueryItem(name: "lng", value: String(lng)))
}
if let category {
items.append(URLQueryItem(name: "category", value: category))
}
@@ -145,7 +177,7 @@ final class ApiService {
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 send(req)
return try await sendEnvelope(req)
}
}
@@ -222,6 +254,12 @@ struct StoreSummary: Decodable {
let statusLabel: String?
}
struct PublicCategory: Decodable {
let id: String
let name: String
let icon: String?
}
struct CustomerProfileUpdatePayload: Encodable {
let addressBook: [CustomerAddressPayload]