@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.
- package/dist/admin/index.js +30 -9
- package/dist/admin/index.mjs +30 -9
- package/dist/index.d.mts +28 -32
- package/dist/index.d.ts +28 -32
- package/dist/index.js +246 -582
- package/dist/index.mjs +260 -597
- package/package.json +1 -1
- package/src/admin/aggregation/forms/README.md +13 -0
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +71 -51
- package/src/services/appointment/README.md +17 -0
- package/src/services/appointment/appointment.service.ts +233 -386
- package/src/services/auth/auth.service.ts +243 -466
|
@@ -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
|
-
|
|
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
|
|
40
|
-
import { FirebaseApp } from
|
|
41
|
-
import { User, UserRole, USERS_COLLECTION } from
|
|
42
|
-
import { z } from
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} from
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
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
|
|
66
|
-
import { clinicAdminSignupSchema } from
|
|
67
|
-
import { ClinicGroupService } from
|
|
68
|
-
import { ClinicAdminService } from
|
|
69
|
-
import { ClinicService } from
|
|
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
|
|
77
|
-
import { PractitionerService } from
|
|
78
|
-
import { practitionerSignupSchema } from
|
|
79
|
-
import { CertificationLevel } from
|
|
80
|
-
import { MediaService } from
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
143
|
+
console.log('[AUTH] Firebase user created successfully', {
|
|
167
144
|
uid: firebaseUser.uid,
|
|
168
145
|
});
|
|
169
146
|
} catch (firebaseError) {
|
|
170
|
-
console.error(
|
|
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(
|
|
152
|
+
console.log('[AUTH] Creating user with CLINIC_ADMIN role');
|
|
176
153
|
let user;
|
|
177
154
|
try {
|
|
178
|
-
user = await this.userService.createUser(
|
|
179
|
-
|
|
180
|
-
|
|
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(
|
|
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(
|
|
174
|
+
console.log('[AUTH] Contact person object created');
|
|
202
175
|
|
|
203
176
|
// Initialize services
|
|
204
|
-
console.log(
|
|
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(
|
|
203
|
+
console.log('[AUTH] Creating new clinic group flow');
|
|
237
204
|
// Create new clinic group
|
|
238
205
|
if (!data.clinicGroupData) {
|
|
239
|
-
console.error(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
407
|
-
throw new Error(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
384
|
+
console.log('[AUTH] Token marked as used successfully');
|
|
447
385
|
} catch (tokenUseError) {
|
|
448
|
-
console.error(
|
|
386
|
+
console.error('[AUTH] Failed to mark token as used:', tokenUseError);
|
|
449
387
|
throw tokenUseError;
|
|
450
388
|
}
|
|
451
389
|
}
|
|
452
390
|
|
|
453
|
-
console.log(
|
|
391
|
+
console.log('[AUTH] Clinic admin signup completed successfully', {
|
|
454
392
|
userId: user.uid,
|
|
455
393
|
clinicGroupId: clinicGroup.id,
|
|
456
|
-
clinicAdminId: adminProfile?.id ||
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
689
|
+
console.log('[AUTH] Firebase user created successfully', {
|
|
944
690
|
uid: firebaseUser.uid,
|
|
945
691
|
});
|
|
946
692
|
} catch (firebaseError) {
|
|
947
|
-
console.error(
|
|
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(
|
|
953
|
-
const transactionResult = await runTransaction(
|
|
954
|
-
|
|
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
|
-
|
|
961
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
736
|
+
console.log('[AUTH] Transaction operations completed successfully');
|
|
737
|
+
return { user, practitioner };
|
|
738
|
+
});
|
|
1012
739
|
|
|
1013
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
780
|
+
console.log('[AUTH] Email already exists:', data.email);
|
|
1057
781
|
throw AUTH_ERRORS.EMAIL_ALREADY_EXISTS;
|
|
1058
782
|
}
|
|
1059
|
-
console.log(
|
|
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
|
-
|
|
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(
|
|
1073
|
-
throw new Error(
|
|
790
|
+
console.log('[AUTH] Invalid token provided:', data.token);
|
|
791
|
+
throw new Error('Invalid or expired invitation token');
|
|
1074
792
|
}
|
|
1075
|
-
console.log(
|
|
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(
|
|
799
|
+
console.log('[AUTH] Profile data validation passed');
|
|
1082
800
|
}
|
|
1083
801
|
|
|
1084
|
-
console.log(
|
|
802
|
+
console.log('[AUTH] All pre-validation checks passed');
|
|
1085
803
|
} catch (error) {
|
|
1086
|
-
console.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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|