@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/index.js CHANGED
@@ -1096,7 +1096,7 @@ var BaseService = class {
1096
1096
  };
1097
1097
 
1098
1098
  // src/services/user.service.ts
1099
- var import_firestore17 = require("firebase/firestore");
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 import_firestore16 = require("firebase/firestore");
4063
+ var import_firestore18 = require("firebase/firestore");
4064
4064
 
4065
- // src/validations/practitioner.schema.ts
4066
- var import_zod14 = require("zod");
4065
+ // src/services/media/media.service.ts
4067
4066
  var import_firestore15 = require("firebase/firestore");
4068
-
4069
- // src/backoffice/types/static/certification.types.ts
4070
- var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
4071
- CertificationLevel2["AESTHETICIAN"] = "aesthetician";
4072
- CertificationLevel2["NURSE_ASSISTANT"] = "nurse_assistant";
4073
- CertificationLevel2["NURSE"] = "nurse";
4074
- CertificationLevel2["NURSE_PRACTITIONER"] = "nurse_practitioner";
4075
- CertificationLevel2["PHYSICIAN_ASSISTANT"] = "physician_assistant";
4076
- CertificationLevel2["DOCTOR"] = "doctor";
4077
- CertificationLevel2["SPECIALIST"] = "specialist";
4078
- CertificationLevel2["PLASTIC_SURGEON"] = "plastic_surgeon";
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
- * Creates a new practitioner
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 createPractitioner(data) {
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 validData = createPractitionerSchema.parse(data);
4245
- const practitionerId = this.generateId();
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
- const practitionerRef = (0, import_firestore16.doc)(
4279
- this.db,
4280
- PRACTITIONERS_COLLECTION,
4281
- practitionerId
4282
- );
4283
- await (0, import_firestore16.setDoc)(practitionerRef, practitioner);
4284
- const createdPractitioner = await this.getPractitioner(practitionerId);
4285
- if (!createdPractitioner) {
4286
- throw new Error(
4287
- `Failed to retrieve created practitioner ${practitionerId}`
4288
- );
4289
- }
4290
- return createdPractitioner;
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
- if (error instanceof import_zod15.z.ZodError) {
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
- * Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
4301
- * Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
4302
- * @param data Podaci za kreiranje draft profila
4303
- * @param createdBy ID administratora koji kreira profil
4304
- * @param clinicId ID klinike za koju se kreira profil
4305
- * @returns Objekt koji sadrži kreirani draft profil i token za registraciju
4306
- */
4307
- async createDraftPractitioner(data, createdBy, clinicId) {
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 validatedData = createDraftPractitionerSchema.parse(data);
4310
- const clinic = await this.getClinicService().getClinic(clinicId);
4311
- if (!clinic) {
4312
- throw new Error(`Clinic ${clinicId} not found`);
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
- const tokenString = this.generateId().slice(0, 6).toUpperCase();
4388
- const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
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
- if (error instanceof import_zod15.z.ZodError) {
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
- * Creates a token for inviting practitioner to claim their profile
4413
- * @param data Data for creating token
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 createPractitionerToken(data, createdBy) {
4418
- try {
4419
- const validatedData = createPractitionerTokenSchema.parse(data);
4420
- const practitioner = await this.getPractitioner(
4421
- validatedData.practitionerId
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
- if (!practitioner) {
4424
- throw new Error("Practitioner not found");
4425
- }
4426
- if (practitioner.status !== "draft" /* DRAFT */) {
4427
- throw new Error(
4428
- "Can only create tokens for practitioners in DRAFT status"
4429
- );
4430
- }
4431
- const clinic = await this.getClinicService().getClinic(
4432
- validatedData.clinicId
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
- if (error instanceof import_zod15.z.ZodError) {
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
- * Gets active tokens for a practitioner
4466
- * @param practitionerId ID of the practitioner
4467
- * @returns Array of active tokens
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 getPractitionerActiveTokens(practitionerId) {
4470
- const tokensRef = (0, import_firestore16.collection)(
4471
- this.db,
4472
- `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
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 querySnapshot = await (0, import_firestore16.getDocs)(q);
4480
- return querySnapshot.docs.map((doc34) => doc34.data());
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
- `[PRACTITIONER] Validating token for practitioner ${practitionerId}`,
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
- const tokenSnapshot = await (0, import_firestore16.getDocs)(q);
4511
- console.log(
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
- `[PRACTITIONER] Error validating token for practitioner ${practitionerId}:`,
4222
+ `[MediaService] Error updating timestamp for media ID ${mediaId}:`,
4529
4223
  error
4530
4224
  );
4531
4225
  throw error;
4532
4226
  }
4533
4227
  }
4534
- return null;
4535
- }
4536
- /**
4537
- * Marks a token as used
4538
- * @param tokenId ID of the token
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
- await (0, import_firestore16.updateDoc)(tokenRef, {
4548
- status: "used" /* USED */,
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
- const validData = data;
4622
- const practitionerRef = (0, import_firestore16.doc)(
4623
- this.db,
4624
- PRACTITIONERS_COLLECTION,
4625
- practitionerId
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
- ...validData,
4634
- updatedAt: (0, import_firestore16.serverTimestamp)()
4254
+ accessLevel: newAccessLevel,
4255
+ path: newStoragePath,
4256
+ url: newDownloadURL,
4257
+ updatedAt: import_firestore15.Timestamp.now()
4635
4258
  };
4636
- await (0, import_firestore16.updateDoc)(practitionerRef, updateData);
4637
- const updatedPractitioner = await this.getPractitioner(practitionerId);
4638
- if (!updatedPractitioner) {
4639
- throw new Error(
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
- const practitionerDoc = await (0, import_firestore16.getDoc)(practitionerRef);
4664
- if (!practitionerDoc.exists()) {
4665
- throw new Error(`Practitioner ${practitionerId} not found`);
4666
- }
4667
- const practitioner = practitionerDoc.data();
4668
- if ((_a = practitioner.clinics) == null ? void 0 : _a.includes(clinicId)) {
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
- `Clinic ${clinicId} already added to practitioner ${practitionerId}`
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
- await (0, import_firestore16.updateDoc)(practitionerRef, {
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 adding clinic ${clinicId} to practitioner ${practitionerId}:`,
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
- * Removes a clinic from a practitioner
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 removeClinic(practitionerId, clinicId) {
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 practitionerRef = (0, import_firestore16.doc)(
4692
- this.db,
4693
- PRACTITIONERS_COLLECTION,
4694
- practitionerId
4336
+ const querySnapshot = await (0, import_firestore16.getDocs)(finalQuery);
4337
+ const mediaList = querySnapshot.docs.map(
4338
+ (doc34) => doc34.data()
4695
4339
  );
4696
- const practitionerDoc = await (0, import_firestore16.getDoc)(practitionerRef);
4697
- if (!practitionerDoc.exists()) {
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
- * Deaktivira profil zdravstvenog radnika
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 deletePractitioner(practitionerId) {
4732
- const practitioner = await this.getPractitioner(practitionerId);
4733
- if (!practitioner) {
4734
- throw new Error("Practitioner not found");
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
- await (0, import_firestore16.deleteDoc)((0, import_firestore16.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId));
4358
+ console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
4359
+ return null;
4737
4360
  }
4738
- /**
4739
- * Validates a registration token and claims the associated draft practitioner profile
4740
- * @param tokenString The token provided by the practitioner
4741
- * @param userId The ID of the user claiming the profile
4742
- * @returns The claimed practitioner profile or null if token is invalid
4743
- */
4744
- async validateTokenAndClaimProfile(tokenString, userId) {
4745
- console.log("[PRACTITIONER] Validating token for claiming profile", {
4746
- tokenString,
4747
- userId
4748
- });
4749
- const token = await this.validateToken(tokenString);
4750
- if (!token) {
4751
- console.log(
4752
- "[PRACTITIONER] Token validation failed - token not found or not valid",
4753
- {
4754
- tokenString
4755
- }
4756
- );
4757
- return null;
4758
- }
4759
- console.log("[PRACTITIONER] Token successfully validated", {
4760
- tokenId: token.id,
4761
- practitionerId: token.practitionerId
4762
- });
4763
- const practitioner = await this.getPractitioner(token.practitionerId);
4764
- if (!practitioner) {
4765
- console.log("[PRACTITIONER] Practitioner not found", {
4766
- practitionerId: token.practitionerId
4767
- });
4768
- return null;
4769
- }
4770
- if (practitioner.status !== "draft" /* DRAFT */) {
4771
- console.log("[PRACTITIONER] Practitioner status is not DRAFT", {
4772
- practitionerId: practitioner.id,
4773
- status: practitioner.status
4774
- });
4775
- throw new Error("This practitioner profile has already been claimed");
4776
- }
4777
- const existingPractitioner = await this.getPractitionerByUserRef(userId);
4778
- if (existingPractitioner) {
4779
- throw new Error("User already has a practitioner profile");
4780
- }
4781
- const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
4782
- userRef: userId,
4783
- status: "active" /* ACTIVE */
4784
- });
4785
- await this.markTokenAsUsed(token.id, token.practitionerId, userId);
4786
- console.log("[PRACTITIONER] Profile claimed successfully", {
4787
- practitionerId: updatedPractitioner.id,
4788
- userId
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, import_firestore16.where)("status", "==", "active" /* ACTIVE */));
5164
+ constraints.push((0, import_firestore18.where)("status", "==", "active" /* ACTIVE */));
4806
5165
  }
4807
- constraints.push((0, import_firestore16.orderBy)("basicInfo.lastName", "asc"));
4808
- constraints.push((0, import_firestore16.orderBy)("basicInfo.firstName", "asc"));
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, import_firestore16.startAfter)(options.lastDoc));
5170
+ constraints.push((0, import_firestore18.startAfter)(options.lastDoc));
4812
5171
  }
4813
- constraints.push((0, import_firestore16.limit)(options.pagination));
5172
+ constraints.push((0, import_firestore18.limit)(options.pagination));
4814
5173
  }
4815
- const q = (0, import_firestore16.query)(
4816
- (0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
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, import_firestore16.getDocs)(q);
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, import_firestore16.where)("status", "==", "active" /* ACTIVE */));
5223
+ constraints.push((0, import_firestore18.where)("status", "==", "active" /* ACTIVE */));
4865
5224
  }
4866
- constraints.push((0, import_firestore16.where)("isActive", "==", true));
5225
+ constraints.push((0, import_firestore18.where)("isActive", "==", true));
4867
5226
  if (filters.certifications && filters.certifications.length > 0) {
4868
5227
  constraints.push(
4869
- (0, import_firestore16.where)(
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, import_firestore16.orderBy)("basicInfo.lastName", "asc"));
4877
- constraints.push((0, import_firestore16.orderBy)("basicInfo.firstName", "asc"));
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, import_firestore16.startAfter)(filters.lastDoc));
5239
+ constraints.push((0, import_firestore18.startAfter)(filters.lastDoc));
4881
5240
  }
4882
- constraints.push((0, import_firestore16.limit)(filters.pagination));
5241
+ constraints.push((0, import_firestore18.limit)(filters.pagination));
4883
5242
  }
4884
- const q = (0, import_firestore16.query)(
4885
- (0, import_firestore16.collection)(this.db, PRACTITIONERS_COLLECTION),
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, import_firestore16.getDocs)(q);
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, import_firestore17.serverTimestamp)(),
5011
- updatedAt: (0, import_firestore17.serverTimestamp)(),
5012
- lastLoginAt: (0, import_firestore17.serverTimestamp)()
5369
+ createdAt: (0, import_firestore19.serverTimestamp)(),
5370
+ updatedAt: (0, import_firestore19.serverTimestamp)(),
5371
+ lastLoginAt: (0, import_firestore19.serverTimestamp)()
5013
5372
  };
5014
- await (0, import_firestore17.setDoc)((0, import_firestore17.doc)(this.db, USERS_COLLECTION, userData.uid), userData);
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, import_firestore17.updateDoc)((0, import_firestore17.doc)(this.db, USERS_COLLECTION, userData.uid), profiles);
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: import_firestore17.Timestamp.now(),
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: import_firestore17.Timestamp.now(),
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, import_firestore17.getDoc)((0, import_firestore17.doc)(this.db, USERS_COLLECTION, uid));
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, import_firestore17.collection)(this.db, USERS_COLLECTION);
5138
- const q = (0, import_firestore17.query)(usersRef, (0, import_firestore17.where)("email", "==", email));
5139
- const querySnapshot = await (0, import_firestore17.getDocs)(q);
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, import_firestore17.where)("roles", "array-contains", role)
5505
+ (0, import_firestore19.where)("roles", "array-contains", role)
5147
5506
  ];
