@blackcode_sa/metaestetics-api 1.12.13 → 1.12.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +40 -47
- package/dist/index.mjs +40 -47
- package/package.json +1 -1
- package/src/services/auth/auth.service.ts +42 -12
- package/src/services/user/user.service.ts +96 -139
package/dist/index.d.mts
CHANGED
|
@@ -6055,7 +6055,7 @@ declare class UserService extends BaseService {
|
|
|
6055
6055
|
*/
|
|
6056
6056
|
updateUserLoginTimestamp(uid: string): Promise<User>;
|
|
6057
6057
|
upgradeAnonymousUser(uid: string, email: string): Promise<User>;
|
|
6058
|
-
updateUser(uid: string, updates: Partial<Omit<User,
|
|
6058
|
+
updateUser(uid: string, updates: Partial<Omit<User, 'uid'>>): Promise<User>;
|
|
6059
6059
|
/**
|
|
6060
6060
|
* Dodaje novu rolu korisniku
|
|
6061
6061
|
*/
|
|
@@ -6185,10 +6185,11 @@ declare class AuthService extends BaseService {
|
|
|
6185
6185
|
}>;
|
|
6186
6186
|
/**
|
|
6187
6187
|
* Signs in a user with a Google ID token from a mobile client.
|
|
6188
|
-
* If the user does not exist,
|
|
6188
|
+
* If the user does not exist in our database, the login is rejected.
|
|
6189
6189
|
* @param idToken - The Google ID token obtained from the mobile app.
|
|
6190
|
-
* @param initialRole - The role to assign to the user
|
|
6191
|
-
* @returns The signed-in
|
|
6190
|
+
* @param initialRole - The role to assign to the user (currently unused).
|
|
6191
|
+
* @returns The signed-in user if they exist in our database.
|
|
6192
|
+
* @throws AuthError if no user profile is found.
|
|
6192
6193
|
*/
|
|
6193
6194
|
signInWithGoogleIdToken(idToken: string, initialRole?: UserRole): Promise<User>;
|
|
6194
6195
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -6055,7 +6055,7 @@ declare class UserService extends BaseService {
|
|
|
6055
6055
|
*/
|
|
6056
6056
|
updateUserLoginTimestamp(uid: string): Promise<User>;
|
|
6057
6057
|
upgradeAnonymousUser(uid: string, email: string): Promise<User>;
|
|
6058
|
-
updateUser(uid: string, updates: Partial<Omit<User,
|
|
6058
|
+
updateUser(uid: string, updates: Partial<Omit<User, 'uid'>>): Promise<User>;
|
|
6059
6059
|
/**
|
|
6060
6060
|
* Dodaje novu rolu korisniku
|
|
6061
6061
|
*/
|
|
@@ -6185,10 +6185,11 @@ declare class AuthService extends BaseService {
|
|
|
6185
6185
|
}>;
|
|
6186
6186
|
/**
|
|
6187
6187
|
* Signs in a user with a Google ID token from a mobile client.
|
|
6188
|
-
* If the user does not exist,
|
|
6188
|
+
* If the user does not exist in our database, the login is rejected.
|
|
6189
6189
|
* @param idToken - The Google ID token obtained from the mobile app.
|
|
6190
|
-
* @param initialRole - The role to assign to the user
|
|
6191
|
-
* @returns The signed-in
|
|
6190
|
+
* @param initialRole - The role to assign to the user (currently unused).
|
|
6191
|
+
* @returns The signed-in user if they exist in our database.
|
|
6192
|
+
* @throws AuthError if no user profile is found.
|
|
6192
6193
|
*/
|
|
6193
6194
|
signInWithGoogleIdToken(idToken: string, initialRole?: UserRole): Promise<User>;
|
|
6194
6195
|
/**
|
package/dist/index.js
CHANGED
|
@@ -7368,11 +7368,7 @@ var UserService = class extends BaseService {
|
|
|
7368
7368
|
if (options == null ? void 0 : options.skipProfileCreation) {
|
|
7369
7369
|
return this.getUserById(userData.uid);
|
|
7370
7370
|
}
|
|
7371
|
-
const profiles = await this.createProfilesForRoles(
|
|
7372
|
-
userData.uid,
|
|
7373
|
-
roles,
|
|
7374
|
-
options
|
|
7375
|
-
);
|
|
7371
|
+
const profiles = await this.createProfilesForRoles(userData.uid, roles, options);
|
|
7376
7372
|
await (0, import_firestore22.updateDoc)((0, import_firestore22.doc)(this.db, USERS_COLLECTION, userData.uid), profiles);
|
|
7377
7373
|
return this.getUserById(userData.uid);
|
|
7378
7374
|
}
|
|
@@ -7399,19 +7395,13 @@ var UserService = class extends BaseService {
|
|
|
7399
7395
|
case "patient" /* PATIENT */:
|
|
7400
7396
|
if (options == null ? void 0 : options.patientInviteToken) {
|
|
7401
7397
|
const patientService = this.getPatientService();
|
|
7402
|
-
const token = await patientService.validatePatientToken(
|
|
7403
|
-
options.patientInviteToken
|
|
7404
|
-
);
|
|
7398
|
+
const token = await patientService.validatePatientToken(options.patientInviteToken);
|
|
7405
7399
|
if (!token) {
|
|
7406
7400
|
throw new Error("Invalid or expired patient invitation token.");
|
|
7407
7401
|
}
|
|
7408
|
-
const patientProfile2 = await patientService.getPatientProfile(
|
|
7409
|
-
token.patientId
|
|
7410
|
-
);
|
|
7402
|
+
const patientProfile2 = await patientService.getPatientProfile(token.patientId);
|
|
7411
7403
|
if (!patientProfile2 || !patientProfile2.isManual) {
|
|
7412
|
-
throw new Error(
|
|
7413
|
-
"Patient profile not found or has already been claimed."
|
|
7414
|
-
);
|
|
7404
|
+
throw new Error("Patient profile not found or has already been claimed.");
|
|
7415
7405
|
}
|
|
7416
7406
|
if ((await this.getUserById(userId)).patientProfile || patientProfile2.userRef) {
|
|
7417
7407
|
throw new Error("User already has a patient profile.");
|
|
@@ -7420,11 +7410,7 @@ var UserService = class extends BaseService {
|
|
|
7420
7410
|
userRef: userId,
|
|
7421
7411
|
isManual: false
|
|
7422
7412
|
});
|
|
7423
|
-
await patientService.markPatientTokenAsUsed(
|
|
7424
|
-
token.id,
|
|
7425
|
-
token.patientId,
|
|
7426
|
-
userId
|
|
7427
|
-
);
|
|
7413
|
+
await patientService.markPatientTokenAsUsed(token.id, token.patientId, userId);
|
|
7428
7414
|
profiles.patientProfile = patientProfile2.id;
|
|
7429
7415
|
break;
|
|
7430
7416
|
}
|
|
@@ -7528,9 +7514,7 @@ var UserService = class extends BaseService {
|
|
|
7528
7514
|
return userSchema.parse(userData);
|
|
7529
7515
|
}
|
|
7530
7516
|
async getUsersByRole(role) {
|
|
7531
|
-
const constraints = [
|
|
7532
|
-
(0, import_firestore22.where)("roles", "array-contains", role)
|
|
7533
|
-
];
|
|
7517
|
+
const constraints = [(0, import_firestore22.where)("roles", "array-contains", role)];
|
|
7534
7518
|
const q = (0, import_firestore22.query)((0, import_firestore22.collection)(this.db, USERS_COLLECTION), ...constraints);
|
|
7535
7519
|
const querySnapshot = await (0, import_firestore22.getDocs)(q);
|
|
7536
7520
|
const users = querySnapshot.docs.map((doc38) => doc38.data());
|
|
@@ -7612,23 +7596,17 @@ var UserService = class extends BaseService {
|
|
|
7612
7596
|
switch (role) {
|
|
7613
7597
|
case "patient" /* PATIENT */:
|
|
7614
7598
|
if (user.patientProfile) {
|
|
7615
|
-
await this.getPatientService().deletePatientProfile(
|
|
7616
|
-
user.patientProfile
|
|
7617
|
-
);
|
|
7599
|
+
await this.getPatientService().deletePatientProfile(user.patientProfile);
|
|
7618
7600
|
}
|
|
7619
7601
|
break;
|
|
7620
7602
|
case "clinic_admin" /* CLINIC_ADMIN */:
|
|
7621
7603
|
if (user.adminProfile) {
|
|
7622
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
7623
|
-
user.adminProfile
|
|
7624
|
-
);
|
|
7604
|
+
await this.getClinicAdminService().deleteClinicAdmin(user.adminProfile);
|
|
7625
7605
|
}
|
|
7626
7606
|
break;
|
|
7627
7607
|
case "practitioner" /* PRACTITIONER */:
|
|
7628
7608
|
if (user.practitionerProfile) {
|
|
7629
|
-
await this.getPractitionerService().deletePractitioner(
|
|
7630
|
-
user.practitionerProfile
|
|
7631
|
-
);
|
|
7609
|
+
await this.getPractitionerService().deletePractitioner(user.practitionerProfile);
|
|
7632
7610
|
}
|
|
7633
7611
|
break;
|
|
7634
7612
|
}
|
|
@@ -7647,19 +7625,13 @@ var UserService = class extends BaseService {
|
|
|
7647
7625
|
const userData = userDoc.data();
|
|
7648
7626
|
try {
|
|
7649
7627
|
if (userData.patientProfile) {
|
|
7650
|
-
await this.getPatientService().deletePatientProfile(
|
|
7651
|
-
userData.patientProfile
|
|
7652
|
-
);
|
|
7628
|
+
await this.getPatientService().deletePatientProfile(userData.patientProfile);
|
|
7653
7629
|
}
|
|
7654
7630
|
if (userData.practitionerProfile) {
|
|
7655
|
-
await this.getPractitionerService().deletePractitioner(
|
|
7656
|
-
userData.practitionerProfile
|
|
7657
|
-
);
|
|
7631
|
+
await this.getPractitionerService().deletePractitioner(userData.practitionerProfile);
|
|
7658
7632
|
}
|
|
7659
7633
|
if (userData.adminProfile) {
|
|
7660
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
7661
|
-
userData.adminProfile
|
|
7662
|
-
);
|
|
7634
|
+
await this.getClinicAdminService().deleteClinicAdmin(userData.adminProfile);
|
|
7663
7635
|
}
|
|
7664
7636
|
await (0, import_firestore22.deleteDoc)(userRef);
|
|
7665
7637
|
} catch (error) {
|
|
@@ -10372,15 +10344,37 @@ var AuthService = class extends BaseService {
|
|
|
10372
10344
|
}
|
|
10373
10345
|
/**
|
|
10374
10346
|
* Signs in a user with a Google ID token from a mobile client.
|
|
10375
|
-
* If the user does not exist,
|
|
10347
|
+
* If the user does not exist in our database, the login is rejected.
|
|
10376
10348
|
* @param idToken - The Google ID token obtained from the mobile app.
|
|
10377
|
-
* @param initialRole - The role to assign to the user
|
|
10378
|
-
* @returns The signed-in
|
|
10349
|
+
* @param initialRole - The role to assign to the user (currently unused).
|
|
10350
|
+
* @returns The signed-in user if they exist in our database.
|
|
10351
|
+
* @throws AuthError if no user profile is found.
|
|
10379
10352
|
*/
|
|
10380
10353
|
async signInWithGoogleIdToken(idToken, initialRole = "patient" /* PATIENT */) {
|
|
10381
10354
|
try {
|
|
10382
10355
|
console.log("[AUTH] Signing in with Google ID Token");
|
|
10383
10356
|
const credential = import_auth7.GoogleAuthProvider.credential(idToken);
|
|
10357
|
+
const decodedToken = JSON.parse(atob(idToken.split(".")[1]));
|
|
10358
|
+
const userEmail = decodedToken.email;
|
|
10359
|
+
const userUid = decodedToken.sub;
|
|
10360
|
+
console.log("[AUTH] Checking if Firebase Auth user exists with email:", userEmail);
|
|
10361
|
+
let existingAuthUser;
|
|
10362
|
+
try {
|
|
10363
|
+
const admin = await import("firebase-admin");
|
|
10364
|
+
existingAuthUser = await admin.auth().getUserByEmail(userEmail);
|
|
10365
|
+
console.log("[AUTH] Firebase Auth user found:", existingAuthUser.uid);
|
|
10366
|
+
} catch (authError) {
|
|
10367
|
+
if (authError.code === "auth/user-not-found") {
|
|
10368
|
+
console.log("[AUTH] No Firebase Auth user found for email:", userEmail);
|
|
10369
|
+
throw new AuthError(
|
|
10370
|
+
'No account found. Please complete registration by starting with "Get Started".',
|
|
10371
|
+
"AUTH/USER_NOT_FOUND",
|
|
10372
|
+
404
|
|
10373
|
+
);
|
|
10374
|
+
}
|
|
10375
|
+
throw authError;
|
|
10376
|
+
}
|
|
10377
|
+
console.log("[AUTH] Firebase Auth user exists, proceeding with sign-in");
|
|
10384
10378
|
const { user: firebaseUser } = await (0, import_auth7.signInWithCredential)(this.auth, credential);
|
|
10385
10379
|
console.log("[AUTH] Firebase user signed in:", firebaseUser.uid);
|
|
10386
10380
|
const existingUser = await this.userService.getUserById(firebaseUser.uid);
|
|
@@ -10388,12 +10382,11 @@ var AuthService = class extends BaseService {
|
|
|
10388
10382
|
console.log("[AUTH] Existing user found, returning profile:", existingUser.uid);
|
|
10389
10383
|
return existingUser;
|
|
10390
10384
|
}
|
|
10391
|
-
console.log("[AUTH] No existing user found for Google account:", firebaseUser.email);
|
|
10392
10385
|
await (0, import_auth7.signOut)(this.auth);
|
|
10393
10386
|
throw new AuthError(
|
|
10394
|
-
|
|
10395
|
-
"AUTH/
|
|
10396
|
-
|
|
10387
|
+
"Account found but registration incomplete. Please complete registration.",
|
|
10388
|
+
"AUTH/INCOMPLETE_REGISTRATION",
|
|
10389
|
+
400
|
|
10397
10390
|
);
|
|
10398
10391
|
} catch (error) {
|
|
10399
10392
|
console.error("[AUTH] Error in signInWithGoogleIdToken:", error);
|
package/dist/index.mjs
CHANGED
|
@@ -7408,11 +7408,7 @@ var UserService = class extends BaseService {
|
|
|
7408
7408
|
if (options == null ? void 0 : options.skipProfileCreation) {
|
|
7409
7409
|
return this.getUserById(userData.uid);
|
|
7410
7410
|
}
|
|
7411
|
-
const profiles = await this.createProfilesForRoles(
|
|
7412
|
-
userData.uid,
|
|
7413
|
-
roles,
|
|
7414
|
-
options
|
|
7415
|
-
);
|
|
7411
|
+
const profiles = await this.createProfilesForRoles(userData.uid, roles, options);
|
|
7416
7412
|
await updateDoc12(doc12(this.db, USERS_COLLECTION, userData.uid), profiles);
|
|
7417
7413
|
return this.getUserById(userData.uid);
|
|
7418
7414
|
}
|
|
@@ -7439,19 +7435,13 @@ var UserService = class extends BaseService {
|
|
|
7439
7435
|
case "patient" /* PATIENT */:
|
|
7440
7436
|
if (options == null ? void 0 : options.patientInviteToken) {
|
|
7441
7437
|
const patientService = this.getPatientService();
|
|
7442
|
-
const token = await patientService.validatePatientToken(
|
|
7443
|
-
options.patientInviteToken
|
|
7444
|
-
);
|
|
7438
|
+
const token = await patientService.validatePatientToken(options.patientInviteToken);
|
|
7445
7439
|
if (!token) {
|
|
7446
7440
|
throw new Error("Invalid or expired patient invitation token.");
|
|
7447
7441
|
}
|
|
7448
|
-
const patientProfile2 = await patientService.getPatientProfile(
|
|
7449
|
-
token.patientId
|
|
7450
|
-
);
|
|
7442
|
+
const patientProfile2 = await patientService.getPatientProfile(token.patientId);
|
|
7451
7443
|
if (!patientProfile2 || !patientProfile2.isManual) {
|
|
7452
|
-
throw new Error(
|
|
7453
|
-
"Patient profile not found or has already been claimed."
|
|
7454
|
-
);
|
|
7444
|
+
throw new Error("Patient profile not found or has already been claimed.");
|
|
7455
7445
|
}
|
|
7456
7446
|
if ((await this.getUserById(userId)).patientProfile || patientProfile2.userRef) {
|
|
7457
7447
|
throw new Error("User already has a patient profile.");
|
|
@@ -7460,11 +7450,7 @@ var UserService = class extends BaseService {
|
|
|
7460
7450
|
userRef: userId,
|
|
7461
7451
|
isManual: false
|
|
7462
7452
|
});
|
|
7463
|
-
await patientService.markPatientTokenAsUsed(
|
|
7464
|
-
token.id,
|
|
7465
|
-
token.patientId,
|
|
7466
|
-
userId
|
|
7467
|
-
);
|
|
7453
|
+
await patientService.markPatientTokenAsUsed(token.id, token.patientId, userId);
|
|
7468
7454
|
profiles.patientProfile = patientProfile2.id;
|
|
7469
7455
|
break;
|
|
7470
7456
|
}
|
|
@@ -7568,9 +7554,7 @@ var UserService = class extends BaseService {
|
|
|
7568
7554
|
return userSchema.parse(userData);
|
|
7569
7555
|
}
|
|
7570
7556
|
async getUsersByRole(role) {
|
|
7571
|
-
const constraints = [
|
|
7572
|
-
where11("roles", "array-contains", role)
|
|
7573
|
-
];
|
|
7557
|
+
const constraints = [where11("roles", "array-contains", role)];
|
|
7574
7558
|
const q = query11(collection11(this.db, USERS_COLLECTION), ...constraints);
|
|
7575
7559
|
const querySnapshot = await getDocs11(q);
|
|
7576
7560
|
const users = querySnapshot.docs.map((doc38) => doc38.data());
|
|
@@ -7652,23 +7636,17 @@ var UserService = class extends BaseService {
|
|
|
7652
7636
|
switch (role) {
|
|
7653
7637
|
case "patient" /* PATIENT */:
|
|
7654
7638
|
if (user.patientProfile) {
|
|
7655
|
-
await this.getPatientService().deletePatientProfile(
|
|
7656
|
-
user.patientProfile
|
|
7657
|
-
);
|
|
7639
|
+
await this.getPatientService().deletePatientProfile(user.patientProfile);
|
|
7658
7640
|
}
|
|
7659
7641
|
break;
|
|
7660
7642
|
case "clinic_admin" /* CLINIC_ADMIN */:
|
|
7661
7643
|
if (user.adminProfile) {
|
|
7662
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
7663
|
-
user.adminProfile
|
|
7664
|
-
);
|
|
7644
|
+
await this.getClinicAdminService().deleteClinicAdmin(user.adminProfile);
|
|
7665
7645
|
}
|
|
7666
7646
|
break;
|
|
7667
7647
|
case "practitioner" /* PRACTITIONER */:
|
|
7668
7648
|
if (user.practitionerProfile) {
|
|
7669
|
-
await this.getPractitionerService().deletePractitioner(
|
|
7670
|
-
user.practitionerProfile
|
|
7671
|
-
);
|
|
7649
|
+
await this.getPractitionerService().deletePractitioner(user.practitionerProfile);
|
|
7672
7650
|
}
|
|
7673
7651
|
break;
|
|
7674
7652
|
}
|
|
@@ -7687,19 +7665,13 @@ var UserService = class extends BaseService {
|
|
|
7687
7665
|
const userData = userDoc.data();
|
|
7688
7666
|
try {
|
|
7689
7667
|
if (userData.patientProfile) {
|
|
7690
|
-
await this.getPatientService().deletePatientProfile(
|
|
7691
|
-
userData.patientProfile
|
|
7692
|
-
);
|
|
7668
|
+
await this.getPatientService().deletePatientProfile(userData.patientProfile);
|
|
7693
7669
|
}
|
|
7694
7670
|
if (userData.practitionerProfile) {
|
|
7695
|
-
await this.getPractitionerService().deletePractitioner(
|
|
7696
|
-
userData.practitionerProfile
|
|
7697
|
-
);
|
|
7671
|
+
await this.getPractitionerService().deletePractitioner(userData.practitionerProfile);
|
|
7698
7672
|
}
|
|
7699
7673
|
if (userData.adminProfile) {
|
|
7700
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
7701
|
-
userData.adminProfile
|
|
7702
|
-
);
|
|
7674
|
+
await this.getClinicAdminService().deleteClinicAdmin(userData.adminProfile);
|
|
7703
7675
|
}
|
|
7704
7676
|
await deleteDoc4(userRef);
|
|
7705
7677
|
} catch (error) {
|
|
@@ -10476,15 +10448,37 @@ var AuthService = class extends BaseService {
|
|
|
10476
10448
|
}
|
|
10477
10449
|
/**
|
|
10478
10450
|
* Signs in a user with a Google ID token from a mobile client.
|
|
10479
|
-
* If the user does not exist,
|
|
10451
|
+
* If the user does not exist in our database, the login is rejected.
|
|
10480
10452
|
* @param idToken - The Google ID token obtained from the mobile app.
|
|
10481
|
-
* @param initialRole - The role to assign to the user
|
|
10482
|
-
* @returns The signed-in
|
|
10453
|
+
* @param initialRole - The role to assign to the user (currently unused).
|
|
10454
|
+
* @returns The signed-in user if they exist in our database.
|
|
10455
|
+
* @throws AuthError if no user profile is found.
|
|
10483
10456
|
*/
|
|
10484
10457
|
async signInWithGoogleIdToken(idToken, initialRole = "patient" /* PATIENT */) {
|
|
10485
10458
|
try {
|
|
10486
10459
|
console.log("[AUTH] Signing in with Google ID Token");
|
|
10487
10460
|
const credential = GoogleAuthProvider.credential(idToken);
|
|
10461
|
+
const decodedToken = JSON.parse(atob(idToken.split(".")[1]));
|
|
10462
|
+
const userEmail = decodedToken.email;
|
|
10463
|
+
const userUid = decodedToken.sub;
|
|
10464
|
+
console.log("[AUTH] Checking if Firebase Auth user exists with email:", userEmail);
|
|
10465
|
+
let existingAuthUser;
|
|
10466
|
+
try {
|
|
10467
|
+
const admin = await import("firebase-admin");
|
|
10468
|
+
existingAuthUser = await admin.auth().getUserByEmail(userEmail);
|
|
10469
|
+
console.log("[AUTH] Firebase Auth user found:", existingAuthUser.uid);
|
|
10470
|
+
} catch (authError) {
|
|
10471
|
+
if (authError.code === "auth/user-not-found") {
|
|
10472
|
+
console.log("[AUTH] No Firebase Auth user found for email:", userEmail);
|
|
10473
|
+
throw new AuthError(
|
|
10474
|
+
'No account found. Please complete registration by starting with "Get Started".',
|
|
10475
|
+
"AUTH/USER_NOT_FOUND",
|
|
10476
|
+
404
|
|
10477
|
+
);
|
|
10478
|
+
}
|
|
10479
|
+
throw authError;
|
|
10480
|
+
}
|
|
10481
|
+
console.log("[AUTH] Firebase Auth user exists, proceeding with sign-in");
|
|
10488
10482
|
const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
|
|
10489
10483
|
console.log("[AUTH] Firebase user signed in:", firebaseUser.uid);
|
|
10490
10484
|
const existingUser = await this.userService.getUserById(firebaseUser.uid);
|
|
@@ -10492,12 +10486,11 @@ var AuthService = class extends BaseService {
|
|
|
10492
10486
|
console.log("[AUTH] Existing user found, returning profile:", existingUser.uid);
|
|
10493
10487
|
return existingUser;
|
|
10494
10488
|
}
|
|
10495
|
-
console.log("[AUTH] No existing user found for Google account:", firebaseUser.email);
|
|
10496
10489
|
await firebaseSignOut(this.auth);
|
|
10497
10490
|
throw new AuthError(
|
|
10498
|
-
|
|
10499
|
-
"AUTH/
|
|
10500
|
-
|
|
10491
|
+
"Account found but registration incomplete. Please complete registration.",
|
|
10492
|
+
"AUTH/INCOMPLETE_REGISTRATION",
|
|
10493
|
+
400
|
|
10501
10494
|
);
|
|
10502
10495
|
} catch (error) {
|
|
10503
10496
|
console.error("[AUTH] Error in signInWithGoogleIdToken:", error);
|
package/package.json
CHANGED
|
@@ -872,10 +872,11 @@ export class AuthService extends BaseService {
|
|
|
872
872
|
|
|
873
873
|
/**
|
|
874
874
|
* Signs in a user with a Google ID token from a mobile client.
|
|
875
|
-
* If the user does not exist,
|
|
875
|
+
* If the user does not exist in our database, the login is rejected.
|
|
876
876
|
* @param idToken - The Google ID token obtained from the mobile app.
|
|
877
|
-
* @param initialRole - The role to assign to the user
|
|
878
|
-
* @returns The signed-in
|
|
877
|
+
* @param initialRole - The role to assign to the user (currently unused).
|
|
878
|
+
* @returns The signed-in user if they exist in our database.
|
|
879
|
+
* @throws AuthError if no user profile is found.
|
|
879
880
|
*/
|
|
880
881
|
async signInWithGoogleIdToken(
|
|
881
882
|
idToken: string,
|
|
@@ -883,27 +884,56 @@ export class AuthService extends BaseService {
|
|
|
883
884
|
): Promise<User> {
|
|
884
885
|
try {
|
|
885
886
|
console.log('[AUTH] Signing in with Google ID Token');
|
|
887
|
+
|
|
888
|
+
// First, decode the ID token to get the user's email without signing them in
|
|
886
889
|
const credential = GoogleAuthProvider.credential(idToken);
|
|
890
|
+
|
|
891
|
+
// Parse the ID token to get user info without creating a Firebase Auth session
|
|
892
|
+
const decodedToken = JSON.parse(atob(idToken.split('.')[1]));
|
|
893
|
+
const userEmail = decodedToken.email;
|
|
894
|
+
const userUid = decodedToken.sub; // This will be the Firebase UID
|
|
895
|
+
|
|
896
|
+
console.log('[AUTH] Checking if Firebase Auth user exists with email:', userEmail);
|
|
897
|
+
|
|
898
|
+
// Check if a Firebase Auth user with this email already exists
|
|
899
|
+
let existingAuthUser;
|
|
900
|
+
try {
|
|
901
|
+
const admin = await import('firebase-admin');
|
|
902
|
+
existingAuthUser = await admin.auth().getUserByEmail(userEmail);
|
|
903
|
+
console.log('[AUTH] Firebase Auth user found:', existingAuthUser.uid);
|
|
904
|
+
} catch (authError: any) {
|
|
905
|
+
if (authError.code === 'auth/user-not-found') {
|
|
906
|
+
// No Firebase Auth user exists - reject the login
|
|
907
|
+
console.log('[AUTH] No Firebase Auth user found for email:', userEmail);
|
|
908
|
+
throw new AuthError(
|
|
909
|
+
'No account found. Please complete registration by starting with "Get Started".',
|
|
910
|
+
'AUTH/USER_NOT_FOUND',
|
|
911
|
+
404,
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
// Re-throw other auth errors
|
|
915
|
+
throw authError;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
console.log('[AUTH] Firebase Auth user exists, proceeding with sign-in');
|
|
919
|
+
|
|
920
|
+
// Now proceed with the actual Firebase sign-in since we know the auth user exists
|
|
887
921
|
const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
|
|
888
922
|
console.log('[AUTH] Firebase user signed in:', firebaseUser.uid);
|
|
889
923
|
|
|
890
|
-
//
|
|
924
|
+
// Get the user profile from our database
|
|
891
925
|
const existingUser = await this.userService.getUserById(firebaseUser.uid);
|
|
892
926
|
if (existingUser) {
|
|
893
927
|
console.log('[AUTH] Existing user found, returning profile:', existingUser.uid);
|
|
894
928
|
return existingUser;
|
|
895
929
|
}
|
|
896
930
|
|
|
897
|
-
//
|
|
898
|
-
console.log('[AUTH] No existing user found for Google account:', firebaseUser.email);
|
|
899
|
-
|
|
900
|
-
// Sign out the Firebase user since we don't allow auto-registration
|
|
931
|
+
// Auth user exists but no profile - this means incomplete registration
|
|
901
932
|
await firebaseSignOut(this.auth);
|
|
902
|
-
|
|
903
933
|
throw new AuthError(
|
|
904
|
-
'
|
|
905
|
-
'AUTH/
|
|
906
|
-
|
|
934
|
+
'Account found but registration incomplete. Please complete registration.',
|
|
935
|
+
'AUTH/INCOMPLETE_REGISTRATION',
|
|
936
|
+
400,
|
|
907
937
|
);
|
|
908
938
|
} catch (error) {
|
|
909
939
|
console.error('[AUTH] Error in signInWithGoogleIdToken:', error);
|
|
@@ -12,24 +12,24 @@ import {
|
|
|
12
12
|
setDoc,
|
|
13
13
|
serverTimestamp,
|
|
14
14
|
FieldValue,
|
|
15
|
-
} from
|
|
16
|
-
import { initializeFirebase } from
|
|
17
|
-
import { User, UserRole, USERS_COLLECTION, CreateUserData } from
|
|
18
|
-
import { userSchema } from
|
|
19
|
-
import { AuthError } from
|
|
20
|
-
import { USER_ERRORS } from
|
|
21
|
-
import { AUTH_ERRORS } from
|
|
22
|
-
import { z } from
|
|
23
|
-
import { BaseService } from
|
|
24
|
-
import { PatientService } from
|
|
25
|
-
import { ClinicAdminService } from
|
|
26
|
-
import { PatientProfile, PATIENTS_COLLECTION } from
|
|
27
|
-
import { User as FirebaseUser } from
|
|
28
|
-
import { Auth } from
|
|
29
|
-
import { PractitionerService } from
|
|
30
|
-
import { CertificationLevel } from
|
|
31
|
-
import { Firestore } from
|
|
32
|
-
import { FirebaseApp } from
|
|
15
|
+
} from 'firebase/firestore';
|
|
16
|
+
import { initializeFirebase } from '../../config/firebase';
|
|
17
|
+
import { User, UserRole, USERS_COLLECTION, CreateUserData } from '../../types';
|
|
18
|
+
import { userSchema } from '../../validations/schemas';
|
|
19
|
+
import { AuthError } from '../../errors/auth.errors';
|
|
20
|
+
import { USER_ERRORS } from '../../errors/user.errors';
|
|
21
|
+
import { AUTH_ERRORS } from '../../errors/auth.errors';
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
import { BaseService } from '../base.service';
|
|
24
|
+
import { PatientService } from '../patient/patient.service';
|
|
25
|
+
import { ClinicAdminService } from '../clinic/clinic-admin.service';
|
|
26
|
+
import { PatientProfile, PATIENTS_COLLECTION } from '../../types/patient';
|
|
27
|
+
import { User as FirebaseUser } from 'firebase/auth';
|
|
28
|
+
import { Auth } from 'firebase/auth';
|
|
29
|
+
import { PractitionerService } from '../practitioner/practitioner.service';
|
|
30
|
+
import { CertificationLevel } from '../../backoffice/types/static/certification.types';
|
|
31
|
+
import { Firestore } from 'firebase/firestore';
|
|
32
|
+
import { FirebaseApp } from 'firebase/app';
|
|
33
33
|
|
|
34
34
|
export class UserService extends BaseService {
|
|
35
35
|
private patientService: PatientService;
|
|
@@ -42,7 +42,7 @@ export class UserService extends BaseService {
|
|
|
42
42
|
app: FirebaseApp,
|
|
43
43
|
patientService?: PatientService,
|
|
44
44
|
clinicAdminService?: ClinicAdminService,
|
|
45
|
-
practitionerService?: PractitionerService
|
|
45
|
+
practitionerService?: PractitionerService,
|
|
46
46
|
) {
|
|
47
47
|
super(db, auth, app);
|
|
48
48
|
|
|
@@ -88,7 +88,7 @@ export class UserService extends BaseService {
|
|
|
88
88
|
};
|
|
89
89
|
patientInviteToken?: string;
|
|
90
90
|
skipProfileCreation?: boolean;
|
|
91
|
-
}
|
|
91
|
+
},
|
|
92
92
|
): Promise<User> {
|
|
93
93
|
const userData: CreateUserData = {
|
|
94
94
|
uid: firebaseUser.uid,
|
|
@@ -108,11 +108,7 @@ export class UserService extends BaseService {
|
|
|
108
108
|
return this.getUserById(userData.uid);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const profiles = await this.createProfilesForRoles(
|
|
112
|
-
userData.uid,
|
|
113
|
-
roles,
|
|
114
|
-
options
|
|
115
|
-
);
|
|
111
|
+
const profiles = await this.createProfilesForRoles(userData.uid, roles, options);
|
|
116
112
|
|
|
117
113
|
// Ažuriramo korisnika sa referencama na profile
|
|
118
114
|
await updateDoc(doc(this.db, USERS_COLLECTION, userData.uid), profiles);
|
|
@@ -123,10 +119,7 @@ export class UserService extends BaseService {
|
|
|
123
119
|
/**
|
|
124
120
|
* Dohvata ili kreira korisnika na osnovu Firebase korisnika
|
|
125
121
|
*/
|
|
126
|
-
async getOrCreateUser(
|
|
127
|
-
firebaseUser: FirebaseUser,
|
|
128
|
-
initialRole?: UserRole
|
|
129
|
-
): Promise<User> {
|
|
122
|
+
async getOrCreateUser(firebaseUser: FirebaseUser, initialRole?: UserRole): Promise<User> {
|
|
130
123
|
try {
|
|
131
124
|
const existingUser = await this.getUserById(firebaseUser.uid);
|
|
132
125
|
await this.updateUserLoginTimestamp(firebaseUser.uid);
|
|
@@ -150,7 +143,7 @@ export class UserService extends BaseService {
|
|
|
150
143
|
};
|
|
151
144
|
patientInviteToken?: string;
|
|
152
145
|
skipProfileCreation?: boolean;
|
|
153
|
-
}
|
|
146
|
+
},
|
|
154
147
|
): Promise<{
|
|
155
148
|
patientProfile?: string;
|
|
156
149
|
practitionerProfile?: string;
|
|
@@ -168,30 +161,21 @@ export class UserService extends BaseService {
|
|
|
168
161
|
// If a token is provided, claim the existing manual profile
|
|
169
162
|
if (options?.patientInviteToken) {
|
|
170
163
|
const patientService = this.getPatientService();
|
|
171
|
-
const token = await patientService.validatePatientToken(
|
|
172
|
-
options.patientInviteToken
|
|
173
|
-
);
|
|
164
|
+
const token = await patientService.validatePatientToken(options.patientInviteToken);
|
|
174
165
|
|
|
175
166
|
if (!token) {
|
|
176
|
-
throw new Error(
|
|
167
|
+
throw new Error('Invalid or expired patient invitation token.');
|
|
177
168
|
}
|
|
178
169
|
|
|
179
170
|
// Get the patient profile
|
|
180
|
-
const patientProfile = await patientService.getPatientProfile(
|
|
181
|
-
token.patientId
|
|
182
|
-
);
|
|
171
|
+
const patientProfile = await patientService.getPatientProfile(token.patientId);
|
|
183
172
|
if (!patientProfile || !patientProfile.isManual) {
|
|
184
|
-
throw new Error(
|
|
185
|
-
"Patient profile not found or has already been claimed."
|
|
186
|
-
);
|
|
173
|
+
throw new Error('Patient profile not found or has already been claimed.');
|
|
187
174
|
}
|
|
188
175
|
|
|
189
176
|
// Check if user already has a patient profile
|
|
190
|
-
if (
|
|
191
|
-
(
|
|
192
|
-
patientProfile.userRef
|
|
193
|
-
) {
|
|
194
|
-
throw new Error("User already has a patient profile.");
|
|
177
|
+
if ((await this.getUserById(userId)).patientProfile || patientProfile.userRef) {
|
|
178
|
+
throw new Error('User already has a patient profile.');
|
|
195
179
|
}
|
|
196
180
|
|
|
197
181
|
// Claim the profile: link userRef and set isManual to false
|
|
@@ -201,29 +185,24 @@ export class UserService extends BaseService {
|
|
|
201
185
|
});
|
|
202
186
|
|
|
203
187
|
// Mark the token as used
|
|
204
|
-
await patientService.markPatientTokenAsUsed(
|
|
205
|
-
token.id,
|
|
206
|
-
token.patientId,
|
|
207
|
-
userId
|
|
208
|
-
);
|
|
188
|
+
await patientService.markPatientTokenAsUsed(token.id, token.patientId, userId);
|
|
209
189
|
|
|
210
190
|
profiles.patientProfile = patientProfile.id;
|
|
211
191
|
break;
|
|
212
192
|
}
|
|
213
193
|
|
|
214
|
-
const patientProfile =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
194
|
+
const patientProfile = await this.getPatientService().createPatientProfile({
|
|
195
|
+
userRef: userId,
|
|
196
|
+
displayName: 'Patient', // Default displayName, može se kasnije promeniti
|
|
197
|
+
expoTokens: [],
|
|
198
|
+
gamification: {
|
|
199
|
+
level: 1,
|
|
200
|
+
points: 0,
|
|
201
|
+
},
|
|
202
|
+
isActive: true,
|
|
203
|
+
isVerified: false,
|
|
204
|
+
isManual: false, // Explicitly set to false for standard signups
|
|
205
|
+
});
|
|
227
206
|
profiles.patientProfile = patientProfile.id;
|
|
228
207
|
break;
|
|
229
208
|
case UserRole.CLINIC_ADMIN:
|
|
@@ -234,66 +213,61 @@ export class UserService extends BaseService {
|
|
|
234
213
|
}
|
|
235
214
|
|
|
236
215
|
// Ako imamo token, verifikujemo ga i dodajemo admina u postojeću grupu
|
|
237
|
-
if (
|
|
238
|
-
options?.clinicAdminData?.groupToken &&
|
|
239
|
-
options?.clinicAdminData?.groupId
|
|
240
|
-
) {
|
|
216
|
+
if (options?.clinicAdminData?.groupToken && options?.clinicAdminData?.groupId) {
|
|
241
217
|
const isValid = await this.getClinicAdminService()
|
|
242
218
|
.getClinicGroupService()
|
|
243
219
|
.verifyAndUseAdminToken(
|
|
244
220
|
options.clinicAdminData.groupId,
|
|
245
221
|
options.clinicAdminData.groupToken,
|
|
246
|
-
userId
|
|
222
|
+
userId,
|
|
247
223
|
);
|
|
248
224
|
|
|
249
225
|
if (!isValid) {
|
|
250
|
-
throw new Error(
|
|
226
|
+
throw new Error('Invalid admin token');
|
|
251
227
|
}
|
|
252
228
|
}
|
|
253
229
|
|
|
254
|
-
const clinicAdminProfile =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
230
|
+
const clinicAdminProfile = await this.getClinicAdminService().createClinicAdmin({
|
|
231
|
+
userRef: userId,
|
|
232
|
+
clinicGroupId: options?.clinicAdminData?.groupId || '',
|
|
233
|
+
isGroupOwner: options?.clinicAdminData?.isGroupOwner || false,
|
|
234
|
+
clinicsManaged: [],
|
|
235
|
+
contactInfo: {
|
|
236
|
+
firstName: '',
|
|
237
|
+
lastName: '',
|
|
238
|
+
title: 'Clinic Administrator',
|
|
239
|
+
email: '',
|
|
240
|
+
phoneNumber: '',
|
|
241
|
+
},
|
|
242
|
+
roleTitle: 'Clinic Administrator',
|
|
243
|
+
isActive: true,
|
|
244
|
+
});
|
|
270
245
|
profiles.adminProfile = clinicAdminProfile.id;
|
|
271
246
|
break;
|
|
272
247
|
case UserRole.PRACTITIONER:
|
|
273
|
-
const practitionerProfile =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
});
|
|
248
|
+
const practitionerProfile = await this.getPractitionerService().createPractitioner({
|
|
249
|
+
userRef: userId,
|
|
250
|
+
basicInfo: {
|
|
251
|
+
firstName: '',
|
|
252
|
+
lastName: '',
|
|
253
|
+
email: '',
|
|
254
|
+
phoneNumber: '',
|
|
255
|
+
title: '',
|
|
256
|
+
dateOfBirth: Timestamp.now(),
|
|
257
|
+
gender: 'other',
|
|
258
|
+
languages: ['Serbian'],
|
|
259
|
+
},
|
|
260
|
+
certification: {
|
|
261
|
+
level: CertificationLevel.AESTHETICIAN,
|
|
262
|
+
specialties: [],
|
|
263
|
+
licenseNumber: '',
|
|
264
|
+
issuingAuthority: '',
|
|
265
|
+
issueDate: Timestamp.now(),
|
|
266
|
+
verificationStatus: 'pending',
|
|
267
|
+
},
|
|
268
|
+
isActive: true,
|
|
269
|
+
isVerified: false,
|
|
270
|
+
});
|
|
297
271
|
profiles.practitionerProfile = practitionerProfile.id;
|
|
298
272
|
break;
|
|
299
273
|
}
|
|
@@ -321,7 +295,7 @@ export class UserService extends BaseService {
|
|
|
321
295
|
*/
|
|
322
296
|
async getUserByEmail(email: string): Promise<User | null> {
|
|
323
297
|
const usersRef = collection(this.db, USERS_COLLECTION);
|
|
324
|
-
const q = query(usersRef, where(
|
|
298
|
+
const q = query(usersRef, where('email', '==', email));
|
|
325
299
|
const querySnapshot = await getDocs(q);
|
|
326
300
|
|
|
327
301
|
if (querySnapshot.empty) return null;
|
|
@@ -331,14 +305,12 @@ export class UserService extends BaseService {
|
|
|
331
305
|
}
|
|
332
306
|
|
|
333
307
|
async getUsersByRole(role: UserRole): Promise<User[]> {
|
|
334
|
-
const constraints: QueryConstraint[] = [
|
|
335
|
-
where("roles", "array-contains", role),
|
|
336
|
-
];
|
|
308
|
+
const constraints: QueryConstraint[] = [where('roles', 'array-contains', role)];
|
|
337
309
|
const q = query(collection(this.db, USERS_COLLECTION), ...constraints);
|
|
338
310
|
const querySnapshot = await getDocs(q);
|
|
339
311
|
|
|
340
|
-
const users = querySnapshot.docs.map(
|
|
341
|
-
return users.map(
|
|
312
|
+
const users = querySnapshot.docs.map(doc => doc.data());
|
|
313
|
+
return users.map(userData => userSchema.parse(userData) as User);
|
|
342
314
|
}
|
|
343
315
|
|
|
344
316
|
/**
|
|
@@ -377,10 +349,7 @@ export class UserService extends BaseService {
|
|
|
377
349
|
return this.getUserById(uid);
|
|
378
350
|
}
|
|
379
351
|
|
|
380
|
-
async updateUser(
|
|
381
|
-
uid: string,
|
|
382
|
-
updates: Partial<Omit<User, "uid">>
|
|
383
|
-
): Promise<User> {
|
|
352
|
+
async updateUser(uid: string, updates: Partial<Omit<User, 'uid'>>): Promise<User> {
|
|
384
353
|
const userRef = doc(this.db, USERS_COLLECTION, uid);
|
|
385
354
|
const userDoc = await getDoc(userRef);
|
|
386
355
|
|
|
@@ -426,7 +395,7 @@ export class UserService extends BaseService {
|
|
|
426
395
|
groupToken?: string;
|
|
427
396
|
groupId?: string;
|
|
428
397
|
};
|
|
429
|
-
}
|
|
398
|
+
},
|
|
430
399
|
): Promise<void> {
|
|
431
400
|
const user = await this.getUserById(uid);
|
|
432
401
|
if (user.roles.includes(role)) return;
|
|
@@ -451,23 +420,17 @@ export class UserService extends BaseService {
|
|
|
451
420
|
switch (role) {
|
|
452
421
|
case UserRole.PATIENT:
|
|
453
422
|
if (user.patientProfile) {
|
|
454
|
-
await this.getPatientService().deletePatientProfile(
|
|
455
|
-
user.patientProfile
|
|
456
|
-
);
|
|
423
|
+
await this.getPatientService().deletePatientProfile(user.patientProfile);
|
|
457
424
|
}
|
|
458
425
|
break;
|
|
459
426
|
case UserRole.CLINIC_ADMIN:
|
|
460
427
|
if (user.adminProfile) {
|
|
461
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
462
|
-
user.adminProfile
|
|
463
|
-
);
|
|
428
|
+
await this.getClinicAdminService().deleteClinicAdmin(user.adminProfile);
|
|
464
429
|
}
|
|
465
430
|
break;
|
|
466
431
|
case UserRole.PRACTITIONER:
|
|
467
432
|
if (user.practitionerProfile) {
|
|
468
|
-
await this.getPractitionerService().deletePractitioner(
|
|
469
|
-
user.practitionerProfile
|
|
470
|
-
);
|
|
433
|
+
await this.getPractitionerService().deletePractitioner(user.practitionerProfile);
|
|
471
434
|
}
|
|
472
435
|
break;
|
|
473
436
|
// Dodati ostale role po potrebi
|
|
@@ -475,7 +438,7 @@ export class UserService extends BaseService {
|
|
|
475
438
|
|
|
476
439
|
// Zatim uklanjamo rolu
|
|
477
440
|
await updateDoc(doc(this.db, USERS_COLLECTION, uid), {
|
|
478
|
-
roles: user.roles.filter(
|
|
441
|
+
roles: user.roles.filter(r => r !== role),
|
|
479
442
|
updatedAt: serverTimestamp(),
|
|
480
443
|
});
|
|
481
444
|
}
|
|
@@ -494,21 +457,15 @@ export class UserService extends BaseService {
|
|
|
494
457
|
try {
|
|
495
458
|
// Delete all associated profiles
|
|
496
459
|
if (userData.patientProfile) {
|
|
497
|
-
await this.getPatientService().deletePatientProfile(
|
|
498
|
-
userData.patientProfile
|
|
499
|
-
);
|
|
460
|
+
await this.getPatientService().deletePatientProfile(userData.patientProfile);
|
|
500
461
|
}
|
|
501
462
|
|
|
502
463
|
if (userData.practitionerProfile) {
|
|
503
|
-
await this.getPractitionerService().deletePractitioner(
|
|
504
|
-
userData.practitionerProfile
|
|
505
|
-
);
|
|
464
|
+
await this.getPractitionerService().deletePractitioner(userData.practitionerProfile);
|
|
506
465
|
}
|
|
507
466
|
|
|
508
467
|
if (userData.adminProfile) {
|
|
509
|
-
await this.getClinicAdminService().deleteClinicAdmin(
|
|
510
|
-
userData.adminProfile
|
|
511
|
-
);
|
|
468
|
+
await this.getClinicAdminService().deleteClinicAdmin(userData.adminProfile);
|
|
512
469
|
}
|
|
513
470
|
|
|
514
471
|
await deleteDoc(userRef);
|