Manual Entry kinda works
This commit is contained in:
@@ -45,6 +45,9 @@ public final class APIClient: Sendable {
|
||||
req.httpMethod = method
|
||||
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let cookieNames = HTTPCookieStorage.shared.cookies(for: url)?.map(\.name) ?? []
|
||||
print("[APIClient] \(method) \(path) — cookies: \(cookieNames)")
|
||||
|
||||
if let body {
|
||||
do {
|
||||
req.httpBody = try JSONEncoder.api.encode(body)
|
||||
@@ -53,19 +56,31 @@ public final class APIClient: Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
let (data, response) = try await session.data(for: req)
|
||||
let (data, response): (Data, URLResponse)
|
||||
do {
|
||||
(data, response) = try await session.data(for: req)
|
||||
} catch {
|
||||
print("[APIClient] Network error on \(method) \(path): \(error)")
|
||||
throw APIError.networkError(error)
|
||||
}
|
||||
|
||||
guard let http = response as? HTTPURLResponse else {
|
||||
print("[APIClient] Non-HTTP response on \(method) \(path)")
|
||||
throw APIError.networkError(URLError(.badServerResponse))
|
||||
}
|
||||
|
||||
print("[APIClient] \(method) \(path) → \(http.statusCode)")
|
||||
|
||||
if http.statusCode == 401 {
|
||||
// Clear cookies and signal the app
|
||||
print("[APIClient] 401 — clearing session and signalling unauthorized")
|
||||
clearSessionCookies()
|
||||
onUnauthorized?()
|
||||
throw APIError.unauthorized
|
||||
}
|
||||
|
||||
guard (200..<300).contains(http.statusCode) else {
|
||||
let body = String(data: data, encoding: .utf8) ?? "<binary \(data.count)b>"
|
||||
print("[APIClient] HTTP \(http.statusCode) on \(method) \(path): \(body)")
|
||||
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
||||
}
|
||||
|
||||
@@ -136,20 +151,25 @@ public final class APIClient: Sendable {
|
||||
do {
|
||||
return try decoder.decode(type, from: data)
|
||||
} catch {
|
||||
let body = String(data: data, encoding: .utf8) ?? "<binary \(data.count)b>"
|
||||
print("[APIClient] Decoding \(T.self) failed: \(error)\nResponse body: \(body)")
|
||||
throw APIError.decodingError(error)
|
||||
}
|
||||
}
|
||||
|
||||
func clearSessionCookies() {
|
||||
let storage = HTTPCookieStorage.shared
|
||||
// Clear all cookies for the server domain
|
||||
if let cookies = storage.cookies(for: baseURL) {
|
||||
for cookie in cookies {
|
||||
storage.deleteCookie(cookie)
|
||||
}
|
||||
}
|
||||
// Also clear the better-auth session cookie by name
|
||||
// Also sweep all cookies for either session token name variant
|
||||
if let allCookies = storage.cookies {
|
||||
for cookie in allCookies where cookie.name == "better-auth.session_token" {
|
||||
for cookie in allCookies where
|
||||
cookie.name == "better-auth.session_token" ||
|
||||
cookie.name == "__Secure-better-auth.session_token" {
|
||||
storage.deleteCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,13 +172,16 @@ public final class AuthClient: Sendable {
|
||||
|
||||
private func injectSessionCookie(token: String) {
|
||||
guard let serverURL = URL(string: baseURL) else { return }
|
||||
// Set expiry 30 days out so the cookie survives app restarts.
|
||||
// (Without an explicit expiry iOS treats it as a session cookie and clears it on termination.)
|
||||
let expiry = Date().addingTimeInterval(30 * 24 * 60 * 60)
|
||||
let properties: [HTTPCookiePropertyKey: Any] = [
|
||||
.name: "__Secure-better-auth.session_token",
|
||||
.value: token,
|
||||
.domain: serverURL.host ?? "labwise.wahwa.com",
|
||||
.path: "/",
|
||||
.secure: "TRUE",
|
||||
// Session cookie — no explicit expiry; persists until the app clears it.
|
||||
.expires: expiry,
|
||||
]
|
||||
if let cookie = HTTPCookie(properties: properties) {
|
||||
HTTPCookieStorage.shared.setCookie(cookie)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
public struct Chemical: Codable, Identifiable, Sendable {
|
||||
public struct Chemical: Identifiable, Sendable, Encodable {
|
||||
public let id: String
|
||||
public var piFirstName: String
|
||||
public var physicalState: String
|
||||
@@ -29,6 +29,15 @@ public struct Chemical: Codable, Identifiable, Sendable {
|
||||
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 = "",
|
||||
@@ -88,6 +97,47 @@ public struct Chemical: Codable, Identifiable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
// percentageFull arrives as a number OR a numeric string ("12.00") depending on the query path
|
||||
if let d = try? c.decodeIfPresent(Double.self, forKey: .percentageFull) {
|
||||
percentageFull = d
|
||||
} else if let s = try? c.decodeIfPresent(String.self, forKey: .percentageFull) {
|
||||
percentageFull = Double(s)
|
||||
} else {
|
||||
percentageFull = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChemicalCreateBody: Codable, Sendable {
|
||||
public var piFirstName: String
|
||||
public var physicalState: String
|
||||
|
||||
Reference in New Issue
Block a user