5148
- const q = (0, import_firestore17.query)((0, import_firestore17.collection)(this.db, USERS_COLLECTION), ...constraints);
5149
- const querySnapshot = await (0, import_firestore17.getDocs)(q);
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, import_firestore17.doc)(this.db, USERS_COLLECTION, uid);
5158
- const userDoc = await (0, import_firestore17.getDoc)(userRef);
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, import_firestore17.updateDoc)(userRef, {
5163
- lastLoginAt: (0, import_firestore17.serverTimestamp)(),
5164
- updatedAt: (0, import_firestore17.serverTimestamp)()
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, import_firestore17.doc)(this.db, USERS_COLLECTION, uid);
5170
- const userDoc = await (0, import_firestore17.getDoc)(userRef);
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, import_firestore17.updateDoc)(userRef, {
5533
+ await (0, import_firestore19.updateDoc)(userRef, {
5175
5534
  email,
5176
5535
  isAnonymous: false,
5177
- updatedAt: (0, import_firestore17.serverTimestamp)()
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, import_firestore17.doc)(this.db, USERS_COLLECTION, uid);
5183
- const userDoc = await (0, import_firestore17.getDoc)(userRef);
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, import_firestore17.serverTimestamp)()
5551
+ updatedAt: (0, import_firestore19.serverTimestamp)()
5193
5552
  };
5194
5553
  userSchema.parse(updatedUser);
5195
- await (0, import_firestore17.updateDoc)(userRef, {
5554
+ await (0, import_firestore19.updateDoc)(userRef, {
5196
5555
  ...updates,
5197
- updatedAt: (0, import_firestore17.serverTimestamp)()
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, import_firestore17.updateDoc)((0, import_firestore17.doc)(this.db, USERS_COLLECTION, uid), {
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, import_firestore17.serverTimestamp)()
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, import_firestore17.updateDoc)((0, import_firestore17.doc)(this.db, USERS_COLLECTION, uid), {
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, import_firestore17.serverTimestamp)()
5610
+ updatedAt: (0, import_firestore19.serverTimestamp)()
5252
5611
  });
5253
5612
  }
5254
5613
  // Delete operations
5255
5614
  async deleteUser(uid) {
5256
- const userRef = (0, import_firestore17.doc)(this.db, USERS_COLLECTION, uid);
5257
- const userDoc = await (0, import_firestore17.getDoc)(userRef);
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, import_firestore17.deleteDoc)(userRef);
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 import_firestore18 = require("firebase/firestore");
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 import_storage4 = require("firebase/storage");
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, import_storage4.getStorage)(app);
5659
+ const storage = (0, import_storage5.getStorage)(app);
5301
5660
  const storageFileName = fileName || `${photoType}-${Date.now()}`;
5302
- const storageRef = (0, import_storage4.ref)(
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, import_storage4.uploadBytes)(storageRef, blob, { contentType });
5315
- const downloadUrl = await (0, import_storage4.getDownloadURL)(storageRef);
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 = import_firestore18.Timestamp.now();
5762
+ const now = import_firestore20.Timestamp.now();
5404
5763
  console.log("[CLINIC_GROUP] Preparing clinic group data object");
5405
- const groupId = (0, import_firestore18.doc)((0, import_firestore18.collection)(db, CLINIC_GROUPS_COLLECTION)).id;
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, import_firestore18.setDoc)((0, import_firestore18.doc)(db, CLINIC_GROUPS_COLLECTION, groupData.id), groupData);
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, import_firestore18.doc)(db, CLINIC_GROUPS_COLLECTION, groupId);
5502
- const docSnap = await (0, import_firestore18.getDoc)(docRef);
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, import_firestore18.query)(
5510
- (0, import_firestore18.collection)(db, CLINIC_GROUPS_COLLECTION),
5511
- (0, import_firestore18.where)("isActive", "==", true)
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, import_firestore18.getDocs)(q);
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: import_firestore18.Timestamp.now()
5901
+ updatedAt: import_firestore20.Timestamp.now()
5543
5902
  };
