@blackcode_sa/metaestetics-api 1.6.17 → 1.6.19

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.
@@ -0,0 +1,404 @@
1
+ import * as admin from "firebase-admin";
2
+ import { User, UserRole, USERS_COLLECTION } from "../../types";
3
+ import {
4
+ PatientProfile,
5
+ PATIENTS_COLLECTION,
6
+ CreatePatientProfileData,
7
+ PatientSensitiveInfo,
8
+ PATIENT_SENSITIVE_INFO_COLLECTION,
9
+ CreatePatientSensitiveInfoData,
10
+ Gender,
11
+ PatientMedicalInfo,
12
+ PATIENT_MEDICAL_INFO_COLLECTION,
13
+ } from "../../types/patient";
14
+
15
+ /**
16
+ * @class UserProfileAdminService
17
+ * @description Handles user profile management operations for admin tasks
18
+ */
19
+ export class UserProfileAdminService {
20
+ private db: admin.firestore.Firestore;
21
+
22
+ /**
23
+ * Constructor for UserProfileAdminService
24
+ * @param firestore Optional Firestore instance. If not provided, uses the default admin SDK instance.
25
+ */
26
+ constructor(firestore?: admin.firestore.Firestore) {
27
+ this.db = firestore || admin.firestore();
28
+ }
29
+
30
+ /**
31
+ * Creates a blank user profile with minimal information
32
+ * @param authUserData Basic user data from Firebase Auth
33
+ * @returns The created user document
34
+ */
35
+ async createBlankUserProfile(authUserData: {
36
+ uid: string;
37
+ email: string | null;
38
+ isAnonymous: boolean;
39
+ }): Promise<User> {
40
+ console.log(
41
+ `[UserProfileAdminService] Creating blank user profile for user ${authUserData.uid}`
42
+ );
43
+
44
+ const userData: User = {
45
+ uid: authUserData.uid,
46
+ email: authUserData.email,
47
+ roles: [], // Empty roles array as requested
48
+ isAnonymous: authUserData.isAnonymous,
49
+ createdAt: admin.firestore.FieldValue.serverTimestamp(),
50
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
51
+ lastLoginAt: admin.firestore.FieldValue.serverTimestamp(),
52
+ };
53
+
54
+ try {
55
+ const userRef = this.db
56
+ .collection(USERS_COLLECTION)
57
+ .doc(authUserData.uid);
58
+ await userRef.set(userData);
59
+
60
+ // Fetch the created document to return with server timestamps
61
+ const userDoc = await userRef.get();
62
+ return userDoc.data() as User;
63
+ } catch (error) {
64
+ console.error(
65
+ `[UserProfileAdminService] Error creating blank user profile for ${authUserData.uid}:`,
66
+ error
67
+ );
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Initializes patient role for a user and creates all required patient documents
74
+ * Creates patient profile, sensitive info, and medical info in one operation
75
+ *
76
+ * @param userId The user ID to initialize with patient role
77
+ * @param options Optional data for different aspects of patient initialization
78
+ * @returns Object containing the updated user and all created patient documents
79
+ */
80
+ async initializePatientRole(
81
+ userId: string,
82
+ options?: {
83
+ profileData?: Partial<CreatePatientProfileData>;
84
+ sensitiveData?: Partial<CreatePatientSensitiveInfoData>;
85
+ }
86
+ ): Promise<{
87
+ user: User;
88
+ patientProfile: PatientProfile;
89
+ patientSensitiveInfo: PatientSensitiveInfo;
90
+ patientMedicalInfo: PatientMedicalInfo;
91
+ }> {
92
+ console.log(
93
+ `[UserProfileAdminService] Initializing complete patient role for user ${userId}`
94
+ );
95
+
96
+ try {
97
+ // Get user document
98
+ const userRef = this.db.collection(USERS_COLLECTION).doc(userId);
99
+ const userDoc = await userRef.get();
100
+
101
+ if (!userDoc.exists) {
102
+ throw new Error(`User ${userId} not found`);
103
+ }
104
+
105
+ const userData = userDoc.data() as User;
106
+ let patientProfileId: string | undefined = userData.patientProfile;
107
+
108
+ // Create patient profile if it doesn't exist
109
+ let patientProfile: PatientProfile;
110
+ if (!patientProfileId) {
111
+ // Create patient profile
112
+ const patientProfileRef = this.db.collection(PATIENTS_COLLECTION).doc();
113
+ patientProfileId = patientProfileRef.id;
114
+
115
+ // Set default profile data
116
+ const defaultProfileData: CreatePatientProfileData = {
117
+ userRef: userId,
118
+ displayName: "Patient", // Default display name
119
+ expoTokens: [],
120
+ gamification: {
121
+ level: 1,
122
+ points: 0,
123
+ },
124
+ isActive: true,
125
+ isVerified: false,
126
+ doctors: [],
127
+ clinics: [],
128
+ doctorIds: [],
129
+ clinicIds: [],
130
+ };
131
+
132
+ // Merge with any provided profile data
133
+ const mergedProfileData = {
134
+ ...defaultProfileData,
135
+ ...options?.profileData,
136
+ };
137
+
138
+ // Create the patient profile document with explicit properties
139
+ const patientProfileData = {
140
+ id: patientProfileId,
141
+ userRef: mergedProfileData.userRef,
142
+ displayName: mergedProfileData.displayName,
143
+ profilePhoto: null,
144
+ gamification: mergedProfileData.gamification || {
145
+ level: 1,
146
+ points: 0,
147
+ },
148
+ expoTokens: mergedProfileData.expoTokens || [],
149
+ isActive: mergedProfileData.isActive,
150
+ isVerified: mergedProfileData.isVerified,
151
+ doctors: mergedProfileData.doctors || [],
152
+ clinics: mergedProfileData.clinics || [],
153
+ doctorIds: mergedProfileData.doctorIds || [],
154
+ clinicIds: mergedProfileData.clinicIds || [],
155
+ createdAt: admin.firestore.FieldValue.serverTimestamp(),
156
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
157
+ };
158
+
159
+ // Store the document
160
+ await patientProfileRef.set(patientProfileData);
161
+
162
+ // For returning to the client, use type assertions
163
+ patientProfile = {
164
+ ...patientProfileData,
165
+ createdAt: null as any,
166
+ updatedAt: null as any,
167
+ } as unknown as PatientProfile;
168
+
169
+ console.log(
170
+ `[UserProfileAdminService] Creating patient profile with ID ${patientProfileId}`
171
+ );
172
+ } else {
173
+ // Get existing patient profile
174
+ const patientProfileRef = this.db
175
+ .collection(PATIENTS_COLLECTION)
176
+ .doc(patientProfileId);
177
+ const patientProfileDoc = await patientProfileRef.get();
178
+
179
+ if (!patientProfileDoc.exists) {
180
+ throw new Error(
181
+ `Patient profile ${patientProfileId} exists in user but not in patients collection`
182
+ );
183
+ }
184
+
185
+ patientProfile = patientProfileDoc.data() as PatientProfile;
186
+ }
187
+
188
+ // Create sensitive information document
189
+ const sensitiveInfoRef = this.db
190
+ .collection(PATIENTS_COLLECTION)
191
+ .doc(patientProfileId)
192
+ .collection(PATIENT_SENSITIVE_INFO_COLLECTION)
193
+ .doc("info"); // Use 'info' as standard document ID for subcollection
194
+
195
+ // Check if sensitive info document already exists
196
+ const sensitiveInfoDoc = await sensitiveInfoRef.get();
197
+ let patientSensitiveInfo: PatientSensitiveInfo;
198
+
199
+ if (!sensitiveInfoDoc.exists) {
200
+ // Create default sensitive info data
201
+ const defaultSensitiveData: CreatePatientSensitiveInfoData = {
202
+ patientId: patientProfileId,
203
+ userRef: userId,
204
+ firstName: "",
205
+ lastName: "",
206
+ dateOfBirth: null,
207
+ gender: Gender.PREFER_NOT_TO_SAY,
208
+ };
209
+
210
+ // Merge with provided data
211
+ const mergedSensitiveData = {
212
+ ...defaultSensitiveData,
213
+ ...options?.sensitiveData,
214
+ };
215
+
216
+ // Create sensitive info
217
+ const sensitiveInfoData = {
218
+ ...mergedSensitiveData,
219
+ createdAt: admin.firestore.FieldValue.serverTimestamp(),
220
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
221
+ // Leave dateOfBirth as is
222
+ };
223
+
224
+ // Store the document
225
+ await sensitiveInfoRef.set(sensitiveInfoData);
226
+
227
+ // Convert for client return with type assertions
228
+ patientSensitiveInfo = {
229
+ ...sensitiveInfoData,
230
+ createdAt: null as any,
231
+ updatedAt: null as any,
232
+ } as unknown as PatientSensitiveInfo;
233
+
234
+ console.log(
235
+ `[UserProfileAdminService] Creating sensitive info in subcollection for patient ${patientProfileId}`
236
+ );
237
+ } else {
238
+ patientSensitiveInfo = sensitiveInfoDoc.data() as PatientSensitiveInfo;
239
+ }
240
+
241
+ // Create medical info document as a subcollection
242
+ const medicalInfoRef = this.db
243
+ .collection(PATIENTS_COLLECTION)
244
+ .doc(patientProfileId)
245
+ .collection(PATIENT_MEDICAL_INFO_COLLECTION)
246
+ .doc("info"); // Use 'info' as standard document ID for subcollection
247
+
248
+ // Check if medical info document already exists
249
+ const medicalInfoDoc = await medicalInfoRef.get();
250
+ let patientMedicalInfo: PatientMedicalInfo;
251
+
252
+ if (!medicalInfoDoc.exists) {
253
+ // Create medical info
254
+ const medicalInfoData = {
255
+ patientId: patientProfileId,
256
+ vitalStats: {},
257
+ blockingConditions: [],
258
+ contraindications: [],
259
+ allergies: [],
260
+ currentMedications: [],
261
+ lastUpdated: admin.firestore.FieldValue.serverTimestamp(),
262
+ updatedBy: userId,
263
+ };
264
+
265
+ // Store the document
266
+ await medicalInfoRef.set(medicalInfoData);
267
+
268
+ // Convert for client return with type assertions
269
+ patientMedicalInfo = {
270
+ ...medicalInfoData,
271
+ lastUpdated: null as any,
272
+ } as unknown as PatientMedicalInfo;
273
+
274
+ console.log(
275
+ `[UserProfileAdminService] Creating medical info in subcollection for patient ${patientProfileId}`
276
+ );
277
+ } else {
278
+ patientMedicalInfo = medicalInfoDoc.data() as PatientMedicalInfo;
279
+ }
280
+
281
+ // Update user document with patient role and profile reference
282
+ const batch = this.db.batch();
283
+
284
+ // Add patient role if not already present
285
+ if (!userData.roles.includes(UserRole.PATIENT)) {
286
+ batch.update(userRef, {
287
+ roles: admin.firestore.FieldValue.arrayUnion(UserRole.PATIENT),
288
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
289
+ });
290
+ }
291
+
292
+ // Add patient profile reference if not already set
293
+ if (!userData.patientProfile) {
294
+ batch.update(userRef, {
295
+ patientProfile: patientProfileId,
296
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
297
+ });
298
+ }
299
+
300
+ await batch.commit();
301
+
302
+ // Get updated user document
303
+ const updatedUserDoc = await userRef.get();
304
+ const updatedUser = updatedUserDoc.data() as User;
305
+
306
+ console.log(
307
+ `[UserProfileAdminService] Successfully initialized patient role for user ${userId}`
308
+ );
309
+
310
+ return {
311
+ user: updatedUser,
312
+ patientProfile,
313
+ patientSensitiveInfo,
314
+ patientMedicalInfo,
315
+ };
316
+ } catch (error) {
317
+ console.error(
318
+ `[UserProfileAdminService] Error initializing patient role for user ${userId}:`,
319
+ error
320
+ );
321
+ throw error;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Initializes clinic admin role for a user
327
+ * @param userId The user ID to initialize with clinic admin role
328
+ * @returns The updated user document
329
+ */
330
+ async initializeClinicAdminRole(userId: string): Promise<User> {
331
+ console.log(
332
+ `[UserProfileAdminService] Initializing clinic admin role for user ${userId}`
333
+ );
334
+
335
+ try {
336
+ const userRef = this.db.collection(USERS_COLLECTION).doc(userId);
337
+ const userDoc = await userRef.get();
338
+
339
+ if (!userDoc.exists) {
340
+ throw new Error(`User ${userId} not found`);
341
+ }
342
+
343
+ const userData = userDoc.data() as User;
344
+
345
+ // Only add the role if it doesn't already exist
346
+ if (!userData.roles.includes(UserRole.CLINIC_ADMIN)) {
347
+ await userRef.update({
348
+ roles: admin.firestore.FieldValue.arrayUnion(UserRole.CLINIC_ADMIN),
349
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
350
+ });
351
+ }
352
+
353
+ // Return the updated user document
354
+ const updatedUserDoc = await userRef.get();
355
+ return updatedUserDoc.data() as User;
356
+ } catch (error) {
357
+ console.error(
358
+ `[UserProfileAdminService] Error initializing clinic admin role for user ${userId}:`,
359
+ error
360
+ );
361
+ throw error;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Initializes practitioner role for a user
367
+ * @param userId The user ID to initialize with practitioner role
368
+ * @returns The updated user document
369
+ */
370
+ async initializePractitionerRole(userId: string): Promise<User> {
371
+ console.log(
372
+ `[UserProfileAdminService] Initializing practitioner role for user ${userId}`
373
+ );
374
+
375
+ try {
376
+ const userRef = this.db.collection(USERS_COLLECTION).doc(userId);
377
+ const userDoc = await userRef.get();
378
+
379
+ if (!userDoc.exists) {
380
+ throw new Error(`User ${userId} not found`);
381
+ }
382
+
383
+ const userData = userDoc.data() as User;
384
+
385
+ // Only add the role if it doesn't already exist
386
+ if (!userData.roles.includes(UserRole.PRACTITIONER)) {
387
+ await userRef.update({
388
+ roles: admin.firestore.FieldValue.arrayUnion(UserRole.PRACTITIONER),
389
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
390
+ });
391
+ }
392
+
393
+ // Return the updated user document
394
+ const updatedUserDoc = await userRef.get();
395
+ return updatedUserDoc.data() as User;
396
+ } catch (error) {
397
+ console.error(
398
+ `[UserProfileAdminService] Error initializing practitioner role for user ${userId}:`,
399
+ error
400
+ );
401
+ throw error;
402
+ }
403
+ }
404
+ }
@@ -3,12 +3,16 @@ import { Firestore, getFirestore } from "firebase/firestore";
3
3
  import { Auth, getAuth } from "firebase/auth";
4
4
  import { Analytics, getAnalytics } from "firebase/analytics";
5
5
  import { Platform } from "react-native";
6
+ import { FirebaseStorage, getStorage } from "firebase/storage";
7
+ import { Functions, getFunctions } from "firebase/functions";
6
8
 
7
9
  interface FirebaseInstance {
8
10
  app: FirebaseApp;
9
11
  db: Firestore;
10
12
  auth: Auth;
11
13
  analytics: Analytics | null;
14
+ storage: FirebaseStorage;
15
+ functions: Functions;
12
16
  }
13
17
 
14
18
  let firebaseInstance: FirebaseInstance | null = null;
@@ -26,13 +30,15 @@ export const initializeFirebase = (config: {
26
30
  const app = initializeApp(config);
27
31
  const db = getFirestore(app);
28
32
  const auth = getAuth(app);
33
+ const storage = getStorage(app);
34
+ const functions = getFunctions(app);
29
35
 
30
36
  let analytics = null;
31
37
  if (typeof window !== "undefined" && Platform.OS === "web") {
32
38
  analytics = getAnalytics(app);
33
39
  }
34
40
 
35
- firebaseInstance = { app, db, auth, analytics };
41
+ firebaseInstance = { app, db, auth, analytics, storage, functions };
36
42
  }
37
43
  return firebaseInstance;
38
44
  };
@@ -60,3 +66,13 @@ export const getFirebaseApp = async (): Promise<FirebaseApp> => {
60
66
  const instance = await getFirebaseInstance();
61
67
  return instance.app;
62
68
  };
69
+
70
+ export const getFirebaseStorage = async (): Promise<FirebaseStorage> => {
71
+ const instance = await getFirebaseInstance();
72
+ return instance.storage;
73
+ };
74
+
75
+ export const getFirebaseFunctions = async (): Promise<Functions> => {
76
+ const instance = await getFirebaseInstance();
77
+ return instance.functions;
78
+ };