@blackcode_sa/metaestetics-api 1.7.20 → 1.7.21

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.
@@ -16,9 +16,8 @@ import {
16
16
  limit,
17
17
  startAfter,
18
18
  doc,
19
- } from 'firebase/firestore';
20
- import { ref, uploadBytes, getDownloadURL, deleteObject, FirebaseStorage } from 'firebase/storage';
21
- import { z } from 'zod';
19
+ } from "firebase/firestore";
20
+ import { z } from "zod";
22
21
  import {
23
22
  PatientProfile,
24
23
  CreatePatientProfileData,
@@ -26,43 +25,42 @@ import {
26
25
  SearchPatientsParams,
27
26
  RequesterInfo,
28
27
  PATIENTS_COLLECTION,
29
- } from '../../../types/patient';
28
+ } from "../../../types/patient";
30
29
  import {
31
30
  patientProfileSchema,
32
31
  createPatientProfileSchema,
33
32
  searchPatientsSchema,
34
33
  requesterInfoSchema,
35
- } from '../../../validations/patient.schema';
34
+ } from "../../../validations/patient.schema";
36
35
  import {
37
36
  getPatientDocRef,
38
37
  getPatientDocRefByUserRef,
39
38
  initSensitiveInfoDocIfNotExists,
40
39
  getSensitiveInfoDocRef,
41
40
  getMedicalInfoDocRef,
42
- } from './docs.utils';
43
- import { ensureMedicalInfoExists } from './medical.utils';
44
- import { DEFAULT_MEDICAL_INFO } from '../../../types/patient/medical-info.types';
41
+ } from "./docs.utils";
42
+ import { ensureMedicalInfoExists } from "./medical.utils";
43
+ import { DEFAULT_MEDICAL_INFO } from "../../../types/patient/medical-info.types";
45
44
 
46
45
  // Funkcije za rad sa profilom
