SubmittedToTestflight
This commit is contained in:
@@ -433,6 +433,7 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = LabWise/Info.plist;
|
INFOPLIST_FILE = LabWise/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = LabWise;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -446,7 +447,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWise;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWiseApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
@@ -469,6 +470,7 @@
|
|||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = LabWise/Info.plist;
|
INFOPLIST_FILE = LabWise/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = LabWise;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@@ -482,7 +484,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWise;
|
PRODUCT_BUNDLE_IDENTIFIER = com.adipu.LabWiseApp;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
|
"filename" : "LabWiseLogo1.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"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 struct SignInResponse: Decodable, Sendable {
|
||||||
public let data: SignInData?
|
public let token: String?
|
||||||
|
public let user: AuthUser?
|
||||||
public let error: SignInError?
|
public let error: SignInError?
|
||||||
|
|
||||||
public struct SignInData: Decodable, Sendable {
|
|
||||||
public let user: AuthUser
|
|
||||||
public let session: AuthSession
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct SignInError: Decodable, Sendable {
|
public struct SignInError: Decodable, Sendable {
|
||||||
public let message: String?
|
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 struct AuthUser: Decodable, Sendable {
|
||||||
public let id: String
|
public let id: String
|
||||||
@@ -41,10 +31,9 @@ public struct AuthUser: Decodable, Sendable {
|
|||||||
public let image: String?
|
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 {
|
private struct SessionResponse: Decodable, Sendable {
|
||||||
let user: AuthUser?
|
let user: AuthUser?
|
||||||
let session: AuthSession?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class AuthClient: Sendable {
|
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")!)
|
var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/sign-in/email")!)
|
||||||
req.httpMethod = "POST"
|
req.httpMethod = "POST"
|
||||||
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||||
|
req.setValue(baseURL, forHTTPHeaderField: "Origin")
|
||||||
req.httpBody = encoded
|
req.httpBody = encoded
|
||||||
|
|
||||||
let session = sharedURLSession()
|
let session = sharedURLSession()
|
||||||
@@ -73,19 +63,22 @@ public final class AuthClient: Sendable {
|
|||||||
throw APIError.networkError(URLError(.badServerResponse))
|
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)
|
let signInResponse = try JSONDecoder.api.decode(SignInResponse.self, from: data)
|
||||||
if let errMsg = signInResponse.error?.message {
|
if let errMsg = signInResponse.error?.message {
|
||||||
throw APIError.httpError(statusCode: http.statusCode, data: Data(errMsg.utf8))
|
throw APIError.httpError(statusCode: http.statusCode, data: Data(errMsg.utf8))
|
||||||
}
|
}
|
||||||
guard http.statusCode == 200 else {
|
guard let user = signInResponse.user else {
|
||||||
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
|
||||||
}
|
|
||||||
guard let user = signInResponse.data?.user else {
|
|
||||||
throw APIError.decodingError(DecodingError.dataCorrupted(
|
throw APIError.decodingError(DecodingError.dataCorrupted(
|
||||||
.init(codingPath: [], debugDescription: "Missing user in sign-in response")
|
.init(codingPath: [], debugDescription: "Missing user in sign-in response")
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
if let token = signInResponse.token {
|
||||||
|
injectSessionCookie(token: token)
|
||||||
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +187,7 @@ public final class AuthClient: Sendable {
|
|||||||
public func fetchCurrentUser() async throws -> AuthUser {
|
public func fetchCurrentUser() async throws -> AuthUser {
|
||||||
var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/get-session")!)
|
var req = URLRequest(url: URL(string: "\(baseURL)/api/auth/get-session")!)
|
||||||
req.httpMethod = "GET"
|
req.httpMethod = "GET"
|
||||||
|
req.setValue(baseURL, forHTTPHeaderField: "Origin")
|
||||||
let session = sharedURLSession()
|
let session = sharedURLSession()
|
||||||
let (data, response) = try await session.data(for: req)
|
let (data, response) = try await session.data(for: req)
|
||||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
|
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 createdAt: String?
|
||||||
public var updatedAt: 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(
|
public init(
|
||||||
id: String = "",
|
id: String = "",
|
||||||
piFirstName: String = "",
|
piFirstName: String = "",
|
||||||
@@ -100,34 +91,33 @@ public struct Chemical: Identifiable, Sendable, Encodable {
|
|||||||
extension Chemical: Decodable {
|
extension Chemical: Decodable {
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
id = try c.decode(String.self, forKey: .id)
|
id = try c.decode(String.self, forKey: .id)
|
||||||
piFirstName = try c.decode(String.self, forKey: .piFirstName)
|
piFirstName = try c.decode(String.self, forKey: .piFirstName)
|
||||||
physicalState = try c.decode(String.self, forKey: .physicalState)
|
physicalState = try c.decode(String.self, forKey: .physicalState)
|
||||||
chemicalName = try c.decode(String.self, forKey: .chemicalName)
|
chemicalName = try c.decode(String.self, forKey: .chemicalName)
|
||||||
bldgCode = try c.decode(String.self, forKey: .bldgCode)
|
bldgCode = try c.decode(String.self, forKey: .bldgCode)
|
||||||
lab = try c.decode(String.self, forKey: .lab)
|
lab = try c.decode(String.self, forKey: .lab)
|
||||||
storageLocation = try c.decode(String.self, forKey: .storageLocation)
|
storageLocation = try c.decode(String.self, forKey: .storageLocation)
|
||||||
storageDevice = try c.decode(String.self, forKey: .storageDevice)
|
storageDevice = try c.decode(String.self, forKey: .storageDevice)
|
||||||
numberOfContainers = try c.decode(String.self, forKey: .numberOfContainers)
|
numberOfContainers = try c.decode(String.self, forKey: .numberOfContainers)
|
||||||
amountPerContainer = try c.decode(String.self, forKey: .amountPerContainer)
|
amountPerContainer = try c.decode(String.self, forKey: .amountPerContainer)
|
||||||
unitOfMeasure = try c.decode(String.self, forKey: .unitOfMeasure)
|
unitOfMeasure = try c.decode(String.self, forKey: .unitOfMeasure)
|
||||||
casNumber = try c.decode(String.self, forKey: .casNumber)
|
casNumber = try c.decode(String.self, forKey: .casNumber)
|
||||||
chemicalFormula = try c.decodeIfPresent(String.self, forKey: .chemicalFormula)
|
chemicalFormula = try c.decodeIfPresent(String.self, forKey: .chemicalFormula)
|
||||||
molecularWeight = try c.decodeIfPresent(String.self, forKey: .molecularWeight)
|
molecularWeight = try c.decodeIfPresent(String.self, forKey: .molecularWeight)
|
||||||
vendor = try c.decodeIfPresent(String.self, forKey: .vendor)
|
vendor = try c.decodeIfPresent(String.self, forKey: .vendor)
|
||||||
catalogNumber = try c.decodeIfPresent(String.self, forKey: .catalogNumber)
|
catalogNumber = try c.decodeIfPresent(String.self, forKey: .catalogNumber)
|
||||||
lotNumber = try c.decodeIfPresent(String.self, forKey: .lotNumber)
|
lotNumber = try c.decodeIfPresent(String.self, forKey: .lotNumber)
|
||||||
expirationDate = try c.decodeIfPresent(String.self, forKey: .expirationDate)
|
concentration = try c.decodeIfPresent(String.self, forKey: .concentration)
|
||||||
concentration = try c.decodeIfPresent(String.self, forKey: .concentration)
|
comments = try c.decodeIfPresent(String.self, forKey: .comments)
|
||||||
comments = try c.decodeIfPresent(String.self, forKey: .comments)
|
barcode = try c.decodeIfPresent(String.self, forKey: .barcode)
|
||||||
barcode = try c.decodeIfPresent(String.self, forKey: .barcode)
|
contact = try c.decodeIfPresent(String.self, forKey: .contact)
|
||||||
contact = try c.decodeIfPresent(String.self, forKey: .contact)
|
scannedImage = try c.decodeIfPresent(String.self, forKey: .scannedImage)
|
||||||
scannedImage = try c.decodeIfPresent(String.self, forKey: .scannedImage)
|
needsManualEntry = try c.decodeIfPresent([String].self, forKey: .needsManualEntry)
|
||||||
needsManualEntry = try c.decodeIfPresent([String].self, forKey: .needsManualEntry)
|
createdAt = try c.decodeIfPresent(String.self, forKey: .createdAt)
|
||||||
createdAt = try c.decodeIfPresent(String.self, forKey: .createdAt)
|
updatedAt = try c.decodeIfPresent(String.self, forKey: .updatedAt)
|
||||||
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) {
|
if let d = try? c.decodeIfPresent(Double.self, forKey: .percentageFull) {
|
||||||
percentageFull = d
|
percentageFull = d
|
||||||
} else if let s = try? c.decodeIfPresent(String.self, forKey: .percentageFull) {
|
} else if let s = try? c.decodeIfPresent(String.self, forKey: .percentageFull) {
|
||||||
@@ -135,6 +125,23 @@ extension Chemical: Decodable {
|
|||||||
} else {
|
} else {
|
||||||
percentageFull = nil
|
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