@blackcode_sa/metaestetics-api 1.12.8 → 1.12.10

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.
@@ -6,8 +6,6 @@ import {
6
6
  signInAnonymously as firebaseSignInAnonymously,
7
7
  signOut as firebaseSignOut,
8
8
  GoogleAuthProvider,
9
- FacebookAuthProvider,
10
- OAuthProvider,
11
9
  signInWithPopup,
12
10
  signInWithRedirect,
13
11
  getRedirectResult,
@@ -18,7 +16,8 @@ import {
18
16
  verifyPasswordResetCode,
19
17
  confirmPasswordReset,
20
18
  fetchSignInMethodsForEmail,
21
- } from "firebase/auth";
19
+ signInWithCredential,
20
+ } from 'firebase/auth';
22
21
  import {
23
22
  getFirestore,
24
23
  collection,
@@ -36,21 +35,17 @@ import {
36
35
  Timestamp,
37
36
  runTransaction,
38
37
  Firestore,
39
- } from "firebase/firestore";
40
- import { FirebaseApp } from "firebase/app";
41
- import { User, UserRole, USERS_COLLECTION } from "../../types";
42
- import { z } from "zod";
43
- import {
44
- emailSchema,
45
- passwordSchema,
46
- userRoleSchema,
47
- } from "../../validations/schemas";
48
- import { AuthError, AUTH_ERRORS } from "../../errors/auth.errors";
49
- import { FirebaseErrorCode } from "../../errors/firebase.errors";
50
- import { FirebaseError } from "../../errors/firebase.errors";
51
- import { BaseService } from "../base.service";
52
- import { UserService } from "../user/user.service";
53
- import { throws } from "assert";
38
+ } from 'firebase/firestore';
39
+ import { FirebaseApp } from 'firebase/app';
40
+ import { User, UserRole, USERS_COLLECTION } from '../../types';
41
+ import { z } from 'zod';
42
+ import { emailSchema, passwordSchema, userRoleSchema } from '../../validations/schemas';
43
+ import { AuthError, AUTH_ERRORS } from '../../errors/auth.errors';
44
+ import { FirebaseErrorCode } from '../../errors/firebase.errors';
45
+ import { FirebaseError } from '../../errors/firebase.errors';
46
+ import { BaseService } from '../base.service';
47
+ import { UserService } from '../user/user.service';
48
+ import { throws } from 'assert';
54
49
  import {
55
50
  ClinicGroup,
56
51
  AdminToken,
@@ -62,22 +57,22 @@ import {
62
57
  SubscriptionModel,
63
58
  CLINIC_GROUPS_COLLECTION,
64
59
  ClinicAdmin,
65
- } from "../../types/clinic";
66
- import { clinicAdminSignupSchema } from "../../validations/clinic.schema";
67
- import { ClinicGroupService } from "../clinic/clinic-group.service";
68
- import { ClinicAdminService } from "../clinic/clinic-admin.service";
69
- import { ClinicService } from "../clinic/clinic.service";
60
+ } from '../../types/clinic';
61
+ import { clinicAdminSignupSchema } from '../../validations/clinic.schema';
62
+ import { ClinicGroupService } from '../clinic/clinic-group.service';
63
+ import { ClinicAdminService } from '../clinic/clinic-admin.service';
64
+ import { ClinicService } from '../clinic/clinic.service';
70
65
  import {
71
66
  Practitioner,
72
67
  CreatePractitionerData,
73
68
  PractitionerStatus,
74
69
  PractitionerBasicInfo,
75
70
  PractitionerCertification,
76
- } from "../../types/practitioner";
77
- import { PractitionerService } from "../practitioner/practitioner.service";
78
- import { practitionerSignupSchema } from "../../validations/practitioner.schema";
79
- import { CertificationLevel } from "../../backoffice/types/static/certification.types";
80
- import { MediaService } from "../media/media.service";
71
+ } from '../../types/practitioner';
72
+ import { PractitionerService } from '../practitioner/practitioner.service';
73
+ import { practitionerSignupSchema } from '../../validations/practitioner.schema';
74
+ import { CertificationLevel } from '../../backoffice/types/static/certification.types';
75
+ import { MediaService } from '../media/media.service';
81
76
  // Import utility functions
82
77
  import {
83
78
  checkEmailExists,
@@ -86,20 +81,13 @@ import {
86
81
  handleSignupError,
87
82
  buildPractitionerData,
88
83
  validatePractitionerProfileData,
89
- } from "./utils";
84
+ } from './utils';
90
85
 
