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