@blackcode_sa/metaestetics-api 1.7.45 → 1.8.0

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.
Files changed (55) hide show
  1. package/dist/admin/index.d.mts +999 -959
  2. package/dist/admin/index.d.ts +999 -959
  3. package/dist/admin/index.js +69 -69
  4. package/dist/admin/index.mjs +67 -69
  5. package/dist/index.d.mts +14675 -13040
  6. package/dist/index.d.ts +14675 -13040
  7. package/dist/index.js +12224 -14615
  8. package/dist/index.mjs +12452 -14968
  9. package/package.json +5 -5
  10. package/src/admin/index.ts +8 -1
  11. package/src/index.backup.ts +407 -0
  12. package/src/index.ts +5 -406
  13. package/src/services/PATIENTAUTH.MD +197 -0
  14. package/src/services/__tests__/auth/auth.setup.ts +2 -2
  15. package/src/services/__tests__/auth.service.test.ts +1 -1
  16. package/src/services/__tests__/user.service.test.ts +1 -1
  17. package/src/services/appointment/index.ts +1 -2
  18. package/src/services/{auth.service.ts → auth/auth.service.ts} +36 -22
  19. package/src/services/{auth.v2.service.ts → auth/auth.v2.service.ts} +17 -17
  20. package/src/services/auth/index.ts +2 -16
  21. package/src/services/calendar/calendar-refactored.service.ts +1 -1
  22. package/src/services/calendar/externalCalendar.service.ts +178 -0
  23. package/src/services/calendar/index.ts +5 -0
  24. package/src/services/clinic/index.ts +4 -0
  25. package/src/services/index.ts +14 -0
  26. package/src/services/media/index.ts +1 -0
  27. package/src/services/notifications/index.ts +1 -0
  28. package/src/services/patient/README.md +48 -0
  29. package/src/services/patient/To-Do.md +43 -0
  30. package/src/services/patient/index.ts +2 -0
  31. package/src/services/patient/patient.service.ts +289 -34
  32. package/src/services/patient/utils/index.ts +9 -0
  33. package/src/services/patient/utils/medical.utils.ts +114 -157
  34. package/src/services/patient/utils/profile.utils.ts +9 -0
  35. package/src/services/patient/utils/sensitive.utils.ts +79 -14
  36. package/src/services/patient/utils/token.utils.ts +211 -0
  37. package/src/services/practitioner/index.ts +1 -0
  38. package/src/services/procedure/index.ts +1 -0
  39. package/src/services/reviews/index.ts +1 -0
  40. package/src/services/user/index.ts +1 -0
  41. package/src/services/{user.service.ts → user/user.service.ts} +61 -12
  42. package/src/services/{user.v2.service.ts → user/user.v2.service.ts} +12 -12
  43. package/src/types/index.ts +42 -42
  44. package/src/types/patient/index.ts +33 -6
  45. package/src/types/patient/token.types.ts +61 -0
  46. package/src/types/user/index.ts +38 -0
  47. package/src/utils/index.ts +1 -0
  48. package/src/validations/calendar.schema.ts +6 -45
  49. package/src/validations/documentation-templates/index.ts +1 -0
  50. package/src/validations/documentation-templates.schema.ts +1 -1
  51. package/src/validations/index.ts +20 -0
  52. package/src/validations/patient/token.schema.ts +29 -0
  53. package/src/validations/patient.schema.ts +23 -6
  54. package/src/validations/profile-info.schema.ts +1 -1
  55. package/src/validations/schemas.ts +24 -24
@@ -37,6 +37,7 @@ import {
37
37
  SearchPatientsParams,
38
38
  RequesterInfo,
39
39
  PatientProfileForDoctor,
40
+ CreateManualPatientData,
40
41
  } from "../../types/patient";
41
42
  import { Auth } from "firebase/auth";
42
43
  import { Firestore } from "firebase/firestore";