5544
5903
  console.log("[CLINIC_GROUP] Updating clinic group in Firestore");
5545
- await (0, import_firestore18.updateDoc)((0, import_firestore18.doc)(db, CLINIC_GROUPS_COLLECTION, groupId), updatedData);
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 = import_firestore18.Timestamp.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 import_firestore18.Timestamp(
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 = import_firestore18.Timestamp.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 import_firestore19 = require("firebase/firestore");
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, import_firestore19.doc)(db, CLINICS_COLLECTION, clinicId);
5941
- const docSnap = await (0, import_firestore19.getDoc)(docRef);
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, import_firestore19.query)(
5949
- (0, import_firestore19.collection)(db, CLINICS_COLLECTION),
5950
- (0, import_firestore19.where)("clinicGroupId", "==", groupId),
5951
- (0, import_firestore19.where)("isActive", "==", true)
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, import_firestore19.getDocs)(q);
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: import_firestore19.Timestamp.now()
6468
+ updatedAt: import_firestore21.Timestamp.now()
6110
6469
  };
6111
6470
  console.log("[CLINIC] Updating clinic in Firestore");
6112
6471
  try {
6113
- await (0, import_firestore19.updateDoc)((0, import_firestore19.doc)(db, CLINICS_COLLECTION, clinicId), updatedData);
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, import_firestore19.where)("id", "in", clinicIds)];
6501
+ const constraints = [(0, import_firestore21.where)("id", "in", clinicIds)];
6143
6502
  if (options.isActive !== void 0) {
6144
- constraints.push((0, import_firestore19.where)("isActive", "==", options.isActive));
6503
+ constraints.push((0, import_firestore21.where)("isActive", "==", options.isActive));
6145
6504
  }
