@blackcode_sa/metaestetics-api 1.14.18 → 1.14.26

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.14.18",
4
+ "version": "1.14.26",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -117,5 +117,8 @@
117
117
  "setupFilesAfterEnv": [
118
118
  "<rootDir>/jest.setup.ts"
119
119
  ]
120
+ },
121
+ "overrides": {
122
+ "node-forge": ">=1.3.2"
120
123
  }
121
124
  }
@@ -78,21 +78,10 @@ export const practitionerInvitationTemplate = `
78
78
 
79
79
  <p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
80
80
 
81
- <p><strong>You have two options to create your account:</strong></p>
82
-
83
- <p><strong>Option 1: Sign in with Google (Recommended)</strong></p>
84
- <ol>
85
- <li>Open the MetaEsthetics Doctor App</li>
86
- <li>Click "Sign in with Google" on the login screen</li>
87
- <li>Select your Google account (use the email address: {{practitionerEmail}})</li>
88
- <li>You'll see an invitation to join {{clinicName}} - simply select it and join!</li>
89
- </ol>
90
-
91
- <p><strong>Option 2: Use Email/Password with Token</strong></p>
81
+ <p>To create your account:</p>
92
82
  <ol>
93
83
  <li>Visit {{registrationUrl}}</li>
94
- <li>Click "Claim Existing Profile with Token"</li>
95
- <li>Enter your email ({{practitionerEmail}}) and create a password</li>
84
+ <li>Enter your email and create a password</li>
96
85
  <li>When prompted, enter the token above</li>
97
86
  </ol>
98
87
 
@@ -46,12 +46,18 @@ export class BrandService extends BaseService {
46
46
  }
47
47
 
48
48
  /**
49
- * Gets a paginated list of active brands, optionally filtered by name.
49
+ * Gets a paginated list of active brands, optionally filtered by name and category.
50
50
  * @param rowsPerPage - The number of brands to fetch.
51
51
  * @param searchTerm - An optional string to filter brand names by (starts-with search).
52
52
  * @param lastVisible - An optional document snapshot to use as a cursor for pagination.
53
+ * @param category - An optional category to filter brands by.
53
54
  */
54
- async getAll(rowsPerPage: number, searchTerm?: string, lastVisible?: any) {
55
+ async getAll(
56
+ rowsPerPage: number,
57
+ searchTerm?: string,
58
+ lastVisible?: any,
59
+ category?: string
60
+ ) {
55
61
  const constraints: QueryConstraint[] = [
56
62
  where("isActive", "==", true),
57
63
  orderBy("name_lowercase"),
@@ -65,6 +71,10 @@ export class BrandService extends BaseService {
65
71
  );
66
72
  }
67
73
 
74
+ if (category) {
75
+ constraints.push(where("category", "==", category));
76
+ }
77
+
68
78
  if (lastVisible) {
69
79
  constraints.push(startAfter(lastVisible));
70
80
  }
@@ -87,10 +97,11 @@ export class BrandService extends BaseService {
87
97
  }
88
98
 
89
99
  /**
90
- * Gets the total count of active brands, optionally filtered by name.
100
+ * Gets the total count of active brands, optionally filtered by name and category.
91
101
  * @param searchTerm - An optional string to filter brand names by (starts-with search).
102
+ * @param category - An optional category to filter brands by.
92
103
  */
93
- async getBrandsCount(searchTerm?: string) {
104
+ async getBrandsCount(searchTerm?: string, category?: string) {
94
105
  const constraints: QueryConstraint[] = [where("isActive", "==", true)];
95
106
 
96
107
  if (searchTerm) {
@@ -101,6 +112,10 @@ export class BrandService extends BaseService {
101
112
  );
102
113
  }
103
114
 
115
+ if (category) {
116
+ constraints.push(where("category", "==", category));
117
+ }
118
+
104
119
  const q = query(this.getBrandsRef(), ...constraints);
105
120
  const snapshot = await getCountFromServer(q);
106
121
  return snapshot.data().count;
@@ -184,6 +199,7 @@ export class BrandService extends BaseService {
184
199
  "id",
185
200
  "name",
186
201
  "manufacturer",
202
+ "category",
187
203
  "website",
188
204
  "description",
189
205
  "isActive",
@@ -230,6 +246,7 @@ export class BrandService extends BaseService {
230
246
  brand.id ?? "",
231
247
  brand.name ?? "",
232
248
  brand.manufacturer ?? "",
249
+ brand.category ?? "",
233
250
  brand.website ?? "",
234
251
  brand.description ?? "",
235
252
  String(brand.isActive ?? ""),
@@ -7,6 +7,7 @@
7
7
  * @property manufacturer - Naziv proizvođača
8
8
  * @property description - Detaljan opis brenda i njegovih proizvoda
9
9
  * @property website - Web stranica brenda
10
+ * @property category - Kategorija brenda (npr. "laser", "peeling", "injectables") - za filtriranje
10
11
  * @property isActive - Da li je brend aktivan u sistemu
11
12
  * @property createdAt - Datum kreiranja
12
13
  * @property updatedAt - Datum poslednjeg ažuriranja
@@ -21,6 +22,7 @@ export interface Brand {
21
22
  isActive: boolean;
22
23
  website?: string;
23
24
  description?: string;
25
+ category?: string;
24
26
  }
25
27
 
26
28
  /**
@@ -35,7 +35,6 @@ import {
35
35
  Timestamp,
36
36
  runTransaction,
37
37
  Firestore,
38
- serverTimestamp,
39
38
  } from 'firebase/firestore';
40
39
  import { FirebaseApp } from 'firebase/app';
41
40
  import { User, UserRole, USERS_COLLECTION } from '../../types';
@@ -91,91 +90,40 @@ export class AuthService extends BaseService {
91
90
  constructor(db: Firestore, auth: Auth, app: FirebaseApp, userService: UserService) {
92
91
  super(db, auth, app);
93
92
  this.userService = userService || new UserService(db, auth, app);
94
-
95
- // Initialize auth state change listener for debugging
96
- // This helps track when auth.currentUser changes, which is critical for debugging
97
- // the permission-denied issue during user document creation
98
- onAuthStateChanged(this.auth, (user) => {
99
- const timestamp = new Date().toISOString();
100
- const stackTrace = new Error().stack?.split('\n').slice(2, 5).join('\n') || 'N/A';
101
- console.log(`[AUTH STATE CHANGE] ${timestamp}`);
102
- console.log(`[AUTH STATE CHANGE] User: ${user?.uid || 'NULL'} (email: ${user?.email || 'N/A'})`);
103
- console.log(`[AUTH STATE CHANGE] auth.currentUser: ${this.auth.currentUser?.uid || 'NULL'}`);
104
- console.log(`[AUTH STATE CHANGE] Stack trace (first 3 frames):\n${stackTrace}`);
105
- console.log('[AUTH STATE CHANGE] ---');
106
- });
107
-
108
- // Log initial auth state
109
- console.log('[AUTH] AuthService initialized');
110
- console.log('[AUTH] Initial auth.currentUser:', this.auth.currentUser?.uid || 'NULL');
111
93
  }
112
94
 
113
95
  /**
114
96
  * Waits for Firebase Auth state to settle after sign-in.
115
- *
116
- * In React Native with AsyncStorage persistence, there's a critical issue:
117
- * 1. signInWithCredential() sets auth.currentUser in memory immediately
118
- * 2. But AsyncStorage persistence happens asynchronously
119
- * 3. If AsyncStorage reads an old NULL value, it can OVERWRITE the current auth state
120
- * 4. This causes auth.currentUser to become NULL even after it was set
121
- *
122
- * This method uses onAuthStateChanged to wait for the auth state to be SET and STABLE.
123
- * It ensures the auth state persists through AsyncStorage operations.
124
- *
125
- * @param expectedUid - The UID we expect to see in auth.currentUser
126
- * @param timeoutMs - Maximum time to wait (default 5 seconds)
127
- * @returns Promise that resolves when auth state is ready and stable
97
+ * In React Native with AsyncStorage persistence, auth state may not be immediately available.
128
98
  */
129
99
  private async waitForAuthStateToSettle(expectedUid: string, timeoutMs: number = 5000): Promise<void> {
130
- // If already correct, still wait a bit to ensure it's stable (not just set in memory)
131
100
  if (this.auth.currentUser?.uid === expectedUid) {
132
- console.log('[AUTH] Auth state appears set, waiting for stability...');
133
- // Wait a small amount to ensure AsyncStorage persistence completes
134
101
  await new Promise(resolve => setTimeout(resolve, 200));
135
- // Check again after wait
136
102
  if (this.auth.currentUser?.uid === expectedUid) {
137
- console.log('[AUTH] ✅ Auth state stable for:', expectedUid);
138
103
  return;
139
104
  }
140
105
  }
141
-
142
- console.log('[AUTH] Waiting for auth state to settle for:', expectedUid);
143
- console.log('[AUTH] Current auth.currentUser:', this.auth.currentUser?.uid || 'NULL');
144
106
 
145
107
  return new Promise((resolve, reject) => {
146
108
  const startTime = Date.now();
147
109
  let resolved = false;
148
110
 
149
- // Use onAuthStateChanged to wait for auth state to be SET
150
- // This is more reliable than polling auth.currentUser
151
111
  const unsubscribe = onAuthStateChanged(this.auth, (user) => {
152
112
  if (resolved) return;
153
113
 
154
114
  const currentUid = user?.uid || null;
155
- console.log('[AUTH] onAuthStateChanged fired:', currentUid || 'NULL');
156
115
 
157
116
  if (currentUid === expectedUid) {
158
- // Auth state is set, but wait a bit more to ensure it's stable
159
- // AsyncStorage might still be writing/reading
160
117
  setTimeout(() => {
161
118
  if (resolved) return;
162
119
 
163
- // Final check - is it still set?
164
120
  if (this.auth.currentUser?.uid === expectedUid) {
165
121
  resolved = true;
166
122
  unsubscribe();
167
123
  clearTimeout(timeout);
168
- const elapsed = Date.now() - startTime;
169
- console.log(`[AUTH] ✅ Auth state settled and stable after ${elapsed}ms for:`, expectedUid);
170
124
  resolve();
171
- } else {
172
- console.warn('[AUTH] ⚠️ Auth state became NULL after being set, waiting more...');
173
125
  }
174
- }, 300); // Wait 300ms for AsyncStorage to stabilize
175
- } else if (currentUid === null && Date.now() - startTime > 1000) {
176
- // Auth state became NULL after being set - this is the bug we're trying to fix
177
- console.error('[AUTH] ❌ Auth state became NULL after being set!');
178
- console.error('[AUTH] This indicates AsyncStorage persistence issue');
126
+ }, 300);
179
127
  }
180
128
  });
181
129
 
@@ -183,9 +131,6 @@ export class AuthService extends BaseService {
183
131
  if (resolved) return;
184
132
  resolved = true;
185
133
  unsubscribe();
186
- console.error('[AUTH] ❌ Timeout waiting for auth state to settle');
187
- console.error('[AUTH] Expected UID:', expectedUid);
188
- console.error('[AUTH] Actual auth.currentUser:', this.auth.currentUser?.uid || 'NULL');
189
134
  reject(new Error(`Timeout waiting for auth state to settle. Expected: ${expectedUid}, Got: ${this.auth.currentUser?.uid || 'NULL'}`));
190
135
  }, timeoutMs);
191
136
  });
@@ -935,31 +880,16 @@ export class AuthService extends BaseService {
935
880
  throw new AuthError('No practitioner profiles selected to claim', 'AUTH/NO_PROFILES_SELECTED', 400);
936
881
  }
937
882
 
938
- // Check auth state BEFORE sign-in
939
- console.log('[AUTH] currentUser BEFORE sign-in:', this.auth.currentUser?.uid || 'NULL');
940
-
941
- // Sign in with Google credential
942
- console.log('[AUTH] Signing in with Google credential...');
943
883
  const credential = GoogleAuthProvider.credential(idToken);
944
884
  const result = await signInWithCredential(this.auth, credential);
945
885
  const firebaseUser = result.user;
946
-
947
- // Check auth state IMMEDIATELY AFTER sign-in
948
- console.log('[AUTH] currentUser IMMEDIATELY AFTER sign-in:', this.auth.currentUser?.uid || 'NULL');
949
- console.log('[AUTH] User returned from signInWithCredential:', firebaseUser.uid);
950
886
 
951
887
  const practitionerService = new PractitionerService(this.db, this.auth, this.app);
952
888
 
953
- // Step 1: Get User document (should already exist from signUpPractitionerWithGoogle)
954
- // The User document is created IMMEDIATELY after Google sign-in in signUpPractitionerWithGoogle
955
- // This matches the token flow pattern - no delays between sign-in and User doc creation
956
889
  let user: User;
957
890
  try {
958
891
  user = await this.userService.getUserById(firebaseUser.uid);
959
- console.log('[AUTH] User document found:', user.uid);
960
892
  } catch (userError) {
961
- console.error('[AUTH] ❌ User document should already exist! It should have been created in signUpPractitionerWithGoogle.');
962
- console.error('[AUTH] This indicates a bug - User doc creation failed in the initial Google sign-in flow.');
963
893
  throw new AuthError(
964
894
  'User account not properly initialized. Please try signing in again.',
965
895
  'AUTH/USER_NOT_INITIALIZED',
@@ -967,39 +897,27 @@ export class AuthService extends BaseService {
967
897
  );
968
898
  }
969
899
 
970
- // Step 3: Claim the draft profiles
971
900
  let practitioner: Practitioner;
972
901
  if (practitionerIds.length === 1) {
973
- console.log('[AUTH] Claiming single draft profile:', practitionerIds[0]);
974
902
  practitioner = await practitionerService.claimDraftProfileWithGoogle(
975
903
  practitionerIds[0],
976
904
  firebaseUser.uid
977
905
  );
978
906
  } else {
979
- console.log('[AUTH] Claiming multiple draft profiles:', practitionerIds);
980
907
  practitioner = await practitionerService.claimMultipleDraftProfilesWithGoogle(
981
908
  practitionerIds,
982
909
  firebaseUser.uid
983
910
  );
984
911
  }
985
- console.log('[AUTH] Draft profiles claimed:', practitioner.id);
986
912
 
987
- // Step 4: Link practitioner to user
988
913
  if (!user.practitionerProfile || user.practitionerProfile !== practitioner.id) {
989
- console.log('[AUTH] Linking practitioner to user');
990
914
  await this.userService.updateUser(firebaseUser.uid, {
991
915
  practitionerProfile: practitioner.id,
992
916
  });
993
917
  }
994
918
 
995
- // Fetch updated user (with practitionerProfile reference)
996
919
  const updatedUser = await this.userService.getUserById(firebaseUser.uid);
997
920
 
998
- console.log('[AUTH] Draft profiles claimed successfully', {
999
- userId: updatedUser.uid,
1000
- practitionerId: practitioner.id,
1001
- });
1002
-
1003
921
  return {
1004
922
  user: updatedUser,
1005
923
  practitioner,
@@ -1180,13 +1098,6 @@ export class AuthService extends BaseService {
1180
1098
  const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
1181
1099
  console.log('[AUTH] Firebase user signed in:', firebaseUser.uid);
1182
1100
 
1183
- // CRITICAL: Wait for auth state to settle before making Firestore queries
1184
- // In React Native with AsyncStorage persistence, auth.currentUser can be NULL
1185
- // immediately after signInWithCredential due to async persistence race condition
1186
- console.log('[AUTH] Waiting for auth state to settle after sign-in...');
1187
- await this.waitForAuthStateToSettle(firebaseUser.uid);
1188
- console.log('[AUTH] ✅ Auth state settled, proceeding with Firestore queries');
1189
-
1190
1101
  // 4) Load our domain user document.
1191
1102
  const existingUser = await this.userService.getUserById(firebaseUser.uid);
1192
1103
  if (existingUser) {
@@ -1252,77 +1163,37 @@ export class AuthService extends BaseService {
1252
1163
  }
1253
1164
 
1254
1165
  const normalizedEmail = email.toLowerCase().trim();
1255
- console.log('[AUTH] Extracted email from Google token:', normalizedEmail);
1256
1166
 
1257
- // Check if user already exists in Firebase Auth
1258
1167
  const methods = await fetchSignInMethodsForEmail(this.auth, normalizedEmail);
1259
1168
  const hasGoogleMethod = methods.includes(GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
1260
1169
  const hasEmailMethod = methods.includes(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD);
1261
1170
 
1262
1171
  const practitionerService = new PractitionerService(this.db, this.auth, this.app);
1263
1172
 
1264
- // Case 1: User exists with Google provider - sign in and check for practitioner profile
1265
1173
  if (hasGoogleMethod) {
1266
- console.log('[AUTH] User exists with Google provider, signing in');
1267
1174
  const credential = GoogleAuthProvider.credential(idToken);
1268
1175
  const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
1269
1176
 
1270
- // CRITICAL: Wait for auth state to settle before making Firestore queries
1271
- // In React Native with AsyncStorage persistence, auth.currentUser can be NULL
1272
- // immediately after signInWithCredential due to async persistence race condition
1273
- console.log('[AUTH] Waiting for auth state to settle after sign-in...');
1274
1177
  await this.waitForAuthStateToSettle(firebaseUser.uid);
1275
- console.log('[AUTH] ✅ Auth state settled, proceeding with Firestore queries');
1276
1178
 
1277
1179
  let existingUser: User | null = null;
1278
1180
  try {
1279
1181
  existingUser = await this.userService.getUserById(firebaseUser.uid);
1280
- console.log('[AUTH] User document found:', existingUser.uid);
1281
1182
  } catch (userError: any) {
1282
- // User document doesn't exist in Firestore
1283
- console.log('[AUTH] User document not found in Firestore, checking for draft profiles', {
1284
- errorCode: userError?.code,
1285
- errorMessage: userError?.message,
1286
- errorType: userError?.constructor?.name,
1287
- isAuthError: userError instanceof AuthError,
1288
- });
1289
-
1290
- // Check for draft profiles before signing out
1291
- // CRITICAL: Firestore queries can trigger AsyncStorage reads which overwrite auth.currentUser
1292
- // If auth.currentUser is NULL, re-sign in immediately to restore it before the query
1293
1183
  if (!this.auth.currentUser || this.auth.currentUser.uid !== firebaseUser.uid) {
1294
- console.error('[AUTH] ❌ auth.currentUser became NULL before draft profile query!');
1295
- console.error('[AUTH] Expected UID:', firebaseUser.uid);
1296
- console.error('[AUTH] Actual auth.currentUser:', this.auth.currentUser?.uid || 'NULL');
1297
- console.log('[AUTH] Re-signing in to restore auth state before query...');
1298
-
1299
- // Re-sign in with the credential to restore auth.currentUser
1300
- // This is the most reliable way to ensure auth state is set before Firestore queries
1301
1184
  const credential = GoogleAuthProvider.credential(idToken);
1302
1185
  await signInWithCredential(this.auth, credential);
1303
-
1304
- // Wait for auth state to settle after re-sign-in
1305
1186
  await this.waitForAuthStateToSettle(firebaseUser.uid, 2000);
1306
-
1307
- console.log('[AUTH] ✅ Auth state restored, proceeding with query');
1308
1187
  }
1309
1188
 
1310
1189
  const practitionerService = new PractitionerService(this.db, this.auth, this.app);
1311
1190
  const draftProfiles = await practitionerService.getDraftProfilesByEmail(normalizedEmail);
1312
1191
 
1313
- console.log('[AUTH] Draft profiles check result:', {
1314
- email: normalizedEmail,
1315
- draftProfilesCount: draftProfiles.length,
1316
- draftProfileIds: draftProfiles.map(p => p.id),
1317
- });
1318
-
1319
1192
  if (draftProfiles.length === 0) {
1320
- // No draft profiles - sign out and throw error
1321
- console.log('[AUTH] No draft profiles found, signing out and throwing error');
1322
1193
  try {
1323
1194
  await firebaseSignOut(this.auth);
1324
1195
  } catch (signOutError) {
1325
- console.warn('[AUTH] Error signing out Firebase user (non-critical):', signOutError);
1196
+ console.warn('[AUTH] Error signing out:', signOutError);
1326
1197
  }
1327
1198
  throw new AuthError(
1328
1199
  'No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.',
@@ -1331,35 +1202,21 @@ export class AuthService extends BaseService {
1331
1202
  );
1332
1203
  }
1333
1204
 
1334
- // Draft profiles exist - CREATE USER DOCUMENT IMMEDIATELY (like token flow)
1335
- // This matches the token flow pattern: create User doc right after sign-in, before any delays
1336
- console.log('[AUTH] Draft profiles found, creating User document IMMEDIATELY after sign-in');
1337
- console.log('[AUTH] auth.currentUser at User creation time:', this.auth.currentUser?.uid || 'NULL');
1338
-
1339
1205
  try {
1340
- // Create User document immediately (same pattern as token flow)
1341
1206
  const newUser = await this.userService.createUser(firebaseUser, [UserRole.PRACTITIONER], {
1342
1207
  skipProfileCreation: true,
1343
1208
  });
1344
- console.log('[AUTH] ✅ User document created successfully:', newUser.uid);
1345
1209
 
1346
- // Return user + draft profiles (user doc already exists, just need to claim profiles)
1347
1210
  return {
1348
1211
  user: newUser,
1349
1212
  practitioner: null,
1350
1213
  draftProfiles: draftProfiles,
1351
1214
  };
1352
1215
  } catch (createUserError: any) {
1353
- console.error('[AUTH] ❌ Failed to create User document:', {
1354
- errorCode: createUserError?.code,
1355
- errorMessage: createUserError?.message,
1356
- uid: firebaseUser.uid,
1357
- });
1358
- // Sign out on failure
1359
1216
  try {
1360
1217
  await firebaseSignOut(this.auth);
1361
1218
  } catch (signOutError) {
1362
- console.warn('[AUTH] Error signing out Firebase user (non-critical):', signOutError);
1219
+ console.warn('[AUTH] Error signing out:', signOutError);
1363
1220
  }
1364
1221
  throw createUserError;
1365
1222
  }
@@ -1391,11 +1248,7 @@ export class AuthService extends BaseService {
1391
1248
  };
1392
1249
  }
1393
1250
 
1394
- // Case 2: User exists with email/password - need to link Google provider
1395
- // Firebase doesn't allow linking without being signed in first
1396
- // So we need to ask user to sign in with email/password first, then link
1397
1251
  if (hasEmailMethod && !hasGoogleMethod) {
1398
- console.log('[AUTH] User exists with email/password only');
1399
1252
  throw new AuthError(
1400
1253
  'An account with this email already exists. Please sign in with your email and password, then link your Google account in settings.',
1401
1254
  'AUTH/EMAIL_ALREADY_EXISTS',
@@ -1403,8 +1256,6 @@ export class AuthService extends BaseService {
1403
1256
  );
1404
1257
  }
1405
1258
 
1406
- // Case 3: New user - sign in with Google and check for draft profiles
1407
- console.log('[AUTH] Signing in with Google credential');
1408
1259
  const credential = GoogleAuthProvider.credential(idToken);
1409
1260
 
1410
1261
  let firebaseUser: FirebaseUser;
@@ -1423,39 +1274,26 @@ export class AuthService extends BaseService {
1423
1274
  throw error;
1424
1275
  }
1425
1276
 
1426
- // CRITICAL: Wait for auth state to settle before making Firestore queries
1427
- // In React Native with AsyncStorage persistence, auth.currentUser can be NULL
1428
- // immediately after signInWithCredential due to async persistence race condition
1429
- console.log('[AUTH] Waiting for auth state to settle after sign-in...');
1430
1277
  await this.waitForAuthStateToSettle(firebaseUser.uid);
1431
- console.log('[AUTH] ✅ Auth state settled, proceeding with Firestore queries');
1432
1278
 
1433
- // Check for existing User document (in case user had email/password account that was just linked)
1434
1279
  let existingUser: User | null = null;
1435
1280
  try {
1436
1281
  const existingUserDoc = await this.userService.getUserById(firebaseUser.uid);
1437
1282
  if (existingUserDoc) {
1438
1283
  existingUser = existingUserDoc;
1439
- console.log('[AUTH] Found existing User document');
1440
1284
  }
1441
1285
  } catch (error) {
1442
- console.error('[AUTH] Error checking for existing user:', error);
1443
1286
  // Continue with new user creation
1444
1287
  }
1445
1288
 
1446
- // Check for draft profiles
1447
- console.log('[AUTH] Checking for draft profiles');
1448
1289
  let draftProfiles: Practitioner[] = [];
1449
1290
  try {
1450
1291
  draftProfiles = await practitionerService.getDraftProfilesByEmail(normalizedEmail);
1451
- console.log('[AUTH] Draft profiles check complete', { count: draftProfiles.length });
1452
1292
  } catch (draftCheckError: any) {
1453
- console.error('[AUTH] Error checking draft profiles:', draftCheckError);
1454
- // If checking draft profiles fails, sign out and throw appropriate error
1455
1293
  try {
1456
1294
  await firebaseSignOut(this.auth);
1457
1295
  } catch (signOutError) {
1458
- console.warn('[AUTH] Error signing out Firebase user (non-critical):', signOutError);
1296
+ console.warn('[AUTH] Error signing out:', signOutError);
1459
1297
  }
1460
1298
  throw new AuthError(
1461
1299
  'No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.',
@@ -1466,72 +1304,43 @@ export class AuthService extends BaseService {
1466
1304
 
1467
1305
  let user: User;
1468
1306
  if (existingUser) {
1469
- // User exists - use existing account
1470
1307
  user = existingUser;
1471
- console.log('[AUTH] Using existing user account');
1472
1308
  } else {
1473
- // For new users: Only create account if there are draft profiles OR if user already has a practitioner profile
1474
- // Since doctors can only join via clinic invitation, we should not create accounts without invitations
1475
1309
  if (draftProfiles.length === 0) {
1476
- console.log('[AUTH] No draft profiles found, signing out and throwing error');
1477
- // Sign out the Firebase user since we're not creating an account
1478
- // Wrap in try-catch to handle any sign-out errors gracefully
1479
1310
  try {
1480
1311
  await firebaseSignOut(this.auth);
1481
1312
  } catch (signOutError) {
1482
- console.warn('[AUTH] Error signing out Firebase user (non-critical):', signOutError);
1483
- // Continue anyway - the important part is we're not creating the account
1313
+ console.warn('[AUTH] Error signing out:', signOutError);
1484
1314
  }
1485
- const noDraftError = new AuthError(
1315
+ throw new AuthError(
1486
1316
  'No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.',
1487
1317
  'AUTH/NO_DRAFT_PROFILES',
1488
1318
  404,
1489
1319
  );
1490
- console.log('[AUTH] Throwing NO_DRAFT_PROFILES error:', noDraftError.code);
1491
- throw noDraftError;
1492
1320
  }
1493
1321
 
1494
- // Create new user document only if draft profiles exist
1495
1322
  user = await this.userService.createUser(firebaseUser, [UserRole.PRACTITIONER], {
1496
1323
  skipProfileCreation: true,
1497
1324
  });
1498
- console.log('[AUTH] Created new user account with draft profiles available');
1499
1325
  }
1500
1326
 
1501
- // Check if user already has practitioner profile
1502
1327
  let practitioner: Practitioner | null = null;
1503
1328
  if (user.practitionerProfile) {
1504
1329
  practitioner = await practitionerService.getPractitioner(user.practitionerProfile);
1505
1330
  }
1506
1331
 
1507
- console.log('[AUTH] Google Sign-In complete', {
1508
- userId: user.uid,
1509
- hasPractitioner: !!practitioner,
1510
- draftProfilesCount: draftProfiles.length,
1511
- });
1512
-
1513
1332
  return {
1514
1333
  user,
1515
1334
  practitioner,
1516
1335
  draftProfiles,
1517
1336
  };
1518
1337
  } catch (error: any) {
1519
- console.error('[AUTH] Error in signUpPractitionerWithGoogle:', error);
1520
- console.error('[AUTH] Error type:', error?.constructor?.name);
1521
- console.error('[AUTH] Error instanceof AuthError:', error instanceof AuthError);
1522
- console.error('[AUTH] Error code:', error?.code);
1523
- console.error('[AUTH] Error message:', error?.message);
1524
-
1525
- // Preserve AuthError instances (like NO_DRAFT_PROFILES) without wrapping
1526
1338
  if (error instanceof AuthError) {
1527
- console.log('[AUTH] Preserving AuthError:', error.code);
1528
1339
  throw error;
1529
1340
  }
1530
1341
 
1531
- // Check if error message contains NO_DRAFT_PROFILES before wrapping
1532
1342
  const errorMessage = error?.message || error?.toString() || '';
1533
1343
  if (errorMessage.includes('NO_DRAFT_PROFILES') || errorMessage.includes('clinic invitation')) {
1534
- console.log('[AUTH] Detected clinic invitation error in message, converting to AuthError');
1535
1344
  throw new AuthError(
1536
1345
  'No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.',
1537
1346
  'AUTH/NO_DRAFT_PROFILES',
@@ -1539,14 +1348,9 @@ export class AuthService extends BaseService {
1539
1348
  );
1540
1349
  }
1541
1350
 
1542
- // For other errors, wrap them but preserve the original message if it's helpful
1543
1351
  const wrappedError = handleFirebaseError(error);
1544
- console.log('[AUTH] Wrapped error:', wrappedError.message);
1545
1352
 
1546
- // If the wrapped error message is generic, try to preserve more context
1547
1353
  if (wrappedError.message.includes('permissions') || wrappedError.message.includes('Account creation failed')) {
1548
- // This might be a permissions error during sign-out or user creation
1549
- // If we got here and there were no draft profiles, it's likely the same issue
1550
1354
  throw new AuthError(
1551
1355
  'No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.',
1552
1356
  'AUTH/NO_DRAFT_PROFILES',