Skip to content

Commit

Permalink
Introduce module for firebase references
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Aug 21, 2024
1 parent 625f98a commit 924620e
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 79 deletions.
12 changes: 12 additions & 0 deletions TemplateApplication.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; };
A92E4DF02BAA001100AC8DE8 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = A92E4DEF2BAA001100AC8DE8 /* OrderedCollections */; };
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */; };
A9A3DCC82C75CBBD00FC9B69 /* FirestoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A3DCC72C75CB9A00FC9B69 /* FirestoreConfiguration.swift */; };
A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */; };
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; };
Expand Down Expand Up @@ -130,6 +131,7 @@
653A256B28338800005D4D48 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = "<group>"; };
653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupHeader.swift; sourceTree = "<group>"; };
A9A3DCC72C75CB9A00FC9B69 /* FirestoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestoreConfiguration.swift; sourceTree = "<group>"; };
A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = "<group>"; };
A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -284,6 +286,7 @@
2F4E23822989D51F0013F3D9 /* TemplateApplicationTestingSetup.swift */,
A9720E412ABB68B300872D23 /* Account */,
2FE5DC2729EDD38D004B9AB4 /* Contacts */,
A9A3DCC62C75CB8D00FC9B69 /* Firestore */,
2FE5DC2829EDD398004B9AB4 /* Onboarding */,
2FE5DC2D29EDD792004B9AB4 /* Resources */,
2FE5DC3B29EDD7D0004B9AB4 /* Schedule */,
Expand Down Expand Up @@ -329,6 +332,14 @@
path = Account;
sourceTree = "<group>";
};
A9A3DCC62C75CB8D00FC9B69 /* Firestore */ = {
isa = PBXGroup;
children = (
A9A3DCC72C75CB9A00FC9B69 /* FirestoreConfiguration.swift */,
);
path = Firestore;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -524,6 +535,7 @@
2FC975A82978F11A00BA99FE /* Home.swift in Sources */,
2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */,
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */,
A9A3DCC82C75CBBD00FC9B69 /* FirestoreConfiguration.swift in Sources */,
2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */,
2F1AC9DF2B4E840E00C24973 /* TemplateApplication.docc in Sources */,
2FF53D8D2A8729D600042B76 /* TemplateApplicationStandard.swift in Sources */,
Expand Down
94 changes: 94 additions & 0 deletions TemplateApplication/Firestore/FirestoreConfiguration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// This source file is part of the Stanford Spezi Template Application open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import FirebaseFirestore
import FirebaseStorage
import Spezi
import SpeziAccount
import SpeziFirebaseAccount


final class FireConfiguration: Module, DefaultInitializable, @unchecked Sendable {
enum ConfigurationError: Error {
case userNotAuthenticatedYet
}

static var userCollection: CollectionReference {
Firestore.firestore().collection("users")
}


@MainActor var userDocumentReference: DocumentReference {
get throws {
guard let details = account?.details else {
throw ConfigurationError.userNotAuthenticatedYet
}

return userDocumentReference(for: details.accountId)
}
}

@MainActor var userBucketReference: StorageReference {
get throws {
guard let details = account?.details else {
throw ConfigurationError.userNotAuthenticatedYet
}

return Storage.storage().reference().child("users/\(details.accountId)")
}
}

@Application(\.logger) private var logger

@Dependency(Account.self) private var account: Account? // optional, as Firebase might be disabled
@Dependency(FirebaseAccountService.self) private var accountService: FirebaseAccountService?

init() {}

func userDocumentReference(for accountId: String) -> DocumentReference {
Self.userCollection.document(accountId)
}


func configure() {
Task {
await setupTestAccount()
}
}


private func setupTestAccount() async {
guard let accountService, FeatureFlags.setupTestAccount else {
return
}

do {
try await accountService.login(userId: "[email protected]", password: "StanfordRocks!")
return
} catch {
guard let accountError = error as? FirebaseAccountError,
case .invalidCredentials = accountError else {
logger.error("Failed to login into test account: \(error)")
return
}
}

// account doesn't exist yet, signup
var details = AccountDetails()
details.userId = "[email protected]"
details.password = "StanfordRocks!"
details.name = PersonNameComponents(givenName: "Leland", familyName: "Stanford")
details.genderIdentity = .male

do {
try await accountService.signUp(with: details)
} catch {
logger.error("Failed to setup test account: \(error)")
}
}
}
1 change: 0 additions & 1 deletion TemplateApplication/TemplateApplicationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// SPDX-License-Identifier: MIT
//

// TODO: why are these two imports now required?
import class FirebaseFirestore.FirestoreSettings
import class FirebaseFirestore.MemoryCacheSettings
import Spezi
Expand Down
83 changes: 5 additions & 78 deletions TemplateApplication/TemplateApplicationStandard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,56 +26,13 @@ actor TemplateApplicationStandard: Standard,
HealthKitConstraint,
OnboardingConstraint,
AccountNotifyConstraint {
enum TemplateApplicationStandardError: Error {
case userNotAuthenticatedYet
}

static var userCollection: CollectionReference {
Firestore.firestore().collection("users")
}

@Application(\.logger) private var logger

@Dependency(Account.self) private var account: Account? // optional, as Firebase might be disabled
@Dependency(FirebaseAccountService.self) private var accountService: FirebaseAccountService?


private var userDocumentReference: DocumentReference {
get async throws {
guard let details = await account?.details else {
throw TemplateApplicationStandardError.userNotAuthenticatedYet
}

return userDocumentReference(for: details.accountId)
}
}

private var userBucketReference: StorageReference {
get async throws {
guard let details = await account?.details else {
throw TemplateApplicationStandardError.userNotAuthenticatedYet
}

return Storage.storage().reference().child("users/\(details.accountId)")
}
}

@Dependency(FireConfiguration.self) private var configuration

init() {}


private func userDocumentReference(for accountId: String) -> DocumentReference {
Self.userCollection.document(accountId)
}


nonisolated func configure() {
Task {
await setupTestAccount()
}
}


func add(sample: HKSample) async {
if FeatureFlags.disableFirebase {
logger.debug("Received new HealthKit sample: \(sample)")
Expand Down Expand Up @@ -113,7 +70,7 @@ actor TemplateApplicationStandard: Standard,
}

do {
try await userDocumentReference
try await configuration.userDocumentReference
.collection("QuestionnaireResponse") // Add all HealthKit sources in a /QuestionnaireResponse collection.
.document(id) // Set the document identifier to the id of the response.
.setData(from: response)
Expand All @@ -124,15 +81,15 @@ actor TemplateApplicationStandard: Standard,


private func healthKitDocument(id uuid: UUID) async throws -> DocumentReference {
try await userDocumentReference
try await configuration.userDocumentReference
.collection("HealthKit") // Add all HealthKit sources in a /HealthKit collection.
.document(uuid.uuidString) // Set the document identifier to the UUID of the document.
}

func respondToEvent(_ event: AccountNotifications.Event) async {
if case let .deletingAccount(accountId) = event {
do {
try await userDocumentReference(for: accountId).delete()
try await configuration.userDocumentReference(for: accountId).delete()
} catch {
logger.error("Could not delete user document: \(error)")
}
Expand Down Expand Up @@ -168,41 +125,11 @@ actor TemplateApplicationStandard: Standard,

let metadata = StorageMetadata()
metadata.contentType = "application/pdf"
_ = try await userBucketReference
_ = try await configuration.userBucketReference
.child("consent/\(dateString).pdf")
.putDataAsync(consentData, metadata: metadata) { @Sendable _ in }
} catch {
await logger.error("Could not store consent form: \(error)")
}
}

private func setupTestAccount() async {
guard let accountService, FeatureFlags.setupTestAccount else {
return
}

do {
try await accountService.login(userId: "[email protected]", password: "StanfordRocks!")
return
} catch {
guard let accountError = error as? FirebaseAccountError,
case .invalidCredentials = accountError else {
logger.error("Failed to login into test account: \(error)")
return
}
}

// account doesn't exist yet, signup
var details = AccountDetails()
details.userId = "[email protected]"
details.password = "StanfordRocks!"
details.name = PersonNameComponents(givenName: "Leland", familyName: "Stanford")
details.genderIdentity = .male

do {
try await accountService.signUp(with: details)
} catch {
logger.error("Failed to setup test account: \(error)")
}
}
}

0 comments on commit 924620e

Please sign in to comment.