@blackcode_sa/metaestetics-api 1.7.20 → 1.7.21
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/admin/index.d.mts +0 -1
- package/dist/admin/index.d.ts +0 -1
- package/dist/index.d.mts +39 -19
- package/dist/index.d.ts +39 -19
- package/dist/index.js +1116 -951
- package/dist/index.mjs +1132 -965
- package/package.json +1 -1
- package/src/services/patient/patient.service.ts +162 -10
- package/src/services/patient/utils/profile.utils.ts +124 -135
- package/src/services/patient/utils/sensitive.utils.ts +76 -2
- package/src/types/patient/index.ts +25 -23
- package/src/validations/patient.schema.ts +4 -4
package/dist/index.mjs
CHANGED
|
@@ -987,31 +987,348 @@ import { z as z16 } from "zod";
|
|
|
987
987
|
|
|
988
988
|
// src/services/patient/patient.service.ts
|
|
989
989
|
import {
|
|
990
|
-
doc as
|
|
991
|
-
getDoc as
|
|
992
|
-
writeBatch
|
|
990
|
+
doc as doc7,
|
|
991
|
+
getDoc as getDoc10,
|
|
992
|
+
writeBatch,
|
|
993
|
+
updateDoc as updateDoc7,
|
|
994
|
+
serverTimestamp as serverTimestamp7
|
|
993
995
|
} from "firebase/firestore";
|
|
994
996
|
|
|
997
|
+
// src/services/media/media.service.ts
|
|
998
|
+
import { Timestamp } from "firebase/firestore";
|
|
999
|
+
import {
|
|
1000
|
+
ref,
|
|
1001
|
+
uploadBytes,
|
|
1002
|
+
getDownloadURL,
|
|
1003
|
+
deleteObject,
|
|
1004
|
+
getBytes
|
|
1005
|
+
} from "firebase/storage";
|
|
1006
|
+
import {
|
|
1007
|
+
doc,
|
|
1008
|
+
getDoc,
|
|
1009
|
+
setDoc,
|
|
1010
|
+
updateDoc,
|
|
1011
|
+
collection,
|
|
1012
|
+
query,
|
|
1013
|
+
where,
|
|
1014
|
+
limit,
|
|
1015
|
+
getDocs,
|
|
1016
|
+
deleteDoc,
|
|
1017
|
+
orderBy
|
|
1018
|
+
} from "firebase/firestore";
|
|
1019
|
+
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
1020
|
+
MediaAccessLevel2["PUBLIC"] = "public";
|
|
1021
|
+
MediaAccessLevel2["PRIVATE"] = "private";
|
|
1022
|
+
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
1023
|
+
return MediaAccessLevel2;
|
|
1024
|
+
})(MediaAccessLevel || {});
|
|
1025
|
+
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
1026
|
+
var MediaService = class extends BaseService {
|
|
1027
|
+
constructor(db, auth, app) {
|
|
1028
|
+
super(db, auth, app);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
1032
|
+
* @param file - The file to upload.
|
|
1033
|
+
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
1034
|
+
* @param accessLevel - Access level (public, private, confidential).
|
|
1035
|
+
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
1036
|
+
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
1037
|
+
* @returns Promise with the media metadata.
|
|
1038
|
+
*/
|
|
1039
|
+
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
1040
|
+
const mediaId = this.generateId();
|
|
1041
|
+
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
1042
|
+
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
1043
|
+
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
1044
|
+
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
1045
|
+
const storageRef = ref(this.storage, filePath);
|
|
1046
|
+
try {
|
|
1047
|
+
const uploadResult = await uploadBytes(storageRef, file, {
|
|
1048
|
+
contentType: file.type
|
|
1049
|
+
});
|
|
1050
|
+
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
1051
|
+
const downloadURL = await getDownloadURL(uploadResult.ref);
|
|
1052
|
+
console.log("[MediaService] Got download URL:", downloadURL);
|
|
1053
|
+
const metadata = {
|
|
1054
|
+
id: mediaId,
|
|
1055
|
+
name: fileNameToUse,
|
|
1056
|
+
url: downloadURL,
|
|
1057
|
+
contentType: file.type,
|
|
1058
|
+
size: file.size,
|
|
1059
|
+
createdAt: Timestamp.now(),
|
|
1060
|
+
accessLevel,
|
|
1061
|
+
ownerId,
|
|
1062
|
+
collectionName,
|
|
1063
|
+
path: filePath
|
|
1064
|
+
};
|
|
1065
|
+
const metadataDocRef = doc(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1066
|
+
await setDoc(metadataDocRef, metadata);
|
|
1067
|
+
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
1068
|
+
return metadata;
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
console.error("[MediaService] Error during media upload:", error);
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get media metadata from Firestore by its ID.
|
|
1076
|
+
* @param mediaId - ID of the media.
|
|
1077
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1078
|
+
*/
|
|
1079
|
+
async getMediaMetadata(mediaId) {
|
|
1080
|
+
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
1081
|
+
const docRef = doc(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1082
|
+
const docSnap = await getDoc(docRef);
|
|
1083
|
+
if (docSnap.exists()) {
|
|
1084
|
+
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
1085
|
+
return docSnap.data();
|
|
1086
|
+
}
|
|
1087
|
+
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Get media metadata from Firestore by its public URL.
|
|
1092
|
+
* @param url - The public URL of the media file.
|
|
1093
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1094
|
+
*/
|
|
1095
|
+
async getMediaMetadataByUrl(url) {
|
|
1096
|
+
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
1097
|
+
const q = query(
|
|
1098
|
+
collection(this.db, MEDIA_METADATA_COLLECTION),
|
|
1099
|
+
where("url", "==", url),
|
|
1100
|
+
limit(1)
|
|
1101
|
+
);
|
|
1102
|
+
try {
|
|
1103
|
+
const querySnapshot = await getDocs(q);
|
|
1104
|
+
if (!querySnapshot.empty) {
|
|
1105
|
+
const metadata = querySnapshot.docs[0].data();
|
|
1106
|
+
console.log("[MediaService] Metadata found by URL:", metadata);
|
|
1107
|
+
return metadata;
|
|
1108
|
+
}
|
|
1109
|
+
console.log("[MediaService] No metadata found for URL:", url);
|
|
1110
|
+
return null;
|
|
1111
|
+
} catch (error) {
|
|
1112
|
+
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
1113
|
+
throw error;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Delete media from storage and remove metadata from Firestore.
|
|
1118
|
+
* @param mediaId - ID of the media to delete.
|
|
1119
|
+
*/
|
|
1120
|
+
async deleteMedia(mediaId) {
|
|
1121
|
+
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
1122
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1123
|
+
if (!metadata) {
|
|
1124
|
+
console.warn(
|
|
1125
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
1126
|
+
);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const storageFileRef = ref(this.storage, metadata.path);
|
|
1130
|
+
try {
|
|
1131
|
+
await deleteObject(storageFileRef);
|
|
1132
|
+
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
1133
|
+
const metadataDocRef = doc(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1134
|
+
await deleteDoc(metadataDocRef);
|
|
1135
|
+
console.log(
|
|
1136
|
+
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
1137
|
+
);
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
1140
|
+
throw error;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Update media access level. This involves moving the file in Firebase Storage
|
|
1145
|
+
* to a new path reflecting the new access level, and updating its metadata.
|
|
1146
|
+
* @param mediaId - ID of the media to update.
|
|
1147
|
+
* @param newAccessLevel - New access level.
|
|
1148
|
+
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
1149
|
+
*/
|
|
1150
|
+
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
1151
|
+
var _a;
|
|
1152
|
+
console.log(
|
|
1153
|
+
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
1154
|
+
);
|
|
1155
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1156
|
+
if (!metadata) {
|
|
1157
|
+
console.warn(
|
|
1158
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
1159
|
+
);
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
if (metadata.accessLevel === newAccessLevel) {
|
|
1163
|
+
console.log(
|
|
1164
|
+
`[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
|
|
1165
|
+
);
|
|
1166
|
+
const metadataDocRef = doc(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1167
|
+
try {
|
|
1168
|
+
await updateDoc(metadataDocRef, { updatedAt: Timestamp.now() });
|
|
1169
|
+
return { ...metadata, updatedAt: Timestamp.now() };
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.error(
|
|
1172
|
+
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
1173
|
+
error
|
|
1174
|
+
);
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const oldStoragePath = metadata.path;
|
|
1179
|
+
const fileNamePart = `${metadata.id}-${metadata.name}`;
|
|
1180
|
+
const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
|
|
1181
|
+
console.log(
|
|
1182
|
+
`[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
|
|
1183
|
+
);
|
|
1184
|
+
const oldStorageFileRef = ref(this.storage, oldStoragePath);
|
|
1185
|
+
const newStorageFileRef = ref(this.storage, newStoragePath);
|
|
1186
|
+
try {
|
|
1187
|
+
console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
|
|
1188
|
+
const fileBytes = await getBytes(oldStorageFileRef);
|
|
1189
|
+
console.log(
|
|
1190
|
+
`[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
|
|
1191
|
+
);
|
|
1192
|
+
console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
|
|
1193
|
+
await uploadBytes(newStorageFileRef, fileBytes, {
|
|
1194
|
+
contentType: metadata.contentType
|
|
1195
|
+
});
|
|
1196
|
+
console.log(
|
|
1197
|
+
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
1198
|
+
);
|
|
1199
|
+
const newDownloadURL = await getDownloadURL(newStorageFileRef);
|
|
1200
|
+
console.log(
|
|
1201
|
+
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
1202
|
+
);
|
|
1203
|
+
const updateData = {
|
|
1204
|
+
accessLevel: newAccessLevel,
|
|
1205
|
+
path: newStoragePath,
|
|
1206
|
+
url: newDownloadURL,
|
|
1207
|
+
updatedAt: Timestamp.now()
|
|
1208
|
+
};
|
|
1209
|
+
const metadataDocRef = doc(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1210
|
+
console.log(
|
|
1211
|
+
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
1212
|
+
updateData
|
|
1213
|
+
);
|
|
1214
|
+
await updateDoc(metadataDocRef, updateData);
|
|
1215
|
+
console.log(
|
|
1216
|
+
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
1217
|
+
);
|
|
1218
|
+
try {
|
|
1219
|
+
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
1220
|
+
await deleteObject(oldStorageFileRef);
|
|
1221
|
+
console.log(
|
|
1222
|
+
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
1223
|
+
);
|
|
1224
|
+
} catch (deleteError) {
|
|
1225
|
+
console.error(
|
|
1226
|
+
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
1227
|
+
deleteError
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
return { ...metadata, ...updateData };
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
console.error(
|
|
1233
|
+
`[MediaService] Error updating media access level and moving file for ${mediaId}:`,
|
|
1234
|
+
error
|
|
1235
|
+
);
|
|
1236
|
+
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
1237
|
+
console.warn(
|
|
1238
|
+
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
1239
|
+
);
|
|
1240
|
+
try {
|
|
1241
|
+
await deleteObject(newStorageFileRef);
|
|
1242
|
+
console.warn(
|
|
1243
|
+
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
1244
|
+
);
|
|
1245
|
+
} catch (cleanupError) {
|
|
1246
|
+
console.error(
|
|
1247
|
+
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
1248
|
+
cleanupError
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* List all media for an owner, optionally filtered by collection and access level.
|
|
1257
|
+
* @param ownerId - ID of the owner.
|
|
1258
|
+
* @param collectionName - Optional: Filter by collection name.
|
|
1259
|
+
* @param accessLevel - Optional: Filter by access level.
|
|
1260
|
+
* @param count - Optional: Number of items to fetch.
|
|
1261
|
+
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
1262
|
+
*/
|
|
1263
|
+
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
1264
|
+
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
1265
|
+
let qConstraints = [where("ownerId", "==", ownerId)];
|
|
1266
|
+
if (collectionName) {
|
|
1267
|
+
qConstraints.push(where("collectionName", "==", collectionName));
|
|
1268
|
+
}
|
|
1269
|
+
if (accessLevel) {
|
|
1270
|
+
qConstraints.push(where("accessLevel", "==", accessLevel));
|
|
1271
|
+
}
|
|
1272
|
+
qConstraints.push(orderBy("createdAt", "desc"));
|
|
1273
|
+
if (count) {
|
|
1274
|
+
qConstraints.push(limit(count));
|
|
1275
|
+
}
|
|
1276
|
+
if (startAfterId) {
|
|
1277
|
+
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
1278
|
+
if (startAfterDoc) {
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const finalQuery = query(
|
|
1282
|
+
collection(this.db, MEDIA_METADATA_COLLECTION),
|
|
1283
|
+
...qConstraints
|
|
1284
|
+
);
|
|
1285
|
+
try {
|
|
1286
|
+
const querySnapshot = await getDocs(finalQuery);
|
|
1287
|
+
const mediaList = querySnapshot.docs.map(
|
|
1288
|
+
(doc34) => doc34.data()
|
|
1289
|
+
);
|
|
1290
|
+
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
1291
|
+
return mediaList;
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
console.error("[MediaService] Error listing media:", error);
|
|
1294
|
+
throw error;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Get download URL for media. (Convenience, as URL is in metadata)
|
|
1299
|
+
* @param mediaId - ID of the media.
|
|
1300
|
+
*/
|
|
1301
|
+
async getMediaDownloadUrl(mediaId) {
|
|
1302
|
+
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
1303
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1304
|
+
if (metadata && metadata.url) {
|
|
1305
|
+
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
1306
|
+
return metadata.url;
|
|
1307
|
+
}
|
|
1308
|
+
console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
|
|
995
1313
|
// src/services/patient/utils/profile.utils.ts
|
|
996
1314
|
import {
|
|
997
|
-
getDoc as
|
|
998
|
-
setDoc as
|
|
999
|
-
updateDoc as
|
|
1315
|
+
getDoc as getDoc6,
|
|
1316
|
+
setDoc as setDoc5,
|
|
1317
|
+
updateDoc as updateDoc4,
|
|
1000
1318
|
arrayUnion as arrayUnion2,
|
|
1001
1319
|
arrayRemove as arrayRemove2,
|
|
1002
1320
|
serverTimestamp as serverTimestamp4,
|
|
1003
1321
|
increment,
|
|
1004
|
-
Timestamp as
|
|
1005
|
-
collection as
|
|
1006
|
-
query as
|
|
1007
|
-
where as
|
|
1008
|
-
getDocs as
|
|
1009
|
-
limit as
|
|
1322
|
+
Timestamp as Timestamp5,
|
|
1323
|
+
collection as collection4,
|
|
1324
|
+
query as query4,
|
|
1325
|
+
where as where4,
|
|
1326
|
+
getDocs as getDocs4,
|
|
1327
|
+
limit as limit3,
|
|
1010
1328
|
startAfter as startAfter2,
|
|
1011
|
-
doc as
|
|
1329
|
+
doc as doc5
|
|
1012
1330
|
} from "firebase/firestore";
|
|
1013
|
-
import {
|
|
1014
|
-
import { z as z8 } from "zod";
|
|
1331
|
+
import { z as z9 } from "zod";
|
|
1015
1332
|
|
|
1016
1333
|
// src/types/patient/medical-info.types.ts
|
|
1017
1334
|
var PATIENT_MEDICAL_INFO_COLLECTION = "medical_info";
|
|
@@ -1040,11 +1357,19 @@ var Gender = /* @__PURE__ */ ((Gender2) => {
|
|
|
1040
1357
|
})(Gender || {});
|
|
1041
1358
|
|
|
1042
1359
|
// src/validations/patient.schema.ts
|
|
1043
|
-
import { z as
|
|
1044
|
-
import { Timestamp as
|
|
1360
|
+
import { z as z7 } from "zod";
|
|
1361
|
+
import { Timestamp as Timestamp3 } from "firebase/firestore";
|
|
1362
|
+
|
|
1363
|
+
// src/validations/media.schema.ts
|
|
1364
|
+
import { z as z4 } from "zod";
|
|
1365
|
+
var mediaResourceSchema = z4.union([
|
|
1366
|
+
z4.string().url(),
|
|
1367
|
+
z4.instanceof(File),
|
|
1368
|
+
z4.instanceof(Blob)
|
|
1369
|
+
]);
|
|
1045
1370
|
|
|
1046
1371
|
// src/validations/patient/medical-info.schema.ts
|
|
1047
|
-
import { z as
|
|
1372
|
+
import { z as z6 } from "zod";
|
|
1048
1373
|
|
|
1049
1374
|
// src/types/patient/allergies.ts
|
|
1050
1375
|
var AllergyType = /* @__PURE__ */ ((AllergyType2) => {
|
|
@@ -1133,80 +1458,80 @@ var Contraindication = /* @__PURE__ */ ((Contraindication2) => {
|
|
|
1133
1458
|
})(Contraindication || {});
|
|
1134
1459
|
|
|
1135
1460
|
// src/validations/common.schema.ts
|
|
1136
|
-
import { z as
|
|
1137
|
-
import { Timestamp } from "firebase/firestore";
|
|
1138
|
-
var timestampSchema2 =
|
|
1139
|
-
|
|
1140
|
-
seconds:
|
|
1141
|
-
nanoseconds:
|
|
1461
|
+
import { z as z5 } from "zod";
|
|
1462
|
+
import { Timestamp as Timestamp2 } from "firebase/firestore";
|
|
1463
|
+
var timestampSchema2 = z5.union([
|
|
1464
|
+
z5.object({
|
|
1465
|
+
seconds: z5.number(),
|
|
1466
|
+
nanoseconds: z5.number()
|
|
1142
1467
|
}),
|
|
1143
|
-
|
|
1468
|
+
z5.instanceof(Timestamp2)
|
|
1144
1469
|
]).transform((data) => {
|
|
1145
|
-
if (data instanceof
|
|
1470
|
+
if (data instanceof Timestamp2) {
|
|
1146
1471
|
return data;
|
|
1147
1472
|
}
|
|
1148
|
-
return new
|
|
1473
|
+
return new Timestamp2(data.seconds, data.nanoseconds);
|
|
1149
1474
|
});
|
|
1150
1475
|
|
|
1151
1476
|
// src/validations/patient/medical-info.schema.ts
|
|
1152
|
-
var allergySubtypeSchema =
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1477
|
+
var allergySubtypeSchema = z6.union([
|
|
1478
|
+
z6.nativeEnum(MedicationAllergySubtype),
|
|
1479
|
+
z6.nativeEnum(FoodAllergySubtype),
|
|
1480
|
+
z6.nativeEnum(EnvironmentalAllergySubtype),
|
|
1481
|
+
z6.nativeEnum(CosmeticAllergySubtype),
|
|
1482
|
+
z6.literal("other")
|
|
1158
1483
|
]);
|
|
1159
|
-
var allergySchema =
|
|
1160
|
-
type:
|
|
1484
|
+
var allergySchema = z6.object({
|
|
1485
|
+
type: z6.nativeEnum(AllergyType),
|
|
1161
1486
|
subtype: allergySubtypeSchema,
|
|
1162
|
-
name:
|
|
1163
|
-
severity:
|
|
1164
|
-
reaction:
|
|
1487
|
+
name: z6.string().optional().nullable(),
|
|
1488
|
+
severity: z6.enum(["mild", "moderate", "severe"]).optional(),
|
|
1489
|
+
reaction: z6.string().optional().nullable(),
|
|
1165
1490
|
diagnosed: timestampSchema2.optional().nullable(),
|
|
1166
|
-
notes:
|
|
1491
|
+
notes: z6.string().optional().nullable()
|
|
1167
1492
|
});
|
|
1168
|
-
var vitalStatsSchema =
|
|
1169
|
-
height:
|
|
1170
|
-
weight:
|
|
1171
|
-
bloodType:
|
|
1172
|
-
bloodPressure:
|
|
1173
|
-
systolic:
|
|
1174
|
-
diastolic:
|
|
1493
|
+
var vitalStatsSchema = z6.object({
|
|
1494
|
+
height: z6.number().positive().optional(),
|
|
1495
|
+
weight: z6.number().positive().optional(),
|
|
1496
|
+
bloodType: z6.enum(["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]).optional(),
|
|
1497
|
+
bloodPressure: z6.object({
|
|
1498
|
+
systolic: z6.number().min(70).max(200),
|
|
1499
|
+
diastolic: z6.number().min(40).max(130),
|
|
1175
1500
|
lastMeasured: timestampSchema2
|
|
1176
1501
|
}).optional()
|
|
1177
1502
|
});
|
|
1178
|
-
var blockingConditionSchema =
|
|
1179
|
-
condition:
|
|
1503
|
+
var blockingConditionSchema = z6.object({
|
|
1504
|
+
condition: z6.nativeEnum(BlockingCondition),
|
|
1180
1505
|
diagnosedAt: timestampSchema2,
|
|
1181
|
-
notes:
|
|
1182
|
-
isActive:
|
|
1506
|
+
notes: z6.string().optional().nullable(),
|
|
1507
|
+
isActive: z6.boolean()
|
|
1183
1508
|
});
|
|
1184
|
-
var contraindicationSchema =
|
|
1185
|
-
condition:
|
|
1509
|
+
var contraindicationSchema = z6.object({
|
|
1510
|
+
condition: z6.nativeEnum(Contraindication),
|
|
1186
1511
|
lastOccurrence: timestampSchema2,
|
|
1187
|
-
frequency:
|
|
1188
|
-
notes:
|
|
1189
|
-
isActive:
|
|
1512
|
+
frequency: z6.enum(["rare", "occasional", "frequent"]),
|
|
1513
|
+
notes: z6.string().optional().nullable(),
|
|
1514
|
+
isActive: z6.boolean()
|
|
1190
1515
|
});
|
|
1191
|
-
var medicationSchema =
|
|
1192
|
-
name:
|
|
1193
|
-
dosage:
|
|
1194
|
-
frequency:
|
|
1516
|
+
var medicationSchema = z6.object({
|
|
1517
|
+
name: z6.string().min(1),
|
|
1518
|
+
dosage: z6.string().min(1),
|
|
1519
|
+
frequency: z6.string().min(1),
|
|
1195
1520
|
startDate: timestampSchema2.optional().nullable(),
|
|
1196
1521
|
endDate: timestampSchema2.optional().nullable(),
|
|
1197
|
-
prescribedBy:
|
|
1522
|
+
prescribedBy: z6.string().optional().nullable()
|
|
1198
1523
|
});
|
|
1199
|
-
var patientMedicalInfoSchema =
|
|
1200
|
-
patientId:
|
|
1524
|
+
var patientMedicalInfoSchema = z6.object({
|
|
1525
|
+
patientId: z6.string(),
|
|
1201
1526
|
vitalStats: vitalStatsSchema,
|
|
1202
|
-
blockingConditions:
|
|
1203
|
-
contraindications:
|
|
1204
|
-
allergies:
|
|
1205
|
-
currentMedications:
|
|
1206
|
-
emergencyNotes:
|
|
1527
|
+
blockingConditions: z6.array(blockingConditionSchema),
|
|
1528
|
+
contraindications: z6.array(contraindicationSchema),
|
|
1529
|
+
allergies: z6.array(allergySchema),
|
|
1530
|
+
currentMedications: z6.array(medicationSchema),
|
|
1531
|
+
emergencyNotes: z6.string().optional(),
|
|
1207
1532
|
lastUpdated: timestampSchema2,
|
|
1208
|
-
updatedBy:
|
|
1209
|
-
verifiedBy:
|
|
1533
|
+
updatedBy: z6.string(),
|
|
1534
|
+
verifiedBy: z6.string().optional(),
|
|
1210
1535
|
verifiedAt: timestampSchema2.optional()
|
|
1211
1536
|
});
|
|
1212
1537
|
var createPatientMedicalInfoSchema = patientMedicalInfoSchema.omit({
|
|
@@ -1220,141 +1545,140 @@ var updatePatientMedicalInfoSchema = createPatientMedicalInfoSchema.partial();
|
|
|
1220
1545
|
var updateVitalStatsSchema = vitalStatsSchema;
|
|
1221
1546
|
var addAllergySchema = allergySchema;
|
|
1222
1547
|
var updateAllergySchema = allergySchema.partial().extend({
|
|
1223
|
-
allergyIndex:
|
|
1548
|
+
allergyIndex: z6.number().min(0)
|
|
1224
1549
|
});
|
|
1225
1550
|
var addBlockingConditionSchema = blockingConditionSchema;
|
|
1226
1551
|
var updateBlockingConditionSchema = blockingConditionSchema.partial().extend({
|
|
1227
|
-
conditionIndex:
|
|
1552
|
+
conditionIndex: z6.number().min(0)
|
|
1228
1553
|
});
|
|
1229
1554
|
var addContraindicationSchema = contraindicationSchema;
|
|
1230
1555
|
var updateContraindicationSchema = contraindicationSchema.partial().extend({
|
|
1231
|
-
contraindicationIndex:
|
|
1556
|
+
contraindicationIndex: z6.number().min(0)
|
|
1232
1557
|
});
|
|
1233
1558
|
var addMedicationSchema = medicationSchema;
|
|
1234
1559
|
var updateMedicationSchema = medicationSchema.partial().extend({
|
|
1235
|
-
medicationIndex:
|
|
1560
|
+
medicationIndex: z6.number().min(0)
|
|
1236
1561
|
});
|
|
1237
1562
|
|
|
1238
1563
|
// src/validations/patient.schema.ts
|
|
1239
|
-
var locationDataSchema =
|
|
1240
|
-
latitude:
|
|
1241
|
-
longitude:
|
|
1242
|
-
geohash:
|
|
1564
|
+
var locationDataSchema = z7.object({
|
|
1565
|
+
latitude: z7.number().min(-90).max(90),
|
|
1566
|
+
longitude: z7.number().min(-180).max(180),
|
|
1567
|
+
geohash: z7.string().optional()
|
|
1243
1568
|
});
|
|
1244
|
-
var addressDataSchema =
|
|
1245
|
-
address:
|
|
1246
|
-
city:
|
|
1247
|
-
country:
|
|
1248
|
-
postalCode:
|
|
1569
|
+
var addressDataSchema = z7.object({
|
|
1570
|
+
address: z7.string(),
|
|
1571
|
+
city: z7.string(),
|
|
1572
|
+
country: z7.string(),
|
|
1573
|
+
postalCode: z7.string()
|
|
1249
1574
|
});
|
|
1250
|
-
var emergencyContactSchema =
|
|
1251
|
-
name:
|
|
1252
|
-
relationship:
|
|
1253
|
-
phoneNumber:
|
|
1254
|
-
isNotifiable:
|
|
1575
|
+
var emergencyContactSchema = z7.object({
|
|
1576
|
+
name: z7.string(),
|
|
1577
|
+
relationship: z7.string(),
|
|
1578
|
+
phoneNumber: z7.string(),
|
|
1579
|
+
isNotifiable: z7.boolean()
|
|
1255
1580
|
});
|
|
1256
|
-
var gamificationSchema =
|
|
1257
|
-
level:
|
|
1258
|
-
points:
|
|
1581
|
+
var gamificationSchema = z7.object({
|
|
1582
|
+
level: z7.number(),
|
|
1583
|
+
points: z7.number()
|
|
1259
1584
|
});
|
|
1260
|
-
var patientLocationInfoSchema =
|
|
1261
|
-
patientId:
|
|
1262
|
-
userRef:
|
|
1585
|
+
var patientLocationInfoSchema = z7.object({
|
|
1586
|
+
patientId: z7.string(),
|
|
1587
|
+
userRef: z7.string(),
|
|
1263
1588
|
locationData: locationDataSchema,
|
|
1264
|
-
createdAt:
|
|
1265
|
-
updatedAt:
|
|
1589
|
+
createdAt: z7.instanceof(Timestamp3),
|
|
1590
|
+
updatedAt: z7.instanceof(Timestamp3)
|
|
1266
1591
|
});
|
|
1267
|
-
var createPatientLocationInfoSchema =
|
|
1268
|
-
patientId:
|
|
1269
|
-
userRef:
|
|
1592
|
+
var createPatientLocationInfoSchema = z7.object({
|
|
1593
|
+
patientId: z7.string(),
|
|
1594
|
+
userRef: z7.string(),
|
|
1270
1595
|
locationData: locationDataSchema
|
|
1271
1596
|
});
|
|
1272
|
-
var patientSensitiveInfoSchema =
|
|
1273
|
-
patientId:
|
|
1274
|
-
userRef:
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
alternativePhoneNumber: z6.string().optional(),
|
|
1597
|
+
var patientSensitiveInfoSchema = z7.object({
|
|
1598
|
+
patientId: z7.string(),
|
|
1599
|
+
userRef: z7.string(),
|
|
1600
|
+
firstName: z7.string().min(2),
|
|
1601
|
+
lastName: z7.string().min(2),
|
|
1602
|
+
dateOfBirth: z7.instanceof(Timestamp3).nullable(),
|
|
1603
|
+
gender: z7.nativeEnum(Gender),
|
|
1604
|
+
email: z7.string().email().optional(),
|
|
1605
|
+
phoneNumber: z7.string().optional(),
|
|
1606
|
+
alternativePhoneNumber: z7.string().optional(),
|
|
1283
1607
|
addressData: addressDataSchema.optional(),
|
|
1284
|
-
emergencyContacts:
|
|
1285
|
-
createdAt:
|
|
1286
|
-
updatedAt:
|
|
1608
|
+
emergencyContacts: z7.array(emergencyContactSchema).optional(),
|
|
1609
|
+
createdAt: z7.instanceof(Timestamp3),
|
|
1610
|
+
updatedAt: z7.instanceof(Timestamp3)
|
|
1287
1611
|
});
|
|
1288
|
-
var patientDoctorSchema =
|
|
1289
|
-
userRef:
|
|
1290
|
-
assignedAt:
|
|
1291
|
-
assignedBy:
|
|
1292
|
-
isActive:
|
|
1293
|
-
notes:
|
|
1612
|
+
var patientDoctorSchema = z7.object({
|
|
1613
|
+
userRef: z7.string(),
|
|
1614
|
+
assignedAt: z7.instanceof(Timestamp3),
|
|
1615
|
+
assignedBy: z7.string().optional(),
|
|
1616
|
+
isActive: z7.boolean(),
|
|
1617
|
+
notes: z7.string().optional()
|
|
1294
1618
|
});
|
|
1295
|
-
var patientClinicSchema =
|
|
1296
|
-
clinicId:
|
|
1297
|
-
assignedAt:
|
|
1298
|
-
assignedBy:
|
|
1299
|
-
isActive:
|
|
1300
|
-
notes:
|
|
1619
|
+
var patientClinicSchema = z7.object({
|
|
1620
|
+
clinicId: z7.string(),
|
|
1621
|
+
assignedAt: z7.instanceof(Timestamp3),
|
|
1622
|
+
assignedBy: z7.string().optional(),
|
|
1623
|
+
isActive: z7.boolean(),
|
|
1624
|
+
notes: z7.string().optional()
|
|
1301
1625
|
});
|
|
1302
|
-
var patientProfileSchema =
|
|
1303
|
-
id:
|
|
1304
|
-
userRef:
|
|
1305
|
-
displayName:
|
|
1306
|
-
profilePhoto: z6.string().url().nullable(),
|
|
1626
|
+
var patientProfileSchema = z7.object({
|
|
1627
|
+
id: z7.string(),
|
|
1628
|
+
userRef: z7.string(),
|
|
1629
|
+
displayName: z7.string(),
|
|
1307
1630
|
gamification: gamificationSchema,
|
|
1308
|
-
expoTokens:
|
|
1309
|
-
isActive:
|
|
1310
|
-
isVerified:
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1631
|
+
expoTokens: z7.array(z7.string()),
|
|
1632
|
+
isActive: z7.boolean(),
|
|
1633
|
+
isVerified: z7.boolean(),
|
|
1634
|
+
phoneNumber: z7.string().nullable().optional(),
|
|
1635
|
+
dateOfBirth: z7.instanceof(Timestamp3).nullable().optional(),
|
|
1636
|
+
doctors: z7.array(patientDoctorSchema),
|
|
1637
|
+
clinics: z7.array(patientClinicSchema),
|
|
1638
|
+
doctorIds: z7.array(z7.string()),
|
|
1639
|
+
clinicIds: z7.array(z7.string()),
|
|
1640
|
+
createdAt: z7.instanceof(Timestamp3),
|
|
1641
|
+
updatedAt: z7.instanceof(Timestamp3)
|
|
1317
1642
|
});
|
|
1318
|
-
var createPatientProfileSchema =
|
|
1319
|
-
userRef:
|
|
1320
|
-
displayName:
|
|
1321
|
-
|
|
1322
|
-
expoTokens: z6.array(z6.string()),
|
|
1643
|
+
var createPatientProfileSchema = z7.object({
|
|
1644
|
+
userRef: z7.string(),
|
|
1645
|
+
displayName: z7.string(),
|
|
1646
|
+
expoTokens: z7.array(z7.string()),
|
|
1323
1647
|
gamification: gamificationSchema.optional(),
|
|
1324
|
-
isActive:
|
|
1325
|
-
isVerified:
|
|
1326
|
-
doctors:
|
|
1327
|
-
clinics:
|
|
1328
|
-
doctorIds:
|
|
1329
|
-
clinicIds:
|
|
1648
|
+
isActive: z7.boolean(),
|
|
1649
|
+
isVerified: z7.boolean(),
|
|
1650
|
+
doctors: z7.array(patientDoctorSchema).optional(),
|
|
1651
|
+
clinics: z7.array(patientClinicSchema).optional(),
|
|
1652
|
+
doctorIds: z7.array(z7.string()).optional(),
|
|
1653
|
+
clinicIds: z7.array(z7.string()).optional()
|
|
1330
1654
|
});
|
|
1331
|
-
var createPatientSensitiveInfoSchema =
|
|
1332
|
-
patientId:
|
|
1333
|
-
userRef:
|
|
1334
|
-
photoUrl:
|
|
1335
|
-
firstName:
|
|
1336
|
-
lastName:
|
|
1337
|
-
dateOfBirth:
|
|
1338
|
-
gender:
|
|
1339
|
-
email:
|
|
1340
|
-
phoneNumber:
|
|
1341
|
-
alternativePhoneNumber:
|
|
1655
|
+
var createPatientSensitiveInfoSchema = z7.object({
|
|
1656
|
+
patientId: z7.string(),
|
|
1657
|
+
userRef: z7.string(),
|
|
1658
|
+
photoUrl: mediaResourceSchema.nullable().optional(),
|
|
1659
|
+
firstName: z7.string().min(2),
|
|
1660
|
+
lastName: z7.string().min(2),
|
|
1661
|
+
dateOfBirth: z7.instanceof(Timestamp3).nullable(),
|
|
1662
|
+
gender: z7.nativeEnum(Gender),
|
|
1663
|
+
email: z7.string().email().optional(),
|
|
1664
|
+
phoneNumber: z7.string().optional(),
|
|
1665
|
+
alternativePhoneNumber: z7.string().optional(),
|
|
1342
1666
|
addressData: addressDataSchema.optional(),
|
|
1343
|
-
emergencyContacts:
|
|
1667
|
+
emergencyContacts: z7.array(emergencyContactSchema).optional()
|
|
1344
1668
|
});
|
|
1345
|
-
var searchPatientsSchema =
|
|
1346
|
-
clinicId:
|
|
1347
|
-
practitionerId:
|
|
1669
|
+
var searchPatientsSchema = z7.object({
|
|
1670
|
+
clinicId: z7.string().optional(),
|
|
1671
|
+
practitionerId: z7.string().optional()
|
|
1348
1672
|
}).refine((data) => data.clinicId || data.practitionerId, {
|
|
1349
1673
|
message: "At least one of clinicId or practitionerId must be provided",
|
|
1350
1674
|
path: []
|
|
1351
1675
|
// Optional: specify a path like ['clinicId'] or ['practitionerId']
|
|
1352
1676
|
});
|
|
1353
|
-
var requesterInfoSchema =
|
|
1354
|
-
id:
|
|
1355
|
-
role:
|
|
1356
|
-
associatedClinicId:
|
|
1357
|
-
associatedPractitionerId:
|
|
1677
|
+
var requesterInfoSchema = z7.object({
|
|
1678
|
+
id: z7.string(),
|
|
1679
|
+
role: z7.enum(["clinic_admin", "practitioner"]),
|
|
1680
|
+
associatedClinicId: z7.string().optional(),
|
|
1681
|
+
associatedPractitionerId: z7.string().optional()
|
|
1358
1682
|
}).refine(
|
|
1359
1683
|
(data) => {
|
|
1360
1684
|
if (data.role === "clinic_admin") {
|
|
@@ -1372,47 +1696,79 @@ var requesterInfoSchema = z6.object({
|
|
|
1372
1696
|
|
|
1373
1697
|
// src/services/patient/utils/docs.utils.ts
|
|
1374
1698
|
import {
|
|
1375
|
-
doc,
|
|
1376
|
-
collection,
|
|
1377
|
-
query,
|
|
1378
|
-
where,
|
|
1379
|
-
getDocs,
|
|
1380
|
-
getDoc as
|
|
1699
|
+
doc as doc2,
|
|
1700
|
+
collection as collection2,
|
|
1701
|
+
query as query2,
|
|
1702
|
+
where as where2,
|
|
1703
|
+
getDocs as getDocs2,
|
|
1704
|
+
getDoc as getDoc3
|
|
1381
1705
|
} from "firebase/firestore";
|
|
1382
1706
|
|
|
1383
1707
|
// src/services/patient/utils/sensitive.utils.ts
|
|
1384
1708
|
import {
|
|
1385
|
-
getDoc,
|
|
1386
|
-
updateDoc,
|
|
1387
|
-
setDoc,
|
|
1709
|
+
getDoc as getDoc2,
|
|
1710
|
+
updateDoc as updateDoc2,
|
|
1711
|
+
setDoc as setDoc2,
|
|
1388
1712
|
serverTimestamp
|
|
1389
1713
|
} from "firebase/firestore";
|
|
1390
|
-
import { z as
|
|
1391
|
-
var
|
|
1714
|
+
import { z as z8 } from "zod";
|
|
1715
|
+
var handlePhotoUrlUpload = async (photoUrl, patientId, mediaService) => {
|
|
1716
|
+
if (!photoUrl) {
|
|
1717
|
+
return null;
|
|
1718
|
+
}
|
|
1719
|
+
if (typeof photoUrl === "string") {
|
|
1720
|
+
return photoUrl;
|
|
1721
|
+
}
|
|
1722
|
+
if (photoUrl instanceof File || photoUrl instanceof Blob) {
|
|
1723
|
+
const mediaMetadata = await mediaService.uploadMedia(
|
|
1724
|
+
photoUrl,
|
|
1725
|
+
patientId,
|
|
1726
|
+
// Using patientId as ownerId
|
|
1727
|
+
"private" /* PRIVATE */,
|
|
1728
|
+
// Sensitive info should be private
|
|
1729
|
+
"patient_sensitive_photos",
|
|
1730
|
+
photoUrl instanceof File ? photoUrl.name : `sensitive_photo_${patientId}`
|
|
1731
|
+
);
|
|
1732
|
+
return mediaMetadata.url;
|
|
1733
|
+
}
|
|
1734
|
+
return null;
|
|
1735
|
+
};
|
|
1736
|
+
var createSensitiveInfoUtil = async (db, data, requesterUserId, mediaService) => {
|
|
1392
1737
|
try {
|
|
1393
1738
|
if (data.userRef !== requesterUserId) {
|
|
1394
1739
|
throw new Error("Only patient can create their sensitive information");
|
|
1395
1740
|
}
|
|
1396
1741
|
const validatedData = createPatientSensitiveInfoSchema.parse(data);
|
|
1397
|
-
const sensitiveDoc = await
|
|
1742
|
+
const sensitiveDoc = await getDoc2(
|
|
1398
1743
|
getSensitiveInfoDocRef(db, data.patientId)
|
|
1399
1744
|
);
|
|
1400
1745
|
if (sensitiveDoc.exists()) {
|
|
1401
1746
|
throw new Error("Sensitive information already exists for this patient");
|
|
1402
1747
|
}
|
|
1748
|
+
let processedPhotoUrl = null;
|
|
1749
|
+
if (validatedData.photoUrl && mediaService) {
|
|
1750
|
+
processedPhotoUrl = await handlePhotoUrlUpload(
|
|
1751
|
+
validatedData.photoUrl,
|
|
1752
|
+
data.patientId,
|
|
1753
|
+
mediaService
|
|
1754
|
+
);
|
|
1755
|
+
} else if (typeof validatedData.photoUrl === "string") {
|
|
1756
|
+
processedPhotoUrl = validatedData.photoUrl;
|
|
1757
|
+
}
|
|
1403
1758
|
const sensitiveInfoData = {
|
|
1404
1759
|
...validatedData,
|
|
1760
|
+
photoUrl: processedPhotoUrl,
|
|
1405
1761
|
createdAt: serverTimestamp(),
|
|
1406
1762
|
updatedAt: serverTimestamp()
|
|
1407
1763
|
};
|
|
1408
|
-
await
|
|
1409
|
-
const createdDoc = await
|
|
1764
|
+
await setDoc2(getSensitiveInfoDocRef(db, data.patientId), sensitiveInfoData);
|
|
1765
|
+
const createdDoc = await getDoc2(getSensitiveInfoDocRef(db, data.patientId));
|
|
1410
1766
|
if (!createdDoc.exists()) {
|
|
1411
1767
|
throw new Error("Failed to create sensitive information");
|
|
1412
1768
|
}
|
|
1413
1769
|
return createdDoc.data();
|
|
1414
1770
|
} catch (error) {
|
|
1415
|
-
if (error instanceof
|
|
1771
|
+
if (error instanceof z8.ZodError) {
|
|
1416
1772
|
throw new Error("Invalid sensitive info data: " + error.message);
|
|
1417
1773
|
}
|
|
1418
1774
|
throw error;
|
|
@@ -1420,17 +1776,32 @@ var createSensitiveInfoUtil = async (db, data, requesterUserId) => {
|
|
|
1420
1776
|
};
|
|
1421
1777
|
var getSensitiveInfoUtil = async (db, patientId, requesterUserId) => {
|
|
1422
1778
|
await initSensitiveInfoDocIfNotExists(db, patientId, requesterUserId);
|
|
1423
|
-
const sensitiveDoc = await
|
|
1779
|
+
const sensitiveDoc = await getDoc2(getSensitiveInfoDocRef(db, patientId));
|
|
1424
1780
|
return sensitiveDoc.exists() ? sensitiveDoc.data() : null;
|
|
1425
1781
|
};
|
|
1426
|
-
var updateSensitiveInfoUtil = async (db, patientId, data, requesterUserId) => {
|
|
1782
|
+
var updateSensitiveInfoUtil = async (db, patientId, data, requesterUserId, mediaService) => {
|
|
1427
1783
|
await initSensitiveInfoDocIfNotExists(db, patientId, requesterUserId);
|
|
1784
|
+
let processedPhotoUrl = void 0;
|
|
1785
|
+
if (data.photoUrl !== void 0) {
|
|
1786
|
+
if (mediaService) {
|
|
1787
|
+
processedPhotoUrl = await handlePhotoUrlUpload(
|
|
1788
|
+
data.photoUrl,
|
|
1789
|
+
patientId,
|
|
1790
|
+
mediaService
|
|
1791
|
+
);
|
|
1792
|
+
} else if (typeof data.photoUrl === "string" || data.photoUrl === null) {
|
|
1793
|
+
processedPhotoUrl = data.photoUrl;
|
|
1794
|
+
} else {
|
|
1795
|
+
throw new Error("MediaService required to process photo upload");
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1428
1798
|
const updateData = {
|
|
1429
1799
|
...data,
|
|
1800
|
+
photoUrl: processedPhotoUrl,
|
|
1430
1801
|
updatedAt: serverTimestamp()
|
|
1431
1802
|
};
|
|
1432
|
-
await
|
|
1433
|
-
const updatedDoc = await
|
|
1803
|
+
await updateDoc2(getSensitiveInfoDocRef(db, patientId), updateData);
|
|
1804
|
+
const updatedDoc = await getDoc2(getSensitiveInfoDocRef(db, patientId));
|
|
1434
1805
|
if (!updatedDoc.exists()) {
|
|
1435
1806
|
throw new Error("Failed to retrieve updated sensitive information");
|
|
1436
1807
|
}
|
|
@@ -1439,21 +1810,21 @@ var updateSensitiveInfoUtil = async (db, patientId, data, requesterUserId) => {
|
|
|
1439
1810
|
|
|
1440
1811
|
// src/services/patient/utils/docs.utils.ts
|
|
1441
1812
|
var getPatientDocRef = (db, patientId) => {
|
|
1442
|
-
return
|
|
1813
|
+
return doc2(db, PATIENTS_COLLECTION, patientId);
|
|
1443
1814
|
};
|
|
1444
1815
|
var getPatientDocRefByUserRef = async (db, userRef) => {
|
|
1445
|
-
const patientsRef =
|
|
1446
|
-
const q =
|
|
1447
|
-
const querySnapshot = await
|
|
1816
|
+
const patientsRef = collection2(db, PATIENTS_COLLECTION);
|
|
1817
|
+
const q = query2(patientsRef, where2("userRef", "==", userRef));
|
|
1818
|
+
const querySnapshot = await getDocs2(q);
|
|
1448
1819
|
if (querySnapshot.empty) {
|
|
1449
1820
|
throw new Error("Patient profile not found");
|
|
1450
1821
|
}
|
|
1451
|
-
return
|
|
1822
|
+
return doc2(db, PATIENTS_COLLECTION, querySnapshot.docs[0].id);
|
|
1452
1823
|
};
|
|
1453
1824
|
var getSensitiveInfoDocRef = (db, patientId) => {
|
|
1454
1825
|
const path = `${PATIENTS_COLLECTION}/${patientId}/${PATIENT_SENSITIVE_INFO_COLLECTION}/${patientId}`;
|
|
1455
1826
|
console.log(`[getSensitiveInfoDocRef] Creating reference with path: ${path}`);
|
|
1456
|
-
return
|
|
1827
|
+
return doc2(
|
|
1457
1828
|
db,
|
|
1458
1829
|
PATIENTS_COLLECTION,
|
|
1459
1830
|
patientId,
|
|
@@ -1464,7 +1835,7 @@ var getSensitiveInfoDocRef = (db, patientId) => {
|
|
|
1464
1835
|
var getLocationInfoDocRef = (db, patientId) => {
|
|
1465
1836
|
const path = `${PATIENTS_COLLECTION}/${patientId}/${PATIENT_LOCATION_INFO_COLLECTION}/${patientId}`;
|
|
1466
1837
|
console.log(`[getLocationInfoDocRef] Creating reference with path: ${path}`);
|
|
1467
|
-
return
|
|
1838
|
+
return doc2(
|
|
1468
1839
|
db,
|
|
1469
1840
|
PATIENTS_COLLECTION,
|
|
1470
1841
|
patientId,
|
|
@@ -1475,7 +1846,7 @@ var getLocationInfoDocRef = (db, patientId) => {
|
|
|
1475
1846
|
var getMedicalInfoDocRef = (db, patientId) => {
|
|
1476
1847
|
const path = `${PATIENTS_COLLECTION}/${patientId}/${PATIENT_MEDICAL_INFO_COLLECTION}/${patientId}`;
|
|
1477
1848
|
console.log(`[getMedicalInfoDocRef] Creating reference with path: ${path}`);
|
|
1478
|
-
return
|
|
1849
|
+
return doc2(
|
|
1479
1850
|
db,
|
|
1480
1851
|
PATIENTS_COLLECTION,
|
|
1481
1852
|
patientId,
|
|
@@ -1492,7 +1863,7 @@ var initSensitiveInfoDocIfNotExists = async (db, patientId, userRef) => {
|
|
|
1492
1863
|
console.log(
|
|
1493
1864
|
`[initSensitiveInfoDocIfNotExists] Got document reference: ${sensitiveInfoRef.path}`
|
|
1494
1865
|
);
|
|
1495
|
-
const sensitiveDoc = await
|
|
1866
|
+
const sensitiveDoc = await getDoc3(sensitiveInfoRef);
|
|
1496
1867
|
console.log(
|
|
1497
1868
|
`[initSensitiveInfoDocIfNotExists] Document exists: ${sensitiveDoc.exists()}`
|
|
1498
1869
|
);
|
|
@@ -1516,7 +1887,7 @@ var initSensitiveInfoDocIfNotExists = async (db, patientId, userRef) => {
|
|
|
1516
1887
|
)
|
|
1517
1888
|
);
|
|
1518
1889
|
await createSensitiveInfoUtil(db, defaultSensitiveInfo, userRef);
|
|
1519
|
-
const verifyDoc = await
|
|
1890
|
+
const verifyDoc = await getDoc3(sensitiveInfoRef);
|
|
1520
1891
|
console.log(
|
|
1521
1892
|
`[initSensitiveInfoDocIfNotExists] Verification - document exists: ${verifyDoc.exists()}`
|
|
1522
1893
|
);
|
|
@@ -1534,24 +1905,24 @@ var initSensitiveInfoDocIfNotExists = async (db, patientId, userRef) => {
|
|
|
1534
1905
|
|
|
1535
1906
|
// src/services/patient/utils/medical.utils.ts
|
|
1536
1907
|
import {
|
|
1537
|
-
getDoc as
|
|
1538
|
-
updateDoc as
|
|
1539
|
-
setDoc as
|
|
1908
|
+
getDoc as getDoc5,
|
|
1909
|
+
updateDoc as updateDoc3,
|
|
1910
|
+
setDoc as setDoc4,
|
|
1540
1911
|
serverTimestamp as serverTimestamp3,
|
|
1541
1912
|
arrayUnion,
|
|
1542
|
-
Timestamp as
|
|
1913
|
+
Timestamp as Timestamp4
|
|
1543
1914
|
} from "firebase/firestore";
|
|
1544
1915
|
|
|
1545
1916
|
// src/services/patient/utils/practitioner.utils.ts
|
|
1546
1917
|
import {
|
|
1547
|
-
collection as
|
|
1548
|
-
query as
|
|
1549
|
-
where as
|
|
1550
|
-
getDocs as
|
|
1551
|
-
limit,
|
|
1918
|
+
collection as collection3,
|
|
1919
|
+
query as query3,
|
|
1920
|
+
where as where3,
|
|
1921
|
+
getDocs as getDocs3,
|
|
1922
|
+
limit as limit2,
|
|
1552
1923
|
startAfter,
|
|
1553
|
-
doc as
|
|
1554
|
-
getDoc as
|
|
1924
|
+
doc as doc3,
|
|
1925
|
+
getDoc as getDoc4
|
|
1555
1926
|
} from "firebase/firestore";
|
|
1556
1927
|
|
|
1557
1928
|
// src/types/practitioner/index.ts
|
|
@@ -1577,23 +1948,23 @@ var getPatientsByPractitionerUtil = async (db, practitionerId, options) => {
|
|
|
1577
1948
|
`[getPatientsByPractitionerUtil] Fetching patients for practitioner ID: ${practitionerId} with options:`,
|
|
1578
1949
|
options
|
|
1579
1950
|
);
|
|
1580
|
-
const patientsCollection =
|
|
1951
|
+
const patientsCollection = collection3(db, PATIENTS_COLLECTION);
|
|
1581
1952
|
const constraints = [
|
|
1582
|
-
|
|
1953
|
+
where3("doctorIds", "array-contains", practitionerId)
|
|
1583
1954
|
];
|
|
1584
|
-
let q =
|
|
1955
|
+
let q = query3(patientsCollection, ...constraints);
|
|
1585
1956
|
if (options == null ? void 0 : options.limit) {
|
|
1586
|
-
q =
|
|
1957
|
+
q = query3(q, limit2(options.limit));
|
|
1587
1958
|
}
|
|
1588
1959
|
if (options == null ? void 0 : options.startAfter) {
|
|
1589
|
-
const startAfterDoc = await
|
|
1590
|
-
|
|
1960
|
+
const startAfterDoc = await getDoc4(
|
|
1961
|
+
doc3(db, PATIENTS_COLLECTION, options.startAfter)
|
|
1591
1962
|
);
|
|
1592
1963
|
if (startAfterDoc.exists()) {
|
|
1593
|
-
q =
|
|
1964
|
+
q = query3(q, startAfter(startAfterDoc));
|
|
1594
1965
|
}
|
|
1595
1966
|
}
|
|
1596
|
-
const patientsSnapshot = await
|
|
1967
|
+
const patientsSnapshot = await getDocs3(q);
|
|
1597
1968
|
const patients = [];
|
|
1598
1969
|
patientsSnapshot.forEach((doc34) => {
|
|
1599
1970
|
patients.push(doc34.data());
|
|
@@ -1626,7 +1997,7 @@ var getPatientsByPractitionerWithDetailsUtil = async (db, practitionerId, option
|
|
|
1626
1997
|
const patientProfilesWithDetails = await Promise.all(
|
|
1627
1998
|
patientProfiles.map(async (profile) => {
|
|
1628
1999
|
try {
|
|
1629
|
-
const sensitiveInfoDoc = await
|
|
2000
|
+
const sensitiveInfoDoc = await getDoc4(
|
|
1630
2001
|
getSensitiveInfoDocRef(db, profile.id)
|
|
1631
2002
|
);
|
|
1632
2003
|
const sensitiveInfo = sensitiveInfoDoc.exists() ? sensitiveInfoDoc.data() : void 0;
|
|
@@ -1662,13 +2033,13 @@ var getPractitionerProfileByUserRef = async (db, userRef) => {
|
|
|
1662
2033
|
console.log(
|
|
1663
2034
|
`[getPractitionerProfileByUserRef] Fetching practitioner with userRef: ${userRef}`
|
|
1664
2035
|
);
|
|
1665
|
-
const practitionersCollection =
|
|
1666
|
-
const q =
|
|
2036
|
+
const practitionersCollection = collection3(db, PRACTITIONERS_COLLECTION);
|
|
2037
|
+
const q = query3(
|
|
1667
2038
|
practitionersCollection,
|
|
1668
|
-
|
|
1669
|
-
|
|
2039
|
+
where3("userRef", "==", userRef),
|
|
2040
|
+
limit2(1)
|
|
1670
2041
|
);
|
|
1671
|
-
const querySnapshot = await
|
|
2042
|
+
const querySnapshot = await getDocs3(q);
|
|
1672
2043
|
if (querySnapshot.empty) {
|
|
1673
2044
|
console.log(
|
|
1674
2045
|
`[getPractitionerProfileByUserRef] No practitioner found with userRef: ${userRef}`
|
|
@@ -1702,7 +2073,7 @@ var ensureMedicalInfoExists = async (db, patientId, userRef) => {
|
|
|
1702
2073
|
console.log(
|
|
1703
2074
|
`[ensureMedicalInfoExists] Got document reference: ${medicalInfoRef.path}`
|
|
1704
2075
|
);
|
|
1705
|
-
const medicalInfoDoc = await
|
|
2076
|
+
const medicalInfoDoc = await getDoc5(medicalInfoRef);
|
|
1706
2077
|
console.log(
|
|
1707
2078
|
`[ensureMedicalInfoExists] Document exists: ${medicalInfoDoc.exists()}`
|
|
1708
2079
|
);
|
|
@@ -1720,9 +2091,9 @@ var ensureMedicalInfoExists = async (db, patientId, userRef) => {
|
|
|
1720
2091
|
(key, value) => value && typeof value === "object" && value.constructor && value.constructor.name === "Object" ? "[serverTimestamp]" : value
|
|
1721
2092
|
)
|
|
1722
2093
|
);
|
|
1723
|
-
await
|
|
2094
|
+
await setDoc4(medicalInfoRef, defaultData);
|
|
1724
2095
|
console.log(`[ensureMedicalInfoExists] Document created successfully`);
|
|
1725
|
-
const verifyDoc = await
|
|
2096
|
+
const verifyDoc = await getDoc5(medicalInfoRef);
|
|
1726
2097
|
console.log(
|
|
1727
2098
|
`[ensureMedicalInfoExists] Verification - document exists: ${verifyDoc.exists()}`
|
|
1728
2099
|
);
|
|
@@ -1738,7 +2109,7 @@ var ensureMedicalInfoExists = async (db, patientId, userRef) => {
|
|
|
1738
2109
|
};
|
|
1739
2110
|
var checkMedicalAccessUtil = async (db, patientId, userRef, userRoles) => {
|
|
1740
2111
|
var _a;
|
|
1741
|
-
const patientDoc = await
|
|
2112
|
+
const patientDoc = await getDoc5(getPatientDocRef(db, patientId));
|
|
1742
2113
|
if (!patientDoc.exists()) {
|
|
1743
2114
|
throw new Error("Patient profile not found");
|
|
1744
2115
|
}
|
|
@@ -1771,17 +2142,17 @@ var checkMedicalAccessUtil = async (db, patientId, userRef, userRoles) => {
|
|
|
1771
2142
|
var createMedicalInfoUtil = async (db, patientId, data, userRef, userRoles) => {
|
|
1772
2143
|
await checkMedicalAccessUtil(db, patientId, userRef, userRoles);
|
|
1773
2144
|
const validatedData = createPatientMedicalInfoSchema.parse(data);
|
|
1774
|
-
await
|
|
2145
|
+
await setDoc4(getMedicalInfoDocRef(db, patientId), {
|
|
1775
2146
|
...validatedData,
|
|
1776
2147
|
patientId,
|
|
1777
|
-
lastUpdated:
|
|
2148
|
+
lastUpdated: Timestamp4.now(),
|
|
1778
2149
|
updatedBy: userRef
|
|
1779
2150
|
});
|
|
1780
2151
|
};
|
|
1781
2152
|
var getMedicalInfoUtil = async (db, patientId, userRef, userRoles) => {
|
|
1782
2153
|
await checkMedicalAccessUtil(db, patientId, userRef, userRoles);
|
|
1783
2154
|
const docRef = getMedicalInfoDocRef(db, patientId);
|
|
1784
|
-
const snapshot = await
|
|
2155
|
+
const snapshot = await getDoc5(docRef);
|
|
1785
2156
|
if (!snapshot.exists()) {
|
|
1786
2157
|
throw new Error("Medicinske informacije nisu prona\u0111ene");
|
|
1787
2158
|
}
|
|
@@ -1790,7 +2161,7 @@ var getMedicalInfoUtil = async (db, patientId, userRef, userRoles) => {
|
|
|
1790
2161
|
var updateVitalStatsUtil = async (db, patientId, data, userRef) => {
|
|
1791
2162
|
await ensureMedicalInfoExists(db, patientId, userRef);
|
|
1792
2163
|
const validatedData = updateVitalStatsSchema.parse(data);
|
|
1793
|
-
await
|
|
2164
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1794
2165
|
vitalStats: validatedData,
|
|
1795
2166
|
lastUpdated: serverTimestamp3(),
|
|
1796
2167
|
updatedBy: userRef
|
|
@@ -1799,7 +2170,7 @@ var updateVitalStatsUtil = async (db, patientId, data, userRef) => {
|
|
|
1799
2170
|
var addAllergyUtil = async (db, patientId, data, userRef) => {
|
|
1800
2171
|
await ensureMedicalInfoExists(db, patientId, userRef);
|
|
1801
2172
|
const validatedData = addAllergySchema.parse(data);
|
|
1802
|
-
await
|
|
2173
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1803
2174
|
allergies: arrayUnion(validatedData),
|
|
1804
2175
|
lastUpdated: serverTimestamp3(),
|
|
1805
2176
|
updatedBy: userRef
|
|
@@ -1808,7 +2179,7 @@ var addAllergyUtil = async (db, patientId, data, userRef) => {
|
|
|
1808
2179
|
var updateAllergyUtil = async (db, patientId, data, userRef) => {
|
|
1809
2180
|
const validatedData = updateAllergySchema.parse(data);
|
|
1810
2181
|
const { allergyIndex, ...updateData } = validatedData;
|
|
1811
|
-
const docSnapshot = await
|
|
2182
|
+
const docSnapshot = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1812
2183
|
if (!docSnapshot.exists()) throw new Error("Medical info not found");
|
|
1813
2184
|
const medicalInfo = patientMedicalInfoSchema.parse(docSnapshot.data());
|
|
1814
2185
|
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
@@ -1819,14 +2190,14 @@ var updateAllergyUtil = async (db, patientId, data, userRef) => {
|
|
|
1819
2190
|
...updatedAllergies[allergyIndex],
|
|
1820
2191
|
...updateData
|
|
1821
2192
|
};
|
|
1822
|
-
await
|
|
2193
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1823
2194
|
allergies: updatedAllergies,
|
|
1824
2195
|
lastUpdated: serverTimestamp3(),
|
|
1825
2196
|
updatedBy: userRef
|
|
1826
2197
|
});
|
|
1827
2198
|
};
|
|
1828
2199
|
var removeAllergyUtil = async (db, patientId, allergyIndex, userRef) => {
|
|
1829
|
-
const doc34 = await
|
|
2200
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1830
2201
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1831
2202
|
const medicalInfo = doc34.data();
|
|
1832
2203
|
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
@@ -1835,7 +2206,7 @@ var removeAllergyUtil = async (db, patientId, allergyIndex, userRef) => {
|
|
|
1835
2206
|
const updatedAllergies = medicalInfo.allergies.filter(
|
|
1836
2207
|
(_, index) => index !== allergyIndex
|
|
1837
2208
|
);
|
|
1838
|
-
await
|
|
2209
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1839
2210
|
allergies: updatedAllergies,
|
|
1840
2211
|
lastUpdated: serverTimestamp3(),
|
|
1841
2212
|
updatedBy: userRef
|
|
@@ -1844,7 +2215,7 @@ var removeAllergyUtil = async (db, patientId, allergyIndex, userRef) => {
|
|
|
1844
2215
|
var addBlockingConditionUtil = async (db, patientId, data, userRef) => {
|
|
1845
2216
|
await ensureMedicalInfoExists(db, patientId, userRef);
|
|
1846
2217
|
const validatedData = addBlockingConditionSchema.parse(data);
|
|
1847
|
-
await
|
|
2218
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1848
2219
|
blockingConditions: arrayUnion(validatedData),
|
|
1849
2220
|
lastUpdated: serverTimestamp3(),
|
|
1850
2221
|
updatedBy: userRef
|
|
@@ -1853,7 +2224,7 @@ var addBlockingConditionUtil = async (db, patientId, data, userRef) => {
|
|
|
1853
2224
|
var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
|
|
1854
2225
|
const validatedData = updateBlockingConditionSchema.parse(data);
|
|
1855
2226
|
const { conditionIndex, ...updateData } = validatedData;
|
|
1856
|
-
const doc34 = await
|
|
2227
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1857
2228
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1858
2229
|
const medicalInfo = doc34.data();
|
|
1859
2230
|
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
@@ -1864,14 +2235,14 @@ var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
|
|
|
1864
2235
|
...updatedConditions[conditionIndex],
|
|
1865
2236
|
...updateData
|
|
1866
2237
|
};
|
|
1867
|
-
await
|
|
2238
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1868
2239
|
blockingConditions: updatedConditions,
|
|
1869
2240
|
lastUpdated: serverTimestamp3(),
|
|
1870
2241
|
updatedBy: userRef
|
|
1871
2242
|
});
|
|
1872
2243
|
};
|
|
1873
2244
|
var removeBlockingConditionUtil = async (db, patientId, conditionIndex, userRef) => {
|
|
1874
|
-
const doc34 = await
|
|
2245
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1875
2246
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1876
2247
|
const medicalInfo = doc34.data();
|
|
1877
2248
|
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
@@ -1880,7 +2251,7 @@ var removeBlockingConditionUtil = async (db, patientId, conditionIndex, userRef)
|
|
|
1880
2251
|
const updatedConditions = medicalInfo.blockingConditions.filter(
|
|
1881
2252
|
(_, index) => index !== conditionIndex
|
|
1882
2253
|
);
|
|
1883
|
-
await
|
|
2254
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1884
2255
|
blockingConditions: updatedConditions,
|
|
1885
2256
|
lastUpdated: serverTimestamp3(),
|
|
1886
2257
|
updatedBy: userRef
|
|
@@ -1889,7 +2260,7 @@ var removeBlockingConditionUtil = async (db, patientId, conditionIndex, userRef)
|
|
|
1889
2260
|
var addContraindicationUtil = async (db, patientId, data, userRef) => {
|
|
1890
2261
|
await ensureMedicalInfoExists(db, patientId, userRef);
|
|
1891
2262
|
const validatedData = addContraindicationSchema.parse(data);
|
|
1892
|
-
await
|
|
2263
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1893
2264
|
contraindications: arrayUnion(validatedData),
|
|
1894
2265
|
lastUpdated: serverTimestamp3(),
|
|
1895
2266
|
updatedBy: userRef
|
|
@@ -1898,7 +2269,7 @@ var addContraindicationUtil = async (db, patientId, data, userRef) => {
|
|
|
1898
2269
|
var updateContraindicationUtil = async (db, patientId, data, userRef) => {
|
|
1899
2270
|
const validatedData = updateContraindicationSchema.parse(data);
|
|
1900
2271
|
const { contraindicationIndex, ...updateData } = validatedData;
|
|
1901
|
-
const doc34 = await
|
|
2272
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1902
2273
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1903
2274
|
const medicalInfo = doc34.data();
|
|
1904
2275
|
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
@@ -1909,14 +2280,14 @@ var updateContraindicationUtil = async (db, patientId, data, userRef) => {
|
|
|
1909
2280
|
...updatedContraindications[contraindicationIndex],
|
|
1910
2281
|
...updateData
|
|
1911
2282
|
};
|
|
1912
|
-
await
|
|
2283
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1913
2284
|
contraindications: updatedContraindications,
|
|
1914
2285
|
lastUpdated: serverTimestamp3(),
|
|
1915
2286
|
updatedBy: userRef
|
|
1916
2287
|
});
|
|
1917
2288
|
};
|
|
1918
2289
|
var removeContraindicationUtil = async (db, patientId, contraindicationIndex, userRef) => {
|
|
1919
|
-
const doc34 = await
|
|
2290
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1920
2291
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1921
2292
|
const medicalInfo = doc34.data();
|
|
1922
2293
|
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
@@ -1925,7 +2296,7 @@ var removeContraindicationUtil = async (db, patientId, contraindicationIndex, us
|
|
|
1925
2296
|
const updatedContraindications = medicalInfo.contraindications.filter(
|
|
1926
2297
|
(_, index) => index !== contraindicationIndex
|
|
1927
2298
|
);
|
|
1928
|
-
await
|
|
2299
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1929
2300
|
contraindications: updatedContraindications,
|
|
1930
2301
|
lastUpdated: serverTimestamp3(),
|
|
1931
2302
|
updatedBy: userRef
|
|
@@ -1934,7 +2305,7 @@ var removeContraindicationUtil = async (db, patientId, contraindicationIndex, us
|
|
|
1934
2305
|
var addMedicationUtil = async (db, patientId, data, userRef) => {
|
|
1935
2306
|
await ensureMedicalInfoExists(db, patientId, userRef);
|
|
1936
2307
|
const validatedData = addMedicationSchema.parse(data);
|
|
1937
|
-
await
|
|
2308
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1938
2309
|
currentMedications: arrayUnion(validatedData),
|
|
1939
2310
|
lastUpdated: serverTimestamp3(),
|
|
1940
2311
|
updatedBy: userRef
|
|
@@ -1943,7 +2314,7 @@ var addMedicationUtil = async (db, patientId, data, userRef) => {
|
|
|
1943
2314
|
var updateMedicationUtil = async (db, patientId, data, userRef) => {
|
|
1944
2315
|
const validatedData = updateMedicationSchema.parse(data);
|
|
1945
2316
|
const { medicationIndex, ...updateData } = validatedData;
|
|
1946
|
-
const doc34 = await
|
|
2317
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1947
2318
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1948
2319
|
const medicalInfo = doc34.data();
|
|
1949
2320
|
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
@@ -1954,14 +2325,14 @@ var updateMedicationUtil = async (db, patientId, data, userRef) => {
|
|
|
1954
2325
|
...updatedMedications[medicationIndex],
|
|
1955
2326
|
...updateData
|
|
1956
2327
|
};
|
|
1957
|
-
await
|
|
2328
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1958
2329
|
currentMedications: updatedMedications,
|
|
1959
2330
|
lastUpdated: serverTimestamp3(),
|
|
1960
2331
|
updatedBy: userRef
|
|
1961
2332
|
});
|
|
1962
2333
|
};
|
|
1963
2334
|
var removeMedicationUtil = async (db, patientId, medicationIndex, userRef) => {
|
|
1964
|
-
const doc34 = await
|
|
2335
|
+
const doc34 = await getDoc5(getMedicalInfoDocRef(db, patientId));
|
|
1965
2336
|
if (!doc34.exists()) throw new Error("Medical info not found");
|
|
1966
2337
|
const medicalInfo = doc34.data();
|
|
1967
2338
|
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
@@ -1970,7 +2341,7 @@ var removeMedicationUtil = async (db, patientId, medicationIndex, userRef) => {
|
|
|
1970
2341
|
const updatedMedications = medicalInfo.currentMedications.filter(
|
|
1971
2342
|
(_, index) => index !== medicationIndex
|
|
1972
2343
|
);
|
|
1973
|
-
await
|
|
2344
|
+
await updateDoc3(getMedicalInfoDocRef(db, patientId), {
|
|
1974
2345
|
currentMedications: updatedMedications,
|
|
1975
2346
|
lastUpdated: serverTimestamp3(),
|
|
1976
2347
|
updatedBy: userRef
|
|
@@ -1989,7 +2360,6 @@ var createPatientProfileUtil = async (db, data, generateId2) => {
|
|
|
1989
2360
|
id: patientId,
|
|
1990
2361
|
userRef: validatedData.userRef,
|
|
1991
2362
|
displayName: validatedData.displayName,
|
|
1992
|
-
profilePhoto: validatedData.profilePhoto || null,
|
|
1993
2363
|
expoTokens: validatedData.expoTokens,
|
|
1994
2364
|
gamification: validatedData.gamification || {
|
|
1995
2365
|
level: 1,
|
|
@@ -2006,10 +2376,10 @@ var createPatientProfileUtil = async (db, data, generateId2) => {
|
|
|
2006
2376
|
};
|
|
2007
2377
|
patientProfileSchema.parse({
|
|
2008
2378
|
...patientData,
|
|
2009
|
-
createdAt:
|
|
2010
|
-
updatedAt:
|
|
2379
|
+
createdAt: Timestamp5.now(),
|
|
2380
|
+
updatedAt: Timestamp5.now()
|
|
2011
2381
|
});
|
|
2012
|
-
await
|
|
2382
|
+
await setDoc5(getPatientDocRef(db, patientId), patientData);
|
|
2013
2383
|
console.log(`[createPatientProfileUtil] Creating sensitive info document`);
|
|
2014
2384
|
let sensitiveInfoSuccess = false;
|
|
2015
2385
|
try {
|
|
@@ -2023,69 +2393,91 @@ var createPatientProfileUtil = async (db, data, generateId2) => {
|
|
|
2023
2393
|
);
|
|
2024
2394
|
sensitiveInfoSuccess = true;
|
|
2025
2395
|
} catch (sensitiveError) {
|
|
2026
|
-
console.error(
|
|
2396
|
+
console.error(
|
|
2397
|
+
`[createPatientProfileUtil] Error creating sensitive info:`,
|
|
2398
|
+
sensitiveError
|
|
2399
|
+
);
|
|
2027
2400
|
}
|
|
2028
2401
|
console.log(`[createPatientProfileUtil] Creating medical info document`);
|
|
2029
2402
|
let medicalInfoSuccess = false;
|
|
2030
2403
|
try {
|
|
2031
2404
|
await ensureMedicalInfoExists(db, patientId, validatedData.userRef);
|
|
2032
|
-
console.log(
|
|
2405
|
+
console.log(
|
|
2406
|
+
`[createPatientProfileUtil] Medical info document created successfully`
|
|
2407
|
+
);
|
|
2033
2408
|
medicalInfoSuccess = true;
|
|
2034
2409
|
} catch (medicalError) {
|
|
2035
|
-
console.error(
|
|
2410
|
+
console.error(
|
|
2411
|
+
`[createPatientProfileUtil] Error creating medical info:`,
|
|
2412
|
+
medicalError
|
|
2413
|
+
);
|
|
2036
2414
|
}
|
|
2037
2415
|
if (!sensitiveInfoSuccess || !medicalInfoSuccess) {
|
|
2038
|
-
console.log(
|
|
2416
|
+
console.log(
|
|
2417
|
+
`[createPatientProfileUtil] Using fallback method to create documents`
|
|
2418
|
+
);
|
|
2039
2419
|
try {
|
|
2040
2420
|
await testCreateSubDocuments(db, patientId, validatedData.userRef);
|
|
2041
|
-
console.log(
|
|
2421
|
+
console.log(
|
|
2422
|
+
`[createPatientProfileUtil] Fallback method completed successfully`
|
|
2423
|
+
);
|
|
2042
2424
|
} catch (fallbackError) {
|
|
2043
|
-
console.error(
|
|
2425
|
+
console.error(
|
|
2426
|
+
`[createPatientProfileUtil] Fallback method failed:`,
|
|
2427
|
+
fallbackError
|
|
2428
|
+
);
|
|
2044
2429
|
}
|
|
2045
2430
|
}
|
|
2046
2431
|
console.log(`[createPatientProfileUtil] Verifying patient document exists`);
|
|
2047
|
-
const patientDoc = await
|
|
2432
|
+
const patientDoc = await getDoc6(getPatientDocRef(db, patientId));
|
|
2048
2433
|
if (!patientDoc.exists()) {
|
|
2049
|
-
console.error(
|
|
2434
|
+
console.error(
|
|
2435
|
+
`[createPatientProfileUtil] Patient document not found after creation`
|
|
2436
|
+
);
|
|
2050
2437
|
throw new Error("Failed to create patient profile");
|
|
2051
2438
|
}
|
|
2052
|
-
console.log(
|
|
2439
|
+
console.log(
|
|
2440
|
+
`[createPatientProfileUtil] Patient profile creation completed successfully`
|
|
2441
|
+
);
|
|
2053
2442
|
return patientDoc.data();
|
|
2054
2443
|
} catch (error) {
|
|
2055
|
-
console.error(
|
|
2056
|
-
|
|
2444
|
+
console.error(
|
|
2445
|
+
`[createPatientProfileUtil] Error in patient profile creation:`,
|
|
2446
|
+
error
|
|
2447
|
+
);
|
|
2448
|
+
if (error instanceof z9.ZodError) {
|
|
2057
2449
|
throw new Error("Invalid patient data: " + error.message);
|
|
2058
2450
|
}
|
|
2059
2451
|
throw error;
|
|
2060
2452
|
}
|
|
2061
2453
|
};
|
|
2062
2454
|
var getPatientProfileUtil = async (db, patientId) => {
|
|
2063
|
-
const patientDoc = await
|
|
2455
|
+
const patientDoc = await getDoc6(getPatientDocRef(db, patientId));
|
|
2064
2456
|
return patientDoc.exists() ? patientDoc.data() : null;
|
|
2065
2457
|
};
|
|
2066
2458
|
var getPatientProfileByUserRefUtil = async (db, userRef) => {
|
|
2067
2459
|
try {
|
|
2068
2460
|
const docRef = await getPatientDocRefByUserRef(db, userRef);
|
|
2069
|
-
const patientDoc = await
|
|
2461
|
+
const patientDoc = await getDoc6(docRef);
|
|
2070
2462
|
return patientDoc.exists() ? patientDoc.data() : null;
|
|
2071
2463
|
} catch (error) {
|
|
2072
2464
|
return null;
|
|
2073
2465
|
}
|
|
2074
2466
|
};
|
|
2075
2467
|
var addExpoTokenUtil = async (db, patientId, token) => {
|
|
2076
|
-
await
|
|
2468
|
+
await updateDoc4(getPatientDocRef(db, patientId), {
|
|
2077
2469
|
expoTokens: arrayUnion2(token),
|
|
2078
2470
|
updatedAt: serverTimestamp4()
|
|
2079
2471
|
});
|
|
2080
2472
|
};
|
|
2081
2473
|
var removeExpoTokenUtil = async (db, patientId, token) => {
|
|
2082
|
-
await
|
|
2474
|
+
await updateDoc4(getPatientDocRef(db, patientId), {
|
|
2083
2475
|
expoTokens: arrayRemove2(token),
|
|
2084
2476
|
updatedAt: serverTimestamp4()
|
|
2085
2477
|
});
|
|
2086
2478
|
};
|
|
2087
2479
|
var addPointsUtil = async (db, patientId, points) => {
|
|
2088
|
-
await
|
|
2480
|
+
await updateDoc4(getPatientDocRef(db, patientId), {
|
|
2089
2481
|
"gamification.points": increment(points),
|
|
2090
2482
|
updatedAt: serverTimestamp4()
|
|
2091
2483
|
});
|
|
@@ -2096,8 +2488,8 @@ var updatePatientProfileUtil = async (db, patientId, data) => {
|
|
|
2096
2488
|
...data,
|
|
2097
2489
|
updatedAt: serverTimestamp4()
|
|
2098
2490
|
};
|
|
2099
|
-
await
|
|
2100
|
-
const updatedDoc = await
|
|
2491
|
+
await updateDoc4(getPatientDocRef(db, patientId), updateData);
|
|
2492
|
+
const updatedDoc = await getDoc6(getPatientDocRef(db, patientId));
|
|
2101
2493
|
if (!updatedDoc.exists()) {
|
|
2102
2494
|
throw new Error("Patient profile not found after update");
|
|
2103
2495
|
}
|
|
@@ -2110,7 +2502,7 @@ var updatePatientProfileUtil = async (db, patientId, data) => {
|
|
|
2110
2502
|
var updatePatientProfileByUserRefUtil = async (db, userRef, data) => {
|
|
2111
2503
|
try {
|
|
2112
2504
|
const docRef = await getPatientDocRefByUserRef(db, userRef);
|
|
2113
|
-
const patientDoc = await
|
|
2505
|
+
const patientDoc = await getDoc6(docRef);
|
|
2114
2506
|
if (!patientDoc.exists()) {
|
|
2115
2507
|
throw new Error("Patient profile not found");
|
|
2116
2508
|
}
|
|
@@ -2118,49 +2510,10 @@ var updatePatientProfileByUserRefUtil = async (db, userRef, data) => {
|
|
|
2118
2510
|
return updatePatientProfileUtil(db, patientData.id, data);
|
|
2119
2511
|
} catch (error) {
|
|
2120
2512
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2121
|
-
throw new Error(
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
var uploadProfilePhotoUtil = async (storage, patientId, file) => {
|
|
2125
|
-
const photoRef = ref(storage, `patient-photos/${patientId}/profile-photo`);
|
|
2126
|
-
await uploadBytes(photoRef, file);
|
|
2127
|
-
return getDownloadURL(photoRef);
|
|
2128
|
-
};
|
|
2129
|
-
var updateProfilePhotoUtil = async (storage, db, patientId, file) => {
|
|
2130
|
-
const patientDoc = await getDoc5(getPatientDocRef(db, patientId));
|
|
2131
|
-
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2132
|
-
const patientData = patientDoc.data();
|
|
2133
|
-
if (patientData.profilePhoto) {
|
|
2134
|
-
try {
|
|
2135
|
-
const oldPhotoRef = ref(storage, patientData.profilePhoto);
|
|
2136
|
-
await deleteObject(oldPhotoRef);
|
|
2137
|
-
} catch (error) {
|
|
2138
|
-
console.warn("Failed to delete old profile photo:", error);
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
const newPhotoUrl = await uploadProfilePhotoUtil(storage, patientId, file);
|
|
2142
|
-
await updateDoc3(getPatientDocRef(db, patientId), {
|
|
2143
|
-
profilePhoto: newPhotoUrl,
|
|
2144
|
-
updatedAt: serverTimestamp4()
|
|
2145
|
-
});
|
|
2146
|
-
return newPhotoUrl;
|
|
2147
|
-
};
|
|
2148
|
-
var deleteProfilePhotoUtil = async (storage, db, patientId) => {
|
|
2149
|
-
const patientDoc = await getDoc5(getPatientDocRef(db, patientId));
|
|
2150
|
-
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2151
|
-
const patientData = patientDoc.data();
|
|
2152
|
-
if (patientData.profilePhoto) {
|
|
2153
|
-
try {
|
|
2154
|
-
const photoRef = ref(storage, patientData.profilePhoto);
|
|
2155
|
-
await deleteObject(photoRef);
|
|
2156
|
-
} catch (error) {
|
|
2157
|
-
console.warn("Failed to delete profile photo:", error);
|
|
2158
|
-
}
|
|
2513
|
+
throw new Error(
|
|
2514
|
+
`Failed to update patient profile by user ref: ${errorMessage}`
|
|
2515
|
+
);
|
|
2159
2516
|
}
|
|
2160
|
-
await updateDoc3(getPatientDocRef(db, patientId), {
|
|
2161
|
-
profilePhoto: null,
|
|
2162
|
-
updatedAt: serverTimestamp4()
|
|
2163
|
-
});
|
|
2164
2517
|
};
|
|
2165
2518
|
var testCreateSubDocuments = async (db, patientId, userRef) => {
|
|
2166
2519
|
console.log(
|
|
@@ -2169,33 +2522,41 @@ var testCreateSubDocuments = async (db, patientId, userRef) => {
|
|
|
2169
2522
|
try {
|
|
2170
2523
|
console.log(`[testCreateSubDocuments] Testing sensitive info creation`);
|
|
2171
2524
|
const sensitiveInfoRef = getSensitiveInfoDocRef(db, patientId);
|
|
2172
|
-
console.log(
|
|
2525
|
+
console.log(
|
|
2526
|
+
`[testCreateSubDocuments] Sensitive info path: ${sensitiveInfoRef.path}`
|
|
2527
|
+
);
|
|
2173
2528
|
const defaultSensitiveInfo = {
|
|
2174
2529
|
patientId,
|
|
2175
2530
|
userRef,
|
|
2176
2531
|
photoUrl: "",
|
|
2177
2532
|
firstName: "Name",
|
|
2178
2533
|
lastName: "Surname",
|
|
2179
|
-
dateOfBirth:
|
|
2534
|
+
dateOfBirth: Timestamp5.now(),
|
|
2180
2535
|
gender: "prefer_not_to_say" /* PREFER_NOT_TO_SAY */,
|
|
2181
2536
|
email: "test@example.com",
|
|
2182
2537
|
phoneNumber: "",
|
|
2183
|
-
createdAt:
|
|
2184
|
-
updatedAt:
|
|
2538
|
+
createdAt: Timestamp5.now(),
|
|
2539
|
+
updatedAt: Timestamp5.now()
|
|
2185
2540
|
};
|
|
2186
|
-
await
|
|
2187
|
-
console.log(
|
|
2541
|
+
await setDoc5(sensitiveInfoRef, defaultSensitiveInfo);
|
|
2542
|
+
console.log(
|
|
2543
|
+
`[testCreateSubDocuments] Sensitive info document created directly`
|
|
2544
|
+
);
|
|
2188
2545
|
console.log(`[testCreateSubDocuments] Testing medical info creation`);
|
|
2189
2546
|
const medicalInfoRef = getMedicalInfoDocRef(db, patientId);
|
|
2190
|
-
console.log(
|
|
2547
|
+
console.log(
|
|
2548
|
+
`[testCreateSubDocuments] Medical info path: ${medicalInfoRef.path}`
|
|
2549
|
+
);
|
|
2191
2550
|
const defaultMedicalInfo = {
|
|
2192
2551
|
...DEFAULT_MEDICAL_INFO,
|
|
2193
2552
|
patientId,
|
|
2194
|
-
lastUpdated:
|
|
2553
|
+
lastUpdated: Timestamp5.now(),
|
|
2195
2554
|
updatedBy: userRef
|
|
2196
2555
|
};
|
|
2197
|
-
await
|
|
2198
|
-
console.log(
|
|
2556
|
+
await setDoc5(medicalInfoRef, defaultMedicalInfo);
|
|
2557
|
+
console.log(
|
|
2558
|
+
`[testCreateSubDocuments] Medical info document created directly`
|
|
2559
|
+
);
|
|
2199
2560
|
console.log(`[testCreateSubDocuments] Test completed successfully`);
|
|
2200
2561
|
} catch (error) {
|
|
2201
2562
|
console.error(`[testCreateSubDocuments] Error:`, error);
|
|
@@ -2206,10 +2567,12 @@ var searchPatientsUtil = async (db, params, requester) => {
|
|
|
2206
2567
|
searchPatientsSchema.parse(params);
|
|
2207
2568
|
requesterInfoSchema.parse(requester);
|
|
2208
2569
|
const constraints = [];
|
|
2209
|
-
const patientsCollectionRef =
|
|
2570
|
+
const patientsCollectionRef = collection4(db, PATIENTS_COLLECTION);
|
|
2210
2571
|
if (requester.role === "clinic_admin") {
|
|
2211
2572
|
if (!requester.associatedClinicId) {
|
|
2212
|
-
throw new Error(
|
|
2573
|
+
throw new Error(
|
|
2574
|
+
"Associated clinic ID is required for clinic admin search."
|
|
2575
|
+
);
|
|
2213
2576
|
}
|
|
2214
2577
|
if (params.clinicId && params.clinicId !== requester.associatedClinicId) {
|
|
2215
2578
|
console.warn(
|
|
@@ -2217,13 +2580,19 @@ var searchPatientsUtil = async (db, params, requester) => {
|
|
|
2217
2580
|
);
|
|
2218
2581
|
return [];
|
|
2219
2582
|
}
|
|
2220
|
-
constraints.push(
|
|
2583
|
+
constraints.push(
|
|
2584
|
+
where4("clinicIds", "array-contains", requester.associatedClinicId)
|
|
2585
|
+
);
|
|
2221
2586
|
if (params.practitionerId) {
|
|
2222
|
-
constraints.push(
|
|
2587
|
+
constraints.push(
|
|
2588
|
+
where4("doctorIds", "array-contains", params.practitionerId)
|
|
2589
|
+
);
|
|
2223
2590
|
}
|
|
2224
2591
|
} else if (requester.role === "practitioner") {
|
|
2225
2592
|
if (!requester.associatedPractitionerId) {
|
|
2226
|
-
throw new Error(
|
|
2593
|
+
throw new Error(
|
|
2594
|
+
"Associated practitioner ID is required for practitioner search."
|
|
2595
|
+
);
|
|
2227
2596
|
}
|
|
2228
2597
|
if (params.practitionerId && params.practitionerId !== requester.associatedPractitionerId) {
|
|
2229
2598
|
console.warn(
|
|
@@ -2231,18 +2600,24 @@ var searchPatientsUtil = async (db, params, requester) => {
|
|
|
2231
2600
|
);
|
|
2232
2601
|
return [];
|
|
2233
2602
|
}
|
|
2234
|
-
constraints.push(
|
|
2603
|
+
constraints.push(
|
|
2604
|
+
where4("doctorIds", "array-contains", requester.associatedPractitionerId)
|
|
2605
|
+
);
|
|
2235
2606
|
if (params.clinicId) {
|
|
2236
|
-
constraints.push(
|
|
2607
|
+
constraints.push(where4("clinicIds", "array-contains", params.clinicId));
|
|
2237
2608
|
}
|
|
2238
2609
|
} else {
|
|
2239
2610
|
throw new Error("Invalid requester role.");
|
|
2240
2611
|
}
|
|
2241
2612
|
try {
|
|
2242
|
-
const finalQuery =
|
|
2243
|
-
const querySnapshot = await
|
|
2244
|
-
const patients = querySnapshot.docs.map(
|
|
2245
|
-
|
|
2613
|
+
const finalQuery = query4(patientsCollectionRef, ...constraints);
|
|
2614
|
+
const querySnapshot = await getDocs4(finalQuery);
|
|
2615
|
+
const patients = querySnapshot.docs.map(
|
|
2616
|
+
(doc34) => doc34.data()
|
|
2617
|
+
);
|
|
2618
|
+
console.log(
|
|
2619
|
+
`[searchPatientsUtil] Found ${patients.length} patients matching criteria.`
|
|
2620
|
+
);
|
|
2246
2621
|
return patients;
|
|
2247
2622
|
} catch (error) {
|
|
2248
2623
|
console.error("[searchPatientsUtil] Error searching patients:", error);
|
|
@@ -2251,19 +2626,24 @@ var searchPatientsUtil = async (db, params, requester) => {
|
|
|
2251
2626
|
};
|
|
2252
2627
|
var getAllPatientsUtil = async (db, options) => {
|
|
2253
2628
|
try {
|
|
2254
|
-
console.log(
|
|
2255
|
-
|
|
2256
|
-
|
|
2629
|
+
console.log(
|
|
2630
|
+
`[getAllPatientsUtil] Fetching patients with options:`,
|
|
2631
|
+
options
|
|
2632
|
+
);
|
|
2633
|
+
const patientsCollection = collection4(db, PATIENTS_COLLECTION);
|
|
2634
|
+
let q = query4(patientsCollection);
|
|
2257
2635
|
if (options == null ? void 0 : options.limit) {
|
|
2258
|
-
q =
|
|
2636
|
+
q = query4(q, limit3(options.limit));
|
|
2259
2637
|
}
|
|
2260
2638
|
if (options == null ? void 0 : options.startAfter) {
|
|
2261
|
-
const startAfterDoc = await
|
|
2639
|
+
const startAfterDoc = await getDoc6(
|
|
2640
|
+
doc5(db, PATIENTS_COLLECTION, options.startAfter)
|
|
2641
|
+
);
|
|
2262
2642
|
if (startAfterDoc.exists()) {
|
|
2263
|
-
q =
|
|
2643
|
+
q = query4(q, startAfter2(startAfterDoc));
|
|
2264
2644
|
}
|
|
2265
2645
|
}
|
|
2266
|
-
const patientsSnapshot = await
|
|
2646
|
+
const patientsSnapshot = await getDocs4(q);
|
|
2267
2647
|
const patients = [];
|
|
2268
2648
|
patientsSnapshot.forEach((doc34) => {
|
|
2269
2649
|
patients.push(doc34.data());
|
|
@@ -2280,12 +2660,12 @@ var getAllPatientsUtil = async (db, options) => {
|
|
|
2280
2660
|
|
|
2281
2661
|
// src/services/patient/utils/location.utils.ts
|
|
2282
2662
|
import {
|
|
2283
|
-
getDoc as
|
|
2284
|
-
updateDoc as
|
|
2285
|
-
setDoc as
|
|
2663
|
+
getDoc as getDoc7,
|
|
2664
|
+
updateDoc as updateDoc5,
|
|
2665
|
+
setDoc as setDoc6,
|
|
2286
2666
|
serverTimestamp as serverTimestamp5
|
|
2287
2667
|
} from "firebase/firestore";
|
|
2288
|
-
import { z as
|
|
2668
|
+
import { z as z10 } from "zod";
|
|
2289
2669
|
import { geohashForLocation } from "geofire-common";
|
|
2290
2670
|
var updatePatientLocationUtil = async (db, patientId, latitude, longitude) => {
|
|
2291
2671
|
const locationData = {
|
|
@@ -2297,7 +2677,7 @@ var updatePatientLocationUtil = async (db, patientId, latitude, longitude) => {
|
|
|
2297
2677
|
locationData,
|
|
2298
2678
|
updatedAt: serverTimestamp5()
|
|
2299
2679
|
};
|
|
2300
|
-
await
|
|
2680
|
+
await updateDoc5(getLocationInfoDocRef(db, patientId), updateData);
|
|
2301
2681
|
};
|
|
2302
2682
|
var createLocationInfoUtil = async (db, data, requesterId) => {
|
|
2303
2683
|
try {
|
|
@@ -2317,14 +2697,14 @@ var createLocationInfoUtil = async (db, data, requesterId) => {
|
|
|
2317
2697
|
createdAt: serverTimestamp5(),
|
|
2318
2698
|
updatedAt: serverTimestamp5()
|
|
2319
2699
|
};
|
|
2320
|
-
await
|
|
2321
|
-
const locationDoc = await
|
|
2700
|
+
await setDoc6(getLocationInfoDocRef(db, data.patientId), locationData);
|
|
2701
|
+
const locationDoc = await getDoc7(getLocationInfoDocRef(db, data.patientId));
|
|
2322
2702
|
if (!locationDoc.exists()) {
|
|
2323
2703
|
throw new Error("Failed to create location information");
|
|
2324
2704
|
}
|
|
2325
2705
|
return locationDoc.data();
|
|
2326
2706
|
} catch (error) {
|
|
2327
|
-
if (error instanceof
|
|
2707
|
+
if (error instanceof z10.ZodError) {
|
|
2328
2708
|
throw new Error("Invalid location data: " + error.message);
|
|
2329
2709
|
}
|
|
2330
2710
|
throw error;
|
|
@@ -2334,7 +2714,7 @@ var getLocationInfoUtil = async (db, patientId, requesterId) => {
|
|
|
2334
2714
|
if (patientId !== requesterId) {
|
|
2335
2715
|
throw new Error("Unauthorized access to location information");
|
|
2336
2716
|
}
|
|
2337
|
-
const locationDoc = await
|
|
2717
|
+
const locationDoc = await getDoc7(getLocationInfoDocRef(db, patientId));
|
|
2338
2718
|
return locationDoc.exists() ? locationDoc.data() : null;
|
|
2339
2719
|
};
|
|
2340
2720
|
var updateLocationInfoUtil = async (db, patientId, data, requesterId) => {
|
|
@@ -2352,7 +2732,7 @@ var updateLocationInfoUtil = async (db, patientId, data, requesterId) => {
|
|
|
2352
2732
|
};
|
|
2353
2733
|
}
|
|
2354
2734
|
updateData.updatedAt = serverTimestamp5();
|
|
2355
|
-
await
|
|
2735
|
+
await updateDoc5(getLocationInfoDocRef(db, patientId), updateData);
|
|
2356
2736
|
const updatedInfo = await getLocationInfoUtil(db, patientId, requesterId);
|
|
2357
2737
|
if (!updatedInfo) {
|
|
2358
2738
|
throw new Error("Failed to retrieve updated location information");
|
|
@@ -2362,22 +2742,22 @@ var updateLocationInfoUtil = async (db, patientId, data, requesterId) => {
|
|
|
2362
2742
|
|
|
2363
2743
|
// src/services/patient/utils/medical-stuff.utils.ts
|
|
2364
2744
|
import {
|
|
2365
|
-
getDoc as
|
|
2366
|
-
updateDoc as
|
|
2745
|
+
getDoc as getDoc8,
|
|
2746
|
+
updateDoc as updateDoc6,
|
|
2367
2747
|
arrayUnion as arrayUnion3,
|
|
2368
2748
|
arrayRemove as arrayRemove3,
|
|
2369
2749
|
serverTimestamp as serverTimestamp6,
|
|
2370
|
-
Timestamp as
|
|
2750
|
+
Timestamp as Timestamp6
|
|
2371
2751
|
} from "firebase/firestore";
|
|
2372
2752
|
var addDoctorUtil = async (db, patientId, doctorRef, assignedBy) => {
|
|
2373
2753
|
var _a;
|
|
2374
2754
|
const newDoctor = {
|
|
2375
2755
|
userRef: doctorRef,
|
|
2376
|
-
assignedAt:
|
|
2756
|
+
assignedAt: Timestamp6.now(),
|
|
2377
2757
|
assignedBy,
|
|
2378
2758
|
isActive: true
|
|
2379
2759
|
};
|
|
2380
|
-
const patientDoc = await
|
|
2760
|
+
const patientDoc = await getDoc8(getPatientDocRef(db, patientId));
|
|
2381
2761
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2382
2762
|
const patientData = patientDoc.data();
|
|
2383
2763
|
const existingDoctorIndex = (_a = patientData.doctors) == null ? void 0 : _a.findIndex(
|
|
@@ -2392,23 +2772,23 @@ var addDoctorUtil = async (db, patientId, doctorRef, assignedBy) => {
|
|
|
2392
2772
|
updatedDoctors[existingDoctorIndex] = {
|
|
2393
2773
|
...updatedDoctors[existingDoctorIndex],
|
|
2394
2774
|
isActive: true,
|
|
2395
|
-
assignedAt:
|
|
2775
|
+
assignedAt: Timestamp6.now(),
|
|
2396
2776
|
assignedBy
|
|
2397
2777
|
};
|
|
2398
2778
|
updates.doctors = updatedDoctors;
|
|
2399
2779
|
} else {
|
|
2400
2780
|
updates.doctors = arrayUnion3(newDoctor);
|
|
2401
2781
|
}
|
|
2402
|
-
await
|
|
2782
|
+
await updateDoc6(getPatientDocRef(db, patientId), updates);
|
|
2403
2783
|
};
|
|
2404
2784
|
var removeDoctorUtil = async (db, patientId, doctorRef) => {
|
|
2405
2785
|
var _a;
|
|
2406
2786
|
const patientDocRef = getPatientDocRef(db, patientId);
|
|
2407
|
-
const patientDoc = await
|
|
2787
|
+
const patientDoc = await getDoc8(patientDocRef);
|
|
2408
2788
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2409
2789
|
const patientData = patientDoc.data();
|
|
2410
2790
|
const updatedDoctors = ((_a = patientData.doctors) == null ? void 0 : _a.filter((doctor) => doctor.userRef !== doctorRef)) || [];
|
|
2411
|
-
await
|
|
2791
|
+
await updateDoc6(patientDocRef, {
|
|
2412
2792
|
doctors: updatedDoctors,
|
|
2413
2793
|
// Set the filtered array
|
|
2414
2794
|
doctorIds: arrayRemove3(doctorRef),
|
|
@@ -2420,11 +2800,11 @@ var addClinicUtil = async (db, patientId, clinicId, assignedBy) => {
|
|
|
2420
2800
|
var _a;
|
|
2421
2801
|
const newClinic = {
|
|
2422
2802
|
clinicId,
|
|
2423
|
-
assignedAt:
|
|
2803
|
+
assignedAt: Timestamp6.now(),
|
|
2424
2804
|
assignedBy,
|
|
2425
2805
|
isActive: true
|
|
2426
2806
|
};
|
|
2427
|
-
const patientDoc = await
|
|
2807
|
+
const patientDoc = await getDoc8(getPatientDocRef(db, patientId));
|
|
2428
2808
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2429
2809
|
const patientData = patientDoc.data();
|
|
2430
2810
|
const existingClinicIndex = (_a = patientData.clinics) == null ? void 0 : _a.findIndex(
|
|
@@ -2439,23 +2819,23 @@ var addClinicUtil = async (db, patientId, clinicId, assignedBy) => {
|
|
|
2439
2819
|
updatedClinics[existingClinicIndex] = {
|
|
2440
2820
|
...updatedClinics[existingClinicIndex],
|
|
2441
2821
|
isActive: true,
|
|
2442
|
-
assignedAt:
|
|
2822
|
+
assignedAt: Timestamp6.now(),
|
|
2443
2823
|
assignedBy
|
|
2444
2824
|
};
|
|
2445
2825
|
updates.clinics = updatedClinics;
|
|
2446
2826
|
} else {
|
|
2447
2827
|
updates.clinics = arrayUnion3(newClinic);
|
|
2448
2828
|
}
|
|
2449
|
-
await
|
|
2829
|
+
await updateDoc6(getPatientDocRef(db, patientId), updates);
|
|
2450
2830
|
};
|
|
2451
2831
|
var removeClinicUtil = async (db, patientId, clinicId) => {
|
|
2452
2832
|
var _a;
|
|
2453
2833
|
const patientDocRef = getPatientDocRef(db, patientId);
|
|
2454
|
-
const patientDoc = await
|
|
2834
|
+
const patientDoc = await getDoc8(patientDocRef);
|
|
2455
2835
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
2456
2836
|
const patientData = patientDoc.data();
|
|
2457
2837
|
const updatedClinics = ((_a = patientData.clinics) == null ? void 0 : _a.filter((clinic) => clinic.clinicId !== clinicId)) || [];
|
|
2458
|
-
await
|
|
2838
|
+
await updateDoc6(patientDocRef, {
|
|
2459
2839
|
clinics: updatedClinics,
|
|
2460
2840
|
// Set the filtered array
|
|
2461
2841
|
clinicIds: arrayRemove3(clinicId),
|
|
@@ -2466,14 +2846,14 @@ var removeClinicUtil = async (db, patientId, clinicId) => {
|
|
|
2466
2846
|
|
|
2467
2847
|
// src/services/patient/utils/clinic.utils.ts
|
|
2468
2848
|
import {
|
|
2469
|
-
collection as
|
|
2470
|
-
query as
|
|
2471
|
-
where as
|
|
2472
|
-
getDocs as
|
|
2473
|
-
limit as
|
|
2849
|
+
collection as collection5,
|
|
2850
|
+
query as query5,
|
|
2851
|
+
where as where5,
|
|
2852
|
+
getDocs as getDocs5,
|
|
2853
|
+
limit as limit4,
|
|
2474
2854
|
startAfter as startAfter3,
|
|
2475
|
-
doc as
|
|
2476
|
-
getDoc as
|
|
2855
|
+
doc as doc6,
|
|
2856
|
+
getDoc as getDoc9
|
|
2477
2857
|
} from "firebase/firestore";
|
|
2478
2858
|
var getPatientsByClinicUtil = async (db, clinicId, options) => {
|
|
2479
2859
|
try {
|
|
@@ -2481,23 +2861,23 @@ var getPatientsByClinicUtil = async (db, clinicId, options) => {
|
|
|
2481
2861
|
`[getPatientsByClinicUtil] Fetching patients for clinic ID: ${clinicId} with options:`,
|
|
2482
2862
|
options
|
|
2483
2863
|
);
|
|
2484
|
-
const patientsCollection =
|
|
2864
|
+
const patientsCollection = collection5(db, PATIENTS_COLLECTION);
|
|
2485
2865
|
const constraints = [
|
|
2486
|
-
|
|
2866
|
+
where5("clinicIds", "array-contains", clinicId)
|
|
2487
2867
|
];
|
|
2488
|
-
let q =
|
|
2868
|
+
let q = query5(patientsCollection, ...constraints);
|
|
2489
2869
|
if (options == null ? void 0 : options.limit) {
|
|
2490
|
-
q =
|
|
2870
|
+
q = query5(q, limit4(options.limit));
|
|
2491
2871
|
}
|
|
2492
2872
|
if (options == null ? void 0 : options.startAfter) {
|
|
2493
|
-
const startAfterDoc = await
|
|
2494
|
-
|
|
2873
|
+
const startAfterDoc = await getDoc9(
|
|
2874
|
+
doc6(db, PATIENTS_COLLECTION, options.startAfter)
|
|
2495
2875
|
);
|
|
2496
2876
|
if (startAfterDoc.exists()) {
|
|
2497
|
-
q =
|
|
2877
|
+
q = query5(q, startAfter3(startAfterDoc));
|
|
2498
2878
|
}
|
|
2499
2879
|
}
|
|
2500
|
-
const patientsSnapshot = await
|
|
2880
|
+
const patientsSnapshot = await getDocs5(q);
|
|
2501
2881
|
const patients = [];
|
|
2502
2882
|
patientsSnapshot.forEach((doc34) => {
|
|
2503
2883
|
patients.push(doc34.data());
|
|
@@ -2521,6 +2901,7 @@ var getPatientsByClinicUtil = async (db, clinicId, options) => {
|
|
|
2521
2901
|
var PatientService = class extends BaseService {
|
|
2522
2902
|
constructor(db, auth, app) {
|
|
2523
2903
|
super(db, auth, app);
|
|
2904
|
+
this.mediaService = new MediaService(db, auth, app);
|
|
2524
2905
|
}
|
|
2525
2906
|
// Metode za rad sa profilom pacijenta
|
|
2526
2907
|
async createPatientProfile(data) {
|
|
@@ -2557,7 +2938,12 @@ var PatientService = class extends BaseService {
|
|
|
2557
2938
|
}
|
|
2558
2939
|
// Metode za rad sa osetljivim informacijama
|
|
2559
2940
|
async createSensitiveInfo(data, requesterUserId) {
|
|
2560
|
-
return createSensitiveInfoUtil(
|
|
2941
|
+
return createSensitiveInfoUtil(
|
|
2942
|
+
this.db,
|
|
2943
|
+
data,
|
|
2944
|
+
requesterUserId,
|
|
2945
|
+
this.mediaService
|
|
2946
|
+
);
|
|
2561
2947
|
}
|
|
2562
2948
|
async getSensitiveInfo(patientId, requesterUserId) {
|
|
2563
2949
|
return getSensitiveInfoUtil(this.db, patientId, requesterUserId);
|
|
@@ -2568,7 +2954,13 @@ var PatientService = class extends BaseService {
|
|
|
2568
2954
|
return this.getSensitiveInfo(profile.id, requesterUserId);
|
|
2569
2955
|
}
|
|
2570
2956
|
async updateSensitiveInfo(patientId, data, requesterUserId) {
|
|
2571
|
-
return updateSensitiveInfoUtil(
|
|
2957
|
+
return updateSensitiveInfoUtil(
|
|
2958
|
+
this.db,
|
|
2959
|
+
patientId,
|
|
2960
|
+
data,
|
|
2961
|
+
requesterUserId,
|
|
2962
|
+
this.mediaService
|
|
2963
|
+
);
|
|
2572
2964
|
}
|
|
2573
2965
|
// Metode za rad sa medicinskim informacijama
|
|
2574
2966
|
async createMedicalInfo(patientId, data) {
|
|
@@ -2701,8 +3093,8 @@ var PatientService = class extends BaseService {
|
|
|
2701
3093
|
if (!this.auth.currentUser) {
|
|
2702
3094
|
throw new Error("No authenticated user");
|
|
2703
3095
|
}
|
|
2704
|
-
const userDoc = await
|
|
2705
|
-
|
|
3096
|
+
const userDoc = await getDoc10(
|
|
3097
|
+
doc7(this.db, "users", this.auth.currentUser.uid)
|
|
2706
3098
|
);
|
|
2707
3099
|
if (!userDoc.exists()) {
|
|
2708
3100
|
throw new Error("User not found");
|
|
@@ -2737,14 +3129,113 @@ var PatientService = class extends BaseService {
|
|
|
2737
3129
|
await removeClinicUtil(this.db, patientId, clinicId);
|
|
2738
3130
|
}
|
|
2739
3131
|
// Metode za rad sa profilnom slikom
|
|
3132
|
+
/**
|
|
3133
|
+
* Uploads a profile photo for a patient
|
|
3134
|
+
* @param patientId - ID of the patient
|
|
3135
|
+
* @param file - File or Blob to upload
|
|
3136
|
+
* @returns URL of the uploaded photo
|
|
3137
|
+
*/
|
|
2740
3138
|
async uploadProfilePhoto(patientId, file) {
|
|
2741
|
-
|
|
3139
|
+
console.log(
|
|
3140
|
+
`[PatientService] Uploading profile photo for patient ${patientId}`
|
|
3141
|
+
);
|
|
3142
|
+
const mediaMetadata = await this.mediaService.uploadMedia(
|
|
3143
|
+
file,
|
|
3144
|
+
patientId,
|
|
3145
|
+
// Using patientId as ownerId
|
|
3146
|
+
"private" /* PRIVATE */,
|
|
3147
|
+
// Profile photos should be private
|
|
3148
|
+
"patient_profile_photos",
|
|
3149
|
+
file instanceof File ? file.name : `profile_photo_${patientId}`
|
|
3150
|
+
);
|
|
3151
|
+
await updateDoc7(getSensitiveInfoDocRef(this.db, patientId), {
|
|
3152
|
+
photoUrl: mediaMetadata.url,
|
|
3153
|
+
updatedAt: serverTimestamp7()
|
|
3154
|
+
});
|
|
3155
|
+
return mediaMetadata.url;
|
|
2742
3156
|
}
|
|
3157
|
+
/**
|
|
3158
|
+
* Updates a patient's profile photo (replaces existing one)
|
|
3159
|
+
* @param patientId - ID of the patient
|
|
3160
|
+
* @param file - New file or Blob to upload
|
|
3161
|
+
* @returns URL of the new uploaded photo
|
|
3162
|
+
*/
|
|
2743
3163
|
async updateProfilePhoto(patientId, file) {
|
|
2744
|
-
|
|
3164
|
+
console.log(
|
|
3165
|
+
`[PatientService] Updating profile photo for patient ${patientId}`
|
|
3166
|
+
);
|
|
3167
|
+
const currentUser = await this.getCurrentUser();
|
|
3168
|
+
const currentSensitiveInfo = await this.getSensitiveInfo(
|
|
3169
|
+
patientId,
|
|
3170
|
+
currentUser.uid
|
|
3171
|
+
);
|
|
3172
|
+
if ((currentSensitiveInfo == null ? void 0 : currentSensitiveInfo.photoUrl) && typeof currentSensitiveInfo.photoUrl === "string") {
|
|
3173
|
+
try {
|
|
3174
|
+
const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
|
|
3175
|
+
currentSensitiveInfo.photoUrl
|
|
3176
|
+
);
|
|
3177
|
+
if (existingMediaMetadata) {
|
|
3178
|
+
await this.mediaService.deleteMedia(existingMediaMetadata.id);
|
|
3179
|
+
}
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
console.warn(
|
|
3182
|
+
`[PatientService] Could not delete old profile photo for patient ${patientId}:`,
|
|
3183
|
+
error
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return this.uploadProfilePhoto(patientId, file);
|
|
2745
3188
|
}
|
|
3189
|
+
/**
|
|
3190
|
+
* Deletes a patient's profile photo
|
|
3191
|
+
* @param patientId - ID of the patient
|
|
3192
|
+
*/
|
|
2746
3193
|
async deleteProfilePhoto(patientId) {
|
|
2747
|
-
|
|
3194
|
+
console.log(
|
|
3195
|
+
`[PatientService] Deleting profile photo for patient ${patientId}`
|
|
3196
|
+
);
|
|
3197
|
+
const currentUser = await this.getCurrentUser();
|
|
3198
|
+
const currentSensitiveInfo = await this.getSensitiveInfo(
|
|
3199
|
+
patientId,
|
|
3200
|
+
currentUser.uid
|
|
3201
|
+
);
|
|
3202
|
+
if ((currentSensitiveInfo == null ? void 0 : currentSensitiveInfo.photoUrl) && typeof currentSensitiveInfo.photoUrl === "string") {
|
|
3203
|
+
try {
|
|
3204
|
+
const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
|
|
3205
|
+
currentSensitiveInfo.photoUrl
|
|
3206
|
+
);
|
|
3207
|
+
if (existingMediaMetadata) {
|
|
3208
|
+
await this.mediaService.deleteMedia(existingMediaMetadata.id);
|
|
3209
|
+
}
|
|
3210
|
+
} catch (error) {
|
|
3211
|
+
console.warn(
|
|
3212
|
+
`[PatientService] Could not delete profile photo for patient ${patientId}:`,
|
|
3213
|
+
error
|
|
3214
|
+
);
|
|
3215
|
+
}
|
|
3216
|
+
await updateDoc7(getSensitiveInfoDocRef(this.db, patientId), {
|
|
3217
|
+
photoUrl: null,
|
|
3218
|
+
updatedAt: serverTimestamp7()
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Handles profile photo upload for patients (supports MediaResource)
|
|
3224
|
+
* @param photoUrl - MediaResource (File, Blob, or URL string) from CreatePatientSensitiveInfoData
|
|
3225
|
+
* @param patientId - ID of the patient
|
|
3226
|
+
* @returns URL string of the uploaded or existing photo
|
|
3227
|
+
*/
|
|
3228
|
+
async handleProfilePhotoUpload(photoUrl, patientId) {
|
|
3229
|
+
if (!photoUrl) {
|
|
3230
|
+
return void 0;
|
|
3231
|
+
}
|
|
3232
|
+
if (typeof photoUrl === "string") {
|
|
3233
|
+
return photoUrl;
|
|
3234
|
+
}
|
|
3235
|
+
if (photoUrl instanceof File || photoUrl instanceof Blob) {
|
|
3236
|
+
return this.uploadProfilePhoto(patientId, photoUrl);
|
|
3237
|
+
}
|
|
3238
|
+
return void 0;
|
|
2748
3239
|
}
|
|
2749
3240
|
// Metode za ažuriranje profila
|
|
2750
3241
|
async updatePatientProfile(patientId, data) {
|
|
@@ -2838,16 +3329,16 @@ var PatientService = class extends BaseService {
|
|
|
2838
3329
|
|
|
2839
3330
|
// src/services/clinic/utils/admin.utils.ts
|
|
2840
3331
|
import {
|
|
2841
|
-
collection as
|
|
2842
|
-
doc as
|
|
2843
|
-
getDoc as
|
|
2844
|
-
getDocs as
|
|
2845
|
-
query as
|
|
2846
|
-
where as
|
|
2847
|
-
updateDoc as
|
|
2848
|
-
setDoc as
|
|
2849
|
-
deleteDoc,
|
|
2850
|
-
Timestamp as
|
|
3332
|
+
collection as collection6,
|
|
3333
|
+
doc as doc8,
|
|
3334
|
+
getDoc as getDoc11,
|
|
3335
|
+
getDocs as getDocs6,
|
|
3336
|
+
query as query6,
|
|
3337
|
+
where as where6,
|
|
3338
|
+
updateDoc as updateDoc8,
|
|
3339
|
+
setDoc as setDoc7,
|
|
3340
|
+
deleteDoc as deleteDoc2,
|
|
3341
|
+
Timestamp as Timestamp8,
|
|
2851
3342
|
serverTimestamp as serverTimestamp8
|
|
2852
3343
|
} from "firebase/firestore";
|
|
2853
3344
|
|
|
@@ -2976,131 +3467,131 @@ var SubscriptionModel = /* @__PURE__ */ ((SubscriptionModel2) => {
|
|
|
2976
3467
|
|
|
2977
3468
|
// src/validations/clinic.schema.ts
|
|
2978
3469
|
import { z as z13 } from "zod";
|
|
2979
|
-
import { Timestamp as
|
|
3470
|
+
import { Timestamp as Timestamp7 } from "firebase/firestore";
|
|
2980
3471
|
|
|
2981
3472
|
// src/validations/reviews.schema.ts
|
|
2982
|
-
import { z as
|
|
2983
|
-
var baseReviewSchema =
|
|
2984
|
-
id:
|
|
2985
|
-
patientId:
|
|
2986
|
-
fullReviewId:
|
|
2987
|
-
createdAt:
|
|
2988
|
-
updatedAt:
|
|
2989
|
-
comment:
|
|
2990
|
-
isVerified:
|
|
2991
|
-
isPublished:
|
|
3473
|
+
import { z as z11 } from "zod";
|
|
3474
|
+
var baseReviewSchema = z11.object({
|
|
3475
|
+
id: z11.string().min(1),
|
|
3476
|
+
patientId: z11.string().min(1),
|
|
3477
|
+
fullReviewId: z11.string().min(1),
|
|
3478
|
+
createdAt: z11.date(),
|
|
3479
|
+
updatedAt: z11.date(),
|
|
3480
|
+
comment: z11.string().min(1).max(2e3),
|
|
3481
|
+
isVerified: z11.boolean(),
|
|
3482
|
+
isPublished: z11.boolean()
|
|
2992
3483
|
});
|
|
2993
|
-
var baseReviewCreateSchema =
|
|
2994
|
-
patientId:
|
|
2995
|
-
comment:
|
|
2996
|
-
isVerified:
|
|
2997
|
-
isPublished:
|
|
3484
|
+
var baseReviewCreateSchema = z11.object({
|
|
3485
|
+
patientId: z11.string().min(1),
|
|
3486
|
+
comment: z11.string().min(1).max(2e3),
|
|
3487
|
+
isVerified: z11.boolean().default(false),
|
|
3488
|
+
isPublished: z11.boolean().default(true)
|
|
2998
3489
|
});
|
|
2999
3490
|
var clinicReviewSchema = baseReviewSchema.extend({
|
|
3000
|
-
clinicId:
|
|
3001
|
-
cleanliness:
|
|
3002
|
-
facilities:
|
|
3003
|
-
staffFriendliness:
|
|
3004
|
-
waitingTime:
|
|
3005
|
-
accessibility:
|
|
3006
|
-
overallRating:
|
|
3007
|
-
wouldRecommend:
|
|
3491
|
+
clinicId: z11.string().min(1),
|
|
3492
|
+
cleanliness: z11.number().min(1).max(5),
|
|
3493
|
+
facilities: z11.number().min(1).max(5),
|
|
3494
|
+
staffFriendliness: z11.number().min(1).max(5),
|
|
3495
|
+
waitingTime: z11.number().min(1).max(5),
|
|
3496
|
+
accessibility: z11.number().min(1).max(5),
|
|
3497
|
+
overallRating: z11.number().min(1).max(5),
|
|
3498
|
+
wouldRecommend: z11.boolean()
|
|
3008
3499
|
});
|
|
3009
3500
|
var createClinicReviewSchema = baseReviewCreateSchema.extend({
|
|
3010
|
-
clinicId:
|
|
3011
|
-
cleanliness:
|
|
3012
|
-
facilities:
|
|
3013
|
-
staffFriendliness:
|
|
3014
|
-
waitingTime:
|
|
3015
|
-
accessibility:
|
|
3016
|
-
wouldRecommend:
|
|
3501
|
+
clinicId: z11.string().min(1),
|
|
3502
|
+
cleanliness: z11.number().min(1).max(5),
|
|
3503
|
+
facilities: z11.number().min(1).max(5),
|
|
3504
|
+
staffFriendliness: z11.number().min(1).max(5),
|
|
3505
|
+
waitingTime: z11.number().min(1).max(5),
|
|
3506
|
+
accessibility: z11.number().min(1).max(5),
|
|
3507
|
+
wouldRecommend: z11.boolean()
|
|
3017
3508
|
});
|
|
3018
3509
|
var practitionerReviewSchema = baseReviewSchema.extend({
|
|
3019
|
-
practitionerId:
|
|
3020
|
-
knowledgeAndExpertise:
|
|
3021
|
-
communicationSkills:
|
|
3022
|
-
bedSideManner:
|
|
3023
|
-
thoroughness:
|
|
3024
|
-
trustworthiness:
|
|
3025
|
-
overallRating:
|
|
3026
|
-
wouldRecommend:
|
|
3510
|
+
practitionerId: z11.string().min(1),
|
|
3511
|
+
knowledgeAndExpertise: z11.number().min(1).max(5),
|
|
3512
|
+
communicationSkills: z11.number().min(1).max(5),
|
|
3513
|
+
bedSideManner: z11.number().min(1).max(5),
|
|
3514
|
+
thoroughness: z11.number().min(1).max(5),
|
|
3515
|
+
trustworthiness: z11.number().min(1).max(5),
|
|
3516
|
+
overallRating: z11.number().min(1).max(5),
|
|
3517
|
+
wouldRecommend: z11.boolean()
|
|
3027
3518
|
});
|
|
3028
3519
|
var createPractitionerReviewSchema = baseReviewCreateSchema.extend({
|
|
3029
|
-
practitionerId:
|
|
3030
|
-
knowledgeAndExpertise:
|
|
3031
|
-
communicationSkills:
|
|
3032
|
-
bedSideManner:
|
|
3033
|
-
thoroughness:
|
|
3034
|
-
trustworthiness:
|
|
3035
|
-
wouldRecommend:
|
|
3520
|
+
practitionerId: z11.string().min(1),
|
|
3521
|
+
knowledgeAndExpertise: z11.number().min(1).max(5),
|
|
3522
|
+
communicationSkills: z11.number().min(1).max(5),
|
|
3523
|
+
bedSideManner: z11.number().min(1).max(5),
|
|
3524
|
+
thoroughness: z11.number().min(1).max(5),
|
|
3525
|
+
trustworthiness: z11.number().min(1).max(5),
|
|
3526
|
+
wouldRecommend: z11.boolean()
|
|
3036
3527
|
});
|
|
3037
3528
|
var procedureReviewSchema = baseReviewSchema.extend({
|
|
3038
|
-
procedureId:
|
|
3039
|
-
effectivenessOfTreatment:
|
|
3040
|
-
outcomeExplanation:
|
|
3041
|
-
painManagement:
|
|
3042
|
-
followUpCare:
|
|
3043
|
-
valueForMoney:
|
|
3044
|
-
overallRating:
|
|
3045
|
-
wouldRecommend:
|
|
3529
|
+
procedureId: z11.string().min(1),
|
|
3530
|
+
effectivenessOfTreatment: z11.number().min(1).max(5),
|
|
3531
|
+
outcomeExplanation: z11.number().min(1).max(5),
|
|
3532
|
+
painManagement: z11.number().min(1).max(5),
|
|
3533
|
+
followUpCare: z11.number().min(1).max(5),
|
|
3534
|
+
valueForMoney: z11.number().min(1).max(5),
|
|
3535
|
+
overallRating: z11.number().min(1).max(5),
|
|
3536
|
+
wouldRecommend: z11.boolean()
|
|
3046
3537
|
});
|
|
3047
3538
|
var createProcedureReviewSchema = baseReviewCreateSchema.extend({
|
|
3048
|
-
procedureId:
|
|
3049
|
-
effectivenessOfTreatment:
|
|
3050
|
-
outcomeExplanation:
|
|
3051
|
-
painManagement:
|
|
3052
|
-
followUpCare:
|
|
3053
|
-
valueForMoney:
|
|
3054
|
-
wouldRecommend:
|
|
3539
|
+
procedureId: z11.string().min(1),
|
|
3540
|
+
effectivenessOfTreatment: z11.number().min(1).max(5),
|
|
3541
|
+
outcomeExplanation: z11.number().min(1).max(5),
|
|
3542
|
+
painManagement: z11.number().min(1).max(5),
|
|
3543
|
+
followUpCare: z11.number().min(1).max(5),
|
|
3544
|
+
valueForMoney: z11.number().min(1).max(5),
|
|
3545
|
+
wouldRecommend: z11.boolean()
|
|
3055
3546
|
});
|
|
3056
|
-
var clinicReviewInfoSchema =
|
|
3057
|
-
totalReviews:
|
|
3058
|
-
averageRating:
|
|
3059
|
-
cleanliness:
|
|
3060
|
-
facilities:
|
|
3061
|
-
staffFriendliness:
|
|
3062
|
-
waitingTime:
|
|
3063
|
-
accessibility:
|
|
3064
|
-
recommendationPercentage:
|
|
3547
|
+
var clinicReviewInfoSchema = z11.object({
|
|
3548
|
+
totalReviews: z11.number().min(0),
|
|
3549
|
+
averageRating: z11.number().min(0).max(5),
|
|
3550
|
+
cleanliness: z11.number().min(0).max(5),
|
|
3551
|
+
facilities: z11.number().min(0).max(5),
|
|
3552
|
+
staffFriendliness: z11.number().min(0).max(5),
|
|
3553
|
+
waitingTime: z11.number().min(0).max(5),
|
|
3554
|
+
accessibility: z11.number().min(0).max(5),
|
|
3555
|
+
recommendationPercentage: z11.number().min(0).max(100)
|
|
3065
3556
|
});
|
|
3066
|
-
var practitionerReviewInfoSchema =
|
|
3067
|
-
totalReviews:
|
|
3068
|
-
averageRating:
|
|
3069
|
-
knowledgeAndExpertise:
|
|
3070
|
-
communicationSkills:
|
|
3071
|
-
bedSideManner:
|
|
3072
|
-
thoroughness:
|
|
3073
|
-
trustworthiness:
|
|
3074
|
-
recommendationPercentage:
|
|
3557
|
+
var practitionerReviewInfoSchema = z11.object({
|
|
3558
|
+
totalReviews: z11.number().min(0),
|
|
3559
|
+
averageRating: z11.number().min(0).max(5),
|
|
3560
|
+
knowledgeAndExpertise: z11.number().min(0).max(5),
|
|
3561
|
+
communicationSkills: z11.number().min(0).max(5),
|
|
3562
|
+
bedSideManner: z11.number().min(0).max(5),
|
|
3563
|
+
thoroughness: z11.number().min(0).max(5),
|
|
3564
|
+
trustworthiness: z11.number().min(0).max(5),
|
|
3565
|
+
recommendationPercentage: z11.number().min(0).max(100)
|
|
3075
3566
|
});
|
|
3076
|
-
var procedureReviewInfoSchema =
|
|
3077
|
-
totalReviews:
|
|
3078
|
-
averageRating:
|
|
3079
|
-
effectivenessOfTreatment:
|
|
3080
|
-
outcomeExplanation:
|
|
3081
|
-
painManagement:
|
|
3082
|
-
followUpCare:
|
|
3083
|
-
valueForMoney:
|
|
3084
|
-
recommendationPercentage:
|
|
3567
|
+
var procedureReviewInfoSchema = z11.object({
|
|
3568
|
+
totalReviews: z11.number().min(0),
|
|
3569
|
+
averageRating: z11.number().min(0).max(5),
|
|
3570
|
+
effectivenessOfTreatment: z11.number().min(0).max(5),
|
|
3571
|
+
outcomeExplanation: z11.number().min(0).max(5),
|
|
3572
|
+
painManagement: z11.number().min(0).max(5),
|
|
3573
|
+
followUpCare: z11.number().min(0).max(5),
|
|
3574
|
+
valueForMoney: z11.number().min(0).max(5),
|
|
3575
|
+
recommendationPercentage: z11.number().min(0).max(100)
|
|
3085
3576
|
});
|
|
3086
|
-
var reviewSchema =
|
|
3087
|
-
id:
|
|
3088
|
-
appointmentId:
|
|
3089
|
-
patientId:
|
|
3090
|
-
createdAt:
|
|
3091
|
-
updatedAt:
|
|
3577
|
+
var reviewSchema = z11.object({
|
|
3578
|
+
id: z11.string().min(1),
|
|
3579
|
+
appointmentId: z11.string().min(1),
|
|
3580
|
+
patientId: z11.string().min(1),
|
|
3581
|
+
createdAt: z11.date(),
|
|
3582
|
+
updatedAt: z11.date(),
|
|
3092
3583
|
clinicReview: clinicReviewSchema.optional(),
|
|
3093
3584
|
practitionerReview: practitionerReviewSchema.optional(),
|
|
3094
3585
|
procedureReview: procedureReviewSchema.optional(),
|
|
3095
|
-
overallComment:
|
|
3096
|
-
overallRating:
|
|
3586
|
+
overallComment: z11.string().min(1).max(2e3),
|
|
3587
|
+
overallRating: z11.number().min(1).max(5)
|
|
3097
3588
|
});
|
|
3098
|
-
var createReviewSchema =
|
|
3099
|
-
patientId:
|
|
3589
|
+
var createReviewSchema = z11.object({
|
|
3590
|
+
patientId: z11.string().min(1),
|
|
3100
3591
|
clinicReview: createClinicReviewSchema.optional(),
|
|
3101
3592
|
practitionerReview: createPractitionerReviewSchema.optional(),
|
|
3102
3593
|
procedureReview: createProcedureReviewSchema.optional(),
|
|
3103
|
-
overallComment:
|
|
3594
|
+
overallComment: z11.string().min(1).max(2e3)
|
|
3104
3595
|
}).refine(
|
|
3105
3596
|
(data) => {
|
|
3106
3597
|
return data.clinicReview || data.practitionerReview || data.procedureReview;
|
|
@@ -3112,7 +3603,7 @@ var createReviewSchema = z10.object({
|
|
|
3112
3603
|
);
|
|
3113
3604
|
|
|
3114
3605
|
// src/validations/shared.schema.ts
|
|
3115
|
-
import { z as
|
|
3606
|
+
import { z as z12 } from "zod";
|
|
3116
3607
|
|
|
3117
3608
|
// src/backoffice/types/static/procedure-family.types.ts
|
|
3118
3609
|
var ProcedureFamily = /* @__PURE__ */ ((ProcedureFamily2) => {
|
|
@@ -3141,65 +3632,57 @@ var Currency = /* @__PURE__ */ ((Currency2) => {
|
|
|
3141
3632
|
})(Currency || {});
|
|
3142
3633
|
|
|
3143
3634
|
// src/validations/shared.schema.ts
|
|
3144
|
-
var sharedClinicContactInfoSchema =
|
|
3145
|
-
email:
|
|
3146
|
-
phoneNumber:
|
|
3147
|
-
alternativePhoneNumber:
|
|
3148
|
-
website:
|
|
3635
|
+
var sharedClinicContactInfoSchema = z12.object({
|
|
3636
|
+
email: z12.string().email(),
|
|
3637
|
+
phoneNumber: z12.string(),
|
|
3638
|
+
alternativePhoneNumber: z12.string().nullable().optional(),
|
|
3639
|
+
website: z12.string().nullable().optional()
|
|
3149
3640
|
});
|
|
3150
|
-
var sharedClinicLocationSchema =
|
|
3151
|
-
address:
|
|
3152
|
-
city:
|
|
3153
|
-
country:
|
|
3154
|
-
postalCode:
|
|
3155
|
-
latitude:
|
|
3156
|
-
longitude:
|
|
3157
|
-
geohash:
|
|
3641
|
+
var sharedClinicLocationSchema = z12.object({
|
|
3642
|
+
address: z12.string(),
|
|
3643
|
+
city: z12.string(),
|
|
3644
|
+
country: z12.string(),
|
|
3645
|
+
postalCode: z12.string(),
|
|
3646
|
+
latitude: z12.number().min(-90).max(90),
|
|
3647
|
+
longitude: z12.number().min(-180).max(180),
|
|
3648
|
+
geohash: z12.string().nullable().optional()
|
|
3158
3649
|
});
|
|
3159
|
-
var procedureSummaryInfoSchema =
|
|
3160
|
-
id:
|
|
3161
|
-
name:
|
|
3162
|
-
description:
|
|
3163
|
-
photo:
|
|
3164
|
-
family:
|
|
3165
|
-
categoryName:
|
|
3166
|
-
subcategoryName:
|
|
3167
|
-
technologyName:
|
|
3168
|
-
price:
|
|
3169
|
-
pricingMeasure:
|
|
3170
|
-
currency:
|
|
3171
|
-
duration:
|
|
3172
|
-
clinicId:
|
|
3173
|
-
clinicName:
|
|
3174
|
-
practitionerId:
|
|
3175
|
-
practitionerName:
|
|
3650
|
+
var procedureSummaryInfoSchema = z12.object({
|
|
3651
|
+
id: z12.string().min(1),
|
|
3652
|
+
name: z12.string().min(1),
|
|
3653
|
+
description: z12.string().optional(),
|
|
3654
|
+
photo: z12.string().optional(),
|
|
3655
|
+
family: z12.nativeEnum(ProcedureFamily),
|
|
3656
|
+
categoryName: z12.string(),
|
|
3657
|
+
subcategoryName: z12.string(),
|
|
3658
|
+
technologyName: z12.string(),
|
|
3659
|
+
price: z12.number().nonnegative(),
|
|
3660
|
+
pricingMeasure: z12.nativeEnum(PricingMeasure),
|
|
3661
|
+
currency: z12.nativeEnum(Currency),
|
|
3662
|
+
duration: z12.number().int().positive(),
|
|
3663
|
+
clinicId: z12.string().min(1),
|
|
3664
|
+
clinicName: z12.string().min(1),
|
|
3665
|
+
practitionerId: z12.string().min(1),
|
|
3666
|
+
practitionerName: z12.string().min(1)
|
|
3176
3667
|
});
|
|
3177
|
-
var clinicInfoSchema =
|
|
3178
|
-
id:
|
|
3179
|
-
featuredPhoto:
|
|
3180
|
-
name:
|
|
3181
|
-
description:
|
|
3668
|
+
var clinicInfoSchema = z12.object({
|
|
3669
|
+
id: z12.string(),
|
|
3670
|
+
featuredPhoto: z12.string(),
|
|
3671
|
+
name: z12.string(),
|
|
3672
|
+
description: z12.string().nullable().optional(),
|
|
3182
3673
|
location: sharedClinicLocationSchema,
|
|
3183
3674
|
contactInfo: sharedClinicContactInfoSchema
|
|
3184
3675
|
});
|
|
3185
|
-
var doctorInfoSchema =
|
|
3186
|
-
id:
|
|
3187
|
-
name:
|
|
3188
|
-
description:
|
|
3189
|
-
photo:
|
|
3190
|
-
rating:
|
|
3191
|
-
services:
|
|
3676
|
+
var doctorInfoSchema = z12.object({
|
|
3677
|
+
id: z12.string(),
|
|
3678
|
+
name: z12.string(),
|
|
3679
|
+
description: z12.string().nullable().optional(),
|
|
3680
|
+
photo: z12.string(),
|
|
3681
|
+
rating: z12.number().min(0).max(5),
|
|
3682
|
+
services: z12.array(z12.string())
|
|
3192
3683
|
// List of procedure IDs practitioner offers
|
|
3193
3684
|
});
|
|
3194
3685
|
|
|
3195
|
-
// src/validations/media.schema.ts
|
|
3196
|
-
import { z as z12 } from "zod";
|
|
3197
|
-
var mediaResourceSchema = z12.union([
|
|
3198
|
-
z12.string().url(),
|
|
3199
|
-
z12.instanceof(File),
|
|
3200
|
-
z12.instanceof(Blob)
|
|
3201
|
-
]);
|
|
3202
|
-
|
|
3203
3686
|
// src/validations/clinic.schema.ts
|
|
3204
3687
|
var clinicContactInfoSchema = z13.object({
|
|
3205
3688
|
email: z13.string().email(),
|
|
@@ -3259,8 +3742,8 @@ var clinicAdminSchema = z13.object({
|
|
|
3259
3742
|
clinicsManagedInfo: z13.array(clinicInfoSchema),
|
|
3260
3743
|
contactInfo: contactPersonSchema,
|
|
3261
3744
|
roleTitle: z13.string(),
|
|
3262
|
-
createdAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3263
|
-
updatedAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3745
|
+
createdAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3746
|
+
updatedAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3264
3747
|
isActive: z13.boolean()
|
|
3265
3748
|
});
|
|
3266
3749
|
var adminTokenSchema = z13.object({
|
|
@@ -3269,9 +3752,9 @@ var adminTokenSchema = z13.object({
|
|
|
3269
3752
|
email: z13.string().email().optional().nullable(),
|
|
3270
3753
|
status: z13.nativeEnum(AdminTokenStatus),
|
|
3271
3754
|
usedByUserRef: z13.string().optional(),
|
|
3272
|
-
createdAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3755
|
+
createdAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3273
3756
|
// Timestamp
|
|
3274
|
-
expiresAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3757
|
+
expiresAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7))
|
|
3275
3758
|
// Timestamp
|
|
3276
3759
|
});
|
|
3277
3760
|
var createAdminTokenSchema = z13.object({
|
|
@@ -3291,9 +3774,9 @@ var clinicGroupSchema = z13.object({
|
|
|
3291
3774
|
adminsInfo: z13.array(adminInfoSchema),
|
|
3292
3775
|
adminTokens: z13.array(adminTokenSchema),
|
|
3293
3776
|
ownerId: z13.string().nullable(),
|
|
3294
|
-
createdAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3777
|
+
createdAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3295
3778
|
// Timestamp
|
|
3296
|
-
updatedAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3779
|
+
updatedAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3297
3780
|
// Timestamp
|
|
3298
3781
|
isActive: z13.boolean(),
|
|
3299
3782
|
logo: mediaResourceSchema.optional().nullable(),
|
|
@@ -3335,9 +3818,9 @@ var clinicSchema = z13.object({
|
|
|
3335
3818
|
// Use the correct schema for aggregated procedure info
|
|
3336
3819
|
reviewInfo: clinicReviewInfoSchema,
|
|
3337
3820
|
admins: z13.array(z13.string()),
|
|
3338
|
-
createdAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3821
|
+
createdAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3339
3822
|
// Timestamp
|
|
3340
|
-
updatedAt: z13.instanceof(Date).or(z13.instanceof(
|
|
3823
|
+
updatedAt: z13.instanceof(Date).or(z13.instanceof(Timestamp7)),
|
|
3341
3824
|
// Timestamp
|
|
3342
3825
|
isActive: z13.boolean(),
|
|
3343
3826
|
isVerified: z13.boolean(),
|
|
@@ -3559,7 +4042,7 @@ async function createClinicAdmin(db, data, clinicGroupService) {
|
|
|
3559
4042
|
}
|
|
3560
4043
|
console.log("[CLINIC_ADMIN] Preparing admin data object");
|
|
3561
4044
|
const adminData = {
|
|
3562
|
-
id:
|
|
4045
|
+
id: doc8(collection6(db, CLINIC_ADMINS_COLLECTION)).id,
|
|
3563
4046
|
// Generate a new ID for the admin document
|
|
3564
4047
|
userRef: validatedData.userRef,
|
|
3565
4048
|
clinicGroupId: clinicGroupId || "",
|
|
@@ -3579,8 +4062,8 @@ async function createClinicAdmin(db, data, clinicGroupService) {
|
|
|
3579
4062
|
try {
|
|
3580
4063
|
clinicAdminSchema.parse({
|
|
3581
4064
|
...adminData,
|
|
3582
|
-
createdAt:
|
|
3583
|
-
updatedAt:
|
|
4065
|
+
createdAt: Timestamp8.now(),
|
|
4066
|
+
updatedAt: Timestamp8.now()
|
|
3584
4067
|
});
|
|
3585
4068
|
console.log("[CLINIC_ADMIN] Admin object validation passed");
|
|
3586
4069
|
} catch (schemaError) {
|
|
@@ -3594,7 +4077,7 @@ async function createClinicAdmin(db, data, clinicGroupService) {
|
|
|
3594
4077
|
adminId: adminData.id
|
|
3595
4078
|
});
|
|
3596
4079
|
try {
|
|
3597
|
-
await
|
|
4080
|
+
await setDoc7(doc8(db, CLINIC_ADMINS_COLLECTION, adminData.id), adminData);
|
|
3598
4081
|
console.log("[CLINIC_ADMIN] Admin saved successfully");
|
|
3599
4082
|
} catch (firestoreError) {
|
|
3600
4083
|
console.error(
|
|
@@ -3643,30 +4126,30 @@ async function checkClinicGroupExists(db, groupId, clinicGroupService) {
|
|
|
3643
4126
|
return !!group;
|
|
3644
4127
|
}
|
|
3645
4128
|
async function getClinicAdmin(db, adminId) {
|
|
3646
|
-
const docRef =
|
|
3647
|
-
const docSnap = await
|
|
4129
|
+
const docRef = doc8(db, CLINIC_ADMINS_COLLECTION, adminId);
|
|
4130
|
+
const docSnap = await getDoc11(docRef);
|
|
3648
4131
|
if (docSnap.exists()) {
|
|
3649
4132
|
return docSnap.data();
|
|
3650
4133
|
}
|
|
3651
4134
|
return null;
|
|
3652
4135
|
}
|
|
3653
4136
|
async function getClinicAdminByUserRef(db, userRef) {
|
|
3654
|
-
const q =
|
|
3655
|
-
|
|
3656
|
-
|
|
4137
|
+
const q = query6(
|
|
4138
|
+
collection6(db, CLINIC_ADMINS_COLLECTION),
|
|
4139
|
+
where6("userRef", "==", userRef)
|
|
3657
4140
|
);
|
|
3658
|
-
const querySnapshot = await
|
|
4141
|
+
const querySnapshot = await getDocs6(q);
|
|
3659
4142
|
if (querySnapshot.empty) {
|
|
3660
4143
|
return null;
|
|
3661
4144
|
}
|
|
3662
4145
|
return querySnapshot.docs[0].data();
|
|
3663
4146
|
}
|
|
3664
4147
|
async function getClinicAdminsByGroup(db, clinicGroupId) {
|
|
3665
|
-
const q =
|
|
3666
|
-
|
|
3667
|
-
|
|
4148
|
+
const q = query6(
|
|
4149
|
+
collection6(db, CLINIC_ADMINS_COLLECTION),
|
|
4150
|
+
where6("clinicGroupId", "==", clinicGroupId)
|
|
3668
4151
|
);
|
|
3669
|
-
const querySnapshot = await
|
|
4152
|
+
const querySnapshot = await getDocs6(q);
|
|
3670
4153
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
3671
4154
|
}
|
|
3672
4155
|
async function updateClinicAdmin(db, adminId, data) {
|
|
@@ -3678,7 +4161,7 @@ async function updateClinicAdmin(db, adminId, data) {
|
|
|
3678
4161
|
...data,
|
|
3679
4162
|
updatedAt: serverTimestamp8()
|
|
3680
4163
|
};
|
|
3681
|
-
await
|
|
4164
|
+
await updateDoc8(doc8(db, CLINIC_ADMINS_COLLECTION, adminId), updatedData);
|
|
3682
4165
|
const updatedAdmin = await getClinicAdmin(db, adminId);
|
|
3683
4166
|
if (!updatedAdmin) {
|
|
3684
4167
|
throw new Error("Failed to retrieve updated admin");
|
|
@@ -3690,7 +4173,7 @@ async function deleteClinicAdmin(db, adminId) {
|
|
|
3690
4173
|
if (!admin) {
|
|
3691
4174
|
throw new Error("Clinic admin not found");
|
|
3692
4175
|
}
|
|
3693
|
-
await
|
|
4176
|
+
await deleteDoc2(doc8(db, CLINIC_ADMINS_COLLECTION, adminId));
|
|
3694
4177
|
}
|
|
3695
4178
|
async function addClinicToManaged(db, adminId, clinicId, requesterId, clinicService) {
|
|
3696
4179
|
const admin = await getClinicAdmin(db, adminId);
|
|
@@ -3861,413 +4344,97 @@ var ClinicAdminService = class extends BaseService {
|
|
|
3861
4344
|
async getClinicAdmin(adminId) {
|
|
3862
4345
|
return getClinicAdmin(this.db, adminId);
|
|
3863
4346
|
}
|
|
3864
|
-
/**
|
|
3865
|
-
* Dohvata admin profil po korisničkom ID-u
|
|
3866
|
-
*/
|
|
3867
|
-
async getClinicAdminByUserRef(userRef) {
|
|
3868
|
-
return getClinicAdminByUserRef(this.db, userRef);
|
|
3869
|
-
}
|
|
3870
|
-
/**
|
|
3871
|
-
* Dohvata sve admine u grupi
|
|
3872
|
-
*/
|
|
3873
|
-
async getClinicAdminsByGroup(clinicGroupId) {
|
|
3874
|
-
return getClinicAdminsByGroup(this.db, clinicGroupId);
|
|
3875
|
-
}
|
|
3876
|
-
/**
|
|
3877
|
-
* Ažurira admin profil
|
|
3878
|
-
*/
|
|
3879
|
-
async updateClinicAdmin(adminId, data) {
|
|
3880
|
-
return updateClinicAdmin(this.db, adminId, data);
|
|
3881
|
-
}
|
|
3882
|
-
/**
|
|
3883
|
-
* Briše admin profil
|
|
3884
|
-
*/
|
|
3885
|
-
async deleteClinicAdmin(adminId) {
|
|
3886
|
-
return deleteClinicAdmin(this.db, adminId);
|
|
3887
|
-
}
|
|
3888
|
-
/**
|
|
3889
|
-
* Dodaje kliniku u listu klinika kojima admin upravlja
|
|
3890
|
-
*/
|
|
3891
|
-
async addClinicToManaged(adminId, clinicId, requesterId) {
|
|
3892
|
-
return addClinicToManaged(
|
|
3893
|
-
this.db,
|
|
3894
|
-
adminId,
|
|
3895
|
-
clinicId,
|
|
3896
|
-
requesterId,
|
|
3897
|
-
this.getClinicService()
|
|
3898
|
-
);
|
|
3899
|
-
}
|
|
3900
|
-
/**
|
|
3901
|
-
* Uklanja kliniku iz liste klinika kojima admin upravlja
|
|
3902
|
-
*/
|
|
3903
|
-
async removeClinicFromManaged(adminId, clinicId, requesterId) {
|
|
3904
|
-
return removeClinicFromManaged(
|
|
3905
|
-
this.db,
|
|
3906
|
-
adminId,
|
|
3907
|
-
clinicId,
|
|
3908
|
-
requesterId
|
|
3909
|
-
);
|
|
3910
|
-
}
|
|
3911
|
-
/**
|
|
3912
|
-
* Dohvata sve klinike kojima admin upravlja
|
|
3913
|
-
*/
|
|
3914
|
-
async getManagedClinics(adminId) {
|
|
3915
|
-
return getManagedClinics(
|
|
3916
|
-
this.db,
|
|
3917
|
-
adminId,
|
|
3918
|
-
this.getClinicService()
|
|
3919
|
-
);
|
|
3920
|
-
}
|
|
3921
|
-
/**
|
|
3922
|
-
* Dohvata sve aktivne klinike kojima admin upravlja
|
|
3923
|
-
*/
|
|
3924
|
-
async getActiveManagedClinics(adminId) {
|
|
3925
|
-
return getActiveManagedClinics(
|
|
3926
|
-
this.db,
|
|
3927
|
-
adminId,
|
|
3928
|
-
this.getClinicService()
|
|
3929
|
-
);
|
|
3930
|
-
}
|
|
3931
|
-
// TODO: Add more methods for clinic admins for managing permissions, editing profiles by the admin, or by the clinic group owner and so on
|
|
3932
|
-
// Generally refactor admin permissions and clinic group permissions and systems for admin management
|
|
3933
|
-
};
|
|
3934
|
-
|
|
3935
|
-
// src/services/practitioner/practitioner.service.ts
|
|
3936
|
-
import {
|
|
3937
|
-
collection as collection7,
|
|
3938
|
-
doc as doc9,
|
|
3939
|
-
getDoc as getDoc12,
|
|
3940
|
-
getDocs as getDocs7,
|
|
3941
|
-
query as query7,
|
|
3942
|
-
where as where7,
|
|
3943
|
-
updateDoc as updateDoc9,
|
|
3944
|
-
setDoc as setDoc8,
|
|
3945
|
-
deleteDoc as deleteDoc3,
|
|
3946
|
-
Timestamp as Timestamp10,
|
|
3947
|
-
serverTimestamp as serverTimestamp9,
|
|
3948
|
-
limit as limit5,
|
|
3949
|
-
startAfter as startAfter4,
|
|
3950
|
-
orderBy as orderBy2,
|
|
3951
|
-
arrayUnion as arrayUnion5,
|
|
3952
|
-
arrayRemove as arrayRemove4
|
|
3953
|
-
} from "firebase/firestore";
|
|
3954
|
-
|
|
3955
|
-
// src/services/media/media.service.ts
|
|
3956
|
-
import { Timestamp as Timestamp8 } from "firebase/firestore";
|
|
3957
|
-
import {
|
|
3958
|
-
ref as ref2,
|
|
3959
|
-
uploadBytes as uploadBytes2,
|
|
3960
|
-
getDownloadURL as getDownloadURL2,
|
|
3961
|
-
deleteObject as deleteObject2,
|
|
3962
|
-
getBytes
|
|
3963
|
-
} from "firebase/storage";
|
|
3964
|
-
import {
|
|
3965
|
-
doc as doc8,
|
|
3966
|
-
getDoc as getDoc11,
|
|
3967
|
-
setDoc as setDoc7,
|
|
3968
|
-
updateDoc as updateDoc8,
|
|
3969
|
-
collection as collection6,
|
|
3970
|
-
query as query6,
|
|
3971
|
-
where as where6,
|
|
3972
|
-
limit as limit4,
|
|
3973
|
-
getDocs as getDocs6,
|
|
3974
|
-
deleteDoc as deleteDoc2,
|
|
3975
|
-
orderBy
|
|
3976
|
-
} from "firebase/firestore";
|
|
3977
|
-
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
3978
|
-
MediaAccessLevel2["PUBLIC"] = "public";
|
|
3979
|
-
MediaAccessLevel2["PRIVATE"] = "private";
|
|
3980
|
-
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
3981
|
-
return MediaAccessLevel2;
|
|
3982
|
-
})(MediaAccessLevel || {});
|
|
3983
|
-
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
3984
|
-
var MediaService = class extends BaseService {
|
|
3985
|
-
constructor(db, auth, app) {
|
|
3986
|
-
super(db, auth, app);
|
|
3987
|
-
}
|
|
3988
|
-
/**
|
|
3989
|
-
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
3990
|
-
* @param file - The file to upload.
|
|
3991
|
-
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
3992
|
-
* @param accessLevel - Access level (public, private, confidential).
|
|
3993
|
-
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
3994
|
-
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
3995
|
-
* @returns Promise with the media metadata.
|
|
3996
|
-
*/
|
|
3997
|
-
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
3998
|
-
const mediaId = this.generateId();
|
|
3999
|
-
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
4000
|
-
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
4001
|
-
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
4002
|
-
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
4003
|
-
const storageRef = ref2(this.storage, filePath);
|
|
4004
|
-
try {
|
|
4005
|
-
const uploadResult = await uploadBytes2(storageRef, file, {
|
|
4006
|
-
contentType: file.type
|
|
4007
|
-
});
|
|
4008
|
-
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
4009
|
-
const downloadURL = await getDownloadURL2(uploadResult.ref);
|
|
4010
|
-
console.log("[MediaService] Got download URL:", downloadURL);
|
|
4011
|
-
const metadata = {
|
|
4012
|
-
id: mediaId,
|
|
4013
|
-
name: fileNameToUse,
|
|
4014
|
-
url: downloadURL,
|
|
4015
|
-
contentType: file.type,
|
|
4016
|
-
size: file.size,
|
|
4017
|
-
createdAt: Timestamp8.now(),
|
|
4018
|
-
accessLevel,
|
|
4019
|
-
ownerId,
|
|
4020
|
-
collectionName,
|
|
4021
|
-
path: filePath
|
|
4022
|
-
};
|
|
4023
|
-
const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4024
|
-
await setDoc7(metadataDocRef, metadata);
|
|
4025
|
-
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
4026
|
-
return metadata;
|
|
4027
|
-
} catch (error) {
|
|
4028
|
-
console.error("[MediaService] Error during media upload:", error);
|
|
4029
|
-
throw error;
|
|
4030
|
-
}
|
|
4347
|
+
/**
|
|
4348
|
+
* Dohvata admin profil po korisničkom ID-u
|
|
4349
|
+
*/
|
|
4350
|
+
async getClinicAdminByUserRef(userRef) {
|
|
4351
|
+
return getClinicAdminByUserRef(this.db, userRef);
|
|
4031
4352
|
}
|
|
4032
4353
|
/**
|
|
4033
|
-
*
|
|
4034
|
-
* @param mediaId - ID of the media.
|
|
4035
|
-
* @returns Promise with the media metadata or null if not found.
|
|
4354
|
+
* Dohvata sve admine u grupi
|
|
4036
4355
|
*/
|
|
4037
|
-
async
|
|
4038
|
-
|
|
4039
|
-
const docRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4040
|
-
const docSnap = await getDoc11(docRef);
|
|
4041
|
-
if (docSnap.exists()) {
|
|
4042
|
-
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
4043
|
-
return docSnap.data();
|
|
4044
|
-
}
|
|
4045
|
-
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
4046
|
-
return null;
|
|
4356
|
+
async getClinicAdminsByGroup(clinicGroupId) {
|
|
4357
|
+
return getClinicAdminsByGroup(this.db, clinicGroupId);
|
|
4047
4358
|
}
|
|
4048
4359
|
/**
|
|
4049
|
-
*
|
|
4050
|
-
* @param url - The public URL of the media file.
|
|
4051
|
-
* @returns Promise with the media metadata or null if not found.
|
|
4360
|
+
* Ažurira admin profil
|
|
4052
4361
|
*/
|
|
4053
|
-
async
|
|
4054
|
-
|
|
4055
|
-
const q = query6(
|
|
4056
|
-
collection6(this.db, MEDIA_METADATA_COLLECTION),
|
|
4057
|
-
where6("url", "==", url),
|
|
4058
|
-
limit4(1)
|
|
4059
|
-
);
|
|
4060
|
-
try {
|
|
4061
|
-
const querySnapshot = await getDocs6(q);
|
|
4062
|
-
if (!querySnapshot.empty) {
|
|
4063
|
-
const metadata = querySnapshot.docs[0].data();
|
|
4064
|
-
console.log("[MediaService] Metadata found by URL:", metadata);
|
|
4065
|
-
return metadata;
|
|
4066
|
-
}
|
|
4067
|
-
console.log("[MediaService] No metadata found for URL:", url);
|
|
4068
|
-
return null;
|
|
4069
|
-
} catch (error) {
|
|
4070
|
-
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
4071
|
-
throw error;
|
|
4072
|
-
}
|
|
4362
|
+
async updateClinicAdmin(adminId, data) {
|
|
4363
|
+
return updateClinicAdmin(this.db, adminId, data);
|
|
4073
4364
|
}
|
|
4074
4365
|
/**
|
|
4075
|
-
*
|
|
4076
|
-
* @param mediaId - ID of the media to delete.
|
|
4366
|
+
* Briše admin profil
|
|
4077
4367
|
*/
|
|
4078
|
-
async
|
|
4079
|
-
|
|
4080
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
4081
|
-
if (!metadata) {
|
|
4082
|
-
console.warn(
|
|
4083
|
-
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
4084
|
-
);
|
|
4085
|
-
return;
|
|
4086
|
-
}
|
|
4087
|
-
const storageFileRef = ref2(this.storage, metadata.path);
|
|
4088
|
-
try {
|
|
4089
|
-
await deleteObject2(storageFileRef);
|
|
4090
|
-
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
4091
|
-
const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4092
|
-
await deleteDoc2(metadataDocRef);
|
|
4093
|
-
console.log(
|
|
4094
|
-
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
4095
|
-
);
|
|
4096
|
-
} catch (error) {
|
|
4097
|
-
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
4098
|
-
throw error;
|
|
4099
|
-
}
|
|
4368
|
+
async deleteClinicAdmin(adminId) {
|
|
4369
|
+
return deleteClinicAdmin(this.db, adminId);
|
|
4100
4370
|
}
|
|
4101
4371
|
/**
|
|
4102
|
-
*
|
|
4103
|
-
* to a new path reflecting the new access level, and updating its metadata.
|
|
4104
|
-
* @param mediaId - ID of the media to update.
|
|
4105
|
-
* @param newAccessLevel - New access level.
|
|
4106
|
-
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
4372
|
+
* Dodaje kliniku u listu klinika kojima admin upravlja
|
|
4107
4373
|
*/
|
|
4108
|
-
async
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4374
|
+
async addClinicToManaged(adminId, clinicId, requesterId) {
|
|
4375
|
+
return addClinicToManaged(
|
|
4376
|
+
this.db,
|
|
4377
|
+
adminId,
|
|
4378
|
+
clinicId,
|
|
4379
|
+
requesterId,
|
|
4380
|
+
this.getClinicService()
|
|
4112
4381
|
);
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
);
|
|
4124
|
-
const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4125
|
-
try {
|
|
4126
|
-
await updateDoc8(metadataDocRef, { updatedAt: Timestamp8.now() });
|
|
4127
|
-
return { ...metadata, updatedAt: Timestamp8.now() };
|
|
4128
|
-
} catch (error) {
|
|
4129
|
-
console.error(
|
|
4130
|
-
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
4131
|
-
error
|
|
4132
|
-
);
|
|
4133
|
-
throw error;
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
const oldStoragePath = metadata.path;
|
|
4137
|
-
const fileNamePart = `${metadata.id}-${metadata.name}`;
|
|
4138
|
-
const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
|
|
4139
|
-
console.log(
|
|
4140
|
-
`[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
|
|
4382
|
+
}
|
|
4383
|
+
/**
|
|
4384
|
+
* Uklanja kliniku iz liste klinika kojima admin upravlja
|
|
4385
|
+
*/
|
|
4386
|
+
async removeClinicFromManaged(adminId, clinicId, requesterId) {
|
|
4387
|
+
return removeClinicFromManaged(
|
|
4388
|
+
this.db,
|
|
4389
|
+
adminId,
|
|
4390
|
+
clinicId,
|
|
4391
|
+
requesterId
|
|
4141
4392
|
);
|
|
4142
|
-
const oldStorageFileRef = ref2(this.storage, oldStoragePath);
|
|
4143
|
-
const newStorageFileRef = ref2(this.storage, newStoragePath);
|
|
4144
|
-
try {
|
|
4145
|
-
console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
|
|
4146
|
-
const fileBytes = await getBytes(oldStorageFileRef);
|
|
4147
|
-
console.log(
|
|
4148
|
-
`[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
|
|
4149
|
-
);
|
|
4150
|
-
console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
|
|
4151
|
-
await uploadBytes2(newStorageFileRef, fileBytes, {
|
|
4152
|
-
contentType: metadata.contentType
|
|
4153
|
-
});
|
|
4154
|
-
console.log(
|
|
4155
|
-
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
4156
|
-
);
|
|
4157
|
-
const newDownloadURL = await getDownloadURL2(newStorageFileRef);
|
|
4158
|
-
console.log(
|
|
4159
|
-
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
4160
|
-
);
|
|
4161
|
-
const updateData = {
|
|
4162
|
-
accessLevel: newAccessLevel,
|
|
4163
|
-
path: newStoragePath,
|
|
4164
|
-
url: newDownloadURL,
|
|
4165
|
-
updatedAt: Timestamp8.now()
|
|
4166
|
-
};
|
|
4167
|
-
const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4168
|
-
console.log(
|
|
4169
|
-
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
4170
|
-
updateData
|
|
4171
|
-
);
|
|
4172
|
-
await updateDoc8(metadataDocRef, updateData);
|
|
4173
|
-
console.log(
|
|
4174
|
-
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
4175
|
-
);
|
|
4176
|
-
try {
|
|
4177
|
-
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
4178
|
-
await deleteObject2(oldStorageFileRef);
|
|
4179
|
-
console.log(
|
|
4180
|
-
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
4181
|
-
);
|
|
4182
|
-
} catch (deleteError) {
|
|
4183
|
-
console.error(
|
|
4184
|
-
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
4185
|
-
deleteError
|
|
4186
|
-
);
|
|
4187
|
-
}
|
|
4188
|
-
return { ...metadata, ...updateData };
|
|
4189
|
-
} catch (error) {
|
|
4190
|
-
console.error(
|
|
4191
|
-
`[MediaService] Error updating media access level and moving file for ${mediaId}:`,
|
|
4192
|
-
error
|
|
4193
|
-
);
|
|
4194
|
-
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
4195
|
-
console.warn(
|
|
4196
|
-
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
4197
|
-
);
|
|
4198
|
-
try {
|
|
4199
|
-
await deleteObject2(newStorageFileRef);
|
|
4200
|
-
console.warn(
|
|
4201
|
-
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
4202
|
-
);
|
|
4203
|
-
} catch (cleanupError) {
|
|
4204
|
-
console.error(
|
|
4205
|
-
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
4206
|
-
cleanupError
|
|
4207
|
-
);
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
throw error;
|
|
4211
|
-
}
|
|
4212
4393
|
}
|
|
4213
4394
|
/**
|
|
4214
|
-
*
|
|
4215
|
-
* @param ownerId - ID of the owner.
|
|
4216
|
-
* @param collectionName - Optional: Filter by collection name.
|
|
4217
|
-
* @param accessLevel - Optional: Filter by access level.
|
|
4218
|
-
* @param count - Optional: Number of items to fetch.
|
|
4219
|
-
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
4395
|
+
* Dohvata sve klinike kojima admin upravlja
|
|
4220
4396
|
*/
|
|
4221
|
-
async
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
}
|
|
4227
|
-
if (accessLevel) {
|
|
4228
|
-
qConstraints.push(where6("accessLevel", "==", accessLevel));
|
|
4229
|
-
}
|
|
4230
|
-
qConstraints.push(orderBy("createdAt", "desc"));
|
|
4231
|
-
if (count) {
|
|
4232
|
-
qConstraints.push(limit4(count));
|
|
4233
|
-
}
|
|
4234
|
-
if (startAfterId) {
|
|
4235
|
-
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
4236
|
-
if (startAfterDoc) {
|
|
4237
|
-
}
|
|
4238
|
-
}
|
|
4239
|
-
const finalQuery = query6(
|
|
4240
|
-
collection6(this.db, MEDIA_METADATA_COLLECTION),
|
|
4241
|
-
...qConstraints
|
|
4397
|
+
async getManagedClinics(adminId) {
|
|
4398
|
+
return getManagedClinics(
|
|
4399
|
+
this.db,
|
|
4400
|
+
adminId,
|
|
4401
|
+
this.getClinicService()
|
|
4242
4402
|
);
|
|
4243
|
-
try {
|
|
4244
|
-
const querySnapshot = await getDocs6(finalQuery);
|
|
4245
|
-
const mediaList = querySnapshot.docs.map(
|
|
4246
|
-
(doc34) => doc34.data()
|
|
4247
|
-
);
|
|
4248
|
-
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
4249
|
-
return mediaList;
|
|
4250
|
-
} catch (error) {
|
|
4251
|
-
console.error("[MediaService] Error listing media:", error);
|
|
4252
|
-
throw error;
|
|
4253
|
-
}
|
|
4254
4403
|
}
|
|
4255
4404
|
/**
|
|
4256
|
-
*
|
|
4257
|
-
* @param mediaId - ID of the media.
|
|
4405
|
+
* Dohvata sve aktivne klinike kojima admin upravlja
|
|
4258
4406
|
*/
|
|
4259
|
-
async
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
}
|
|
4266
|
-
console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
|
|
4267
|
-
return null;
|
|
4407
|
+
async getActiveManagedClinics(adminId) {
|
|
4408
|
+
return getActiveManagedClinics(
|
|
4409
|
+
this.db,
|
|
4410
|
+
adminId,
|
|
4411
|
+
this.getClinicService()
|
|
4412
|
+
);
|
|
4268
4413
|
}
|
|
4414
|
+
// TODO: Add more methods for clinic admins for managing permissions, editing profiles by the admin, or by the clinic group owner and so on
|
|
4415
|
+
// Generally refactor admin permissions and clinic group permissions and systems for admin management
|
|
4269
4416
|
};
|
|
4270
4417
|
|
|
4418
|
+
// src/services/practitioner/practitioner.service.ts
|
|
4419
|
+
import {
|
|
4420
|
+
collection as collection7,
|
|
4421
|
+
doc as doc9,
|
|
4422
|
+
getDoc as getDoc12,
|
|
4423
|
+
getDocs as getDocs7,
|
|
4424
|
+
query as query7,
|
|
4425
|
+
where as where7,
|
|
4426
|
+
updateDoc as updateDoc9,
|
|
4427
|
+
setDoc as setDoc8,
|
|
4428
|
+
deleteDoc as deleteDoc3,
|
|
4429
|
+
Timestamp as Timestamp10,
|
|
4430
|
+
serverTimestamp as serverTimestamp9,
|
|
4431
|
+
limit as limit5,
|
|
4432
|
+
startAfter as startAfter4,
|
|
4433
|
+
orderBy as orderBy2,
|
|
4434
|
+
arrayUnion as arrayUnion5,
|
|
4435
|
+
arrayRemove as arrayRemove4
|
|
4436
|
+
} from "firebase/firestore";
|
|
4437
|
+
|
|
4271
4438
|
// src/validations/practitioner.schema.ts
|
|
4272
4439
|
import { z as z14 } from "zod";
|
|
4273
4440
|
import { Timestamp as Timestamp9 } from "firebase/firestore";
|
|
@@ -5567,10 +5734,10 @@ import { z as z17 } from "zod";
|
|
|
5567
5734
|
// src/services/clinic/utils/photos.utils.ts
|
|
5568
5735
|
import {
|
|
5569
5736
|
getStorage as getStorage3,
|
|
5570
|
-
ref as
|
|
5571
|
-
uploadBytes as
|
|
5572
|
-
getDownloadURL as
|
|
5573
|
-
deleteObject as
|
|
5737
|
+
ref as ref2,
|
|
5738
|
+
uploadBytes as uploadBytes2,
|
|
5739
|
+
getDownloadURL as getDownloadURL2,
|
|
5740
|
+
deleteObject as deleteObject2
|
|
5574
5741
|
} from "firebase/storage";
|
|
5575
5742
|
async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName) {
|
|
5576
5743
|
if (!photo || typeof photo !== "string" || !photo.startsWith("data:")) {
|
|
@@ -5582,7 +5749,7 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
|
|
|
5582
5749
|
);
|
|
5583
5750
|
const storage = getStorage3(app);
|
|
5584
5751
|
const storageFileName = fileName || `${photoType}-${Date.now()}`;
|
|
5585
|
-
const storageRef =
|
|
5752
|
+
const storageRef = ref2(
|
|
5586
5753
|
storage,
|
|
5587
5754
|
`${entityType}/${entityId}/${storageFileName}`
|
|
5588
5755
|
);
|
|
@@ -5594,8 +5761,8 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
|
|
|
5594
5761
|
byteArrays.push(byteCharacters.charCodeAt(i));
|
|
5595
5762
|
}
|
|
5596
5763
|
const blob = new Blob([new Uint8Array(byteArrays)], { type: contentType });
|
|
5597
|
-
await
|
|
5598
|
-
const downloadUrl = await
|
|
5764
|
+
await uploadBytes2(storageRef, blob, { contentType });
|
|
5765
|
+
const downloadUrl = await getDownloadURL2(storageRef);
|
|
5599
5766
|
console.log(`[PHOTO_UTILS] ${photoType} uploaded successfully`, {
|
|
5600
5767
|
downloadUrl
|
|
5601
5768
|
});
|