@blackcode_sa/metaestetics-api 1.12.26 → 1.12.28

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.
@@ -1,31 +1,21 @@
1
- import {
2
- getDoc,
3
- updateDoc,
4
- setDoc,
5
- serverTimestamp,
6
- Firestore,
7
- } from "firebase/firestore";
1
+ import { getDoc, updateDoc, setDoc, serverTimestamp, Firestore } from 'firebase/firestore';
8
2
  import {
9
3
  PatientSensitiveInfo,
10
4
  CreatePatientSensitiveInfoData,
11
5
  UpdatePatientSensitiveInfoData,
12
- } from "../../../types/patient";
13
- import { UserRole } from "../../../types";
14
- import { createPatientSensitiveInfoSchema } from "../../../validations/patient.schema";
15
- import { z } from "zod";
6
+ } from '../../../types/patient';
7
+ import { UserRole } from '../../../types';
8
+ import { createPatientSensitiveInfoSchema } from '../../../validations/patient.schema';
9
+ import { z } from 'zod';
16
10
  import {
17
11
  getSensitiveInfoDocRef,
18
12
  initSensitiveInfoDocIfNotExists,
19
13
  getPatientDocRef,
20
- } from "./docs.utils";
21
- import {
22
- MediaService,
23
- MediaAccessLevel,
24
- MediaResource,
25
- } from "../../media/media.service";
26
- import { AuthError } from "../../../errors/auth.errors";
27
- import { getPractitionerProfileByUserRef } from "./practitioner.utils";
28
- import { getClinicAdminByUserRef } from "../../clinic/utils/admin.utils";
14
+ } from './docs.utils';
15
+ import { MediaService, MediaAccessLevel, MediaResource } from '../../media/media.service';
16
+ import { AuthError } from '../../../errors/auth.errors';
17
+ import { getPractitionerProfileByUserRef } from './practitioner.utils';
18
+ import { getClinicAdminByUserRef } from '../../clinic/utils/admin.utils';
29
19
 
