@blackcode_sa/metaestetics-api 1.7.19 → 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 +1 -2
- package/dist/admin/index.d.ts +1 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/backoffice/index.d.mts +6 -1
- package/dist/backoffice/index.d.ts +6 -1
- package/dist/index.d.mts +80 -45
- package/dist/index.d.ts +80 -45
- package/dist/index.js +1635 -1407
- package/dist/index.mjs +1656 -1426
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +4 -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/services/practitioner/practitioner.service.ts +92 -3
- package/src/services/procedure/procedure.service.ts +8 -2
- package/src/types/patient/index.ts +25 -23
- package/src/types/practitioner/index.ts +2 -1
- package/src/validations/media.schema.ts +1 -1
- package/src/validations/patient.schema.ts +4 -4
- package/src/validations/practitioner.schema.ts +3 -2
package/dist/index.mjs
CHANGED
|
@@ -877,16 +877,16 @@ var BaseService = class {
|
|
|
877
877
|
|
|
878
878
|
// src/services/user.service.ts
|
|
879
879
|
import {
|
|
880
|
-
collection as
|
|
881
|
-
doc as
|
|
882
|
-
getDoc as
|
|
883
|
-
getDocs as
|
|
884
|
-
query as
|
|
885
|
-
where as
|
|
886
|
-
updateDoc as
|
|
887
|
-
deleteDoc as
|
|
888
|
-
Timestamp as
|
|
889
|
-
setDoc as
|
|
880
|
+
collection as collection8,
|
|
881
|
+
doc as doc10,
|
|
882
|
+
getDoc as getDoc13,
|
|
883
|
+
getDocs as getDocs8,
|
|
884
|
+
query as query8,
|
|
885
|
+
where as where8,
|
|
886
|
+
updateDoc as updateDoc10,
|
|
887
|
+
deleteDoc as deleteDoc4,
|
|
888
|
+
Timestamp as Timestamp11,
|
|
889
|
+
setDoc as setDoc9,
|
|
890
890
|
serverTimestamp as serverTimestamp10
|
|
891
891
|
} from "firebase/firestore";
|
|
892
892
|
|
|
@@ -987,432 +987,788 @@ 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
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
// src/services/patient/utils/profile.utils.ts
|
|
996
|
-
import {
|
|
997
|
-
getDoc as getDoc5,
|
|
998
|
-
setDoc as setDoc4,
|
|
999
|
-
updateDoc as updateDoc3,
|
|
1000
|
-
arrayUnion as arrayUnion2,
|
|
1001
|
-
arrayRemove as arrayRemove2,
|
|
1002
|
-
serverTimestamp as serverTimestamp4,
|
|
1003
|
-
increment,
|
|
1004
|
-
Timestamp as Timestamp4,
|
|
1005
|
-
collection as collection3,
|
|
1006
|
-
query as query3,
|
|
1007
|
-
where as where3,
|
|
1008
|
-
getDocs as getDocs3,
|
|
1009
|
-
limit as limit2,
|
|
1010
|
-
startAfter as startAfter2,
|
|
1011
|
-
doc as doc4
|
|
990
|
+
doc as doc7,
|
|
991
|
+
getDoc as getDoc10,
|
|
992
|
+
writeBatch,
|
|
993
|
+
updateDoc as updateDoc7,
|
|
994
|
+
serverTimestamp as serverTimestamp7
|
|
1012
995
|
} from "firebase/firestore";
|
|
1013
|
-
import { ref, uploadBytes, getDownloadURL, deleteObject } from "firebase/storage";
|
|
1014
|
-
import { z as z8 } from "zod";
|
|
1015
|
-
|
|
1016
|
-
// src/types/patient/medical-info.types.ts
|
|
1017
|
-
var PATIENT_MEDICAL_INFO_COLLECTION = "medical_info";
|
|
1018
|
-
var DEFAULT_MEDICAL_INFO = {
|
|
1019
|
-
vitalStats: {},
|
|
1020
|
-
blockingConditions: [],
|
|
1021
|
-
contraindications: [],
|
|
1022
|
-
allergies: [],
|
|
1023
|
-
currentMedications: []
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
// src/types/patient/index.ts
|
|
1027
|
-
var PATIENTS_COLLECTION = "patients";
|
|
1028
|
-
var PATIENT_SENSITIVE_INFO_COLLECTION = "sensitive-info";
|
|
1029
|
-
var PATIENT_MEDICAL_HISTORY_COLLECTION = "medical-history";
|
|
1030
|
-
var PATIENT_APPOINTMENTS_COLLECTION = "appointments";
|
|
1031
|
-
var PATIENT_LOCATION_INFO_COLLECTION = "location-info";
|
|
1032
|
-
var Gender = /* @__PURE__ */ ((Gender2) => {
|
|
1033
|
-
Gender2["MALE"] = "male";
|
|
1034
|
-
Gender2["FEMALE"] = "female";
|
|
1035
|
-
Gender2["TRANSGENDER_MALE"] = "transgender_male";
|
|
1036
|
-
Gender2["TRANSGENDER_FEMALE"] = "transgender_female";
|
|
1037
|
-
Gender2["PREFER_NOT_TO_SAY"] = "prefer_not_to_say";
|
|
1038
|
-
Gender2["OTHER"] = "other";
|
|
1039
|
-
return Gender2;
|
|
1040
|
-
})(Gender || {});
|
|
1041
|
-
|
|
1042
|
-
// src/validations/patient.schema.ts
|
|
1043
|
-
import { z as z6 } from "zod";
|
|
1044
|
-
import { Timestamp as Timestamp2 } from "firebase/firestore";
|
|
1045
|
-
|
|
1046
|
-
// src/validations/patient/medical-info.schema.ts
|
|
1047
|
-
import { z as z5 } from "zod";
|
|
1048
|
-
|
|
1049
|
-
// src/types/patient/allergies.ts
|
|
1050
|
-
var AllergyType = /* @__PURE__ */ ((AllergyType2) => {
|
|
1051
|
-
AllergyType2["MEDICATION"] = "medication";
|
|
1052
|
-
AllergyType2["FOOD"] = "food";
|
|
1053
|
-
AllergyType2["ENVIRONMENTAL"] = "environmental";
|
|
1054
|
-
AllergyType2["LATEX"] = "latex";
|
|
1055
|
-
AllergyType2["COSMETIC"] = "cosmetic";
|
|
1056
|
-
AllergyType2["OTHER"] = "other";
|
|
1057
|
-
return AllergyType2;
|
|
1058
|
-
})(AllergyType || {});
|
|
1059
|
-
var MedicationAllergySubtype = /* @__PURE__ */ ((MedicationAllergySubtype2) => {
|
|
1060
|
-
MedicationAllergySubtype2["ANTIBIOTICS"] = "antibiotics";
|
|
1061
|
-
MedicationAllergySubtype2["NSAIDS"] = "nsaids";
|
|
1062
|
-
MedicationAllergySubtype2["OPIOIDS"] = "opioids";
|
|
1063
|
-
MedicationAllergySubtype2["ANESTHETICS"] = "anesthetics";
|
|
1064
|
-
MedicationAllergySubtype2["VACCINES"] = "vaccines";
|
|
1065
|
-
MedicationAllergySubtype2["OTHER"] = "other";
|
|
1066
|
-
return MedicationAllergySubtype2;
|
|
1067
|
-
})(MedicationAllergySubtype || {});
|
|
1068
|
-
var FoodAllergySubtype = /* @__PURE__ */ ((FoodAllergySubtype2) => {
|
|
1069
|
-
FoodAllergySubtype2["NUTS"] = "nuts";
|
|
1070
|
-
FoodAllergySubtype2["SHELLFISH"] = "shellfish";
|
|
1071
|
-
FoodAllergySubtype2["DAIRY"] = "dairy";
|
|
1072
|
-
FoodAllergySubtype2["EGGS"] = "eggs";
|
|
1073
|
-
FoodAllergySubtype2["WHEAT"] = "wheat";
|
|
1074
|
-
FoodAllergySubtype2["SOY"] = "soy";
|
|
1075
|
-
FoodAllergySubtype2["FISH"] = "fish";
|
|
1076
|
-
FoodAllergySubtype2["FRUITS"] = "fruits";
|
|
1077
|
-
FoodAllergySubtype2["OTHER"] = "other";
|
|
1078
|
-
return FoodAllergySubtype2;
|
|
1079
|
-
})(FoodAllergySubtype || {});
|
|
1080
|
-
var EnvironmentalAllergySubtype = /* @__PURE__ */ ((EnvironmentalAllergySubtype2) => {
|
|
1081
|
-
EnvironmentalAllergySubtype2["POLLEN"] = "pollen";
|
|
1082
|
-
EnvironmentalAllergySubtype2["DUST"] = "dust";
|
|
1083
|
-
EnvironmentalAllergySubtype2["MOLD"] = "mold";
|
|
1084
|
-
EnvironmentalAllergySubtype2["PET_DANDER"] = "pet_dander";
|
|
1085
|
-
EnvironmentalAllergySubtype2["INSECTS"] = "insects";
|
|
1086
|
-
EnvironmentalAllergySubtype2["OTHER"] = "other";
|
|
1087
|
-
return EnvironmentalAllergySubtype2;
|
|
1088
|
-
})(EnvironmentalAllergySubtype || {});
|
|
1089
|
-
var CosmeticAllergySubtype = /* @__PURE__ */ ((CosmeticAllergySubtype2) => {
|
|
1090
|
-
CosmeticAllergySubtype2["FRAGRANCES"] = "fragrances";
|
|
1091
|
-
CosmeticAllergySubtype2["PRESERVATIVES"] = "preservatives";
|
|
1092
|
-
CosmeticAllergySubtype2["DYES"] = "dyes";
|
|
1093
|
-
CosmeticAllergySubtype2["METALS"] = "metals";
|
|
1094
|
-
CosmeticAllergySubtype2["OTHER"] = "other";
|
|
1095
|
-
return CosmeticAllergySubtype2;
|
|
1096
|
-
})(CosmeticAllergySubtype || {});
|
|
1097
|
-
|
|
1098
|
-
// src/backoffice/types/static/blocking-condition.types.ts
|
|
1099
|
-
var BlockingCondition = /* @__PURE__ */ ((BlockingCondition2) => {
|
|
1100
|
-
BlockingCondition2["PREGNANCY"] = "pregnancy";
|
|
1101
|
-
BlockingCondition2["BREASTFEEDING"] = "breastfeeding";
|
|
1102
|
-
BlockingCondition2["ACTIVE_INFECTION"] = "active_infection";
|
|
1103
|
-
BlockingCondition2["SKIN_CONDITION"] = "skin_condition";
|
|
1104
|
-
BlockingCondition2["AUTOIMMUNE_DISEASE"] = "autoimmune_disease";
|
|
1105
|
-
BlockingCondition2["BLOOD_THINNERS"] = "blood_thinners";
|
|
1106
|
-
BlockingCondition2["RECENT_SURGERY"] = "recent_surgery";
|
|
1107
|
-
BlockingCondition2["DIABETES"] = "diabetes";
|
|
1108
|
-
BlockingCondition2["HEART_CONDITION"] = "heart_condition";
|
|
1109
|
-
BlockingCondition2["HIGH_BLOOD_PRESSURE"] = "high_blood_pressure";
|
|
1110
|
-
BlockingCondition2["KELOID_SCARRING"] = "keloid_scarring";
|
|
1111
|
-
BlockingCondition2["METAL_IMPLANTS"] = "metal_implants";
|
|
1112
|
-
BlockingCondition2["PACEMAKER"] = "pacemaker";
|
|
1113
|
-
BlockingCondition2["CANCER"] = "cancer";
|
|
1114
|
-
BlockingCondition2["EPILEPSY"] = "epilepsy";
|
|
1115
|
-
return BlockingCondition2;
|
|
1116
|
-
})(BlockingCondition || {});
|
|
1117
|
-
|
|
1118
|
-
// src/backoffice/types/static/contraindication.types.ts
|
|
1119
|
-
var Contraindication = /* @__PURE__ */ ((Contraindication2) => {
|
|
1120
|
-
Contraindication2["SENSITIVE_SKIN"] = "sensitive_skin";
|
|
1121
|
-
Contraindication2["RECENT_TANNING"] = "recent_tanning";
|
|
1122
|
-
Contraindication2["RECENT_BOTOX"] = "recent_botox";
|
|
1123
|
-
Contraindication2["RECENT_FILLERS"] = "recent_fillers";
|
|
1124
|
-
Contraindication2["SKIN_ALLERGIES"] = "skin_allergies";
|
|
1125
|
-
Contraindication2["MEDICATIONS"] = "medications";
|
|
1126
|
-
Contraindication2["RECENT_CHEMICAL_PEEL"] = "recent_chemical_peel";
|
|
1127
|
-
Contraindication2["RECENT_LASER"] = "recent_laser";
|
|
1128
|
-
Contraindication2["SKIN_INFLAMMATION"] = "skin_inflammation";
|
|
1129
|
-
Contraindication2["OPEN_WOUNDS"] = "open_wounds";
|
|
1130
|
-
Contraindication2["HERPES_SIMPLEX"] = "herpes_simplex";
|
|
1131
|
-
Contraindication2["COLD_SORES"] = "cold_sores";
|
|
1132
|
-
return Contraindication2;
|
|
1133
|
-
})(Contraindication || {});
|
|
1134
996
|
|
|
1135
|
-
// src/
|
|
1136
|
-
import { z as z4 } from "zod";
|
|
997
|
+
// src/services/media/media.service.ts
|
|
1137
998
|
import { Timestamp } from "firebase/firestore";
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
if (data instanceof Timestamp) {
|
|
1146
|
-
return data;
|
|
1147
|
-
}
|
|
1148
|
-
return new Timestamp(data.seconds, data.nanoseconds);
|
|
1149
|
-
});
|
|
1150
|
-
|
|
1151
|
-
// src/validations/patient/medical-info.schema.ts
|
|
1152
|
-
var allergySubtypeSchema = z5.union([
|
|
1153
|
-
z5.nativeEnum(MedicationAllergySubtype),
|
|
1154
|
-
z5.nativeEnum(FoodAllergySubtype),
|
|
1155
|
-
z5.nativeEnum(EnvironmentalAllergySubtype),
|
|
1156
|
-
z5.nativeEnum(CosmeticAllergySubtype),
|
|
1157
|
-
z5.literal("other")
|
|
1158
|
-
]);
|
|
1159
|
-
var allergySchema = z5.object({
|
|
1160
|
-
type: z5.nativeEnum(AllergyType),
|
|
1161
|
-
subtype: allergySubtypeSchema,
|
|
1162
|
-
name: z5.string().optional().nullable(),
|
|
1163
|
-
severity: z5.enum(["mild", "moderate", "severe"]).optional(),
|
|
1164
|
-
reaction: z5.string().optional().nullable(),
|
|
1165
|
-
diagnosed: timestampSchema2.optional().nullable(),
|
|
1166
|
-
notes: z5.string().optional().nullable()
|
|
1167
|
-
});
|
|
1168
|
-
var vitalStatsSchema = z5.object({
|
|
1169
|
-
height: z5.number().positive().optional(),
|
|
1170
|
-
weight: z5.number().positive().optional(),
|
|
1171
|
-
bloodType: z5.enum(["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]).optional(),
|
|
1172
|
-
bloodPressure: z5.object({
|
|
1173
|
-
systolic: z5.number().min(70).max(200),
|
|
1174
|
-
diastolic: z5.number().min(40).max(130),
|
|
1175
|
-
lastMeasured: timestampSchema2
|
|
1176
|
-
}).optional()
|
|
1177
|
-
});
|
|
1178
|
-
var blockingConditionSchema = z5.object({
|
|
1179
|
-
condition: z5.nativeEnum(BlockingCondition),
|
|
1180
|
-
diagnosedAt: timestampSchema2,
|
|
1181
|
-
notes: z5.string().optional().nullable(),
|
|
1182
|
-
isActive: z5.boolean()
|
|
1183
|
-
});
|
|
1184
|
-
var contraindicationSchema = z5.object({
|
|
1185
|
-
condition: z5.nativeEnum(Contraindication),
|
|
1186
|
-
lastOccurrence: timestampSchema2,
|
|
1187
|
-
frequency: z5.enum(["rare", "occasional", "frequent"]),
|
|
1188
|
-
notes: z5.string().optional().nullable(),
|
|
1189
|
-
isActive: z5.boolean()
|
|
1190
|
-
});
|
|
1191
|
-
var medicationSchema = z5.object({
|
|
1192
|
-
name: z5.string().min(1),
|
|
1193
|
-
dosage: z5.string().min(1),
|
|
1194
|
-
frequency: z5.string().min(1),
|
|
1195
|
-
startDate: timestampSchema2.optional().nullable(),
|
|
1196
|
-
endDate: timestampSchema2.optional().nullable(),
|
|
1197
|
-
prescribedBy: z5.string().optional().nullable()
|
|
1198
|
-
});
|
|
1199
|
-
var patientMedicalInfoSchema = z5.object({
|
|
1200
|
-
patientId: z5.string(),
|
|
1201
|
-
vitalStats: vitalStatsSchema,
|
|
1202
|
-
blockingConditions: z5.array(blockingConditionSchema),
|
|
1203
|
-
contraindications: z5.array(contraindicationSchema),
|
|
1204
|
-
allergies: z5.array(allergySchema),
|
|
1205
|
-
currentMedications: z5.array(medicationSchema),
|
|
1206
|
-
emergencyNotes: z5.string().optional(),
|
|
1207
|
-
lastUpdated: timestampSchema2,
|
|
1208
|
-
updatedBy: z5.string(),
|
|
1209
|
-
verifiedBy: z5.string().optional(),
|
|
1210
|
-
verifiedAt: timestampSchema2.optional()
|
|
1211
|
-
});
|
|
1212
|
-
var createPatientMedicalInfoSchema = patientMedicalInfoSchema.omit({
|
|
1213
|
-
patientId: true,
|
|
1214
|
-
lastUpdated: true,
|
|
1215
|
-
updatedBy: true,
|
|
1216
|
-
verifiedBy: true,
|
|
1217
|
-
verifiedAt: true
|
|
1218
|
-
});
|
|
1219
|
-
var updatePatientMedicalInfoSchema = createPatientMedicalInfoSchema.partial();
|
|
1220
|
-
var updateVitalStatsSchema = vitalStatsSchema;
|
|
1221
|
-
var addAllergySchema = allergySchema;
|
|
1222
|
-
var updateAllergySchema = allergySchema.partial().extend({
|
|
1223
|
-
allergyIndex: z5.number().min(0)
|
|
1224
|
-
});
|
|
1225
|
-
var addBlockingConditionSchema = blockingConditionSchema;
|
|
1226
|
-
var updateBlockingConditionSchema = blockingConditionSchema.partial().extend({
|
|
1227
|
-
conditionIndex: z5.number().min(0)
|
|
1228
|
-
});
|
|
1229
|
-
var addContraindicationSchema = contraindicationSchema;
|
|
1230
|
-
var updateContraindicationSchema = contraindicationSchema.partial().extend({
|
|
1231
|
-
contraindicationIndex: z5.number().min(0)
|
|
1232
|
-
});
|
|
1233
|
-
var addMedicationSchema = medicationSchema;
|
|
1234
|
-
var updateMedicationSchema = medicationSchema.partial().extend({
|
|
1235
|
-
medicationIndex: z5.number().min(0)
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
// src/validations/patient.schema.ts
|
|
1239
|
-
var locationDataSchema = z6.object({
|
|
1240
|
-
latitude: z6.number().min(-90).max(90),
|
|
1241
|
-
longitude: z6.number().min(-180).max(180),
|
|
1242
|
-
geohash: z6.string().optional()
|
|
1243
|
-
});
|
|
1244
|
-
var addressDataSchema = z6.object({
|
|
1245
|
-
address: z6.string(),
|
|
1246
|
-
city: z6.string(),
|
|
1247
|
-
country: z6.string(),
|
|
1248
|
-
postalCode: z6.string()
|
|
1249
|
-
});
|
|
1250
|
-
var emergencyContactSchema = z6.object({
|
|
1251
|
-
name: z6.string(),
|
|
1252
|
-
relationship: z6.string(),
|
|
1253
|
-
phoneNumber: z6.string(),
|
|
1254
|
-
isNotifiable: z6.boolean()
|
|
1255
|
-
});
|
|
1256
|
-
var gamificationSchema = z6.object({
|
|
1257
|
-
level: z6.number(),
|
|
1258
|
-
points: z6.number()
|
|
1259
|
-
});
|
|
1260
|
-
var patientLocationInfoSchema = z6.object({
|
|
1261
|
-
patientId: z6.string(),
|
|
1262
|
-
userRef: z6.string(),
|
|
1263
|
-
locationData: locationDataSchema,
|
|
1264
|
-
createdAt: z6.instanceof(Timestamp2),
|
|
1265
|
-
updatedAt: z6.instanceof(Timestamp2)
|
|
1266
|
-
});
|
|
1267
|
-
var createPatientLocationInfoSchema = z6.object({
|
|
1268
|
-
patientId: z6.string(),
|
|
1269
|
-
userRef: z6.string(),
|
|
1270
|
-
locationData: locationDataSchema
|
|
1271
|
-
});
|
|
1272
|
-
var patientSensitiveInfoSchema = z6.object({
|
|
1273
|
-
patientId: z6.string(),
|
|
1274
|
-
userRef: z6.string(),
|
|
1275
|
-
photoUrl: z6.string().optional(),
|
|
1276
|
-
firstName: z6.string().min(2),
|
|
1277
|
-
lastName: z6.string().min(2),
|
|
1278
|
-
dateOfBirth: z6.instanceof(Timestamp2).nullable(),
|
|
1279
|
-
gender: z6.nativeEnum(Gender),
|
|
1280
|
-
email: z6.string().email().optional(),
|
|
1281
|
-
phoneNumber: z6.string().optional(),
|
|
1282
|
-
alternativePhoneNumber: z6.string().optional(),
|
|
1283
|
-
addressData: addressDataSchema.optional(),
|
|
1284
|
-
emergencyContacts: z6.array(emergencyContactSchema).optional(),
|
|
1285
|
-
createdAt: z6.instanceof(Timestamp2),
|
|
1286
|
-
updatedAt: z6.instanceof(Timestamp2)
|
|
1287
|
-
});
|
|
1288
|
-
var patientDoctorSchema = z6.object({
|
|
1289
|
-
userRef: z6.string(),
|
|
1290
|
-
assignedAt: z6.instanceof(Timestamp2),
|
|
1291
|
-
assignedBy: z6.string().optional(),
|
|
1292
|
-
isActive: z6.boolean(),
|
|
1293
|
-
notes: z6.string().optional()
|
|
1294
|
-
});
|
|
1295
|
-
var patientClinicSchema = z6.object({
|
|
1296
|
-
clinicId: z6.string(),
|
|
1297
|
-
assignedAt: z6.instanceof(Timestamp2),
|
|
1298
|
-
assignedBy: z6.string().optional(),
|
|
1299
|
-
isActive: z6.boolean(),
|
|
1300
|
-
notes: z6.string().optional()
|
|
1301
|
-
});
|
|
1302
|
-
var patientProfileSchema = z6.object({
|
|
1303
|
-
id: z6.string(),
|
|
1304
|
-
userRef: z6.string(),
|
|
1305
|
-
displayName: z6.string(),
|
|
1306
|
-
profilePhoto: z6.string().url().nullable(),
|
|
1307
|
-
gamification: gamificationSchema,
|
|
1308
|
-
expoTokens: z6.array(z6.string()),
|
|
1309
|
-
isActive: z6.boolean(),
|
|
1310
|
-
isVerified: z6.boolean(),
|
|
1311
|
-
doctors: z6.array(patientDoctorSchema),
|
|
1312
|
-
clinics: z6.array(patientClinicSchema),
|
|
1313
|
-
doctorIds: z6.array(z6.string()),
|
|
1314
|
-
clinicIds: z6.array(z6.string()),
|
|
1315
|
-
createdAt: z6.instanceof(Timestamp2),
|
|
1316
|
-
updatedAt: z6.instanceof(Timestamp2)
|
|
1317
|
-
});
|
|
1318
|
-
var createPatientProfileSchema = z6.object({
|
|
1319
|
-
userRef: z6.string(),
|
|
1320
|
-
displayName: z6.string(),
|
|
1321
|
-
profilePhoto: z6.string().url().nullable().optional(),
|
|
1322
|
-
expoTokens: z6.array(z6.string()),
|
|
1323
|
-
gamification: gamificationSchema.optional(),
|
|
1324
|
-
isActive: z6.boolean(),
|
|
1325
|
-
isVerified: z6.boolean(),
|
|
1326
|
-
doctors: z6.array(patientDoctorSchema).optional(),
|
|
1327
|
-
clinics: z6.array(patientClinicSchema).optional(),
|
|
1328
|
-
doctorIds: z6.array(z6.string()).optional(),
|
|
1329
|
-
clinicIds: z6.array(z6.string()).optional()
|
|
1330
|
-
});
|
|
1331
|
-
var createPatientSensitiveInfoSchema = z6.object({
|
|
1332
|
-
patientId: z6.string(),
|
|
1333
|
-
userRef: z6.string(),
|
|
1334
|
-
photoUrl: z6.string().optional(),
|
|
1335
|
-
firstName: z6.string().min(2),
|
|
1336
|
-
lastName: z6.string().min(2),
|
|
1337
|
-
dateOfBirth: z6.instanceof(Timestamp2).nullable(),
|
|
1338
|
-
gender: z6.nativeEnum(Gender),
|
|
1339
|
-
email: z6.string().email().optional(),
|
|
1340
|
-
phoneNumber: z6.string().optional(),
|
|
1341
|
-
alternativePhoneNumber: z6.string().optional(),
|
|
1342
|
-
addressData: addressDataSchema.optional(),
|
|
1343
|
-
emergencyContacts: z6.array(emergencyContactSchema).optional()
|
|
1344
|
-
});
|
|
1345
|
-
var searchPatientsSchema = z6.object({
|
|
1346
|
-
clinicId: z6.string().optional(),
|
|
1347
|
-
practitionerId: z6.string().optional()
|
|
1348
|
-
}).refine((data) => data.clinicId || data.practitionerId, {
|
|
1349
|
-
message: "At least one of clinicId or practitionerId must be provided",
|
|
1350
|
-
path: []
|
|
1351
|
-
// Optional: specify a path like ['clinicId'] or ['practitionerId']
|
|
1352
|
-
});
|
|
1353
|
-
var requesterInfoSchema = z6.object({
|
|
1354
|
-
id: z6.string(),
|
|
1355
|
-
role: z6.enum(["clinic_admin", "practitioner"]),
|
|
1356
|
-
associatedClinicId: z6.string().optional(),
|
|
1357
|
-
associatedPractitionerId: z6.string().optional()
|
|
1358
|
-
}).refine(
|
|
1359
|
-
(data) => {
|
|
1360
|
-
if (data.role === "clinic_admin") {
|
|
1361
|
-
return !!data.associatedClinicId;
|
|
1362
|
-
} else if (data.role === "practitioner") {
|
|
1363
|
-
return !!data.associatedPractitionerId;
|
|
1364
|
-
}
|
|
1365
|
-
return false;
|
|
1366
|
-
},
|
|
1367
|
-
{
|
|
1368
|
-
message: "Associated ID (clinic or practitioner) is required based on role",
|
|
1369
|
-
path: ["associatedClinicId", "associatedPractitionerId"]
|
|
1370
|
-
}
|
|
1371
|
-
);
|
|
1372
|
-
|
|
1373
|
-
// src/services/patient/utils/docs.utils.ts
|
|
999
|
+
import {
|
|
1000
|
+
ref,
|
|
1001
|
+
uploadBytes,
|
|
1002
|
+
getDownloadURL,
|
|
1003
|
+
deleteObject,
|
|
1004
|
+
getBytes
|
|
1005
|
+
} from "firebase/storage";
|
|
1374
1006
|
import {
|
|
1375
1007
|
doc,
|
|
1008
|
+
getDoc,
|
|
1009
|
+
setDoc,
|
|
1010
|
+
updateDoc,
|
|
1376
1011
|
collection,
|
|
1377
1012
|
query,
|
|
1378
1013
|
where,
|
|
1014
|
+
limit,
|
|
1379
1015
|
getDocs,
|
|
1380
|
-
|
|
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
|
+
|
|
1313
|
+
// src/services/patient/utils/profile.utils.ts
|
|
1314
|
+
import {
|
|
1315
|
+
getDoc as getDoc6,
|
|
1316
|
+
setDoc as setDoc5,
|
|
1317
|
+
updateDoc as updateDoc4,
|
|
1318
|
+
arrayUnion as arrayUnion2,
|
|
1319
|
+
arrayRemove as arrayRemove2,
|
|
1320
|
+
serverTimestamp as serverTimestamp4,
|
|
1321
|
+
increment,
|
|
1322
|
+
Timestamp as Timestamp5,
|
|
1323
|
+
collection as collection4,
|
|
1324
|
+
query as query4,
|
|
1325
|
+
where as where4,
|
|
1326
|
+
getDocs as getDocs4,
|
|
1327
|
+
limit as limit3,
|
|
1328
|
+
startAfter as startAfter2,
|
|
1329
|
+
doc as doc5
|
|
1330
|
+
} from "firebase/firestore";
|
|
1331
|
+
import { z as z9 } from "zod";
|
|
1332
|
+
|
|
1333
|
+
// src/types/patient/medical-info.types.ts
|
|
1334
|
+
var PATIENT_MEDICAL_INFO_COLLECTION = "medical_info";
|
|
1335
|
+
var DEFAULT_MEDICAL_INFO = {
|
|
1336
|
+
vitalStats: {},
|
|
1337
|
+
blockingConditions: [],
|
|
1338
|
+
contraindications: [],
|
|
1339
|
+
allergies: [],
|
|
1340
|
+
currentMedications: []
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
// src/types/patient/index.ts
|
|
1344
|
+
var PATIENTS_COLLECTION = "patients";
|
|
1345
|
+
var PATIENT_SENSITIVE_INFO_COLLECTION = "sensitive-info";
|
|
1346
|
+
var PATIENT_MEDICAL_HISTORY_COLLECTION = "medical-history";
|
|
1347
|
+
var PATIENT_APPOINTMENTS_COLLECTION = "appointments";
|
|
1348
|
+
var PATIENT_LOCATION_INFO_COLLECTION = "location-info";
|
|
1349
|
+
var Gender = /* @__PURE__ */ ((Gender2) => {
|
|
1350
|
+
Gender2["MALE"] = "male";
|
|
1351
|
+
Gender2["FEMALE"] = "female";
|
|
1352
|
+
Gender2["TRANSGENDER_MALE"] = "transgender_male";
|
|
1353
|
+
Gender2["TRANSGENDER_FEMALE"] = "transgender_female";
|
|
1354
|
+
Gender2["PREFER_NOT_TO_SAY"] = "prefer_not_to_say";
|
|
1355
|
+
Gender2["OTHER"] = "other";
|
|
1356
|
+
return Gender2;
|
|
1357
|
+
})(Gender || {});
|
|
1358
|
+
|
|
1359
|
+
// src/validations/patient.schema.ts
|
|
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
|
+
]);
|
|
1370
|
+
|
|
1371
|
+
// src/validations/patient/medical-info.schema.ts
|
|
1372
|
+
import { z as z6 } from "zod";
|
|
1373
|
+
|
|
1374
|
+
// src/types/patient/allergies.ts
|
|
1375
|
+
var AllergyType = /* @__PURE__ */ ((AllergyType2) => {
|
|
1376
|
+
AllergyType2["MEDICATION"] = "medication";
|
|
1377
|
+
AllergyType2["FOOD"] = "food";
|
|
1378
|
+
AllergyType2["ENVIRONMENTAL"] = "environmental";
|
|
1379
|
+
AllergyType2["LATEX"] = "latex";
|
|
1380
|
+
AllergyType2["COSMETIC"] = "cosmetic";
|
|
1381
|
+
AllergyType2["OTHER"] = "other";
|
|
1382
|
+
return AllergyType2;
|
|
1383
|
+
})(AllergyType || {});
|
|
1384
|
+
var MedicationAllergySubtype = /* @__PURE__ */ ((MedicationAllergySubtype2) => {
|
|
1385
|
+
MedicationAllergySubtype2["ANTIBIOTICS"] = "antibiotics";
|
|
1386
|
+
MedicationAllergySubtype2["NSAIDS"] = "nsaids";
|
|
1387
|
+
MedicationAllergySubtype2["OPIOIDS"] = "opioids";
|
|
1388
|
+
MedicationAllergySubtype2["ANESTHETICS"] = "anesthetics";
|
|
1389
|
+
MedicationAllergySubtype2["VACCINES"] = "vaccines";
|
|
1390
|
+
MedicationAllergySubtype2["OTHER"] = "other";
|
|
1391
|
+
return MedicationAllergySubtype2;
|
|
1392
|
+
})(MedicationAllergySubtype || {});
|
|
1393
|
+
var FoodAllergySubtype = /* @__PURE__ */ ((FoodAllergySubtype2) => {
|
|
1394
|
+
FoodAllergySubtype2["NUTS"] = "nuts";
|
|
1395
|
+
FoodAllergySubtype2["SHELLFISH"] = "shellfish";
|
|
1396
|
+
FoodAllergySubtype2["DAIRY"] = "dairy";
|
|
1397
|
+
FoodAllergySubtype2["EGGS"] = "eggs";
|
|
1398
|
+
FoodAllergySubtype2["WHEAT"] = "wheat";
|
|
1399
|
+
FoodAllergySubtype2["SOY"] = "soy";
|
|
1400
|
+
FoodAllergySubtype2["FISH"] = "fish";
|
|
1401
|
+
FoodAllergySubtype2["FRUITS"] = "fruits";
|
|
1402
|
+
FoodAllergySubtype2["OTHER"] = "other";
|
|
1403
|
+
return FoodAllergySubtype2;
|
|
1404
|
+
})(FoodAllergySubtype || {});
|
|
1405
|
+
var EnvironmentalAllergySubtype = /* @__PURE__ */ ((EnvironmentalAllergySubtype2) => {
|
|
1406
|
+
EnvironmentalAllergySubtype2["POLLEN"] = "pollen";
|
|
1407
|
+
EnvironmentalAllergySubtype2["DUST"] = "dust";
|
|
1408
|
+
EnvironmentalAllergySubtype2["MOLD"] = "mold";
|
|
1409
|
+
EnvironmentalAllergySubtype2["PET_DANDER"] = "pet_dander";
|
|
1410
|
+
EnvironmentalAllergySubtype2["INSECTS"] = "insects";
|
|
1411
|
+
EnvironmentalAllergySubtype2["OTHER"] = "other";
|
|
1412
|
+
return EnvironmentalAllergySubtype2;
|
|
1413
|
+
})(EnvironmentalAllergySubtype || {});
|
|
1414
|
+
var CosmeticAllergySubtype = /* @__PURE__ */ ((CosmeticAllergySubtype2) => {
|
|
1415
|
+
CosmeticAllergySubtype2["FRAGRANCES"] = "fragrances";
|
|
1416
|
+
CosmeticAllergySubtype2["PRESERVATIVES"] = "preservatives";
|
|
1417
|
+
CosmeticAllergySubtype2["DYES"] = "dyes";
|
|
1418
|
+
CosmeticAllergySubtype2["METALS"] = "metals";
|
|
1419
|
+
CosmeticAllergySubtype2["OTHER"] = "other";
|
|
1420
|
+
return CosmeticAllergySubtype2;
|
|
1421
|
+
})(CosmeticAllergySubtype || {});
|
|
1422
|
+
|
|
1423
|
+
// src/backoffice/types/static/blocking-condition.types.ts
|
|
1424
|
+
var BlockingCondition = /* @__PURE__ */ ((BlockingCondition2) => {
|
|
1425
|
+
BlockingCondition2["PREGNANCY"] = "pregnancy";
|
|
1426
|
+
BlockingCondition2["BREASTFEEDING"] = "breastfeeding";
|
|
1427
|
+
BlockingCondition2["ACTIVE_INFECTION"] = "active_infection";
|
|
1428
|
+
BlockingCondition2["SKIN_CONDITION"] = "skin_condition";
|
|
1429
|
+
BlockingCondition2["AUTOIMMUNE_DISEASE"] = "autoimmune_disease";
|
|
1430
|
+
BlockingCondition2["BLOOD_THINNERS"] = "blood_thinners";
|
|
1431
|
+
BlockingCondition2["RECENT_SURGERY"] = "recent_surgery";
|
|
1432
|
+
BlockingCondition2["DIABETES"] = "diabetes";
|
|
1433
|
+
BlockingCondition2["HEART_CONDITION"] = "heart_condition";
|
|
1434
|
+
BlockingCondition2["HIGH_BLOOD_PRESSURE"] = "high_blood_pressure";
|
|
1435
|
+
BlockingCondition2["KELOID_SCARRING"] = "keloid_scarring";
|
|
1436
|
+
BlockingCondition2["METAL_IMPLANTS"] = "metal_implants";
|
|
1437
|
+
BlockingCondition2["PACEMAKER"] = "pacemaker";
|
|
1438
|
+
BlockingCondition2["CANCER"] = "cancer";
|
|
1439
|
+
BlockingCondition2["EPILEPSY"] = "epilepsy";
|
|
1440
|
+
return BlockingCondition2;
|
|
1441
|
+
})(BlockingCondition || {});
|
|
1442
|
+
|
|
1443
|
+
// src/backoffice/types/static/contraindication.types.ts
|
|
1444
|
+
var Contraindication = /* @__PURE__ */ ((Contraindication2) => {
|
|
1445
|
+
Contraindication2["SENSITIVE_SKIN"] = "sensitive_skin";
|
|
1446
|
+
Contraindication2["RECENT_TANNING"] = "recent_tanning";
|
|
1447
|
+
Contraindication2["RECENT_BOTOX"] = "recent_botox";
|
|
1448
|
+
Contraindication2["RECENT_FILLERS"] = "recent_fillers";
|
|
1449
|
+
Contraindication2["SKIN_ALLERGIES"] = "skin_allergies";
|
|
1450
|
+
Contraindication2["MEDICATIONS"] = "medications";
|
|
1451
|
+
Contraindication2["RECENT_CHEMICAL_PEEL"] = "recent_chemical_peel";
|
|
1452
|
+
Contraindication2["RECENT_LASER"] = "recent_laser";
|
|
1453
|
+
Contraindication2["SKIN_INFLAMMATION"] = "skin_inflammation";
|
|
1454
|
+
Contraindication2["OPEN_WOUNDS"] = "open_wounds";
|
|
1455
|
+
Contraindication2["HERPES_SIMPLEX"] = "herpes_simplex";
|
|
1456
|
+
Contraindication2["COLD_SORES"] = "cold_sores";
|
|
1457
|
+
return Contraindication2;
|
|
1458
|
+
})(Contraindication || {});
|
|
1459
|
+
|
|
1460
|
+
// src/validations/common.schema.ts
|
|
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()
|
|
1467
|
+
}),
|
|
1468
|
+
z5.instanceof(Timestamp2)
|
|
1469
|
+
]).transform((data) => {
|
|
1470
|
+
if (data instanceof Timestamp2) {
|
|
1471
|
+
return data;
|
|
1472
|
+
}
|
|
1473
|
+
return new Timestamp2(data.seconds, data.nanoseconds);
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
// src/validations/patient/medical-info.schema.ts
|
|
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")
|
|
1483
|
+
]);
|
|
1484
|
+
var allergySchema = z6.object({
|
|
1485
|
+
type: z6.nativeEnum(AllergyType),
|
|
1486
|
+
subtype: allergySubtypeSchema,
|
|
1487
|
+
name: z6.string().optional().nullable(),
|
|
1488
|
+
severity: z6.enum(["mild", "moderate", "severe"]).optional(),
|
|
1489
|
+
reaction: z6.string().optional().nullable(),
|
|
1490
|
+
diagnosed: timestampSchema2.optional().nullable(),
|
|
1491
|
+
notes: z6.string().optional().nullable()
|
|
1492
|
+
});
|
|
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),
|
|
1500
|
+
lastMeasured: timestampSchema2
|
|
1501
|
+
}).optional()
|
|
1502
|
+
});
|
|
1503
|
+
var blockingConditionSchema = z6.object({
|
|
1504
|
+
condition: z6.nativeEnum(BlockingCondition),
|
|
1505
|
+
diagnosedAt: timestampSchema2,
|
|
1506
|
+
notes: z6.string().optional().nullable(),
|
|
1507
|
+
isActive: z6.boolean()
|
|
1508
|
+
});
|
|
1509
|
+
var contraindicationSchema = z6.object({
|
|
1510
|
+
condition: z6.nativeEnum(Contraindication),
|
|
1511
|
+
lastOccurrence: timestampSchema2,
|
|
1512
|
+
frequency: z6.enum(["rare", "occasional", "frequent"]),
|
|
1513
|
+
notes: z6.string().optional().nullable(),
|
|
1514
|
+
isActive: z6.boolean()
|
|
1515
|
+
});
|
|
1516
|
+
var medicationSchema = z6.object({
|
|
1517
|
+
name: z6.string().min(1),
|
|
1518
|
+
dosage: z6.string().min(1),
|
|
1519
|
+
frequency: z6.string().min(1),
|
|
1520
|
+
startDate: timestampSchema2.optional().nullable(),
|
|
1521
|
+
endDate: timestampSchema2.optional().nullable(),
|
|
1522
|
+
prescribedBy: z6.string().optional().nullable()
|
|
1523
|
+
});
|
|
1524
|
+
var patientMedicalInfoSchema = z6.object({
|
|
1525
|
+
patientId: z6.string(),
|
|
1526
|
+
vitalStats: vitalStatsSchema,
|
|
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(),
|
|
1532
|
+
lastUpdated: timestampSchema2,
|
|
1533
|
+
updatedBy: z6.string(),
|
|
1534
|
+
verifiedBy: z6.string().optional(),
|
|
1535
|
+
verifiedAt: timestampSchema2.optional()
|
|
1536
|
+
});
|
|
1537
|
+
var createPatientMedicalInfoSchema = patientMedicalInfoSchema.omit({
|
|
1538
|
+
patientId: true,
|
|
1539
|
+
lastUpdated: true,
|
|
1540
|
+
updatedBy: true,
|
|
1541
|
+
verifiedBy: true,
|
|
1542
|
+
verifiedAt: true
|
|
1543
|
+
});
|
|
1544
|
+
var updatePatientMedicalInfoSchema = createPatientMedicalInfoSchema.partial();
|
|
1545
|
+
var updateVitalStatsSchema = vitalStatsSchema;
|
|
1546
|
+
var addAllergySchema = allergySchema;
|
|
1547
|
+
var updateAllergySchema = allergySchema.partial().extend({
|
|
1548
|
+
allergyIndex: z6.number().min(0)
|
|
1549
|
+
});
|
|
1550
|
+
var addBlockingConditionSchema = blockingConditionSchema;
|
|
1551
|
+
var updateBlockingConditionSchema = blockingConditionSchema.partial().extend({
|
|
1552
|
+
conditionIndex: z6.number().min(0)
|
|
1553
|
+
});
|
|
1554
|
+
var addContraindicationSchema = contraindicationSchema;
|
|
1555
|
+
var updateContraindicationSchema = contraindicationSchema.partial().extend({
|
|
1556
|
+
contraindicationIndex: z6.number().min(0)
|
|
1557
|
+
});
|
|
1558
|
+
var addMedicationSchema = medicationSchema;
|
|
1559
|
+
var updateMedicationSchema = medicationSchema.partial().extend({
|
|
1560
|
+
medicationIndex: z6.number().min(0)
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
// src/validations/patient.schema.ts
|
|
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()
|
|
1568
|
+
});
|
|
1569
|
+
var addressDataSchema = z7.object({
|
|
1570
|
+
address: z7.string(),
|
|
1571
|
+
city: z7.string(),
|
|
1572
|
+
country: z7.string(),
|
|
1573
|
+
postalCode: z7.string()
|
|
1574
|
+
});
|
|
1575
|
+
var emergencyContactSchema = z7.object({
|
|
1576
|
+
name: z7.string(),
|
|
1577
|
+
relationship: z7.string(),
|
|
1578
|
+
phoneNumber: z7.string(),
|
|
1579
|
+
isNotifiable: z7.boolean()
|
|
1580
|
+
});
|
|
1581
|
+
var gamificationSchema = z7.object({
|
|
1582
|
+
level: z7.number(),
|
|
1583
|
+
points: z7.number()
|
|
1584
|
+
});
|
|
1585
|
+
var patientLocationInfoSchema = z7.object({
|
|
1586
|
+
patientId: z7.string(),
|
|
1587
|
+
userRef: z7.string(),
|
|
1588
|
+
locationData: locationDataSchema,
|
|
1589
|
+
createdAt: z7.instanceof(Timestamp3),
|
|
1590
|
+
updatedAt: z7.instanceof(Timestamp3)
|
|
1591
|
+
});
|
|
1592
|
+
var createPatientLocationInfoSchema = z7.object({
|
|
1593
|
+
patientId: z7.string(),
|
|
1594
|
+
userRef: z7.string(),
|
|
1595
|
+
locationData: locationDataSchema
|
|
1596
|
+
});
|
|
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(),
|
|
1607
|
+
addressData: addressDataSchema.optional(),
|
|
1608
|
+
emergencyContacts: z7.array(emergencyContactSchema).optional(),
|
|
1609
|
+
createdAt: z7.instanceof(Timestamp3),
|
|
1610
|
+
updatedAt: z7.instanceof(Timestamp3)
|
|
1611
|
+
});
|
|
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()
|
|
1618
|
+
});
|
|
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()
|
|
1625
|
+
});
|
|
1626
|
+
var patientProfileSchema = z7.object({
|
|
1627
|
+
id: z7.string(),
|
|
1628
|
+
userRef: z7.string(),
|
|
1629
|
+
displayName: z7.string(),
|
|
1630
|
+
gamification: gamificationSchema,
|
|
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)
|
|
1642
|
+
});
|
|
1643
|
+
var createPatientProfileSchema = z7.object({
|
|
1644
|
+
userRef: z7.string(),
|
|
1645
|
+
displayName: z7.string(),
|
|
1646
|
+
expoTokens: z7.array(z7.string()),
|
|
1647
|
+
gamification: gamificationSchema.optional(),
|
|
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()
|
|
1654
|
+
});
|
|
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(),
|
|
1666
|
+
addressData: addressDataSchema.optional(),
|
|
1667
|
+
emergencyContacts: z7.array(emergencyContactSchema).optional()
|
|
1668
|
+
});
|
|
1669
|
+
var searchPatientsSchema = z7.object({
|
|
1670
|
+
clinicId: z7.string().optional(),
|
|
1671
|
+
practitionerId: z7.string().optional()
|
|
1672
|
+
}).refine((data) => data.clinicId || data.practitionerId, {
|
|
1673
|
+
message: "At least one of clinicId or practitionerId must be provided",
|
|
1674
|
+
path: []
|
|
1675
|
+
// Optional: specify a path like ['clinicId'] or ['practitionerId']
|
|
1676
|
+
});
|
|
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()
|
|
1682
|
+
}).refine(
|
|
1683
|
+
(data) => {
|
|
1684
|
+
if (data.role === "clinic_admin") {
|
|
1685
|
+
return !!data.associatedClinicId;
|
|
1686
|
+
} else if (data.role === "practitioner") {
|
|
1687
|
+
return !!data.associatedPractitionerId;
|
|
1688
|
+
}
|
|
1689
|
+
return false;
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
message: "Associated ID (clinic or practitioner) is required based on role",
|
|
1693
|
+
path: ["associatedClinicId", "associatedPractitionerId"]
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1696
|
+
|
|
1697
|
+
// src/services/patient/utils/docs.utils.ts
|
|
1698
|
+
import {
|
|
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(),
|
|
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);
|
|
@@ -3934,27 +4417,27 @@ var ClinicAdminService = class extends BaseService {
|
|
|
3934
4417
|
|
|
3935
4418
|
// src/services/practitioner/practitioner.service.ts
|
|
3936
4419
|
import {
|
|
3937
|
-
collection as
|
|
3938
|
-
doc as
|
|
3939
|
-
getDoc as
|
|
3940
|
-
getDocs as
|
|
3941
|
-
query as
|
|
3942
|
-
where as
|
|
3943
|
-
updateDoc as
|
|
3944
|
-
setDoc as
|
|
3945
|
-
deleteDoc as
|
|
3946
|
-
Timestamp as
|
|
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,
|
|
3947
4430
|
serverTimestamp as serverTimestamp9,
|
|
3948
|
-
limit as
|
|
4431
|
+
limit as limit5,
|
|
3949
4432
|
startAfter as startAfter4,
|
|
3950
|
-
orderBy,
|
|
4433
|
+
orderBy as orderBy2,
|
|
3951
4434
|
arrayUnion as arrayUnion5,
|
|
3952
4435
|
arrayRemove as arrayRemove4
|
|
3953
4436
|
} from "firebase/firestore";
|
|
3954
4437
|
|
|
3955
4438
|
// src/validations/practitioner.schema.ts
|
|
3956
4439
|
import { z as z14 } from "zod";
|
|
3957
|
-
import { Timestamp as
|
|
4440
|
+
import { Timestamp as Timestamp9 } from "firebase/firestore";
|
|
3958
4441
|
|
|
3959
4442
|
// src/backoffice/types/static/certification.types.ts
|
|
3960
4443
|
var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
|
|
@@ -3987,9 +4470,9 @@ var practitionerBasicInfoSchema = z14.object({
|
|
|
3987
4470
|
title: z14.string().min(2).max(100),
|
|
3988
4471
|
email: z14.string().email(),
|
|
3989
4472
|
phoneNumber: z14.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
|
3990
|
-
dateOfBirth: z14.instanceof(
|
|
4473
|
+
dateOfBirth: z14.instanceof(Timestamp9).or(z14.date()),
|
|
3991
4474
|
gender: z14.enum(["male", "female", "other"]),
|
|
3992
|
-
profileImageUrl:
|
|
4475
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
3993
4476
|
bio: z14.string().max(1e3).optional(),
|
|
3994
4477
|
languages: z14.array(z14.string()).min(1)
|
|
3995
4478
|
});
|
|
@@ -3998,8 +4481,8 @@ var practitionerCertificationSchema = z14.object({
|
|
|
3998
4481
|
specialties: z14.array(z14.nativeEnum(CertificationSpecialty)),
|
|
3999
4482
|
licenseNumber: z14.string().min(3).max(50),
|
|
4000
4483
|
issuingAuthority: z14.string().min(2).max(100),
|
|
4001
|
-
issueDate: z14.instanceof(
|
|
4002
|
-
expiryDate: z14.instanceof(
|
|
4484
|
+
issueDate: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4485
|
+
expiryDate: z14.instanceof(Timestamp9).or(z14.date()).optional(),
|
|
4003
4486
|
verificationStatus: z14.enum(["pending", "verified", "rejected"])
|
|
4004
4487
|
});
|
|
4005
4488
|
var timeSlotSchema = z14.object({
|
|
@@ -4016,8 +4499,8 @@ var practitionerWorkingHoursSchema = z14.object({
|
|
|
4016
4499
|
friday: timeSlotSchema,
|
|
4017
4500
|
saturday: timeSlotSchema,
|
|
4018
4501
|
sunday: timeSlotSchema,
|
|
4019
|
-
createdAt: z14.instanceof(
|
|
4020
|
-
updatedAt: z14.instanceof(
|
|
4502
|
+
createdAt: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4503
|
+
updatedAt: z14.instanceof(Timestamp9).or(z14.date())
|
|
4021
4504
|
});
|
|
4022
4505
|
var practitionerClinicWorkingHoursSchema = z14.object({
|
|
4023
4506
|
clinicId: z14.string().min(1),
|
|
@@ -4031,8 +4514,8 @@ var practitionerClinicWorkingHoursSchema = z14.object({
|
|
|
4031
4514
|
sunday: timeSlotSchema
|
|
4032
4515
|
}),
|
|
4033
4516
|
isActive: z14.boolean(),
|
|
4034
|
-
createdAt: z14.instanceof(
|
|
4035
|
-
updatedAt: z14.instanceof(
|
|
4517
|
+
createdAt: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4518
|
+
updatedAt: z14.instanceof(Timestamp9).or(z14.date())
|
|
4036
4519
|
});
|
|
4037
4520
|
var practitionerSchema = z14.object({
|
|
4038
4521
|
id: z14.string().min(1),
|
|
@@ -4048,8 +4531,8 @@ var practitionerSchema = z14.object({
|
|
|
4048
4531
|
isActive: z14.boolean(),
|
|
4049
4532
|
isVerified: z14.boolean(),
|
|
4050
4533
|
status: z14.nativeEnum(PractitionerStatus),
|
|
4051
|
-
createdAt: z14.instanceof(
|
|
4052
|
-
updatedAt: z14.instanceof(
|
|
4534
|
+
createdAt: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4535
|
+
updatedAt: z14.instanceof(Timestamp9).or(z14.date())
|
|
4053
4536
|
});
|
|
4054
4537
|
var createPractitionerSchema = z14.object({
|
|
4055
4538
|
userRef: z14.string().min(1),
|
|
@@ -4081,10 +4564,10 @@ var practitionerTokenSchema = z14.object({
|
|
|
4081
4564
|
clinicId: z14.string().min(1),
|
|
4082
4565
|
status: z14.nativeEnum(PractitionerTokenStatus),
|
|
4083
4566
|
createdBy: z14.string().min(1),
|
|
4084
|
-
createdAt: z14.instanceof(
|
|
4085
|
-
expiresAt: z14.instanceof(
|
|
4567
|
+
createdAt: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4568
|
+
expiresAt: z14.instanceof(Timestamp9).or(z14.date()),
|
|
4086
4569
|
usedBy: z14.string().optional(),
|
|
4087
|
-
usedAt: z14.instanceof(
|
|
4570
|
+
usedAt: z14.instanceof(Timestamp9).or(z14.date()).optional()
|
|
4088
4571
|
});
|
|
4089
4572
|
var createPractitionerTokenSchema = z14.object({
|
|
4090
4573
|
practitionerId: z14.string().min(1),
|
|
@@ -4101,7 +4584,7 @@ var practitionerSignupSchema = z14.object({
|
|
|
4101
4584
|
profileData: z14.object({
|
|
4102
4585
|
basicInfo: z14.object({
|
|
4103
4586
|
phoneNumber: z14.string().optional(),
|
|
4104
|
-
profileImageUrl:
|
|
4587
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
4105
4588
|
gender: z14.enum(["male", "female", "other"]).optional(),
|
|
4106
4589
|
bio: z14.string().optional()
|
|
4107
4590
|
}).optional(),
|
|
@@ -4116,6 +4599,7 @@ var PractitionerService = class extends BaseService {
|
|
|
4116
4599
|
constructor(db, auth, app, clinicService) {
|
|
4117
4600
|
super(db, auth, app);
|
|
4118
4601
|
this.clinicService = clinicService;
|
|
4602
|
+
this.mediaService = new MediaService(db, auth, app);
|
|
4119
4603
|
}
|
|
4120
4604
|
getClinicService() {
|
|
4121
4605
|
if (!this.clinicService) {
|
|
@@ -4126,6 +4610,53 @@ var PractitionerService = class extends BaseService {
|
|
|
4126
4610
|
setClinicService(clinicService) {
|
|
4127
4611
|
this.clinicService = clinicService;
|
|
4128
4612
|
}
|
|
4613
|
+
/**
|
|
4614
|
+
* Handles profile photo upload for practitioners
|
|
4615
|
+
* @param profilePhoto - MediaResource (File, Blob, or URL string)
|
|
4616
|
+
* @param practitionerId - ID of the practitioner
|
|
4617
|
+
* @returns URL string of the uploaded or existing photo
|
|
4618
|
+
*/
|
|
4619
|
+
async handleProfilePhotoUpload(profilePhoto, practitionerId) {
|
|
4620
|
+
if (!profilePhoto) {
|
|
4621
|
+
return void 0;
|
|
4622
|
+
}
|
|
4623
|
+
if (typeof profilePhoto === "string") {
|
|
4624
|
+
return profilePhoto;
|
|
4625
|
+
}
|
|
4626
|
+
if (profilePhoto instanceof File || profilePhoto instanceof Blob) {
|
|
4627
|
+
console.log(
|
|
4628
|
+
`[PractitionerService] Uploading profile photo for practitioner ${practitionerId}`
|
|
4629
|
+
);
|
|
4630
|
+
const mediaMetadata = await this.mediaService.uploadMedia(
|
|
4631
|
+
profilePhoto,
|
|
4632
|
+
practitionerId,
|
|
4633
|
+
// Using practitionerId as ownerId
|
|
4634
|
+
"public" /* PUBLIC */,
|
|
4635
|
+
// Profile photos should be public
|
|
4636
|
+
"practitioner_profile_photos",
|
|
4637
|
+
profilePhoto instanceof File ? profilePhoto.name : `profile_photo_${practitionerId}`
|
|
4638
|
+
);
|
|
4639
|
+
return mediaMetadata.url;
|
|
4640
|
+
}
|
|
4641
|
+
return void 0;
|
|
4642
|
+
}
|
|
4643
|
+
/**
|
|
4644
|
+
* Processes BasicPractitionerInfo to handle profile photo uploads
|
|
4645
|
+
* @param basicInfo - The basic info containing potential MediaResource profile photo
|
|
4646
|
+
* @param practitionerId - ID of the practitioner
|
|
4647
|
+
* @returns Processed basic info with URL string for profileImageUrl
|
|
4648
|
+
*/
|
|
4649
|
+
async processBasicInfo(basicInfo, practitionerId) {
|
|
4650
|
+
const processedBasicInfo = { ...basicInfo };
|
|
4651
|
+
if (basicInfo.profileImageUrl) {
|
|
4652
|
+
const uploadedUrl = await this.handleProfilePhotoUpload(
|
|
4653
|
+
basicInfo.profileImageUrl,
|
|
4654
|
+
practitionerId
|
|
4655
|
+
);
|
|
4656
|
+
processedBasicInfo.profileImageUrl = uploadedUrl;
|
|
4657
|
+
}
|
|
4658
|
+
return processedBasicInfo;
|
|
4659
|
+
}
|
|
4129
4660
|
/**
|
|
4130
4661
|
* Creates a new practitioner
|
|
4131
4662
|
*/
|
|
@@ -4146,7 +4677,10 @@ var PractitionerService = class extends BaseService {
|
|
|
4146
4677
|
const practitioner = {
|
|
4147
4678
|
id: practitionerId,
|
|
4148
4679
|
userRef: validData.userRef,
|
|
4149
|
-
basicInfo:
|
|
4680
|
+
basicInfo: await this.processBasicInfo(
|
|
4681
|
+
validData.basicInfo,
|
|
4682
|
+
practitionerId
|
|
4683
|
+
),
|
|
4150
4684
|
certification: validData.certification,
|
|
4151
4685
|
clinics: validData.clinics || [],
|
|
4152
4686
|
clinicWorkingHours: validData.clinicWorkingHours || [],
|
|
@@ -4162,15 +4696,15 @@ var PractitionerService = class extends BaseService {
|
|
|
4162
4696
|
};
|
|
4163
4697
|
practitionerSchema.parse({
|
|
4164
4698
|
...practitioner,
|
|
4165
|
-
createdAt:
|
|
4166
|
-
updatedAt:
|
|
4699
|
+
createdAt: Timestamp10.now(),
|
|
4700
|
+
updatedAt: Timestamp10.now()
|
|
4167
4701
|
});
|
|
4168
|
-
const practitionerRef =
|
|
4702
|
+
const practitionerRef = doc9(
|
|
4169
4703
|
this.db,
|
|
4170
4704
|
PRACTITIONERS_COLLECTION,
|
|
4171
4705
|
practitionerId
|
|
4172
4706
|
);
|
|
4173
|
-
await
|
|
4707
|
+
await setDoc8(practitionerRef, practitioner);
|
|
4174
4708
|
const createdPractitioner = await this.getPractitioner(practitionerId);
|
|
4175
4709
|
if (!createdPractitioner) {
|
|
4176
4710
|
throw new Error(
|
|
@@ -4246,7 +4780,10 @@ var PractitionerService = class extends BaseService {
|
|
|
4246
4780
|
id: practitionerId,
|
|
4247
4781
|
userRef: "",
|
|
4248
4782
|
// Prazno - biće popunjeno kada korisnik kreira nalog
|
|
4249
|
-
basicInfo:
|
|
4783
|
+
basicInfo: await this.processBasicInfo(
|
|
4784
|
+
validatedData.basicInfo,
|
|
4785
|
+
practitionerId
|
|
4786
|
+
),
|
|
4250
4787
|
certification: validatedData.certification,
|
|
4251
4788
|
clinics,
|
|
4252
4789
|
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
@@ -4263,11 +4800,11 @@ var PractitionerService = class extends BaseService {
|
|
|
4263
4800
|
practitionerSchema.parse({
|
|
4264
4801
|
...practitionerData,
|
|
4265
4802
|
userRef: "temp-for-validation",
|
|
4266
|
-
createdAt:
|
|
4267
|
-
updatedAt:
|
|
4803
|
+
createdAt: Timestamp10.now(),
|
|
4804
|
+
updatedAt: Timestamp10.now()
|
|
4268
4805
|
});
|
|
4269
|
-
await
|
|
4270
|
-
|
|
4806
|
+
await setDoc8(
|
|
4807
|
+
doc9(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
|
|
4271
4808
|
practitionerData
|
|
4272
4809
|
);
|
|
4273
4810
|
const savedPractitioner = await this.getPractitioner(practitionerData.id);
|
|
@@ -4284,12 +4821,12 @@ var PractitionerService = class extends BaseService {
|
|
|
4284
4821
|
clinicId,
|
|
4285
4822
|
status: "active" /* ACTIVE */,
|
|
4286
4823
|
createdBy,
|
|
4287
|
-
createdAt:
|
|
4288
|
-
expiresAt:
|
|
4824
|
+
createdAt: Timestamp10.now(),
|
|
4825
|
+
expiresAt: Timestamp10.fromDate(expiration)
|
|
4289
4826
|
};
|
|
4290
4827
|
practitionerTokenSchema.parse(token);
|
|
4291
4828
|
const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4292
|
-
await
|
|
4829
|
+
await setDoc8(doc9(this.db, tokenPath), token);
|
|
4293
4830
|
return { practitioner: savedPractitioner, token };
|
|
4294
4831
|
} catch (error) {
|
|
4295
4832
|
if (error instanceof z15.ZodError) {
|
|
@@ -4337,12 +4874,12 @@ var PractitionerService = class extends BaseService {
|
|
|
4337
4874
|
clinicId: validatedData.clinicId,
|
|
4338
4875
|
status: "active" /* ACTIVE */,
|
|
4339
4876
|
createdBy,
|
|
4340
|
-
createdAt:
|
|
4341
|
-
expiresAt:
|
|
4877
|
+
createdAt: Timestamp10.now(),
|
|
4878
|
+
expiresAt: Timestamp10.fromDate(expiration)
|
|
4342
4879
|
};
|
|
4343
4880
|
practitionerTokenSchema.parse(token);
|
|
4344
4881
|
const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4345
|
-
await
|
|
4882
|
+
await setDoc8(doc9(this.db, tokenPath), token);
|
|
4346
4883
|
return token;
|
|
4347
4884
|
} catch (error) {
|
|
4348
4885
|
if (error instanceof z15.ZodError) {
|
|
@@ -4357,16 +4894,16 @@ var PractitionerService = class extends BaseService {
|
|
|
4357
4894
|
* @returns Array of active tokens
|
|
4358
4895
|
*/
|
|
4359
4896
|
async getPractitionerActiveTokens(practitionerId) {
|
|
4360
|
-
const tokensRef =
|
|
4897
|
+
const tokensRef = collection7(
|
|
4361
4898
|
this.db,
|
|
4362
4899
|
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
4363
4900
|
);
|
|
4364
|
-
const q =
|
|
4901
|
+
const q = query7(
|
|
4365
4902
|
tokensRef,
|
|
4366
|
-
|
|
4367
|
-
|
|
4903
|
+
where7("status", "==", "active" /* ACTIVE */),
|
|
4904
|
+
where7("expiresAt", ">", Timestamp10.now())
|
|
4368
4905
|
);
|
|
4369
|
-
const querySnapshot = await
|
|
4906
|
+
const querySnapshot = await getDocs7(q);
|
|
4370
4907
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4371
4908
|
}
|
|
4372
4909
|
/**
|
|
@@ -4375,11 +4912,11 @@ var PractitionerService = class extends BaseService {
|
|
|
4375
4912
|
* @returns The token if found and valid, null otherwise
|
|
4376
4913
|
*/
|
|
4377
4914
|
async validateToken(tokenString) {
|
|
4378
|
-
const practitionersRef =
|
|
4379
|
-
const practitionersSnapshot = await
|
|
4915
|
+
const practitionersRef = collection7(this.db, PRACTITIONERS_COLLECTION);
|
|
4916
|
+
const practitionersSnapshot = await getDocs7(practitionersRef);
|
|
4380
4917
|
for (const practitionerDoc of practitionersSnapshot.docs) {
|
|
4381
4918
|
const practitionerId = practitionerDoc.id;
|
|
4382
|
-
const tokensRef =
|
|
4919
|
+
const tokensRef = collection7(
|
|
4383
4920
|
this.db,
|
|
4384
4921
|
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
4385
4922
|
);
|
|
@@ -4387,17 +4924,17 @@ var PractitionerService = class extends BaseService {
|
|
|
4387
4924
|
`[PRACTITIONER] Validating token for practitioner ${practitionerId}`,
|
|
4388
4925
|
{
|
|
4389
4926
|
tokenString,
|
|
4390
|
-
timestamp:
|
|
4927
|
+
timestamp: Timestamp10.now().toDate()
|
|
4391
4928
|
}
|
|
4392
4929
|
);
|
|
4393
|
-
const q =
|
|
4930
|
+
const q = query7(
|
|
4394
4931
|
tokensRef,
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4932
|
+
where7("token", "==", tokenString),
|
|
4933
|
+
where7("status", "==", "active" /* ACTIVE */),
|
|
4934
|
+
where7("expiresAt", ">", Timestamp10.now())
|
|
4398
4935
|
);
|
|
4399
4936
|
try {
|
|
4400
|
-
const tokenSnapshot = await
|
|
4937
|
+
const tokenSnapshot = await getDocs7(q);
|
|
4401
4938
|
console.log(
|
|
4402
4939
|
`[PRACTITIONER] Token query results for practitioner ${practitionerId}`,
|
|
4403
4940
|
{
|
|
@@ -4430,22 +4967,22 @@ var PractitionerService = class extends BaseService {
|
|
|
4430
4967
|
* @param userId ID of the user using the token
|
|
4431
4968
|
*/
|
|
4432
4969
|
async markTokenAsUsed(tokenId, practitionerId, userId) {
|
|
4433
|
-
const tokenRef =
|
|
4970
|
+
const tokenRef = doc9(
|
|
4434
4971
|
this.db,
|
|
4435
4972
|
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
|
|
4436
4973
|
);
|
|
4437
|
-
await
|
|
4974
|
+
await updateDoc9(tokenRef, {
|
|
4438
4975
|
status: "used" /* USED */,
|
|
4439
4976
|
usedBy: userId,
|
|
4440
|
-
usedAt:
|
|
4977
|
+
usedAt: Timestamp10.now()
|
|
4441
4978
|
});
|
|
4442
4979
|
}
|
|
4443
4980
|
/**
|
|
4444
4981
|
* Dohvata zdravstvenog radnika po ID-u
|
|
4445
4982
|
*/
|
|
4446
4983
|
async getPractitioner(practitionerId) {
|
|
4447
|
-
const practitionerDoc = await
|
|
4448
|
-
|
|
4984
|
+
const practitionerDoc = await getDoc12(
|
|
4985
|
+
doc9(this.db, PRACTITIONERS_COLLECTION, practitionerId)
|
|
4449
4986
|
);
|
|
4450
4987
|
if (!practitionerDoc.exists()) {
|
|
4451
4988
|
return null;
|
|
@@ -4456,11 +4993,11 @@ var PractitionerService = class extends BaseService {
|
|
|
4456
4993
|
* Dohvata zdravstvenog radnika po User ID-u
|
|
4457
4994
|
*/
|
|
4458
4995
|
async getPractitionerByUserRef(userRef) {
|
|
4459
|
-
const q =
|
|
4460
|
-
|
|
4461
|
-
|
|
4996
|
+
const q = query7(
|
|
4997
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
4998
|
+
where7("userRef", "==", userRef)
|
|
4462
4999
|
);
|
|
4463
|
-
const querySnapshot = await
|
|
5000
|
+
const querySnapshot = await getDocs7(q);
|
|
4464
5001
|
if (querySnapshot.empty) {
|
|
4465
5002
|
return null;
|
|
4466
5003
|
}
|
|
@@ -4470,37 +5007,37 @@ var PractitionerService = class extends BaseService {
|
|
|
4470
5007
|
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
4471
5008
|
*/
|
|
4472
5009
|
async getPractitionersByClinic(clinicId) {
|
|
4473
|
-
const q =
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
5010
|
+
const q = query7(
|
|
5011
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
5012
|
+
where7("clinics", "array-contains", clinicId),
|
|
5013
|
+
where7("isActive", "==", true),
|
|
5014
|
+
where7("status", "==", "active" /* ACTIVE */)
|
|
4478
5015
|
);
|
|
4479
|
-
const querySnapshot = await
|
|
5016
|
+
const querySnapshot = await getDocs7(q);
|
|
4480
5017
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4481
5018
|
}
|
|
4482
5019
|
/**
|
|
4483
5020
|
* Dohvata sve zdravstvene radnike za određenu kliniku
|
|
4484
5021
|
*/
|
|
4485
5022
|
async getAllPractitionersByClinic(clinicId) {
|
|
4486
|
-
const q =
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
5023
|
+
const q = query7(
|
|
5024
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
5025
|
+
where7("clinics", "array-contains", clinicId),
|
|
5026
|
+
where7("isActive", "==", true)
|
|
4490
5027
|
);
|
|
4491
|
-
const querySnapshot = await
|
|
5028
|
+
const querySnapshot = await getDocs7(q);
|
|
4492
5029
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4493
5030
|
}
|
|
4494
5031
|
/**
|
|
4495
5032
|
* Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
|
|
4496
5033
|
*/
|
|
4497
5034
|
async getDraftPractitionersByClinic(clinicId) {
|
|
4498
|
-
const q =
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
5035
|
+
const q = query7(
|
|
5036
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
5037
|
+
where7("clinics", "array-contains", clinicId),
|
|
5038
|
+
where7("status", "==", "draft" /* DRAFT */)
|
|
4502
5039
|
);
|
|
4503
|
-
const querySnapshot = await
|
|
5040
|
+
const querySnapshot = await getDocs7(q);
|
|
4504
5041
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4505
5042
|
}
|
|
4506
5043
|
/**
|
|
@@ -4509,21 +5046,28 @@ var PractitionerService = class extends BaseService {
|
|
|
4509
5046
|
async updatePractitioner(practitionerId, data) {
|
|
4510
5047
|
try {
|
|
4511
5048
|
const validData = data;
|
|
4512
|
-
const practitionerRef =
|
|
5049
|
+
const practitionerRef = doc9(
|
|
4513
5050
|
this.db,
|
|
4514
5051
|
PRACTITIONERS_COLLECTION,
|
|
4515
5052
|
practitionerId
|
|
4516
5053
|
);
|
|
4517
|
-
const practitionerDoc = await
|
|
5054
|
+
const practitionerDoc = await getDoc12(practitionerRef);
|
|
4518
5055
|
if (!practitionerDoc.exists()) {
|
|
4519
5056
|
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4520
5057
|
}
|
|
4521
5058
|
const currentPractitioner = practitionerDoc.data();
|
|
5059
|
+
let processedData = { ...validData };
|
|
5060
|
+
if (validData.basicInfo) {
|
|
5061
|
+
processedData.basicInfo = await this.processBasicInfo(
|
|
5062
|
+
validData.basicInfo,
|
|
5063
|
+
practitionerId
|
|
5064
|
+
);
|
|
5065
|
+
}
|
|
4522
5066
|
const updateData = {
|
|
4523
|
-
...
|
|
5067
|
+
...processedData,
|
|
4524
5068
|
updatedAt: serverTimestamp9()
|
|
4525
5069
|
};
|
|
4526
|
-
await
|
|
5070
|
+
await updateDoc9(practitionerRef, updateData);
|
|
4527
5071
|
const updatedPractitioner = await this.getPractitioner(practitionerId);
|
|
4528
5072
|
if (!updatedPractitioner) {
|
|
4529
5073
|
throw new Error(
|
|
@@ -4545,12 +5089,12 @@ var PractitionerService = class extends BaseService {
|
|
|
4545
5089
|
async addClinic(practitionerId, clinicId) {
|
|
4546
5090
|
var _a;
|
|
4547
5091
|
try {
|
|
4548
|
-
const practitionerRef =
|
|
5092
|
+
const practitionerRef = doc9(
|
|
4549
5093
|
this.db,
|
|
4550
5094
|
PRACTITIONERS_COLLECTION,
|
|
4551
5095
|
practitionerId
|
|
4552
5096
|
);
|
|
4553
|
-
const practitionerDoc = await
|
|
5097
|
+
const practitionerDoc = await getDoc12(practitionerRef);
|
|
4554
5098
|
if (!practitionerDoc.exists()) {
|
|
4555
5099
|
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4556
5100
|
}
|
|
@@ -4561,7 +5105,7 @@ var PractitionerService = class extends BaseService {
|
|
|
4561
5105
|
);
|
|
4562
5106
|
return;
|
|
4563
5107
|
}
|
|
4564
|
-
await
|
|
5108
|
+
await updateDoc9(practitionerRef, {
|
|
4565
5109
|
clinics: arrayUnion5(clinicId),
|
|
4566
5110
|
updatedAt: serverTimestamp9()
|
|
4567
5111
|
});
|
|
@@ -4578,16 +5122,16 @@ var PractitionerService = class extends BaseService {
|
|
|
4578
5122
|
*/
|
|
4579
5123
|
async removeClinic(practitionerId, clinicId) {
|
|
4580
5124
|
try {
|
|
4581
|
-
const practitionerRef =
|
|
5125
|
+
const practitionerRef = doc9(
|
|
4582
5126
|
this.db,
|
|
4583
5127
|
PRACTITIONERS_COLLECTION,
|
|
4584
5128
|
practitionerId
|
|
4585
5129
|
);
|
|
4586
|
-
const practitionerDoc = await
|
|
5130
|
+
const practitionerDoc = await getDoc12(practitionerRef);
|
|
4587
5131
|
if (!practitionerDoc.exists()) {
|
|
4588
5132
|
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4589
5133
|
}
|
|
4590
|
-
await
|
|
5134
|
+
await updateDoc9(practitionerRef, {
|
|
4591
5135
|
clinics: arrayRemove4(clinicId),
|
|
4592
5136
|
updatedAt: serverTimestamp9()
|
|
4593
5137
|
});
|
|
@@ -4623,7 +5167,7 @@ var PractitionerService = class extends BaseService {
|
|
|
4623
5167
|
if (!practitioner) {
|
|
4624
5168
|
throw new Error("Practitioner not found");
|
|
4625
5169
|
}
|
|
4626
|
-
await
|
|
5170
|
+
await deleteDoc3(doc9(this.db, PRACTITIONERS_COLLECTION, practitionerId));
|
|
4627
5171
|
}
|
|
4628
5172
|
/**
|
|
4629
5173
|
* Validates a registration token and claims the associated draft practitioner profile
|
|
@@ -4692,21 +5236,21 @@ var PractitionerService = class extends BaseService {
|
|
|
4692
5236
|
try {
|
|
4693
5237
|
const constraints = [];
|
|
4694
5238
|
if (!(options == null ? void 0 : options.includeDraftPractitioners)) {
|
|
4695
|
-
constraints.push(
|
|
5239
|
+
constraints.push(where7("status", "==", "active" /* ACTIVE */));
|
|
4696
5240
|
}
|
|
4697
|
-
constraints.push(
|
|
4698
|
-
constraints.push(
|
|
5241
|
+
constraints.push(orderBy2("basicInfo.lastName", "asc"));
|
|
5242
|
+
constraints.push(orderBy2("basicInfo.firstName", "asc"));
|
|
4699
5243
|
if ((options == null ? void 0 : options.pagination) && options.pagination > 0) {
|
|
4700
5244
|
if (options.lastDoc) {
|
|
4701
5245
|
constraints.push(startAfter4(options.lastDoc));
|
|
4702
5246
|
}
|
|
4703
|
-
constraints.push(
|
|
5247
|
+
constraints.push(limit5(options.pagination));
|
|
4704
5248
|
}
|
|
4705
|
-
const q =
|
|
4706
|
-
|
|
5249
|
+
const q = query7(
|
|
5250
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
4707
5251
|
...constraints
|
|
4708
5252
|
);
|
|
4709
|
-
const querySnapshot = await
|
|
5253
|
+
const querySnapshot = await getDocs7(q);
|
|
4710
5254
|
const practitioners = querySnapshot.docs.map(
|
|
4711
5255
|
(doc34) => doc34.data()
|
|
4712
5256
|
);
|
|
@@ -4751,31 +5295,31 @@ var PractitionerService = class extends BaseService {
|
|
|
4751
5295
|
);
|
|
4752
5296
|
const constraints = [];
|
|
4753
5297
|
if (!filters.includeDraftPractitioners) {
|
|
4754
|
-
constraints.push(
|
|
5298
|
+
constraints.push(where7("status", "==", "active" /* ACTIVE */));
|
|
4755
5299
|
}
|
|
4756
|
-
constraints.push(
|
|
5300
|
+
constraints.push(where7("isActive", "==", true));
|
|
4757
5301
|
if (filters.certifications && filters.certifications.length > 0) {
|
|
4758
5302
|
constraints.push(
|
|
4759
|
-
|
|
5303
|
+
where7(
|
|
4760
5304
|
"certification.certifications",
|
|
4761
5305
|
"array-contains-any",
|
|
4762
5306
|
filters.certifications
|
|
4763
5307
|
)
|
|
4764
5308
|
);
|
|
4765
5309
|
}
|
|
4766
|
-
constraints.push(
|
|
4767
|
-
constraints.push(
|
|
5310
|
+
constraints.push(orderBy2("basicInfo.lastName", "asc"));
|
|
5311
|
+
constraints.push(orderBy2("basicInfo.firstName", "asc"));
|
|
4768
5312
|
if (filters.pagination && filters.pagination > 0) {
|
|
4769
5313
|
if (filters.lastDoc) {
|
|
4770
5314
|
constraints.push(startAfter4(filters.lastDoc));
|
|
4771
5315
|
}
|
|
4772
|
-
constraints.push(
|
|
5316
|
+
constraints.push(limit5(filters.pagination));
|
|
4773
5317
|
}
|
|
4774
|
-
const q =
|
|
4775
|
-
|
|
5318
|
+
const q = query7(
|
|
5319
|
+
collection7(this.db, PRACTITIONERS_COLLECTION),
|
|
4776
5320
|
...constraints
|
|
4777
5321
|
);
|
|
4778
|
-
const querySnapshot = await
|
|
5322
|
+
const querySnapshot = await getDocs7(q);
|
|
4779
5323
|
console.log(
|
|
4780
5324
|
`[PRACTITIONER_SERVICE] Found ${querySnapshot.docs.length} practitioners with base query`
|
|
4781
5325
|
);
|
|
@@ -4901,7 +5445,7 @@ var UserService = class extends BaseService {
|
|
|
4901
5445
|
updatedAt: serverTimestamp10(),
|
|
4902
5446
|
lastLoginAt: serverTimestamp10()
|
|
4903
5447
|
};
|
|
4904
|
-
await
|
|
5448
|
+
await setDoc9(doc10(this.db, USERS_COLLECTION, userData.uid), userData);
|
|
4905
5449
|
if (options == null ? void 0 : options.skipProfileCreation) {
|
|
4906
5450
|
return this.getUserById(userData.uid);
|
|
4907
5451
|
}
|
|
@@ -4910,7 +5454,7 @@ var UserService = class extends BaseService {
|
|
|
4910
5454
|
roles,
|
|
4911
5455
|
options
|
|
4912
5456
|
);
|
|
4913
|
-
await
|
|
5457
|
+
await updateDoc10(doc10(this.db, USERS_COLLECTION, userData.uid), profiles);
|
|
4914
5458
|
return this.getUserById(userData.uid);
|
|
4915
5459
|
}
|
|
4916
5460
|
/**
|
|
@@ -4988,7 +5532,7 @@ var UserService = class extends BaseService {
|
|
|
4988
5532
|
email: "",
|
|
4989
5533
|
phoneNumber: "",
|
|
4990
5534
|
title: "",
|
|
4991
|
-
dateOfBirth:
|
|
5535
|
+
dateOfBirth: Timestamp11.now(),
|
|
4992
5536
|
gender: "other",
|
|
4993
5537
|
languages: ["Serbian"]
|
|
4994
5538
|
},
|
|
@@ -4997,7 +5541,7 @@ var UserService = class extends BaseService {
|
|
|
4997
5541
|
specialties: [],
|
|
4998
5542
|
licenseNumber: "",
|
|
4999
5543
|
issuingAuthority: "",
|
|
5000
|
-
issueDate:
|
|
5544
|
+
issueDate: Timestamp11.now(),
|
|
5001
5545
|
verificationStatus: "pending"
|
|
5002
5546
|
},
|
|
5003
5547
|
isActive: true,
|
|
@@ -5013,7 +5557,7 @@ var UserService = class extends BaseService {
|
|
|
5013
5557
|
* Dohvata korisnika po ID-u
|
|
5014
5558
|
*/
|
|
5015
5559
|
async getUserById(uid) {
|
|
5016
|
-
const userDoc = await
|
|
5560
|
+
const userDoc = await getDoc13(doc10(this.db, USERS_COLLECTION, uid));
|
|
5017
5561
|
if (!userDoc.exists()) {
|
|
5018
5562
|
throw USER_ERRORS.NOT_FOUND;
|
|
5019
5563
|
}
|
|
@@ -5024,19 +5568,19 @@ var UserService = class extends BaseService {
|
|
|
5024
5568
|
* Dohvata korisnika po email-u
|
|
5025
5569
|
*/
|
|
5026
5570
|
async getUserByEmail(email) {
|
|
5027
|
-
const usersRef =
|
|
5028
|
-
const q =
|
|
5029
|
-
const querySnapshot = await
|
|
5571
|
+
const usersRef = collection8(this.db, USERS_COLLECTION);
|
|
5572
|
+
const q = query8(usersRef, where8("email", "==", email));
|
|
5573
|
+
const querySnapshot = await getDocs8(q);
|
|
5030
5574
|
if (querySnapshot.empty) return null;
|
|
5031
5575
|
const userData = querySnapshot.docs[0].data();
|
|
5032
5576
|
return userSchema.parse(userData);
|
|
5033
5577
|
}
|
|
5034
5578
|
async getUsersByRole(role) {
|
|
5035
5579
|
const constraints = [
|
|
5036
|
-
|
|
5580
|
+
where8("roles", "array-contains", role)
|
|
5037
5581
|
];
|
|
5038
|
-
const q =
|
|
5039
|
-
const querySnapshot = await
|
|
5582
|
+
const q = query8(collection8(this.db, USERS_COLLECTION), ...constraints);
|
|
5583
|
+
const querySnapshot = await getDocs8(q);
|
|
5040
5584
|
const users = querySnapshot.docs.map((doc34) => doc34.data());
|
|
5041
5585
|
return Promise.all(users.map((userData) => userSchema.parse(userData)));
|
|
5042
5586
|
}
|
|
@@ -5044,24 +5588,24 @@ var UserService = class extends BaseService {
|
|
|
5044
5588
|
* Ažurira timestamp poslednjeg logovanja
|
|
5045
5589
|
*/
|
|
5046
5590
|
async updateUserLoginTimestamp(uid) {
|
|
5047
|
-
const userRef =
|
|
5048
|
-
const userDoc = await
|
|
5591
|
+
const userRef = doc10(this.db, USERS_COLLECTION, uid);
|
|
5592
|
+
const userDoc = await getDoc13(userRef);
|
|
5049
5593
|
if (!userDoc.exists()) {
|
|
5050
5594
|
throw AUTH_ERRORS.USER_NOT_FOUND;
|
|
5051
5595
|
}
|
|
5052
|
-
await
|
|
5596
|
+
await updateDoc10(userRef, {
|
|
5053
5597
|
lastLoginAt: serverTimestamp10(),
|
|
5054
5598
|
updatedAt: serverTimestamp10()
|
|
5055
5599
|
});
|
|
5056
5600
|
return this.getUserById(uid);
|
|
5057
5601
|
}
|
|
5058
5602
|
async upgradeAnonymousUser(uid, email) {
|
|
5059
|
-
const userRef =
|
|
5060
|
-
const userDoc = await
|
|
5603
|
+
const userRef = doc10(this.db, USERS_COLLECTION, uid);
|
|
5604
|
+
const userDoc = await getDoc13(userRef);
|
|
5061
5605
|
if (!userDoc.exists()) {
|
|
5062
5606
|
throw USER_ERRORS.NOT_FOUND;
|
|
5063
5607
|
}
|
|
5064
|
-
await
|
|
5608
|
+
await updateDoc10(userRef, {
|
|
5065
5609
|
email,
|
|
5066
5610
|
isAnonymous: false,
|
|
5067
5611
|
updatedAt: serverTimestamp10()
|
|
@@ -5069,8 +5613,8 @@ var UserService = class extends BaseService {
|
|
|
5069
5613
|
return this.getUserById(uid);
|
|
5070
5614
|
}
|
|
5071
5615
|
async updateUser(uid, updates) {
|
|
5072
|
-
const userRef =
|
|
5073
|
-
const userDoc = await
|
|
5616
|
+
const userRef = doc10(this.db, USERS_COLLECTION, uid);
|
|
5617
|
+
const userDoc = await getDoc13(userRef);
|
|
5074
5618
|
if (!userDoc.exists()) {
|
|
5075
5619
|
throw USER_ERRORS.NOT_FOUND;
|
|
5076
5620
|
}
|
|
@@ -5082,7 +5626,7 @@ var UserService = class extends BaseService {
|
|
|
5082
5626
|
updatedAt: serverTimestamp10()
|
|
5083
5627
|
};
|
|
5084
5628
|
userSchema.parse(updatedUser);
|
|
5085
|
-
await
|
|
5629
|
+
await updateDoc10(userRef, {
|
|
5086
5630
|
...updates,
|
|
5087
5631
|
updatedAt: serverTimestamp10()
|
|
5088
5632
|
});
|
|
@@ -5101,7 +5645,7 @@ var UserService = class extends BaseService {
|
|
|
5101
5645
|
const user = await this.getUserById(uid);
|
|
5102
5646
|
if (user.roles.includes(role)) return;
|
|
5103
5647
|
const profiles = await this.createProfilesForRoles(uid, [role], options);
|
|
5104
|
-
await
|
|
5648
|
+
await updateDoc10(doc10(this.db, USERS_COLLECTION, uid), {
|
|
5105
5649
|
roles: [...user.roles, role],
|
|
5106
5650
|
...profiles,
|
|
5107
5651
|
updatedAt: serverTimestamp10()
|
|
@@ -5136,15 +5680,15 @@ var UserService = class extends BaseService {
|
|
|
5136
5680
|
}
|
|
5137
5681
|
break;
|
|
5138
5682
|
}
|
|
5139
|
-
await
|
|
5683
|
+
await updateDoc10(doc10(this.db, USERS_COLLECTION, uid), {
|
|
5140
5684
|
roles: user.roles.filter((r) => r !== role),
|
|
5141
5685
|
updatedAt: serverTimestamp10()
|
|
5142
5686
|
});
|
|
5143
5687
|
}
|
|
5144
5688
|
// Delete operations
|
|
5145
5689
|
async deleteUser(uid) {
|
|
5146
|
-
const userRef =
|
|
5147
|
-
const userDoc = await
|
|
5690
|
+
const userRef = doc10(this.db, USERS_COLLECTION, uid);
|
|
5691
|
+
const userDoc = await getDoc13(userRef);
|
|
5148
5692
|
if (!userDoc.exists()) {
|
|
5149
5693
|
throw USER_ERRORS.NOT_FOUND;
|
|
5150
5694
|
}
|
|
@@ -5165,7 +5709,7 @@ var UserService = class extends BaseService {
|
|
|
5165
5709
|
userData.adminProfile
|
|
5166
5710
|
);
|
|
5167
5711
|
}
|
|
5168
|
-
await
|
|
5712
|
+
await deleteDoc4(userRef);
|
|
5169
5713
|
} catch (error) {
|
|
5170
5714
|
throw error;
|
|
5171
5715
|
}
|
|
@@ -5174,15 +5718,15 @@ var UserService = class extends BaseService {
|
|
|
5174
5718
|
|
|
5175
5719
|
// src/services/clinic/utils/clinic-group.utils.ts
|
|
5176
5720
|
import {
|
|
5177
|
-
collection as
|
|
5178
|
-
doc as
|
|
5179
|
-
getDoc as
|
|
5180
|
-
getDocs as
|
|
5181
|
-
query as
|
|
5182
|
-
where as
|
|
5183
|
-
updateDoc as
|
|
5184
|
-
setDoc as
|
|
5185
|
-
Timestamp as
|
|
5721
|
+
collection as collection9,
|
|
5722
|
+
doc as doc11,
|
|
5723
|
+
getDoc as getDoc14,
|
|
5724
|
+
getDocs as getDocs9,
|
|
5725
|
+
query as query9,
|
|
5726
|
+
where as where9,
|
|
5727
|
+
updateDoc as updateDoc11,
|
|
5728
|
+
setDoc as setDoc10,
|
|
5729
|
+
Timestamp as Timestamp12
|
|
5186
5730
|
} from "firebase/firestore";
|
|
5187
5731
|
import { geohashForLocation as geohashForLocation2 } from "geofire-common";
|
|
5188
5732
|
import { z as z17 } from "zod";
|
|
@@ -5306,9 +5850,9 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5306
5850
|
throw geohashError;
|
|
5307
5851
|
}
|
|
5308
5852
|
}
|
|
5309
|
-
const now =
|
|
5853
|
+
const now = Timestamp12.now();
|
|
5310
5854
|
console.log("[CLINIC_GROUP] Preparing clinic group data object");
|
|
5311
|
-
const groupId =
|
|
5855
|
+
const groupId = doc11(collection9(db, CLINIC_GROUPS_COLLECTION)).id;
|
|
5312
5856
|
console.log("[CLINIC_GROUP] Logo value:", {
|
|
5313
5857
|
logoValue: validatedData.logo,
|
|
5314
5858
|
logoType: validatedData.logo === null ? "null" : typeof validatedData.logo
|
|
@@ -5358,7 +5902,7 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5358
5902
|
groupId: groupData.id
|
|
5359
5903
|
});
|
|
5360
5904
|
try {
|
|
5361
|
-
await
|
|
5905
|
+
await setDoc10(doc11(db, CLINIC_GROUPS_COLLECTION, groupData.id), groupData);
|
|
5362
5906
|
console.log("[CLINIC_GROUP] Clinic group saved successfully");
|
|
5363
5907
|
} catch (firestoreError) {
|
|
5364
5908
|
console.error(
|
|
@@ -5404,19 +5948,19 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5404
5948
|
}
|
|
5405
5949
|
}
|
|
5406
5950
|
async function getClinicGroup(db, groupId) {
|
|
5407
|
-
const docRef =
|
|
5408
|
-
const docSnap = await
|
|
5951
|
+
const docRef = doc11(db, CLINIC_GROUPS_COLLECTION, groupId);
|
|
5952
|
+
const docSnap = await getDoc14(docRef);
|
|
5409
5953
|
if (docSnap.exists()) {
|
|
5410
5954
|
return docSnap.data();
|
|
5411
5955
|
}
|
|
5412
5956
|
return null;
|
|
5413
5957
|
}
|
|
5414
5958
|
async function getAllActiveGroups(db) {
|
|
5415
|
-
const q =
|
|
5416
|
-
|
|
5417
|
-
|
|
5959
|
+
const q = query9(
|
|
5960
|
+
collection9(db, CLINIC_GROUPS_COLLECTION),
|
|
5961
|
+
where9("isActive", "==", true)
|
|
5418
5962
|
);
|
|
5419
|
-
const querySnapshot = await
|
|
5963
|
+
const querySnapshot = await getDocs9(q);
|
|
5420
5964
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
5421
5965
|
}
|
|
5422
5966
|
async function updateClinicGroup(db, groupId, data, app) {
|
|
@@ -5445,10 +5989,10 @@ async function updateClinicGroup(db, groupId, data, app) {
|
|
|
5445
5989
|
}
|
|
5446
5990
|
updatedData = {
|
|
5447
5991
|
...updatedData,
|
|
5448
|
-
updatedAt:
|
|
5992
|
+
updatedAt: Timestamp12.now()
|
|
5449
5993
|
};
|
|
5450
5994
|
console.log("[CLINIC_GROUP] Updating clinic group in Firestore");
|
|
5451
|
-
await
|
|
5995
|
+
await updateDoc11(doc11(db, CLINIC_GROUPS_COLLECTION, groupId), updatedData);
|
|
5452
5996
|
console.log("[CLINIC_GROUP] Clinic group updated successfully");
|
|
5453
5997
|
const updatedGroup = await getClinicGroup(db, groupId);
|
|
5454
5998
|
if (!updatedGroup) {
|
|
@@ -5529,10 +6073,10 @@ async function createAdminToken(db, groupId, creatorAdminId, app, data) {
|
|
|
5529
6073
|
if (!group.admins.includes(creatorAdminId)) {
|
|
5530
6074
|
throw new Error("Admin does not belong to this clinic group");
|
|
5531
6075
|
}
|
|
5532
|
-
const now =
|
|
6076
|
+
const now = Timestamp12.now();
|
|
5533
6077
|
const expiresInDays = (data == null ? void 0 : data.expiresInDays) || 7;
|
|
5534
6078
|
const email = (data == null ? void 0 : data.email) || null;
|
|
5535
|
-
const expiresAt = new
|
|
6079
|
+
const expiresAt = new Timestamp12(
|
|
5536
6080
|
now.seconds + expiresInDays * 24 * 60 * 60,
|
|
5537
6081
|
now.nanoseconds
|
|
5538
6082
|
);
|
|
@@ -5566,7 +6110,7 @@ async function verifyAndUseAdminToken(db, groupId, token, userRef, app) {
|
|
|
5566
6110
|
if (adminToken.status !== "active" /* ACTIVE */) {
|
|
5567
6111
|
throw new Error("Admin token is not active");
|
|
5568
6112
|
}
|
|
5569
|
-
const now =
|
|
6113
|
+
const now = Timestamp12.now();
|
|
5570
6114
|
if (adminToken.expiresAt.seconds < now.seconds) {
|
|
5571
6115
|
const updatedTokens2 = group.adminTokens.map(
|
|
5572
6116
|
(t) => t.id === adminToken.id ? { ...t, status: "expired" /* EXPIRED */ } : t
|
|
@@ -5849,16 +6393,16 @@ import { z as z19 } from "zod";
|
|
|
5849
6393
|
|
|
5850
6394
|
// src/services/clinic/utils/clinic.utils.ts
|
|
5851
6395
|
import {
|
|
5852
|
-
collection as
|
|
5853
|
-
doc as
|
|
5854
|
-
getDoc as
|
|
5855
|
-
getDocs as
|
|
5856
|
-
query as
|
|
5857
|
-
where as
|
|
5858
|
-
updateDoc as
|
|
5859
|
-
setDoc as
|
|
5860
|
-
Timestamp as
|
|
5861
|
-
limit as
|
|
6396
|
+
collection as collection10,
|
|
6397
|
+
doc as doc12,
|
|
6398
|
+
getDoc as getDoc15,
|
|
6399
|
+
getDocs as getDocs10,
|
|
6400
|
+
query as query10,
|
|
6401
|
+
where as where10,
|
|
6402
|
+
updateDoc as updateDoc12,
|
|
6403
|
+
setDoc as setDoc11,
|
|
6404
|
+
Timestamp as Timestamp13,
|
|
6405
|
+
limit as limit6,
|
|
5862
6406
|
startAfter as startAfter5
|
|
5863
6407
|
} from "firebase/firestore";
|
|
5864
6408
|
import {
|
|
@@ -5868,20 +6412,20 @@ import {
|
|
|
5868
6412
|
} from "geofire-common";
|
|
5869
6413
|
import { z as z18 } from "zod";
|
|
5870
6414
|
async function getClinic(db, clinicId) {
|
|
5871
|
-
const docRef =
|
|
5872
|
-
const docSnap = await
|
|
6415
|
+
const docRef = doc12(db, CLINICS_COLLECTION, clinicId);
|
|
6416
|
+
const docSnap = await getDoc15(docRef);
|
|
5873
6417
|
if (docSnap.exists()) {
|
|
5874
6418
|
return docSnap.data();
|
|
5875
6419
|
}
|
|
5876
6420
|
return null;
|
|
5877
6421
|
}
|
|
5878
6422
|
async function getClinicsByGroup(db, groupId) {
|
|
5879
|
-
const q =
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
6423
|
+
const q = query10(
|
|
6424
|
+
collection10(db, CLINICS_COLLECTION),
|
|
6425
|
+
where10("clinicGroupId", "==", groupId),
|
|
6426
|
+
where10("isActive", "==", true)
|
|
5883
6427
|
);
|
|
5884
|
-
const querySnapshot = await
|
|
6428
|
+
const querySnapshot = await getDocs10(q);
|
|
5885
6429
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
5886
6430
|
}
|
|
5887
6431
|
async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
|
|
@@ -6037,11 +6581,11 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
6037
6581
|
}
|
|
6038
6582
|
updatedData = {
|
|
6039
6583
|
...updatedData,
|
|
6040
|
-
updatedAt:
|
|
6584
|
+
updatedAt: Timestamp13.now()
|
|
6041
6585
|
};
|
|
6042
6586
|
console.log("[CLINIC] Updating clinic in Firestore");
|
|
6043
6587
|
try {
|
|
6044
|
-
await
|
|
6588
|
+
await updateDoc12(doc12(db, CLINICS_COLLECTION, clinicId), updatedData);
|
|
6045
6589
|
console.log("[CLINIC] Clinic updated successfully");
|
|
6046
6590
|
} catch (updateError) {
|
|
6047
6591
|
console.error("[CLINIC] Error updating clinic in Firestore:", updateError);
|
|
@@ -6070,12 +6614,12 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
|
|
|
6070
6614
|
if (clinicIds.length === 0) {
|
|
6071
6615
|
return [];
|
|
6072
6616
|
}
|
|
6073
|
-
const constraints = [
|
|
6617
|
+
const constraints = [where10("id", "in", clinicIds)];
|
|
6074
6618
|
if (options.isActive !== void 0) {
|
|
6075
|
-
constraints.push(
|
|
6619
|
+
constraints.push(where10("isActive", "==", options.isActive));
|
|
6076
6620
|
}
|
|
6077
|
-
const q =
|
|
6078
|
-
const querySnapshot = await
|
|
6621
|
+
const q = query10(collection10(db, CLINICS_COLLECTION), ...constraints);
|
|
6622
|
+
const querySnapshot = await getDocs10(q);
|
|
6079
6623
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
6080
6624
|
}
|
|
6081
6625
|
async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
|
|
@@ -6089,8 +6633,8 @@ async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGr
|
|
|
6089
6633
|
}
|
|
6090
6634
|
async function getClinicById(db, clinicId) {
|
|
6091
6635
|
try {
|
|
6092
|
-
const clinicRef =
|
|
6093
|
-
const clinicSnapshot = await
|
|
6636
|
+
const clinicRef = doc12(db, CLINICS_COLLECTION, clinicId);
|
|
6637
|
+
const clinicSnapshot = await getDoc15(clinicRef);
|
|
6094
6638
|
if (!clinicSnapshot.exists()) {
|
|
6095
6639
|
return null;
|
|
6096
6640
|
}
|
|
@@ -6106,20 +6650,20 @@ async function getClinicById(db, clinicId) {
|
|
|
6106
6650
|
}
|
|
6107
6651
|
async function getAllClinics(db, pagination, lastDoc) {
|
|
6108
6652
|
try {
|
|
6109
|
-
const clinicsCollection =
|
|
6110
|
-
let clinicsQuery =
|
|
6653
|
+
const clinicsCollection = collection10(db, CLINICS_COLLECTION);
|
|
6654
|
+
let clinicsQuery = query10(clinicsCollection);
|
|
6111
6655
|
if (pagination && pagination > 0) {
|
|
6112
6656
|
if (lastDoc) {
|
|
6113
|
-
clinicsQuery =
|
|
6657
|
+
clinicsQuery = query10(
|
|
6114
6658
|
clinicsCollection,
|
|
6115
6659
|
startAfter5(lastDoc),
|
|
6116
|
-
|
|
6660
|
+
limit6(pagination)
|
|
6117
6661
|
);
|
|
6118
6662
|
} else {
|
|
6119
|
-
clinicsQuery =
|
|
6663
|
+
clinicsQuery = query10(clinicsCollection, limit6(pagination));
|
|
6120
6664
|
}
|
|
6121
6665
|
}
|
|
6122
|
-
const clinicsSnapshot = await
|
|
6666
|
+
const clinicsSnapshot = await getDocs10(clinicsQuery);
|
|
6123
6667
|
const lastVisible = clinicsSnapshot.docs[clinicsSnapshot.docs.length - 1];
|
|
6124
6668
|
const clinics = clinicsSnapshot.docs.map((doc34) => {
|
|
6125
6669
|
const data = doc34.data();
|
|
@@ -6146,12 +6690,12 @@ async function getAllClinicsInRange(db, center, rangeInKm, pagination, lastDoc)
|
|
|
6146
6690
|
let lastDocSnapshot = null;
|
|
6147
6691
|
for (const b of bounds) {
|
|
6148
6692
|
const constraints = [
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6693
|
+
where10("location.geohash", ">=", b[0]),
|
|
6694
|
+
where10("location.geohash", "<=", b[1]),
|
|
6695
|
+
where10("isActive", "==", true)
|
|
6152
6696
|
];
|
|
6153
|
-
const q =
|
|
6154
|
-
const querySnapshot = await
|
|
6697
|
+
const q = query10(collection10(db, CLINICS_COLLECTION), ...constraints);
|
|
6698
|
+
const querySnapshot = await getDocs10(q);
|
|
6155
6699
|
for (const doc34 of querySnapshot.docs) {
|
|
6156
6700
|
const clinic = doc34.data();
|
|
6157
6701
|
const distance = distanceBetween2(
|
|
@@ -6247,10 +6791,10 @@ async function removeTags(db, clinicId, adminId, tagsToRemove, clinicAdminServic
|
|
|
6247
6791
|
|
|
6248
6792
|
// src/services/clinic/utils/search.utils.ts
|
|
6249
6793
|
import {
|
|
6250
|
-
collection as
|
|
6251
|
-
query as
|
|
6252
|
-
where as
|
|
6253
|
-
getDocs as
|
|
6794
|
+
collection as collection11,
|
|
6795
|
+
query as query11,
|
|
6796
|
+
where as where11,
|
|
6797
|
+
getDocs as getDocs11
|
|
6254
6798
|
} from "firebase/firestore";
|
|
6255
6799
|
import { geohashQueryBounds as geohashQueryBounds2, distanceBetween as distanceBetween3 } from "geofire-common";
|
|
6256
6800
|
async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
@@ -6261,20 +6805,20 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
6261
6805
|
const matchingDocs = [];
|
|
6262
6806
|
for (const b of bounds) {
|
|
6263
6807
|
const constraints = [
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6808
|
+
where11("location.geohash", ">=", b[0]),
|
|
6809
|
+
where11("location.geohash", "<=", b[1]),
|
|
6810
|
+
where11("isActive", "==", true)
|
|
6267
6811
|
];
|
|
6268
6812
|
if (filters == null ? void 0 : filters.services) {
|
|
6269
6813
|
constraints.push(
|
|
6270
|
-
|
|
6814
|
+
where11("services", "array-contains-any", filters.services)
|
|
6271
6815
|
);
|
|
6272
6816
|
}
|
|
6273
6817
|
if ((filters == null ? void 0 : filters.tags) && filters.tags.length > 0) {
|
|
6274
|
-
constraints.push(
|
|
6818
|
+
constraints.push(where11("tags", "array-contains-any", filters.tags));
|
|
6275
6819
|
}
|
|
6276
|
-
const q =
|
|
6277
|
-
const querySnapshot = await
|
|
6820
|
+
const q = query11(collection11(db, CLINICS_COLLECTION), ...constraints);
|
|
6821
|
+
const querySnapshot = await getDocs11(q);
|
|
6278
6822
|
for (const doc34 of querySnapshot.docs) {
|
|
6279
6823
|
const clinic = doc34.data();
|
|
6280
6824
|
const distance = distanceBetween3(
|
|
@@ -6302,13 +6846,13 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
6302
6846
|
|
|
6303
6847
|
// src/services/clinic/utils/filter.utils.ts
|
|
6304
6848
|
import {
|
|
6305
|
-
collection as
|
|
6306
|
-
query as
|
|
6307
|
-
where as
|
|
6308
|
-
getDocs as
|
|
6849
|
+
collection as collection12,
|
|
6850
|
+
query as query12,
|
|
6851
|
+
where as where12,
|
|
6852
|
+
getDocs as getDocs12,
|
|
6309
6853
|
startAfter as startAfter6,
|
|
6310
|
-
limit as
|
|
6311
|
-
orderBy as
|
|
6854
|
+
limit as limit7,
|
|
6855
|
+
orderBy as orderBy3
|
|
6312
6856
|
} from "firebase/firestore";
|
|
6313
6857
|
import { geohashQueryBounds as geohashQueryBounds3, distanceBetween as distanceBetween4 } from "geofire-common";
|
|
6314
6858
|
async function getClinicsByFilters(db, filters) {
|
|
@@ -6319,37 +6863,37 @@ async function getClinicsByFilters(db, filters) {
|
|
|
6319
6863
|
const isGeoQuery = filters.center && filters.radiusInKm && filters.radiusInKm > 0;
|
|
6320
6864
|
const constraints = [];
|
|
6321
6865
|
if (filters.isActive !== void 0) {
|
|
6322
|
-
constraints.push(
|
|
6866
|
+
constraints.push(where12("isActive", "==", filters.isActive));
|
|
6323
6867
|
} else {
|
|
6324
|
-
constraints.push(
|
|
6868
|
+
constraints.push(where12("isActive", "==", true));
|
|
6325
6869
|
}
|
|
6326
6870
|
if (filters.tags && filters.tags.length > 0) {
|
|
6327
|
-
constraints.push(
|
|
6871
|
+
constraints.push(where12("tags", "array-contains", filters.tags[0]));
|
|
6328
6872
|
}
|
|
6329
6873
|
if (filters.procedureTechnology) {
|
|
6330
6874
|
constraints.push(
|
|
6331
|
-
|
|
6875
|
+
where12("servicesInfo.technology", "==", filters.procedureTechnology)
|
|
6332
6876
|
);
|
|
6333
6877
|
} else if (filters.procedureSubcategory) {
|
|
6334
6878
|
constraints.push(
|
|
6335
|
-
|
|
6879
|
+
where12("servicesInfo.subCategory", "==", filters.procedureSubcategory)
|
|
6336
6880
|
);
|
|
6337
6881
|
} else if (filters.procedureCategory) {
|
|
6338
6882
|
constraints.push(
|
|
6339
|
-
|
|
6883
|
+
where12("servicesInfo.category", "==", filters.procedureCategory)
|
|
6340
6884
|
);
|
|
6341
6885
|
} else if (filters.procedureFamily) {
|
|
6342
6886
|
constraints.push(
|
|
6343
|
-
|
|
6887
|
+
where12("servicesInfo.procedureFamily", "==", filters.procedureFamily)
|
|
6344
6888
|
);
|
|
6345
6889
|
}
|
|
6346
6890
|
if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
|
|
6347
6891
|
constraints.push(startAfter6(filters.lastDoc));
|
|
6348
|
-
constraints.push(
|
|
6892
|
+
constraints.push(limit7(filters.pagination));
|
|
6349
6893
|
} else if (filters.pagination && filters.pagination > 0) {
|
|
6350
|
-
constraints.push(
|
|
6894
|
+
constraints.push(limit7(filters.pagination));
|
|
6351
6895
|
}
|
|
6352
|
-
constraints.push(
|
|
6896
|
+
constraints.push(orderBy3("location.geohash"));
|
|
6353
6897
|
let clinicsResult = [];
|
|
6354
6898
|
let lastVisibleDoc = null;
|
|
6355
6899
|
if (isGeoQuery) {
|
|
@@ -6357,440 +6901,124 @@ async function getClinicsByFilters(db, filters) {
|
|
|
6357
6901
|
const radiusInKm = filters.radiusInKm;
|
|
6358
6902
|
const bounds = geohashQueryBounds3(
|
|
6359
6903
|
[center.latitude, center.longitude],
|
|
6360
|
-
radiusInKm * 1e3
|
|
6361
|
-
// Convert to meters
|
|
6362
|
-
);
|
|
6363
|
-
const matchingClinics = [];
|
|
6364
|
-
for (const bound of bounds) {
|
|
6365
|
-
const geoConstraints = [
|
|
6366
|
-
...constraints,
|
|
6367
|
-
where11("location.geohash", ">=", bound[0]),
|
|
6368
|
-
where11("location.geohash", "<=", bound[1])
|
|
6369
|
-
];
|
|
6370
|
-
const q = query11(collection11(db, CLINICS_COLLECTION), ...geoConstraints);
|
|
6371
|
-
const querySnapshot = await getDocs11(q);
|
|
6372
|
-
console.log(
|
|
6373
|
-
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
6374
|
-
);
|
|
6375
|
-
for (const doc34 of querySnapshot.docs) {
|
|
6376
|
-
const clinic = { ...doc34.data(), id: doc34.id };
|
|
6377
|
-
const distance = distanceBetween4(
|
|
6378
|
-
[center.latitude, center.longitude],
|
|
6379
|
-
[clinic.location.latitude, clinic.location.longitude]
|
|
6380
|
-
);
|
|
6381
|
-
const distanceInKm = distance / 1e3;
|
|
6382
|
-
if (distanceInKm <= radiusInKm) {
|
|
6383
|
-
matchingClinics.push({
|
|
6384
|
-
...clinic,
|
|
6385
|
-
distance: distanceInKm
|
|
6386
|
-
});
|
|
6387
|
-
}
|
|
6388
|
-
}
|
|
6389
|
-
}
|
|
6390
|
-
let filteredClinics = matchingClinics;
|
|
6391
|
-
if (filters.tags && filters.tags.length > 1) {
|
|
6392
|
-
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6393
|
-
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6394
|
-
});
|
|
6395
|
-
}
|
|
6396
|
-
if (filters.minRating !== void 0) {
|
|
6397
|
-
filteredClinics = filteredClinics.filter(
|
|
6398
|
-
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6399
|
-
);
|
|
6400
|
-
}
|
|
6401
|
-
if (filters.maxRating !== void 0) {
|
|
6402
|
-
filteredClinics = filteredClinics.filter(
|
|
6403
|
-
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6404
|
-
);
|
|
6405
|
-
}
|
|
6406
|
-
filteredClinics.sort((a, b) => a.distance - b.distance);
|
|
6407
|
-
if (filters.pagination && filters.pagination > 0) {
|
|
6408
|
-
let startIndex = 0;
|
|
6409
|
-
if (filters.lastDoc) {
|
|
6410
|
-
const lastDocIndex = filteredClinics.findIndex(
|
|
6411
|
-
(clinic) => clinic.id === filters.lastDoc.id
|
|
6412
|
-
);
|
|
6413
|
-
if (lastDocIndex !== -1) {
|
|
6414
|
-
startIndex = lastDocIndex + 1;
|
|
6415
|
-
}
|
|
6416
|
-
}
|
|
6417
|
-
const paginatedClinics = filteredClinics.slice(
|
|
6418
|
-
startIndex,
|
|
6419
|
-
startIndex + filters.pagination
|
|
6420
|
-
);
|
|
6421
|
-
lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
|
|
6422
|
-
clinicsResult = paginatedClinics;
|
|
6423
|
-
} else {
|
|
6424
|
-
clinicsResult = filteredClinics;
|
|
6425
|
-
}
|
|
6426
|
-
} else {
|
|
6427
|
-
const q = query11(collection11(db, CLINICS_COLLECTION), ...constraints);
|
|
6428
|
-
const querySnapshot = await getDocs11(q);
|
|
6429
|
-
console.log(
|
|
6430
|
-
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
6431
|
-
);
|
|
6432
|
-
const clinics = querySnapshot.docs.map((doc34) => {
|
|
6433
|
-
return { ...doc34.data(), id: doc34.id };
|
|
6434
|
-
});
|
|
6435
|
-
let filteredClinics = clinics;
|
|
6436
|
-
if (filters.center) {
|
|
6437
|
-
const center = filters.center;
|
|
6438
|
-
const clinicsWithDistance = [];
|
|
6439
|
-
filteredClinics.forEach((clinic) => {
|
|
6440
|
-
const distance = distanceBetween4(
|
|
6441
|
-
[center.latitude, center.longitude],
|
|
6442
|
-
[clinic.location.latitude, clinic.location.longitude]
|
|
6443
|
-
);
|
|
6444
|
-
clinicsWithDistance.push({
|
|
6445
|
-
...clinic,
|
|
6446
|
-
distance: distance / 1e3
|
|
6447
|
-
// Convert to kilometers
|
|
6448
|
-
});
|
|
6449
|
-
});
|
|
6450
|
-
filteredClinics = clinicsWithDistance;
|
|
6451
|
-
filteredClinics.sort(
|
|
6452
|
-
(a, b) => a.distance - b.distance
|
|
6453
|
-
);
|
|
6454
|
-
}
|
|
6455
|
-
if (filters.tags && filters.tags.length > 1) {
|
|
6456
|
-
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6457
|
-
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6458
|
-
});
|
|
6459
|
-
}
|
|
6460
|
-
if (filters.minRating !== void 0) {
|
|
6461
|
-
filteredClinics = filteredClinics.filter(
|
|
6462
|
-
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6463
|
-
);
|
|
6464
|
-
}
|
|
6465
|
-
if (filters.maxRating !== void 0) {
|
|
6466
|
-
filteredClinics = filteredClinics.filter(
|
|
6467
|
-
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6468
|
-
);
|
|
6469
|
-
}
|
|
6470
|
-
lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
6471
|
-
clinicsResult = filteredClinics;
|
|
6472
|
-
}
|
|
6473
|
-
return {
|
|
6474
|
-
clinics: clinicsResult,
|
|
6475
|
-
lastDoc: lastVisibleDoc
|
|
6476
|
-
};
|
|
6477
|
-
}
|
|
6478
|
-
|
|
6479
|
-
// src/services/media/media.service.ts
|
|
6480
|
-
import { Timestamp as Timestamp13 } from "firebase/firestore";
|
|
6481
|
-
import {
|
|
6482
|
-
ref as ref3,
|
|
6483
|
-
uploadBytes as uploadBytes3,
|
|
6484
|
-
getDownloadURL as getDownloadURL3,
|
|
6485
|
-
deleteObject as deleteObject3,
|
|
6486
|
-
getBytes
|
|
6487
|
-
} from "firebase/storage";
|
|
6488
|
-
import {
|
|
6489
|
-
doc as doc12,
|
|
6490
|
-
getDoc as getDoc15,
|
|
6491
|
-
setDoc as setDoc11,
|
|
6492
|
-
updateDoc as updateDoc12,
|
|
6493
|
-
collection as collection12,
|
|
6494
|
-
query as query12,
|
|
6495
|
-
where as where12,
|
|
6496
|
-
limit as limit7,
|
|
6497
|
-
getDocs as getDocs12,
|
|
6498
|
-
deleteDoc as deleteDoc6,
|
|
6499
|
-
orderBy as orderBy3
|
|
6500
|
-
} from "firebase/firestore";
|
|
6501
|
-
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
6502
|
-
MediaAccessLevel2["PUBLIC"] = "public";
|
|
6503
|
-
MediaAccessLevel2["PRIVATE"] = "private";
|
|
6504
|
-
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
6505
|
-
return MediaAccessLevel2;
|
|
6506
|
-
})(MediaAccessLevel || {});
|
|
6507
|
-
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
6508
|
-
var MediaService = class extends BaseService {
|
|
6509
|
-
constructor(db, auth, app) {
|
|
6510
|
-
super(db, auth, app);
|
|
6511
|
-
}
|
|
6512
|
-
/**
|
|
6513
|
-
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
6514
|
-
* @param file - The file to upload.
|
|
6515
|
-
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
6516
|
-
* @param accessLevel - Access level (public, private, confidential).
|
|
6517
|
-
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
6518
|
-
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
6519
|
-
* @returns Promise with the media metadata.
|
|
6520
|
-
*/
|
|
6521
|
-
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
6522
|
-
const mediaId = this.generateId();
|
|
6523
|
-
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
6524
|
-
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
6525
|
-
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
6526
|
-
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
6527
|
-
const storageRef = ref3(this.storage, filePath);
|
|
6528
|
-
try {
|
|
6529
|
-
const uploadResult = await uploadBytes3(storageRef, file, {
|
|
6530
|
-
contentType: file.type
|
|
6531
|
-
});
|
|
6532
|
-
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
6533
|
-
const downloadURL = await getDownloadURL3(uploadResult.ref);
|
|
6534
|
-
console.log("[MediaService] Got download URL:", downloadURL);
|
|
6535
|
-
const metadata = {
|
|
6536
|
-
id: mediaId,
|
|
6537
|
-
name: fileNameToUse,
|
|
6538
|
-
url: downloadURL,
|
|
6539
|
-
contentType: file.type,
|
|
6540
|
-
size: file.size,
|
|
6541
|
-
createdAt: Timestamp13.now(),
|
|
6542
|
-
accessLevel,
|
|
6543
|
-
ownerId,
|
|
6544
|
-
collectionName,
|
|
6545
|
-
path: filePath
|
|
6546
|
-
};
|
|
6547
|
-
const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6548
|
-
await setDoc11(metadataDocRef, metadata);
|
|
6549
|
-
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
6550
|
-
return metadata;
|
|
6551
|
-
} catch (error) {
|
|
6552
|
-
console.error("[MediaService] Error during media upload:", error);
|
|
6553
|
-
throw error;
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
/**
|
|
6557
|
-
* Get media metadata from Firestore by its ID.
|
|
6558
|
-
* @param mediaId - ID of the media.
|
|
6559
|
-
* @returns Promise with the media metadata or null if not found.
|
|
6560
|
-
*/
|
|
6561
|
-
async getMediaMetadata(mediaId) {
|
|
6562
|
-
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
6563
|
-
const docRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6564
|
-
const docSnap = await getDoc15(docRef);
|
|
6565
|
-
if (docSnap.exists()) {
|
|
6566
|
-
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
6567
|
-
return docSnap.data();
|
|
6568
|
-
}
|
|
6569
|
-
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
6570
|
-
return null;
|
|
6571
|
-
}
|
|
6572
|
-
/**
|
|
6573
|
-
* Get media metadata from Firestore by its public URL.
|
|
6574
|
-
* @param url - The public URL of the media file.
|
|
6575
|
-
* @returns Promise with the media metadata or null if not found.
|
|
6576
|
-
*/
|
|
6577
|
-
async getMediaMetadataByUrl(url) {
|
|
6578
|
-
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
6579
|
-
const q = query12(
|
|
6580
|
-
collection12(this.db, MEDIA_METADATA_COLLECTION),
|
|
6581
|
-
where12("url", "==", url),
|
|
6582
|
-
limit7(1)
|
|
6904
|
+
radiusInKm * 1e3
|
|
6905
|
+
// Convert to meters
|
|
6583
6906
|
);
|
|
6584
|
-
|
|
6907
|
+
const matchingClinics = [];
|
|
6908
|
+
for (const bound of bounds) {
|
|
6909
|
+
const geoConstraints = [
|
|
6910
|
+
...constraints,
|
|
6911
|
+
where12("location.geohash", ">=", bound[0]),
|
|
6912
|
+
where12("location.geohash", "<=", bound[1])
|
|
6913
|
+
];
|
|
6914
|
+
const q = query12(collection12(db, CLINICS_COLLECTION), ...geoConstraints);
|
|
6585
6915
|
const querySnapshot = await getDocs12(q);
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6916
|
+
console.log(
|
|
6917
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
6918
|
+
);
|
|
6919
|
+
for (const doc34 of querySnapshot.docs) {
|
|
6920
|
+
const clinic = { ...doc34.data(), id: doc34.id };
|
|
6921
|
+
const distance = distanceBetween4(
|
|
6922
|
+
[center.latitude, center.longitude],
|
|
6923
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
6924
|
+
);
|
|
6925
|
+
const distanceInKm = distance / 1e3;
|
|
6926
|
+
if (distanceInKm <= radiusInKm) {
|
|
6927
|
+
matchingClinics.push({
|
|
6928
|
+
...clinic,
|
|
6929
|
+
distance: distanceInKm
|
|
6930
|
+
});
|
|
6931
|
+
}
|
|
6590
6932
|
}
|
|
6591
|
-
console.log("[MediaService] No metadata found for URL:", url);
|
|
6592
|
-
return null;
|
|
6593
|
-
} catch (error) {
|
|
6594
|
-
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
6595
|
-
throw error;
|
|
6596
6933
|
}
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
async deleteMedia(mediaId) {
|
|
6603
|
-
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
6604
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6605
|
-
if (!metadata) {
|
|
6606
|
-
console.warn(
|
|
6607
|
-
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
6608
|
-
);
|
|
6609
|
-
return;
|
|
6934
|
+
let filteredClinics = matchingClinics;
|
|
6935
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
6936
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6937
|
+
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6938
|
+
});
|
|
6610
6939
|
}
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
6615
|
-
const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6616
|
-
await deleteDoc6(metadataDocRef);
|
|
6617
|
-
console.log(
|
|
6618
|
-
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
6940
|
+
if (filters.minRating !== void 0) {
|
|
6941
|
+
filteredClinics = filteredClinics.filter(
|
|
6942
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6619
6943
|
);
|
|
6620
|
-
} catch (error) {
|
|
6621
|
-
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
6622
|
-
throw error;
|
|
6623
6944
|
}
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
* to a new path reflecting the new access level, and updating its metadata.
|
|
6628
|
-
* @param mediaId - ID of the media to update.
|
|
6629
|
-
* @param newAccessLevel - New access level.
|
|
6630
|
-
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
6631
|
-
*/
|
|
6632
|
-
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
6633
|
-
var _a;
|
|
6634
|
-
console.log(
|
|
6635
|
-
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
6636
|
-
);
|
|
6637
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6638
|
-
if (!metadata) {
|
|
6639
|
-
console.warn(
|
|
6640
|
-
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
6945
|
+
if (filters.maxRating !== void 0) {
|
|
6946
|
+
filteredClinics = filteredClinics.filter(
|
|
6947
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6641
6948
|
);
|
|
6642
|
-
return null;
|
|
6643
6949
|
}
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
)
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
await updateDoc12(metadataDocRef, { updatedAt: Timestamp13.now() });
|
|
6651
|
-
return { ...metadata, updatedAt: Timestamp13.now() };
|
|
6652
|
-
} catch (error) {
|
|
6653
|
-
console.error(
|
|
6654
|
-
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
6655
|
-
error
|
|
6950
|
+
filteredClinics.sort((a, b) => a.distance - b.distance);
|
|
6951
|
+
if (filters.pagination && filters.pagination > 0) {
|
|
6952
|
+
let startIndex = 0;
|
|
6953
|
+
if (filters.lastDoc) {
|
|
6954
|
+
const lastDocIndex = filteredClinics.findIndex(
|
|
6955
|
+
(clinic) => clinic.id === filters.lastDoc.id
|
|
6656
6956
|
);
|
|
6657
|
-
|
|
6957
|
+
if (lastDocIndex !== -1) {
|
|
6958
|
+
startIndex = lastDocIndex + 1;
|
|
6959
|
+
}
|
|
6658
6960
|
}
|
|
6961
|
+
const paginatedClinics = filteredClinics.slice(
|
|
6962
|
+
startIndex,
|
|
6963
|
+
startIndex + filters.pagination
|
|
6964
|
+
);
|
|
6965
|
+
lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
|
|
6966
|
+
clinicsResult = paginatedClinics;
|
|
6967
|
+
} else {
|
|
6968
|
+
clinicsResult = filteredClinics;
|
|
6659
6969
|
}
|
|
6660
|
-
|
|
6661
|
-
const
|
|
6662
|
-
const
|
|
6970
|
+
} else {
|
|
6971
|
+
const q = query12(collection12(db, CLINICS_COLLECTION), ...constraints);
|
|
6972
|
+
const querySnapshot = await getDocs12(q);
|
|
6663
6973
|
console.log(
|
|
6664
|
-
`[
|
|
6974
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
6665
6975
|
);
|
|
6666
|
-
const
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
)
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
});
|
|
6678
|
-
console.log(
|
|
6679
|
-
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
6680
|
-
);
|
|
6681
|
-
const newDownloadURL = await getDownloadURL3(newStorageFileRef);
|
|
6682
|
-
console.log(
|
|
6683
|
-
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
6684
|
-
);
|
|
6685
|
-
const updateData = {
|
|
6686
|
-
accessLevel: newAccessLevel,
|
|
6687
|
-
path: newStoragePath,
|
|
6688
|
-
url: newDownloadURL,
|
|
6689
|
-
updatedAt: Timestamp13.now()
|
|
6690
|
-
};
|
|
6691
|
-
const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6692
|
-
console.log(
|
|
6693
|
-
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
6694
|
-
updateData
|
|
6695
|
-
);
|
|
6696
|
-
await updateDoc12(metadataDocRef, updateData);
|
|
6697
|
-
console.log(
|
|
6698
|
-
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
6699
|
-
);
|
|
6700
|
-
try {
|
|
6701
|
-
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
6702
|
-
await deleteObject3(oldStorageFileRef);
|
|
6703
|
-
console.log(
|
|
6704
|
-
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
6705
|
-
);
|
|
6706
|
-
} catch (deleteError) {
|
|
6707
|
-
console.error(
|
|
6708
|
-
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
6709
|
-
deleteError
|
|
6976
|
+
const clinics = querySnapshot.docs.map((doc34) => {
|
|
6977
|
+
return { ...doc34.data(), id: doc34.id };
|
|
6978
|
+
});
|
|
6979
|
+
let filteredClinics = clinics;
|
|
6980
|
+
if (filters.center) {
|
|
6981
|
+
const center = filters.center;
|
|
6982
|
+
const clinicsWithDistance = [];
|
|
6983
|
+
filteredClinics.forEach((clinic) => {
|
|
6984
|
+
const distance = distanceBetween4(
|
|
6985
|
+
[center.latitude, center.longitude],
|
|
6986
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
6710
6987
|
);
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6988
|
+
clinicsWithDistance.push({
|
|
6989
|
+
...clinic,
|
|
6990
|
+
distance: distance / 1e3
|
|
6991
|
+
// Convert to kilometers
|
|
6992
|
+
});
|
|
6993
|
+
});
|
|
6994
|
+
filteredClinics = clinicsWithDistance;
|
|
6995
|
+
filteredClinics.sort(
|
|
6996
|
+
(a, b) => a.distance - b.distance
|
|
6717
6997
|
);
|
|
6718
|
-
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
6719
|
-
console.warn(
|
|
6720
|
-
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
6721
|
-
);
|
|
6722
|
-
try {
|
|
6723
|
-
await deleteObject3(newStorageFileRef);
|
|
6724
|
-
console.warn(
|
|
6725
|
-
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
6726
|
-
);
|
|
6727
|
-
} catch (cleanupError) {
|
|
6728
|
-
console.error(
|
|
6729
|
-
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
6730
|
-
cleanupError
|
|
6731
|
-
);
|
|
6732
|
-
}
|
|
6733
|
-
}
|
|
6734
|
-
throw error;
|
|
6735
|
-
}
|
|
6736
|
-
}
|
|
6737
|
-
/**
|
|
6738
|
-
* List all media for an owner, optionally filtered by collection and access level.
|
|
6739
|
-
* @param ownerId - ID of the owner.
|
|
6740
|
-
* @param collectionName - Optional: Filter by collection name.
|
|
6741
|
-
* @param accessLevel - Optional: Filter by access level.
|
|
6742
|
-
* @param count - Optional: Number of items to fetch.
|
|
6743
|
-
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
6744
|
-
*/
|
|
6745
|
-
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
6746
|
-
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
6747
|
-
let qConstraints = [where12("ownerId", "==", ownerId)];
|
|
6748
|
-
if (collectionName) {
|
|
6749
|
-
qConstraints.push(where12("collectionName", "==", collectionName));
|
|
6750
|
-
}
|
|
6751
|
-
if (accessLevel) {
|
|
6752
|
-
qConstraints.push(where12("accessLevel", "==", accessLevel));
|
|
6753
6998
|
}
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
if (startAfterId) {
|
|
6759
|
-
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
6760
|
-
if (startAfterDoc) {
|
|
6761
|
-
}
|
|
6999
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
7000
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
7001
|
+
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
7002
|
+
});
|
|
6762
7003
|
}
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
);
|
|
6767
|
-
try {
|
|
6768
|
-
const querySnapshot = await getDocs12(finalQuery);
|
|
6769
|
-
const mediaList = querySnapshot.docs.map(
|
|
6770
|
-
(doc34) => doc34.data()
|
|
7004
|
+
if (filters.minRating !== void 0) {
|
|
7005
|
+
filteredClinics = filteredClinics.filter(
|
|
7006
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6771
7007
|
);
|
|
6772
|
-
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
6773
|
-
return mediaList;
|
|
6774
|
-
} catch (error) {
|
|
6775
|
-
console.error("[MediaService] Error listing media:", error);
|
|
6776
|
-
throw error;
|
|
6777
7008
|
}
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
*/
|
|
6783
|
-
async getMediaDownloadUrl(mediaId) {
|
|
6784
|
-
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
6785
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6786
|
-
if (metadata && metadata.url) {
|
|
6787
|
-
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
6788
|
-
return metadata.url;
|
|
7009
|
+
if (filters.maxRating !== void 0) {
|
|
7010
|
+
filteredClinics = filteredClinics.filter(
|
|
7011
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
7012
|
+
);
|
|
6789
7013
|
}
|
|
6790
|
-
|
|
6791
|
-
|
|
7014
|
+
lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
7015
|
+
clinicsResult = filteredClinics;
|
|
6792
7016
|
}
|
|
6793
|
-
|
|
7017
|
+
return {
|
|
7018
|
+
clinics: clinicsResult,
|
|
7019
|
+
lastDoc: lastVisibleDoc
|
|
7020
|
+
};
|
|
7021
|
+
}
|
|
6794
7022
|
|
|
6795
7023
|
// src/services/clinic/clinic.service.ts
|
|
6796
7024
|
var ClinicService = class extends BaseService {
|
|
@@ -8522,7 +8750,8 @@ var ProcedureService = class extends BaseService {
|
|
|
8522
8750
|
id: practitionerSnapshot.id,
|
|
8523
8751
|
name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
|
|
8524
8752
|
description: practitioner.basicInfo.bio || "",
|
|
8525
|
-
photo: practitioner.basicInfo.profileImageUrl
|
|
8753
|
+
photo: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : "",
|
|
8754
|
+
// Default to empty string if not a processed URL
|
|
8526
8755
|
rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
|
|
8527
8756
|
services: practitioner.procedures || []
|
|
8528
8757
|
};
|
|
@@ -8656,7 +8885,8 @@ var ProcedureService = class extends BaseService {
|
|
|
8656
8885
|
id: newPractitioner.id,
|
|
8657
8886
|
name: `${newPractitioner.basicInfo.firstName} ${newPractitioner.basicInfo.lastName}`,
|
|
8658
8887
|
description: newPractitioner.basicInfo.bio || "",
|
|
8659
|
-
photo: newPractitioner.basicInfo.profileImageUrl
|
|
8888
|
+
photo: typeof newPractitioner.basicInfo.profileImageUrl === "string" ? newPractitioner.basicInfo.profileImageUrl : "",
|
|
8889
|
+
// Default to empty string if not a processed URL
|
|
8660
8890
|
rating: ((_a = newPractitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
|
|
8661
8891
|
services: newPractitioner.procedures || []
|
|
8662
8892
|
};
|