Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invitation Codes #54

Merged
merged 45 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3226bad
Helpers for invitation codes
MatthewTurk247 Mar 10, 2024
97c9e2c
Simplify `invitationCodeIsValid`
MatthewTurk247 Mar 10, 2024
b51a37b
Some documentation for storing codes
MatthewTurk247 Mar 11, 2024
dd54d73
Update user document with invitation code
MatthewTurk247 Mar 11, 2024
b74930c
First attempt at `beforecreated`
MatthewTurk247 Mar 16, 2024
7224490
Init functions folder
MatthewTurk247 Mar 16, 2024
b2a369a
Pointing to (emulator) cloud functions
MatthewTurk247 Mar 16, 2024
af08661
Fixed typo
MatthewTurk247 Mar 16, 2024
655ba46
Remove leftover lines
MatthewTurk247 Mar 16, 2024
ea86a58
Use emulator depending on flag
MatthewTurk247 Mar 17, 2024
e6a2e15
Fixed typo
MatthewTurk247 Mar 21, 2024
af20323
Merge branch 'main' into invitation-codes
PSchmiedmayer Mar 21, 2024
28f9948
Smaller things noticed during the review
PSchmiedmayer Mar 22, 2024
c192752
Check that user doc exists and contains invitation
MatthewTurk247 Mar 25, 2024
113fb7c
Update to reflect the headers of the PAWS project
MatthewTurk247 Mar 25, 2024
8a732e9
Invitation codes work in emulator
MatthewTurk247 Mar 27, 2024
9ad7326
Up and running with `beforecreated`
MatthewTurk247 Mar 27, 2024
eb3e0a9
Add license files
MatthewTurk247 Mar 27, 2024
840accb
Add copyright and licensing for `functions/*.json`
MatthewTurk247 Mar 27, 2024
7470cbd
Outline `AccountCreationTests`
MatthewTurk247 Mar 27, 2024
1f50398
Remove empty test case
MatthewTurk247 Mar 27, 2024
ba71beb
Update PAWS/Onboarding/InvitationCodeError.swift
MatthewTurk247 Mar 27, 2024
8260bd9
Minor revision to logic
MatthewTurk247 Mar 27, 2024
a801ca5
Fixed typo
MatthewTurk247 Mar 27, 2024
73df52a
Additional emulator setup
MatthewTurk247 Mar 28, 2024
0d683a6
Additional emulator setup
MatthewTurk247 Mar 29, 2024
fde6b56
Add MIT license info for `firebase` directory
MatthewTurk247 Mar 29, 2024
010cda3
UI test adaptations from SpeziTemplateApplication
MatthewTurk247 Mar 29, 2024
afa6c74
Minor refactoring
MatthewTurk247 Mar 29, 2024
2d24d83
Improved UI tests for date picker
MatthewTurk247 Mar 30, 2024
5353ea9
Refactor UI tests
MatthewTurk247 Apr 2, 2024
951349f
Merge branch 'main' of https://github.com/StanfordBDHG/PediatricApple…
MatthewTurk247 Apr 2, 2024
cfe3988
Upgrade to latest version of HealthKitOnFHIR
MatthewTurk247 Apr 3, 2024
953c8dd
Upgrade SpeziFirebase to latest version
MatthewTurk247 Apr 4, 2024
a6a3657
Refactor cloud functions
MatthewTurk247 Apr 4, 2024
5ec516e
Secondary button for `InvitationCodeView`
MatthewTurk247 Apr 4, 2024
de1bd78
`testOnboardingFlow` is almost there
MatthewTurk247 Apr 5, 2024
30ca5e6
Minor refactoring
MatthewTurk247 Apr 5, 2024
61e17c0
Remove leftover code
MatthewTurk247 Apr 5, 2024
d7b439a
Adjust strings and indentation
MatthewTurk247 Apr 7, 2024
d8161a0
Update InvitationCodeView.swift
MatthewTurk247 Apr 7, 2024
0001e56
Update InvitationCodeView.swift
MatthewTurk247 Apr 7, 2024
2cdf86c
License headers for package-lock.json and package.json
MatthewTurk247 Apr 7, 2024
b7a39b1
Remove leftover lines
MatthewTurk247 Apr 7, 2024
91d3be6
Rerun tests
MatthewTurk247 Apr 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
artifactname: PAWS.xcresult
runsonlabels: '["macOS", "self-hosted"]'
setupfirebaseemulator: true
customcommand: "firebase emulators:exec 'fastlane test'"
customcommand: "npm install --prefix ./functions && firebase emulators:exec --import=./firebase 'fastlane test'"
uploadcoveragereport:
name: Upload Coverage Report
needs: buildandtest
Expand Down
5 changes: 4 additions & 1 deletion .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ Copyright: 2024 Stanford University and the project authors (see CONTRIBUTORS.md
License: MIT
Comment: All files are part of the Stanford Spezi Data Pipeline Template open source project.


Files: firebase/*
Copyright: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
License: MIT
Comment: All files are part of the PAWS application based on the Stanford Spezi Template Application project.
24 changes: 22 additions & 2 deletions PAWS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; };
B28A0DDC2BA4AEDE0068258D /* Date+Bool.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28A0DDB2BA4AEDE0068258D /* Date+Bool.swift */; };
B2DB1AD32BB4BCB100B0F49B /* AccountCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */; };
B2F7F1DC2BA53F6400BE93BE /* InvitationCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */; };
B2F7F1DF2BA540BB00BE93BE /* FirebaseFunctions in Frameworks */ = {isa = PBXBuildFile; productRef = B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */; };
B2F7F1E22BA549A900BE93BE /* InvitationCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -145,6 +149,9 @@
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>"; };
B28A0DDB2BA4AEDE0068258D /* Date+Bool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Bool.swift"; sourceTree = "<group>"; };
B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreationTests.swift; sourceTree = "<group>"; };
B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCodeView.swift; sourceTree = "<group>"; };
B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCodeError.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -169,6 +176,7 @@
2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */,
2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */,
2F49B7762980407C00BCB272 /* Spezi in Frameworks */,
B2F7F1DF2BA540BB00BE93BE /* FirebaseFunctions in Frameworks */,
2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */,
2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */,
2FBD738C2A3BD150004228E7 /* SpeziScheduler in Frameworks */,
Expand Down Expand Up @@ -239,6 +247,8 @@
2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */,
2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */,
2FE5DC3229EDD7CA004B9AB4 /* InterestingModules.swift */,
B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */,
B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */,
2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */,
2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */,
2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */,
Expand Down Expand Up @@ -367,6 +377,7 @@
isa = PBXGroup;
children = (
2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */,
B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */,
);
path = PAWSUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -429,6 +440,7 @@
9739A0C52AD7B5730084BEA5 /* FirebaseStorage */,
97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */,
A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */,
B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */,
);
productName = PAWS;
productReference = 653A254D283387FE005D4D48 /* PAWS.app */;
Expand Down Expand Up @@ -587,6 +599,7 @@
2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */,
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */,
2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */,
B2F7F1DC2BA53F6400BE93BE /* InvitationCodeView.swift in Sources */,
2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */,
2FFD22FC2B59AED8005DD268 /* FAQ.swift in Sources */,
2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */,
Expand All @@ -601,6 +614,7 @@
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */,
653A2551283387FE005D4D48 /* PAWS.swift in Sources */,
2FFD22F62B59ABE2005DD268 /* PAWSCard.swift in Sources */,
B2F7F1E22BA549A900BE93BE /* InvitationCodeError.swift in Sources */,
2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */,
2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */,
5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */,
Expand All @@ -621,6 +635,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2DB1AD32BB4BCB100B0F49B /* AccountCreationTests.swift in Sources */,
2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1157,7 +1172,7 @@
repositoryURL = "https://github.com/StanfordBDHG/HealthKitOnFHIR.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 0.2.4;
minimumVersion = 0.2.7;
};
};
2FCC1DCE2B6A2CE000C686BE /* XCRemoteSwiftPackageReference "SwiftLint" */ = {
Expand Down Expand Up @@ -1197,7 +1212,7 @@
repositoryURL = "https://github.com/StanfordSpezi/SpeziFirebase.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
minimumVersion = 1.1.0;
};
};
2FE5DC8829EDD972004B9AB4 /* XCRemoteSwiftPackageReference "SpeziStorage" */ = {
Expand Down Expand Up @@ -1396,6 +1411,11 @@
package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */;
productName = SpeziFirebaseAccountStorage;
};
B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */ = {
isa = XCSwiftPackageProductDependency;
package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseFunctions;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 653A2545283387FE005D4D48 /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/HealthKitOnFHIR.git",
"state" : {
"revision" : "00d64d38a8f0d826ee9e27b6f3ce32314a29fd3e",
"version" : "0.2.6"
"revision" : "d6ceecf11800d73fed0c6ce33717f3dc71a44bd7",
"version" : "0.2.7"
}
},
{
Expand Down Expand Up @@ -168,8 +168,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziAccount.git",
"state" : {
"revision" : "a7d289ef3be54de62b25dc92e8f7ff1a0f093906",
"version" : "1.2.1"
"revision" : "cb9441e5fe9ca31a17be2507d03817a080e63e9d",
"version" : "1.2.2"
}
},
{
Expand All @@ -186,8 +186,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziFirebase.git",
"state" : {
"revision" : "e05e665b7da39aa399ecd7fba393aab49b8f3034",
"version" : "1.0.1"
"revision" : "16c1c751c14b08ae593eacf9bc2752c2e070fe2f",
"version" : "1.1.0"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion PAWS/ECGRecordings/ECGRecording.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
var body: some View {
PAWSCard {
VStack(alignment: .leading) {
Text("EEG Recording")
Text("ECG Recording")

Check warning on line 21 in PAWS/ECGRecordings/ECGRecording.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/ECGRecordings/ECGRecording.swift#L21

Added line #L21 was not covered by tests
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
.font(.title)
Text(electrocardiogram.endDate.formatted())
.font(.subheadline)
Expand Down
27 changes: 27 additions & 0 deletions PAWS/Onboarding/InvitationCodeError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// This source file is part of the PAWS application based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation


enum InvitationCodeError: LocalizedError {
case invitationCodeInvalid
case userNotAuthenticated
case generalError(String)

var errorDescription: String? {
switch self {
case .invitationCodeInvalid:
String(localized: "The invitation code is invalid or has already been used.", comment: "Invitation Code Invalid")
case .userNotAuthenticated:
String(localized: "User authentication failed. Please try to sign in again.", comment: "User Not Authenticated")
case .generalError(let message):
String(localized: "An error occurred: \(message)", comment: "General Error")
}
}

Check warning on line 26 in PAWS/Onboarding/InvitationCodeError.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeError.swift#L17-L26

Added lines #L17 - L26 were not covered by tests
}
157 changes: 157 additions & 0 deletions PAWS/Onboarding/InvitationCodeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//
// This source file is part of the PAWS application based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Firebase
import FirebaseAuth
import FirebaseFunctions
import SpeziOnboarding
import SpeziValidation
import SpeziViews
import SwiftUI


struct InvitationCodeView: View {
@Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath
@State private var invitationCode = ""
@State private var viewState: ViewState = .idle
@ValidationState private var validation


var body: some View {
ScrollView {
VStack(spacing: 32) {
invitationCodeHeader
Divider()
Grid(horizontalSpacing: 16, verticalSpacing: 16) {
invitationCodeView
}
.padding(.top, -8)
.padding(.bottom, -12)
Divider()
OnboardingActionsView(
primaryText: "Redeem Invitation Code",
primaryAction: {
guard validation.validateSubviews() else {
return

Check warning on line 40 in PAWS/Onboarding/InvitationCodeView.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeView.swift#L40

Added line #L40 was not covered by tests
}

await verifyOnboardingCode()
},
secondaryText: "I Already Have an Account",
secondaryAction: {
try Auth.auth().signOut()
onboardingNavigationPath.nextStep()
}

Check warning on line 49 in PAWS/Onboarding/InvitationCodeView.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeView.swift#L47-L49

Added lines #L47 - L49 were not covered by tests
)
}
.padding(.horizontal)
.padding(.bottom)
.viewStateAlert(state: $viewState)
.navigationBarTitleDisplayMode(.large)
.navigationTitle(String(localized: "Invitation Code"))
}
}


@ViewBuilder private var invitationCodeView: some View {
DescriptionGridRow {
Text("Invitation Code")
} content: {
VerifiableTextField(
LocalizedStringResource("Invitation Code"),
text: $invitationCode
)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.characters)
.textContentType(.oneTimeCode)
.validate(input: invitationCode, rules: [invitationCodeValidationRule])
}
.receiveValidation(in: $validation)
}

@ViewBuilder private var invitationCodeHeader: some View {
VStack(spacing: 32) {
Image(systemName: "rectangle.and.pencil.and.ellipsis")
.resizable()
.scaledToFit()
.frame(height: 100)
.accessibilityHidden(true)
.foregroundStyle(Color.accentColor)
Text("Please enter your invitation code to join the PAWS study.")
}
}

private var invitationCodeValidationRule: ValidationRule {
ValidationRule(
rule: { invitationCode in
invitationCode.count >= 8
},
message: "An invitation code is at least 8 characters long."
)
}

init() {
if FeatureFlags.useFirebaseEmulator {
Functions.functions().useEmulator(withHost: "localhost", port: 5001)
}
}

private func verifyOnboardingCode() async {
do {
if FeatureFlags.disableFirebase {
guard invitationCode == "VASCTRAC" else {
throw InvitationCodeError.invitationCodeInvalid
}

try? await Task.sleep(for: .seconds(0.25))

Check warning on line 111 in PAWS/Onboarding/InvitationCodeView.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeView.swift#L107-L111

Added lines #L107 - L111 were not covered by tests
} else {
if Auth.auth().currentUser == nil {
async let authResult = Auth.auth().signInAnonymously()
let checkInvitationCode = Functions.functions().httpsCallable("checkInvitationCode")

do {
_ = try await checkInvitationCode.call(
[
"invitationCode": invitationCode,
"userId": authResult.user.uid
]
)
} catch {
throw InvitationCodeError.invitationCodeInvalid

Check warning on line 125 in PAWS/Onboarding/InvitationCodeView.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeView.swift#L125

Added line #L125 was not covered by tests
}
}
}

await onboardingNavigationPath.nextStep()
} catch let error as NSError {
if let errorCode = FunctionsErrorCode(rawValue: error.code) {
// Handle Firebase-specific errors.
switch errorCode {
case .unauthenticated:
viewState = .error(InvitationCodeError.userNotAuthenticated)
case .notFound:
viewState = .error(InvitationCodeError.invitationCodeInvalid)
default:
viewState = .error(InvitationCodeError.generalError(error.localizedDescription))
}
} else {
// Handle other errors, such as network issues or unexpected behavior.
viewState = .error(InvitationCodeError.generalError(error.localizedDescription))
}

Check warning on line 145 in PAWS/Onboarding/InvitationCodeView.swift

View check run for this annotation

Codecov / codecov/patch

PAWS/Onboarding/InvitationCodeView.swift#L132-L145

Added lines #L132 - L145 were not covered by tests
}
}
}
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved


#Preview {
FirebaseApp.configure()

return OnboardingStack {
InvitationCodeView()
}
}
1 change: 1 addition & 0 deletions PAWS/Onboarding/OnboardingFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct OnboardingFlow: View {
InterestingModules()

if !FeatureFlags.disableFirebase {
InvitationCodeView()
AccountOnboarding()
}

Expand Down
5 changes: 0 additions & 5 deletions PAWS/PAWSStandard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,6 @@ actor PAWSStandard: Standard, EnvironmentAccessible, HealthKitConstraint, Onboar
guard let accountStorage else {
preconditionFailure("Account Storage was requested although not enabled in current configuration.")
}
if let dob = details.dateOfBrith {
// Store whether the participant is older or younger than 18.
try await userDocumentReference.getDocument().setValue(dob.isAdultDateOfBirth, forKey: "ageGroupIsAdult")
}

try await accountStorage.create(identifier, details)
}

Expand Down
Loading
Loading