diff --git a/LabWise.xcodeproj/project.pbxproj b/LabWise.xcodeproj/project.pbxproj index 54e230d..03299f6 100644 --- a/LabWise.xcodeproj/project.pbxproj +++ b/LabWise.xcodeproj/project.pbxproj @@ -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; diff --git a/LabWise/Assets.xcassets/AppIcon.appiconset/Contents.json b/LabWise/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..b32eb88 100644 --- a/LabWise/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/LabWise/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "LabWiseLogo1.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/LabWise/Assets.xcassets/AppIcon.appiconset/LabWiseLogo1.png b/LabWise/Assets.xcassets/AppIcon.appiconset/LabWiseLogo1.png new file mode 100644 index 0000000..e2a276d Binary files /dev/null and b/LabWise/Assets.xcassets/AppIcon.appiconset/LabWiseLogo1.png differ diff --git a/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift b/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift index 894e689..1cbf236 100644 --- a/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift +++ b/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift @@ -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 { diff --git a/LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift b/LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift index f93c3ed..6008b7c 100644 --- a/LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift +++ b/LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift @@ -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 } }