47
46
  export const createPatientProfileUtil = async (
48
47
  db: Firestore,
49
48
  data: CreatePatientProfileData,
50
- generateId: () => string,
49
+ generateId: () => string
51
50
  ): Promise<PatientProfile> => {
52
51
  try {
53
- console.log('[createPatientProfileUtil] Starting patient profile creation');
52
+ console.log("[createPatientProfileUtil] Starting patient profile creation");
54
53
  const validatedData = createPatientProfileSchema.parse(data);
55
54
  const patientId = generateId();
56
55
  console.log(`[createPatientProfileUtil] Generated patientId: ${patientId}`);
57
56
 
58
- const patientData: Omit<PatientProfile, 'createdAt' | 'updatedAt'> & {
57
+ const patientData: Omit<PatientProfile, "createdAt" | "updatedAt"> & {
59
58
  createdAt: ReturnType<typeof serverTimestamp>;
60
59
  updatedAt: ReturnType<typeof serverTimestamp>;
61
60
  } = {
62
61
  id: patientId,
63
62
  userRef: validatedData.userRef,
64
63
  displayName: validatedData.displayName,
65
- profilePhoto: validatedData.profilePhoto || null,
66
64
  expoTokens: validatedData.expoTokens,
67
65
  gamification: validatedData.gamification || {
68
66
  level: 1,
@@ -93,16 +91,21 @@ export const createPatientProfileUtil = async (
93
91
  const sensitiveInfoCreated = await initSensitiveInfoDocIfNotExists(
94
92
  db,
95
93
  patientId,
96
- validatedData.userRef,
94
+ validatedData.userRef
97
95
  );
98
96
  console.log(
99
97
  `[createPatientProfileUtil] Sensitive info document creation result: ${
100
- sensitiveInfoCreated ? 'Document already existed' : 'New document created'
101
- }`,
98
+ sensitiveInfoCreated
99
+ ? "Document already existed"
100
+ : "New document created"
101
+ }`
102
102
  );
103
103
  sensitiveInfoSuccess = true;
104
104
  } catch (sensitiveError) {
105
- console.error(`[createPatientProfileUtil] Error creating sensitive info:`, sensitiveError);
105
+ console.error(
106
+ `[createPatientProfileUtil] Error creating sensitive info:`,
107
+ sensitiveError
108
+ );
106
109
  // Don't throw the error, we'll try the direct method
107
110
  }
108
111
 
@@ -111,21 +114,33 @@ export const createPatientProfileUtil = async (
111
114
  let medicalInfoSuccess = false;
112
115
  try {
113
116
  await ensureMedicalInfoExists(db, patientId, validatedData.userRef);
114
- console.log(`[createPatientProfileUtil] Medical info document created successfully`);
117
+ console.log(
118
+ `[createPatientProfileUtil] Medical info document created successfully`
119
+ );
115
120
  medicalInfoSuccess = true;
116
121
  } catch (medicalError) {
117
- console.error(`[createPatientProfileUtil] Error creating medical info:`, medicalError);
122
+ console.error(
123
+ `[createPatientProfileUtil] Error creating medical info:`,
124
+ medicalError
125
+ );
118
126
  // Don't throw the error, we'll try the direct method
119
127
  }
120
128
 
121
129
  // If either utility function failed, try the direct method
122
130
  if (!sensitiveInfoSuccess || !medicalInfoSuccess) {
123
- console.log(`[createPatientProfileUtil] Using fallback method to create documents`);
131
+ console.log(
132
+ `[createPatientProfileUtil] Using fallback method to create documents`
133
+ );
124
134
  try {
125
135
  await testCreateSubDocuments(db, patientId, validatedData.userRef);
126
- console.log(`[createPatientProfileUtil] Fallback method completed successfully`);
136
+ console.log(
137
+ `[createPatientProfileUtil] Fallback method completed successfully`
138
+ );
127
139
  } catch (fallbackError) {
128
- console.error(`[createPatientProfileUtil] Fallback method failed:`, fallbackError);
140
+ console.error(
141
+ `[createPatientProfileUtil] Fallback method failed:`,
142
+ fallbackError
143
+ );
129
144
  // Still continue to return the patient profile
130
145
  }
131
146
  }
@@ -133,16 +148,23 @@ export const createPatientProfileUtil = async (
133
148
  console.log(`[createPatientProfileUtil] Verifying patient document exists`);
134
149
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
135
150
  if (!patientDoc.exists()) {
136
- console.error(`[createPatientProfileUtil] Patient document not found after creation`);
137
- throw new Error('Failed to create patient profile');
151
+ console.error(
152
+ `[createPatientProfileUtil] Patient document not found after creation`
153
+ );
154
+ throw new Error("Failed to create patient profile");
138
155
  }
139
156
 
140
- console.log(`[createPatientProfileUtil] Patient profile creation completed successfully`);
157
+ console.log(
158
+ `[createPatientProfileUtil] Patient profile creation completed successfully`
159
+ );
141
160
  return patientDoc.data() as PatientProfile;
142
161
  } catch (error) {
143
- console.error(`[createPatientProfileUtil] Error in patient profile creation:`, error);
162
+ console.error(
163
+ `[createPatientProfileUtil] Error in patient profile creation:`,
164
+ error
165
+ );
144
166
  if (error instanceof z.ZodError) {
145
- throw new Error('Invalid patient data: ' + error.message);
167
+ throw new Error("Invalid patient data: " + error.message);
146
168
  }
147
169
  throw error;
148
170
  }
@@ -150,7 +172,7 @@ export const createPatientProfileUtil = async (
150
172
 
151
173
  export const getPatientProfileUtil = async (
152
174
  db: Firestore,
153
- patientId: string,
175
+ patientId: string
154
176
  ): Promise<PatientProfile | null> => {
155
177
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
156
178
  return patientDoc.exists() ? (patientDoc.data() as PatientProfile) : null;
@@ -158,7 +180,7 @@ export const getPatientProfileUtil = async (
158
180
 
159
181
  export const getPatientProfileByUserRefUtil = async (
160
182
  db: Firestore,
161
- userRef: string,
183
+ userRef: string
162
184
  ): Promise<PatientProfile | null> => {
163
185
  try {
164
186
  const docRef = await getPatientDocRefByUserRef(db, userRef);
@@ -173,7 +195,7 @@ export const getPatientProfileByUserRefUtil = async (
173
195
  export const addExpoTokenUtil = async (
174
196
  db: Firestore,
175
197
  patientId: string,
176
- token: string,
198
+ token: string
177
199
  ): Promise<void> => {
178
200
  await updateDoc(getPatientDocRef(db, patientId), {
179
201
  expoTokens: arrayUnion(token),
@@ -184,7 +206,7 @@ export const addExpoTokenUtil = async (
184
206
  export const removeExpoTokenUtil = async (
185
207
  db: Firestore,
186
208
  patientId: string,
187
- token: string,
209
+ token: string
188
210
  ): Promise<void> => {
189
211
  await updateDoc(getPatientDocRef(db, patientId), {
190
212
  expoTokens: arrayRemove(token),
@@ -195,10 +217,10 @@ export const removeExpoTokenUtil = async (
195
217
  export const addPointsUtil = async (
196
218
  db: Firestore,
197
219
  patientId: string,
198
- points: number,
220
+ points: number
199
221
  ): Promise<void> => {
200
222
  await updateDoc(getPatientDocRef(db, patientId), {
201
- 'gamification.points': increment(points),
223
+ "gamification.points": increment(points),
202
224
  updatedAt: serverTimestamp(),
203
225
  });
204
226
  };
@@ -206,7 +228,7 @@ export const addPointsUtil = async (
206
228
  export const updatePatientProfileUtil = async (
207
229
  db: Firestore,
208
230
  patientId: string,
209
- data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
231
+ data: Partial<Omit<PatientProfile, "id" | "createdAt" | "updatedAt">>
210
232
  ): Promise<PatientProfile> => {
211
233
  try {
212
234
  const updateData = {
@@ -218,7 +240,7 @@ export const updatePatientProfileUtil = async (
218
240
 
219
241
  const updatedDoc = await getDoc(getPatientDocRef(db, patientId));
220
242
  if (!updatedDoc.exists()) {
221
- throw new Error('Patient profile not found after update');
243
+ throw new Error("Patient profile not found after update");
222
244
  }
223
245
 
224
246
  return updatedDoc.data() as PatientProfile;
@@ -231,89 +253,24 @@ export const updatePatientProfileUtil = async (
231
253
  export const updatePatientProfileByUserRefUtil = async (
232
254
  db: Firestore,
233
255
  userRef: string,
234
- data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
256
+ data: Partial<Omit<PatientProfile, "id" | "createdAt" | "updatedAt">>
235
257
  ): Promise<PatientProfile> => {
236
258
  try {
237
259
  const docRef = await getPatientDocRefByUserRef(db, userRef);
238
260
  const patientDoc = await getDoc(docRef);
239
261
 
240
262
  if (!patientDoc.exists()) {
241
- throw new Error('Patient profile not found');
263
+ throw new Error("Patient profile not found");
242
264
  }
243
265
 
244
266
  const patientData = patientDoc.data() as PatientProfile;
245
267
  return updatePatientProfileUtil(db, patientData.id, data);
246
268
  } catch (error: unknown) {
247
269
  const errorMessage = error instanceof Error ? error.message : String(error);
248
- throw new Error(`Failed to update patient profile by user ref: ${errorMessage}`);
249
- }
250
- };
251
-
252
- // Pomoćne funkcije za profilnu sliku
253
- export const uploadProfilePhotoUtil = async (
254
- storage: FirebaseStorage,
255
- patientId: string,
256
- file: File,
257
- ): Promise<string> => {
258
- const photoRef = ref(storage, `patient-photos/${patientId}/profile-photo`);
259
- await uploadBytes(photoRef, file);
260
- return getDownloadURL(photoRef);
261
- };
262
-
263
- export const updateProfilePhotoUtil = async (
264
- storage: FirebaseStorage,
265
- db: Firestore,
266
- patientId: string,
267
- file: File,
268
- ): Promise<string> => {
269
- // Prvo obrišemo staru sliku ako postoji
270
- const patientDoc = await getDoc(getPatientDocRef(db, patientId));
271
- if (!patientDoc.exists()) throw new Error('Patient profile not found');
272
-
273
- const patientData = patientDoc.data() as PatientProfile;
274
- if (patientData.profilePhoto) {
275
- try {
276
- const oldPhotoRef = ref(storage, patientData.profilePhoto);
277
- await deleteObject(oldPhotoRef);
278
- } catch (error) {
279
- console.warn('Failed to delete old profile photo:', error);
280
- }
281
- }
282
-
283
- // Zatim uploadujemo novu sliku
284
- const newPhotoUrl = await uploadProfilePhotoUtil(storage, patientId, file);
285
-
286
- // Ažuriramo profil sa novim URL-om
287
- await updateDoc(getPatientDocRef(db, patientId), {
288
- profilePhoto: newPhotoUrl,
289
- updatedAt: serverTimestamp(),
290
- });
291
-
292
- return newPhotoUrl;
293
- };
294
-
295
- export const deleteProfilePhotoUtil = async (
296
- storage: FirebaseStorage,
297
- db: Firestore,
298
- patientId: string,
299
- ): Promise<void> => {
300
- const patientDoc = await getDoc(getPatientDocRef(db, patientId));
301
- if (!patientDoc.exists()) throw new Error('Patient profile not found');
302
-
303
- const patientData = patientDoc.data() as PatientProfile;
304
- if (patientData.profilePhoto) {
305
- try {
306
- const photoRef = ref(storage, patientData.profilePhoto);
307
- await deleteObject(photoRef);
308
- } catch (error) {
309
- console.warn('Failed to delete profile photo:', error);
310
- }
270
+ throw new Error(
271
+ `Failed to update patient profile by user ref: ${errorMessage}`
272
+ );
311
273
  }
312
-
313
- await updateDoc(getPatientDocRef(db, patientId), {
314
- profilePhoto: null,
315
- updatedAt: serverTimestamp(),
316
- });
317
274
  };
318
275
 
319
276
  /**
@@ -323,39 +280,45 @@ export const deleteProfilePhotoUtil = async (
323
280
  export const testCreateSubDocuments = async (
324
281
  db: Firestore,
325
282
  patientId: string,
326
- userRef: string,
283
+ userRef: string
327
284
  ): Promise<void> => {
328
285
  console.log(
329
- `[testCreateSubDocuments] Starting test for patientId: ${patientId}, userRef: ${userRef}`,
286
+ `[testCreateSubDocuments] Starting test for patientId: ${patientId}, userRef: ${userRef}`
330
287
  );
331
288
 
332
289
  try {
333
290
  // Test sensitive info creation
334
291
  console.log(`[testCreateSubDocuments] Testing sensitive info creation`);
335
292
  const sensitiveInfoRef = getSensitiveInfoDocRef(db, patientId);
336
- console.log(`[testCreateSubDocuments] Sensitive info path: ${sensitiveInfoRef.path}`);
293
+ console.log(
294
+ `[testCreateSubDocuments] Sensitive info path: ${sensitiveInfoRef.path}`
295
+ );
337
296
 
338
297
  const defaultSensitiveInfo = {
339
298
  patientId,
340
299
  userRef,
341
- photoUrl: '',
342
- firstName: 'Name',
343
- lastName: 'Surname',
300
+ photoUrl: "",
301
+ firstName: "Name",
302
+ lastName: "Surname",
344
303
  dateOfBirth: Timestamp.now(),
345
304
  gender: Gender.PREFER_NOT_TO_SAY,
346
- email: 'test@example.com',
347
- phoneNumber: '',
305
+ email: "test@example.com",
306
+ phoneNumber: "",
348
307
  createdAt: Timestamp.now(),
349
308
  updatedAt: Timestamp.now(),
350
309
  };
351
310
 
352
311
  await setDoc(sensitiveInfoRef, defaultSensitiveInfo);
353
- console.log(`[testCreateSubDocuments] Sensitive info document created directly`);
312
+ console.log(
313
+ `[testCreateSubDocuments] Sensitive info document created directly`
314
+ );
354
315
 
355
316
  // Test medical info creation
356
317
  console.log(`[testCreateSubDocuments] Testing medical info creation`);
357
318
  const medicalInfoRef = getMedicalInfoDocRef(db, patientId);
358
- console.log(`[testCreateSubDocuments] Medical info path: ${medicalInfoRef.path}`);
319
+ console.log(
320
+ `[testCreateSubDocuments] Medical info path: ${medicalInfoRef.path}`
321
+ );
359
322
 
360
323
  const defaultMedicalInfo = {
361
324
  ...DEFAULT_MEDICAL_INFO,
@@ -365,7 +328,9 @@ export const testCreateSubDocuments = async (
365
328
  };
366
329
 
367
330
  await setDoc(medicalInfoRef, defaultMedicalInfo);
368
- console.log(`[testCreateSubDocuments] Medical info document created directly`);
331
+ console.log(
332
+ `[testCreateSubDocuments] Medical info document created directly`
333
+ );
369
334
 
370
335
  console.log(`[testCreateSubDocuments] Test completed successfully`);
371
336
  } catch (error) {
@@ -386,7 +351,7 @@ export const testCreateSubDocuments = async (
386
351
  export const searchPatientsUtil = async (
387
352
  db: Firestore,
388
353
  params: SearchPatientsParams,
389
- requester: RequesterInfo,
354
+ requester: RequesterInfo
390
355
  ): Promise<PatientProfile[]> => {
391
356
  // Validate input
392
357
  searchPatientsSchema.parse(params);
@@ -397,52 +362,65 @@ export const searchPatientsUtil = async (
397
362
 
398
363
  // --- Security Checks & Initial Filtering ---
399
364
 
400
- if (requester.role === 'clinic_admin') {
365
+ if (requester.role === "clinic_admin") {
401
366
  // Clinic admin can only search within their own clinic
402
367
  if (!requester.associatedClinicId) {
403
- throw new Error('Associated clinic ID is required for clinic admin search.');
368
+ throw new Error(
369
+ "Associated clinic ID is required for clinic admin search."
370
+ );
404
371
  }
405
372
  // If the search params specify a different clinic, it's an invalid request for this admin.
406
373
  if (params.clinicId && params.clinicId !== requester.associatedClinicId) {
407
374
  console.warn(
408
- `Clinic admin (${requester.id}) attempted to search outside their associated clinic (${requester.associatedClinicId})`,
375
+ `Clinic admin (${requester.id}) attempted to search outside their associated clinic (${requester.associatedClinicId})`
409
376
  );
410
377
  return []; // Or throw an error
411
378
  }
412
379
 
413
380
  // **Mandatory filter**: Ensure patients belong to the admin's clinic.
414
- constraints.push(where('clinicIds', 'array-contains', requester.associatedClinicId));
381
+ constraints.push(
382
+ where("clinicIds", "array-contains", requester.associatedClinicId)
383
+ );
415
384
 
416
385
  // Optional filter: If practitionerId is also provided, filter by that practitioner *within the admin's clinic*.
417
386
  if (params.practitionerId) {
418
- constraints.push(where('doctorIds', 'array-contains', params.practitionerId));
387
+ constraints.push(
388
+ where("doctorIds", "array-contains", params.practitionerId)
389
+ );
419
390
  // We might need an additional check here if the practitioner MUST work at the admin's clinic.
420
391
  // This would require fetching practitioner data or having denormalized clinic IDs on the practitioner.
421
392
  }
422
- } else if (requester.role === 'practitioner') {
393
+ } else if (requester.role === "practitioner") {
423
394
  // Practitioner can only search for their own patients.
424
395
  if (!requester.associatedPractitionerId) {
425
- throw new Error('Associated practitioner ID is required for practitioner search.');
396
+ throw new Error(
397
+ "Associated practitioner ID is required for practitioner search."
398
+ );
426
399
  }
427
400
  // If the search params specify a different practitioner, it's invalid.
428
- if (params.practitionerId && params.practitionerId !== requester.associatedPractitionerId) {
401
+ if (
402
+ params.practitionerId &&
403
+ params.practitionerId !== requester.associatedPractitionerId
404
+ ) {
429
405
  console.warn(
430
- `Practitioner (${requester.id}) attempted to search for patients of another practitioner (${params.practitionerId})`,
406
+ `Practitioner (${requester.id}) attempted to search for patients of another practitioner (${params.practitionerId})`
431
407
  );
432
408
  return []; // Or throw an error
433
409
  }
434
410
 
435
411
  // **Mandatory filter**: Ensure patients are associated with this practitioner.
436
- constraints.push(where('doctorIds', 'array-contains', requester.associatedPractitionerId));
412
+ constraints.push(
413
+ where("doctorIds", "array-contains", requester.associatedPractitionerId)
414
+ );
437
415
 
438
416
  // Optional filter: If clinicId is provided, filter by patients of this practitioner *at that specific clinic*.
439
417
  if (params.clinicId) {
440
- constraints.push(where('clinicIds', 'array-contains', params.clinicId));
418
+ constraints.push(where("clinicIds", "array-contains", params.clinicId));
441
419
  // Similar to above, we might need to check if the practitioner actually works at this clinic.
442
420
  }
443
421
  } else {
444
422
  // Should not happen due to validation, but good practice to handle.
445
- throw new Error('Invalid requester role.');
423
+ throw new Error("Invalid requester role.");
446
424
  }
447
425
 
448
426
  // --- Execute Query ---
@@ -450,12 +428,16 @@ export const searchPatientsUtil = async (
450
428
  const finalQuery = query(patientsCollectionRef, ...constraints);
451
429
  const querySnapshot = await getDocs(finalQuery);
452
430
 
453
- const patients = querySnapshot.docs.map((doc) => doc.data() as PatientProfile);
431
+ const patients = querySnapshot.docs.map(
432
+ (doc) => doc.data() as PatientProfile
433
+ );
454
434
 
455
- console.log(`[searchPatientsUtil] Found ${patients.length} patients matching criteria.`);
435
+ console.log(
436
+ `[searchPatientsUtil] Found ${patients.length} patients matching criteria.`
437
+ );
456
438
  return patients;
457
439
  } catch (error) {
458
- console.error('[searchPatientsUtil] Error searching patients:', error);
440
+ console.error("[searchPatientsUtil] Error searching patients:", error);
459
441
  // Consider logging more details or re-throwing a specific error type
460
442
  return []; // Return empty array on error
461
443
  }
@@ -472,10 +454,13 @@ export const searchPatientsUtil = async (
472
454
  */
473
455
  export const getAllPatientsUtil = async (
474
456
  db: Firestore,
475
- options?: { limit?: number; startAfter?: string },
457
+ options?: { limit?: number; startAfter?: string }
476
458
  ): Promise<PatientProfile[]> => {
477
459
  try {
478
- console.log(`[getAllPatientsUtil] Fetching patients with options:`, options);
460
+ console.log(
461
+ `[getAllPatientsUtil] Fetching patients with options:`,
462
+ options
463
+ );
479
464
 
480
465
  const patientsCollection = collection(db, PATIENTS_COLLECTION);
481
466
 
@@ -488,7 +473,9 @@ export const getAllPatientsUtil = async (
488
473
 
489
474
  // If startAfter is provided, get that document and use it for pagination
490
475
  if (options?.startAfter) {
491
- const startAfterDoc = await getDoc(doc(db, PATIENTS_COLLECTION, options.startAfter));
476
+ const startAfterDoc = await getDoc(
477
+ doc(db, PATIENTS_COLLECTION, options.startAfter)
478
+ );
492
479
  if (startAfterDoc.exists()) {
493
480
  q = query(q, startAfter(startAfterDoc));
494
481
  }
@@ -506,7 +493,9 @@ export const getAllPatientsUtil = async (
506
493
  } catch (error) {
507
494
  console.error(`[getAllPatientsUtil] Error fetching patients:`, error);
508
495
  throw new Error(
509
- `Failed to retrieve patients: ${error instanceof Error ? error.message : String(error)}`,
496
+ `Failed to retrieve patients: ${
497
+ error instanceof Error ? error.message : String(error)
498
+ }`
510
499
  );
511
500
  }
512
501
  };
@@ -16,12 +16,54 @@ import {
16
16
  getSensitiveInfoDocRef,
17
17
  initSensitiveInfoDocIfNotExists,
18
18
  } from "./docs.utils";
19
+ import {
20
+ MediaService,
21
+ MediaAccessLevel,
22
+ MediaResource,
23
+ } from "../../media/media.service";
24
+
25
+ /**
26
+ * Handles photoUrl upload for sensitive info (supports MediaResource)
27
+ * @param photoUrl - MediaResource (File, Blob, or URL string)
28
+ * @param patientId - ID of the patient
29
+ * @param mediaService - MediaService instance
30
+ * @returns URL string of the uploaded or existing photo
31
+ */
32
+ const handlePhotoUrlUpload = async (
33
+ photoUrl: MediaResource | undefined | null,
34
+ patientId: string,
35
+ mediaService: MediaService
36
+ ): Promise<string | null> => {
37
+ if (!photoUrl) {
38
+ return null;
39
+ }
40
+
41
+ // If it's already a URL string, return it as is
42
+ if (typeof photoUrl === "string") {
43
+ return photoUrl;
44
+ }
45
+
46
+ // If it's a File or Blob, upload it
47
+ if (photoUrl instanceof File || photoUrl instanceof Blob) {
48
+ const mediaMetadata = await mediaService.uploadMedia(
49
+ photoUrl,
50
+ patientId, // Using patientId as ownerId
51
+ MediaAccessLevel.PRIVATE, // Sensitive info should be private
52
+ "patient_sensitive_photos",
53
+ photoUrl instanceof File ? photoUrl.name : `sensitive_photo_${patientId}`
54
+ );
55
+ return mediaMetadata.url;
56
+ }
57
+
58
+ return null;
59
+ };
19
60
 
20
61
  // Funkcije za rad sa osetljivim informacijama
21
62
  export const createSensitiveInfoUtil = async (
22
63
  db: Firestore,
23
64
  data: CreatePatientSensitiveInfoData,
24
- requesterUserId: string
65
+ requesterUserId: string,
66
+ mediaService?: MediaService
25
67
  ): Promise<PatientSensitiveInfo> => {
26
68
  try {
27
69
  if (data.userRef !== requesterUserId) {
@@ -38,8 +80,21 @@ export const createSensitiveInfoUtil = async (
38
80
  throw new Error("Sensitive information already exists for this patient");
39
81
  }
40
82
 
83
+ // Process photoUrl if it's a MediaResource and mediaService is provided
84
+ let processedPhotoUrl: string | null = null;
85
+ if (validatedData.photoUrl && mediaService) {
86
+ processedPhotoUrl = await handlePhotoUrlUpload(
87
+ validatedData.photoUrl,
88
+ data.patientId,
89
+ mediaService
90
+ );
91
+ } else if (typeof validatedData.photoUrl === "string") {
92
+ processedPhotoUrl = validatedData.photoUrl;
93
+ }
94
+
41
95
  const sensitiveInfoData = {
42
96
  ...validatedData,
97
+ photoUrl: processedPhotoUrl,
43
98
  createdAt: serverTimestamp(),
44
99
  updatedAt: serverTimestamp(),
45
100
  };
@@ -82,7 +137,8 @@ export const updateSensitiveInfoUtil = async (
82
137
  db: Firestore,
83
138
  patientId: string,
84
139
  data: UpdatePatientSensitiveInfoData,
85
- requesterUserId: string
140
+ requesterUserId: string,
141
+ mediaService?: MediaService
86
142
  ): Promise<PatientSensitiveInfo> => {
87
143
  // if (data.userRef !== requesterUserId) {
88
144
  // throw new Error("Only patient can update their sensitive information");
@@ -91,8 +147,26 @@ export const updateSensitiveInfoUtil = async (
91
147
  // Inicijalizacija dokumenta ako ne postoji
92
148
  await initSensitiveInfoDocIfNotExists(db, patientId, requesterUserId);
93
149
 
150
+ // Process photoUrl if it's a MediaResource and mediaService is provided
151
+ let processedPhotoUrl: string | null | undefined = undefined;
152
+ if (data.photoUrl !== undefined) {
153
+ if (mediaService) {
154
+ processedPhotoUrl = await handlePhotoUrlUpload(
155
+ data.photoUrl,
156
+ patientId,
157
+ mediaService
158
+ );
159
+ } else if (typeof data.photoUrl === "string" || data.photoUrl === null) {
160
+ processedPhotoUrl = data.photoUrl;
161
+ } else {
162
+ // If photoUrl is a File/Blob but no mediaService provided, throw error
163
+ throw new Error("MediaService required to process photo upload");
164
+ }
165
+ }
166
+
94
167
  const updateData = {
95
168
  ...data,
169
+ photoUrl: processedPhotoUrl,
96
170
  updatedAt: serverTimestamp(),
97
171
  };
98
172