Manual Entry kinda works

This commit is contained in:
2026-03-20 02:30:15 -05:00
parent 2110c13ea1
commit 310d9faf33
9 changed files with 1029 additions and 88 deletions

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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