@blackcode_sa/metaestetics-api 1.13.21 → 1.14.1
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/admin/index.js +13 -2
- package/dist/admin/index.mjs +13 -2
- package/dist/index.d.mts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +477 -0
- package/dist/index.mjs +477 -0
- package/package.json +1 -1
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +13 -2
- package/src/services/auth/auth.service.ts +290 -0
- package/src/services/practitioner/practitioner.service.ts +343 -0
package/dist/admin/index.js
CHANGED
|
@@ -11860,10 +11860,21 @@ var practitionerInvitationTemplate = `
|
|
|
11860
11860
|
|
|
11861
11861
|
<p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
|
|
11862
11862
|
|
|
11863
|
-
<p>
|
|
11863
|
+
<p><strong>You have two options to create your account:</strong></p>
|
|
11864
|
+
|
|
11865
|
+
<p><strong>Option 1: Sign in with Google (Recommended)</strong></p>
|
|
11866
|
+
<ol>
|
|
11867
|
+
<li>Open the MetaEsthetics Doctor App</li>
|
|
11868
|
+
<li>Click "Sign in with Google" on the login screen</li>
|
|
11869
|
+
<li>Select your Google account (use the email address: {{practitionerEmail}})</li>
|
|
11870
|
+
<li>You'll see an invitation to join {{clinicName}} - simply select it and join!</li>
|
|
11871
|
+
</ol>
|
|
11872
|
+
|
|
11873
|
+
<p><strong>Option 2: Use Email/Password with Token</strong></p>
|
|
11864
11874
|
<ol>
|
|
11865
11875
|
<li>Visit {{registrationUrl}}</li>
|
|
11866
|
-
<li>
|
|
11876
|
+
<li>Click "Claim Existing Profile with Token"</li>
|
|
11877
|
+
<li>Enter your email ({{practitionerEmail}}) and create a password</li>
|
|
11867
11878
|
<li>When prompted, enter the token above</li>
|
|
11868
11879
|
</ol>
|
|
11869
11880
|
|
package/dist/admin/index.mjs
CHANGED
|
@@ -11787,10 +11787,21 @@ var practitionerInvitationTemplate = `
|
|
|
11787
11787
|
|
|
11788
11788
|
<p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
|
|
11789
11789
|
|
|
11790
|
-
<p>
|
|
11790
|
+
<p><strong>You have two options to create your account:</strong></p>
|
|
11791
|
+
|
|
11792
|
+
<p><strong>Option 1: Sign in with Google (Recommended)</strong></p>
|
|
11793
|
+
<ol>
|
|
11794
|
+
<li>Open the MetaEsthetics Doctor App</li>
|
|
11795
|
+
<li>Click "Sign in with Google" on the login screen</li>
|
|
11796
|
+
<li>Select your Google account (use the email address: {{practitionerEmail}})</li>
|
|
11797
|
+
<li>You'll see an invitation to join {{clinicName}} - simply select it and join!</li>
|
|
11798
|
+
</ol>
|
|
11799
|
+
|
|
11800
|
+
<p><strong>Option 2: Use Email/Password with Token</strong></p>
|
|
11791
11801
|
<ol>
|
|
11792
11802
|
<li>Visit {{registrationUrl}}</li>
|
|
11793
|
-
<li>
|
|
11803
|
+
<li>Click "Claim Existing Profile with Token"</li>
|
|
11804
|
+
<li>Enter your email ({{practitionerEmail}}) and create a password</li>
|
|
11794
11805
|
<li>When prompted, enter the token above</li>
|
|
11795
11806
|
</ol>
|
|
11796
11807
|
|
package/dist/index.d.mts
CHANGED
|
@@ -7029,6 +7029,37 @@ declare class PractitionerService extends BaseService {
|
|
|
7029
7029
|
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7030
7030
|
*/
|
|
7031
7031
|
findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
|
|
7032
|
+
/**
|
|
7033
|
+
* Finds all draft practitioner profiles by email address
|
|
7034
|
+
* Used when a doctor signs in with Google to show all clinic invitations
|
|
7035
|
+
*
|
|
7036
|
+
* @param email - Email address to search for
|
|
7037
|
+
* @returns Array of draft practitioner profiles with clinic information
|
|
7038
|
+
*
|
|
7039
|
+
* @remarks
|
|
7040
|
+
* Requires Firestore composite index on:
|
|
7041
|
+
* - Collection: practitioners
|
|
7042
|
+
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7043
|
+
*/
|
|
7044
|
+
getDraftProfilesByEmail(email: string): Promise<Practitioner[]>;
|
|
7045
|
+
/**
|
|
7046
|
+
* Claims a draft practitioner profile and links it to a user account
|
|
7047
|
+
* Used when a doctor selects which clinic(s) to join after Google Sign-In
|
|
7048
|
+
*
|
|
7049
|
+
* @param practitionerId - ID of the draft practitioner profile to claim
|
|
7050
|
+
* @param userId - ID of the user account to link the profile to
|
|
7051
|
+
* @returns The claimed practitioner profile
|
|
7052
|
+
*/
|
|
7053
|
+
claimDraftProfileWithGoogle(practitionerId: string, userId: string): Promise<Practitioner>;
|
|
7054
|
+
/**
|
|
7055
|
+
* Claims multiple draft practitioner profiles and merges them into one profile
|
|
7056
|
+
* Used when a doctor selects multiple clinics to join after Google Sign-In
|
|
7057
|
+
*
|
|
7058
|
+
* @param practitionerIds - Array of draft practitioner profile IDs to claim
|
|
7059
|
+
* @param userId - ID of the user account to link the profiles to
|
|
7060
|
+
* @returns The claimed practitioner profile (first one becomes main, others merged)
|
|
7061
|
+
*/
|
|
7062
|
+
claimMultipleDraftProfilesWithGoogle(practitionerIds: string[], userId: string): Promise<Practitioner>;
|
|
7032
7063
|
/**
|
|
7033
7064
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
7034
7065
|
*/
|
|
@@ -8404,6 +8435,18 @@ declare class AuthService extends BaseService {
|
|
|
8404
8435
|
* @returns The signed-in or newly created user.
|
|
8405
8436
|
*/
|
|
8406
8437
|
signInWithGoogleIdToken(idToken: string, initialRole?: UserRole): Promise<User>;
|
|
8438
|
+
/**
|
|
8439
|
+
* Signs up or signs in a practitioner with Google authentication.
|
|
8440
|
+
* Checks for existing practitioner account or draft profiles.
|
|
8441
|
+
*
|
|
8442
|
+
* @param idToken - The Google ID token obtained from the mobile app
|
|
8443
|
+
* @returns Object containing user, practitioner (if exists), and draft profiles (if any)
|
|
8444
|
+
*/
|
|
8445
|
+
signUpPractitionerWithGoogle(idToken: string): Promise<{
|
|
8446
|
+
user: User;
|
|
8447
|
+
practitioner: Practitioner | null;
|
|
8448
|
+
draftProfiles: Practitioner[];
|
|
8449
|
+
}>;
|
|
8407
8450
|
/**
|
|
8408
8451
|
* Links a Google account to the currently signed-in user using an ID token.
|
|
8409
8452
|
* This is used to upgrade an anonymous user or to allow an existing user
|
package/dist/index.d.ts
CHANGED
|
@@ -7029,6 +7029,37 @@ declare class PractitionerService extends BaseService {
|
|
|
7029
7029
|
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7030
7030
|
*/
|
|
7031
7031
|
findDraftPractitionerByEmail(email: string): Promise<Practitioner | null>;
|
|
7032
|
+
/**
|
|
7033
|
+
* Finds all draft practitioner profiles by email address
|
|
7034
|
+
* Used when a doctor signs in with Google to show all clinic invitations
|
|
7035
|
+
*
|
|
7036
|
+
* @param email - Email address to search for
|
|
7037
|
+
* @returns Array of draft practitioner profiles with clinic information
|
|
7038
|
+
*
|
|
7039
|
+
* @remarks
|
|
7040
|
+
* Requires Firestore composite index on:
|
|
7041
|
+
* - Collection: practitioners
|
|
7042
|
+
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
7043
|
+
*/
|
|
7044
|
+
getDraftProfilesByEmail(email: string): Promise<Practitioner[]>;
|
|
7045
|
+
/**
|
|
7046
|
+
* Claims a draft practitioner profile and links it to a user account
|
|
7047
|
+
* Used when a doctor selects which clinic(s) to join after Google Sign-In
|
|
7048
|
+
*
|
|
7049
|
+
* @param practitionerId - ID of the draft practitioner profile to claim
|
|
7050
|
+
* @param userId - ID of the user account to link the profile to
|
|
7051
|
+
* @returns The claimed practitioner profile
|
|
7052
|
+
*/
|
|
7053
|
+
claimDraftProfileWithGoogle(practitionerId: string, userId: string): Promise<Practitioner>;
|
|
7054
|
+
/**
|
|
7055
|
+
* Claims multiple draft practitioner profiles and merges them into one profile
|
|
7056
|
+
* Used when a doctor selects multiple clinics to join after Google Sign-In
|
|
7057
|
+
*
|
|
7058
|
+
* @param practitionerIds - Array of draft practitioner profile IDs to claim
|
|
7059
|
+
* @param userId - ID of the user account to link the profiles to
|
|
7060
|
+
* @returns The claimed practitioner profile (first one becomes main, others merged)
|
|
7061
|
+
*/
|
|
7062
|
+
claimMultipleDraftProfilesWithGoogle(practitionerIds: string[], userId: string): Promise<Practitioner>;
|
|
7032
7063
|
/**
|
|
7033
7064
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
7034
7065
|
*/
|
|
@@ -8404,6 +8435,18 @@ declare class AuthService extends BaseService {
|
|
|
8404
8435
|
* @returns The signed-in or newly created user.
|
|
8405
8436
|
*/
|
|
8406
8437
|
signInWithGoogleIdToken(idToken: string, initialRole?: UserRole): Promise<User>;
|
|
8438
|
+
/**
|
|
8439
|
+
* Signs up or signs in a practitioner with Google authentication.
|
|
8440
|
+
* Checks for existing practitioner account or draft profiles.
|
|
8441
|
+
*
|
|
8442
|
+
* @param idToken - The Google ID token obtained from the mobile app
|
|
8443
|
+
* @returns Object containing user, practitioner (if exists), and draft profiles (if any)
|
|
8444
|
+
*/
|
|
8445
|
+
signUpPractitionerWithGoogle(idToken: string): Promise<{
|
|
8446
|
+
user: User;
|
|
8447
|
+
practitioner: Practitioner | null;
|
|
8448
|
+
draftProfiles: Practitioner[];
|
|
8449
|
+
}>;
|
|
8407
8450
|
/**
|
|
8408
8451
|
* Links a Google account to the currently signed-in user using an ID token.
|
|
8409
8452
|
* This is used to upgrade an anonymous user or to allow an existing user
|
package/dist/index.js
CHANGED
|
@@ -11308,6 +11308,9 @@ var PractitionerService = class extends BaseService {
|
|
|
11308
11308
|
*/
|
|
11309
11309
|
async processBasicInfo(basicInfo, practitionerId) {
|
|
11310
11310
|
const processedBasicInfo = { ...basicInfo };
|
|
11311
|
+
if (processedBasicInfo.email) {
|
|
11312
|
+
processedBasicInfo.email = processedBasicInfo.email.toLowerCase().trim();
|
|
11313
|
+
}
|
|
11311
11314
|
if (basicInfo.profileImageUrl) {
|
|
11312
11315
|
const uploadedUrl = await this.handleProfilePhotoUpload(
|
|
11313
11316
|
basicInfo.profileImageUrl,
|
|
@@ -11715,6 +11718,260 @@ var PractitionerService = class extends BaseService {
|
|
|
11715
11718
|
return null;
|
|
11716
11719
|
}
|
|
11717
11720
|
}
|
|
11721
|
+
/**
|
|
11722
|
+
* Finds all draft practitioner profiles by email address
|
|
11723
|
+
* Used when a doctor signs in with Google to show all clinic invitations
|
|
11724
|
+
*
|
|
11725
|
+
* @param email - Email address to search for
|
|
11726
|
+
* @returns Array of draft practitioner profiles with clinic information
|
|
11727
|
+
*
|
|
11728
|
+
* @remarks
|
|
11729
|
+
* Requires Firestore composite index on:
|
|
11730
|
+
* - Collection: practitioners
|
|
11731
|
+
* - Fields: basicInfo.email (Ascending), status (Ascending), userRef (Ascending)
|
|
11732
|
+
*/
|
|
11733
|
+
async getDraftProfilesByEmail(email) {
|
|
11734
|
+
try {
|
|
11735
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
11736
|
+
console.log("[PRACTITIONER] Searching for all draft practitioners by email", {
|
|
11737
|
+
email: normalizedEmail,
|
|
11738
|
+
originalEmail: email
|
|
11739
|
+
});
|
|
11740
|
+
const q = (0, import_firestore32.query)(
|
|
11741
|
+
(0, import_firestore32.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
11742
|
+
(0, import_firestore32.where)("basicInfo.email", "==", normalizedEmail),
|
|
11743
|
+
(0, import_firestore32.where)("status", "==", "draft" /* DRAFT */),
|
|
11744
|
+
(0, import_firestore32.where)("userRef", "==", "")
|
|
11745
|
+
);
|
|
11746
|
+
const querySnapshot = await (0, import_firestore32.getDocs)(q);
|
|
11747
|
+
if (querySnapshot.empty) {
|
|
11748
|
+
console.log("[PRACTITIONER] No draft practitioners found for email", {
|
|
11749
|
+
email: normalizedEmail,
|
|
11750
|
+
originalEmail: email
|
|
11751
|
+
});
|
|
11752
|
+
const debugQ = (0, import_firestore32.query)(
|
|
11753
|
+
(0, import_firestore32.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
11754
|
+
(0, import_firestore32.where)("basicInfo.email", "==", normalizedEmail),
|
|
11755
|
+
(0, import_firestore32.limit)(5)
|
|
11756
|
+
);
|
|
11757
|
+
const debugSnapshot = await (0, import_firestore32.getDocs)(debugQ);
|
|
11758
|
+
console.log("[PRACTITIONER] Debug: Found practitioners with this email (any status):", {
|
|
11759
|
+
count: debugSnapshot.size,
|
|
11760
|
+
practitioners: debugSnapshot.docs.map((doc47) => {
|
|
11761
|
+
var _a;
|
|
11762
|
+
return {
|
|
11763
|
+
id: doc47.id,
|
|
11764
|
+
email: (_a = doc47.data().basicInfo) == null ? void 0 : _a.email,
|
|
11765
|
+
status: doc47.data().status,
|
|
11766
|
+
userRef: doc47.data().userRef
|
|
11767
|
+
};
|
|
11768
|
+
})
|
|
11769
|
+
});
|
|
11770
|
+
return [];
|
|
11771
|
+
}
|
|
11772
|
+
const draftPractitioners = querySnapshot.docs.map(
|
|
11773
|
+
(doc47) => doc47.data()
|
|
11774
|
+
);
|
|
11775
|
+
console.log("[PRACTITIONER] Found draft practitioners", {
|
|
11776
|
+
email: normalizedEmail,
|
|
11777
|
+
count: draftPractitioners.length,
|
|
11778
|
+
practitionerIds: draftPractitioners.map((p) => p.id)
|
|
11779
|
+
});
|
|
11780
|
+
return draftPractitioners;
|
|
11781
|
+
} catch (error) {
|
|
11782
|
+
console.error(
|
|
11783
|
+
"[PRACTITIONER] Error finding draft practitioners by email:",
|
|
11784
|
+
error
|
|
11785
|
+
);
|
|
11786
|
+
return [];
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
/**
|
|
11790
|
+
* Claims a draft practitioner profile and links it to a user account
|
|
11791
|
+
* Used when a doctor selects which clinic(s) to join after Google Sign-In
|
|
11792
|
+
*
|
|
11793
|
+
* @param practitionerId - ID of the draft practitioner profile to claim
|
|
11794
|
+
* @param userId - ID of the user account to link the profile to
|
|
11795
|
+
* @returns The claimed practitioner profile
|
|
11796
|
+
*/
|
|
11797
|
+
async claimDraftProfileWithGoogle(practitionerId, userId) {
|
|
11798
|
+
try {
|
|
11799
|
+
console.log("[PRACTITIONER] Claiming draft profile with Google", {
|
|
11800
|
+
practitionerId,
|
|
11801
|
+
userId
|
|
11802
|
+
});
|
|
11803
|
+
const practitioner = await this.getPractitioner(practitionerId);
|
|
11804
|
+
if (!practitioner) {
|
|
11805
|
+
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
11806
|
+
}
|
|
11807
|
+
if (practitioner.status !== "draft" /* DRAFT */) {
|
|
11808
|
+
throw new Error("This practitioner profile has already been claimed");
|
|
11809
|
+
}
|
|
11810
|
+
const existingPractitioner = await this.getPractitionerByUserRef(userId);
|
|
11811
|
+
if (existingPractitioner) {
|
|
11812
|
+
console.log("[PRACTITIONER] User already has profile, merging clinics");
|
|
11813
|
+
const mergedClinics = Array.from(/* @__PURE__ */ new Set([
|
|
11814
|
+
...existingPractitioner.clinics,
|
|
11815
|
+
...practitioner.clinics
|
|
11816
|
+
]));
|
|
11817
|
+
const mergedWorkingHours = [...existingPractitioner.clinicWorkingHours];
|
|
11818
|
+
for (const workingHours of practitioner.clinicWorkingHours) {
|
|
11819
|
+
if (!mergedWorkingHours.find((wh) => wh.clinicId === workingHours.clinicId)) {
|
|
11820
|
+
mergedWorkingHours.push(workingHours);
|
|
11821
|
+
}
|
|
11822
|
+
}
|
|
11823
|
+
const mergedClinicsInfo = [...existingPractitioner.clinicsInfo];
|
|
11824
|
+
for (const clinicInfo of practitioner.clinicsInfo) {
|
|
11825
|
+
if (!mergedClinicsInfo.find((ci) => ci.id === clinicInfo.id)) {
|
|
11826
|
+
mergedClinicsInfo.push(clinicInfo);
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
const updatedPractitioner2 = await this.updatePractitioner(existingPractitioner.id, {
|
|
11830
|
+
clinics: mergedClinics,
|
|
11831
|
+
clinicWorkingHours: mergedWorkingHours,
|
|
11832
|
+
clinicsInfo: mergedClinicsInfo
|
|
11833
|
+
});
|
|
11834
|
+
await (0, import_firestore32.deleteDoc)((0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId));
|
|
11835
|
+
const activeTokens2 = await this.getPractitionerActiveTokens(practitionerId);
|
|
11836
|
+
for (const token of activeTokens2) {
|
|
11837
|
+
await this.markTokenAsUsed(token.id, practitionerId, userId);
|
|
11838
|
+
}
|
|
11839
|
+
return updatedPractitioner2;
|
|
11840
|
+
}
|
|
11841
|
+
const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
|
|
11842
|
+
userRef: userId,
|
|
11843
|
+
status: "active" /* ACTIVE */
|
|
11844
|
+
});
|
|
11845
|
+
const activeTokens = await this.getPractitionerActiveTokens(practitionerId);
|
|
11846
|
+
for (const token of activeTokens) {
|
|
11847
|
+
await this.markTokenAsUsed(token.id, practitionerId, userId);
|
|
11848
|
+
}
|
|
11849
|
+
console.log("[PRACTITIONER] Draft profile claimed successfully", {
|
|
11850
|
+
practitionerId: updatedPractitioner.id,
|
|
11851
|
+
userId
|
|
11852
|
+
});
|
|
11853
|
+
return updatedPractitioner;
|
|
11854
|
+
} catch (error) {
|
|
11855
|
+
console.error(
|
|
11856
|
+
"[PRACTITIONER] Error claiming draft profile with Google:",
|
|
11857
|
+
error
|
|
11858
|
+
);
|
|
11859
|
+
throw error;
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
/**
|
|
11863
|
+
* Claims multiple draft practitioner profiles and merges them into one profile
|
|
11864
|
+
* Used when a doctor selects multiple clinics to join after Google Sign-In
|
|
11865
|
+
*
|
|
11866
|
+
* @param practitionerIds - Array of draft practitioner profile IDs to claim
|
|
11867
|
+
* @param userId - ID of the user account to link the profiles to
|
|
11868
|
+
* @returns The claimed practitioner profile (first one becomes main, others merged)
|
|
11869
|
+
*/
|
|
11870
|
+
async claimMultipleDraftProfilesWithGoogle(practitionerIds, userId) {
|
|
11871
|
+
try {
|
|
11872
|
+
if (practitionerIds.length === 0) {
|
|
11873
|
+
throw new Error("No practitioner IDs provided");
|
|
11874
|
+
}
|
|
11875
|
+
console.log("[PRACTITIONER] Claiming multiple draft profiles with Google", {
|
|
11876
|
+
practitionerIds,
|
|
11877
|
+
userId,
|
|
11878
|
+
count: practitionerIds.length
|
|
11879
|
+
});
|
|
11880
|
+
const draftProfiles = await Promise.all(
|
|
11881
|
+
practitionerIds.map((id) => this.getPractitioner(id))
|
|
11882
|
+
);
|
|
11883
|
+
const validDrafts = draftProfiles.filter((p) => {
|
|
11884
|
+
if (!p) return false;
|
|
11885
|
+
if (p.status !== "draft" /* DRAFT */) {
|
|
11886
|
+
throw new Error(`Practitioner ${p.id} has already been claimed`);
|
|
11887
|
+
}
|
|
11888
|
+
return true;
|
|
11889
|
+
});
|
|
11890
|
+
if (validDrafts.length === 0) {
|
|
11891
|
+
throw new Error("No valid draft profiles found");
|
|
11892
|
+
}
|
|
11893
|
+
const existingPractitioner = await this.getPractitionerByUserRef(userId);
|
|
11894
|
+
if (existingPractitioner) {
|
|
11895
|
+
let mergedClinics2 = new Set(existingPractitioner.clinics);
|
|
11896
|
+
let mergedWorkingHours2 = [...existingPractitioner.clinicWorkingHours];
|
|
11897
|
+
let mergedClinicsInfo2 = [...existingPractitioner.clinicsInfo];
|
|
11898
|
+
for (const draft of validDrafts) {
|
|
11899
|
+
draft.clinics.forEach((clinicId) => mergedClinics2.add(clinicId));
|
|
11900
|
+
for (const workingHours of draft.clinicWorkingHours) {
|
|
11901
|
+
if (!mergedWorkingHours2.find((wh) => wh.clinicId === workingHours.clinicId)) {
|
|
11902
|
+
mergedWorkingHours2.push(workingHours);
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
for (const clinicInfo of draft.clinicsInfo) {
|
|
11906
|
+
if (!mergedClinicsInfo2.find((ci) => ci.id === clinicInfo.id)) {
|
|
11907
|
+
mergedClinicsInfo2.push(clinicInfo);
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
}
|
|
11911
|
+
const updatedPractitioner2 = await this.updatePractitioner(existingPractitioner.id, {
|
|
11912
|
+
clinics: Array.from(mergedClinics2),
|
|
11913
|
+
clinicWorkingHours: mergedWorkingHours2,
|
|
11914
|
+
clinicsInfo: mergedClinicsInfo2
|
|
11915
|
+
});
|
|
11916
|
+
for (const draft of validDrafts) {
|
|
11917
|
+
await (0, import_firestore32.deleteDoc)((0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, draft.id));
|
|
11918
|
+
const activeTokens = await this.getPractitionerActiveTokens(draft.id);
|
|
11919
|
+
for (const token of activeTokens) {
|
|
11920
|
+
await this.markTokenAsUsed(token.id, draft.id, userId);
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
return updatedPractitioner2;
|
|
11924
|
+
}
|
|
11925
|
+
const mainDraft = validDrafts[0];
|
|
11926
|
+
const otherDrafts = validDrafts.slice(1);
|
|
11927
|
+
let mergedClinics = new Set(mainDraft.clinics);
|
|
11928
|
+
let mergedWorkingHours = [...mainDraft.clinicWorkingHours];
|
|
11929
|
+
let mergedClinicsInfo = [...mainDraft.clinicsInfo];
|
|
11930
|
+
for (const draft of otherDrafts) {
|
|
11931
|
+
draft.clinics.forEach((clinicId) => mergedClinics.add(clinicId));
|
|
11932
|
+
for (const workingHours of draft.clinicWorkingHours) {
|
|
11933
|
+
if (!mergedWorkingHours.find((wh) => wh.clinicId === workingHours.clinicId)) {
|
|
11934
|
+
mergedWorkingHours.push(workingHours);
|
|
11935
|
+
}
|
|
11936
|
+
}
|
|
11937
|
+
for (const clinicInfo of draft.clinicsInfo) {
|
|
11938
|
+
if (!mergedClinicsInfo.find((ci) => ci.id === clinicInfo.id)) {
|
|
11939
|
+
mergedClinicsInfo.push(clinicInfo);
|
|
11940
|
+
}
|
|
11941
|
+
}
|
|
11942
|
+
}
|
|
11943
|
+
const updatedPractitioner = await this.updatePractitioner(mainDraft.id, {
|
|
11944
|
+
userRef: userId,
|
|
11945
|
+
status: "active" /* ACTIVE */,
|
|
11946
|
+
clinics: Array.from(mergedClinics),
|
|
11947
|
+
clinicWorkingHours: mergedWorkingHours,
|
|
11948
|
+
clinicsInfo: mergedClinicsInfo
|
|
11949
|
+
});
|
|
11950
|
+
const mainActiveTokens = await this.getPractitionerActiveTokens(mainDraft.id);
|
|
11951
|
+
for (const token of mainActiveTokens) {
|
|
11952
|
+
await this.markTokenAsUsed(token.id, mainDraft.id, userId);
|
|
11953
|
+
}
|
|
11954
|
+
for (const draft of otherDrafts) {
|
|
11955
|
+
await (0, import_firestore32.deleteDoc)((0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, draft.id));
|
|
11956
|
+
const activeTokens = await this.getPractitionerActiveTokens(draft.id);
|
|
11957
|
+
for (const token of activeTokens) {
|
|
11958
|
+
await this.markTokenAsUsed(token.id, draft.id, userId);
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11961
|
+
console.log("[PRACTITIONER] Multiple draft profiles claimed successfully", {
|
|
11962
|
+
practitionerId: updatedPractitioner.id,
|
|
11963
|
+
userId,
|
|
11964
|
+
mergedCount: validDrafts.length
|
|
11965
|
+
});
|
|
11966
|
+
return updatedPractitioner;
|
|
11967
|
+
} catch (error) {
|
|
11968
|
+
console.error(
|
|
11969
|
+
"[PRACTITIONER] Error claiming multiple draft profiles with Google:",
|
|
11970
|
+
error
|
|
11971
|
+
);
|
|
11972
|
+
throw error;
|
|
11973
|
+
}
|
|
11974
|
+
}
|
|
11718
11975
|
/**
|
|
11719
11976
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
11720
11977
|
*/
|
|
@@ -15787,6 +16044,226 @@ var AuthService = class extends BaseService {
|
|
|
15787
16044
|
throw handleFirebaseError(error);
|
|
15788
16045
|
}
|
|
15789
16046
|
}
|
|
16047
|
+
/**
|
|
16048
|
+
* Signs up or signs in a practitioner with Google authentication.
|
|
16049
|
+
* Checks for existing practitioner account or draft profiles.
|
|
16050
|
+
*
|
|
16051
|
+
* @param idToken - The Google ID token obtained from the mobile app
|
|
16052
|
+
* @returns Object containing user, practitioner (if exists), and draft profiles (if any)
|
|
16053
|
+
*/
|
|
16054
|
+
async signUpPractitionerWithGoogle(idToken) {
|
|
16055
|
+
var _a, _b;
|
|
16056
|
+
try {
|
|
16057
|
+
console.log("[AUTH] Starting practitioner Google Sign-In/Sign-Up");
|
|
16058
|
+
let email;
|
|
16059
|
+
try {
|
|
16060
|
+
const payloadBase64 = idToken.split(".")[1];
|
|
16061
|
+
const payloadJson = globalThis.atob ? globalThis.atob(payloadBase64) : Buffer.from(payloadBase64, "base64").toString("utf8");
|
|
16062
|
+
const payload = JSON.parse(payloadJson);
|
|
16063
|
+
email = payload.email;
|
|
16064
|
+
} catch (decodeError) {
|
|
16065
|
+
console.error("[AUTH] Failed to decode email from Google ID token:", decodeError);
|
|
16066
|
+
throw new AuthError(
|
|
16067
|
+
"Unable to read email from Google token. Please try again.",
|
|
16068
|
+
"AUTH/INVALID_GOOGLE_TOKEN",
|
|
16069
|
+
400
|
|
16070
|
+
);
|
|
16071
|
+
}
|
|
16072
|
+
if (!email) {
|
|
16073
|
+
throw new AuthError(
|
|
16074
|
+
"Unable to read email from Google token. Please try again.",
|
|
16075
|
+
"AUTH/INVALID_GOOGLE_TOKEN",
|
|
16076
|
+
400
|
|
16077
|
+
);
|
|
16078
|
+
}
|
|
16079
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
16080
|
+
console.log("[AUTH] Extracted email from Google token:", normalizedEmail);
|
|
16081
|
+
const methods = await (0, import_auth8.fetchSignInMethodsForEmail)(this.auth, normalizedEmail);
|
|
16082
|
+
const hasGoogleMethod = methods.includes(import_auth8.GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
|
|
16083
|
+
const hasEmailMethod = methods.includes(import_auth8.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD);
|
|
16084
|
+
const practitionerService = new PractitionerService(this.db, this.auth, this.app);
|
|
16085
|
+
if (hasGoogleMethod) {
|
|
16086
|
+
console.log("[AUTH] User exists with Google provider, signing in");
|
|
16087
|
+
const credential2 = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16088
|
+
const { user: firebaseUser2 } = await (0, import_auth8.signInWithCredential)(this.auth, credential2);
|
|
16089
|
+
let existingUser2 = null;
|
|
16090
|
+
try {
|
|
16091
|
+
existingUser2 = await this.userService.getUserById(firebaseUser2.uid);
|
|
16092
|
+
console.log("[AUTH] User document found:", existingUser2.uid);
|
|
16093
|
+
} catch (userError) {
|
|
16094
|
+
console.log("[AUTH] User document not found in Firestore, checking for draft profiles", {
|
|
16095
|
+
errorCode: userError == null ? void 0 : userError.code,
|
|
16096
|
+
errorMessage: userError == null ? void 0 : userError.message,
|
|
16097
|
+
errorType: (_a = userError == null ? void 0 : userError.constructor) == null ? void 0 : _a.name,
|
|
16098
|
+
isAuthError: userError instanceof AuthError
|
|
16099
|
+
});
|
|
16100
|
+
const practitionerService2 = new PractitionerService(this.db, this.auth, this.app);
|
|
16101
|
+
const draftProfiles3 = await practitionerService2.getDraftProfilesByEmail(normalizedEmail);
|
|
16102
|
+
console.log("[AUTH] Draft profiles check result:", {
|
|
16103
|
+
email: normalizedEmail,
|
|
16104
|
+
draftProfilesCount: draftProfiles3.length,
|
|
16105
|
+
draftProfileIds: draftProfiles3.map((p) => p.id)
|
|
16106
|
+
});
|
|
16107
|
+
if (draftProfiles3.length === 0) {
|
|
16108
|
+
console.log("[AUTH] No draft profiles found, signing out and throwing error");
|
|
16109
|
+
try {
|
|
16110
|
+
await (0, import_auth8.signOut)(this.auth);
|
|
16111
|
+
} catch (signOutError) {
|
|
16112
|
+
console.warn("[AUTH] Error signing out Firebase user (non-critical):", signOutError);
|
|
16113
|
+
}
|
|
16114
|
+
throw new AuthError(
|
|
16115
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16116
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16117
|
+
404
|
|
16118
|
+
);
|
|
16119
|
+
}
|
|
16120
|
+
console.log("[AUTH] Draft profiles found, creating user document");
|
|
16121
|
+
existingUser2 = await this.userService.createUser(firebaseUser2, ["practitioner" /* PRACTITIONER */], {
|
|
16122
|
+
skipProfileCreation: true
|
|
16123
|
+
});
|
|
16124
|
+
console.log("[AUTH] Created user document for existing Firebase user with draft profiles:", existingUser2.uid);
|
|
16125
|
+
}
|
|
16126
|
+
if (!existingUser2) {
|
|
16127
|
+
await (0, import_auth8.signOut)(this.auth);
|
|
16128
|
+
throw new AuthError(
|
|
16129
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16130
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16131
|
+
404
|
|
16132
|
+
);
|
|
16133
|
+
}
|
|
16134
|
+
let practitioner2 = null;
|
|
16135
|
+
if (existingUser2.practitionerProfile) {
|
|
16136
|
+
practitioner2 = await practitionerService.getPractitioner(existingUser2.practitionerProfile);
|
|
16137
|
+
}
|
|
16138
|
+
const draftProfiles2 = await practitionerService.getDraftProfilesByEmail(normalizedEmail);
|
|
16139
|
+
return {
|
|
16140
|
+
user: existingUser2,
|
|
16141
|
+
practitioner: practitioner2,
|
|
16142
|
+
draftProfiles: draftProfiles2
|
|
16143
|
+
};
|
|
16144
|
+
}
|
|
16145
|
+
if (hasEmailMethod && !hasGoogleMethod) {
|
|
16146
|
+
console.log("[AUTH] User exists with email/password only");
|
|
16147
|
+
throw new AuthError(
|
|
16148
|
+
"An account with this email already exists. Please sign in with your email and password, then link your Google account in settings.",
|
|
16149
|
+
"AUTH/EMAIL_ALREADY_EXISTS",
|
|
16150
|
+
409
|
|
16151
|
+
);
|
|
16152
|
+
}
|
|
16153
|
+
console.log("[AUTH] Signing in with Google credential");
|
|
16154
|
+
const credential = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16155
|
+
let firebaseUser;
|
|
16156
|
+
try {
|
|
16157
|
+
const result = await (0, import_auth8.signInWithCredential)(this.auth, credential);
|
|
16158
|
+
firebaseUser = result.user;
|
|
16159
|
+
} catch (error) {
|
|
16160
|
+
if (error.code === "auth/account-exists-with-different-credential") {
|
|
16161
|
+
throw new AuthError(
|
|
16162
|
+
"An account with this email already exists. Please sign in with your email and password, then link your Google account in settings.",
|
|
16163
|
+
"AUTH/EMAIL_ALREADY_EXISTS",
|
|
16164
|
+
409
|
|
16165
|
+
);
|
|
16166
|
+
}
|
|
16167
|
+
throw error;
|
|
16168
|
+
}
|
|
16169
|
+
let existingUser = null;
|
|
16170
|
+
try {
|
|
16171
|
+
const existingUserDoc = await this.userService.getUserById(firebaseUser.uid);
|
|
16172
|
+
if (existingUserDoc) {
|
|
16173
|
+
existingUser = existingUserDoc;
|
|
16174
|
+
console.log("[AUTH] Found existing User document");
|
|
16175
|
+
}
|
|
16176
|
+
} catch (error) {
|
|
16177
|
+
console.error("[AUTH] Error checking for existing user:", error);
|
|
16178
|
+
}
|
|
16179
|
+
console.log("[AUTH] Checking for draft profiles");
|
|
16180
|
+
let draftProfiles = [];
|
|
16181
|
+
try {
|
|
16182
|
+
draftProfiles = await practitionerService.getDraftProfilesByEmail(normalizedEmail);
|
|
16183
|
+
console.log("[AUTH] Draft profiles check complete", { count: draftProfiles.length });
|
|
16184
|
+
} catch (draftCheckError) {
|
|
16185
|
+
console.error("[AUTH] Error checking draft profiles:", draftCheckError);
|
|
16186
|
+
try {
|
|
16187
|
+
await (0, import_auth8.signOut)(this.auth);
|
|
16188
|
+
} catch (signOutError) {
|
|
16189
|
+
console.warn("[AUTH] Error signing out Firebase user (non-critical):", signOutError);
|
|
16190
|
+
}
|
|
16191
|
+
throw new AuthError(
|
|
16192
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16193
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16194
|
+
404
|
|
16195
|
+
);
|
|
16196
|
+
}
|
|
16197
|
+
let user;
|
|
16198
|
+
if (existingUser) {
|
|
16199
|
+
user = existingUser;
|
|
16200
|
+
console.log("[AUTH] Using existing user account");
|
|
16201
|
+
} else {
|
|
16202
|
+
if (draftProfiles.length === 0) {
|
|
16203
|
+
console.log("[AUTH] No draft profiles found, signing out and throwing error");
|
|
16204
|
+
try {
|
|
16205
|
+
await (0, import_auth8.signOut)(this.auth);
|
|
16206
|
+
} catch (signOutError) {
|
|
16207
|
+
console.warn("[AUTH] Error signing out Firebase user (non-critical):", signOutError);
|
|
16208
|
+
}
|
|
16209
|
+
const noDraftError = new AuthError(
|
|
16210
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16211
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16212
|
+
404
|
|
16213
|
+
);
|
|
16214
|
+
console.log("[AUTH] Throwing NO_DRAFT_PROFILES error:", noDraftError.code);
|
|
16215
|
+
throw noDraftError;
|
|
16216
|
+
}
|
|
16217
|
+
user = await this.userService.createUser(firebaseUser, ["practitioner" /* PRACTITIONER */], {
|
|
16218
|
+
skipProfileCreation: true
|
|
16219
|
+
});
|
|
16220
|
+
console.log("[AUTH] Created new user account with draft profiles available");
|
|
16221
|
+
}
|
|
16222
|
+
let practitioner = null;
|
|
16223
|
+
if (user.practitionerProfile) {
|
|
16224
|
+
practitioner = await practitionerService.getPractitioner(user.practitionerProfile);
|
|
16225
|
+
}
|
|
16226
|
+
console.log("[AUTH] Google Sign-In complete", {
|
|
16227
|
+
userId: user.uid,
|
|
16228
|
+
hasPractitioner: !!practitioner,
|
|
16229
|
+
draftProfilesCount: draftProfiles.length
|
|
16230
|
+
});
|
|
16231
|
+
return {
|
|
16232
|
+
user,
|
|
16233
|
+
practitioner,
|
|
16234
|
+
draftProfiles
|
|
16235
|
+
};
|
|
16236
|
+
} catch (error) {
|
|
16237
|
+
console.error("[AUTH] Error in signUpPractitionerWithGoogle:", error);
|
|
16238
|
+
console.error("[AUTH] Error type:", (_b = error == null ? void 0 : error.constructor) == null ? void 0 : _b.name);
|
|
16239
|
+
console.error("[AUTH] Error instanceof AuthError:", error instanceof AuthError);
|
|
16240
|
+
console.error("[AUTH] Error code:", error == null ? void 0 : error.code);
|
|
16241
|
+
console.error("[AUTH] Error message:", error == null ? void 0 : error.message);
|
|
16242
|
+
if (error instanceof AuthError) {
|
|
16243
|
+
console.log("[AUTH] Preserving AuthError:", error.code);
|
|
16244
|
+
throw error;
|
|
16245
|
+
}
|
|
16246
|
+
const errorMessage = (error == null ? void 0 : error.message) || (error == null ? void 0 : error.toString()) || "";
|
|
16247
|
+
if (errorMessage.includes("NO_DRAFT_PROFILES") || errorMessage.includes("clinic invitation")) {
|
|
16248
|
+
console.log("[AUTH] Detected clinic invitation error in message, converting to AuthError");
|
|
16249
|
+
throw new AuthError(
|
|
16250
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16251
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16252
|
+
404
|
|
16253
|
+
);
|
|
16254
|
+
}
|
|
16255
|
+
const wrappedError = handleFirebaseError(error);
|
|
16256
|
+
console.log("[AUTH] Wrapped error:", wrappedError.message);
|
|
16257
|
+
if (wrappedError.message.includes("permissions") || wrappedError.message.includes("Account creation failed")) {
|
|
16258
|
+
throw new AuthError(
|
|
16259
|
+
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16260
|
+
"AUTH/NO_DRAFT_PROFILES",
|
|
16261
|
+
404
|
|
16262
|
+
);
|
|
16263
|
+
}
|
|
16264
|
+
throw wrappedError;
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
15790
16267
|
/**
|
|
15791
16268
|
* Links a Google account to the currently signed-in user using an ID token.
|
|
15792
16269
|
* This is used to upgrade an anonymous user or to allow an existing user
|