SubmittedToTestflight
This commit is contained in:
@@ -433,6 +433,7 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = LabWise/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = LabWise;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
@@ -446,7 +447,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWise;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWiseApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
@@ -469,6 +470,7 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = LabWise/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = LabWise;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
@@ -482,7 +484,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWise;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWiseApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LabWiseLogo1.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
||||
BIN
LabWise/Assets.xcassets/AppIcon.appiconset/LabWiseLogo1.png
Normal file
BIN
LabWise/Assets.xcassets/AppIcon.appiconset/LabWiseLogo1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 374 KiB |
@@ -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 = "",
|
||||
@@ -117,7 +108,6 @@ extension Chemical: Decodable {
|
||||
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)
|
||||
@@ -127,7 +117,7 @@ extension Chemical: Decodable {
|
||||
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