91
86
  export class AuthService extends BaseService {
92
87
  private googleProvider = new GoogleAuthProvider();
93
- private facebookProvider = new FacebookAuthProvider();
94
- private appleProvider = new OAuthProvider("apple.com");
95
88
  private userService: UserService;
96
89
 
97
- constructor(
98
- db: Firestore,
99
- auth: Auth,
100
- app: FirebaseApp,
101
- userService: UserService
102
- ) {
90
+ constructor(db: Firestore, auth: Auth, app: FirebaseApp, userService: UserService) {
103
91
  super(db, auth, app);
104
92
  this.userService = userService || new UserService(db, auth, app);
105
93
  }
@@ -113,13 +101,9 @@ export class AuthService extends BaseService {
113
101
  initialRole: UserRole = UserRole.PATIENT,
114
102
  options?: {
115
103
  patientInviteToken?: string;
116
- }
104
+ },
117
105
  ): Promise<User> {
118
- const { user: firebaseUser } = await createUserWithEmailAndPassword(
119
- this.auth,
120
- email,
121
- password
122
- );
106
+ const { user: firebaseUser } = await createUserWithEmailAndPassword(this.auth, email, password);
123
107
 
124
108
  return this.userService.createUser(firebaseUser, [initialRole], options);
125
109
  }
@@ -137,56 +121,45 @@ export class AuthService extends BaseService {
137
121
  clinicAdmin: ClinicAdmin;
138
122
  }> {
139
123
  try {
140
- console.log("[AUTH] Starting clinic admin signup process", {
124
+ console.log('[AUTH] Starting clinic admin signup process', {
141
125
  email: data.email,
142
126
  });
143
127
 
144
128
  // Validate data
145
129
  try {
146
130
  await clinicAdminSignupSchema.parseAsync(data);
147
- console.log("[AUTH] Clinic admin signup data validation passed");
131
+ console.log('[AUTH] Clinic admin signup data validation passed');
148
132
  } catch (validationError) {
149
- console.error(
150
- "[AUTH] Validation error in signUpClinicAdmin:",
151
- validationError
152
- );
133
+ console.error('[AUTH] Validation error in signUpClinicAdmin:', validationError);
153
134
  throw validationError;
154
135
  }
155
136
 
156
137
  // Create Firebase user
157
- console.log("[AUTH] Creating Firebase user");
138
+ console.log('[AUTH] Creating Firebase user');
158
139
  let firebaseUser;
159
140
  try {
160
- const result = await createUserWithEmailAndPassword(
161
- this.auth,
162
- data.email,
163
- data.password
164
- );
141
+ const result = await createUserWithEmailAndPassword(this.auth, data.email, data.password);
165
142
  firebaseUser = result.user;
166
- console.log("[AUTH] Firebase user created successfully", {
143
+ console.log('[AUTH] Firebase user created successfully', {
167
144
  uid: firebaseUser.uid,
168
145
  });
169
146
  } catch (firebaseError) {
170
- console.error("[AUTH] Firebase user creation failed:", firebaseError);
147
+ console.error('[AUTH] Firebase user creation failed:', firebaseError);
171
148
  throw handleFirebaseError(firebaseError);
172
149
  }
173
150
 
174
151
  // Create user with CLINIC_ADMIN role
175
- console.log("[AUTH] Creating user with CLINIC_ADMIN role");
152
+ console.log('[AUTH] Creating user with CLINIC_ADMIN role');
176
153
  let user;
177
154
  try {
178
- user = await this.userService.createUser(
179
- firebaseUser,
180
- [UserRole.CLINIC_ADMIN],
181
- {
182
- skipProfileCreation: true,
183
- }
184
- );
185
- console.log("[AUTH] User with CLINIC_ADMIN role created successfully", {
155
+ user = await this.userService.createUser(firebaseUser, [UserRole.CLINIC_ADMIN], {
156
+ skipProfileCreation: true,
157
+ });
158
+ console.log('[AUTH] User with CLINIC_ADMIN role created successfully', {
186
159
  userId: user.uid,
187
160
  });
188
161
  } catch (userCreationError) {
189
- console.error("[AUTH] User creation failed:", userCreationError);
162
+ console.error('[AUTH] User creation failed:', userCreationError);
190
163
  throw userCreationError;
191
164
  }
192
165
 
@@ -198,20 +171,16 @@ export class AuthService extends BaseService {
198
171
  email: data.email,
199
172
  phoneNumber: data.phoneNumber,
200
173
  };
201
- console.log("[AUTH] Contact person object created");
174
+ console.log('[AUTH] Contact person object created');
202
175
 
203
176
  // Initialize services
204
- console.log("[AUTH] Initializing clinic services");
205
- const clinicAdminService = new ClinicAdminService(
206
- this.db,
207
- this.auth,
208
- this.app
209
- );
177
+ console.log('[AUTH] Initializing clinic services');
178
+ const clinicAdminService = new ClinicAdminService(this.db, this.auth, this.app);
210
179
  const clinicGroupService = new ClinicGroupService(
211
180
  this.db,
212
181
  this.auth,
213
182
  this.app,
214
- clinicAdminService
183
+ clinicAdminService,
215
184
  );
216
185
  const mediaService = new MediaService(this.db, this.auth, this.app);
217
186
  const clinicService = new ClinicService(
@@ -220,30 +189,26 @@ export class AuthService extends BaseService {
220
189
  this.app,
221
190
  clinicGroupService,
222
191
  clinicAdminService,
223
- mediaService
192
+ mediaService,
224
193
  );
225
194
 
226
195
  // Set services to resolve circular dependencies
227
196
  clinicAdminService.setServices(clinicGroupService, clinicService);
228
- console.log(
229
- "[AUTH] Services initialized and circular dependencies resolved"
230
- );
197
+ console.log('[AUTH] Services initialized and circular dependencies resolved');
231
198
 
232
199
  let clinicGroup: ClinicGroup | null = null;
233
200
  let adminProfile: ClinicAdmin | null = null;
234
201
 
235
202
  if (data.isCreatingNewGroup) {
236
- console.log("[AUTH] Creating new clinic group flow");
203
+ console.log('[AUTH] Creating new clinic group flow');
237
204
  // Create new clinic group
238
205
  if (!data.clinicGroupData) {
239
- console.error("[AUTH] Clinic group data is missing");
240
- throw new Error(
241
- "Clinic group data is required when creating a new group"
242
- );
206
+ console.error('[AUTH] Clinic group data is missing');
207
+ throw new Error('Clinic group data is required when creating a new group');
243
208
  }
244
209
 
245
210
  // First create the clinic admin without a group
246
- console.log("[AUTH] Creating clinic admin first (without group)");
211
+ console.log('[AUTH] Creating clinic admin first (without group)');
247
212
  const createClinicAdminData: CreateClinicAdminData = {
248
213
  userRef: firebaseUser.uid,
249
214
  isGroupOwner: true,
@@ -255,34 +220,24 @@ export class AuthService extends BaseService {
255
220
  };
256
221
 
257
222
  try {
258
- adminProfile = await clinicAdminService.createClinicAdmin(
259
- createClinicAdminData
260
- );
261
- console.log("[AUTH] Clinic admin created successfully", {
223
+ adminProfile = await clinicAdminService.createClinicAdmin(createClinicAdminData);
224
+ console.log('[AUTH] Clinic admin created successfully', {
262
225
  adminId: adminProfile.id,
263
226
  });
264
227
  } catch (adminCreationError) {
265
- console.error(
266
- "[AUTH] Clinic admin creation failed:",
267
- adminCreationError
268
- );
228
+ console.error('[AUTH] Clinic admin creation failed:', adminCreationError);
269
229
  throw adminCreationError;
270
230
  }
271
231
 
272
232
  // Update user document with admin profile reference
273
233
  try {
274
- console.log("[AUTH] Updating user with admin profile reference");
234
+ console.log('[AUTH] Updating user with admin profile reference');
275
235
  user = await this.userService.updateUser(firebaseUser.uid, {
276
236
  adminProfile: adminProfile.id,
277
237
  });
278
- console.log(
279
- "[AUTH] User updated with admin profile reference successfully"
280
- );
238
+ console.log('[AUTH] User updated with admin profile reference successfully');
281
239
  } catch (userUpdateError) {
282
- console.error(
283
- "[AUTH] Failed to update user with admin profile:",
284
- userUpdateError
285
- );
240
+ console.error('[AUTH] Failed to update user with admin profile:', userUpdateError);
286
241
  throw userUpdateError;
287
242
  }
288
243
 
@@ -296,14 +251,13 @@ export class AuthService extends BaseService {
296
251
  isActive: true,
297
252
  logo: data.clinicGroupData.logo || null,
298
253
  subscriptionModel:
299
- data.clinicGroupData.subscriptionModel ||
300
- SubscriptionModel.NO_SUBSCRIPTION,
254
+ data.clinicGroupData.subscriptionModel || SubscriptionModel.NO_SUBSCRIPTION,
301
255
  onboarding: {
302
256
  completed: false,
303
257
  step: 1,
304
258
  },
305
259
  };
306
- console.log("[AUTH] Clinic group data prepared", {
260
+ console.log('[AUTH] Clinic group data prepared', {
307
261
  groupName: createClinicGroupData.name,
308
262
  });
309
263
 
@@ -312,46 +266,39 @@ export class AuthService extends BaseService {
312
266
  clinicGroup = await clinicGroupService.createClinicGroup(
313
267
  createClinicGroupData,
314
268
  adminProfile.id, // Use admin profile ID, not user UID
315
- false // This is not a default group since we're providing complete data
269
+ false, // This is not a default group since we're providing complete data
316
270
  );
317
- console.log("[AUTH] Clinic group created successfully", {
271
+ console.log('[AUTH] Clinic group created successfully', {
318
272
  groupId: clinicGroup.id,
319
273
  });
320
274
 
321
275
  // Now update the admin with the group ID
322
- console.log("[AUTH] Updating admin with clinic group ID");
276
+ console.log('[AUTH] Updating admin with clinic group ID');
323
277
  await clinicAdminService.updateClinicAdmin(adminProfile.id, {
324
278
  // Use admin profile ID, not user UID
325
279
  clinicGroupId: clinicGroup.id,
326
280
  });
327
- console.log("[AUTH] Admin updated with clinic group ID successfully");
281
+ console.log('[AUTH] Admin updated with clinic group ID successfully');
328
282
 
329
283
  // Get the updated admin profile
330
- adminProfile = await clinicAdminService.getClinicAdmin(
331
- adminProfile.id
332
- );
284
+ adminProfile = await clinicAdminService.getClinicAdmin(adminProfile.id);
333
285
  } catch (groupCreationError) {
334
- console.error(
335
- "[AUTH] Clinic group creation failed:",
336
- groupCreationError
337
- );
286
+ console.error('[AUTH] Clinic group creation failed:', groupCreationError);
338
287
  throw groupCreationError;
339
288
  }
340
289
  } else {
341
- console.log("[AUTH] Joining existing clinic group flow");
290
+ console.log('[AUTH] Joining existing clinic group flow');
342
291
  // Join existing clinic group with token
343
292
  if (!data.inviteToken) {
344
- console.error("[AUTH] Invite token is missing");
345
- throw new Error(
346
- "Invite token is required when joining an existing group"
347
- );
293
+ console.error('[AUTH] Invite token is missing');
294
+ throw new Error('Invite token is required when joining an existing group');
348
295
  }
349
- console.log("[AUTH] Invite token provided", {
296
+ console.log('[AUTH] Invite token provided', {
350
297
  token: data.inviteToken,
351
298
  });
352
299
 
353
300
  // Find the token in the database
354
- console.log("[AUTH] Searching for token in clinic groups");
301
+ console.log('[AUTH] Searching for token in clinic groups');
355
302
  const groupsRef = collection(this.db, CLINIC_GROUPS_COLLECTION);
356
303
  const q = query(groupsRef);
357
304
  const querySnapshot = await getDocs(q);
@@ -359,26 +306,22 @@ export class AuthService extends BaseService {
359
306
  let foundGroup: ClinicGroup | null = null;
360
307
  let foundToken: AdminToken | null = null;
361
308
 
362
- console.log(
363
- "[AUTH] Found",
364
- querySnapshot.size,
365
- "clinic groups to check"
366
- );
309
+ console.log('[AUTH] Found', querySnapshot.size, 'clinic groups to check');
367
310
  for (const docSnapshot of querySnapshot.docs) {
368
311
  const group = docSnapshot.data() as ClinicGroup;
369
- console.log("[AUTH] Checking group", {
312
+ console.log('[AUTH] Checking group', {
370
313
  groupId: group.id,
371
314
  groupName: group.name,
372
315
  });
373
316
 
374
317
  // Find the token in the group's tokens
375
- const token = group.adminTokens.find((t) => {
318
+ const token = group.adminTokens.find(t => {
376
319
  const isMatch =
377
320
  t.token === data.inviteToken &&
378
321
  t.status === AdminTokenStatus.ACTIVE &&
379
322
  new Date(t.expiresAt.toDate()) > new Date();
380
323
 
381
- console.log("[AUTH] Checking token", {
324
+ console.log('[AUTH] Checking token', {
382
325
  tokenId: t.id,
383
326
  tokenMatch: t.token === data.inviteToken,
384
327
  tokenStatus: t.status,
@@ -394,7 +337,7 @@ export class AuthService extends BaseService {
394
337
  if (token) {
395
338
  foundGroup = group;
396
339
  foundToken = token;
397
- console.log("[AUTH] Found matching token in group", {
340
+ console.log('[AUTH] Found matching token in group', {
398
341
  groupId: group.id,
399
342
  tokenId: token.id,
400
343
  });
@@ -403,14 +346,14 @@ export class AuthService extends BaseService {
403
346
  }
404
347
 
405
348
  if (!foundGroup || !foundToken) {
406
- console.error("[AUTH] No valid token found in any clinic group");
407
- throw new Error("Invalid or expired invite token");
349
+ console.error('[AUTH] No valid token found in any clinic group');
350
+ throw new Error('Invalid or expired invite token');
408
351
  }
409
352
 
410
353
  clinicGroup = foundGroup;
411
354
 
412
355
  // Create clinic admin
413
- console.log("[AUTH] Creating clinic admin");
356
+ console.log('[AUTH] Creating clinic admin');
414
357
  const createClinicAdminData: CreateClinicAdminData = {
415
358
  userRef: firebaseUser.uid,
416
359
  clinicGroupId: foundGroup.id,
@@ -422,17 +365,12 @@ export class AuthService extends BaseService {
422
365
  };
423
366
 
424
367
  try {
425
- adminProfile = await clinicAdminService.createClinicAdmin(
426
- createClinicAdminData
427
- );
428
- console.log("[AUTH] Clinic admin created successfully", {
368
+ adminProfile = await clinicAdminService.createClinicAdmin(createClinicAdminData);
369
+ console.log('[AUTH] Clinic admin created successfully', {
429
370
  adminId: adminProfile.id,
430
371
  });
431
372
  } catch (adminCreationError) {
432
- console.error(
433
- "[AUTH] Clinic admin creation failed:",
434
- adminCreationError
435
- );
373
+ console.error('[AUTH] Clinic admin creation failed:', adminCreationError);
436
374
  throw adminCreationError;
437
375
  }
438
376
 
@@ -441,26 +379,24 @@ export class AuthService extends BaseService {
441
379
  await clinicGroupService.verifyAndUseAdminToken(
442
380
  foundGroup.id,
443
381
  data.inviteToken,
444
- firebaseUser.uid
382
+ firebaseUser.uid,
445
383
  );
446
- console.log("[AUTH] Token marked as used successfully");
384
+ console.log('[AUTH] Token marked as used successfully');
447
385
  } catch (tokenUseError) {
448
- console.error("[AUTH] Failed to mark token as used:", tokenUseError);
386
+ console.error('[AUTH] Failed to mark token as used:', tokenUseError);
449
387
  throw tokenUseError;
450
388
  }
451
389
  }
452
390
 
453
- console.log("[AUTH] Clinic admin signup completed successfully", {
391
+ console.log('[AUTH] Clinic admin signup completed successfully', {
454
392
  userId: user.uid,
455
393
  clinicGroupId: clinicGroup.id,
456
- clinicAdminId: adminProfile?.id || "unknown",
394
+ clinicAdminId: adminProfile?.id || 'unknown',
457
395
  });
458
396
 
459
397
  // Ensure we have all required data before returning
460
398
  if (!clinicGroup || !adminProfile) {
461
- throw new Error(
462
- "Failed to create or retrieve clinic group or admin profile"
463
- );
399
+ throw new Error('Failed to create or retrieve clinic group or admin profile');
464
400
  }
465
401
 
466
402
  return {
@@ -471,19 +407,19 @@ export class AuthService extends BaseService {
471
407
  } catch (error) {
472
408
  if (error instanceof z.ZodError) {
473
409
  console.error(
474
- "[AUTH] Zod validation error in signUpClinicAdmin:",
475
- JSON.stringify(error.errors, null, 2)
410
+ '[AUTH] Zod validation error in signUpClinicAdmin:',
411
+ JSON.stringify(error.errors, null, 2),
476
412
  );
477
413
  throw AUTH_ERRORS.VALIDATION_ERROR;
478
414
  }
479
415
 
480
416
  const firebaseError = error as FirebaseError;
481
417
  if (firebaseError.code === FirebaseErrorCode.EMAIL_ALREADY_IN_USE) {
482
- console.error("[AUTH] Email already in use:", data.email);
418
+ console.error('[AUTH] Email already in use:', data.email);
483
419
  throw AUTH_ERRORS.EMAIL_ALREADY_EXISTS;
484
420
  }
485
421
 
486
- console.error("[AUTH] Unhandled error in signUpClinicAdmin:", error);
422
+ console.error('[AUTH] Unhandled error in signUpClinicAdmin:', error);
487
423
  throw error;
488
424
  }
489
425
  }
@@ -492,11 +428,7 @@ export class AuthService extends BaseService {
492
428
  * Prijavljuje korisnika sa email-om i lozinkom
493
429
  */
494
430
  async signIn(email: string, password: string): Promise<User> {
495
- const { user: firebaseUser } = await signInWithEmailAndPassword(
496
- this.auth,
497
- email,
498
- password
499
- );
431
+ const { user: firebaseUser } = await signInWithEmailAndPassword(this.auth, email, password);
500
432
 
501
433
  return this.userService.getOrCreateUser(firebaseUser);
502
434
  }
@@ -511,7 +443,7 @@ export class AuthService extends BaseService {
511
443
  */
512
444
  async signInClinicAdmin(
513
445
  email: string,
514
- password: string
446
+ password: string,
515
447
  ): Promise<{
516
448
  user: User;
517
449
  clinicAdmin: ClinicAdmin;
@@ -519,16 +451,12 @@ export class AuthService extends BaseService {
519
451
  }> {
520
452
  try {
521
453
  // Initialize required services
522
- const clinicAdminService = new ClinicAdminService(
523
- this.db,
524
- this.auth,
525
- this.app
526
- );
454
+ const clinicAdminService = new ClinicAdminService(this.db, this.auth, this.app);
527
455
  const clinicGroupService = new ClinicGroupService(
528
456
  this.db,
529
457
  this.auth,
530
458
  this.app,
531
- clinicAdminService
459
+ clinicAdminService,
532
460
  );
533
461
  const mediaService = new MediaService(this.db, this.auth, this.app);
534
462
  const clinicService = new ClinicService(
@@ -537,52 +465,41 @@ export class AuthService extends BaseService {
537
465
  this.app,
538
466
  clinicGroupService,
539
467
  clinicAdminService,
540
- mediaService
468
+ mediaService,
541
469
  );
542
470
 
543
471
  // Set services to resolve circular dependencies
544
472
  clinicAdminService.setServices(clinicGroupService, clinicService);
545
473
 
546
474
  // Sign in with email/password
547
- const { user: firebaseUser } = await signInWithEmailAndPassword(
548
- this.auth,
549
- email,
550
- password
551
- );
475
+ const { user: firebaseUser } = await signInWithEmailAndPassword(this.auth, email, password);
552
476
 
553
477
  // Get or create user
554
478
  const user = await this.userService.getOrCreateUser(firebaseUser);
555
479
 
556
480
  // Check if user has clinic_admin role
557
481
  if (!user.roles?.includes(UserRole.CLINIC_ADMIN)) {
558
- console.error("[AUTH] User is not a clinic admin:", user.uid);
482
+ console.error('[AUTH] User is not a clinic admin:', user.uid);
559
483
  throw AUTH_ERRORS.INVALID_ROLE;
560
484
  }
561
485
 
562
486
  // Check and get admin profile
563
487
  if (!user.adminProfile) {
564
- console.error("[AUTH] User has no admin profile:", user.uid);
488
+ console.error('[AUTH] User has no admin profile:', user.uid);
565
489
  throw AUTH_ERRORS.NOT_FOUND;
566
490
  }
567
491
 
568
492
  // Get clinic admin profile
569
- const adminProfile = await clinicAdminService.getClinicAdmin(
570
- user.adminProfile
571
- );
493
+ const adminProfile = await clinicAdminService.getClinicAdmin(user.adminProfile);
572
494
  if (!adminProfile) {
573
- console.error("[AUTH] Admin profile not found:", user.adminProfile);
495
+ console.error('[AUTH] Admin profile not found:', user.adminProfile);
574
496
  throw AUTH_ERRORS.NOT_FOUND;
575
497
  }
576
498
 
577
499
  // Get clinic group
578
- const clinicGroup = await clinicGroupService.getClinicGroup(
579
- adminProfile.clinicGroupId
580
- );
500
+ const clinicGroup = await clinicGroupService.getClinicGroup(adminProfile.clinicGroupId);
581
501
  if (!clinicGroup) {
582
- console.error(
583
- "[AUTH] Clinic group not found:",
584
- adminProfile.clinicGroupId
585
- );
502
+ console.error('[AUTH] Clinic group not found:', adminProfile.clinicGroupId);
586
503
  throw AUTH_ERRORS.NOT_FOUND;
587
504
  }
588
505
 
@@ -592,56 +509,11 @@ export class AuthService extends BaseService {
592
509
  clinicGroup,
593
510
  };
594
511
  } catch (error) {
595
- console.error("[AUTH] Error in signInClinicAdmin:", error);
512
+ console.error('[AUTH] Error in signInClinicAdmin:', error);
596
513
  throw error;
597
514
  }
598
515
  }
599
516
 
600
- /**
601
- * Prijavljuje korisnika sa Facebook-om
602
- */
603
- async signInWithFacebook(): Promise<User> {
604
- const provider = new FacebookAuthProvider();
605
- const { user: firebaseUser } = await signInWithPopup(this.auth, provider);
606
-
607
- return this.userService.getOrCreateUser(firebaseUser);
608
- }
609
-
610
- /**
611
- * Prijavljuje korisnika sa Google nalogom
612
- */
613
- async signInWithGoogle(
614
- initialRole: UserRole = UserRole.PATIENT,
615
- options?: {
616
- patientInviteToken?: string;
617
- }
618
- ): Promise<User> {
619
- const { user: firebaseUser } = await signInWithPopup(
620
- this.auth,
621
- this.googleProvider
622
- );
623
-
624
- const existingUser = await this.userService.getUserByEmail(
625
- firebaseUser.email!
626
- );
627
- if (existingUser) {
628
- await this.userService.updateUserLoginTimestamp(existingUser.uid);
629
- return existingUser;
630
- }
631
-
632
- return this.userService.createUser(firebaseUser, [initialRole], options);
633
- }
634
-
635
- /**
636
- * Prijavljuje korisnika sa Apple-om
637
- */
638
- async signInWithApple(): Promise<User> {
639
- const provider = new OAuthProvider("apple.com");
640
- const { user: firebaseUser } = await signInWithPopup(this.auth, provider);
641
-
642
- return this.userService.getOrCreateUser(firebaseUser);
643
- }
644
-
645
517
  /**
646
518
  * Prijavljuje korisnika anonimno
647
519
  */
@@ -685,20 +557,13 @@ export class AuthService extends BaseService {
685
557
  throw AUTH_ERRORS.NOT_AUTHENTICATED;
686
558
  }
687
559
  if (!currentUser.isAnonymous) {
688
- throw new AuthError(
689
- "User is not anonymous",
690
- "AUTH/NOT_ANONYMOUS_USER",
691
- 400
692
- );
560
+ throw new AuthError('User is not anonymous', 'AUTH/NOT_ANONYMOUS_USER', 400);
693
561
  }
694
562
 
695
563
  const credential = EmailAuthProvider.credential(email, password);
696
564
  await linkWithCredential(currentUser, credential);
697
565
 
698
- return await this.userService.upgradeAnonymousUser(
699
- currentUser.uid,
700
- email
701
- );
566
+ return await this.userService.upgradeAnonymousUser(currentUser.uid, email);
702
567
  } catch (error) {
703
568
  if (error instanceof z.ZodError) {
704
569
  throw AUTH_ERRORS.VALIDATION_ERROR;
@@ -711,118 +576,6 @@ export class AuthService extends BaseService {
711
576
  }
712
577
  }
713
578
 
714
- /**
715
- * Upgrades an anonymous user to a regular user by signing in with a Google account.
716
- *
717
- * @throws {AuthError} If the user is not anonymous.
718
- * @throws {AuthError} If the user is not authenticated.
719
- * @throws {AuthError} If the popup window is closed by the user.
720
- * @throws {FirebaseError} If any other Firebase error occurs.
721
- *
722
- * @returns {Promise<User>} The upgraded user.
723
- */
724
- async upgradeAnonymousUserWithGoogle(): Promise<User> {
725
- try {
726
- const currentUser = this.auth.currentUser;
727
- if (!currentUser) {
728
- throw AUTH_ERRORS.NOT_AUTHENTICATED;
729
- }
730
- if (!currentUser.isAnonymous) {
731
- throw new AuthError(
732
- "User is not anonymous",
733
- "AUTH/NOT_ANONYMOUS_USER",
734
- 400
735
- );
736
- }
737
-
738
- const userCredential = await signInWithPopup(
739
- this.auth,
740
- this.googleProvider
741
- );
742
- if (!userCredential) throw AUTH_ERRORS.INVALID_CREDENTIAL;
743
-
744
- return await this.userService.upgradeAnonymousUser(
745
- currentUser.uid,
746
- userCredential.user.email!
747
- );
748
- } catch (error: unknown) {
749
- const firebaseError = error as FirebaseError;
750
- if (firebaseError.code === FirebaseErrorCode.POPUP_CLOSED_BY_USER) {
751
- throw AUTH_ERRORS.POPUP_CLOSED;
752
- }
753
- throw error;
754
- }
755
- }
756
-
757
- async upgradeAnonymousUserWithFacebook(): Promise<User> {
758
- try {
759
- const currentUser = this.auth.currentUser;
760
- if (!currentUser) {
761
- throw AUTH_ERRORS.NOT_AUTHENTICATED;
762
- }
763
- if (!currentUser.isAnonymous) {
764
- throw new AuthError(
765
- "User is not anonymous",
766
- "AUTH/NOT_ANONYMOUS_USER",
767
- 400
768
- );
769
- }
770
-
771
- this.facebookProvider.addScope("email");
772
- const userCredential = await signInWithPopup(
773
- this.auth,
774
- this.facebookProvider
775
- );
776
- if (!userCredential) throw AUTH_ERRORS.INVALID_CREDENTIAL;
777
-
778
- return await this.userService.upgradeAnonymousUser(
779
- currentUser.uid,
780
- userCredential.user.email!
781
- );
782
- } catch (error: unknown) {
783
- const firebaseError = error as FirebaseError;
784
- if (firebaseError.code === FirebaseErrorCode.POPUP_CLOSED_BY_USER) {
785
- throw AUTH_ERRORS.POPUP_CLOSED;
786
- }
787
- throw error;
788
- }
789
- }
790
-
791
- async upgradeAnonymousUserWithApple(): Promise<User> {
792
- try {
793
- const currentUser = this.auth.currentUser;
794
- if (!currentUser) {
795
- throw AUTH_ERRORS.NOT_AUTHENTICATED;
796
- }
797
- if (!currentUser.isAnonymous) {
798
- throw new AuthError(
799
- "User is not anonymous",
800
- "AUTH/NOT_ANONYMOUS_USER",
801
- 400
802
- );
803
- }
804
-
805
- this.appleProvider.addScope("email");
806
- this.appleProvider.addScope("name");
807
- const userCredential = await signInWithPopup(
808
- this.auth,
809
- this.appleProvider
810
- );
811
- if (!userCredential) throw AUTH_ERRORS.INVALID_CREDENTIAL;
812
-
813
- return await this.userService.upgradeAnonymousUser(
814
- currentUser.uid,
815
- userCredential.user.email!
816
- );
817
- } catch (error: unknown) {
818
- const firebaseError = error as FirebaseError;
819
- if (firebaseError.code === FirebaseErrorCode.POPUP_CLOSED_BY_USER) {
820
- throw AUTH_ERRORS.POPUP_CLOSED;
821
- }
822
- throw error;
823
- }
824
- }
825
-
826
579
  /**
827
580
  * Šalje email za resetovanje lozinke korisniku
828
581
  * @param email Email adresa korisnika
@@ -875,10 +628,7 @@ export class AuthService extends BaseService {
875
628
  * @param newPassword Nova lozinka
876
629
  * @returns Promise koji se razrešava kada je lozinka promenjena
877
630
  */
878
- async confirmPasswordReset(
879
- oobCode: string,
880
- newPassword: string
881
- ): Promise<void> {
631
+ async confirmPasswordReset(oobCode: string, newPassword: string): Promise<void> {
882
632
  try {
883
633
  await passwordSchema.parseAsync(newPassword);
884
634
 
@@ -923,7 +673,7 @@ export class AuthService extends BaseService {
923
673
  let firebaseUser: any = null;
924
674
 
925
675
  try {
926
- console.log("[AUTH] Starting atomic practitioner signup process", {
676
+ console.log('[AUTH] Starting atomic practitioner signup process', {
927
677
  email: data.email,
928
678
  hasToken: !!data.token,
929
679
  });
@@ -932,95 +682,69 @@ export class AuthService extends BaseService {
932
682
  await this.validateSignupData(data);
933
683
 
934
684
  // Step 2: Create Firebase user (outside transaction - can't be easily rolled back)
935
- console.log("[AUTH] Creating Firebase user");
685
+ console.log('[AUTH] Creating Firebase user');
936
686
  try {
937
- const result = await createUserWithEmailAndPassword(
938
- this.auth,
939
- data.email,
940
- data.password
941
- );
687
+ const result = await createUserWithEmailAndPassword(this.auth, data.email, data.password);
942
688
  firebaseUser = result.user;
943
- console.log("[AUTH] Firebase user created successfully", {
689
+ console.log('[AUTH] Firebase user created successfully', {
944
690
  uid: firebaseUser.uid,
945
691
  });
946
692
  } catch (firebaseError) {
947
- console.error("[AUTH] Firebase user creation failed:", firebaseError);
693
+ console.error('[AUTH] Firebase user creation failed:', firebaseError);
948
694
  throw handleFirebaseError(firebaseError);
949
695
  }
950
696
 
951
697
  // Step 3: Execute all database operations in a single transaction
952
- console.log("[AUTH] Starting Firestore transaction");
953
- const transactionResult = await runTransaction(
954
- this.db,
955
- async (transaction) => {
956
- console.log(
957
- "[AUTH] Transaction started - creating user and practitioner"
958
- );
698
+ console.log('[AUTH] Starting Firestore transaction');
699
+ const transactionResult = await runTransaction(this.db, async transaction => {
700
+ console.log('[AUTH] Transaction started - creating user and practitioner');
959
701
 
960
- // Initialize services
961
- const practitionerService = new PractitionerService(
962
- this.db,
963
- this.auth,
964
- this.app
965
- );
702
+ // Initialize services
703
+ const practitionerService = new PractitionerService(this.db, this.auth, this.app);
966
704
 
967
- // Create user document using existing method (not in transaction for now)
968
- console.log("[AUTH] Creating user document");
969
- const user = await this.userService.createUser(
970
- firebaseUser,
971
- [UserRole.PRACTITIONER],
972
- { skipProfileCreation: true }
973
- );
705
+ // Create user document using existing method (not in transaction for now)
706
+ console.log('[AUTH] Creating user document');
707
+ const user = await this.userService.createUser(firebaseUser, [UserRole.PRACTITIONER], {
708
+ skipProfileCreation: true,
709
+ });
974
710
 
975
- let practitioner: Practitioner;
976
-
977
- // Handle practitioner profile creation/claiming
978
- if (data.token) {
979
- console.log(
980
- "[AUTH] Claiming existing practitioner profile with token"
981
- );
982
- const claimedPractitioner =
983
- await practitionerService.validateTokenAndClaimProfile(
984
- data.token,
985
- firebaseUser.uid
986
- );
987
- if (!claimedPractitioner) {
988
- throw new Error("Invalid or expired invitation token");
989
- }
990
- practitioner = claimedPractitioner;
991
- } else {
992
- console.log("[AUTH] Creating new practitioner profile");
993
- const practitionerData = buildPractitionerData(
994
- data,
995
- firebaseUser.uid
996
- );
997
- practitioner = await practitionerService.createPractitioner(
998
- practitionerData
999
- );
711
+ let practitioner: Practitioner;
712
+
713
+ // Handle practitioner profile creation/claiming
714
+ if (data.token) {
715
+ console.log('[AUTH] Claiming existing practitioner profile with token');
716
+ const claimedPractitioner = await practitionerService.validateTokenAndClaimProfile(
717
+ data.token,
718
+ firebaseUser.uid,
719
+ );
720
+ if (!claimedPractitioner) {
721
+ throw new Error('Invalid or expired invitation token');
1000
722
  }
723
+ practitioner = claimedPractitioner;
724
+ } else {
725
+ console.log('[AUTH] Creating new practitioner profile');
726
+ const practitionerData = buildPractitionerData(data, firebaseUser.uid);
727
+ practitioner = await practitionerService.createPractitioner(practitionerData);
728
+ }
1001
729
 
1002
- // Link practitioner to user
1003
- console.log("[AUTH] Linking practitioner to user");
1004
- await this.userService.updateUser(firebaseUser.uid, {
1005
- practitionerProfile: practitioner.id,
1006
- });
730
+ // Link practitioner to user
731
+ console.log('[AUTH] Linking practitioner to user');
732
+ await this.userService.updateUser(firebaseUser.uid, {
733
+ practitionerProfile: practitioner.id,
734
+ });
1007
735
 
1008
- console.log("[AUTH] Transaction operations completed successfully");
1009
- return { user, practitioner };
1010
- }
1011
- );
736
+ console.log('[AUTH] Transaction operations completed successfully');
737
+ return { user, practitioner };
738
+ });
1012
739
 
1013
- console.log("[AUTH] Atomic practitioner signup completed successfully", {
740
+ console.log('[AUTH] Atomic practitioner signup completed successfully', {
1014
741
  userId: transactionResult.user.uid,
1015
742
  practitionerId: transactionResult.practitioner.id,
1016
743
  });
1017
744
 
1018
745
  return transactionResult;
1019
746
  } catch (error) {
1020
- console.error(
1021
- "[AUTH] Atomic signup failed, initiating cleanup...",
1022
- error
1023
- );
747
+ console.error('[AUTH] Atomic signup failed, initiating cleanup...', error);
1024
748
 
1025
749
  // Cleanup Firebase user if transaction failed
1026
750
  if (firebaseUser) {
@@ -1043,47 +767,41 @@ export class AuthService extends BaseService {
1043
767
  token?: string;
1044
768
  profileData?: Partial<CreatePractitionerData>;
1045
769
  }): Promise<void> {
1046
- console.log("[AUTH] Pre-validating signup data");
770
+ console.log('[AUTH] Pre-validating signup data');
1047
771
 
1048
772
  try {
1049
773
  // 1. Schema validation
1050
774
  await practitionerSignupSchema.parseAsync(data);
1051
- console.log("[AUTH] Schema validation passed");
775
+ console.log('[AUTH] Schema validation passed');
1052
776
 
1053
777
  // 2. Check if email already exists (before creating Firebase user)
1054
778
  const emailExists = await checkEmailExists(this.auth, data.email);
1055
779
  if (emailExists) {
1056
- console.log("[AUTH] Email already exists:", data.email);
780
+ console.log('[AUTH] Email already exists:', data.email);
1057
781
  throw AUTH_ERRORS.EMAIL_ALREADY_EXISTS;
1058
782
  }
1059
- console.log("[AUTH] Email availability confirmed");
783
+ console.log('[AUTH] Email availability confirmed');
1060
784
 
1061
785
  // 3. Validate token if provided
1062
786
  if (data.token) {
1063
- const practitionerService = new PractitionerService(
1064
- this.db,
1065
- this.auth,
1066
- this.app
1067
- );
1068
- const isValidToken = await practitionerService.validateToken(
1069
- data.token
1070
- );
787
+ const practitionerService = new PractitionerService(this.db, this.auth, this.app);
788
+ const isValidToken = await practitionerService.validateToken(data.token);
1071
789
  if (!isValidToken) {
1072
- console.log("[AUTH] Invalid token provided:", data.token);
1073
- throw new Error("Invalid or expired invitation token");
790
+ console.log('[AUTH] Invalid token provided:', data.token);
791
+ throw new Error('Invalid or expired invitation token');
1074
792
  }
1075
- console.log("[AUTH] Token validation passed");
793
+ console.log('[AUTH] Token validation passed');
1076
794
  }
1077
795
 
1078
796
  // 4. Validate profile data structure if provided
1079
797
  if (data.profileData) {
1080
798
  await validatePractitionerProfileData(data.profileData);
1081
- console.log("[AUTH] Profile data validation passed");
799
+ console.log('[AUTH] Profile data validation passed');
1082
800
  }
1083
801
 
1084
- console.log("[AUTH] All pre-validation checks passed");
802
+ console.log('[AUTH] All pre-validation checks passed');
1085
803
  } catch (error) {
1086
- console.error("[AUTH] Pre-validation failed:", error);
804
+ console.error('[AUTH] Pre-validation failed:', error);
1087
805
  throw error;
1088
806
  }
1089
807
  }
@@ -1098,59 +816,46 @@ export class AuthService extends BaseService {
1098
816
  */
1099
817
  async signInPractitioner(
1100
818
  email: string,
1101
- password: string
819
+ password: string,
1102
820
  ): Promise<{
1103
821
  user: User;
1104
822
  practitioner: Practitioner;
1105
823
  }> {
1106
824
  try {
1107
- console.log("[AUTH] Starting practitioner signin process", {
825
+ console.log('[AUTH] Starting practitioner signin process', {
1108
826
  email: email,
1109
827
  });
1110
828
 
1111
829
  // Initialize required service
1112
- const practitionerService = new PractitionerService(
1113
- this.db,
1114
- this.auth,
1115
- this.app
1116
- );
830
+ const practitionerService = new PractitionerService(this.db, this.auth, this.app);
1117
831
 
1118
832
  // Sign in with email/password
1119
- const { user: firebaseUser } = await signInWithEmailAndPassword(
1120
- this.auth,
1121
- email,
1122
- password
1123
- );
833
+ const { user: firebaseUser } = await signInWithEmailAndPassword(this.auth, email, password);
1124
834
 
1125
835
  // Get or create user
1126
836
  const user = await this.userService.getOrCreateUser(firebaseUser);
1127
- console.log("[AUTH] User retrieved", { uid: user.uid });
837
+ console.log('[AUTH] User retrieved', { uid: user.uid });
1128
838
 
1129
839
  // Check if user has practitioner role
1130
840
  if (!user.roles?.includes(UserRole.PRACTITIONER)) {
1131
- console.error("[AUTH] User is not a practitioner:", user.uid);
841
+ console.error('[AUTH] User is not a practitioner:', user.uid);
1132
842
  throw AUTH_ERRORS.INVALID_ROLE;
1133
843
  }
1134
844
 
1135
845
  // Check and get practitioner profile
1136
846
  if (!user.practitionerProfile) {
1137
- console.error("[AUTH] User has no practitioner profile:", user.uid);
847
+ console.error('[AUTH] User has no practitioner profile:', user.uid);
1138
848
  throw AUTH_ERRORS.NOT_FOUND;
1139
849
  }
1140
850
 
1141
851
  // Get practitioner profile
1142
- const practitioner = await practitionerService.getPractitioner(
1143
- user.practitionerProfile
1144
- );
852
+ const practitioner = await practitionerService.getPractitioner(user.practitionerProfile);
1145
853
  if (!practitioner) {
1146
- console.error(
1147
- "[AUTH] Practitioner profile not found:",
1148
- user.practitionerProfile
1149
- );
854
+ console.error('[AUTH] Practitioner profile not found:', user.practitionerProfile);
1150
855
  throw AUTH_ERRORS.NOT_FOUND;
1151
856
  }
1152
857
 
1153
- console.log("[AUTH] Practitioner signin completed successfully", {
858
+ console.log('[AUTH] Practitioner signin completed successfully', {
1154
859
  userId: user.uid,
1155
860
  practitionerId: practitioner.id,
1156
861
  });
@@ -1160,8 +865,80 @@ export class AuthService extends BaseService {
1160
865
  practitioner,
1161
866
  };
1162
867
  } catch (error) {
1163
- console.error("[AUTH] Error in signInPractitioner:", error);
868
+ console.error('[AUTH] Error in signInPractitioner:', error);
1164
869
  throw error;
1165
870
  }
1166
871
  }
872
+
873
+ /**
874
+ * Signs in a user with a Google ID token from a mobile client.
875
+ * If the user does not exist, a new user is created.
876
+ * @param idToken - The Google ID token obtained from the mobile app.
877
+ * @param initialRole - The role to assign to the user if they are being created.
878
+ * @returns The signed-in or newly created user.
879
+ */
880
+ async signInWithGoogleIdToken(
881
+ idToken: string,
882
+ initialRole: UserRole = UserRole.PATIENT,
883
+ ): Promise<User> {
884
+ try {
885
+ console.log('[AUTH] Signing in with Google ID Token');
886
+ const credential = GoogleAuthProvider.credential(idToken);
887
+ const { user: firebaseUser } = await signInWithCredential(this.auth, credential);
888
+ console.log('[AUTH] Firebase user signed in:', firebaseUser.uid);
889
+
890
+ // Check if the user already has a profile in our database
891
+ const existingUser = await this.userService.getUserById(firebaseUser.uid);
892
+ if (existingUser) {
893
+ console.log('[AUTH] Existing user found, returning profile:', existingUser.uid);
894
+ return existingUser;
895
+ }
896
+
897
+ // If no profile exists, create a new one
898
+ console.log('[AUTH] No existing user found, creating new profile for:', firebaseUser.uid);
899
+ return this.userService.createUser(firebaseUser, [initialRole]);
900
+ } catch (error) {
901
+ console.error('[AUTH] Error in signInWithGoogleIdToken:', error);
902
+ throw handleFirebaseError(error);
903
+ }
904
+ }
905
+
906
+ /**
907
+ * Links a Google account to the currently signed-in user using an ID token.
908
+ * This is used to upgrade an anonymous user or to allow an existing user
909
+ * to sign in with Google in the future.
910
+ * @param idToken - The Google ID token obtained from the mobile app.
911
+ * @returns The updated user profile.
912
+ */
913
+ async linkGoogleAccount(idToken: string): Promise<User> {
914
+ try {
915
+ console.log('[AUTH] Linking Google account with ID Token');
916
+ const currentUser = this.auth.currentUser;
917
+ if (!currentUser) {
918
+ throw AUTH_ERRORS.NOT_AUTHENTICATED;
919
+ }
920
+
921
+ const wasAnonymous = currentUser.isAnonymous;
922
+ console.log(`[AUTH] Current user is ${wasAnonymous ? 'anonymous' : 'not anonymous'}`);
923
+
924
+ const credential = GoogleAuthProvider.credential(idToken);
925
+ const userCredential = await linkWithCredential(currentUser, credential);
926
+ const linkedFirebaseUser = userCredential.user;
927
+ console.log('[AUTH] Google account linked successfully to user:', linkedFirebaseUser.uid);
928
+
929
+ if (wasAnonymous) {
930
+ console.log('[AUTH] Upgrading anonymous user profile');
931
+ return await this.userService.upgradeAnonymousUser(
932
+ linkedFirebaseUser.uid,
933
+ linkedFirebaseUser.email!,
934
+ );
935
+ }
936
+
937
+ // If the user was not anonymous, just return their updated profile
938
+ return (await this.userService.getUserById(linkedFirebaseUser.uid))!;
939
+ } catch (error) {
940
+ console.error('[AUTH] Error in linkGoogleAccount:', error);
941
+ throw handleFirebaseError(error);
942
+ }
943
+ }
1167
944
  }