@blackcode_sa/metaestetics-api 1.7.2 → 1.7.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.7.2",
4
+ "version": "1.7.3",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -25,6 +25,7 @@ import {
25
25
  PATIENTS_COLLECTION,
26
26
  PATIENT_MEDICAL_INFO_COLLECTION,
27
27
  PatientDoctor,
28
+ type PatientProfile,
28
29
  } from "../../../types/patient";
29
30
  import {
30
31
  createPatientMedicalInfoSchema,
@@ -43,6 +44,7 @@ import { z } from "zod";
43
44
  import { AuthError } from "../../../errors/auth.errors";
44
45
  import { UserRole } from "../../../types";
45
46
  import { getMedicalInfoDocRef, getPatientDocRef } from "./docs.utils";
47
+ import { getPractitionerProfileByUserRef } from "./practitioner.utils";
46
48
 
47
49
  // Pomoćna funkcija za proveru i inicijalizaciju medical info dokumenta
48
50
  export const ensureMedicalInfoExists = async (
@@ -117,32 +119,29 @@ const checkMedicalAccessUtil = async (
117
119
  throw new Error("Patient profile not found");
118
120
  }
119
121
 
120
- const patientData = patientDoc.data();
122
+ const patientData = patientDoc.data() as PatientProfile;
121
123
 
122
124
  // Proveri da li je korisnik vlasnik profila
123
125
  if (patientData.userRef === userRef) return;
124
126
 
125
- // Proveri da li korisnik ima potrebnu ulogu
126
- if (
127
- !(
128
- userRoles.includes(UserRole.PRACTITIONER) ||
129
- userRoles.includes(UserRole.PATIENT)
130
- )
131
- ) {
132
- throw new AuthError(
133
- "Nedozvoljen pristup medicinskim informacijama",
134
- "AUTH/UNAUTHORIZED_ACCESS",
135
- 403
127
+ // Ako je doktor, proveri da li je povezan sa pacijentom
128
+ if (userRoles.includes(UserRole.PRACTITIONER)) {
129
+ const practitionerProfile = await getPractitionerProfileByUserRef(
130
+ db,
131
+ userRef
136
132
  );
137
- }
138
133
 
139
- // Ako je doktor, proveri da li je povezan sa pacijentom
140
- if (
141
- userRoles.includes(UserRole.PRACTITIONER) &&
142
- !userRoles.includes(UserRole.PATIENT)
143
- ) {
144
- const isAssignedDoctor = patientData.doctors?.some(
145
- (doctor: PatientDoctor) => doctor.userRef === userRef && doctor.isActive
134
+ if (!practitionerProfile) {
135
+ throw new AuthError(
136
+ "Practitioner profile not found",
137
+ "AUTH/UNAUTHORIZED_ACCESS",
138
+ 403
139
+ );
140
+ }
141
+
142
+ // Check if practitioner's ID is in the patient's doctorIds array
143
+ const isAssignedDoctor = patientData.doctorIds?.includes(
144
+ practitionerProfile.id
146
145
  );
147
146
 
148
147
  if (!isAssignedDoctor) {
@@ -190,8 +189,10 @@ export const getMedicalInfoUtil = async (
190
189
  throw new Error("Medicinske informacije nisu pronađene");
191
190
  }
192
191
 
192
+ // The schema will transform raw timestamp objects to Timestamp instances
193
193
  return patientMedicalInfoSchema.parse(snapshot.data());
194
194
  };
195
+
195
196
  // Funkcije za rad sa vitalnim statistikama
196
197
  export const updateVitalStatsUtil = async (
197
198
  db: Firestore,
@@ -235,10 +236,12 @@ export const updateAllergyUtil = async (
235
236
  const validatedData = updateAllergySchema.parse(data);
236
237
  const { allergyIndex, ...updateData } = validatedData;
237
238
 
238
- const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
239
- if (!doc.exists()) throw new Error("Medical info not found");
239
+ const docSnapshot = await getDoc(getMedicalInfoDocRef(db, patientId));
240
+ if (!docSnapshot.exists()) throw new Error("Medical info not found");
241
+
242
+ // Parse through schema to ensure proper Timestamp objects
243
+ const medicalInfo = patientMedicalInfoSchema.parse(docSnapshot.data());
240
244
 
241
- const medicalInfo = doc.data() as PatientMedicalInfo;
242
245
  if (allergyIndex >= medicalInfo.allergies.length) {
243
246
  throw new Error("Invalid allergy index");
244
247
  }
@@ -17,6 +17,10 @@ import {
17
17
  PATIENTS_COLLECTION,
18
18
  } from "../../../types/patient";
19
19
  import { getSensitiveInfoDocRef } from "./docs.utils";
20
+ import {
21
+ Practitioner,
22
+ PRACTITIONERS_COLLECTION,
23
+ } from "../../../types/practitioner";
20
24
 
21
25
  /**
22
26
  * Retrieves all patients associated with a specific practitioner with pagination support.
@@ -156,3 +160,101 @@ export const getPatientsByPractitionerWithDetailsUtil = async (
156
160
  );
157
161
  }
158
162
  };
163
+
164
+ /**
165
+ * Retrieves a practitioner profile by ID
166
+ *
167
+ * @param {Firestore} db - Firestore instance
168
+ * @param {string} practitionerId - ID of the practitioner to retrieve
169
+ * @returns {Promise<Practitioner | null>} A promise resolving to the practitioner profile or null if not found
170
+ */
171
+ export const getPractitionerProfile = async (
172
+ db: Firestore,
173
+ practitionerId: string
174
+ ): Promise<Practitioner | null> => {
175
+ try {
176
+ console.log(
177
+ `[getPractitionerProfile] Fetching practitioner with ID: ${practitionerId}`
178
+ );
179
+
180
+ const practitionerRef = doc(db, PRACTITIONERS_COLLECTION, practitionerId);
181
+ const practitionerSnapshot = await getDoc(practitionerRef);
182
+
183
+ if (!practitionerSnapshot.exists()) {
184
+ console.log(
185
+ `[getPractitionerProfile] Practitioner with ID ${practitionerId} not found`
186
+ );
187
+ return null;
188
+ }
189
+
190
+ const practitioner = practitionerSnapshot.data() as Practitioner;
191
+ console.log(
192
+ `[getPractitionerProfile] Successfully retrieved practitioner: ${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`
193
+ );
194
+
195
+ return practitioner;
196
+ } catch (error) {
197
+ console.error(
198
+ `[getPractitionerProfile] Error fetching practitioner:`,
199
+ error
200
+ );
201
+ throw new Error(
202
+ `Failed to retrieve practitioner: ${
203
+ error instanceof Error ? error.message : String(error)
204
+ }`
205
+ );
206
+ }
207
+ };
208
+
209
+ /**
210
+ * Retrieves a practitioner profile by user reference ID
211
+ *
212
+ * @param {Firestore} db - Firestore instance
213
+ * @param {string} userRef - Firebase Auth user ID reference
214
+ * @returns {Promise<Practitioner | null>} A promise resolving to the practitioner profile or null if not found
215
+ */
216
+ export const getPractitionerProfileByUserRef = async (
217
+ db: Firestore,
218
+ userRef: string
219
+ ): Promise<Practitioner | null> => {
220
+ try {
221
+ console.log(
222
+ `[getPractitionerProfileByUserRef] Fetching practitioner with userRef: ${userRef}`
223
+ );
224
+
225
+ const practitionersCollection = collection(db, PRACTITIONERS_COLLECTION);
226
+ const q = query(
227
+ practitionersCollection,
228
+ where("userRef", "==", userRef),
229
+ limit(1)
230
+ );
231
+
232
+ const querySnapshot = await getDocs(q);
233
+
234
+ if (querySnapshot.empty) {
235
+ console.log(
236
+ `[getPractitionerProfileByUserRef] No practitioner found with userRef: ${userRef}`
237
+ );
238
+ return null;
239
+ }
240
+
241
+ const practitionerDoc = querySnapshot.docs[0];
242
+ const practitioner = practitionerDoc.data() as Practitioner;
243
+
244
+ console.log(
245
+ `[getPractitionerProfileByUserRef] Successfully retrieved practitioner: ${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`
246
+ );
247
+
248
+ return practitioner;
249
+ } catch (error) {
250
+ console.error(
251
+ `[getPractitionerProfileByUserRef] Error fetching practitioner:`,
252
+ error
253
+ );
254
+ throw new Error(
255
+ `Failed to retrieve practitioner by userRef: ${
256
+ error instanceof Error ? error.message : String(error)
257
+ }`
258
+ );
259
+ }
260
+ };
@@ -34,7 +34,7 @@ export interface PatientMedicalInfo {
34
34
  condition: BlockingCondition;
35
35
  diagnosedAt: Timestamp;
36
36
  isActive: boolean;
37
- notes?: string;
37
+ notes?: string | null;
38
38
  }[];
39
39
 
40
40
  contraindications: {
@@ -42,7 +42,7 @@ export interface PatientMedicalInfo {
42
42
  lastOccurrence: Timestamp;
43
43
  frequency: "rare" | "occasional" | "frequent";
44
44
  isActive: boolean;
45
- notes?: string;
45
+ notes?: string | null;
46
46
  }[];
47
47
 
48
48
  allergies: Allergy[];
@@ -1,9 +1,20 @@
1
1
  import { z } from "zod";
2
2
  import { Timestamp } from "firebase/firestore";
3
3
 
4
- export const timestampSchema = z.object({
5
- seconds: z.number(),
6
- nanoseconds: z.number(),
7
- });
4
+ // Create a custom schema for Timestamp that properly handles both raw objects and Timestamp instances
5
+ export const timestampSchema = z
6
+ .union([
7
+ z.object({
8
+ seconds: z.number(),
9
+ nanoseconds: z.number(),
10
+ }),
11
+ z.instanceof(Timestamp),
12
+ ])
13
+ .transform((data) => {
14
+ if (data instanceof Timestamp) {
15
+ return data;
16
+ }
17
+ return new Timestamp(data.seconds, data.nanoseconds);
18
+ });
8
19
 
9
20
  // export const timestampSchema = z.instanceof(Timestamp);