@blackcode_sa/metaestetics-api 1.6.1 → 1.6.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.6.1",
4
+ "version": "1.6.3",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -31,6 +31,7 @@ import {
31
31
  PatientClinic,
32
32
  SearchPatientsParams,
33
33
  RequesterInfo,
34
+ PatientProfileForDoctor,
34
35
  } from "../../types/patient";
35
36
  import { Auth } from "firebase/auth";
36
37
  import { Firestore } from "firebase/firestore";
@@ -99,7 +100,10 @@ import {
99
100
  removeClinicUtil,
100
101
  } from "./utils/medical-stuff.utils";
101
102
 
102
- import { getPatientsByPractitionerUtil } from "./utils/practitioner.utils";
103
+ import {
104
+ getPatientsByPractitionerUtil,
105
+ getPatientsByPractitionerWithDetailsUtil,
106
+ } from "./utils/practitioner.utils";
103
107
  import { getPatientsByClinicUtil } from "./utils/clinic.utils";
104
108
 
105
109
  export class PatientService extends BaseService {
@@ -543,6 +547,32 @@ export class PatientService extends BaseService {
543
547
  return getPatientsByPractitionerUtil(this.db, practitionerId, options);
544
548
  }
545
549
 
550
+ /**
551
+ * Gets all patients associated with a specific practitioner with their sensitive information.
552
+ *
553
+ * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
554
+ * @param {Object} options - Optional parameters for pagination
555
+ * @param {number} options.limit - Maximum number of profiles to return
556
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
557
+ * @returns {Promise<PatientProfileForDoctor[]>} A promise resolving to an array of patient profiles with sensitive info
558
+ */
559
+ async getPatientsByPractitionerWithDetails(
560
+ practitionerId: string,
561
+ options?: {
562
+ limit?: number;
563
+ startAfter?: string;
564
+ }
565
+ ): Promise<PatientProfileForDoctor[]> {
566
+ console.log(
567
+ `[PatientService.getPatientsByPractitionerWithDetails] Fetching detailed patient profiles for practitioner: ${practitionerId}`
568
+ );
569
+ return getPatientsByPractitionerWithDetailsUtil(
570
+ this.db,
571
+ practitionerId,
572
+ options
573
+ );
574
+ }
575
+
546
576
  /**
547
577
  * Gets all patients associated with a specific clinic.
548
578
  *
@@ -10,7 +10,13 @@ import {
10
10
  getDoc,
11
11
  QueryConstraint,
12
12
  } from "firebase/firestore";
13
- import { PatientProfile, PATIENTS_COLLECTION } from "../../../types/patient";
13
+ import {
14
+ PatientProfile,
15
+ PatientSensitiveInfo,
16
+ PatientProfileForDoctor,
17
+ PATIENTS_COLLECTION,
18
+ } from "../../../types/patient";
19
+ import { getSensitiveInfoDocRef } from "./docs.utils";
14
20
 
15
21
  /**
16
22
  * Retrieves all patients associated with a specific practitioner with pagination support.
@@ -78,3 +84,75 @@ export const getPatientsByPractitionerUtil = async (
78
84
  );
79
85
  }
80
86
  };
87
+
88
+ /**
89
+ * Retrieves all patients associated with a specific practitioner with their sensitive information.
90
+ *
91
+ * @param {Firestore} db - Firestore instance
92
+ * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
93
+ * @param {Object} options - Optional parameters for pagination
94
+ * @param {number} options.limit - Maximum number of profiles to return
95
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
96
+ * @returns {Promise<PatientProfileForDoctor[]>} A promise resolving to an array of patient profiles with sensitive info
97
+ */
98
+ export const getPatientsByPractitionerWithDetailsUtil = async (
99
+ db: Firestore,
100
+ practitionerId: string,
101
+ options?: { limit?: number; startAfter?: string }
102
+ ): Promise<PatientProfileForDoctor[]> => {
103
+ try {
104
+ console.log(
105
+ `[getPatientsByPractitionerWithDetailsUtil] Fetching detailed patient profiles for practitioner ID: ${practitionerId} with options:`,
106
+ options
107
+ );
108
+
109
+ // First, get all patient profiles for this practitioner
110
+ const patientProfiles = await getPatientsByPractitionerUtil(
111
+ db,
112
+ practitionerId,
113
+ options
114
+ );
115
+
116
+ // Then, fetch sensitive info for each patient
117
+ const patientProfilesWithDetails: PatientProfileForDoctor[] =
118
+ await Promise.all(
119
+ patientProfiles.map(async (profile) => {
120
+ try {
121
+ const sensitiveInfoDoc = await getDoc(
122
+ getSensitiveInfoDocRef(db, profile.id)
123
+ );
124
+ const sensitiveInfo = sensitiveInfoDoc.exists()
125
+ ? (sensitiveInfoDoc.data() as PatientSensitiveInfo)
126
+ : undefined;
127
+
128
+ return {
129
+ patientProfile: profile,
130
+ patientSensitiveInfo: sensitiveInfo,
131
+ };
132
+ } catch (error) {
133
+ console.error(
134
+ `[getPatientsByPractitionerWithDetailsUtil] Error fetching sensitive info for patient ${profile.id}:`,
135
+ error
136
+ );
137
+ // Return profile without sensitive info in case of error
138
+ return { patientProfile: profile };
139
+ }
140
+ })
141
+ );
142
+
143
+ console.log(
144
+ `[getPatientsByPractitionerWithDetailsUtil] Found ${patientProfilesWithDetails.length} detailed patient profiles for practitioner ID: ${practitionerId}`
145
+ );
146
+ return patientProfilesWithDetails;
147
+ } catch (error) {
148
+ console.error(
149
+ `[getPatientsByPractitionerWithDetailsUtil] Error fetching detailed patient profiles:`,
150
+ error
151
+ );
152
+ throw new Error(
153
+ `Failed to retrieve detailed patient profiles: ${
154
+ error instanceof Error ? error.message : String(error)
155
+ }`
156
+ );
157
+ }
158
+ };
@@ -220,12 +220,17 @@ export class PractitionerService extends BaseService {
220
220
  for (const cId of clinics) {
221
221
  const clinicData = await this.getClinicService().getClinic(cId);
222
222
  if (clinicData) {
223
+ // Ensure we're creating a ClinicInfo object that matches the interface structure
223
224
  clinicsInfo.push({
224
225
  id: clinicData.id,
225
226
  name: clinicData.name,
226
227
  location: clinicData.location,
227
228
  contactInfo: clinicData.contactInfo,
228
- featuredPhoto: clinicData.coverPhoto || "",
229
+ // Make sure we're using the right property for featuredPhoto
230
+ featuredPhoto:
231
+ clinicData.featuredPhotos && clinicData.featuredPhotos.length > 0
232
+ ? clinicData.featuredPhotos[0]
233
+ : clinicData.coverPhoto || "",
229
234
  description: clinicData.description || null,
230
235
  });
231
236
  }
@@ -237,3 +237,9 @@ export interface PatientProfileComplete {
237
237
  patientMedicalInfo?: PatientMedicalInfo;
238
238
  patientLocationInfo?: PatientLocationInfo;
239
239
  }
240
+
241
+ // Create a type that combines patientProfile and patientSensitiveInfo, this will be used in the doctor's app only
242
+ export interface PatientProfileForDoctor {
243
+ patientProfile?: PatientProfile;
244
+ patientSensitiveInfo?: PatientSensitiveInfo;
245
+ }
@@ -13,8 +13,12 @@ import {
13
13
  Currency,
14
14
  PricingMeasure,
15
15
  } from "../backoffice/types/static/pricing.types";
16
- import { procedureSummaryInfoSchema } from "./practitioner.schema";
17
16
  import { clinicReviewInfoSchema } from "./reviews.schema";
17
+ import {
18
+ procedureSummaryInfoSchema,
19
+ clinicInfoSchema,
20
+ doctorInfoSchema,
21
+ } from "./shared.schema";
18
22
 
19
23
  /**
20
24
  * Validaciona šema za kontakt informacije
@@ -92,30 +96,6 @@ export const adminInfoSchema = z.object({
92
96
  email: z.string().email(),
93
97
  });
94
98
 
95
- /**
96
- * Validaciona šema za osnovne informacije o klinici (for aggregation)
97
- */
98
- export const clinicInfoSchema = z.object({
99
- id: z.string(),
100
- featuredPhoto: z.string(),
101
- name: z.string(),
102
- description: z.string().nullable().optional(),
103
- location: clinicLocationSchema,
104
- contactInfo: clinicContactInfoSchema,
105
- });
106
-
107
- /**
108
- * Validaciona šema za informacije o doktoru (for aggregation in Clinic)
109
- */
110
- export const doctorInfoSchema = z.object({
111
- id: z.string(),
112
- name: z.string(),
113
- description: z.string().nullable().optional(),
114
- photo: z.string(),
115
- rating: z.number().min(0).max(5),
116
- services: z.array(z.string()), // List of procedure IDs practitioner offers
117
- });
118
-
119
99
  /**
120
100
  * @deprecated Replaced by procedureSummaryInfoSchema.
121
101
  * Validaciona šema za informacije o usluzi (Old aggregation schema)
@@ -9,12 +9,12 @@ import {
9
9
  PractitionerTokenStatus,
10
10
  } from "../types/practitioner";
11
11
  import { practitionerReviewInfoSchema } from "./reviews.schema";
12
- import { clinicInfoSchema } from "./clinic.schema";
13
12
  import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
14
13
  import {
15
14
  Currency,
16
15
  PricingMeasure,
17
16
  } from "../backoffice/types/static/pricing.types";
17
+ import { clinicInfoSchema, procedureSummaryInfoSchema } from "./shared.schema";
18
18
 
19
19
  /**
20
20
  * Šema za validaciju osnovnih informacija o zdravstvenom radniku
@@ -93,7 +93,7 @@ export const practitionerClinicWorkingHoursSchema = z.object({
93
93
  /**
94
94
  * Schema matching ProcedureSummaryInfo interface
95
95
  */
96
- export const procedureSummaryInfoSchema = z.object({
96
+ /* export const procedureSummaryInfoSchema = z.object({
97
97
  id: z.string().min(1),
98
98
  name: z.string().min(1),
99
99
  description: z.string().optional(),
@@ -110,7 +110,7 @@ export const procedureSummaryInfoSchema = z.object({
110
110
  clinicName: z.string().min(1),
111
111
  practitionerId: z.string().min(1),
112
112
  practitionerName: z.string().min(1),
113
- });
113
+ }); */
114
114
 
115
115
  /**
116
116
  * Šema za validaciju kompletnog profila zdravstvenog radnika
@@ -4,7 +4,7 @@ import {
4
4
  Currency,
5
5
  PricingMeasure,
6
6
  } from "../backoffice/types/static/pricing.types";
7
- import { clinicInfoSchema, doctorInfoSchema } from "./clinic.schema";
7
+ import { clinicInfoSchema, doctorInfoSchema } from "./shared.schema";
8
8
  import { procedureReviewInfoSchema } from "./reviews.schema";
9
9
 
10
10
  /**
@@ -0,0 +1,78 @@
1
+ import { z } from "zod";
2
+ import { Timestamp } from "firebase/firestore";
3
+ import { ProcedureFamily } from "../backoffice/types/static/procedure-family.types";
4
+ import {
5
+ Currency,
6
+ PricingMeasure,
7
+ } from "../backoffice/types/static/pricing.types";
8
+ // Remove import from clinic schema to avoid circular dependency
9
+ // import { clinicLocationSchema, clinicContactInfoSchema } from "./clinic.schema";
10
+
11
+ /**
12
+ * Validation schema for clinic contact information
13
+ */
14
+ export const sharedClinicContactInfoSchema = z.object({
15
+ email: z.string().email(),
16
+ phoneNumber: z.string(),
17
+ alternativePhoneNumber: z.string().nullable().optional(),
18
+ website: z.string().nullable().optional(),
19
+ });
20
+
21
+ /**
22
+ * Validation schema for clinic location
23
+ */
24
+ export const sharedClinicLocationSchema = z.object({
25
+ address: z.string(),
26
+ city: z.string(),
27
+ country: z.string(),
28
+ postalCode: z.string(),
29
+ latitude: z.number().min(-90).max(90),
30
+ longitude: z.number().min(-180).max(180),
31
+ geohash: z.string().nullable().optional(),
32
+ });
33
+
34
+ /**
35
+ * Schema for procedure summary info - shared between practitioner and clinic schemas
36
+ */
37
+ export const procedureSummaryInfoSchema = z.object({
38
+ id: z.string().min(1),
39
+ name: z.string().min(1),
40
+ description: z.string().optional(),
41
+ photo: z.string().optional(),
42
+ family: z.nativeEnum(ProcedureFamily),
43
+ categoryName: z.string(),
44
+ subcategoryName: z.string(),
45
+ technologyName: z.string(),
46
+ price: z.number().nonnegative(),
47
+ pricingMeasure: z.nativeEnum(PricingMeasure),
48
+ currency: z.nativeEnum(Currency),
49
+ duration: z.number().int().positive(),
50
+ clinicId: z.string().min(1),
51
+ clinicName: z.string().min(1),
52
+ practitionerId: z.string().min(1),
53
+ practitionerName: z.string().min(1),
54
+ });
55
+
56
+ /**
57
+ * Schema for clinic info - shared between practitioner and clinic schemas
58
+ */
59
+ export const clinicInfoSchema = z.object({
60
+ id: z.string(),
61
+ featuredPhoto: z.string(),
62
+ name: z.string(),
63
+ description: z.string().nullable().optional(),
64
+ location: sharedClinicLocationSchema,
65
+ contactInfo: sharedClinicContactInfoSchema,
66
+ });
67
+
68
+ /**
69
+ * Schema for doctor info - shared between procedure and clinic schemas
70
+ */
71
+ export const doctorInfoSchema = z.object({
72
+ id: z.string(),
73
+ name: z.string(),
74
+ description: z.string().nullable().optional(),
75
+ photo: z.string(),
76
+ rating: z.number().min(0).max(5),
77
+ services: z.array(z.string()), // List of procedure IDs practitioner offers
78
+ });