From d1167a9da6a34cb6b1d7a5b274e3dd7d75f85955 Mon Sep 17 00:00:00 2001 From: mathieu J Date: Mon, 12 Feb 2024 22:22:18 +0200 Subject: [PATCH 01/10] :label: (AccountKit): Database operations errors and collections types --- .../Database/DatabaseCollections.swift | 11 ++++++++++ .../Sources/Database/DatabaseErrors.swift | 12 +++++++++++ .../Sources/Database/DatabaseOperations.swift | 21 +++++++++++++++++++ .../Extensions/JSONDecoderExtension.swift | 12 +++++++++++ 4 files changed, 56 insertions(+) create mode 100644 Modules/AccountKit/Sources/Database/DatabaseCollections.swift create mode 100644 Modules/AccountKit/Sources/Database/DatabaseErrors.swift create mode 100644 Modules/AccountKit/Sources/Database/DatabaseOperations.swift create mode 100644 Modules/AccountKit/Sources/Extensions/JSONDecoderExtension.swift diff --git a/Modules/AccountKit/Sources/Database/DatabaseCollections.swift b/Modules/AccountKit/Sources/Database/DatabaseCollections.swift new file mode 100644 index 0000000000..4f264dafe8 --- /dev/null +++ b/Modules/AccountKit/Sources/Database/DatabaseCollections.swift @@ -0,0 +1,11 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +public enum DatabaseCollection: String { + case rootAccounts + case caregivers + case carereceivers +} diff --git a/Modules/AccountKit/Sources/Database/DatabaseErrors.swift b/Modules/AccountKit/Sources/Database/DatabaseErrors.swift new file mode 100644 index 0000000000..a6813c3b2d --- /dev/null +++ b/Modules/AccountKit/Sources/Database/DatabaseErrors.swift @@ -0,0 +1,12 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +public enum DatabaseError: Error { + case customError(String) + case documentNotFound + case decodeError + case encodeError +} diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift new file mode 100644 index 0000000000..ab5e92d70b --- /dev/null +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -0,0 +1,21 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import Combine +import FirebaseAuth +import FirebaseFirestore + +public class DatabaseOperations { + // MARK: Lifecycle + + public init() { + // Just to expose the init publicly + } + + // CRUD methods here + + // MARK: Private + + private let database = Firestore.firestore() +} diff --git a/Modules/AccountKit/Sources/Extensions/JSONDecoderExtension.swift b/Modules/AccountKit/Sources/Extensions/JSONDecoderExtension.swift new file mode 100644 index 0000000000..4535e6c0fd --- /dev/null +++ b/Modules/AccountKit/Sources/Extensions/JSONDecoderExtension.swift @@ -0,0 +1,12 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +extension JSONDecoder { + func decode(_: T.Type, fromJSONObject object: Any) throws -> T { + let data = try JSONSerialization.data(withJSONObject: object) + return try self.decode(T.self, from: data) + } +} From 94a04582371bf614ddaa8f6414b861475eace3ef Mon Sep 17 00:00:00 2001 From: mathieu J Date: Mon, 12 Feb 2024 22:25:51 +0200 Subject: [PATCH 02/10] :sparkles: (AccountKit): Add AccountDocument protocol to RootAccount type --- Modules/AccountKit/Sources/Models/RootAccount.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/AccountKit/Sources/Models/RootAccount.swift b/Modules/AccountKit/Sources/Models/RootAccount.swift index ac0359ffbe..b1282c452a 100644 --- a/Modules/AccountKit/Sources/Models/RootAccount.swift +++ b/Modules/AccountKit/Sources/Models/RootAccount.swift @@ -4,9 +4,18 @@ import SwiftUI +// MARK: - AccountDocument + +protocol AccountDocument: Codable, Identifiable { + var id: String { get set } + var rootOwnerUid: String { get set } + var createdAt: Date { get set } + var lastEditedAt: Date { get set } +} + // MARK: - RootAccount -struct RootAccount: Identifiable, Codable { +struct RootAccount: AccountDocument { var id: String var rootOwnerUid: String var createdAt: Date From 5e9cbbe7f209d533b753138901ee9b0a9b185e5c Mon Sep 17 00:00:00 2001 From: mathieu J Date: Mon, 12 Feb 2024 22:29:05 +0200 Subject: [PATCH 03/10] :sparkles: (AccountKit): Add RootOwner CodingKeys and Firestore PropertyWrappers --- .../Sources/Models/RootAccount.swift | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Modules/AccountKit/Sources/Models/RootAccount.swift b/Modules/AccountKit/Sources/Models/RootAccount.swift index b1282c452a..1dbde8cc93 100644 --- a/Modules/AccountKit/Sources/Models/RootAccount.swift +++ b/Modules/AccountKit/Sources/Models/RootAccount.swift @@ -2,22 +2,30 @@ // Copyright APF France handicap // SPDX-License-Identifier: Apache-2.0 +import FirebaseFirestore import SwiftUI // MARK: - AccountDocument -protocol AccountDocument: Codable, Identifiable { - var id: String { get set } +public protocol AccountDocument: Codable, Identifiable { + var id: String? { get set } var rootOwnerUid: String { get set } - var createdAt: Date { get set } - var lastEditedAt: Date { get set } + var createdAt: Date? { get set } + var lastEditedAt: Date? { get set } } // MARK: - RootAccount struct RootAccount: AccountDocument { - var id: String + enum CodingKeys: String, CodingKey { + case id + case rootOwnerUid = "root_owner_uid" + case createdAt = "created_at" + case lastEditedAt = "last_edited_at" + } + + @DocumentID var id: String? var rootOwnerUid: String - var createdAt: Date - var lastEditedAt: Date + @ServerTimestamp var createdAt: Date? + @ServerTimestamp var lastEditedAt: Date? } From 0930b262e3a44e5c4d9cab6343ccb17704229dee Mon Sep 17 00:00:00 2001 From: mathieu J Date: Mon, 12 Feb 2024 23:06:36 +0200 Subject: [PATCH 04/10] :sparkles: (AccountKit): Add create function and make protocol public --- .../Sources/Database/DatabaseOperations.swift | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index ab5e92d70b..845a9532e5 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -13,7 +13,29 @@ public class DatabaseOperations { // Just to expose the init publicly } - // CRUD methods here + // MARK: Public + + public func create(data: T, in collection: DatabaseCollection) -> AnyPublisher { + Future { promise in + let docRef = self.database.collection(collection.rawValue).document() + + var documentData = data + documentData.rootOwnerUid = Auth.auth().currentUser?.uid ?? "" + + do { + try docRef.setData(from: documentData) { error in + if let error { + promise(.failure(DatabaseError.customError(error.localizedDescription))) + } else { + promise(.success(documentData)) + } + } + } catch { + promise(.failure(error)) + } + } + .eraseToAnyPublisher() + } // MARK: Private From af70dcb5c138650c48a93982634e92e2f0b83067 Mon Sep 17 00:00:00 2001 From: mathieu J Date: Mon, 12 Feb 2024 23:23:35 +0200 Subject: [PATCH 05/10] :sparkles: (AccountKit): Add read function --- .../Sources/Database/DatabaseOperations.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index 845a9532e5..4943f69d9c 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -37,6 +37,29 @@ public class DatabaseOperations { .eraseToAnyPublisher() } + public func read(from collection: DatabaseCollection, documentID: String) -> AnyPublisher { + Future { promise in + let docRef = self.database.collection(collection.rawValue).document(documentID) + docRef.getDocument { document, error in + if let error { + promise(.failure(error)) + } else { + do { + let object = try document?.data(as: T.self) + if let object { + promise(.success(object)) + } else { + promise(.failure(DatabaseError.documentNotFound)) + } + } catch { + promise(.failure(error)) + } + } + } + } + .eraseToAnyPublisher() + } + // MARK: Private private let database = Firestore.firestore() From bbf19c111c06e078779350efe0eac438e22ed3a2 Mon Sep 17 00:00:00 2001 From: mathieu J Date: Tue, 13 Feb 2024 14:38:05 +0200 Subject: [PATCH 06/10] :sparkles: (AccountKit): Add method readAll --- .../Sources/Database/DatabaseOperations.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index 4943f69d9c..d01d112a27 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -60,6 +60,23 @@ public class DatabaseOperations { .eraseToAnyPublisher() } + public func readAll(from collection: DatabaseCollection) -> AnyPublisher<[T], Error> { + Future<[T], Error> { promise in + self.database.collection(collection.rawValue) + .getDocuments { querySnapshot, error in + if let error { + promise(.failure(error)) + } else { + let objects = querySnapshot?.documents.compactMap { document -> T? in + try? document.data(as: T.self) + } ?? [] + promise(.success(objects)) + } + } + } + .eraseToAnyPublisher() + } + // MARK: Private private let database = Firestore.firestore() From db9e92187d8e3ba6a72fe8cced65774684c028fa Mon Sep 17 00:00:00 2001 From: mathieu J Date: Wed, 14 Feb 2024 16:59:55 +0200 Subject: [PATCH 07/10] :loud_sound: (AccountKit): Add logs to DatabaseOperations --- .../AccountKit/Sources/Database/DatabaseOperations.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index d01d112a27..3ebc8581c2 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -25,12 +25,15 @@ public class DatabaseOperations { do { try docRef.setData(from: documentData) { error in if let error { + log.error("\(error.localizedDescription)") promise(.failure(DatabaseError.customError(error.localizedDescription))) } else { + log.info("Document \(String(describing: documentData.id)) created successfully. 🎉") promise(.success(documentData)) } } } catch { + log.error("\(error.localizedDescription)") promise(.failure(error)) } } @@ -42,16 +45,20 @@ public class DatabaseOperations { let docRef = self.database.collection(collection.rawValue).document(documentID) docRef.getDocument { document, error in if let error { + log.error("\(error.localizedDescription)") promise(.failure(error)) } else { do { let object = try document?.data(as: T.self) if let object { + log.info("Document \(String(describing: object.id)) fetched successfully. 🎉") promise(.success(object)) } else { + log.error("Document not found.") promise(.failure(DatabaseError.documentNotFound)) } } catch { + log.error("\(error.localizedDescription)") promise(.failure(error)) } } @@ -65,11 +72,13 @@ public class DatabaseOperations { self.database.collection(collection.rawValue) .getDocuments { querySnapshot, error in if let error { + log.error("\(error.localizedDescription)") promise(.failure(error)) } else { let objects = querySnapshot?.documents.compactMap { document -> T? in try? document.data(as: T.self) } ?? [] + log.info("\(String(describing: objects.count)) documents fetched successfully. 🎉") promise(.success(objects)) } } From 28df00b93c657016a3123fd3ffec39f225dc3142 Mon Sep 17 00:00:00 2001 From: mathieu J Date: Wed, 14 Feb 2024 17:21:45 +0200 Subject: [PATCH 08/10] :sparkles: (AccountKit): Add DatabaseOperations shared instance --- Modules/AccountKit/Sources/Database/DatabaseOperations.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index 3ebc8581c2..0d42842dd8 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -15,6 +15,8 @@ public class DatabaseOperations { // MARK: Public + public static let shared = DatabaseOperations() + public func create(data: T, in collection: DatabaseCollection) -> AnyPublisher { Future { promise in let docRef = self.database.collection(collection.rawValue).document() From e930f722410f2a797b99b41dbf2337c5a4a861bb Mon Sep 17 00:00:00 2001 From: mathieu J Date: Fri, 16 Feb 2024 15:48:26 +0200 Subject: [PATCH 09/10] :adhesive_bandage: (AccountKit): Edit create method to return actual documentID --- .../Sources/Database/DatabaseOperations.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index 0d42842dd8..0dceecdde5 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -17,27 +17,26 @@ public class DatabaseOperations { public static let shared = DatabaseOperations() - public func create(data: T, in collection: DatabaseCollection) -> AnyPublisher { - Future { promise in - let docRef = self.database.collection(collection.rawValue).document() - + public func create(data: some AccountDocument, in collection: DatabaseCollection) -> AnyPublisher { + Future { promise in var documentData = data documentData.rootOwnerUid = Auth.auth().currentUser?.uid ?? "" + let docRef = self.database.collection(collection.rawValue).document() + do { try docRef.setData(from: documentData) { error in if let error { - log.error("\(error.localizedDescription)") promise(.failure(DatabaseError.customError(error.localizedDescription))) - } else { - log.info("Document \(String(describing: documentData.id)) created successfully. 🎉") - promise(.success(documentData)) } } } catch { log.error("\(error.localizedDescription)") - promise(.failure(error)) + promise(.failure(DatabaseError.encodeError)) } + + log.info("Document \(docRef.documentID) created successfully in \(collection). 🎉") + promise(.success(docRef.documentID)) } .eraseToAnyPublisher() } From 198ccdd8d2df52104c7c10f9b4a4804cb20a0d9b Mon Sep 17 00:00:00 2001 From: mathieu J Date: Sun, 18 Feb 2024 22:24:13 +0200 Subject: [PATCH 10/10] :adhesive_bandage: (AccountKit): Fix issue with create function always succeeding --- .../AccountKit/Sources/Database/DatabaseOperations.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift index 0dceecdde5..680626e6d9 100644 --- a/Modules/AccountKit/Sources/Database/DatabaseOperations.swift +++ b/Modules/AccountKit/Sources/Database/DatabaseOperations.swift @@ -27,16 +27,17 @@ public class DatabaseOperations { do { try docRef.setData(from: documentData) { error in if let error { + log.error("\(error.localizedDescription)") promise(.failure(DatabaseError.customError(error.localizedDescription))) + } else { + log.info("Document \(docRef.documentID) created successfully in \(collection). 🎉") + promise(.success(docRef.documentID)) } } } catch { log.error("\(error.localizedDescription)") promise(.failure(DatabaseError.encodeError)) } - - log.info("Document \(docRef.documentID) created successfully in \(collection). 🎉") - promise(.success(docRef.documentID)) } .eraseToAnyPublisher() }