@@ -55,22 +56,13 @@ import {
55
56
  updatePatientProfileByUserRefUtil,
56
57
  searchPatientsUtil,
57
58
  getAllPatientsUtil,
58
- } from "./utils/profile.utils";
59
-
60
- import {
61
59
  updatePatientLocationUtil,
62
60
  createLocationInfoUtil,
63
61
  getLocationInfoUtil,
64
62
  updateLocationInfoUtil,
65
- } from "./utils/location.utils";
66
-
67
- import {
68
63
  createSensitiveInfoUtil,
69
64
  getSensitiveInfoUtil,
70
65
  updateSensitiveInfoUtil,
71
- } from "./utils/sensitive.utils";
72
-
73
- import {
74
66
  createMedicalInfoUtil,
75
67
  getMedicalInfoUtil,
76
68
  updateVitalStatsUtil,
@@ -86,27 +78,28 @@ import {
86
78
  addMedicationUtil,
87
79
  updateMedicationUtil,
88
80
  removeMedicationUtil,
89
- } from "./utils/medical.utils";
90
-
91
- import {
92
81
  getPatientDocRef,
93
82
  getSensitiveInfoDocRef,
94
83
  getLocationInfoDocRef,
95
84
  getMedicalInfoDocRef,
96
- } from "./utils/docs.utils";
97
-
98
- import {
99
85
  addDoctorUtil,
100
86
  removeDoctorUtil,
101
87
  addClinicUtil,
102
88
  removeClinicUtil,
103
- } from "./utils/medical-stuff.utils";
104
-
105
- import {
106
89
  getPatientsByPractitionerUtil,
107
90
  getPatientsByPractitionerWithDetailsUtil,
108
- } from "./utils/practitioner.utils";
109
- import { getPatientsByClinicUtil } from "./utils/clinic.utils";
91
+ getPatientsByClinicUtil,
92
+ createPatientTokenUtil,
93
+ validatePatientTokenUtil,
94
+ markPatientTokenAsUsedUtil,
95
+ getActiveInviteTokensByClinicUtil,
96
+ getActiveInviteTokensByPatientUtil,
97
+ } from "./utils";
98
+
99
+ import {
100
+ CreatePatientTokenData,
101
+ PatientToken,
102
+ } from "../../types/patient/token.types";
110
103
 