6146
- const q = (0, import_firestore19.query)((0, import_firestore19.collection)(db, CLINICS_COLLECTION), ...constraints);
6147
- const querySnapshot = await (0, import_firestore19.getDocs)(q);
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, import_firestore19.doc)(db, CLINICS_COLLECTION, clinicId);
6162
- const clinicSnapshot = await (0, import_firestore19.getDoc)(clinicRef);
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, import_firestore19.collection)(db, CLINICS_COLLECTION);
6179
- let clinicsQuery = (0, import_firestore19.query)(clinicsCollection);
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, import_firestore19.query)(
6541
+ clinicsQuery = (0, import_firestore21.query)(
6183
6542
  clinicsCollection,
6184
- (0, import_firestore19.startAfter)(lastDoc),
6185
- (0, import_firestore19.limit)(pagination)
6543
+ (0, import_firestore21.startAfter)(lastDoc),
6544
+ (0, import_firestore21.limit)(pagination)
6186
6545
  );
6187
6546
  } else {
6188
- clinicsQuery = (0, import_firestore19.query)(clinicsCollection, (0, import_firestore19.limit)(pagination));
6547
+ clinicsQuery = (0, import_firestore21.query)(clinicsCollection, (0, import_firestore21.limit)(pagination));
6189
6548
  }
