@blackcode_sa/metaestetics-api 1.7.19 → 1.7.20
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 -1
- package/dist/admin/index.d.ts +1 -1
- 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 +41 -26
- package/dist/index.d.ts +41 -26
- package/dist/index.js +1240 -1177
- package/dist/index.mjs +1229 -1166
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +4 -1
- package/src/services/practitioner/practitioner.service.ts +92 -3
- package/src/services/procedure/procedure.service.ts +8 -2
- package/src/types/practitioner/index.ts +2 -1
- package/src/validations/media.schema.ts +1 -1
- package/src/validations/practitioner.schema.ts +3 -2
package/dist/index.js
CHANGED
|
@@ -1096,7 +1096,7 @@ var BaseService = class {
|
|
|
1096
1096
|
};
|
|
1097
1097
|
|
|
1098
1098
|
// src/services/user.service.ts
|
|
1099
|
-
var
|
|
1099
|
+
var import_firestore19 = require("firebase/firestore");
|
|
1100
1100
|
|
|
1101
1101
|
// src/errors/user.errors.ts
|
|
1102
1102
|
var USER_ERRORS = {
|
|
@@ -3322,7 +3322,7 @@ var doctorInfoSchema = import_zod11.z.object({
|
|
|
3322
3322
|
// src/validations/media.schema.ts
|
|
3323
3323
|
var import_zod12 = require("zod");
|
|
3324
3324
|
var mediaResourceSchema = import_zod12.z.union([
|
|
3325
|
-
import_zod12.z.string(),
|
|
3325
|
+
import_zod12.z.string().url(),
|
|
3326
3326
|
import_zod12.z.instanceof(File),
|
|
3327
3327
|
import_zod12.z.instanceof(Blob)
|
|
3328
3328
|
]);
|
|
@@ -4060,732 +4060,1091 @@ var ClinicAdminService = class extends BaseService {
|
|
|
4060
4060
|
};
|
|
4061
4061
|
|
|
4062
4062
|
// src/services/practitioner/practitioner.service.ts
|
|
4063
|
-
var
|
|
4063
|
+
var import_firestore18 = require("firebase/firestore");
|
|
4064
4064
|
|
|
4065
|
-
// src/
|
|
4066
|
-
var import_zod14 = require("zod");
|
|
4065
|
+
// src/services/media/media.service.ts
|
|
4067
4066
|
var import_firestore15 = require("firebase/firestore");
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
var
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
return CertificationLevel2;
|
|
4080
|
-
})(CertificationLevel || {});
|
|
4081
|
-
var CertificationSpecialty = /* @__PURE__ */ ((CertificationSpecialty3) => {
|
|
4082
|
-
CertificationSpecialty3["LASER"] = "laser";
|
|
4083
|
-
CertificationSpecialty3["INJECTABLES"] = "injectables";
|
|
4084
|
-
CertificationSpecialty3["CHEMICAL_PEELS"] = "chemical_peels";
|
|
4085
|
-
CertificationSpecialty3["MICRODERMABRASION"] = "microdermabrasion";
|
|
4086
|
-
CertificationSpecialty3["BODY_CONTOURING"] = "body_contouring";
|
|
4087
|
-
CertificationSpecialty3["SKIN_CARE"] = "skin_care";
|
|
4088
|
-
CertificationSpecialty3["WOUND_CARE"] = "wound_care";
|
|
4089
|
-
CertificationSpecialty3["ANESTHESIA"] = "anesthesia";
|
|
4090
|
-
return CertificationSpecialty3;
|
|
4091
|
-
})(CertificationSpecialty || {});
|
|
4092
|
-
|
|
4093
|
-
// src/validations/practitioner.schema.ts
|
|
4094
|
-
var practitionerBasicInfoSchema = import_zod14.z.object({
|
|
4095
|
-
firstName: import_zod14.z.string().min(2).max(50),
|
|
4096
|
-
lastName: import_zod14.z.string().min(2).max(50),
|
|
4097
|
-
title: import_zod14.z.string().min(2).max(100),
|
|
4098
|
-
email: import_zod14.z.string().email(),
|
|
4099
|
-
phoneNumber: import_zod14.z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
|
4100
|
-
dateOfBirth: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4101
|
-
gender: import_zod14.z.enum(["male", "female", "other"]),
|
|
4102
|
-
profileImageUrl: import_zod14.z.string().url().optional(),
|
|
4103
|
-
bio: import_zod14.z.string().max(1e3).optional(),
|
|
4104
|
-
languages: import_zod14.z.array(import_zod14.z.string()).min(1)
|
|
4105
|
-
});
|
|
4106
|
-
var practitionerCertificationSchema = import_zod14.z.object({
|
|
4107
|
-
level: import_zod14.z.nativeEnum(CertificationLevel),
|
|
4108
|
-
specialties: import_zod14.z.array(import_zod14.z.nativeEnum(CertificationSpecialty)),
|
|
4109
|
-
licenseNumber: import_zod14.z.string().min(3).max(50),
|
|
4110
|
-
issuingAuthority: import_zod14.z.string().min(2).max(100),
|
|
4111
|
-
issueDate: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4112
|
-
expiryDate: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()).optional(),
|
|
4113
|
-
verificationStatus: import_zod14.z.enum(["pending", "verified", "rejected"])
|
|
4114
|
-
});
|
|
4115
|
-
var timeSlotSchema = import_zod14.z.object({
|
|
4116
|
-
start: import_zod14.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format"),
|
|
4117
|
-
end: import_zod14.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format")
|
|
4118
|
-
}).nullable();
|
|
4119
|
-
var practitionerWorkingHoursSchema = import_zod14.z.object({
|
|
4120
|
-
practitionerId: import_zod14.z.string().min(1),
|
|
4121
|
-
clinicId: import_zod14.z.string().min(1),
|
|
4122
|
-
monday: timeSlotSchema,
|
|
4123
|
-
tuesday: timeSlotSchema,
|
|
4124
|
-
wednesday: timeSlotSchema,
|
|
4125
|
-
thursday: timeSlotSchema,
|
|
4126
|
-
friday: timeSlotSchema,
|
|
4127
|
-
saturday: timeSlotSchema,
|
|
4128
|
-
sunday: timeSlotSchema,
|
|
4129
|
-
createdAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4130
|
-
updatedAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date())
|
|
4131
|
-
});
|
|
4132
|
-
var practitionerClinicWorkingHoursSchema = import_zod14.z.object({
|
|
4133
|
-
clinicId: import_zod14.z.string().min(1),
|
|
4134
|
-
workingHours: import_zod14.z.object({
|
|
4135
|
-
monday: timeSlotSchema,
|
|
4136
|
-
tuesday: timeSlotSchema,
|
|
4137
|
-
wednesday: timeSlotSchema,
|
|
4138
|
-
thursday: timeSlotSchema,
|
|
4139
|
-
friday: timeSlotSchema,
|
|
4140
|
-
saturday: timeSlotSchema,
|
|
4141
|
-
sunday: timeSlotSchema
|
|
4142
|
-
}),
|
|
4143
|
-
isActive: import_zod14.z.boolean(),
|
|
4144
|
-
createdAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4145
|
-
updatedAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date())
|
|
4146
|
-
});
|
|
4147
|
-
var practitionerSchema = import_zod14.z.object({
|
|
4148
|
-
id: import_zod14.z.string().min(1),
|
|
4149
|
-
userRef: import_zod14.z.string().min(1),
|
|
4150
|
-
basicInfo: practitionerBasicInfoSchema,
|
|
4151
|
-
certification: practitionerCertificationSchema,
|
|
4152
|
-
clinics: import_zod14.z.array(import_zod14.z.string()),
|
|
4153
|
-
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema),
|
|
4154
|
-
clinicsInfo: import_zod14.z.array(clinicInfoSchema),
|
|
4155
|
-
procedures: import_zod14.z.array(import_zod14.z.string()),
|
|
4156
|
-
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema),
|
|
4157
|
-
reviewInfo: practitionerReviewInfoSchema,
|
|
4158
|
-
isActive: import_zod14.z.boolean(),
|
|
4159
|
-
isVerified: import_zod14.z.boolean(),
|
|
4160
|
-
status: import_zod14.z.nativeEnum(PractitionerStatus),
|
|
4161
|
-
createdAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4162
|
-
updatedAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date())
|
|
4163
|
-
});
|
|
4164
|
-
var createPractitionerSchema = import_zod14.z.object({
|
|
4165
|
-
userRef: import_zod14.z.string().min(1),
|
|
4166
|
-
basicInfo: practitionerBasicInfoSchema,
|
|
4167
|
-
certification: practitionerCertificationSchema,
|
|
4168
|
-
clinics: import_zod14.z.array(import_zod14.z.string()).optional(),
|
|
4169
|
-
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
4170
|
-
clinicsInfo: import_zod14.z.array(clinicInfoSchema).optional(),
|
|
4171
|
-
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema).optional(),
|
|
4172
|
-
isActive: import_zod14.z.boolean(),
|
|
4173
|
-
isVerified: import_zod14.z.boolean(),
|
|
4174
|
-
status: import_zod14.z.nativeEnum(PractitionerStatus).optional()
|
|
4175
|
-
});
|
|
4176
|
-
var createDraftPractitionerSchema = import_zod14.z.object({
|
|
4177
|
-
basicInfo: practitionerBasicInfoSchema,
|
|
4178
|
-
certification: practitionerCertificationSchema,
|
|
4179
|
-
clinics: import_zod14.z.array(import_zod14.z.string()).optional(),
|
|
4180
|
-
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
4181
|
-
clinicsInfo: import_zod14.z.array(clinicInfoSchema).optional(),
|
|
4182
|
-
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema).optional(),
|
|
4183
|
-
isActive: import_zod14.z.boolean().optional().default(false),
|
|
4184
|
-
isVerified: import_zod14.z.boolean().optional().default(false)
|
|
4185
|
-
});
|
|
4186
|
-
var practitionerTokenSchema = import_zod14.z.object({
|
|
4187
|
-
id: import_zod14.z.string().min(1),
|
|
4188
|
-
token: import_zod14.z.string().min(6),
|
|
4189
|
-
practitionerId: import_zod14.z.string().min(1),
|
|
4190
|
-
email: import_zod14.z.string().email(),
|
|
4191
|
-
clinicId: import_zod14.z.string().min(1),
|
|
4192
|
-
status: import_zod14.z.nativeEnum(PractitionerTokenStatus),
|
|
4193
|
-
createdBy: import_zod14.z.string().min(1),
|
|
4194
|
-
createdAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4195
|
-
expiresAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()),
|
|
4196
|
-
usedBy: import_zod14.z.string().optional(),
|
|
4197
|
-
usedAt: import_zod14.z.instanceof(import_firestore15.Timestamp).or(import_zod14.z.date()).optional()
|
|
4198
|
-
});
|
|
4199
|
-
var createPractitionerTokenSchema = import_zod14.z.object({
|
|
4200
|
-
practitionerId: import_zod14.z.string().min(1),
|
|
4201
|
-
email: import_zod14.z.string().email(),
|
|
4202
|
-
clinicId: import_zod14.z.string().min(1),
|
|
4203
|
-
expiresAt: import_zod14.z.date().optional()
|
|
4204
|
-
});
|
|
4205
|
-
var practitionerSignupSchema = import_zod14.z.object({
|
|
4206
|
-
email: import_zod14.z.string().email(),
|
|
4207
|
-
password: import_zod14.z.string().min(8),
|
|
4208
|
-
firstName: import_zod14.z.string().min(2).max(50).optional(),
|
|
4209
|
-
lastName: import_zod14.z.string().min(2).max(50).optional(),
|
|
4210
|
-
token: import_zod14.z.string().optional(),
|
|
4211
|
-
profileData: import_zod14.z.object({
|
|
4212
|
-
basicInfo: import_zod14.z.object({
|
|
4213
|
-
phoneNumber: import_zod14.z.string().optional(),
|
|
4214
|
-
profileImageUrl: import_zod14.z.string().optional(),
|
|
4215
|
-
gender: import_zod14.z.enum(["male", "female", "other"]).optional(),
|
|
4216
|
-
bio: import_zod14.z.string().optional()
|
|
4217
|
-
}).optional(),
|
|
4218
|
-
certification: import_zod14.z.any().optional()
|
|
4219
|
-
}).optional()
|
|
4220
|
-
});
|
|
4221
|
-
|
|
4222
|
-
// src/services/practitioner/practitioner.service.ts
|
|
4223
|
-
var import_zod15 = require("zod");
|
|
4224
|
-
var import_geofire_common2 = require("geofire-common");
|
|
4225
|
-
var PractitionerService = class extends BaseService {
|
|
4226
|
-
constructor(db, auth, app, clinicService) {
|
|
4067
|
+
var import_storage4 = require("firebase/storage");
|
|
4068
|
+
var import_firestore16 = require("firebase/firestore");
|
|
4069
|
+
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
4070
|
+
MediaAccessLevel2["PUBLIC"] = "public";
|
|
4071
|
+
MediaAccessLevel2["PRIVATE"] = "private";
|
|
4072
|
+
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
4073
|
+
return MediaAccessLevel2;
|
|
4074
|
+
})(MediaAccessLevel || {});
|
|
4075
|
+
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
4076
|
+
var MediaService = class extends BaseService {
|
|
4077
|
+
constructor(db, auth, app) {
|
|
4227
4078
|
super(db, auth, app);
|
|
4228
|
-
this.clinicService = clinicService;
|
|
4229
|
-
}
|
|
4230
|
-
getClinicService() {
|
|
4231
|
-
if (!this.clinicService) {
|
|
4232
|
-
throw new Error("Clinic service not initialized!");
|
|
4233
|
-
}
|
|
4234
|
-
return this.clinicService;
|
|
4235
|
-
}
|
|
4236
|
-
setClinicService(clinicService) {
|
|
4237
|
-
this.clinicService = clinicService;
|
|
4238
4079
|
}
|
|
4239
4080
|
/**
|
|
4240
|
-
*
|
|
4081
|
+
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
4082
|
+
* @param file - The file to upload.
|
|
4083
|
+
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
4084
|
+
* @param accessLevel - Access level (public, private, confidential).
|
|
4085
|
+
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
4086
|
+
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
4087
|
+
* @returns Promise with the media metadata.
|
|
4241
4088
|
*/
|
|
4242
|
-
async
|
|
4089
|
+
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
4090
|
+
const mediaId = this.generateId();
|
|
4091
|
+
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
4092
|
+
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
4093
|
+
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
4094
|
+
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
4095
|
+
const storageRef = (0, import_storage4.ref)(this.storage, filePath);
|
|
4243
4096
|
try {
|
|
4244
|
-
const
|
|
4245
|
-
|
|
4246
|
-
const reviewInfo = {
|
|
4247
|
-
totalReviews: 0,
|
|
4248
|
-
averageRating: 0,
|
|
4249
|
-
knowledgeAndExpertise: 0,
|
|
4250
|
-
communicationSkills: 0,
|
|
4251
|
-
bedSideManner: 0,
|
|
4252
|
-
thoroughness: 0,
|
|
4253
|
-
trustworthiness: 0,
|
|
4254
|
-
recommendationPercentage: 0
|
|
4255
|
-
};
|
|
4256
|
-
const practitioner = {
|
|
4257
|
-
id: practitionerId,
|
|
4258
|
-
userRef: validData.userRef,
|
|
4259
|
-
basicInfo: validData.basicInfo,
|
|
4260
|
-
certification: validData.certification,
|
|
4261
|
-
clinics: validData.clinics || [],
|
|
4262
|
-
clinicWorkingHours: validData.clinicWorkingHours || [],
|
|
4263
|
-
clinicsInfo: [],
|
|
4264
|
-
procedures: [],
|
|
4265
|
-
proceduresInfo: [],
|
|
4266
|
-
reviewInfo,
|
|
4267
|
-
isActive: validData.isActive !== void 0 ? validData.isActive : true,
|
|
4268
|
-
isVerified: validData.isVerified !== void 0 ? validData.isVerified : false,
|
|
4269
|
-
status: validData.status || "active" /* ACTIVE */,
|
|
4270
|
-
createdAt: (0, import_firestore16.serverTimestamp)(),
|
|
4271
|
-
updatedAt: (0, import_firestore16.serverTimestamp)()
|
|
4272
|
-
};
|
|
4273
|
-
practitionerSchema.parse({
|
|
4274
|
-
...practitioner,
|
|
4275
|
-
createdAt: import_firestore16.Timestamp.now(),
|
|
4276
|
-
updatedAt: import_firestore16.Timestamp.now()
|
|
4097
|
+
const uploadResult = await (0, import_storage4.uploadBytes)(storageRef, file, {
|
|
4098
|
+
contentType: file.type
|
|
4277
4099
|
});
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4100
|
+
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
4101
|
+
const downloadURL = await (0, import_storage4.getDownloadURL)(uploadResult.ref);
|
|
4102
|
+
console.log("[MediaService] Got download URL:", downloadURL);
|
|
4103
|
+
const metadata = {
|
|
4104
|
+
id: mediaId,
|
|
4105
|
+
name: fileNameToUse,
|
|
4106
|
+
url: downloadURL,
|
|
4107
|
+
contentType: file.type,
|
|
4108
|
+
size: file.size,
|
|
4109
|
+
createdAt: import_firestore15.Timestamp.now(),
|
|
4110
|
+
accessLevel,
|
|
4111
|
+
ownerId,
|
|
4112
|
+
collectionName,
|
|
4113
|
+
path: filePath
|
|
4114
|
+
};
|
|
4115
|
+
const metadataDocRef = (0, import_firestore16.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4116
|
+
await (0, import_firestore16.setDoc)(metadataDocRef, metadata);
|
|
4117
|
+
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
4118
|
+
return metadata;
|
|
4291
4119
|
} catch (error) {
|
|
4292
|
-
|
|
4293
|
-
throw new Error(`Invalid practitioner data: ${error.message}`);
|
|
4294
|
-
}
|
|
4295
|
-
console.error("Error creating practitioner:", error);
|
|
4120
|
+
console.error("[MediaService] Error during media upload:", error);
|
|
4296
4121
|
throw error;
|
|
4297
4122
|
}
|
|
4298
4123
|
}
|
|
4299
4124
|
/**
|
|
4300
|
-
*
|
|
4301
|
-
*
|
|
4302
|
-
* @
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4125
|
+
* Get media metadata from Firestore by its ID.
|
|
4126
|
+
* @param mediaId - ID of the media.
|
|
4127
|
+
* @returns Promise with the media metadata or null if not found.
|
|
4128
|
+
*/
|
|
4129
|
+
async getMediaMetadata(mediaId) {
|
|
4130
|
+
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
4131
|
+
const docRef = (0, import_firestore16.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4132
|
+
const docSnap = await (0, import_firestore16.getDoc)(docRef);
|
|
4133
|
+
if (docSnap.exists()) {
|
|
4134
|
+
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
4135
|
+
return docSnap.data();
|
|
4136
|
+
}
|
|
4137
|
+
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
4138
|
+
return null;
|
|
4139
|
+
}
|
|
4140
|
+
/**
|
|
4141
|
+
* Get media metadata from Firestore by its public URL.
|
|
4142
|
+
* @param url - The public URL of the media file.
|
|
4143
|
+
* @returns Promise with the media metadata or null if not found.
|
|
4144
|
+
*/
|
|
4145
|
+
async getMediaMetadataByUrl(url) {
|
|
4146
|
+
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
4147
|
+
const q = (0, import_firestore16.query)(
|
|
4148
|
+
(0, import_firestore16.collection)(this.db, MEDIA_METADATA_COLLECTION),
|
|
4149
|
+
(0, import_firestore16.where)("url", "==", url),
|
|
4150
|
+
(0, import_firestore16.limit)(1)
|
|
4151
|
+
);
|
|
4308
4152
|
try {
|
|
4309
|
-
const
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
4315
|
-
if (data.clinics && data.clinics.length > 0) {
|
|
4316
|
-
for (const cId of data.clinics) {
|
|
4317
|
-
if (cId !== clinicId) {
|
|
4318
|
-
const otherClinic = await this.getClinicService().getClinic(cId);
|
|
4319
|
-
if (!otherClinic) {
|
|
4320
|
-
throw new Error(`Clinic ${cId} not found`);
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
clinicsToAdd.add(cId);
|
|
4324
|
-
}
|
|
4325
|
-
}
|
|
4326
|
-
const clinics = Array.from(clinicsToAdd);
|
|
4327
|
-
const defaultReviewInfo = {
|
|
4328
|
-
totalReviews: 0,
|
|
4329
|
-
averageRating: 0,
|
|
4330
|
-
knowledgeAndExpertise: 0,
|
|
4331
|
-
communicationSkills: 0,
|
|
4332
|
-
bedSideManner: 0,
|
|
4333
|
-
thoroughness: 0,
|
|
4334
|
-
trustworthiness: 0,
|
|
4335
|
-
recommendationPercentage: 0
|
|
4336
|
-
};
|
|
4337
|
-
const practitionerId = this.generateId();
|
|
4338
|
-
const clinicsInfo = [];
|
|
4339
|
-
for (const cId of clinics) {
|
|
4340
|
-
const clinicData = await this.getClinicService().getClinic(cId);
|
|
4341
|
-
if (clinicData) {
|
|
4342
|
-
clinicsInfo.push({
|
|
4343
|
-
id: clinicData.id,
|
|
4344
|
-
name: clinicData.name,
|
|
4345
|
-
location: clinicData.location,
|
|
4346
|
-
contactInfo: clinicData.contactInfo,
|
|
4347
|
-
// Make sure we're using the right property for featuredPhoto
|
|
4348
|
-
featuredPhoto: clinicData.featuredPhotos && clinicData.featuredPhotos.length > 0 ? typeof clinicData.featuredPhotos[0] === "string" ? clinicData.featuredPhotos[0] : "" : (typeof clinicData.coverPhoto === "string" ? clinicData.coverPhoto : "") || "",
|
|
4349
|
-
description: clinicData.description || null
|
|
4350
|
-
});
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
const finalClinicsInfo = validatedData.clinicsInfo && validatedData.clinicsInfo.length > 0 ? validatedData.clinicsInfo : clinicsInfo;
|
|
4354
|
-
const proceduresInfo = [];
|
|
4355
|
-
const practitionerData = {
|
|
4356
|
-
id: practitionerId,
|
|
4357
|
-
userRef: "",
|
|
4358
|
-
// Prazno - biće popunjeno kada korisnik kreira nalog
|
|
4359
|
-
basicInfo: validatedData.basicInfo,
|
|
4360
|
-
certification: validatedData.certification,
|
|
4361
|
-
clinics,
|
|
4362
|
-
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
4363
|
-
clinicsInfo: finalClinicsInfo,
|
|
4364
|
-
procedures: [],
|
|
4365
|
-
proceduresInfo,
|
|
4366
|
-
reviewInfo: defaultReviewInfo,
|
|
4367
|
-
isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
|
|
4368
|
-
isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
|
|
4369
|
-
status: "draft" /* DRAFT */,
|
|
4370
|
-
createdAt: (0, import_firestore16.serverTimestamp)(),
|
|
4371
|
-
updatedAt: (0, import_firestore16.serverTimestamp)()
|
|
4372
|
-
};
|
|
4373
|
-
practitionerSchema.parse({
|
|
4374
|
-
...practitionerData,
|
|
4375
|
-
userRef: "temp-for-validation",
|
|
4376
|
-
createdAt: import_firestore16.Timestamp.now(),
|
|
4377
|
-
updatedAt: import_firestore16.Timestamp.now()
|
|
4378
|
-
});
|
|
4379
|
-
await (0, import_firestore16.setDoc)(
|
|
4380
|
-
(0, import_firestore16.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
|
|
4381
|
-
practitionerData
|
|
4382
|
-
);
|
|
4383
|
-
const savedPractitioner = await this.getPractitioner(practitionerData.id);
|
|
4384
|
-
if (!savedPractitioner) {
|
|
4385
|
-
throw new Error("Failed to create draft practitioner profile");
|
|
4153
|
+
const querySnapshot = await (0, import_firestore16.getDocs)(q);
|
|
4154
|
+
if (!querySnapshot.empty) {
|
|
4155
|
+
const metadata = querySnapshot.docs[0].data();
|
|
4156
|
+
console.log("[MediaService] Metadata found by URL:", metadata);
|
|
4157
|
+
return metadata;
|
|
4386
4158
|
}
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
const token = {
|
|
4390
|
-
id: this.generateId(),
|
|
4391
|
-
token: tokenString,
|
|
4392
|
-
practitionerId,
|
|
4393
|
-
email: practitionerData.basicInfo.email,
|
|
4394
|
-
clinicId,
|
|
4395
|
-
status: "active" /* ACTIVE */,
|
|
4396
|
-
createdBy,
|
|
4397
|
-
createdAt: import_firestore16.Timestamp.now(),
|
|
4398
|
-
expiresAt: import_firestore16.Timestamp.fromDate(expiration)
|
|
4399
|
-
};
|
|
4400
|
-
practitionerTokenSchema.parse(token);
|
|
4401
|
-
const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4402
|
-
await (0, import_firestore16.setDoc)((0, import_firestore16.doc)(this.db, tokenPath), token);
|
|
4403
|
-
return { practitioner: savedPractitioner, token };
|
|
4159
|
+
console.log("[MediaService] No metadata found for URL:", url);
|
|
4160
|
+
return null;
|
|
4404
4161
|
} catch (error) {
|
|
4405
|
-
|
|
4406
|
-
throw new Error("Invalid practitioner data: " + error.message);
|
|
4407
|
-
}
|
|
4162
|
+
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
4408
4163
|
throw error;
|
|
4409
4164
|
}
|
|
4410
4165
|
}
|
|
4411
4166
|
/**
|
|
4412
|
-
*
|
|
4413
|
-
* @param
|
|
4414
|
-
* @param createdBy ID of the user creating the token
|
|
4415
|
-
* @returns Created token
|
|
4167
|
+
* Delete media from storage and remove metadata from Firestore.
|
|
4168
|
+
* @param mediaId - ID of the media to delete.
|
|
4416
4169
|
*/
|
|
4417
|
-
async
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4170
|
+
async deleteMedia(mediaId) {
|
|
4171
|
+
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
4172
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
4173
|
+
if (!metadata) {
|
|
4174
|
+
console.warn(
|
|
4175
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
4422
4176
|
);
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
const storageFileRef = (0, import_storage4.ref)(this.storage, metadata.path);
|
|
4180
|
+
try {
|
|
4181
|
+
await (0, import_storage4.deleteObject)(storageFileRef);
|
|
4182
|
+
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
4183
|
+
const metadataDocRef = (0, import_firestore16.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4184
|
+
await (0, import_firestore16.deleteDoc)(metadataDocRef);
|
|
4185
|
+
console.log(
|
|
4186
|
+
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
4433
4187
|
);
|
|
4434
|
-
if (!clinic) {
|
|
4435
|
-
throw new Error(`Clinic ${validatedData.clinicId} not found`);
|
|
4436
|
-
}
|
|
4437
|
-
if (!practitioner.clinics.includes(validatedData.clinicId)) {
|
|
4438
|
-
throw new Error("Practitioner is not associated with this clinic");
|
|
4439
|
-
}
|
|
4440
|
-
const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
4441
|
-
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
4442
|
-
const token = {
|
|
4443
|
-
id: this.generateId(),
|
|
4444
|
-
token: tokenString,
|
|
4445
|
-
practitionerId: validatedData.practitionerId,
|
|
4446
|
-
email: validatedData.email,
|
|
4447
|
-
clinicId: validatedData.clinicId,
|
|
4448
|
-
status: "active" /* ACTIVE */,
|
|
4449
|
-
createdBy,
|
|
4450
|
-
createdAt: import_firestore16.Timestamp.now(),
|
|
4451
|
-
expiresAt: import_firestore16.Timestamp.fromDate(expiration)
|
|
4452
|
-
};
|
|
4453
|
-
practitionerTokenSchema.parse(token);
|
|
4454
|
-
const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4455
|
-
await (0, import_firestore16.setDoc)((0, import_firestore16.doc)(this.db, tokenPath), token);
|
|
4456
|
-
return token;
|
|
4457
4188
|
} catch (error) {
|
|
4458
|
-
|
|
4459
|
-
throw new Error("Invalid token data: " + error.message);
|
|
4460
|
-
}
|
|
4189
|
+
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
4461
4190
|
throw error;
|
|
4462
4191
|
}
|
|
4463
4192
|
}
|
|
4464
4193
|
/**
|
|
4465
|
-
*
|
|
4466
|
-
*
|
|
4467
|
-
* @
|
|
4194
|
+
* Update media access level. This involves moving the file in Firebase Storage
|
|
4195
|
+
* to a new path reflecting the new access level, and updating its metadata.
|
|
4196
|
+
* @param mediaId - ID of the media to update.
|
|
4197
|
+
* @param newAccessLevel - New access level.
|
|
4198
|
+
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
4468
4199
|
*/
|
|
4469
|
-
async
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
);
|
|
4474
|
-
const q = (0, import_firestore16.query)(
|
|
4475
|
-
tokensRef,
|
|
4476
|
-
(0, import_firestore16.where)("status", "==", "active" /* ACTIVE */),
|
|
4477
|
-
(0, import_firestore16.where)("expiresAt", ">", import_firestore16.Timestamp.now())
|
|
4200
|
+
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
4201
|
+
var _a;
|
|
4202
|
+
console.log(
|
|
4203
|
+
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
4478
4204
|
);
|
|
4479
|
-
const
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
* Gets a token by its string value and validates it
|
|
4484
|
-
* @param tokenString The token string to find
|
|
4485
|
-
* @returns The token if found and valid, null otherwise
|
|
4486
|
-
*/
|
|
4487
|
-
async validateToken(tokenString) {
|
|
4488
|
-
const practitionersRef = (0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION);
|
|
4489
|
-
const practitionersSnapshot = await (0, import_firestore16.getDocs)(practitionersRef);
|
|
4490
|
-
for (const practitionerDoc of practitionersSnapshot.docs) {
|
|
4491
|
-
const practitionerId = practitionerDoc.id;
|
|
4492
|
-
const tokensRef = (0, import_firestore16.collection)(
|
|
4493
|
-
this.db,
|
|
4494
|
-
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
4205
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
4206
|
+
if (!metadata) {
|
|
4207
|
+
console.warn(
|
|
4208
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
4495
4209
|
);
|
|
4210
|
+
return null;
|
|
4211
|
+
}
|
|
4212
|
+
if (metadata.accessLevel === newAccessLevel) {
|
|
4496
4213
|
console.log(
|
|
4497
|
-
`[
|
|
4498
|
-
{
|
|
4499
|
-
tokenString,
|
|
4500
|
-
timestamp: import_firestore16.Timestamp.now().toDate()
|
|
4501
|
-
}
|
|
4502
|
-
);
|
|
4503
|
-
const q = (0, import_firestore16.query)(
|
|
4504
|
-
tokensRef,
|
|
4505
|
-
(0, import_firestore16.where)("token", "==", tokenString),
|
|
4506
|
-
(0, import_firestore16.where)("status", "==", "active" /* ACTIVE */),
|
|
4507
|
-
(0, import_firestore16.where)("expiresAt", ">", import_firestore16.Timestamp.now())
|
|
4214
|
+
`[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
|
|
4508
4215
|
);
|
|
4216
|
+
const metadataDocRef = (0, import_firestore16.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4509
4217
|
try {
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
`[PRACTITIONER] Token query results for practitioner ${practitionerId}`,
|
|
4513
|
-
{
|
|
4514
|
-
found: !tokenSnapshot.empty,
|
|
4515
|
-
count: tokenSnapshot.size
|
|
4516
|
-
}
|
|
4517
|
-
);
|
|
4518
|
-
if (!tokenSnapshot.empty) {
|
|
4519
|
-
const tokenData = tokenSnapshot.docs[0].data();
|
|
4520
|
-
console.log(`[PRACTITIONER] Valid token found`, {
|
|
4521
|
-
tokenId: tokenData.id,
|
|
4522
|
-
expiresAt: tokenData.expiresAt.toDate()
|
|
4523
|
-
});
|
|
4524
|
-
return tokenData;
|
|
4525
|
-
}
|
|
4218
|
+
await (0, import_firestore16.updateDoc)(metadataDocRef, { updatedAt: import_firestore15.Timestamp.now() });
|
|
4219
|
+
return { ...metadata, updatedAt: import_firestore15.Timestamp.now() };
|
|
4526
4220
|
} catch (error) {
|
|
4527
4221
|
console.error(
|
|
4528
|
-
`[
|
|
4222
|
+
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
4529
4223
|
error
|
|
4530
4224
|
);
|
|
4531
4225
|
throw error;
|
|
4532
4226
|
}
|
|
4533
4227
|
}
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
* @param practitionerId ID of the practitioner
|
|
4540
|
-
* @param userId ID of the user using the token
|
|
4541
|
-
*/
|
|
4542
|
-
async markTokenAsUsed(tokenId, practitionerId, userId) {
|
|
4543
|
-
const tokenRef = (0, import_firestore16.doc)(
|
|
4544
|
-
this.db,
|
|
4545
|
-
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
|
|
4228
|
+
const oldStoragePath = metadata.path;
|
|
4229
|
+
const fileNamePart = `${metadata.id}-${metadata.name}`;
|
|
4230
|
+
const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
|
|
4231
|
+
console.log(
|
|
4232
|
+
`[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
|
|
4546
4233
|
);
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
usedBy: userId,
|
|
4550
|
-
usedAt: import_firestore16.Timestamp.now()
|
|
4551
|
-
});
|
|
4552
|
-
}
|
|
4553
|
-
/**
|
|
4554
|
-
* Dohvata zdravstvenog radnika po ID-u
|
|
4555
|
-
*/
|
|
4556
|
-
async getPractitioner(practitionerId) {
|
|
4557
|
-
const practitionerDoc = await (0, import_firestore16.getDoc)(
|
|
4558
|
-
(0, import_firestore16.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId)
|
|
4559
|
-
);
|
|
4560
|
-
if (!practitionerDoc.exists()) {
|
|
4561
|
-
return null;
|
|
4562
|
-
}
|
|
4563
|
-
return practitionerDoc.data();
|
|
4564
|
-
}
|
|
4565
|
-
/**
|
|
4566
|
-
* Dohvata zdravstvenog radnika po User ID-u
|
|
4567
|
-
*/
|
|
4568
|
-
async getPractitionerByUserRef(userRef) {
|
|
4569
|
-
const q = (0, import_firestore16.query)(
|
|
4570
|
-
(0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4571
|
-
(0, import_firestore16.where)("userRef", "==", userRef)
|
|
4572
|
-
);
|
|
4573
|
-
const querySnapshot = await (0, import_firestore16.getDocs)(q);
|
|
4574
|
-
if (querySnapshot.empty) {
|
|
4575
|
-
return null;
|
|
4576
|
-
}
|
|
4577
|
-
return querySnapshot.docs[0].data();
|
|
4578
|
-
}
|
|
4579
|
-
/**
|
|
4580
|
-
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
4581
|
-
*/
|
|
4582
|
-
async getPractitionersByClinic(clinicId) {
|
|
4583
|
-
const q = (0, import_firestore16.query)(
|
|
4584
|
-
(0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4585
|
-
(0, import_firestore16.where)("clinics", "array-contains", clinicId),
|
|
4586
|
-
(0, import_firestore16.where)("isActive", "==", true),
|
|
4587
|
-
(0, import_firestore16.where)("status", "==", "active" /* ACTIVE */)
|
|
4588
|
-
);
|
|
4589
|
-
const querySnapshot = await (0, import_firestore16.getDocs)(q);
|
|
4590
|
-
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4591
|
-
}
|
|
4592
|
-
/**
|
|
4593
|
-
* Dohvata sve zdravstvene radnike za određenu kliniku
|
|
4594
|
-
*/
|
|
4595
|
-
async getAllPractitionersByClinic(clinicId) {
|
|
4596
|
-
const q = (0, import_firestore16.query)(
|
|
4597
|
-
(0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4598
|
-
(0, import_firestore16.where)("clinics", "array-contains", clinicId),
|
|
4599
|
-
(0, import_firestore16.where)("isActive", "==", true)
|
|
4600
|
-
);
|
|
4601
|
-
const querySnapshot = await (0, import_firestore16.getDocs)(q);
|
|
4602
|
-
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4603
|
-
}
|
|
4604
|
-
/**
|
|
4605
|
-
* Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
|
|
4606
|
-
*/
|
|
4607
|
-
async getDraftPractitionersByClinic(clinicId) {
|
|
4608
|
-
const q = (0, import_firestore16.query)(
|
|
4609
|
-
(0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4610
|
-
(0, import_firestore16.where)("clinics", "array-contains", clinicId),
|
|
4611
|
-
(0, import_firestore16.where)("status", "==", "draft" /* DRAFT */)
|
|
4612
|
-
);
|
|
4613
|
-
const querySnapshot = await (0, import_firestore16.getDocs)(q);
|
|
4614
|
-
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4615
|
-
}
|
|
4616
|
-
/**
|
|
4617
|
-
* Updates a practitioner
|
|
4618
|
-
*/
|
|
4619
|
-
async updatePractitioner(practitionerId, data) {
|
|
4234
|
+
const oldStorageFileRef = (0, import_storage4.ref)(this.storage, oldStoragePath);
|
|
4235
|
+
const newStorageFileRef = (0, import_storage4.ref)(this.storage, newStoragePath);
|
|
4620
4236
|
try {
|
|
4621
|
-
|
|
4622
|
-
const
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4237
|
+
console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
|
|
4238
|
+
const fileBytes = await (0, import_storage4.getBytes)(oldStorageFileRef);
|
|
4239
|
+
console.log(
|
|
4240
|
+
`[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
|
|
4241
|
+
);
|
|
4242
|
+
console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
|
|
4243
|
+
await (0, import_storage4.uploadBytes)(newStorageFileRef, fileBytes, {
|
|
4244
|
+
contentType: metadata.contentType
|
|
4245
|
+
});
|
|
4246
|
+
console.log(
|
|
4247
|
+
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
4248
|
+
);
|
|
4249
|
+
const newDownloadURL = await (0, import_storage4.getDownloadURL)(newStorageFileRef);
|
|
4250
|
+
console.log(
|
|
4251
|
+
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
4626
4252
|
);
|
|
4627
|
-
const practitionerDoc = await (0, import_firestore16.getDoc)(practitionerRef);
|
|
4628
|
-
if (!practitionerDoc.exists()) {
|
|
4629
|
-
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4630
|
-
}
|
|
4631
|
-
const currentPractitioner = practitionerDoc.data();
|
|
4632
4253
|
const updateData = {
|
|
4633
|
-
|
|
4634
|
-
|
|
4254
|
+
accessLevel: newAccessLevel,
|
|
4255
|
+
path: newStoragePath,
|
|
4256
|
+
url: newDownloadURL,
|
|
4257
|
+
updatedAt: import_firestore15.Timestamp.now()
|
|
4635
4258
|
};
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
`Failed to retrieve updated practitioner ${practitionerId}`
|
|
4641
|
-
);
|
|
4642
|
-
}
|
|
4643
|
-
return updatedPractitioner;
|
|
4644
|
-
} catch (error) {
|
|
4645
|
-
if (error instanceof import_zod15.z.ZodError) {
|
|
4646
|
-
throw new Error(`Invalid practitioner update data: ${error.message}`);
|
|
4647
|
-
}
|
|
4648
|
-
console.error(`Error updating practitioner ${practitionerId}:`, error);
|
|
4649
|
-
throw error;
|
|
4650
|
-
}
|
|
4651
|
-
}
|
|
4652
|
-
/**
|
|
4653
|
-
* Adds a clinic to a practitioner
|
|
4654
|
-
*/
|
|
4655
|
-
async addClinic(practitionerId, clinicId) {
|
|
4656
|
-
var _a;
|
|
4657
|
-
try {
|
|
4658
|
-
const practitionerRef = (0, import_firestore16.doc)(
|
|
4659
|
-
this.db,
|
|
4660
|
-
PRACTITIONERS_COLLECTION,
|
|
4661
|
-
practitionerId
|
|
4259
|
+
const metadataDocRef = (0, import_firestore16.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
4260
|
+
console.log(
|
|
4261
|
+
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
4262
|
+
updateData
|
|
4662
4263
|
);
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4264
|
+
await (0, import_firestore16.updateDoc)(metadataDocRef, updateData);
|
|
4265
|
+
console.log(
|
|
4266
|
+
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
4267
|
+
);
|
|
4268
|
+
try {
|
|
4269
|
+
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
4270
|
+
await (0, import_storage4.deleteObject)(oldStorageFileRef);
|
|
4669
4271
|
console.log(
|
|
4670
|
-
`
|
|
4272
|
+
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
4273
|
+
);
|
|
4274
|
+
} catch (deleteError) {
|
|
4275
|
+
console.error(
|
|
4276
|
+
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
4277
|
+
deleteError
|
|
4671
4278
|
);
|
|
4672
|
-
return;
|
|
4673
4279
|
}
|
|
4674
|
-
|
|
4675
|
-
clinics: (0, import_firestore16.arrayUnion)(clinicId),
|
|
4676
|
-
updatedAt: (0, import_firestore16.serverTimestamp)()
|
|
4677
|
-
});
|
|
4280
|
+
return { ...metadata, ...updateData };
|
|
4678
4281
|
} catch (error) {
|
|
4679
4282
|
console.error(
|
|
4680
|
-
`Error
|
|
4283
|
+
`[MediaService] Error updating media access level and moving file for ${mediaId}:`,
|
|
4681
4284
|
error
|
|
4682
4285
|
);
|
|
4286
|
+
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
4287
|
+
console.warn(
|
|
4288
|
+
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
4289
|
+
);
|
|
4290
|
+
try {
|
|
4291
|
+
await (0, import_storage4.deleteObject)(newStorageFileRef);
|
|
4292
|
+
console.warn(
|
|
4293
|
+
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
4294
|
+
);
|
|
4295
|
+
} catch (cleanupError) {
|
|
4296
|
+
console.error(
|
|
4297
|
+
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
4298
|
+
cleanupError
|
|
4299
|
+
);
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4683
4302
|
throw error;
|
|
4684
4303
|
}
|
|
4685
4304
|
}
|
|
4686
4305
|
/**
|
|
4687
|
-
*
|
|
4306
|
+
* List all media for an owner, optionally filtered by collection and access level.
|
|
4307
|
+
* @param ownerId - ID of the owner.
|
|
4308
|
+
* @param collectionName - Optional: Filter by collection name.
|
|
4309
|
+
* @param accessLevel - Optional: Filter by access level.
|
|
4310
|
+
* @param count - Optional: Number of items to fetch.
|
|
4311
|
+
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
4688
4312
|
*/
|
|
4689
|
-
async
|
|
4313
|
+
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
4314
|
+
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
4315
|
+
let qConstraints = [(0, import_firestore16.where)("ownerId", "==", ownerId)];
|
|
4316
|
+
if (collectionName) {
|
|
4317
|
+
qConstraints.push((0, import_firestore16.where)("collectionName", "==", collectionName));
|
|
4318
|
+
}
|
|
4319
|
+
if (accessLevel) {
|
|
4320
|
+
qConstraints.push((0, import_firestore16.where)("accessLevel", "==", accessLevel));
|
|
4321
|
+
}
|
|
4322
|
+
qConstraints.push((0, import_firestore16.orderBy)("createdAt", "desc"));
|
|
4323
|
+
if (count) {
|
|
4324
|
+
qConstraints.push((0, import_firestore16.limit)(count));
|
|
4325
|
+
}
|
|
4326
|
+
if (startAfterId) {
|
|
4327
|
+
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
4328
|
+
if (startAfterDoc) {
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
const finalQuery = (0, import_firestore16.query)(
|
|
4332
|
+
(0, import_firestore16.collection)(this.db, MEDIA_METADATA_COLLECTION),
|
|
4333
|
+
...qConstraints
|
|
4334
|
+
);
|
|
4690
4335
|
try {
|
|
4691
|
-
const
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
practitionerId
|
|
4336
|
+
const querySnapshot = await (0, import_firestore16.getDocs)(finalQuery);
|
|
4337
|
+
const mediaList = querySnapshot.docs.map(
|
|
4338
|
+
(doc34) => doc34.data()
|
|
4695
4339
|
);
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4699
|
-
}
|
|
4700
|
-
await (0, import_firestore16.updateDoc)(practitionerRef, {
|
|
4701
|
-
clinics: (0, import_firestore16.arrayRemove)(clinicId),
|
|
4702
|
-
updatedAt: (0, import_firestore16.serverTimestamp)()
|
|
4703
|
-
});
|
|
4340
|
+
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
4341
|
+
return mediaList;
|
|
4704
4342
|
} catch (error) {
|
|
4705
|
-
console.error(
|
|
4706
|
-
`Error removing clinic ${clinicId} from practitioner ${practitionerId}:`,
|
|
4707
|
-
error
|
|
4708
|
-
);
|
|
4343
|
+
console.error("[MediaService] Error listing media:", error);
|
|
4709
4344
|
throw error;
|
|
4710
4345
|
}
|
|
4711
4346
|
}
|
|
4712
4347
|
/**
|
|
4713
|
-
*
|
|
4714
|
-
|
|
4715
|
-
async deactivatePractitioner(practitionerId) {
|
|
4716
|
-
await this.updatePractitioner(practitionerId, {
|
|
4717
|
-
isActive: false
|
|
4718
|
-
});
|
|
4719
|
-
}
|
|
4720
|
-
/**
|
|
4721
|
-
* Aktivira profil zdravstvenog radnika
|
|
4722
|
-
*/
|
|
4723
|
-
async activatePractitioner(practitionerId) {
|
|
4724
|
-
await this.updatePractitioner(practitionerId, {
|
|
4725
|
-
isActive: true
|
|
4726
|
-
});
|
|
4727
|
-
}
|
|
4728
|
-
/**
|
|
4729
|
-
* Briše profil zdravstvenog radnika
|
|
4348
|
+
* Get download URL for media. (Convenience, as URL is in metadata)
|
|
4349
|
+
* @param mediaId - ID of the media.
|
|
4730
4350
|
*/
|
|
4731
|
-
async
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4351
|
+
async getMediaDownloadUrl(mediaId) {
|
|
4352
|
+
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
4353
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
4354
|
+
if (metadata && metadata.url) {
|
|
4355
|
+
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
4356
|
+
return metadata.url;
|
|
4735
4357
|
}
|
|
4736
|
-
|
|
4358
|
+
console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
|
|
4359
|
+
return null;
|
|
4737
4360
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4361
|
+
};
|
|
4362
|
+
|
|
4363
|
+
// src/validations/practitioner.schema.ts
|
|
4364
|
+
var import_zod14 = require("zod");
|
|
4365
|
+
var import_firestore17 = require("firebase/firestore");
|
|
4366
|
+
|
|
4367
|
+
// src/backoffice/types/static/certification.types.ts
|
|
4368
|
+
var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
|
|
4369
|
+
CertificationLevel2["AESTHETICIAN"] = "aesthetician";
|
|
4370
|
+
CertificationLevel2["NURSE_ASSISTANT"] = "nurse_assistant";
|
|
4371
|
+
CertificationLevel2["NURSE"] = "nurse";
|
|
4372
|
+
CertificationLevel2["NURSE_PRACTITIONER"] = "nurse_practitioner";
|
|
4373
|
+
CertificationLevel2["PHYSICIAN_ASSISTANT"] = "physician_assistant";
|
|
4374
|
+
CertificationLevel2["DOCTOR"] = "doctor";
|
|
4375
|
+
CertificationLevel2["SPECIALIST"] = "specialist";
|
|
4376
|
+
CertificationLevel2["PLASTIC_SURGEON"] = "plastic_surgeon";
|
|
4377
|
+
return CertificationLevel2;
|
|
4378
|
+
})(CertificationLevel || {});
|
|
4379
|
+
var CertificationSpecialty = /* @__PURE__ */ ((CertificationSpecialty3) => {
|
|
4380
|
+
CertificationSpecialty3["LASER"] = "laser";
|
|
4381
|
+
CertificationSpecialty3["INJECTABLES"] = "injectables";
|
|
4382
|
+
CertificationSpecialty3["CHEMICAL_PEELS"] = "chemical_peels";
|
|
4383
|
+
CertificationSpecialty3["MICRODERMABRASION"] = "microdermabrasion";
|
|
4384
|
+
CertificationSpecialty3["BODY_CONTOURING"] = "body_contouring";
|
|
4385
|
+
CertificationSpecialty3["SKIN_CARE"] = "skin_care";
|
|
4386
|
+
CertificationSpecialty3["WOUND_CARE"] = "wound_care";
|
|
4387
|
+
CertificationSpecialty3["ANESTHESIA"] = "anesthesia";
|
|
4388
|
+
return CertificationSpecialty3;
|
|
4389
|
+
})(CertificationSpecialty || {});
|
|
4390
|
+
|
|
4391
|
+
// src/validations/practitioner.schema.ts
|
|
4392
|
+
var practitionerBasicInfoSchema = import_zod14.z.object({
|
|
4393
|
+
firstName: import_zod14.z.string().min(2).max(50),
|
|
4394
|
+
lastName: import_zod14.z.string().min(2).max(50),
|
|
4395
|
+
title: import_zod14.z.string().min(2).max(100),
|
|
4396
|
+
email: import_zod14.z.string().email(),
|
|
4397
|
+
phoneNumber: import_zod14.z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
|
|
4398
|
+
dateOfBirth: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4399
|
+
gender: import_zod14.z.enum(["male", "female", "other"]),
|
|
4400
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
4401
|
+
bio: import_zod14.z.string().max(1e3).optional(),
|
|
4402
|
+
languages: import_zod14.z.array(import_zod14.z.string()).min(1)
|
|
4403
|
+
});
|
|
4404
|
+
var practitionerCertificationSchema = import_zod14.z.object({
|
|
4405
|
+
level: import_zod14.z.nativeEnum(CertificationLevel),
|
|
4406
|
+
specialties: import_zod14.z.array(import_zod14.z.nativeEnum(CertificationSpecialty)),
|
|
4407
|
+
licenseNumber: import_zod14.z.string().min(3).max(50),
|
|
4408
|
+
issuingAuthority: import_zod14.z.string().min(2).max(100),
|
|
4409
|
+
issueDate: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4410
|
+
expiryDate: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()).optional(),
|
|
4411
|
+
verificationStatus: import_zod14.z.enum(["pending", "verified", "rejected"])
|
|
4412
|
+
});
|
|
4413
|
+
var timeSlotSchema = import_zod14.z.object({
|
|
4414
|
+
start: import_zod14.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format"),
|
|
4415
|
+
end: import_zod14.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format")
|
|
4416
|
+
}).nullable();
|
|
4417
|
+
var practitionerWorkingHoursSchema = import_zod14.z.object({
|
|
4418
|
+
practitionerId: import_zod14.z.string().min(1),
|
|
4419
|
+
clinicId: import_zod14.z.string().min(1),
|
|
4420
|
+
monday: timeSlotSchema,
|
|
4421
|
+
tuesday: timeSlotSchema,
|
|
4422
|
+
wednesday: timeSlotSchema,
|
|
4423
|
+
thursday: timeSlotSchema,
|
|
4424
|
+
friday: timeSlotSchema,
|
|
4425
|
+
saturday: timeSlotSchema,
|
|
4426
|
+
sunday: timeSlotSchema,
|
|
4427
|
+
createdAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4428
|
+
updatedAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date())
|
|
4429
|
+
});
|
|
4430
|
+
var practitionerClinicWorkingHoursSchema = import_zod14.z.object({
|
|
4431
|
+
clinicId: import_zod14.z.string().min(1),
|
|
4432
|
+
workingHours: import_zod14.z.object({
|
|
4433
|
+
monday: timeSlotSchema,
|
|
4434
|
+
tuesday: timeSlotSchema,
|
|
4435
|
+
wednesday: timeSlotSchema,
|
|
4436
|
+
thursday: timeSlotSchema,
|
|
4437
|
+
friday: timeSlotSchema,
|
|
4438
|
+
saturday: timeSlotSchema,
|
|
4439
|
+
sunday: timeSlotSchema
|
|
4440
|
+
}),
|
|
4441
|
+
isActive: import_zod14.z.boolean(),
|
|
4442
|
+
createdAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4443
|
+
updatedAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date())
|
|
4444
|
+
});
|
|
4445
|
+
var practitionerSchema = import_zod14.z.object({
|
|
4446
|
+
id: import_zod14.z.string().min(1),
|
|
4447
|
+
userRef: import_zod14.z.string().min(1),
|
|
4448
|
+
basicInfo: practitionerBasicInfoSchema,
|
|
4449
|
+
certification: practitionerCertificationSchema,
|
|
4450
|
+
clinics: import_zod14.z.array(import_zod14.z.string()),
|
|
4451
|
+
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema),
|
|
4452
|
+
clinicsInfo: import_zod14.z.array(clinicInfoSchema),
|
|
4453
|
+
procedures: import_zod14.z.array(import_zod14.z.string()),
|
|
4454
|
+
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema),
|
|
4455
|
+
reviewInfo: practitionerReviewInfoSchema,
|
|
4456
|
+
isActive: import_zod14.z.boolean(),
|
|
4457
|
+
isVerified: import_zod14.z.boolean(),
|
|
4458
|
+
status: import_zod14.z.nativeEnum(PractitionerStatus),
|
|
4459
|
+
createdAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4460
|
+
updatedAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date())
|
|
4461
|
+
});
|
|
4462
|
+
var createPractitionerSchema = import_zod14.z.object({
|
|
4463
|
+
userRef: import_zod14.z.string().min(1),
|
|
4464
|
+
basicInfo: practitionerBasicInfoSchema,
|
|
4465
|
+
certification: practitionerCertificationSchema,
|
|
4466
|
+
clinics: import_zod14.z.array(import_zod14.z.string()).optional(),
|
|
4467
|
+
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
4468
|
+
clinicsInfo: import_zod14.z.array(clinicInfoSchema).optional(),
|
|
4469
|
+
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema).optional(),
|
|
4470
|
+
isActive: import_zod14.z.boolean(),
|
|
4471
|
+
isVerified: import_zod14.z.boolean(),
|
|
4472
|
+
status: import_zod14.z.nativeEnum(PractitionerStatus).optional()
|
|
4473
|
+
});
|
|
4474
|
+
var createDraftPractitionerSchema = import_zod14.z.object({
|
|
4475
|
+
basicInfo: practitionerBasicInfoSchema,
|
|
4476
|
+
certification: practitionerCertificationSchema,
|
|
4477
|
+
clinics: import_zod14.z.array(import_zod14.z.string()).optional(),
|
|
4478
|
+
clinicWorkingHours: import_zod14.z.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
4479
|
+
clinicsInfo: import_zod14.z.array(clinicInfoSchema).optional(),
|
|
4480
|
+
proceduresInfo: import_zod14.z.array(procedureSummaryInfoSchema).optional(),
|
|
4481
|
+
isActive: import_zod14.z.boolean().optional().default(false),
|
|
4482
|
+
isVerified: import_zod14.z.boolean().optional().default(false)
|
|
4483
|
+
});
|
|
4484
|
+
var practitionerTokenSchema = import_zod14.z.object({
|
|
4485
|
+
id: import_zod14.z.string().min(1),
|
|
4486
|
+
token: import_zod14.z.string().min(6),
|
|
4487
|
+
practitionerId: import_zod14.z.string().min(1),
|
|
4488
|
+
email: import_zod14.z.string().email(),
|
|
4489
|
+
clinicId: import_zod14.z.string().min(1),
|
|
4490
|
+
status: import_zod14.z.nativeEnum(PractitionerTokenStatus),
|
|
4491
|
+
createdBy: import_zod14.z.string().min(1),
|
|
4492
|
+
createdAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4493
|
+
expiresAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()),
|
|
4494
|
+
usedBy: import_zod14.z.string().optional(),
|
|
4495
|
+
usedAt: import_zod14.z.instanceof(import_firestore17.Timestamp).or(import_zod14.z.date()).optional()
|
|
4496
|
+
});
|
|
4497
|
+
var createPractitionerTokenSchema = import_zod14.z.object({
|
|
4498
|
+
practitionerId: import_zod14.z.string().min(1),
|
|
4499
|
+
email: import_zod14.z.string().email(),
|
|
4500
|
+
clinicId: import_zod14.z.string().min(1),
|
|
4501
|
+
expiresAt: import_zod14.z.date().optional()
|
|
4502
|
+
});
|
|
4503
|
+
var practitionerSignupSchema = import_zod14.z.object({
|
|
4504
|
+
email: import_zod14.z.string().email(),
|
|
4505
|
+
password: import_zod14.z.string().min(8),
|
|
4506
|
+
firstName: import_zod14.z.string().min(2).max(50).optional(),
|
|
4507
|
+
lastName: import_zod14.z.string().min(2).max(50).optional(),
|
|
4508
|
+
token: import_zod14.z.string().optional(),
|
|
4509
|
+
profileData: import_zod14.z.object({
|
|
4510
|
+
basicInfo: import_zod14.z.object({
|
|
4511
|
+
phoneNumber: import_zod14.z.string().optional(),
|
|
4512
|
+
profileImageUrl: mediaResourceSchema.optional(),
|
|
4513
|
+
gender: import_zod14.z.enum(["male", "female", "other"]).optional(),
|
|
4514
|
+
bio: import_zod14.z.string().optional()
|
|
4515
|
+
}).optional(),
|
|
4516
|
+
certification: import_zod14.z.any().optional()
|
|
4517
|
+
}).optional()
|
|
4518
|
+
});
|
|
4519
|
+
|
|
4520
|
+
// src/services/practitioner/practitioner.service.ts
|
|
4521
|
+
var import_zod15 = require("zod");
|
|
4522
|
+
var import_geofire_common2 = require("geofire-common");
|
|
4523
|
+
var PractitionerService = class extends BaseService {
|
|
4524
|
+
constructor(db, auth, app, clinicService) {
|
|
4525
|
+
super(db, auth, app);
|
|
4526
|
+
this.clinicService = clinicService;
|
|
4527
|
+
this.mediaService = new MediaService(db, auth, app);
|
|
4528
|
+
}
|
|
4529
|
+
getClinicService() {
|
|
4530
|
+
if (!this.clinicService) {
|
|
4531
|
+
throw new Error("Clinic service not initialized!");
|
|
4532
|
+
}
|
|
4533
|
+
return this.clinicService;
|
|
4534
|
+
}
|
|
4535
|
+
setClinicService(clinicService) {
|
|
4536
|
+
this.clinicService = clinicService;
|
|
4537
|
+
}
|
|
4538
|
+
/**
|
|
4539
|
+
* Handles profile photo upload for practitioners
|
|
4540
|
+
* @param profilePhoto - MediaResource (File, Blob, or URL string)
|
|
4541
|
+
* @param practitionerId - ID of the practitioner
|
|
4542
|
+
* @returns URL string of the uploaded or existing photo
|
|
4543
|
+
*/
|
|
4544
|
+
async handleProfilePhotoUpload(profilePhoto, practitionerId) {
|
|
4545
|
+
if (!profilePhoto) {
|
|
4546
|
+
return void 0;
|
|
4547
|
+
}
|
|
4548
|
+
if (typeof profilePhoto === "string") {
|
|
4549
|
+
return profilePhoto;
|
|
4550
|
+
}
|
|
4551
|
+
if (profilePhoto instanceof File || profilePhoto instanceof Blob) {
|
|
4552
|
+
console.log(
|
|
4553
|
+
`[PractitionerService] Uploading profile photo for practitioner ${practitionerId}`
|
|
4554
|
+
);
|
|
4555
|
+
const mediaMetadata = await this.mediaService.uploadMedia(
|
|
4556
|
+
profilePhoto,
|
|
4557
|
+
practitionerId,
|
|
4558
|
+
// Using practitionerId as ownerId
|
|
4559
|
+
"public" /* PUBLIC */,
|
|
4560
|
+
// Profile photos should be public
|
|
4561
|
+
"practitioner_profile_photos",
|
|
4562
|
+
profilePhoto instanceof File ? profilePhoto.name : `profile_photo_${practitionerId}`
|
|
4563
|
+
);
|
|
4564
|
+
return mediaMetadata.url;
|
|
4565
|
+
}
|
|
4566
|
+
return void 0;
|
|
4567
|
+
}
|
|
4568
|
+
/**
|
|
4569
|
+
* Processes BasicPractitionerInfo to handle profile photo uploads
|
|
4570
|
+
* @param basicInfo - The basic info containing potential MediaResource profile photo
|
|
4571
|
+
* @param practitionerId - ID of the practitioner
|
|
4572
|
+
* @returns Processed basic info with URL string for profileImageUrl
|
|
4573
|
+
*/
|
|
4574
|
+
async processBasicInfo(basicInfo, practitionerId) {
|
|
4575
|
+
const processedBasicInfo = { ...basicInfo };
|
|
4576
|
+
if (basicInfo.profileImageUrl) {
|
|
4577
|
+
const uploadedUrl = await this.handleProfilePhotoUpload(
|
|
4578
|
+
basicInfo.profileImageUrl,
|
|
4579
|
+
practitionerId
|
|
4580
|
+
);
|
|
4581
|
+
processedBasicInfo.profileImageUrl = uploadedUrl;
|
|
4582
|
+
}
|
|
4583
|
+
return processedBasicInfo;
|
|
4584
|
+
}
|
|
4585
|
+
/**
|
|
4586
|
+
* Creates a new practitioner
|
|
4587
|
+
*/
|
|
4588
|
+
async createPractitioner(data) {
|
|
4589
|
+
try {
|
|
4590
|
+
const validData = createPractitionerSchema.parse(data);
|
|
4591
|
+
const practitionerId = this.generateId();
|
|
4592
|
+
const reviewInfo = {
|
|
4593
|
+
totalReviews: 0,
|
|
4594
|
+
averageRating: 0,
|
|
4595
|
+
knowledgeAndExpertise: 0,
|
|
4596
|
+
communicationSkills: 0,
|
|
4597
|
+
bedSideManner: 0,
|
|
4598
|
+
thoroughness: 0,
|
|
4599
|
+
trustworthiness: 0,
|
|
4600
|
+
recommendationPercentage: 0
|
|
4601
|
+
};
|
|
4602
|
+
const practitioner = {
|
|
4603
|
+
id: practitionerId,
|
|
4604
|
+
userRef: validData.userRef,
|
|
4605
|
+
basicInfo: await this.processBasicInfo(
|
|
4606
|
+
validData.basicInfo,
|
|
4607
|
+
practitionerId
|
|
4608
|
+
),
|
|
4609
|
+
certification: validData.certification,
|
|
4610
|
+
clinics: validData.clinics || [],
|
|
4611
|
+
clinicWorkingHours: validData.clinicWorkingHours || [],
|
|
4612
|
+
clinicsInfo: [],
|
|
4613
|
+
procedures: [],
|
|
4614
|
+
proceduresInfo: [],
|
|
4615
|
+
reviewInfo,
|
|
4616
|
+
isActive: validData.isActive !== void 0 ? validData.isActive : true,
|
|
4617
|
+
isVerified: validData.isVerified !== void 0 ? validData.isVerified : false,
|
|
4618
|
+
status: validData.status || "active" /* ACTIVE */,
|
|
4619
|
+
createdAt: (0, import_firestore18.serverTimestamp)(),
|
|
4620
|
+
updatedAt: (0, import_firestore18.serverTimestamp)()
|
|
4621
|
+
};
|
|
4622
|
+
practitionerSchema.parse({
|
|
4623
|
+
...practitioner,
|
|
4624
|
+
createdAt: import_firestore18.Timestamp.now(),
|
|
4625
|
+
updatedAt: import_firestore18.Timestamp.now()
|
|
4626
|
+
});
|
|
4627
|
+
const practitionerRef = (0, import_firestore18.doc)(
|
|
4628
|
+
this.db,
|
|
4629
|
+
PRACTITIONERS_COLLECTION,
|
|
4630
|
+
practitionerId
|
|
4631
|
+
);
|
|
4632
|
+
await (0, import_firestore18.setDoc)(practitionerRef, practitioner);
|
|
4633
|
+
const createdPractitioner = await this.getPractitioner(practitionerId);
|
|
4634
|
+
if (!createdPractitioner) {
|
|
4635
|
+
throw new Error(
|
|
4636
|
+
`Failed to retrieve created practitioner ${practitionerId}`
|
|
4637
|
+
);
|
|
4638
|
+
}
|
|
4639
|
+
return createdPractitioner;
|
|
4640
|
+
} catch (error) {
|
|
4641
|
+
if (error instanceof import_zod15.z.ZodError) {
|
|
4642
|
+
throw new Error(`Invalid practitioner data: ${error.message}`);
|
|
4643
|
+
}
|
|
4644
|
+
console.error("Error creating practitioner:", error);
|
|
4645
|
+
throw error;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
/**
|
|
4649
|
+
* Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
|
|
4650
|
+
* Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
|
|
4651
|
+
* @param data Podaci za kreiranje draft profila
|
|
4652
|
+
* @param createdBy ID administratora koji kreira profil
|
|
4653
|
+
* @param clinicId ID klinike za koju se kreira profil
|
|
4654
|
+
* @returns Objekt koji sadrži kreirani draft profil i token za registraciju
|
|
4655
|
+
*/
|
|
4656
|
+
async createDraftPractitioner(data, createdBy, clinicId) {
|
|
4657
|
+
try {
|
|
4658
|
+
const validatedData = createDraftPractitionerSchema.parse(data);
|
|
4659
|
+
const clinic = await this.getClinicService().getClinic(clinicId);
|
|
4660
|
+
if (!clinic) {
|
|
4661
|
+
throw new Error(`Clinic ${clinicId} not found`);
|
|
4662
|
+
}
|
|
4663
|
+
const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
|
|
4664
|
+
if (data.clinics && data.clinics.length > 0) {
|
|
4665
|
+
for (const cId of data.clinics) {
|
|
4666
|
+
if (cId !== clinicId) {
|
|
4667
|
+
const otherClinic = await this.getClinicService().getClinic(cId);
|
|
4668
|
+
if (!otherClinic) {
|
|
4669
|
+
throw new Error(`Clinic ${cId} not found`);
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
clinicsToAdd.add(cId);
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
const clinics = Array.from(clinicsToAdd);
|
|
4676
|
+
const defaultReviewInfo = {
|
|
4677
|
+
totalReviews: 0,
|
|
4678
|
+
averageRating: 0,
|
|
4679
|
+
knowledgeAndExpertise: 0,
|
|
4680
|
+
communicationSkills: 0,
|
|
4681
|
+
bedSideManner: 0,
|
|
4682
|
+
thoroughness: 0,
|
|
4683
|
+
trustworthiness: 0,
|
|
4684
|
+
recommendationPercentage: 0
|
|
4685
|
+
};
|
|
4686
|
+
const practitionerId = this.generateId();
|
|
4687
|
+
const clinicsInfo = [];
|
|
4688
|
+
for (const cId of clinics) {
|
|
4689
|
+
const clinicData = await this.getClinicService().getClinic(cId);
|
|
4690
|
+
if (clinicData) {
|
|
4691
|
+
clinicsInfo.push({
|
|
4692
|
+
id: clinicData.id,
|
|
4693
|
+
name: clinicData.name,
|
|
4694
|
+
location: clinicData.location,
|
|
4695
|
+
contactInfo: clinicData.contactInfo,
|
|
4696
|
+
// Make sure we're using the right property for featuredPhoto
|
|
4697
|
+
featuredPhoto: clinicData.featuredPhotos && clinicData.featuredPhotos.length > 0 ? typeof clinicData.featuredPhotos[0] === "string" ? clinicData.featuredPhotos[0] : "" : (typeof clinicData.coverPhoto === "string" ? clinicData.coverPhoto : "") || "",
|
|
4698
|
+
description: clinicData.description || null
|
|
4699
|
+
});
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
const finalClinicsInfo = validatedData.clinicsInfo && validatedData.clinicsInfo.length > 0 ? validatedData.clinicsInfo : clinicsInfo;
|
|
4703
|
+
const proceduresInfo = [];
|
|
4704
|
+
const practitionerData = {
|
|
4705
|
+
id: practitionerId,
|
|
4706
|
+
userRef: "",
|
|
4707
|
+
// Prazno - biće popunjeno kada korisnik kreira nalog
|
|
4708
|
+
basicInfo: await this.processBasicInfo(
|
|
4709
|
+
validatedData.basicInfo,
|
|
4710
|
+
practitionerId
|
|
4711
|
+
),
|
|
4712
|
+
certification: validatedData.certification,
|
|
4713
|
+
clinics,
|
|
4714
|
+
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
4715
|
+
clinicsInfo: finalClinicsInfo,
|
|
4716
|
+
procedures: [],
|
|
4717
|
+
proceduresInfo,
|
|
4718
|
+
reviewInfo: defaultReviewInfo,
|
|
4719
|
+
isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
|
|
4720
|
+
isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
|
|
4721
|
+
status: "draft" /* DRAFT */,
|
|
4722
|
+
createdAt: (0, import_firestore18.serverTimestamp)(),
|
|
4723
|
+
updatedAt: (0, import_firestore18.serverTimestamp)()
|
|
4724
|
+
};
|
|
4725
|
+
practitionerSchema.parse({
|
|
4726
|
+
...practitionerData,
|
|
4727
|
+
userRef: "temp-for-validation",
|
|
4728
|
+
createdAt: import_firestore18.Timestamp.now(),
|
|
4729
|
+
updatedAt: import_firestore18.Timestamp.now()
|
|
4730
|
+
});
|
|
4731
|
+
await (0, import_firestore18.setDoc)(
|
|
4732
|
+
(0, import_firestore18.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
|
|
4733
|
+
practitionerData
|
|
4734
|
+
);
|
|
4735
|
+
const savedPractitioner = await this.getPractitioner(practitionerData.id);
|
|
4736
|
+
if (!savedPractitioner) {
|
|
4737
|
+
throw new Error("Failed to create draft practitioner profile");
|
|
4738
|
+
}
|
|
4739
|
+
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
4740
|
+
const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
4741
|
+
const token = {
|
|
4742
|
+
id: this.generateId(),
|
|
4743
|
+
token: tokenString,
|
|
4744
|
+
practitionerId,
|
|
4745
|
+
email: practitionerData.basicInfo.email,
|
|
4746
|
+
clinicId,
|
|
4747
|
+
status: "active" /* ACTIVE */,
|
|
4748
|
+
createdBy,
|
|
4749
|
+
createdAt: import_firestore18.Timestamp.now(),
|
|
4750
|
+
expiresAt: import_firestore18.Timestamp.fromDate(expiration)
|
|
4751
|
+
};
|
|
4752
|
+
practitionerTokenSchema.parse(token);
|
|
4753
|
+
const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4754
|
+
await (0, import_firestore18.setDoc)((0, import_firestore18.doc)(this.db, tokenPath), token);
|
|
4755
|
+
return { practitioner: savedPractitioner, token };
|
|
4756
|
+
} catch (error) {
|
|
4757
|
+
if (error instanceof import_zod15.z.ZodError) {
|
|
4758
|
+
throw new Error("Invalid practitioner data: " + error.message);
|
|
4759
|
+
}
|
|
4760
|
+
throw error;
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
/**
|
|
4764
|
+
* Creates a token for inviting practitioner to claim their profile
|
|
4765
|
+
* @param data Data for creating token
|
|
4766
|
+
* @param createdBy ID of the user creating the token
|
|
4767
|
+
* @returns Created token
|
|
4768
|
+
*/
|
|
4769
|
+
async createPractitionerToken(data, createdBy) {
|
|
4770
|
+
try {
|
|
4771
|
+
const validatedData = createPractitionerTokenSchema.parse(data);
|
|
4772
|
+
const practitioner = await this.getPractitioner(
|
|
4773
|
+
validatedData.practitionerId
|
|
4774
|
+
);
|
|
4775
|
+
if (!practitioner) {
|
|
4776
|
+
throw new Error("Practitioner not found");
|
|
4777
|
+
}
|
|
4778
|
+
if (practitioner.status !== "draft" /* DRAFT */) {
|
|
4779
|
+
throw new Error(
|
|
4780
|
+
"Can only create tokens for practitioners in DRAFT status"
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
const clinic = await this.getClinicService().getClinic(
|
|
4784
|
+
validatedData.clinicId
|
|
4785
|
+
);
|
|
4786
|
+
if (!clinic) {
|
|
4787
|
+
throw new Error(`Clinic ${validatedData.clinicId} not found`);
|
|
4788
|
+
}
|
|
4789
|
+
if (!practitioner.clinics.includes(validatedData.clinicId)) {
|
|
4790
|
+
throw new Error("Practitioner is not associated with this clinic");
|
|
4791
|
+
}
|
|
4792
|
+
const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
4793
|
+
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
4794
|
+
const token = {
|
|
4795
|
+
id: this.generateId(),
|
|
4796
|
+
token: tokenString,
|
|
4797
|
+
practitionerId: validatedData.practitionerId,
|
|
4798
|
+
email: validatedData.email,
|
|
4799
|
+
clinicId: validatedData.clinicId,
|
|
4800
|
+
status: "active" /* ACTIVE */,
|
|
4801
|
+
createdBy,
|
|
4802
|
+
createdAt: import_firestore18.Timestamp.now(),
|
|
4803
|
+
expiresAt: import_firestore18.Timestamp.fromDate(expiration)
|
|
4804
|
+
};
|
|
4805
|
+
practitionerTokenSchema.parse(token);
|
|
4806
|
+
const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
|
|
4807
|
+
await (0, import_firestore18.setDoc)((0, import_firestore18.doc)(this.db, tokenPath), token);
|
|
4808
|
+
return token;
|
|
4809
|
+
} catch (error) {
|
|
4810
|
+
if (error instanceof import_zod15.z.ZodError) {
|
|
4811
|
+
throw new Error("Invalid token data: " + error.message);
|
|
4812
|
+
}
|
|
4813
|
+
throw error;
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
/**
|
|
4817
|
+
* Gets active tokens for a practitioner
|
|
4818
|
+
* @param practitionerId ID of the practitioner
|
|
4819
|
+
* @returns Array of active tokens
|
|
4820
|
+
*/
|
|
4821
|
+
async getPractitionerActiveTokens(practitionerId) {
|
|
4822
|
+
const tokensRef = (0, import_firestore18.collection)(
|
|
4823
|
+
this.db,
|
|
4824
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
4825
|
+
);
|
|
4826
|
+
const q = (0, import_firestore18.query)(
|
|
4827
|
+
tokensRef,
|
|
4828
|
+
(0, import_firestore18.where)("status", "==", "active" /* ACTIVE */),
|
|
4829
|
+
(0, import_firestore18.where)("expiresAt", ">", import_firestore18.Timestamp.now())
|
|
4830
|
+
);
|
|
4831
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4832
|
+
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4833
|
+
}
|
|
4834
|
+
/**
|
|
4835
|
+
* Gets a token by its string value and validates it
|
|
4836
|
+
* @param tokenString The token string to find
|
|
4837
|
+
* @returns The token if found and valid, null otherwise
|
|
4838
|
+
*/
|
|
4839
|
+
async validateToken(tokenString) {
|
|
4840
|
+
const practitionersRef = (0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION);
|
|
4841
|
+
const practitionersSnapshot = await (0, import_firestore18.getDocs)(practitionersRef);
|
|
4842
|
+
for (const practitionerDoc of practitionersSnapshot.docs) {
|
|
4843
|
+
const practitionerId = practitionerDoc.id;
|
|
4844
|
+
const tokensRef = (0, import_firestore18.collection)(
|
|
4845
|
+
this.db,
|
|
4846
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
4847
|
+
);
|
|
4848
|
+
console.log(
|
|
4849
|
+
`[PRACTITIONER] Validating token for practitioner ${practitionerId}`,
|
|
4850
|
+
{
|
|
4851
|
+
tokenString,
|
|
4852
|
+
timestamp: import_firestore18.Timestamp.now().toDate()
|
|
4853
|
+
}
|
|
4854
|
+
);
|
|
4855
|
+
const q = (0, import_firestore18.query)(
|
|
4856
|
+
tokensRef,
|
|
4857
|
+
(0, import_firestore18.where)("token", "==", tokenString),
|
|
4858
|
+
(0, import_firestore18.where)("status", "==", "active" /* ACTIVE */),
|
|
4859
|
+
(0, import_firestore18.where)("expiresAt", ">", import_firestore18.Timestamp.now())
|
|
4860
|
+
);
|
|
4861
|
+
try {
|
|
4862
|
+
const tokenSnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4863
|
+
console.log(
|
|
4864
|
+
`[PRACTITIONER] Token query results for practitioner ${practitionerId}`,
|
|
4865
|
+
{
|
|
4866
|
+
found: !tokenSnapshot.empty,
|
|
4867
|
+
count: tokenSnapshot.size
|
|
4868
|
+
}
|
|
4869
|
+
);
|
|
4870
|
+
if (!tokenSnapshot.empty) {
|
|
4871
|
+
const tokenData = tokenSnapshot.docs[0].data();
|
|
4872
|
+
console.log(`[PRACTITIONER] Valid token found`, {
|
|
4873
|
+
tokenId: tokenData.id,
|
|
4874
|
+
expiresAt: tokenData.expiresAt.toDate()
|
|
4875
|
+
});
|
|
4876
|
+
return tokenData;
|
|
4877
|
+
}
|
|
4878
|
+
} catch (error) {
|
|
4879
|
+
console.error(
|
|
4880
|
+
`[PRACTITIONER] Error validating token for practitioner ${practitionerId}:`,
|
|
4881
|
+
error
|
|
4882
|
+
);
|
|
4883
|
+
throw error;
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
return null;
|
|
4887
|
+
}
|
|
4888
|
+
/**
|
|
4889
|
+
* Marks a token as used
|
|
4890
|
+
* @param tokenId ID of the token
|
|
4891
|
+
* @param practitionerId ID of the practitioner
|
|
4892
|
+
* @param userId ID of the user using the token
|
|
4893
|
+
*/
|
|
4894
|
+
async markTokenAsUsed(tokenId, practitionerId, userId) {
|
|
4895
|
+
const tokenRef = (0, import_firestore18.doc)(
|
|
4896
|
+
this.db,
|
|
4897
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
|
|
4898
|
+
);
|
|
4899
|
+
await (0, import_firestore18.updateDoc)(tokenRef, {
|
|
4900
|
+
status: "used" /* USED */,
|
|
4901
|
+
usedBy: userId,
|
|
4902
|
+
usedAt: import_firestore18.Timestamp.now()
|
|
4903
|
+
});
|
|
4904
|
+
}
|
|
4905
|
+
/**
|
|
4906
|
+
* Dohvata zdravstvenog radnika po ID-u
|
|
4907
|
+
*/
|
|
4908
|
+
async getPractitioner(practitionerId) {
|
|
4909
|
+
const practitionerDoc = await (0, import_firestore18.getDoc)(
|
|
4910
|
+
(0, import_firestore18.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId)
|
|
4911
|
+
);
|
|
4912
|
+
if (!practitionerDoc.exists()) {
|
|
4913
|
+
return null;
|
|
4914
|
+
}
|
|
4915
|
+
return practitionerDoc.data();
|
|
4916
|
+
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Dohvata zdravstvenog radnika po User ID-u
|
|
4919
|
+
*/
|
|
4920
|
+
async getPractitionerByUserRef(userRef) {
|
|
4921
|
+
const q = (0, import_firestore18.query)(
|
|
4922
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4923
|
+
(0, import_firestore18.where)("userRef", "==", userRef)
|
|
4924
|
+
);
|
|
4925
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4926
|
+
if (querySnapshot.empty) {
|
|
4927
|
+
return null;
|
|
4928
|
+
}
|
|
4929
|
+
return querySnapshot.docs[0].data();
|
|
4930
|
+
}
|
|
4931
|
+
/**
|
|
4932
|
+
* Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
|
|
4933
|
+
*/
|
|
4934
|
+
async getPractitionersByClinic(clinicId) {
|
|
4935
|
+
const q = (0, import_firestore18.query)(
|
|
4936
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4937
|
+
(0, import_firestore18.where)("clinics", "array-contains", clinicId),
|
|
4938
|
+
(0, import_firestore18.where)("isActive", "==", true),
|
|
4939
|
+
(0, import_firestore18.where)("status", "==", "active" /* ACTIVE */)
|
|
4940
|
+
);
|
|
4941
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4942
|
+
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4943
|
+
}
|
|
4944
|
+
/**
|
|
4945
|
+
* Dohvata sve zdravstvene radnike za određenu kliniku
|
|
4946
|
+
*/
|
|
4947
|
+
async getAllPractitionersByClinic(clinicId) {
|
|
4948
|
+
const q = (0, import_firestore18.query)(
|
|
4949
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4950
|
+
(0, import_firestore18.where)("clinics", "array-contains", clinicId),
|
|
4951
|
+
(0, import_firestore18.where)("isActive", "==", true)
|
|
4952
|
+
);
|
|
4953
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4954
|
+
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4955
|
+
}
|
|
4956
|
+
/**
|
|
4957
|
+
* Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
|
|
4958
|
+
*/
|
|
4959
|
+
async getDraftPractitionersByClinic(clinicId) {
|
|
4960
|
+
const q = (0, import_firestore18.query)(
|
|
4961
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4962
|
+
(0, import_firestore18.where)("clinics", "array-contains", clinicId),
|
|
4963
|
+
(0, import_firestore18.where)("status", "==", "draft" /* DRAFT */)
|
|
4964
|
+
);
|
|
4965
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4966
|
+
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
4967
|
+
}
|
|
4968
|
+
/**
|
|
4969
|
+
* Updates a practitioner
|
|
4970
|
+
*/
|
|
4971
|
+
async updatePractitioner(practitionerId, data) {
|
|
4972
|
+
try {
|
|
4973
|
+
const validData = data;
|
|
4974
|
+
const practitionerRef = (0, import_firestore18.doc)(
|
|
4975
|
+
this.db,
|
|
4976
|
+
PRACTITIONERS_COLLECTION,
|
|
4977
|
+
practitionerId
|
|
4978
|
+
);
|
|
4979
|
+
const practitionerDoc = await (0, import_firestore18.getDoc)(practitionerRef);
|
|
4980
|
+
if (!practitionerDoc.exists()) {
|
|
4981
|
+
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
4982
|
+
}
|
|
4983
|
+
const currentPractitioner = practitionerDoc.data();
|
|
4984
|
+
let processedData = { ...validData };
|
|
4985
|
+
if (validData.basicInfo) {
|
|
4986
|
+
processedData.basicInfo = await this.processBasicInfo(
|
|
4987
|
+
validData.basicInfo,
|
|
4988
|
+
practitionerId
|
|
4989
|
+
);
|
|
4990
|
+
}
|
|
4991
|
+
const updateData = {
|
|
4992
|
+
...processedData,
|
|
4993
|
+
updatedAt: (0, import_firestore18.serverTimestamp)()
|
|
4994
|
+
};
|
|
4995
|
+
await (0, import_firestore18.updateDoc)(practitionerRef, updateData);
|
|
4996
|
+
const updatedPractitioner = await this.getPractitioner(practitionerId);
|
|
4997
|
+
if (!updatedPractitioner) {
|
|
4998
|
+
throw new Error(
|
|
4999
|
+
`Failed to retrieve updated practitioner ${practitionerId}`
|
|
5000
|
+
);
|
|
5001
|
+
}
|
|
5002
|
+
return updatedPractitioner;
|
|
5003
|
+
} catch (error) {
|
|
5004
|
+
if (error instanceof import_zod15.z.ZodError) {
|
|
5005
|
+
throw new Error(`Invalid practitioner update data: ${error.message}`);
|
|
5006
|
+
}
|
|
5007
|
+
console.error(`Error updating practitioner ${practitionerId}:`, error);
|
|
5008
|
+
throw error;
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
/**
|
|
5012
|
+
* Adds a clinic to a practitioner
|
|
5013
|
+
*/
|
|
5014
|
+
async addClinic(practitionerId, clinicId) {
|
|
5015
|
+
var _a;
|
|
5016
|
+
try {
|
|
5017
|
+
const practitionerRef = (0, import_firestore18.doc)(
|
|
5018
|
+
this.db,
|
|
5019
|
+
PRACTITIONERS_COLLECTION,
|
|
5020
|
+
practitionerId
|
|
5021
|
+
);
|
|
5022
|
+
const practitionerDoc = await (0, import_firestore18.getDoc)(practitionerRef);
|
|
5023
|
+
if (!practitionerDoc.exists()) {
|
|
5024
|
+
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
5025
|
+
}
|
|
5026
|
+
const practitioner = practitionerDoc.data();
|
|
5027
|
+
if ((_a = practitioner.clinics) == null ? void 0 : _a.includes(clinicId)) {
|
|
5028
|
+
console.log(
|
|
5029
|
+
`Clinic ${clinicId} already added to practitioner ${practitionerId}`
|
|
5030
|
+
);
|
|
5031
|
+
return;
|
|
5032
|
+
}
|
|
5033
|
+
await (0, import_firestore18.updateDoc)(practitionerRef, {
|
|
5034
|
+
clinics: (0, import_firestore18.arrayUnion)(clinicId),
|
|
5035
|
+
updatedAt: (0, import_firestore18.serverTimestamp)()
|
|
5036
|
+
});
|
|
5037
|
+
} catch (error) {
|
|
5038
|
+
console.error(
|
|
5039
|
+
`Error adding clinic ${clinicId} to practitioner ${practitionerId}:`,
|
|
5040
|
+
error
|
|
5041
|
+
);
|
|
5042
|
+
throw error;
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
/**
|
|
5046
|
+
* Removes a clinic from a practitioner
|
|
5047
|
+
*/
|
|
5048
|
+
async removeClinic(practitionerId, clinicId) {
|
|
5049
|
+
try {
|
|
5050
|
+
const practitionerRef = (0, import_firestore18.doc)(
|
|
5051
|
+
this.db,
|
|
5052
|
+
PRACTITIONERS_COLLECTION,
|
|
5053
|
+
practitionerId
|
|
5054
|
+
);
|
|
5055
|
+
const practitionerDoc = await (0, import_firestore18.getDoc)(practitionerRef);
|
|
5056
|
+
if (!practitionerDoc.exists()) {
|
|
5057
|
+
throw new Error(`Practitioner ${practitionerId} not found`);
|
|
5058
|
+
}
|
|
5059
|
+
await (0, import_firestore18.updateDoc)(practitionerRef, {
|
|
5060
|
+
clinics: (0, import_firestore18.arrayRemove)(clinicId),
|
|
5061
|
+
updatedAt: (0, import_firestore18.serverTimestamp)()
|
|
5062
|
+
});
|
|
5063
|
+
} catch (error) {
|
|
5064
|
+
console.error(
|
|
5065
|
+
`Error removing clinic ${clinicId} from practitioner ${practitionerId}:`,
|
|
5066
|
+
error
|
|
5067
|
+
);
|
|
5068
|
+
throw error;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
/**
|
|
5072
|
+
* Deaktivira profil zdravstvenog radnika
|
|
5073
|
+
*/
|
|
5074
|
+
async deactivatePractitioner(practitionerId) {
|
|
5075
|
+
await this.updatePractitioner(practitionerId, {
|
|
5076
|
+
isActive: false
|
|
5077
|
+
});
|
|
5078
|
+
}
|
|
5079
|
+
/**
|
|
5080
|
+
* Aktivira profil zdravstvenog radnika
|
|
5081
|
+
*/
|
|
5082
|
+
async activatePractitioner(practitionerId) {
|
|
5083
|
+
await this.updatePractitioner(practitionerId, {
|
|
5084
|
+
isActive: true
|
|
5085
|
+
});
|
|
5086
|
+
}
|
|
5087
|
+
/**
|
|
5088
|
+
* Briše profil zdravstvenog radnika
|
|
5089
|
+
*/
|
|
5090
|
+
async deletePractitioner(practitionerId) {
|
|
5091
|
+
const practitioner = await this.getPractitioner(practitionerId);
|
|
5092
|
+
if (!practitioner) {
|
|
5093
|
+
throw new Error("Practitioner not found");
|
|
5094
|
+
}
|
|
5095
|
+
await (0, import_firestore18.deleteDoc)((0, import_firestore18.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId));
|
|
5096
|
+
}
|
|
5097
|
+
/**
|
|
5098
|
+
* Validates a registration token and claims the associated draft practitioner profile
|
|
5099
|
+
* @param tokenString The token provided by the practitioner
|
|
5100
|
+
* @param userId The ID of the user claiming the profile
|
|
5101
|
+
* @returns The claimed practitioner profile or null if token is invalid
|
|
5102
|
+
*/
|
|
5103
|
+
async validateTokenAndClaimProfile(tokenString, userId) {
|
|
5104
|
+
console.log("[PRACTITIONER] Validating token for claiming profile", {
|
|
5105
|
+
tokenString,
|
|
5106
|
+
userId
|
|
5107
|
+
});
|
|
5108
|
+
const token = await this.validateToken(tokenString);
|
|
5109
|
+
if (!token) {
|
|
5110
|
+
console.log(
|
|
5111
|
+
"[PRACTITIONER] Token validation failed - token not found or not valid",
|
|
5112
|
+
{
|
|
5113
|
+
tokenString
|
|
5114
|
+
}
|
|
5115
|
+
);
|
|
5116
|
+
return null;
|
|
5117
|
+
}
|
|
5118
|
+
console.log("[PRACTITIONER] Token successfully validated", {
|
|
5119
|
+
tokenId: token.id,
|
|
5120
|
+
practitionerId: token.practitionerId
|
|
5121
|
+
});
|
|
5122
|
+
const practitioner = await this.getPractitioner(token.practitionerId);
|
|
5123
|
+
if (!practitioner) {
|
|
5124
|
+
console.log("[PRACTITIONER] Practitioner not found", {
|
|
5125
|
+
practitionerId: token.practitionerId
|
|
5126
|
+
});
|
|
5127
|
+
return null;
|
|
5128
|
+
}
|
|
5129
|
+
if (practitioner.status !== "draft" /* DRAFT */) {
|
|
5130
|
+
console.log("[PRACTITIONER] Practitioner status is not DRAFT", {
|
|
5131
|
+
practitionerId: practitioner.id,
|
|
5132
|
+
status: practitioner.status
|
|
5133
|
+
});
|
|
5134
|
+
throw new Error("This practitioner profile has already been claimed");
|
|
5135
|
+
}
|
|
5136
|
+
const existingPractitioner = await this.getPractitionerByUserRef(userId);
|
|
5137
|
+
if (existingPractitioner) {
|
|
5138
|
+
throw new Error("User already has a practitioner profile");
|
|
5139
|
+
}
|
|
5140
|
+
const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
|
|
5141
|
+
userRef: userId,
|
|
5142
|
+
status: "active" /* ACTIVE */
|
|
5143
|
+
});
|
|
5144
|
+
await this.markTokenAsUsed(token.id, token.practitionerId, userId);
|
|
5145
|
+
console.log("[PRACTITIONER] Profile claimed successfully", {
|
|
5146
|
+
practitionerId: updatedPractitioner.id,
|
|
5147
|
+
userId
|
|
4789
5148
|
});
|
|
4790
5149
|
return updatedPractitioner;
|
|
4791
5150
|
}
|
|
@@ -4802,21 +5161,21 @@ var PractitionerService = class extends BaseService {
|
|
|
4802
5161
|
try {
|
|
4803
5162
|
const constraints = [];
|
|
4804
5163
|
if (!(options == null ? void 0 : options.includeDraftPractitioners)) {
|
|
4805
|
-
constraints.push((0,
|
|
5164
|
+
constraints.push((0, import_firestore18.where)("status", "==", "active" /* ACTIVE */));
|
|
4806
5165
|
}
|
|
4807
|
-
constraints.push((0,
|
|
4808
|
-
constraints.push((0,
|
|
5166
|
+
constraints.push((0, import_firestore18.orderBy)("basicInfo.lastName", "asc"));
|
|
5167
|
+
constraints.push((0, import_firestore18.orderBy)("basicInfo.firstName", "asc"));
|
|
4809
5168
|
if ((options == null ? void 0 : options.pagination) && options.pagination > 0) {
|
|
4810
5169
|
if (options.lastDoc) {
|
|
4811
|
-
constraints.push((0,
|
|
5170
|
+
constraints.push((0, import_firestore18.startAfter)(options.lastDoc));
|
|
4812
5171
|
}
|
|
4813
|
-
constraints.push((0,
|
|
5172
|
+
constraints.push((0, import_firestore18.limit)(options.pagination));
|
|
4814
5173
|
}
|
|
4815
|
-
const q = (0,
|
|
4816
|
-
(0,
|
|
5174
|
+
const q = (0, import_firestore18.query)(
|
|
5175
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4817
5176
|
...constraints
|
|
4818
5177
|
);
|
|
4819
|
-
const querySnapshot = await (0,
|
|
5178
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4820
5179
|
const practitioners = querySnapshot.docs.map(
|
|
4821
5180
|
(doc34) => doc34.data()
|
|
4822
5181
|
);
|
|
@@ -4861,31 +5220,31 @@ var PractitionerService = class extends BaseService {
|
|
|
4861
5220
|
);
|
|
4862
5221
|
const constraints = [];
|
|
4863
5222
|
if (!filters.includeDraftPractitioners) {
|
|
4864
|
-
constraints.push((0,
|
|
5223
|
+
constraints.push((0, import_firestore18.where)("status", "==", "active" /* ACTIVE */));
|
|
4865
5224
|
}
|
|
4866
|
-
constraints.push((0,
|
|
5225
|
+
constraints.push((0, import_firestore18.where)("isActive", "==", true));
|
|
4867
5226
|
if (filters.certifications && filters.certifications.length > 0) {
|
|
4868
5227
|
constraints.push(
|
|
4869
|
-
(0,
|
|
5228
|
+
(0, import_firestore18.where)(
|
|
4870
5229
|
"certification.certifications",
|
|
4871
5230
|
"array-contains-any",
|
|
4872
5231
|
filters.certifications
|
|
4873
5232
|
)
|
|
4874
5233
|
);
|
|
4875
5234
|
}
|
|
4876
|
-
constraints.push((0,
|
|
4877
|
-
constraints.push((0,
|
|
5235
|
+
constraints.push((0, import_firestore18.orderBy)("basicInfo.lastName", "asc"));
|
|
5236
|
+
constraints.push((0, import_firestore18.orderBy)("basicInfo.firstName", "asc"));
|
|
4878
5237
|
if (filters.pagination && filters.pagination > 0) {
|
|
4879
5238
|
if (filters.lastDoc) {
|
|
4880
|
-
constraints.push((0,
|
|
5239
|
+
constraints.push((0, import_firestore18.startAfter)(filters.lastDoc));
|
|
4881
5240
|
}
|
|
4882
|
-
constraints.push((0,
|
|
5241
|
+
constraints.push((0, import_firestore18.limit)(filters.pagination));
|
|
4883
5242
|
}
|
|
4884
|
-
const q = (0,
|
|
4885
|
-
(0,
|
|
5243
|
+
const q = (0, import_firestore18.query)(
|
|
5244
|
+
(0, import_firestore18.collection)(this.db, PRACTITIONERS_COLLECTION),
|
|
4886
5245
|
...constraints
|
|
4887
5246
|
);
|
|
4888
|
-
const querySnapshot = await (0,
|
|
5247
|
+
const querySnapshot = await (0, import_firestore18.getDocs)(q);
|
|
4889
5248
|
console.log(
|
|
4890
5249
|
`[PRACTITIONER_SERVICE] Found ${querySnapshot.docs.length} practitioners with base query`
|
|
4891
5250
|
);
|
|
@@ -5007,11 +5366,11 @@ var UserService = class extends BaseService {
|
|
|
5007
5366
|
email: firebaseUser.email,
|
|
5008
5367
|
roles: roles.length > 0 ? roles : ["patient" /* PATIENT */],
|
|
5009
5368
|
isAnonymous: firebaseUser.isAnonymous,
|
|
5010
|
-
createdAt: (0,
|
|
5011
|
-
updatedAt: (0,
|
|
5012
|
-
lastLoginAt: (0,
|
|
5369
|
+
createdAt: (0, import_firestore19.serverTimestamp)(),
|
|
5370
|
+
updatedAt: (0, import_firestore19.serverTimestamp)(),
|
|
5371
|
+
lastLoginAt: (0, import_firestore19.serverTimestamp)()
|
|
5013
5372
|
};
|
|
5014
|
-
await (0,
|
|
5373
|
+
await (0, import_firestore19.setDoc)((0, import_firestore19.doc)(this.db, USERS_COLLECTION, userData.uid), userData);
|
|
5015
5374
|
if (options == null ? void 0 : options.skipProfileCreation) {
|
|
5016
5375
|
return this.getUserById(userData.uid);
|
|
5017
5376
|
}
|
|
@@ -5020,7 +5379,7 @@ var UserService = class extends BaseService {
|
|
|
5020
5379
|
roles,
|
|
5021
5380
|
options
|
|
5022
5381
|
);
|
|
5023
|
-
await (0,
|
|
5382
|
+
await (0, import_firestore19.updateDoc)((0, import_firestore19.doc)(this.db, USERS_COLLECTION, userData.uid), profiles);
|
|
5024
5383
|
return this.getUserById(userData.uid);
|
|
5025
5384
|
}
|
|
5026
5385
|
/**
|
|
@@ -5098,7 +5457,7 @@ var UserService = class extends BaseService {
|
|
|
5098
5457
|
email: "",
|
|
5099
5458
|
phoneNumber: "",
|
|
5100
5459
|
title: "",
|
|
5101
|
-
dateOfBirth:
|
|
5460
|
+
dateOfBirth: import_firestore19.Timestamp.now(),
|
|
5102
5461
|
gender: "other",
|
|
5103
5462
|
languages: ["Serbian"]
|
|
5104
5463
|
},
|
|
@@ -5107,7 +5466,7 @@ var UserService = class extends BaseService {
|
|
|
5107
5466
|
specialties: [],
|
|
5108
5467
|
licenseNumber: "",
|
|
5109
5468
|
issuingAuthority: "",
|
|
5110
|
-
issueDate:
|
|
5469
|
+
issueDate: import_firestore19.Timestamp.now(),
|
|
5111
5470
|
verificationStatus: "pending"
|
|
5112
5471
|
},
|
|
5113
5472
|
isActive: true,
|
|
@@ -5123,7 +5482,7 @@ var UserService = class extends BaseService {
|
|
|
5123
5482
|
* Dohvata korisnika po ID-u
|
|
5124
5483
|
*/
|
|
5125
5484
|
async getUserById(uid) {
|
|
5126
|
-
const userDoc = await (0,
|
|
5485
|
+
const userDoc = await (0, import_firestore19.getDoc)((0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid));
|
|
5127
5486
|
if (!userDoc.exists()) {
|
|
5128
5487
|
throw USER_ERRORS.NOT_FOUND;
|
|
5129
5488
|
}
|
|
@@ -5134,19 +5493,19 @@ var UserService = class extends BaseService {
|
|
|
5134
5493
|
* Dohvata korisnika po email-u
|
|
5135
5494
|
*/
|
|
5136
5495
|
async getUserByEmail(email) {
|
|
5137
|
-
const usersRef = (0,
|
|
5138
|
-
const q = (0,
|
|
5139
|
-
const querySnapshot = await (0,
|
|
5496
|
+
const usersRef = (0, import_firestore19.collection)(this.db, USERS_COLLECTION);
|
|
5497
|
+
const q = (0, import_firestore19.query)(usersRef, (0, import_firestore19.where)("email", "==", email));
|
|
5498
|
+
const querySnapshot = await (0, import_firestore19.getDocs)(q);
|
|
5140
5499
|
if (querySnapshot.empty) return null;
|
|
5141
5500
|
const userData = querySnapshot.docs[0].data();
|
|
5142
5501
|
return userSchema.parse(userData);
|
|
5143
5502
|
}
|
|
5144
5503
|
async getUsersByRole(role) {
|
|
5145
5504
|
const constraints = [
|
|
5146
|
-
(0,
|
|
5505
|
+
(0, import_firestore19.where)("roles", "array-contains", role)
|
|
5147
5506
|
];
|
|
5148
|
-
const q = (0,
|
|
5149
|
-
const querySnapshot = await (0,
|
|
5507
|
+
const q = (0, import_firestore19.query)((0, import_firestore19.collection)(this.db, USERS_COLLECTION), ...constraints);
|
|
5508
|
+
const querySnapshot = await (0, import_firestore19.getDocs)(q);
|
|
5150
5509
|
const users = querySnapshot.docs.map((doc34) => doc34.data());
|
|
5151
5510
|
return Promise.all(users.map((userData) => userSchema.parse(userData)));
|
|
5152
5511
|
}
|
|
@@ -5154,33 +5513,33 @@ var UserService = class extends BaseService {
|
|
|
5154
5513
|
* Ažurira timestamp poslednjeg logovanja
|
|
5155
5514
|
*/
|
|
5156
5515
|
async updateUserLoginTimestamp(uid) {
|
|
5157
|
-
const userRef = (0,
|
|
5158
|
-
const userDoc = await (0,
|
|
5516
|
+
const userRef = (0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid);
|
|
5517
|
+
const userDoc = await (0, import_firestore19.getDoc)(userRef);
|
|
5159
5518
|
if (!userDoc.exists()) {
|
|
5160
5519
|
throw AUTH_ERRORS.USER_NOT_FOUND;
|
|
5161
5520
|
}
|
|
5162
|
-
await (0,
|
|
5163
|
-
lastLoginAt: (0,
|
|
5164
|
-
updatedAt: (0,
|
|
5521
|
+
await (0, import_firestore19.updateDoc)(userRef, {
|
|
5522
|
+
lastLoginAt: (0, import_firestore19.serverTimestamp)(),
|
|
5523
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5165
5524
|
});
|
|
5166
5525
|
return this.getUserById(uid);
|
|
5167
5526
|
}
|
|
5168
5527
|
async upgradeAnonymousUser(uid, email) {
|
|
5169
|
-
const userRef = (0,
|
|
5170
|
-
const userDoc = await (0,
|
|
5528
|
+
const userRef = (0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid);
|
|
5529
|
+
const userDoc = await (0, import_firestore19.getDoc)(userRef);
|
|
5171
5530
|
if (!userDoc.exists()) {
|
|
5172
5531
|
throw USER_ERRORS.NOT_FOUND;
|
|
5173
5532
|
}
|
|
5174
|
-
await (0,
|
|
5533
|
+
await (0, import_firestore19.updateDoc)(userRef, {
|
|
5175
5534
|
email,
|
|
5176
5535
|
isAnonymous: false,
|
|
5177
|
-
updatedAt: (0,
|
|
5536
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5178
5537
|
});
|
|
5179
5538
|
return this.getUserById(uid);
|
|
5180
5539
|
}
|
|
5181
5540
|
async updateUser(uid, updates) {
|
|
5182
|
-
const userRef = (0,
|
|
5183
|
-
const userDoc = await (0,
|
|
5541
|
+
const userRef = (0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid);
|
|
5542
|
+
const userDoc = await (0, import_firestore19.getDoc)(userRef);
|
|
5184
5543
|
if (!userDoc.exists()) {
|
|
5185
5544
|
throw USER_ERRORS.NOT_FOUND;
|
|
5186
5545
|
}
|
|
@@ -5189,12 +5548,12 @@ var UserService = class extends BaseService {
|
|
|
5189
5548
|
const updatedUser = {
|
|
5190
5549
|
...currentUser,
|
|
5191
5550
|
...updates,
|
|
5192
|
-
updatedAt: (0,
|
|
5551
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5193
5552
|
};
|
|
5194
5553
|
userSchema.parse(updatedUser);
|
|
5195
|
-
await (0,
|
|
5554
|
+
await (0, import_firestore19.updateDoc)(userRef, {
|
|
5196
5555
|
...updates,
|
|
5197
|
-
updatedAt: (0,
|
|
5556
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5198
5557
|
});
|
|
5199
5558
|
return this.getUserById(uid);
|
|
5200
5559
|
} catch (error) {
|
|
@@ -5211,10 +5570,10 @@ var UserService = class extends BaseService {
|
|
|
5211
5570
|
const user = await this.getUserById(uid);
|
|
5212
5571
|
if (user.roles.includes(role)) return;
|
|
5213
5572
|
const profiles = await this.createProfilesForRoles(uid, [role], options);
|
|
5214
|
-
await (0,
|
|
5573
|
+
await (0, import_firestore19.updateDoc)((0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid), {
|
|
5215
5574
|
roles: [...user.roles, role],
|
|
5216
5575
|
...profiles,
|
|
5217
|
-
updatedAt: (0,
|
|
5576
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5218
5577
|
});
|
|
5219
5578
|
}
|
|
5220
5579
|
/**
|
|
@@ -5246,15 +5605,15 @@ var UserService = class extends BaseService {
|
|
|
5246
5605
|
}
|
|
5247
5606
|
break;
|
|
5248
5607
|
}
|
|
5249
|
-
await (0,
|
|
5608
|
+
await (0, import_firestore19.updateDoc)((0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid), {
|
|
5250
5609
|
roles: user.roles.filter((r) => r !== role),
|
|
5251
|
-
updatedAt: (0,
|
|
5610
|
+
updatedAt: (0, import_firestore19.serverTimestamp)()
|
|
5252
5611
|
});
|
|
5253
5612
|
}
|
|
5254
5613
|
// Delete operations
|
|
5255
5614
|
async deleteUser(uid) {
|
|
5256
|
-
const userRef = (0,
|
|
5257
|
-
const userDoc = await (0,
|
|
5615
|
+
const userRef = (0, import_firestore19.doc)(this.db, USERS_COLLECTION, uid);
|
|
5616
|
+
const userDoc = await (0, import_firestore19.getDoc)(userRef);
|
|
5258
5617
|
if (!userDoc.exists()) {
|
|
5259
5618
|
throw USER_ERRORS.NOT_FOUND;
|
|
5260
5619
|
}
|
|
@@ -5275,7 +5634,7 @@ var UserService = class extends BaseService {
|
|
|
5275
5634
|
userData.adminProfile
|
|
5276
5635
|
);
|
|
5277
5636
|
}
|
|
5278
|
-
await (0,
|
|
5637
|
+
await (0, import_firestore19.deleteDoc)(userRef);
|
|
5279
5638
|
} catch (error) {
|
|
5280
5639
|
throw error;
|
|
5281
5640
|
}
|
|
@@ -5283,12 +5642,12 @@ var UserService = class extends BaseService {
|
|
|
5283
5642
|
};
|
|
5284
5643
|
|
|
5285
5644
|
// src/services/clinic/utils/clinic-group.utils.ts
|
|
5286
|
-
var
|
|
5645
|
+
var import_firestore20 = require("firebase/firestore");
|
|
5287
5646
|
var import_geofire_common3 = require("geofire-common");
|
|
5288
5647
|
var import_zod17 = require("zod");
|
|
5289
5648
|
|
|
5290
5649
|
// src/services/clinic/utils/photos.utils.ts
|
|
5291
|
-
var
|
|
5650
|
+
var import_storage5 = require("firebase/storage");
|
|
5292
5651
|
async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName) {
|
|
5293
5652
|
if (!photo || typeof photo !== "string" || !photo.startsWith("data:")) {
|
|
5294
5653
|
return photo;
|
|
@@ -5297,9 +5656,9 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
|
|
|
5297
5656
|
console.log(
|
|
5298
5657
|
`[PHOTO_UTILS] Uploading ${photoType} for ${entityType}/${entityId}`
|
|
5299
5658
|
);
|
|
5300
|
-
const storage = (0,
|
|
5659
|
+
const storage = (0, import_storage5.getStorage)(app);
|
|
5301
5660
|
const storageFileName = fileName || `${photoType}-${Date.now()}`;
|
|
5302
|
-
const storageRef = (0,
|
|
5661
|
+
const storageRef = (0, import_storage5.ref)(
|
|
5303
5662
|
storage,
|
|
5304
5663
|
`${entityType}/${entityId}/${storageFileName}`
|
|
5305
5664
|
);
|
|
@@ -5311,8 +5670,8 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
|
|
|
5311
5670
|
byteArrays.push(byteCharacters.charCodeAt(i));
|
|
5312
5671
|
}
|
|
5313
5672
|
const blob = new Blob([new Uint8Array(byteArrays)], { type: contentType });
|
|
5314
|
-
await (0,
|
|
5315
|
-
const downloadUrl = await (0,
|
|
5673
|
+
await (0, import_storage5.uploadBytes)(storageRef, blob, { contentType });
|
|
5674
|
+
const downloadUrl = await (0, import_storage5.getDownloadURL)(storageRef);
|
|
5316
5675
|
console.log(`[PHOTO_UTILS] ${photoType} uploaded successfully`, {
|
|
5317
5676
|
downloadUrl
|
|
5318
5677
|
});
|
|
@@ -5400,9 +5759,9 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5400
5759
|
throw geohashError;
|
|
5401
5760
|
}
|
|
5402
5761
|
}
|
|
5403
|
-
const now =
|
|
5762
|
+
const now = import_firestore20.Timestamp.now();
|
|
5404
5763
|
console.log("[CLINIC_GROUP] Preparing clinic group data object");
|
|
5405
|
-
const groupId = (0,
|
|
5764
|
+
const groupId = (0, import_firestore20.doc)((0, import_firestore20.collection)(db, CLINIC_GROUPS_COLLECTION)).id;
|
|
5406
5765
|
console.log("[CLINIC_GROUP] Logo value:", {
|
|
5407
5766
|
logoValue: validatedData.logo,
|
|
5408
5767
|
logoType: validatedData.logo === null ? "null" : typeof validatedData.logo
|
|
@@ -5452,7 +5811,7 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5452
5811
|
groupId: groupData.id
|
|
5453
5812
|
});
|
|
5454
5813
|
try {
|
|
5455
|
-
await (0,
|
|
5814
|
+
await (0, import_firestore20.setDoc)((0, import_firestore20.doc)(db, CLINIC_GROUPS_COLLECTION, groupData.id), groupData);
|
|
5456
5815
|
console.log("[CLINIC_GROUP] Clinic group saved successfully");
|
|
5457
5816
|
} catch (firestoreError) {
|
|
5458
5817
|
console.error(
|
|
@@ -5498,19 +5857,19 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
|
|
|
5498
5857
|
}
|
|
5499
5858
|
}
|
|
5500
5859
|
async function getClinicGroup(db, groupId) {
|
|
5501
|
-
const docRef = (0,
|
|
5502
|
-
const docSnap = await (0,
|
|
5860
|
+
const docRef = (0, import_firestore20.doc)(db, CLINIC_GROUPS_COLLECTION, groupId);
|
|
5861
|
+
const docSnap = await (0, import_firestore20.getDoc)(docRef);
|
|
5503
5862
|
if (docSnap.exists()) {
|
|
5504
5863
|
return docSnap.data();
|
|
5505
5864
|
}
|
|
5506
5865
|
return null;
|
|
5507
5866
|
}
|
|
5508
5867
|
async function getAllActiveGroups(db) {
|
|
5509
|
-
const q = (0,
|
|
5510
|
-
(0,
|
|
5511
|
-
(0,
|
|
5868
|
+
const q = (0, import_firestore20.query)(
|
|
5869
|
+
(0, import_firestore20.collection)(db, CLINIC_GROUPS_COLLECTION),
|
|
5870
|
+
(0, import_firestore20.where)("isActive", "==", true)
|
|
5512
5871
|
);
|
|
5513
|
-
const querySnapshot = await (0,
|
|
5872
|
+
const querySnapshot = await (0, import_firestore20.getDocs)(q);
|
|
5514
5873
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
5515
5874
|
}
|
|
5516
5875
|
async function updateClinicGroup(db, groupId, data, app) {
|
|
@@ -5539,10 +5898,10 @@ async function updateClinicGroup(db, groupId, data, app) {
|
|
|
5539
5898
|
}
|
|
5540
5899
|
updatedData = {
|
|
5541
5900
|
...updatedData,
|
|
5542
|
-
updatedAt:
|
|
5901
|
+
updatedAt: import_firestore20.Timestamp.now()
|
|
5543
5902
|
};
|
|
5544
5903
|
console.log("[CLINIC_GROUP] Updating clinic group in Firestore");
|
|
5545
|
-
await (0,
|
|
5904
|
+
await (0, import_firestore20.updateDoc)((0, import_firestore20.doc)(db, CLINIC_GROUPS_COLLECTION, groupId), updatedData);
|
|
5546
5905
|
console.log("[CLINIC_GROUP] Clinic group updated successfully");
|
|
5547
5906
|
const updatedGroup = await getClinicGroup(db, groupId);
|
|
5548
5907
|
if (!updatedGroup) {
|
|
@@ -5623,10 +5982,10 @@ async function createAdminToken(db, groupId, creatorAdminId, app, data) {
|
|
|
5623
5982
|
if (!group.admins.includes(creatorAdminId)) {
|
|
5624
5983
|
throw new Error("Admin does not belong to this clinic group");
|
|
5625
5984
|
}
|
|
5626
|
-
const now =
|
|
5985
|
+
const now = import_firestore20.Timestamp.now();
|
|
5627
5986
|
const expiresInDays = (data == null ? void 0 : data.expiresInDays) || 7;
|
|
5628
5987
|
const email = (data == null ? void 0 : data.email) || null;
|
|
5629
|
-
const expiresAt = new
|
|
5988
|
+
const expiresAt = new import_firestore20.Timestamp(
|
|
5630
5989
|
now.seconds + expiresInDays * 24 * 60 * 60,
|
|
5631
5990
|
now.nanoseconds
|
|
5632
5991
|
);
|
|
@@ -5660,7 +6019,7 @@ async function verifyAndUseAdminToken(db, groupId, token, userRef, app) {
|
|
|
5660
6019
|
if (adminToken.status !== "active" /* ACTIVE */) {
|
|
5661
6020
|
throw new Error("Admin token is not active");
|
|
5662
6021
|
}
|
|
5663
|
-
const now =
|
|
6022
|
+
const now = import_firestore20.Timestamp.now();
|
|
5664
6023
|
if (adminToken.expiresAt.seconds < now.seconds) {
|
|
5665
6024
|
const updatedTokens2 = group.adminTokens.map(
|
|
5666
6025
|
(t) => t.id === adminToken.id ? { ...t, status: "expired" /* EXPIRED */ } : t
|
|
@@ -5933,24 +6292,24 @@ var import_geofire_common7 = require("geofire-common");
|
|
|
5933
6292
|
var import_zod19 = require("zod");
|
|
5934
6293
|
|
|
5935
6294
|
// src/services/clinic/utils/clinic.utils.ts
|
|
5936
|
-
var
|
|
6295
|
+
var import_firestore21 = require("firebase/firestore");
|
|
5937
6296
|
var import_geofire_common4 = require("geofire-common");
|
|
5938
6297
|
var import_zod18 = require("zod");
|
|
5939
6298
|
async function getClinic(db, clinicId) {
|
|
5940
|
-
const docRef = (0,
|
|
5941
|
-
const docSnap = await (0,
|
|
6299
|
+
const docRef = (0, import_firestore21.doc)(db, CLINICS_COLLECTION, clinicId);
|
|
6300
|
+
const docSnap = await (0, import_firestore21.getDoc)(docRef);
|
|
5942
6301
|
if (docSnap.exists()) {
|
|
5943
6302
|
return docSnap.data();
|
|
5944
6303
|
}
|
|
5945
6304
|
return null;
|
|
5946
6305
|
}
|
|
5947
6306
|
async function getClinicsByGroup(db, groupId) {
|
|
5948
|
-
const q = (0,
|
|
5949
|
-
(0,
|
|
5950
|
-
(0,
|
|
5951
|
-
(0,
|
|
6307
|
+
const q = (0, import_firestore21.query)(
|
|
6308
|
+
(0, import_firestore21.collection)(db, CLINICS_COLLECTION),
|
|
6309
|
+
(0, import_firestore21.where)("clinicGroupId", "==", groupId),
|
|
6310
|
+
(0, import_firestore21.where)("isActive", "==", true)
|
|
5952
6311
|
);
|
|
5953
|
-
const querySnapshot = await (0,
|
|
6312
|
+
const querySnapshot = await (0, import_firestore21.getDocs)(q);
|
|
5954
6313
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
5955
6314
|
}
|
|
5956
6315
|
async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
|
|
@@ -6106,11 +6465,11 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
|
|
|
6106
6465
|
}
|
|
6107
6466
|
updatedData = {
|
|
6108
6467
|
...updatedData,
|
|
6109
|
-
updatedAt:
|
|
6468
|
+
updatedAt: import_firestore21.Timestamp.now()
|
|
6110
6469
|
};
|
|
6111
6470
|
console.log("[CLINIC] Updating clinic in Firestore");
|
|
6112
6471
|
try {
|
|
6113
|
-
await (0,
|
|
6472
|
+
await (0, import_firestore21.updateDoc)((0, import_firestore21.doc)(db, CLINICS_COLLECTION, clinicId), updatedData);
|
|
6114
6473
|
console.log("[CLINIC] Clinic updated successfully");
|
|
6115
6474
|
} catch (updateError) {
|
|
6116
6475
|
console.error("[CLINIC] Error updating clinic in Firestore:", updateError);
|
|
@@ -6139,12 +6498,12 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
|
|
|
6139
6498
|
if (clinicIds.length === 0) {
|
|
6140
6499
|
return [];
|
|
6141
6500
|
}
|
|
6142
|
-
const constraints = [(0,
|
|
6501
|
+
const constraints = [(0, import_firestore21.where)("id", "in", clinicIds)];
|
|
6143
6502
|
if (options.isActive !== void 0) {
|
|
6144
|
-
constraints.push((0,
|
|
6503
|
+
constraints.push((0, import_firestore21.where)("isActive", "==", options.isActive));
|
|
6145
6504
|
}
|
|
6146
|
-
const q = (0,
|
|
6147
|
-
const querySnapshot = await (0,
|
|
6505
|
+
const q = (0, import_firestore21.query)((0, import_firestore21.collection)(db, CLINICS_COLLECTION), ...constraints);
|
|
6506
|
+
const querySnapshot = await (0, import_firestore21.getDocs)(q);
|
|
6148
6507
|
return querySnapshot.docs.map((doc34) => doc34.data());
|
|
6149
6508
|
}
|
|
6150
6509
|
async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
|
|
@@ -6158,8 +6517,8 @@ async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGr
|
|
|
6158
6517
|
}
|
|
6159
6518
|
async function getClinicById(db, clinicId) {
|
|
6160
6519
|
try {
|
|
6161
|
-
const clinicRef = (0,
|
|
6162
|
-
const clinicSnapshot = await (0,
|
|
6520
|
+
const clinicRef = (0, import_firestore21.doc)(db, CLINICS_COLLECTION, clinicId);
|
|
6521
|
+
const clinicSnapshot = await (0, import_firestore21.getDoc)(clinicRef);
|
|
6163
6522
|
if (!clinicSnapshot.exists()) {
|
|
6164
6523
|
return null;
|
|
6165
6524
|
}
|
|
@@ -6175,20 +6534,20 @@ async function getClinicById(db, clinicId) {
|
|
|
6175
6534
|
}
|
|
6176
6535
|
async function getAllClinics(db, pagination, lastDoc) {
|
|
6177
6536
|
try {
|
|
6178
|
-
const clinicsCollection = (0,
|
|
6179
|
-
let clinicsQuery = (0,
|
|
6537
|
+
const clinicsCollection = (0, import_firestore21.collection)(db, CLINICS_COLLECTION);
|
|
6538
|
+
let clinicsQuery = (0, import_firestore21.query)(clinicsCollection);
|
|
6180
6539
|
if (pagination && pagination > 0) {
|
|
6181
6540
|
if (lastDoc) {
|
|
6182
|
-
clinicsQuery = (0,
|
|
6541
|
+
clinicsQuery = (0, import_firestore21.query)(
|
|
6183
6542
|
clinicsCollection,
|
|
6184
|
-
(0,
|
|
6185
|
-
(0,
|
|
6543
|
+
(0, import_firestore21.startAfter)(lastDoc),
|
|
6544
|
+
(0, import_firestore21.limit)(pagination)
|
|
6186
6545
|
);
|
|
6187
6546
|
} else {
|
|
6188
|
-
clinicsQuery = (0,
|
|
6547
|
+
clinicsQuery = (0, import_firestore21.query)(clinicsCollection, (0, import_firestore21.limit)(pagination));
|
|
6189
6548
|
}
|
|
6190
6549
|
}
|
|
6191
|
-
const clinicsSnapshot = await (0,
|
|
6550
|
+
const clinicsSnapshot = await (0, import_firestore21.getDocs)(clinicsQuery);
|
|
6192
6551
|
const lastVisible = clinicsSnapshot.docs[clinicsSnapshot.docs.length - 1];
|
|
6193
6552
|
const clinics = clinicsSnapshot.docs.map((doc34) => {
|
|
6194
6553
|
const data = doc34.data();
|
|
@@ -6215,12 +6574,12 @@ async function getAllClinicsInRange(db, center, rangeInKm, pagination, lastDoc)
|
|
|
6215
6574
|
let lastDocSnapshot = null;
|
|
6216
6575
|
for (const b of bounds) {
|
|
6217
6576
|
const constraints = [
|
|
6218
|
-
(0,
|
|
6219
|
-
(0,
|
|
6220
|
-
(0,
|
|
6577
|
+
(0, import_firestore21.where)("location.geohash", ">=", b[0]),
|
|
6578
|
+
(0, import_firestore21.where)("location.geohash", "<=", b[1]),
|
|
6579
|
+
(0, import_firestore21.where)("isActive", "==", true)
|
|
6221
6580
|
];
|
|
6222
|
-
const q = (0,
|
|
6223
|
-
const querySnapshot = await (0,
|
|
6581
|
+
const q = (0, import_firestore21.query)((0, import_firestore21.collection)(db, CLINICS_COLLECTION), ...constraints);
|
|
6582
|
+
const querySnapshot = await (0, import_firestore21.getDocs)(q);
|
|
6224
6583
|
for (const doc34 of querySnapshot.docs) {
|
|
6225
6584
|
const clinic = doc34.data();
|
|
6226
6585
|
const distance = (0, import_geofire_common4.distanceBetween)(
|
|
@@ -6315,7 +6674,7 @@ async function removeTags(db, clinicId, adminId, tagsToRemove, clinicAdminServic
|
|
|
6315
6674
|
}
|
|
6316
6675
|
|
|
6317
6676
|
// src/services/clinic/utils/search.utils.ts
|
|
6318
|
-
var
|
|
6677
|
+
var import_firestore22 = require("firebase/firestore");
|
|
6319
6678
|
var import_geofire_common5 = require("geofire-common");
|
|
6320
6679
|
async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
6321
6680
|
const bounds = (0, import_geofire_common5.geohashQueryBounds)(
|
|
@@ -6325,20 +6684,20 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
6325
6684
|
const matchingDocs = [];
|
|
6326
6685
|
for (const b of bounds) {
|
|
6327
6686
|
const constraints = [
|
|
6328
|
-
(0,
|
|
6329
|
-
(0,
|
|
6330
|
-
(0,
|
|
6687
|
+
(0, import_firestore22.where)("location.geohash", ">=", b[0]),
|
|
6688
|
+
(0, import_firestore22.where)("location.geohash", "<=", b[1]),
|
|
6689
|
+
(0, import_firestore22.where)("isActive", "==", true)
|
|
6331
6690
|
];
|
|
6332
6691
|
if (filters == null ? void 0 : filters.services) {
|
|
6333
6692
|
constraints.push(
|
|
6334
|
-
(0,
|
|
6693
|
+
(0, import_firestore22.where)("services", "array-contains-any", filters.services)
|
|
6335
6694
|
);
|
|
6336
6695
|
}
|
|
6337
6696
|
if ((filters == null ? void 0 : filters.tags) && filters.tags.length > 0) {
|
|
6338
|
-
constraints.push((0,
|
|
6697
|
+
constraints.push((0, import_firestore22.where)("tags", "array-contains-any", filters.tags));
|
|
6339
6698
|
}
|
|
6340
|
-
const q = (0,
|
|
6341
|
-
const querySnapshot = await (0,
|
|
6699
|
+
const q = (0, import_firestore22.query)((0, import_firestore22.collection)(db, CLINICS_COLLECTION), ...constraints);
|
|
6700
|
+
const querySnapshot = await (0, import_firestore22.getDocs)(q);
|
|
6342
6701
|
for (const doc34 of querySnapshot.docs) {
|
|
6343
6702
|
const clinic = doc34.data();
|
|
6344
6703
|
const distance = (0, import_geofire_common5.distanceBetween)(
|
|
@@ -6365,7 +6724,7 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
6365
6724
|
}
|
|
6366
6725
|
|
|
6367
6726
|
// src/services/clinic/utils/filter.utils.ts
|
|
6368
|
-
var
|
|
6727
|
+
var import_firestore23 = require("firebase/firestore");
|
|
6369
6728
|
var import_geofire_common6 = require("geofire-common");
|
|
6370
6729
|
async function getClinicsByFilters(db, filters) {
|
|
6371
6730
|
console.log(
|
|
@@ -6375,460 +6734,162 @@ async function getClinicsByFilters(db, filters) {
|
|
|
6375
6734
|
const isGeoQuery = filters.center && filters.radiusInKm && filters.radiusInKm > 0;
|
|
6376
6735
|
const constraints = [];
|
|
6377
6736
|
if (filters.isActive !== void 0) {
|
|
6378
|
-
constraints.push((0,
|
|
6737
|
+
constraints.push((0, import_firestore23.where)("isActive", "==", filters.isActive));
|
|
6379
6738
|
} else {
|
|
6380
|
-
constraints.push((0,
|
|
6739
|
+
constraints.push((0, import_firestore23.where)("isActive", "==", true));
|
|
6381
6740
|
}
|
|
6382
6741
|
if (filters.tags && filters.tags.length > 0) {
|
|
6383
|
-
constraints.push((0,
|
|
6742
|
+
constraints.push((0, import_firestore23.where)("tags", "array-contains", filters.tags[0]));
|
|
6384
6743
|
}
|
|
6385
6744
|
if (filters.procedureTechnology) {
|
|
6386
6745
|
constraints.push(
|
|
6387
|
-
(0,
|
|
6746
|
+
(0, import_firestore23.where)("servicesInfo.technology", "==", filters.procedureTechnology)
|
|
6388
6747
|
);
|
|
6389
6748
|
} else if (filters.procedureSubcategory) {
|
|
6390
6749
|
constraints.push(
|
|
6391
|
-
(0,
|
|
6750
|
+
(0, import_firestore23.where)("servicesInfo.subCategory", "==", filters.procedureSubcategory)
|
|
6392
6751
|
);
|
|
6393
6752
|
} else if (filters.procedureCategory) {
|
|
6394
6753
|
constraints.push(
|
|
6395
|
-
(0,
|
|
6754
|
+
(0, import_firestore23.where)("servicesInfo.category", "==", filters.procedureCategory)
|
|
6396
6755
|
);
|
|
6397
6756
|
} else if (filters.procedureFamily) {
|
|
6398
6757
|
constraints.push(
|
|
6399
|
-
(0,
|
|
6758
|
+
(0, import_firestore23.where)("servicesInfo.procedureFamily", "==", filters.procedureFamily)
|
|
6400
6759
|
);
|
|
6401
6760
|
}
|
|
6402
6761
|
if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
|
|
6403
|
-
constraints.push((0,
|
|
6404
|
-
constraints.push((0,
|
|
6762
|
+
constraints.push((0, import_firestore23.startAfter)(filters.lastDoc));
|
|
6763
|
+
constraints.push((0, import_firestore23.limit)(filters.pagination));
|
|
6405
6764
|
} else if (filters.pagination && filters.pagination > 0) {
|
|
6406
|
-
constraints.push((0,
|
|
6765
|
+
constraints.push((0, import_firestore23.limit)(filters.pagination));
|
|
6407
6766
|
}
|
|
6408
|
-
constraints.push((0,
|
|
6767
|
+
constraints.push((0, import_firestore23.orderBy)("location.geohash"));
|
|
6409
6768
|
let clinicsResult = [];
|
|
6410
6769
|
let lastVisibleDoc = null;
|
|
6411
6770
|
if (isGeoQuery) {
|
|
6412
6771
|
const center = filters.center;
|
|
6413
6772
|
const radiusInKm = filters.radiusInKm;
|
|
6414
6773
|
const bounds = (0, import_geofire_common6.geohashQueryBounds)(
|
|
6415
|
-
[center.latitude, center.longitude],
|
|
6416
|
-
radiusInKm * 1e3
|
|
6417
|
-
// Convert to meters
|
|
6418
|
-
);
|
|
6419
|
-
const matchingClinics = [];
|
|
6420
|
-
for (const bound of bounds) {
|
|
6421
|
-
const geoConstraints = [
|
|
6422
|
-
...constraints,
|
|
6423
|
-
(0, import_firestore21.where)("location.geohash", ">=", bound[0]),
|
|
6424
|
-
(0, import_firestore21.where)("location.geohash", "<=", bound[1])
|
|
6425
|
-
];
|
|
6426
|
-
const q = (0, import_firestore21.query)((0, import_firestore21.collection)(db, CLINICS_COLLECTION), ...geoConstraints);
|
|
6427
|
-
const querySnapshot = await (0, import_firestore21.getDocs)(q);
|
|
6428
|
-
console.log(
|
|
6429
|
-
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
6430
|
-
);
|
|
6431
|
-
for (const doc34 of querySnapshot.docs) {
|
|
6432
|
-
const clinic = { ...doc34.data(), id: doc34.id };
|
|
6433
|
-
const distance = (0, import_geofire_common6.distanceBetween)(
|
|
6434
|
-
[center.latitude, center.longitude],
|
|
6435
|
-
[clinic.location.latitude, clinic.location.longitude]
|
|
6436
|
-
);
|
|
6437
|
-
const distanceInKm = distance / 1e3;
|
|
6438
|
-
if (distanceInKm <= radiusInKm) {
|
|
6439
|
-
matchingClinics.push({
|
|
6440
|
-
...clinic,
|
|
6441
|
-
distance: distanceInKm
|
|
6442
|
-
});
|
|
6443
|
-
}
|
|
6444
|
-
}
|
|
6445
|
-
}
|
|
6446
|
-
let filteredClinics = matchingClinics;
|
|
6447
|
-
if (filters.tags && filters.tags.length > 1) {
|
|
6448
|
-
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6449
|
-
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6450
|
-
});
|
|
6451
|
-
}
|
|
6452
|
-
if (filters.minRating !== void 0) {
|
|
6453
|
-
filteredClinics = filteredClinics.filter(
|
|
6454
|
-
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6455
|
-
);
|
|
6456
|
-
}
|
|
6457
|
-
if (filters.maxRating !== void 0) {
|
|
6458
|
-
filteredClinics = filteredClinics.filter(
|
|
6459
|
-
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6460
|
-
);
|
|
6461
|
-
}
|
|
6462
|
-
filteredClinics.sort((a, b) => a.distance - b.distance);
|
|
6463
|
-
if (filters.pagination && filters.pagination > 0) {
|
|
6464
|
-
let startIndex = 0;
|
|
6465
|
-
if (filters.lastDoc) {
|
|
6466
|
-
const lastDocIndex = filteredClinics.findIndex(
|
|
6467
|
-
(clinic) => clinic.id === filters.lastDoc.id
|
|
6468
|
-
);
|
|
6469
|
-
if (lastDocIndex !== -1) {
|
|
6470
|
-
startIndex = lastDocIndex + 1;
|
|
6471
|
-
}
|
|
6472
|
-
}
|
|
6473
|
-
const paginatedClinics = filteredClinics.slice(
|
|
6474
|
-
startIndex,
|
|
6475
|
-
startIndex + filters.pagination
|
|
6476
|
-
);
|
|
6477
|
-
lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
|
|
6478
|
-
clinicsResult = paginatedClinics;
|
|
6479
|
-
} else {
|
|
6480
|
-
clinicsResult = filteredClinics;
|
|
6481
|
-
}
|
|
6482
|
-
} else {
|
|
6483
|
-
const q = (0, import_firestore21.query)((0, import_firestore21.collection)(db, CLINICS_COLLECTION), ...constraints);
|
|
6484
|
-
const querySnapshot = await (0, import_firestore21.getDocs)(q);
|
|
6485
|
-
console.log(
|
|
6486
|
-
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
6487
|
-
);
|
|
6488
|
-
const clinics = querySnapshot.docs.map((doc34) => {
|
|
6489
|
-
return { ...doc34.data(), id: doc34.id };
|
|
6490
|
-
});
|
|
6491
|
-
let filteredClinics = clinics;
|
|
6492
|
-
if (filters.center) {
|
|
6493
|
-
const center = filters.center;
|
|
6494
|
-
const clinicsWithDistance = [];
|
|
6495
|
-
filteredClinics.forEach((clinic) => {
|
|
6496
|
-
const distance = (0, import_geofire_common6.distanceBetween)(
|
|
6497
|
-
[center.latitude, center.longitude],
|
|
6498
|
-
[clinic.location.latitude, clinic.location.longitude]
|
|
6499
|
-
);
|
|
6500
|
-
clinicsWithDistance.push({
|
|
6501
|
-
...clinic,
|
|
6502
|
-
distance: distance / 1e3
|
|
6503
|
-
// Convert to kilometers
|
|
6504
|
-
});
|
|
6505
|
-
});
|
|
6506
|
-
filteredClinics = clinicsWithDistance;
|
|
6507
|
-
filteredClinics.sort(
|
|
6508
|
-
(a, b) => a.distance - b.distance
|
|
6509
|
-
);
|
|
6510
|
-
}
|
|
6511
|
-
if (filters.tags && filters.tags.length > 1) {
|
|
6512
|
-
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6513
|
-
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6514
|
-
});
|
|
6515
|
-
}
|
|
6516
|
-
if (filters.minRating !== void 0) {
|
|
6517
|
-
filteredClinics = filteredClinics.filter(
|
|
6518
|
-
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6519
|
-
);
|
|
6520
|
-
}
|
|
6521
|
-
if (filters.maxRating !== void 0) {
|
|
6522
|
-
filteredClinics = filteredClinics.filter(
|
|
6523
|
-
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6524
|
-
);
|
|
6525
|
-
}
|
|
6526
|
-
lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
6527
|
-
clinicsResult = filteredClinics;
|
|
6528
|
-
}
|
|
6529
|
-
return {
|
|
6530
|
-
clinics: clinicsResult,
|
|
6531
|
-
lastDoc: lastVisibleDoc
|
|
6532
|
-
};
|
|
6533
|
-
}
|
|
6534
|
-
|
|
6535
|
-
// src/services/media/media.service.ts
|
|
6536
|
-
var import_firestore22 = require("firebase/firestore");
|
|
6537
|
-
var import_storage5 = require("firebase/storage");
|
|
6538
|
-
var import_firestore23 = require("firebase/firestore");
|
|
6539
|
-
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
6540
|
-
MediaAccessLevel2["PUBLIC"] = "public";
|
|
6541
|
-
MediaAccessLevel2["PRIVATE"] = "private";
|
|
6542
|
-
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
6543
|
-
return MediaAccessLevel2;
|
|
6544
|
-
})(MediaAccessLevel || {});
|
|
6545
|
-
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
6546
|
-
var MediaService = class extends BaseService {
|
|
6547
|
-
constructor(db, auth, app) {
|
|
6548
|
-
super(db, auth, app);
|
|
6549
|
-
}
|
|
6550
|
-
/**
|
|
6551
|
-
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
6552
|
-
* @param file - The file to upload.
|
|
6553
|
-
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
6554
|
-
* @param accessLevel - Access level (public, private, confidential).
|
|
6555
|
-
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
6556
|
-
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
6557
|
-
* @returns Promise with the media metadata.
|
|
6558
|
-
*/
|
|
6559
|
-
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
6560
|
-
const mediaId = this.generateId();
|
|
6561
|
-
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
6562
|
-
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
6563
|
-
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
6564
|
-
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
6565
|
-
const storageRef = (0, import_storage5.ref)(this.storage, filePath);
|
|
6566
|
-
try {
|
|
6567
|
-
const uploadResult = await (0, import_storage5.uploadBytes)(storageRef, file, {
|
|
6568
|
-
contentType: file.type
|
|
6569
|
-
});
|
|
6570
|
-
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
6571
|
-
const downloadURL = await (0, import_storage5.getDownloadURL)(uploadResult.ref);
|
|
6572
|
-
console.log("[MediaService] Got download URL:", downloadURL);
|
|
6573
|
-
const metadata = {
|
|
6574
|
-
id: mediaId,
|
|
6575
|
-
name: fileNameToUse,
|
|
6576
|
-
url: downloadURL,
|
|
6577
|
-
contentType: file.type,
|
|
6578
|
-
size: file.size,
|
|
6579
|
-
createdAt: import_firestore22.Timestamp.now(),
|
|
6580
|
-
accessLevel,
|
|
6581
|
-
ownerId,
|
|
6582
|
-
collectionName,
|
|
6583
|
-
path: filePath
|
|
6584
|
-
};
|
|
6585
|
-
const metadataDocRef = (0, import_firestore23.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6586
|
-
await (0, import_firestore23.setDoc)(metadataDocRef, metadata);
|
|
6587
|
-
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
6588
|
-
return metadata;
|
|
6589
|
-
} catch (error) {
|
|
6590
|
-
console.error("[MediaService] Error during media upload:", error);
|
|
6591
|
-
throw error;
|
|
6592
|
-
}
|
|
6593
|
-
}
|
|
6594
|
-
/**
|
|
6595
|
-
* Get media metadata from Firestore by its ID.
|
|
6596
|
-
* @param mediaId - ID of the media.
|
|
6597
|
-
* @returns Promise with the media metadata or null if not found.
|
|
6598
|
-
*/
|
|
6599
|
-
async getMediaMetadata(mediaId) {
|
|
6600
|
-
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
6601
|
-
const docRef = (0, import_firestore23.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6602
|
-
const docSnap = await (0, import_firestore23.getDoc)(docRef);
|
|
6603
|
-
if (docSnap.exists()) {
|
|
6604
|
-
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
6605
|
-
return docSnap.data();
|
|
6606
|
-
}
|
|
6607
|
-
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
6608
|
-
return null;
|
|
6609
|
-
}
|
|
6610
|
-
/**
|
|
6611
|
-
* Get media metadata from Firestore by its public URL.
|
|
6612
|
-
* @param url - The public URL of the media file.
|
|
6613
|
-
* @returns Promise with the media metadata or null if not found.
|
|
6614
|
-
*/
|
|
6615
|
-
async getMediaMetadataByUrl(url) {
|
|
6616
|
-
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
6617
|
-
const q = (0, import_firestore23.query)(
|
|
6618
|
-
(0, import_firestore23.collection)(this.db, MEDIA_METADATA_COLLECTION),
|
|
6619
|
-
(0, import_firestore23.where)("url", "==", url),
|
|
6620
|
-
(0, import_firestore23.limit)(1)
|
|
6774
|
+
[center.latitude, center.longitude],
|
|
6775
|
+
radiusInKm * 1e3
|
|
6776
|
+
// Convert to meters
|
|
6621
6777
|
);
|
|
6622
|
-
|
|
6778
|
+
const matchingClinics = [];
|
|
6779
|
+
for (const bound of bounds) {
|
|
6780
|
+
const geoConstraints = [
|
|
6781
|
+
...constraints,
|
|
6782
|
+
(0, import_firestore23.where)("location.geohash", ">=", bound[0]),
|
|
6783
|
+
(0, import_firestore23.where)("location.geohash", "<=", bound[1])
|
|
6784
|
+
];
|
|
6785
|
+
const q = (0, import_firestore23.query)((0, import_firestore23.collection)(db, CLINICS_COLLECTION), ...geoConstraints);
|
|
6623
6786
|
const querySnapshot = await (0, import_firestore23.getDocs)(q);
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6787
|
+
console.log(
|
|
6788
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
6789
|
+
);
|
|
6790
|
+
for (const doc34 of querySnapshot.docs) {
|
|
6791
|
+
const clinic = { ...doc34.data(), id: doc34.id };
|
|
6792
|
+
const distance = (0, import_geofire_common6.distanceBetween)(
|
|
6793
|
+
[center.latitude, center.longitude],
|
|
6794
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
6795
|
+
);
|
|
6796
|
+
const distanceInKm = distance / 1e3;
|
|
6797
|
+
if (distanceInKm <= radiusInKm) {
|
|
6798
|
+
matchingClinics.push({
|
|
6799
|
+
...clinic,
|
|
6800
|
+
distance: distanceInKm
|
|
6801
|
+
});
|
|
6802
|
+
}
|
|
6628
6803
|
}
|
|
6629
|
-
console.log("[MediaService] No metadata found for URL:", url);
|
|
6630
|
-
return null;
|
|
6631
|
-
} catch (error) {
|
|
6632
|
-
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
6633
|
-
throw error;
|
|
6634
6804
|
}
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
async deleteMedia(mediaId) {
|
|
6641
|
-
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
6642
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6643
|
-
if (!metadata) {
|
|
6644
|
-
console.warn(
|
|
6645
|
-
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
6646
|
-
);
|
|
6647
|
-
return;
|
|
6805
|
+
let filteredClinics = matchingClinics;
|
|
6806
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
6807
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6808
|
+
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6809
|
+
});
|
|
6648
6810
|
}
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
6653
|
-
const metadataDocRef = (0, import_firestore23.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6654
|
-
await (0, import_firestore23.deleteDoc)(metadataDocRef);
|
|
6655
|
-
console.log(
|
|
6656
|
-
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
6811
|
+
if (filters.minRating !== void 0) {
|
|
6812
|
+
filteredClinics = filteredClinics.filter(
|
|
6813
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6657
6814
|
);
|
|
6658
|
-
} catch (error) {
|
|
6659
|
-
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
6660
|
-
throw error;
|
|
6661
6815
|
}
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
* to a new path reflecting the new access level, and updating its metadata.
|
|
6666
|
-
* @param mediaId - ID of the media to update.
|
|
6667
|
-
* @param newAccessLevel - New access level.
|
|
6668
|
-
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
6669
|
-
*/
|
|
6670
|
-
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
6671
|
-
var _a;
|
|
6672
|
-
console.log(
|
|
6673
|
-
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
6674
|
-
);
|
|
6675
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6676
|
-
if (!metadata) {
|
|
6677
|
-
console.warn(
|
|
6678
|
-
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
6816
|
+
if (filters.maxRating !== void 0) {
|
|
6817
|
+
filteredClinics = filteredClinics.filter(
|
|
6818
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6679
6819
|
);
|
|
6680
|
-
return null;
|
|
6681
6820
|
}
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
)
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
await (0, import_firestore23.updateDoc)(metadataDocRef, { updatedAt: import_firestore22.Timestamp.now() });
|
|
6689
|
-
return { ...metadata, updatedAt: import_firestore22.Timestamp.now() };
|
|
6690
|
-
} catch (error) {
|
|
6691
|
-
console.error(
|
|
6692
|
-
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
6693
|
-
error
|
|
6821
|
+
filteredClinics.sort((a, b) => a.distance - b.distance);
|
|
6822
|
+
if (filters.pagination && filters.pagination > 0) {
|
|
6823
|
+
let startIndex = 0;
|
|
6824
|
+
if (filters.lastDoc) {
|
|
6825
|
+
const lastDocIndex = filteredClinics.findIndex(
|
|
6826
|
+
(clinic) => clinic.id === filters.lastDoc.id
|
|
6694
6827
|
);
|
|
6695
|
-
|
|
6828
|
+
if (lastDocIndex !== -1) {
|
|
6829
|
+
startIndex = lastDocIndex + 1;
|
|
6830
|
+
}
|
|
6696
6831
|
}
|
|
6832
|
+
const paginatedClinics = filteredClinics.slice(
|
|
6833
|
+
startIndex,
|
|
6834
|
+
startIndex + filters.pagination
|
|
6835
|
+
);
|
|
6836
|
+
lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
|
|
6837
|
+
clinicsResult = paginatedClinics;
|
|
6838
|
+
} else {
|
|
6839
|
+
clinicsResult = filteredClinics;
|
|
6697
6840
|
}
|
|
6698
|
-
|
|
6699
|
-
const
|
|
6700
|
-
const
|
|
6841
|
+
} else {
|
|
6842
|
+
const q = (0, import_firestore23.query)((0, import_firestore23.collection)(db, CLINICS_COLLECTION), ...constraints);
|
|
6843
|
+
const querySnapshot = await (0, import_firestore23.getDocs)(q);
|
|
6701
6844
|
console.log(
|
|
6702
|
-
`[
|
|
6845
|
+
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
6703
6846
|
);
|
|
6704
|
-
const
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
)
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
});
|
|
6716
|
-
console.log(
|
|
6717
|
-
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
6718
|
-
);
|
|
6719
|
-
const newDownloadURL = await (0, import_storage5.getDownloadURL)(newStorageFileRef);
|
|
6720
|
-
console.log(
|
|
6721
|
-
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
6722
|
-
);
|
|
6723
|
-
const updateData = {
|
|
6724
|
-
accessLevel: newAccessLevel,
|
|
6725
|
-
path: newStoragePath,
|
|
6726
|
-
url: newDownloadURL,
|
|
6727
|
-
updatedAt: import_firestore22.Timestamp.now()
|
|
6728
|
-
};
|
|
6729
|
-
const metadataDocRef = (0, import_firestore23.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
6730
|
-
console.log(
|
|
6731
|
-
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
6732
|
-
updateData
|
|
6733
|
-
);
|
|
6734
|
-
await (0, import_firestore23.updateDoc)(metadataDocRef, updateData);
|
|
6735
|
-
console.log(
|
|
6736
|
-
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
6737
|
-
);
|
|
6738
|
-
try {
|
|
6739
|
-
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
6740
|
-
await (0, import_storage5.deleteObject)(oldStorageFileRef);
|
|
6741
|
-
console.log(
|
|
6742
|
-
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
6743
|
-
);
|
|
6744
|
-
} catch (deleteError) {
|
|
6745
|
-
console.error(
|
|
6746
|
-
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
6747
|
-
deleteError
|
|
6847
|
+
const clinics = querySnapshot.docs.map((doc34) => {
|
|
6848
|
+
return { ...doc34.data(), id: doc34.id };
|
|
6849
|
+
});
|
|
6850
|
+
let filteredClinics = clinics;
|
|
6851
|
+
if (filters.center) {
|
|
6852
|
+
const center = filters.center;
|
|
6853
|
+
const clinicsWithDistance = [];
|
|
6854
|
+
filteredClinics.forEach((clinic) => {
|
|
6855
|
+
const distance = (0, import_geofire_common6.distanceBetween)(
|
|
6856
|
+
[center.latitude, center.longitude],
|
|
6857
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
6748
6858
|
);
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6859
|
+
clinicsWithDistance.push({
|
|
6860
|
+
...clinic,
|
|
6861
|
+
distance: distance / 1e3
|
|
6862
|
+
// Convert to kilometers
|
|
6863
|
+
});
|
|
6864
|
+
});
|
|
6865
|
+
filteredClinics = clinicsWithDistance;
|
|
6866
|
+
filteredClinics.sort(
|
|
6867
|
+
(a, b) => a.distance - b.distance
|
|
6755
6868
|
);
|
|
6756
|
-
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
6757
|
-
console.warn(
|
|
6758
|
-
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
6759
|
-
);
|
|
6760
|
-
try {
|
|
6761
|
-
await (0, import_storage5.deleteObject)(newStorageFileRef);
|
|
6762
|
-
console.warn(
|
|
6763
|
-
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
6764
|
-
);
|
|
6765
|
-
} catch (cleanupError) {
|
|
6766
|
-
console.error(
|
|
6767
|
-
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
6768
|
-
cleanupError
|
|
6769
|
-
);
|
|
6770
|
-
}
|
|
6771
|
-
}
|
|
6772
|
-
throw error;
|
|
6773
|
-
}
|
|
6774
|
-
}
|
|
6775
|
-
/**
|
|
6776
|
-
* List all media for an owner, optionally filtered by collection and access level.
|
|
6777
|
-
* @param ownerId - ID of the owner.
|
|
6778
|
-
* @param collectionName - Optional: Filter by collection name.
|
|
6779
|
-
* @param accessLevel - Optional: Filter by access level.
|
|
6780
|
-
* @param count - Optional: Number of items to fetch.
|
|
6781
|
-
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
6782
|
-
*/
|
|
6783
|
-
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
6784
|
-
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
6785
|
-
let qConstraints = [(0, import_firestore23.where)("ownerId", "==", ownerId)];
|
|
6786
|
-
if (collectionName) {
|
|
6787
|
-
qConstraints.push((0, import_firestore23.where)("collectionName", "==", collectionName));
|
|
6788
|
-
}
|
|
6789
|
-
if (accessLevel) {
|
|
6790
|
-
qConstraints.push((0, import_firestore23.where)("accessLevel", "==", accessLevel));
|
|
6791
6869
|
}
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
if (startAfterId) {
|
|
6797
|
-
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
6798
|
-
if (startAfterDoc) {
|
|
6799
|
-
}
|
|
6870
|
+
if (filters.tags && filters.tags.length > 1) {
|
|
6871
|
+
filteredClinics = filteredClinics.filter((clinic) => {
|
|
6872
|
+
return filters.tags.every((tag) => clinic.tags.includes(tag));
|
|
6873
|
+
});
|
|
6800
6874
|
}
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
);
|
|
6805
|
-
try {
|
|
6806
|
-
const querySnapshot = await (0, import_firestore23.getDocs)(finalQuery);
|
|
6807
|
-
const mediaList = querySnapshot.docs.map(
|
|
6808
|
-
(doc34) => doc34.data()
|
|
6875
|
+
if (filters.minRating !== void 0) {
|
|
6876
|
+
filteredClinics = filteredClinics.filter(
|
|
6877
|
+
(clinic) => clinic.reviewInfo.averageRating >= filters.minRating
|
|
6809
6878
|
);
|
|
6810
|
-
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
6811
|
-
return mediaList;
|
|
6812
|
-
} catch (error) {
|
|
6813
|
-
console.error("[MediaService] Error listing media:", error);
|
|
6814
|
-
throw error;
|
|
6815
6879
|
}
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
*/
|
|
6821
|
-
async getMediaDownloadUrl(mediaId) {
|
|
6822
|
-
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
6823
|
-
const metadata = await this.getMediaMetadata(mediaId);
|
|
6824
|
-
if (metadata && metadata.url) {
|
|
6825
|
-
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
6826
|
-
return metadata.url;
|
|
6880
|
+
if (filters.maxRating !== void 0) {
|
|
6881
|
+
filteredClinics = filteredClinics.filter(
|
|
6882
|
+
(clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
|
|
6883
|
+
);
|
|
6827
6884
|
}
|
|
6828
|
-
|
|
6829
|
-
|
|
6885
|
+
lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
6886
|
+
clinicsResult = filteredClinics;
|
|
6830
6887
|
}
|
|
6831
|
-
|
|
6888
|
+
return {
|
|
6889
|
+
clinics: clinicsResult,
|
|
6890
|
+
lastDoc: lastVisibleDoc
|
|
6891
|
+
};
|
|
6892
|
+
}
|
|
6832
6893
|
|
|
6833
6894
|
// src/services/clinic/clinic.service.ts
|
|
6834
6895
|
var ClinicService = class extends BaseService {
|
|
@@ -8533,7 +8594,8 @@ var ProcedureService = class extends BaseService {
|
|
|
8533
8594
|
id: practitionerSnapshot.id,
|
|
8534
8595
|
name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
|
|
8535
8596
|
description: practitioner.basicInfo.bio || "",
|
|
8536
|
-
photo: practitioner.basicInfo.profileImageUrl
|
|
8597
|
+
photo: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : "",
|
|
8598
|
+
// Default to empty string if not a processed URL
|
|
8537
8599
|
rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
|
|
8538
8600
|
services: practitioner.procedures || []
|
|
8539
8601
|
};
|
|
@@ -8667,7 +8729,8 @@ var ProcedureService = class extends BaseService {
|
|
|
8667
8729
|
id: newPractitioner.id,
|
|
8668
8730
|
name: `${newPractitioner.basicInfo.firstName} ${newPractitioner.basicInfo.lastName}`,
|
|
8669
8731
|
description: newPractitioner.basicInfo.bio || "",
|
|
8670
|
-
photo: newPractitioner.basicInfo.profileImageUrl
|
|
8732
|
+
photo: typeof newPractitioner.basicInfo.profileImageUrl === "string" ? newPractitioner.basicInfo.profileImageUrl : "",
|
|
8733
|
+
// Default to empty string if not a processed URL
|
|
8671
8734
|
rating: ((_a = newPractitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
|
|
8672
8735
|
services: newPractitioner.procedures || []
|
|
8673
8736
|
};
|