111
104
  export class PatientService extends BaseService {
112
105
  private mediaService: MediaService;
@@ -127,6 +120,111 @@ export class PatientService extends BaseService {
127
120
  return getPatientProfileUtil(this.db, patientId);
128
121
  }
129
122
 
123
+ /**
124
+ * Manually creates a new patient profile, typically initiated by a clinic admin.
125
+ * This creates a patient record that is not initially linked to an authenticated user.
126
+ *
127
+ * @param {CreateManualPatientData} data - The data for the new patient.
128
+ * @param {RequesterInfo} requester - Information about the user creating the patient (must be a clinic admin).
129
+ * @returns {Promise<PatientProfile>} The newly created patient profile.
130
+ * @throws {Error} If the requester is not a valid clinic admin.
131
+ */
132
+ async createManualPatient(
133
+ data: CreateManualPatientData,
134
+ requester: RequesterInfo
135
+ ): Promise<PatientProfile> {
136
+ console.log(
137
+ `[PatientService.createManualPatient] Attempting to create manual patient by requester:`,
138
+ requester
139
+ );
140
+
141
+ // Security Check: Ensure the requester is a clinic admin
142
+ if (
143
+ requester.role !== "clinic_admin" ||
144
+ !requester.associatedClinicId ||
145
+ requester.associatedClinicId !== data.clinicId
146
+ ) {
147
+ throw new Error(
148
+ "Unauthorized: Requester must be a clinic admin and can only add patients to their own clinic."
149
+ );
150
+ }
151
+
152
+ const patientId = this.generateId();
153
+ const batch = writeBatch(this.db);
154
+ const now = Timestamp.now();
155
+
156
+ // 1. Create Patient Profile
157
+ const patientProfileRef = getPatientDocRef(this.db, patientId);
158
+ const newProfile: PatientProfile = {
159
+ id: patientId,
160
+ displayName: `${data.firstName} ${data.lastName.charAt(0)}.`,
161
+ expoTokens: [],
162
+ gamification: { level: 1, points: 0 },
163
+ isActive: true,
164
+ isVerified: false, // Manual profiles are not verified by default
165
+ isManual: true,
166
+ doctors: [],
167
+ clinics: [
168
+ {
169
+ clinicId: data.clinicId,
170
+ assignedAt: now,
171
+ assignedBy: requester.id,
172
+ isActive: true,
173
+ notes: data.notes,
174
+ },
175
+ ],
176
+ doctorIds: [],
177
+ clinicIds: [data.clinicId],
178
+ createdAt: now,
179
+ updatedAt: now,
180
+ phoneNumber: data.phoneNumber,
181
+ dateOfBirth: data.dateOfBirth,
182
+ };
183
+ batch.set(patientProfileRef, newProfile);
184
+
185
+ // 2. Create Patient Sensitive Info
186
+ const sensitiveInfoRef = getSensitiveInfoDocRef(this.db, patientId);
187
+ const newSensitiveInfo: Omit<PatientSensitiveInfo, "photoUrl"> = {
188
+ patientId,
189
+ firstName: data.firstName,
190
+ lastName: data.lastName,
191
+ dateOfBirth: data.dateOfBirth,
192
+ gender: data.gender,
193
+ email: data.email,
194
+ phoneNumber: data.phoneNumber,
195
+ addressData: data.addressData,
196
+ emergencyContacts: [],
197
+ createdAt: now,
198
+ updatedAt: now,
199
+ };
200
+ batch.set(sensitiveInfoRef, newSensitiveInfo);
201
+
202
+ // 3. Create Patient Medical Info
203
+ const medicalInfoRef = getMedicalInfoDocRef(this.db, patientId);
204
+ const newMedicalInfo: PatientMedicalInfo = {
205
+ patientId,
206
+ vitalStats: {},
207
+ blockingConditions: [],
208
+ contraindications: [],
209
+ allergies: [],
210
+ currentMedications: [],
211
+ emergencyNotes: "",
212
+ lastUpdated: now,
213
+ updatedBy: requester.id, // The admin who created the record
214
+ verifiedAt: undefined,
215
+ verifiedBy: undefined,
216
+ };
217
+ batch.set(medicalInfoRef, newMedicalInfo);
218
+
219
+ await batch.commit();
220
+
221
+ console.log(
222
+ `[PatientService.createManualPatient] Successfully created manual patient with ID: ${patientId}`
223
+ );
224
+
225
+ return newProfile;
226
+ }
227
+
130
228
  async getPatientProfileByUserRef(
131
229
  userRef: string
132
230
  ): Promise<PatientProfile | null> {
@@ -188,10 +286,16 @@ export class PatientService extends BaseService {
188
286
  data: CreatePatientSensitiveInfoData,
189
287
  requesterUserId: string
190
288
  ): Promise<PatientSensitiveInfo> {
289
+ const currentUser = await this.getCurrentUser();
290
+ if (currentUser.uid !== requesterUserId) {
291
+ throw new Error("Requester does not match authenticated user.");
292
+ }
293
+
191
294
  return createSensitiveInfoUtil(
192
295
  this.db,
193
296
  data,
194
297
  requesterUserId,
298
+ currentUser.roles,
195
299
  this.mediaService
196
300
  );
197
301
  }
@@ -200,7 +304,17 @@ export class PatientService extends BaseService {
200
304
  patientId: string,
201
305
  requesterUserId: string
202
306
  ): Promise<PatientSensitiveInfo | null> {
203
- return getSensitiveInfoUtil(this.db, patientId, requesterUserId);
307
+ const currentUser = await this.getCurrentUser();
308
+ if (currentUser.uid !== requesterUserId) {
309
+ // Allow for read-only access if authorized, but for now we check identity
310
+ // This could be expanded later based on practitioner/admin roles
311
+ }
312
+ return getSensitiveInfoUtil(
313
+ this.db,
314
+ patientId,
315
+ requesterUserId,
316
+ currentUser.roles
317
+ );
204
318
  }
205
319
 
206
320
  async getSensitiveInfoByUserRef(
@@ -209,6 +323,7 @@ export class PatientService extends BaseService {
209
323
  ): Promise<PatientSensitiveInfo | null> {
210
324
  const profile = await this.getPatientProfileByUserRef(userRef);
211
325
  if (!profile) return null;
326
+ // We pass requesterUserId which is the UID of the one asking.
212
327
  return this.getSensitiveInfo(profile.id, requesterUserId);
213
328
  }
214
329
 
@@ -217,11 +332,16 @@ export class PatientService extends BaseService {
217
332
  data: UpdatePatientSensitiveInfoData,
218
333
  requesterUserId: string
219
334
  ): Promise<PatientSensitiveInfo> {
335
+ const currentUser = await this.getCurrentUser();
336
+ if (currentUser.uid !== requesterUserId) {
337
+ throw new Error("Requester does not match authenticated user.");
338
+ }
220
339
  return updateSensitiveInfoUtil(
221
340
  this.db,
222
341
  patientId,
223
342
  data,
224
343
  requesterUserId,
344
+ currentUser.roles,
225
345
  this.mediaService
226
346
  );
227
347
  }
@@ -263,13 +383,25 @@ export class PatientService extends BaseService {
263
383
  data: UpdateVitalStatsData
264
384
  ): Promise<void> {
265
385
  const currentUser = await this.getCurrentUser();
266
- await updateVitalStatsUtil(this.db, patientId, data, currentUser.uid);
386
+ await updateVitalStatsUtil(
387
+ this.db,
388
+ patientId,
389
+ data,
390
+ currentUser.uid,
391
+ currentUser.roles
392
+ );
267
393
  }
268
394
 
269
395
  // Metode za rad sa alergijama
270
396
  async addAllergy(patientId: string, data: AddAllergyData): Promise<void> {
271
397
  const currentUser = await this.getCurrentUser();
272
- await addAllergyUtil(this.db, patientId, data, currentUser.uid);
398
+ await addAllergyUtil(
399
+ this.db,
400
+ patientId,
401
+ data,
402
+ currentUser.uid,
403
+ currentUser.roles
404
+ );
273
405
  }
274
406
 
275
407
  async updateAllergy(
@@ -277,12 +409,24 @@ export class PatientService extends BaseService {
277
409
  data: UpdateAllergyData
278
410
  ): Promise<void> {
279
411
  const currentUser = await this.getCurrentUser();
280
- await updateAllergyUtil(this.db, patientId, data, currentUser.uid);
412
+ await updateAllergyUtil(
413
+ this.db,
414
+ patientId,
415
+ data,
416
+ currentUser.uid,
417
+ currentUser.roles
418
+ );
281
419
  }
282
420
 
283
421
  async removeAllergy(patientId: string, allergyIndex: number): Promise<void> {
284
422
  const currentUser = await this.getCurrentUser();
285
- await removeAllergyUtil(this.db, patientId, allergyIndex, currentUser.uid);
423
+ await removeAllergyUtil(
424
+ this.db,
425
+ patientId,
426
+ allergyIndex,
427
+ currentUser.uid,
428
+ currentUser.roles
429
+ );
286
430
  }
287
431
 
288
432
  // Metode za rad sa blocking conditions
@@ -291,7 +435,13 @@ export class PatientService extends BaseService {
291
435
  data: AddBlockingConditionData
292
436
  ): Promise<void> {
293
437
  const currentUser = await this.getCurrentUser();
294
- await addBlockingConditionUtil(this.db, patientId, data, currentUser.uid);
438
+ await addBlockingConditionUtil(
439
+ this.db,
440
+ patientId,
441
+ data,
442
+ currentUser.uid,
443
+ currentUser.roles
444
+ );
295
445
  }
296
446
 
297
447
  async updateBlockingCondition(
@@ -303,7 +453,8 @@ export class PatientService extends BaseService {
303
453
  this.db,
304
454
  patientId,
305
455
  data,
306
- currentUser.uid
456
+ currentUser.uid,
457
+ currentUser.roles
307
458
  );
308
459
  }
309
460
 
@@ -316,7 +467,8 @@ export class PatientService extends BaseService {
316
467
  this.db,
317
468
  patientId,
318
469
  conditionIndex,
319
- currentUser.uid
470
+ currentUser.uid,
471
+ currentUser.roles
320
472
  );
321
473
  }
322
474
 
@@ -326,7 +478,13 @@ export class PatientService extends BaseService {
326
478
  data: AddContraindicationData
327
479
  ): Promise<void> {
328
480
  const currentUser = await this.getCurrentUser();
329
- await addContraindicationUtil(this.db, patientId, data, currentUser.uid);
481
+ await addContraindicationUtil(
482
+ this.db,
483
+ patientId,
484
+ data,
485
+ currentUser.uid,
486
+ currentUser.roles
487
+ );
330
488
  }
331
489
 
332
490
  async updateContraindication(
@@ -334,7 +492,13 @@ export class PatientService extends BaseService {
334
492
  data: UpdateContraindicationData
335
493
  ): Promise<void> {
336
494
  const currentUser = await this.getCurrentUser();
337
- await updateContraindicationUtil(this.db, patientId, data, currentUser.uid);
495
+ await updateContraindicationUtil(
496
+ this.db,
497
+ patientId,
498
+ data,
499
+ currentUser.uid,
500
+ currentUser.roles
501
+ );
338
502
  }
339
503
 
340
504
  async removeContraindication(
@@ -346,7 +510,8 @@ export class PatientService extends BaseService {
346
510
  this.db,
347
511
  patientId,
348
512
  contraindicationIndex,
349
- currentUser.uid
513
+ currentUser.uid,
514
+ currentUser.roles
350
515
  );
351
516
  }
352
517
 
@@ -356,7 +521,13 @@ export class PatientService extends BaseService {
356
521
  data: AddMedicationData
357
522
  ): Promise<void> {
358
523
  const currentUser = await this.getCurrentUser();
359
- await addMedicationUtil(this.db, patientId, data, currentUser.uid);
524
+ await addMedicationUtil(
525
+ this.db,
526
+ patientId,
527
+ data,
528
+ currentUser.uid,
529
+ currentUser.roles
530
+ );
360
531
  }
361
532
 
362
533
  async updateMedication(
@@ -364,7 +535,13 @@ export class PatientService extends BaseService {
364
535
  data: UpdateMedicationData
365
536
  ): Promise<void> {
366
537
  const currentUser = await this.getCurrentUser();
367
- await updateMedicationUtil(this.db, patientId, data, currentUser.uid);
538
+ await updateMedicationUtil(
539
+ this.db,
540
+ patientId,
541
+ data,
542
+ currentUser.uid,
543
+ currentUser.roles
544
+ );
368
545
  }
369
546
 
370
547
  async removeMedication(
@@ -376,7 +553,8 @@ export class PatientService extends BaseService {
376
553
  this.db,
377
554
  patientId,
378
555
  medicationIndex,
379
- currentUser.uid
556
+ currentUser.uid,
557
+ currentUser.roles
380
558
  );
381
559
  }
382
560
 
@@ -746,4 +924,81 @@ export class PatientService extends BaseService {
746
924
  );
747
925
  return getPatientsByClinicUtil(this.db, clinicId, options);
748
926
  }
927
+
928
+ /**
929
+ * Creates a token for inviting a patient to claim their profile.
930
+ *
931
+ * @param {CreatePatientTokenData} data - Data for creating the token.
932
+ * @param {string} createdBy - ID of the admin user creating the token.
933
+ * @returns {Promise<PatientToken>} The created token.
934
+ */
935
+ async createPatientToken(
936
+ data: CreatePatientTokenData,
937
+ createdBy: string
938
+ ): Promise<PatientToken> {
939
+ // We assume the 'createdBy' user is validated to be a clinic admin
940
+ // in the calling context (e.g., a cloud function or API endpoint).
941
+ return createPatientTokenUtil(
942
+ this.db,
943
+ data,
944
+ createdBy,
945
+ () => this.generateId() // Pass the ID generation function
946
+ );
947
+ }
948
+
949
+ /**
950
+ * Validates a patient invitation token.
951
+ *
952
+ * @param {string} tokenString - The token string to validate.
953
+ * @returns {Promise<PatientToken | null>} The token if found and valid, otherwise null.
954
+ */
955
+ async validatePatientToken(
956
+ tokenString: string
957
+ ): Promise<PatientToken | null> {
958
+ return validatePatientTokenUtil(this.db, tokenString);
959
+ }
960
+
961
+ /**
962
+ * Marks a patient invitation token as used.
963
+ *
964
+ * @param {string} tokenId - The ID of the token to mark as used.
965
+ * @param {string} patientId - The ID of the patient associated with the token.
966
+ * @param {string} userId - The ID of the user who is using the token.
967
+ * @returns {Promise<void>}
968
+ */
969
+ async markPatientTokenAsUsed(
970
+ tokenId: string,
971
+ patientId: string,
972
+ userId: string
973
+ ): Promise<void> {
974
+ return markPatientTokenAsUsedUtil(this.db, tokenId, patientId, userId);
975
+ }
976
+
977
+ /**
978
+ * Retrieves all active invitation tokens for a specific clinic.
979
+ * NOTE: This should be protected and only exposed to authorized clinic admins.
980
+ *
981
+ * @param {string} clinicId - The ID of the clinic.
982
+ * @returns {Promise<PatientToken[]>} An array of active tokens for the clinic.
983
+ */
984
+ async getActiveInviteTokensByClinic(
985
+ clinicId: string
986
+ ): Promise<PatientToken[]> {
987
+ return getActiveInviteTokensByClinicUtil(this.db, clinicId);
988
+ }
989
+
990
+ /**
991
+ * Retrieves all active invitation tokens for a specific patient.
992
+ * NOTE: This should be protected and only exposed to authorized clinic admins.
993
+ *
994
+ * @param {string} patientId - The ID of the patient.
995
+ * @returns {Promise<PatientToken[]>} An array of active tokens for the patient.
996
+ */
997
+ async getActiveInviteTokensByPatient(
998
+ patientId: string
999
+ ): Promise<PatientToken[]> {
1000
+ // Security check should be done in the calling context to ensure
1001
+ // the admin has permission to view this patient's tokens.
1002
+ return getActiveInviteTokensByPatientUtil(this.db, patientId);
1003
+ }
749
1004
  }
@@ -0,0 +1,9 @@
1
+ export * from "./clinic.utils";
2
+ export * from "./docs.utils";
3
+ export * from "./location.utils";
4
+ export * from "./medical-stuff.utils";
5
+ export * from "./medical.utils";
6
+ export * from "./practitioner.utils";
7
+ export * from "./profile.utils";
8
+ export * from "./sensitive.utils";
9
+ export * from "./token.utils";