6190
6549
  }
6191
- const clinicsSnapshot = await (0, import_firestore19.getDocs)(clinicsQuery);
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, import_firestore19.where)("location.geohash", ">=", b[0]),
6219
- (0, import_firestore19.where)("location.geohash", "<=", b[1]),
6220
- (0, import_firestore19.where)("isActive", "==", true)
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, import_firestore19.query)((0, import_firestore19.collection)(db, CLINICS_COLLECTION), ...constraints);
6223
- const querySnapshot = await (0, import_firestore19.getDocs)(q);
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 import_firestore20 = require("firebase/firestore");
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, import_firestore20.where)("location.geohash", ">=", b[0]),
6329
- (0, import_firestore20.where)("location.geohash", "<=", b[1]),
6330
- (0, import_firestore20.where)("isActive", "==", true)
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, import_firestore20.where)("services", "array-contains-any", filters.services)
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, import_firestore20.where)("tags", "array-contains-any", filters.tags));
6697
+ constraints.push((0, import_firestore22.where)("tags", "array-contains-any", filters.tags));
6339
6698
  }
6340
- const q = (0, import_firestore20.query)((0, import_firestore20.collection)(db, CLINICS_COLLECTION), ...constraints);
6341
- const querySnapshot = await (0, import_firestore20.getDocs)(q);
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 import_firestore21 = require("firebase/firestore");
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, import_firestore21.where)("isActive", "==", filters.isActive));
6737
+ constraints.push((0, import_firestore23.where)("isActive", "==", filters.isActive));
6379
6738
  } else {
6380
- constraints.push((0, import_firestore21.where)("isActive", "==", true));
6739
+ constraints.push((0, import_firestore23.where)("isActive", "==", true));
6381
6740
  }