30
20
  /**
31
21
  * Checks if the requester has permission to access/modify sensitive info.
@@ -35,11 +25,11 @@ const checkSensitiveAccessUtil = async (
35
25
  db: Firestore,
36
26
  patientId: string,
37
27
  requesterId: string,
38
- requesterRoles: UserRole[]
28
+ requesterRoles: UserRole[],
39
29
  ): Promise<void> => {
40
30
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
41
31
  if (!patientDoc.exists()) {
42
- throw new Error("Patient profile not found");
32
+ throw new Error('Patient profile not found');
43
33
  }
44
34
  const patientData = patientDoc.data() as any; // Cast to any to access properties
45
35
 
@@ -50,14 +40,8 @@ const checkSensitiveAccessUtil = async (
50
40
 
51
41
  // 2. Requester is an associated practitioner
52
42
  if (requesterRoles.includes(UserRole.PRACTITIONER)) {
53
- const practitionerProfile = await getPractitionerProfileByUserRef(
54
- db,
55
- requesterId
56
- );
57
- if (
58
- practitionerProfile &&
59
- patientData.doctorIds?.includes(practitionerProfile.id)
60
- ) {
43
+ const practitionerProfile = await getPractitionerProfileByUserRef(db, requesterId);
44
+ if (practitionerProfile && patientData.doctorIds?.includes(practitionerProfile.id)) {
61
45
  return;
62
46
  }
63
47
  }
@@ -66,8 +50,8 @@ const checkSensitiveAccessUtil = async (
66
50
  if (requesterRoles.includes(UserRole.CLINIC_ADMIN)) {
67
51
  const adminProfile = await getClinicAdminByUserRef(db, requesterId);
68
52
  if (adminProfile && adminProfile.clinicsManaged) {
69
- const hasAccess = adminProfile.clinicsManaged.some((managedClinicId) =>
70
- patientData.clinicIds?.includes(managedClinicId)
53
+ const hasAccess = adminProfile.clinicsManaged.some(managedClinicId =>
54
+ patientData.clinicIds?.includes(managedClinicId),
71
55
  );
72
56
  if (hasAccess) {
73
57
  return;
@@ -76,9 +60,9 @@ const checkSensitiveAccessUtil = async (
76
60
  }
77
61
 
78
62
  throw new AuthError(
79
- "Unauthorized access to sensitive information.",
80
- "AUTH/UNAUTHORIZED_ACCESS",
81
- 403
63
+ 'Unauthorized access to sensitive information.',
64
+ 'AUTH/UNAUTHORIZED_ACCESS',
65
+ 403,
82
66
  );
83
67
  };
84
68
 
@@ -92,14 +76,14 @@ const checkSensitiveAccessUtil = async (
92
76
  const handlePhotoUrlUpload = async (
93
77
  photoUrl: MediaResource | undefined | null,
94
78
  patientId: string,
95
- mediaService: MediaService
79
+ mediaService: MediaService,
96
80
  ): Promise<string | null> => {
97
81
  if (!photoUrl) {
98
82
  return null;
99
83
  }
100
84
 
101
85
  // If it's already a URL string, return it as is
102
- if (typeof photoUrl === "string") {
86
+ if (typeof photoUrl === 'string') {
103
87
  return photoUrl;
104
88
  }
105
89
 
@@ -109,8 +93,8 @@ const handlePhotoUrlUpload = async (
109
93
  photoUrl,
110
94
  patientId, // Using patientId as ownerId
111
95
  MediaAccessLevel.PRIVATE, // Sensitive info should be private
112
- "patient_sensitive_photos",
113
- photoUrl instanceof File ? photoUrl.name : `sensitive_photo_${patientId}`
96
+ 'patient_sensitive_photos',
97
+ photoUrl instanceof File ? photoUrl.name : `sensitive_photo_${patientId}`,
114
98
  );
115
99
  return mediaMetadata.url;
116
100
  }
@@ -124,25 +108,18 @@ export const createSensitiveInfoUtil = async (
124
108
  data: CreatePatientSensitiveInfoData,
125
109
  requesterId: string,
126
110
  requesterRoles: UserRole[],
127
- mediaService?: MediaService
111
+ mediaService?: MediaService,
128
112
  ): Promise<PatientSensitiveInfo> => {
129
113
  try {
130
114
  // Security check
131
- await checkSensitiveAccessUtil(
132
- db,
133
- data.patientId,
134
- requesterId,
135
- requesterRoles
136
- );
115
+ await checkSensitiveAccessUtil(db, data.patientId, requesterId, requesterRoles);
137
116
 
138
117
  const validatedData = createPatientSensitiveInfoSchema.parse(data);
139
118
 
140
119
  // Proveriti da li dokument već postoji
141
- const sensitiveDoc = await getDoc(
142
- getSensitiveInfoDocRef(db, data.patientId)
143
- );
120
+ const sensitiveDoc = await getDoc(getSensitiveInfoDocRef(db, data.patientId));
144
121
  if (sensitiveDoc.exists()) {
145
- throw new Error("Sensitive information already exists for this patient");
122
+ throw new Error('Sensitive information already exists for this patient');
146
123
  }
147
124
 
148
125
  // Process photoUrl if it's a MediaResource and mediaService is provided
@@ -151,9 +128,9 @@ export const createSensitiveInfoUtil = async (
151
128
  processedPhotoUrl = await handlePhotoUrlUpload(
152
129
  validatedData.photoUrl,
153
130
  data.patientId,
154
- mediaService
131
+ mediaService,
155
132
  );
156
- } else if (typeof validatedData.photoUrl === "string") {
133
+ } else if (typeof validatedData.photoUrl === 'string') {
157
134
  processedPhotoUrl = validatedData.photoUrl;
158
135
  }
159
136
 
@@ -168,13 +145,13 @@ export const createSensitiveInfoUtil = async (
168
145
 
169
146
  const createdDoc = await getDoc(getSensitiveInfoDocRef(db, data.patientId));
170
147
  if (!createdDoc.exists()) {
171
- throw new Error("Failed to create sensitive information");
148
+ throw new Error('Failed to create sensitive information');
172
149
  }
173
150
 
174
151
  return createdDoc.data() as PatientSensitiveInfo;
175
152
  } catch (error) {
176
153
  if (error instanceof z.ZodError) {
177
- throw new Error("Invalid sensitive info data: " + error.message);
154
+ throw new Error('Invalid sensitive info data: ' + error.message);
178
155
  }
179
156
  throw error;
180
157
  }
@@ -184,7 +161,7 @@ export const getSensitiveInfoUtil = async (
184
161
  db: Firestore,
185
162
  patientId: string,
186
163
  requesterId: string,
187
- requesterRoles: UserRole[]
164
+ requesterRoles: UserRole[],
188
165
  ): Promise<PatientSensitiveInfo | null> => {
189
166
  // Security check
190
167
  await checkSensitiveAccessUtil(db, patientId, requesterId, requesterRoles);
@@ -193,9 +170,7 @@ export const getSensitiveInfoUtil = async (
193
170
  await initSensitiveInfoDocIfNotExists(db, patientId, requesterId);
194
171
 
195
172
  const sensitiveDoc = await getDoc(getSensitiveInfoDocRef(db, patientId));
196
- return sensitiveDoc.exists()
197
- ? (sensitiveDoc.data() as PatientSensitiveInfo)
198
- : null;
173
+ return sensitiveDoc.exists() ? (sensitiveDoc.data() as PatientSensitiveInfo) : null;
199
174
  };
200
175
 
201
176
  export const updateSensitiveInfoUtil = async (
@@ -204,7 +179,7 @@ export const updateSensitiveInfoUtil = async (
204
179
  data: UpdatePatientSensitiveInfoData,
205
180
  requesterId: string,
206
181
  requesterRoles: UserRole[],
207
- mediaService?: MediaService
182
+ mediaService?: MediaService,
208
183
  ): Promise<PatientSensitiveInfo> => {
209
184
  // Security check
210
185
  await checkSensitiveAccessUtil(db, patientId, requesterId, requesterRoles);
@@ -216,16 +191,12 @@ export const updateSensitiveInfoUtil = async (
216
191
  let processedPhotoUrl: string | null | undefined = undefined;
217
192
  if (data.photoUrl !== undefined) {
218
193
  if (mediaService) {
219
- processedPhotoUrl = await handlePhotoUrlUpload(
220
- data.photoUrl,
221
- patientId,
222
- mediaService
223
- );
224
- } else if (typeof data.photoUrl === "string" || data.photoUrl === null) {
194
+ processedPhotoUrl = await handlePhotoUrlUpload(data.photoUrl, patientId, mediaService);
195
+ } else if (typeof data.photoUrl === 'string' || data.photoUrl === null) {
225
196
  processedPhotoUrl = data.photoUrl;
226
197
  } else {
227
198
  // If photoUrl is a File/Blob but no mediaService provided, throw error
228
- throw new Error("MediaService required to process photo upload");
199
+ throw new Error('MediaService required to process photo upload');
229
200
  }
230
201
  }
231
202
 
@@ -239,7 +210,50 @@ export const updateSensitiveInfoUtil = async (
239
210
 
240
211
  const updatedDoc = await getDoc(getSensitiveInfoDocRef(db, patientId));
241
212
  if (!updatedDoc.exists()) {
242
- throw new Error("Failed to retrieve updated sensitive information");
213
+ throw new Error('Failed to retrieve updated sensitive information');
214
+ }
215
+
216
+ return updatedDoc.data() as PatientSensitiveInfo;
217
+ };
218
+
219
+ export const claimPatientSensitiveInfoUtil = async (
220
+ db: Firestore,
221
+ patientId: string,
222
+ userId: string,
223
+ ): Promise<PatientSensitiveInfo> => {
224
+ const patientDoc = await getDoc(getPatientDocRef(db, patientId));
225
+ if (!patientDoc.exists()) {
226
+ throw new Error('Patient profile not found');
227
+ }
228
+
229
+ const patientData = patientDoc.data() as any;
230
+
231
+ if (!patientData.isManual) {
232
+ throw new Error('Only manually created patient profiles can be claimed');
233
+ }
234
+
235
+ if (patientData.userRef) {
236
+ throw new Error('Patient profile has already been claimed');
237
+ }
238
+
239
+ const sensitiveDoc = await getDoc(getSensitiveInfoDocRef(db, patientId));
240
+ if (!sensitiveDoc.exists()) {
241
+ throw new Error('Patient sensitive information not found');
242
+ }
243
+
244
+ const sensitiveData = sensitiveDoc.data() as PatientSensitiveInfo;
245
+ if (sensitiveData.userRef) {
246
+ throw new Error('Patient sensitive information has already been claimed');
247
+ }
248
+
249
+ await updateDoc(getSensitiveInfoDocRef(db, patientId), {
250
+ userRef: userId,
251
+ updatedAt: serverTimestamp(),
252
+ });
253
+
254
+ const updatedDoc = await getDoc(getSensitiveInfoDocRef(db, patientId));
255
+ if (!updatedDoc.exists()) {
256
+ throw new Error('Failed to retrieve updated sensitive information');
243
257
  }
244
258
 
245
259
  return updatedDoc.data() as PatientSensitiveInfo;
@@ -178,16 +178,22 @@ export class UserService extends BaseService {
178
178
  throw new Error('User already has a patient profile.');
179
179
  }
180
180
 
181
- // Claim the profile: link userRef, set isManual to false, and update displayName
182
- // Get sensitive info to construct full display name
183
- const sensitiveInfo = await patientService.getSensitiveInfo(patientProfile.id, userId);
181
+ // Claim sensitive info first (this adds userRef to sensitive info)
182
+ const sensitiveInfo = await patientService.claimPatientSensitiveInfo(
183
+ patientProfile.id,
184
+ userId,
185
+ );
186
+
187
+ // Construct full display name
184
188
  const fullDisplayName = sensitiveInfo
185
189
  ? `${sensitiveInfo.firstName} ${sensitiveInfo.lastName}`
186
190
  : patientProfile.displayName;
187
191
 
192
+ // Update patient profile: link userRef, set isManual to false, and update displayName
188
193
  await patientService.updatePatientProfile(patientProfile.id, {
189
194
  userRef: userId,
190
195
  isManual: false,
196
+ isVerified: true,
191
197
  displayName: fullDisplayName,
192
198
  });
193
199