SubmittedToTestflight
This commit is contained in:
@@ -11,27 +11,17 @@ public struct SignInBody: Encodable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Better Auth wraps the sign-in response as { data: { user, session } }
|
||||
/// Server returns { token, user } directly (or { error: { message } } on failure)
|
||||
public struct SignInResponse: Decodable, Sendable {
|
||||
public let data: SignInData?
|
||||
public let token: String?
|
||||
public let user: AuthUser?
|
||||
public let error: SignInError?
|
||||
|
||||
public struct SignInData: Decodable, Sendable {
|
||||
public let user: AuthUser
|
||||
public let session: AuthSession
|
||||
}
|
||||
|
||||
public struct SignInError: Decodable, Sendable {
|
||||
public let message: String?
|
||||
}
|
||||
}
|
||||
|
||||
public struct AuthSession: Decodable, Sendable {
|
||||
public let id: String
|
||||
public let token: String
|
||||
public let userId: String
|
||||
public let expiresAt: String
|
||||
}
|
||||
|
||||
public struct AuthUser: Decodable, Sendable {
|
||||
public let id: String
|
||||
@@ -41,10 +31,9 @@ public struct AuthUser: Decodable, Sendable {
|
||||
public let image: String?
|
||||
}
|
||||
|
||||
/// Thin wrapper around the /api/auth/session endpoint response.
|
||||
/// Thin wrapper around the /api/auth/get-session endpoint response.
|
||||
private struct SessionResponse: Decodable, Sendable {
|
||||
let user: AuthUser?
|
||||
let session: AuthSession?
|
||||
}
|
||||
|
||||
public final class AuthClient: Sendable {
|
||||
@@ -64,6 +53,7 @@ public final class AuthClient: Sendable {
|
||||
var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/sign-in/email")!)
|
||||
req.httpMethod = "POST"
|
||||
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
req.setValue(baseURL, forHTTPHeaderField: "Origin")
|
||||
req.httpBody = encoded
|
||||
|
||||
let session = sharedURLSession()
|
||||
@@ -73,19 +63,22 @@ public final class AuthClient: Sendable {
|
||||
throw APIError.networkError(URLError(.badServerResponse))
|
||||
}
|
||||
|
||||
// Better Auth may return 200 with an error body, or a non-200 status
|
||||
guard http.statusCode == 200 else {
|
||||
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
||||
}
|
||||
|
||||
let signInResponse = try JSONDecoder.api.decode(SignInResponse.self, from: data)
|
||||
if let errMsg = signInResponse.error?.message {
|
||||
throw APIError.httpError(statusCode: http.statusCode, data: Data(errMsg.utf8))
|
||||
}
|
||||
guard http.statusCode == 200 else {
|
||||
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
||||
}
|
||||
guard let user = signInResponse.data?.user else {
|
||||
guard let user = signInResponse.user else {
|
||||
throw APIError.decodingError(DecodingError.dataCorrupted(
|
||||
.init(codingPath: [], debugDescription: "Missing user in sign-in response")
|
||||
))
|
||||
}
|
||||
if let token = signInResponse.token {
|
||||
injectSessionCookie(token: token)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -194,6 +187,7 @@ public final class AuthClient: Sendable {
|
||||
public func fetchCurrentUser() async throws -> AuthUser {
|
||||
var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/get-session")!)
|
||||
req.httpMethod = "GET"
|
||||
req.setValue(baseURL, forHTTPHeaderField: "Origin")
|
||||
let session = sharedURLSession()
|
||||
let (data, response) = try await session.data(for: req)
|
||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
|
||||
|
||||
@@ -29,15 +29,6 @@ public struct Chemical: Identifiable, Sendable, Encodable {
|
||||
public var createdAt: String?
|
||||
public var updatedAt: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, piFirstName, physicalState, chemicalName, bldgCode, lab
|
||||
case storageLocation, storageDevice, numberOfContainers, amountPerContainer
|
||||
case unitOfMeasure, casNumber, chemicalFormula, molecularWeight, vendor
|
||||
case catalogNumber, lotNumber, expirationDate, concentration, percentageFull
|
||||
case comments, barcode, contact, scannedImage, needsManualEntry
|
||||
case createdAt, updatedAt
|
||||
}
|
||||
|
||||
public init(
|
||||
id: String = "",
|
||||
piFirstName: String = "",
|
||||
@@ -100,34 +91,33 @@ public struct Chemical: Identifiable, Sendable, Encodable {
|
||||
extension Chemical: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try c.decode(String.self, forKey: .id)
|
||||
piFirstName = try c.decode(String.self, forKey: .piFirstName)
|
||||
physicalState = try c.decode(String.self, forKey: .physicalState)
|
||||
chemicalName = try c.decode(String.self, forKey: .chemicalName)
|
||||
bldgCode = try c.decode(String.self, forKey: .bldgCode)
|
||||
lab = try c.decode(String.self, forKey: .lab)
|
||||
storageLocation = try c.decode(String.self, forKey: .storageLocation)
|
||||
storageDevice = try c.decode(String.self, forKey: .storageDevice)
|
||||
id = try c.decode(String.self, forKey: .id)
|
||||
piFirstName = try c.decode(String.self, forKey: .piFirstName)
|
||||
physicalState = try c.decode(String.self, forKey: .physicalState)
|
||||
chemicalName = try c.decode(String.self, forKey: .chemicalName)
|
||||
bldgCode = try c.decode(String.self, forKey: .bldgCode)
|
||||
lab = try c.decode(String.self, forKey: .lab)
|
||||
storageLocation = try c.decode(String.self, forKey: .storageLocation)
|
||||
storageDevice = try c.decode(String.self, forKey: .storageDevice)
|
||||
numberOfContainers = try c.decode(String.self, forKey: .numberOfContainers)
|
||||
amountPerContainer = try c.decode(String.self, forKey: .amountPerContainer)
|
||||
unitOfMeasure = try c.decode(String.self, forKey: .unitOfMeasure)
|
||||
casNumber = try c.decode(String.self, forKey: .casNumber)
|
||||
chemicalFormula = try c.decodeIfPresent(String.self, forKey: .chemicalFormula)
|
||||
molecularWeight = try c.decodeIfPresent(String.self, forKey: .molecularWeight)
|
||||
vendor = try c.decodeIfPresent(String.self, forKey: .vendor)
|
||||
catalogNumber = try c.decodeIfPresent(String.self, forKey: .catalogNumber)
|
||||
lotNumber = try c.decodeIfPresent(String.self, forKey: .lotNumber)
|
||||
expirationDate = try c.decodeIfPresent(String.self, forKey: .expirationDate)
|
||||
concentration = try c.decodeIfPresent(String.self, forKey: .concentration)
|
||||
comments = try c.decodeIfPresent(String.self, forKey: .comments)
|
||||
barcode = try c.decodeIfPresent(String.self, forKey: .barcode)
|
||||
contact = try c.decodeIfPresent(String.self, forKey: .contact)
|
||||
scannedImage = try c.decodeIfPresent(String.self, forKey: .scannedImage)
|
||||
needsManualEntry = try c.decodeIfPresent([String].self, forKey: .needsManualEntry)
|
||||
createdAt = try c.decodeIfPresent(String.self, forKey: .createdAt)
|
||||
updatedAt = try c.decodeIfPresent(String.self, forKey: .updatedAt)
|
||||
unitOfMeasure = try c.decode(String.self, forKey: .unitOfMeasure)
|
||||
casNumber = try c.decode(String.self, forKey: .casNumber)
|
||||
chemicalFormula = try c.decodeIfPresent(String.self, forKey: .chemicalFormula)
|
||||
molecularWeight = try c.decodeIfPresent(String.self, forKey: .molecularWeight)
|
||||
vendor = try c.decodeIfPresent(String.self, forKey: .vendor)
|
||||
catalogNumber = try c.decodeIfPresent(String.self, forKey: .catalogNumber)
|
||||
lotNumber = try c.decodeIfPresent(String.self, forKey: .lotNumber)
|
||||
concentration = try c.decodeIfPresent(String.self, forKey: .concentration)
|
||||
comments = try c.decodeIfPresent(String.self, forKey: .comments)
|
||||
barcode = try c.decodeIfPresent(String.self, forKey: .barcode)
|
||||
contact = try c.decodeIfPresent(String.self, forKey: .contact)
|
||||
scannedImage = try c.decodeIfPresent(String.self, forKey: .scannedImage)
|
||||
needsManualEntry = try c.decodeIfPresent([String].self, forKey: .needsManualEntry)
|
||||
createdAt = try c.decodeIfPresent(String.self, forKey: .createdAt)
|
||||
updatedAt = try c.decodeIfPresent(String.self, forKey: .updatedAt)
|
||||
|
||||
// percentageFull arrives as a number OR a numeric string ("12.00") depending on the query path
|
||||
// percentageFull: GET returns Float (after ::float cast), POST/PATCH RETURNING still returns String
|
||||
if let d = try? c.decodeIfPresent(Double.self, forKey: .percentageFull) {
|
||||
percentageFull = d
|
||||
} else if let s = try? c.decodeIfPresent(String.self, forKey: .percentageFull) {
|
||||
@@ -135,6 +125,23 @@ extension Chemical: Decodable {
|
||||
} else {
|
||||
percentageFull = nil
|
||||
}
|
||||
|
||||
// expirationDate: GET returns "YYYY-MM-DD", POST/PATCH RETURNING returns full ISO timestamp
|
||||
// Normalise to "YYYY-MM-DD" in all cases
|
||||
if let raw = try? c.decodeIfPresent(String.self, forKey: .expirationDate) {
|
||||
expirationDate = String(raw.prefix(10))
|
||||
} else {
|
||||
expirationDate = nil
|
||||
}
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, piFirstName, physicalState, chemicalName, bldgCode, lab
|
||||
case storageLocation, storageDevice, numberOfContainers, amountPerContainer
|
||||
case unitOfMeasure, casNumber, chemicalFormula, molecularWeight, vendor
|
||||
case catalogNumber, lotNumber, expirationDate, concentration, percentageFull
|
||||
case comments, barcode, contact, scannedImage, needsManualEntry
|
||||
case createdAt, updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user