6382
6741
  if (filters.tags && filters.tags.length > 0) {
6383
- constraints.push((0, import_firestore21.where)("tags", "array-contains", filters.tags[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, import_firestore21.where)("servicesInfo.technology", "==", filters.procedureTechnology)
6746
+ (0, import_firestore23.where)("servicesInfo.technology", "==", filters.procedureTechnology)
6388
6747
  );
6389
6748
  } else if (filters.procedureSubcategory) {
6390
6749
  constraints.push(
6391
- (0, import_firestore21.where)("servicesInfo.subCategory", "==", filters.procedureSubcategory)
6750
+ (0, import_firestore23.where)("servicesInfo.subCategory", "==", filters.procedureSubcategory)
6392
6751
  );
6393
6752
  } else if (filters.procedureCategory) {
6394
6753
  constraints.push(
6395
- (0, import_firestore21.where)("servicesInfo.category", "==", filters.procedureCategory)
6754
+ (0, import_firestore23.where)("servicesInfo.category", "==", filters.procedureCategory)
6396
6755
  );
6397
6756
  } else if (filters.procedureFamily) {
6398
6757
  constraints.push(
6399
- (0, import_firestore21.where)("servicesInfo.procedureFamily", "==", filters.procedureFamily)
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, import_firestore21.startAfter)(filters.lastDoc));
6404
- constraints.push((0, import_firestore21.limit)(filters.pagination));
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, import_firestore21.limit)(filters.pagination));
6765
+ constraints.push((0, import_firestore23.limit)(filters.pagination));
6407
6766
  }
6408
- constraints.push((0, import_firestore21.orderBy)("location.geohash"));
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
- try {
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
- if (!querySnapshot.empty) {
6625
- const metadata = querySnapshot.docs[0].data();
6626
- console.log("[MediaService] Metadata found by URL:", metadata);
6627
- return metadata;
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
- * Delete media from storage and remove metadata from Firestore.
6638
- * @param mediaId - ID of the media to delete.
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
- const storageFileRef = (0, import_storage5.ref)(this.storage, metadata.path);
6650
- try {
6651
- await (0, import_storage5.deleteObject)(storageFileRef);
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
- * Update media access level. This involves moving the file in Firebase Storage
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
- if (metadata.accessLevel === newAccessLevel) {
6683
- console.log(
6684
- `[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
6685
- );
6686
- const metadataDocRef = (0, import_firestore23.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6687
- try {
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
- throw error;
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
- const oldStoragePath = metadata.path;
6699
- const fileNamePart = `${metadata.id}-${metadata.name}`;
6700
- const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
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
- `[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
6845
+ `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
6703
6846
  );
6704
- const oldStorageFileRef = (0, import_storage5.ref)(this.storage, oldStoragePath);
6705
- const newStorageFileRef = (0, import_storage5.ref)(this.storage, newStoragePath);
6706
- try {
6707
- console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
6708
- const fileBytes = await (0, import_storage5.getBytes)(oldStorageFileRef);
6709
- console.log(
6710
- `[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
6711
- );
6712
- console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
6713
- await (0, import_storage5.uploadBytes)(newStorageFileRef, fileBytes, {
6714
- contentType: metadata.contentType
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
- return { ...metadata, ...updateData };
6751
- } catch (error) {
6752
- console.error(
6753
- `[MediaService] Error updating media access level and moving file for ${mediaId}:`,
6754
- error
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
- qConstraints.push((0, import_firestore23.orderBy)("createdAt", "desc"));
6793
- if (count) {
6794
- qConstraints.push((0, import_firestore23.limit)(count));
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
- const finalQuery = (0, import_firestore23.query)(
6802
- (0, import_firestore23.collection)(this.db, MEDIA_METADATA_COLLECTION),
6803
- ...qConstraints
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
- * Get download URL for media. (Convenience, as URL is in metadata)
6819
- * @param mediaId - ID of the media.
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
- console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
6829
- return null;
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
  };