fix: align OTP auth with actual API response format
Three bugs found and fixed:
1. sendOTP was sending "ContactNumber" but API expects "Phone"
2. APIResponse expected {"Success":true,"Data":{}} but API returns {"OK":true,"UUID":"..."}
3. verifyOTP was sending "Code" but API expects "OTP"
Now decodes the raw API format directly instead of going through the
generic APIResponse wrapper (which doesn't match auth endpoints).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fd4b1bf8ca
commit
2242260f5a
1 changed files with 56 additions and 19 deletions
|
|
@ -36,39 +36,76 @@ actor APIClient {
|
||||||
|
|
||||||
// MARK: - Auth
|
// MARK: - Auth
|
||||||
|
|
||||||
struct OTPResponse: Codable {
|
/// Raw response from /auth/loginOTP.php
|
||||||
let uuid: String?
|
/// API returns: { "OK": true, "UUID": "...", "MESSAGE": "..." }
|
||||||
|
private struct OTPRawResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
let UUID: String?
|
let UUID: String?
|
||||||
var otpUUID: String { uuid ?? UUID ?? "" }
|
let MESSAGE: String?
|
||||||
|
let ERROR: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendOTP(phone: String) async throws -> String {
|
func sendOTP(phone: String) async throws -> String {
|
||||||
let body: [String: Any] = ["ContactNumber": phone]
|
let body: [String: Any] = ["Phone": phone]
|
||||||
let data = try await post(path: "/auth/loginOTP.php", body: body)
|
let data = try await post(path: "/auth/loginOTP.php", body: body)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<OTPResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(OTPRawResponse.self, from: data)
|
||||||
guard resp.success, let payload = resp.data else {
|
guard resp.OK, let uuid = resp.UUID, !uuid.isEmpty else {
|
||||||
throw APIError.serverError(resp.message ?? "Failed to send OTP")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to send OTP")
|
||||||
}
|
}
|
||||||
return payload.otpUUID
|
return uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoginResponse: Codable {
|
/// Raw response from /auth/verifyLoginOTP.php
|
||||||
let token: String?
|
/// API returns: { "OK": true, "UserID": 123, "Token": "...", "FirstName": "..." }
|
||||||
|
private struct VerifyOTPRawResponse: Codable {
|
||||||
|
let OK: Bool
|
||||||
let Token: String?
|
let Token: String?
|
||||||
let userId: String?
|
let UserID: IntOrString?
|
||||||
let UserID: String?
|
let MESSAGE: String?
|
||||||
var authToken: String { token ?? Token ?? "" }
|
let ERROR: String?
|
||||||
var authUserId: String { userId ?? UserID ?? "" }
|
}
|
||||||
|
|
||||||
|
/// Handles UserID coming as either int or string from API
|
||||||
|
enum IntOrString: Codable {
|
||||||
|
case int(Int)
|
||||||
|
case string(String)
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
if let i = try? container.decode(Int.self) {
|
||||||
|
self = .int(i)
|
||||||
|
} else if let s = try? container.decode(String.self) {
|
||||||
|
self = .string(s)
|
||||||
|
} else {
|
||||||
|
self = .string("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
switch self {
|
||||||
|
case .int(let i): try container.encode(i)
|
||||||
|
case .string(let s): try container.encode(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue: String {
|
||||||
|
switch self {
|
||||||
|
case .int(let i): return String(i)
|
||||||
|
case .string(let s): return s
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyOTP(uuid: String, code: String) async throws -> (token: String, userId: String) {
|
func verifyOTP(uuid: String, code: String) async throws -> (token: String, userId: String) {
|
||||||
let body: [String: Any] = ["UUID": uuid, "Code": code]
|
let body: [String: Any] = ["UUID": uuid, "OTP": code]
|
||||||
let data = try await post(path: "/auth/verifyLoginOTP.php", body: body)
|
let data = try await post(path: "/auth/verifyLoginOTP.php", body: body)
|
||||||
let resp = try JSONDecoder().decode(APIResponse<LoginResponse>.self, from: data)
|
let resp = try JSONDecoder().decode(VerifyOTPRawResponse.self, from: data)
|
||||||
guard resp.success, let payload = resp.data else {
|
guard resp.OK, let token = resp.Token, !token.isEmpty else {
|
||||||
throw APIError.serverError(resp.message ?? "Invalid OTP")
|
throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Invalid OTP")
|
||||||
}
|
}
|
||||||
return (payload.authToken, payload.authUserId)
|
let userId = resp.UserID?.stringValue ?? ""
|
||||||
|
return (token, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Businesses
|
// MARK: - Businesses
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue