@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.mjs CHANGED
@@ -877,16 +877,16 @@ var BaseService = class {
877
877
 
878
878
  // src/services/user.service.ts
879
879
  import {
880
- collection as collection7,
881
- doc as doc9,
882
- getDoc as getDoc12,
883
- getDocs as getDocs7,
884
- query as query7,
885
- where as where7,
886
- updateDoc as updateDoc9,
887
- deleteDoc as deleteDoc3,
888
- Timestamp as Timestamp10,
889
- setDoc as setDoc8,
880
+ collection as collection8,
881
+ doc as doc10,
882
+ getDoc as getDoc13,
883
+ getDocs as getDocs8,
884
+ query as query8,
885
+ where as where8,
886
+ updateDoc as updateDoc10,
887
+ deleteDoc as deleteDoc4,
888
+ Timestamp as Timestamp11,
889
+ setDoc as setDoc9,
890
890
  serverTimestamp as serverTimestamp10
891
891
  } from "firebase/firestore";
892
892
 
@@ -3195,7 +3195,7 @@ var doctorInfoSchema = z11.object({
3195
3195
  // src/validations/media.schema.ts
3196
3196
  import { z as z12 } from "zod";
3197
3197
  var mediaResourceSchema = z12.union([
3198
- z12.string(),
3198
+ z12.string().url(),
3199
3199
  z12.instanceof(File),
3200
3200
  z12.instanceof(Blob)
3201
3201
  ]);
@@ -3934,686 +3934,1063 @@ var ClinicAdminService = class extends BaseService {
3934
3934
 
3935
3935
  // src/services/practitioner/practitioner.service.ts
3936
3936
  import {
3937
- collection as collection6,
3938
- doc as doc8,
3939
- getDoc as getDoc11,
3940
- getDocs as getDocs6,
3941
- query as query6,
3942
- where as where6,
3943
- updateDoc as updateDoc8,
3944
- setDoc as setDoc7,
3945
- deleteDoc as deleteDoc2,
3946
- Timestamp as Timestamp9,
3937
+ collection as collection7,
3938
+ doc as doc9,
3939
+ getDoc as getDoc12,
3940
+ getDocs as getDocs7,
3941
+ query as query7,
3942
+ where as where7,
3943
+ updateDoc as updateDoc9,
3944
+ setDoc as setDoc8,
3945
+ deleteDoc as deleteDoc3,
3946
+ Timestamp as Timestamp10,
3947
3947
  serverTimestamp as serverTimestamp9,
3948
- limit as limit4,
3948
+ limit as limit5,
3949
3949
  startAfter as startAfter4,
3950
- orderBy,
3950
+ orderBy as orderBy2,
3951
3951
  arrayUnion as arrayUnion5,
3952
3952
  arrayRemove as arrayRemove4
3953
3953
  } from "firebase/firestore";
3954
3954
 
3955
- // src/validations/practitioner.schema.ts
3956
- import { z as z14 } from "zod";
3955
+ // src/services/media/media.service.ts
3957
3956
  import { Timestamp as Timestamp8 } from "firebase/firestore";
3958
-
3959
- // src/backoffice/types/static/certification.types.ts
3960
- var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
3961
- CertificationLevel2["AESTHETICIAN"] = "aesthetician";
3962
- CertificationLevel2["NURSE_ASSISTANT"] = "nurse_assistant";
3963
- CertificationLevel2["NURSE"] = "nurse";
3964
- CertificationLevel2["NURSE_PRACTITIONER"] = "nurse_practitioner";
3965
- CertificationLevel2["PHYSICIAN_ASSISTANT"] = "physician_assistant";
3966
- CertificationLevel2["DOCTOR"] = "doctor";
3967
- CertificationLevel2["SPECIALIST"] = "specialist";
3968
- CertificationLevel2["PLASTIC_SURGEON"] = "plastic_surgeon";
3969
- return CertificationLevel2;
3970
- })(CertificationLevel || {});
3971
- var CertificationSpecialty = /* @__PURE__ */ ((CertificationSpecialty3) => {
3972
- CertificationSpecialty3["LASER"] = "laser";
3973
- CertificationSpecialty3["INJECTABLES"] = "injectables";
3974
- CertificationSpecialty3["CHEMICAL_PEELS"] = "chemical_peels";
3975
- CertificationSpecialty3["MICRODERMABRASION"] = "microdermabrasion";
3976
- CertificationSpecialty3["BODY_CONTOURING"] = "body_contouring";
3977
- CertificationSpecialty3["SKIN_CARE"] = "skin_care";
3978
- CertificationSpecialty3["WOUND_CARE"] = "wound_care";
3979
- CertificationSpecialty3["ANESTHESIA"] = "anesthesia";
3980
- return CertificationSpecialty3;
3981
- })(CertificationSpecialty || {});
3982
-
3983
- // src/validations/practitioner.schema.ts
3984
- var practitionerBasicInfoSchema = z14.object({
3985
- firstName: z14.string().min(2).max(50),
3986
- lastName: z14.string().min(2).max(50),
3987
- title: z14.string().min(2).max(100),
3988
- email: z14.string().email(),
3989
- phoneNumber: z14.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
3990
- dateOfBirth: z14.instanceof(Timestamp8).or(z14.date()),
3991
- gender: z14.enum(["male", "female", "other"]),
3992
- profileImageUrl: z14.string().url().optional(),
3993
- bio: z14.string().max(1e3).optional(),
3994
- languages: z14.array(z14.string()).min(1)
3995
- });
3996
- var practitionerCertificationSchema = z14.object({
3997
- level: z14.nativeEnum(CertificationLevel),
3998
- specialties: z14.array(z14.nativeEnum(CertificationSpecialty)),
3999
- licenseNumber: z14.string().min(3).max(50),
4000
- issuingAuthority: z14.string().min(2).max(100),
4001
- issueDate: z14.instanceof(Timestamp8).or(z14.date()),
4002
- expiryDate: z14.instanceof(Timestamp8).or(z14.date()).optional(),
4003
- verificationStatus: z14.enum(["pending", "verified", "rejected"])
4004
- });
4005
- var timeSlotSchema = z14.object({
4006
- start: z14.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format"),
4007
- end: z14.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format")
4008
- }).nullable();
4009
- var practitionerWorkingHoursSchema = z14.object({
4010
- practitionerId: z14.string().min(1),
4011
- clinicId: z14.string().min(1),
4012
- monday: timeSlotSchema,
4013
- tuesday: timeSlotSchema,
4014
- wednesday: timeSlotSchema,
4015
- thursday: timeSlotSchema,
4016
- friday: timeSlotSchema,
4017
- saturday: timeSlotSchema,
4018
- sunday: timeSlotSchema,
4019
- createdAt: z14.instanceof(Timestamp8).or(z14.date()),
4020
- updatedAt: z14.instanceof(Timestamp8).or(z14.date())
4021
- });
4022
- var practitionerClinicWorkingHoursSchema = z14.object({
4023
- clinicId: z14.string().min(1),
4024
- workingHours: z14.object({
4025
- monday: timeSlotSchema,
4026
- tuesday: timeSlotSchema,
4027
- wednesday: timeSlotSchema,
4028
- thursday: timeSlotSchema,
4029
- friday: timeSlotSchema,
4030
- saturday: timeSlotSchema,
4031
- sunday: timeSlotSchema
4032
- }),
4033
- isActive: z14.boolean(),
4034
- createdAt: z14.instanceof(Timestamp8).or(z14.date()),
4035
- updatedAt: z14.instanceof(Timestamp8).or(z14.date())
4036
- });
4037
- var practitionerSchema = z14.object({
4038
- id: z14.string().min(1),
4039
- userRef: z14.string().min(1),
4040
- basicInfo: practitionerBasicInfoSchema,
4041
- certification: practitionerCertificationSchema,
4042
- clinics: z14.array(z14.string()),
4043
- clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema),
4044
- clinicsInfo: z14.array(clinicInfoSchema),
4045
- procedures: z14.array(z14.string()),
4046
- proceduresInfo: z14.array(procedureSummaryInfoSchema),
4047
- reviewInfo: practitionerReviewInfoSchema,
4048
- isActive: z14.boolean(),
4049
- isVerified: z14.boolean(),
4050
- status: z14.nativeEnum(PractitionerStatus),
4051
- createdAt: z14.instanceof(Timestamp8).or(z14.date()),
4052
- updatedAt: z14.instanceof(Timestamp8).or(z14.date())
4053
- });
4054
- var createPractitionerSchema = z14.object({
4055
- userRef: z14.string().min(1),
4056
- basicInfo: practitionerBasicInfoSchema,
4057
- certification: practitionerCertificationSchema,
4058
- clinics: z14.array(z14.string()).optional(),
4059
- clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema).optional(),
4060
- clinicsInfo: z14.array(clinicInfoSchema).optional(),
4061
- proceduresInfo: z14.array(procedureSummaryInfoSchema).optional(),
4062
- isActive: z14.boolean(),
4063
- isVerified: z14.boolean(),
4064
- status: z14.nativeEnum(PractitionerStatus).optional()
4065
- });
4066
- var createDraftPractitionerSchema = z14.object({
4067
- basicInfo: practitionerBasicInfoSchema,
4068
- certification: practitionerCertificationSchema,
4069
- clinics: z14.array(z14.string()).optional(),
4070
- clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema).optional(),
4071
- clinicsInfo: z14.array(clinicInfoSchema).optional(),
4072
- proceduresInfo: z14.array(procedureSummaryInfoSchema).optional(),
4073
- isActive: z14.boolean().optional().default(false),
4074
- isVerified: z14.boolean().optional().default(false)
4075
- });
4076
- var practitionerTokenSchema = z14.object({
4077
- id: z14.string().min(1),
4078
- token: z14.string().min(6),
4079
- practitionerId: z14.string().min(1),
4080
- email: z14.string().email(),
4081
- clinicId: z14.string().min(1),
4082
- status: z14.nativeEnum(PractitionerTokenStatus),
4083
- createdBy: z14.string().min(1),
4084
- createdAt: z14.instanceof(Timestamp8).or(z14.date()),
4085
- expiresAt: z14.instanceof(Timestamp8).or(z14.date()),
4086
- usedBy: z14.string().optional(),
4087
- usedAt: z14.instanceof(Timestamp8).or(z14.date()).optional()
4088
- });
4089
- var createPractitionerTokenSchema = z14.object({
4090
- practitionerId: z14.string().min(1),
4091
- email: z14.string().email(),
4092
- clinicId: z14.string().min(1),
4093
- expiresAt: z14.date().optional()
4094
- });
4095
- var practitionerSignupSchema = z14.object({
4096
- email: z14.string().email(),
4097
- password: z14.string().min(8),
4098
- firstName: z14.string().min(2).max(50).optional(),
4099
- lastName: z14.string().min(2).max(50).optional(),
4100
- token: z14.string().optional(),
4101
- profileData: z14.object({
4102
- basicInfo: z14.object({
4103
- phoneNumber: z14.string().optional(),
4104
- profileImageUrl: z14.string().optional(),
4105
- gender: z14.enum(["male", "female", "other"]).optional(),
4106
- bio: z14.string().optional()
4107
- }).optional(),
4108
- certification: z14.any().optional()
4109
- }).optional()
4110
- });
4111
-
4112
- // src/services/practitioner/practitioner.service.ts
4113
- import { z as z15 } from "zod";
4114
- import { distanceBetween } from "geofire-common";
4115
- var PractitionerService = class extends BaseService {
4116
- constructor(db, auth, app, clinicService) {
3957
+ import {
3958
+ ref as ref2,
3959
+ uploadBytes as uploadBytes2,
3960
+ getDownloadURL as getDownloadURL2,
3961
+ deleteObject as deleteObject2,
3962
+ getBytes
3963
+ } from "firebase/storage";
3964
+ import {
3965
+ doc as doc8,
3966
+ getDoc as getDoc11,
3967
+ setDoc as setDoc7,
3968
+ updateDoc as updateDoc8,
3969
+ collection as collection6,
3970
+ query as query6,
3971
+ where as where6,
3972
+ limit as limit4,
3973
+ getDocs as getDocs6,
3974
+ deleteDoc as deleteDoc2,
3975
+ orderBy
3976
+ } from "firebase/firestore";
3977
+ var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
3978
+ MediaAccessLevel2["PUBLIC"] = "public";
3979
+ MediaAccessLevel2["PRIVATE"] = "private";
3980
+ MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
3981
+ return MediaAccessLevel2;
3982
+ })(MediaAccessLevel || {});
3983
+ var MEDIA_METADATA_COLLECTION = "media_metadata";
3984
+ var MediaService = class extends BaseService {
3985
+ constructor(db, auth, app) {
4117
3986
  super(db, auth, app);
4118
- this.clinicService = clinicService;
4119
- }
4120
- getClinicService() {
4121
- if (!this.clinicService) {
4122
- throw new Error("Clinic service not initialized!");
4123
- }
4124
- return this.clinicService;
4125
- }
4126
- setClinicService(clinicService) {
4127
- this.clinicService = clinicService;
4128
3987
  }
4129
3988
  /**
4130
- * Creates a new practitioner
3989
+ * Upload a media file, store its metadata, and return the metadata including the URL.
3990
+ * @param file - The file to upload.
3991
+ * @param ownerId - ID of the owner (user, patient, clinic, etc.).
3992
+ * @param accessLevel - Access level (public, private, confidential).
3993
+ * @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
3994
+ * @param originalFileName - Optional: the original name of the file, if not using file.name.
3995
+ * @returns Promise with the media metadata.
4131
3996
  */
4132
- async createPractitioner(data) {
3997
+ async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
3998
+ const mediaId = this.generateId();
3999
+ const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
4000
+ const uniqueFileName = `${mediaId}-${fileNameToUse}`;
4001
+ const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
4002
+ console.log(`[MediaService] Uploading file to: ${filePath}`);
4003
+ const storageRef = ref2(this.storage, filePath);
4133
4004
  try {
4134
- const validData = createPractitionerSchema.parse(data);
4135
- const practitionerId = this.generateId();
4136
- const reviewInfo = {
4137
- totalReviews: 0,
4138
- averageRating: 0,
4139
- knowledgeAndExpertise: 0,
4140
- communicationSkills: 0,
4141
- bedSideManner: 0,
4142
- thoroughness: 0,
4143
- trustworthiness: 0,
4144
- recommendationPercentage: 0
4145
- };
4146
- const practitioner = {
4147
- id: practitionerId,
4148
- userRef: validData.userRef,
4149
- basicInfo: validData.basicInfo,
4150
- certification: validData.certification,
4151
- clinics: validData.clinics || [],
4152
- clinicWorkingHours: validData.clinicWorkingHours || [],
4153
- clinicsInfo: [],
4154
- procedures: [],
4155
- proceduresInfo: [],
4156
- reviewInfo,
4157
- isActive: validData.isActive !== void 0 ? validData.isActive : true,
4158
- isVerified: validData.isVerified !== void 0 ? validData.isVerified : false,
4159
- status: validData.status || "active" /* ACTIVE */,
4160
- createdAt: serverTimestamp9(),
4161
- updatedAt: serverTimestamp9()
4162
- };
4163
- practitionerSchema.parse({
4164
- ...practitioner,
4165
- createdAt: Timestamp9.now(),
4166
- updatedAt: Timestamp9.now()
4005
+ const uploadResult = await uploadBytes2(storageRef, file, {
4006
+ contentType: file.type
4167
4007
  });
4168
- const practitionerRef = doc8(
4169
- this.db,
4170
- PRACTITIONERS_COLLECTION,
4171
- practitionerId
4172
- );
4173
- await setDoc7(practitionerRef, practitioner);
4174
- const createdPractitioner = await this.getPractitioner(practitionerId);
4175
- if (!createdPractitioner) {
4176
- throw new Error(
4177
- `Failed to retrieve created practitioner ${practitionerId}`
4178
- );
4179
- }
4180
- return createdPractitioner;
4008
+ console.log("[MediaService] File uploaded successfully", uploadResult);
4009
+ const downloadURL = await getDownloadURL2(uploadResult.ref);
4010
+ console.log("[MediaService] Got download URL:", downloadURL);
4011
+ const metadata = {
4012
+ id: mediaId,
4013
+ name: fileNameToUse,
4014
+ url: downloadURL,
4015
+ contentType: file.type,
4016
+ size: file.size,
4017
+ createdAt: Timestamp8.now(),
4018
+ accessLevel,
4019
+ ownerId,
4020
+ collectionName,
4021
+ path: filePath
4022
+ };
4023
+ const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
4024
+ await setDoc7(metadataDocRef, metadata);
4025
+ console.log("[MediaService] Metadata stored in Firestore:", mediaId);
4026
+ return metadata;
4181
4027
  } catch (error) {
4182
- if (error instanceof z15.ZodError) {
4183
- throw new Error(`Invalid practitioner data: ${error.message}`);
4184
- }
4185
- console.error("Error creating practitioner:", error);
4028
+ console.error("[MediaService] Error during media upload:", error);
4186
4029
  throw error;
4187
4030
  }
4188
4031
  }
4189
4032
  /**
4190
- * Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
4191
- * Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
4192
- * @param data Podaci za kreiranje draft profila
4193
- * @param createdBy ID administratora koji kreira profil
4194
- * @param clinicId ID klinike za koju se kreira profil
4195
- * @returns Objekt koji sadrži kreirani draft profil i token za registraciju
4033
+ * Get media metadata from Firestore by its ID.
4034
+ * @param mediaId - ID of the media.
4035
+ * @returns Promise with the media metadata or null if not found.
4196
4036
  */
4197
- async createDraftPractitioner(data, createdBy, clinicId) {
4037
+ async getMediaMetadata(mediaId) {
4038
+ console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
4039
+ const docRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
4040
+ const docSnap = await getDoc11(docRef);
4041
+ if (docSnap.exists()) {
4042
+ console.log("[MediaService] Metadata found:", docSnap.data());
4043
+ return docSnap.data();
4044
+ }
4045
+ console.log("[MediaService] No metadata found for ID:", mediaId);
4046
+ return null;
4047
+ }
4048
+ /**
4049
+ * Get media metadata from Firestore by its public URL.
4050
+ * @param url - The public URL of the media file.
4051
+ * @returns Promise with the media metadata or null if not found.
4052
+ */
4053
+ async getMediaMetadataByUrl(url) {
4054
+ console.log(`[MediaService] Getting media metadata by URL: ${url}`);
4055
+ const q = query6(
4056
+ collection6(this.db, MEDIA_METADATA_COLLECTION),
4057
+ where6("url", "==", url),
4058
+ limit4(1)
4059
+ );
4198
4060
  try {
4199
- const validatedData = createDraftPractitionerSchema.parse(data);
4200
- const clinic = await this.getClinicService().getClinic(clinicId);
4201
- if (!clinic) {
4202
- throw new Error(`Clinic ${clinicId} not found`);
4203
- }
4204
- const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
4205
- if (data.clinics && data.clinics.length > 0) {
4206
- for (const cId of data.clinics) {
4207
- if (cId !== clinicId) {
4208
- const otherClinic = await this.getClinicService().getClinic(cId);
4209
- if (!otherClinic) {
4210
- throw new Error(`Clinic ${cId} not found`);
4211
- }
4212
- }
4213
- clinicsToAdd.add(cId);
4214
- }
4215
- }
4216
- const clinics = Array.from(clinicsToAdd);
4217
- const defaultReviewInfo = {
4218
- totalReviews: 0,
4219
- averageRating: 0,
4220
- knowledgeAndExpertise: 0,
4221
- communicationSkills: 0,
4222
- bedSideManner: 0,
4223
- thoroughness: 0,
4224
- trustworthiness: 0,
4225
- recommendationPercentage: 0
4226
- };
4227
- const practitionerId = this.generateId();
4228
- const clinicsInfo = [];
4229
- for (const cId of clinics) {
4230
- const clinicData = await this.getClinicService().getClinic(cId);
4231
- if (clinicData) {
4232
- clinicsInfo.push({
4233
- id: clinicData.id,
4234
- name: clinicData.name,
4235
- location: clinicData.location,
4236
- contactInfo: clinicData.contactInfo,
4237
- // Make sure we're using the right property for featuredPhoto
4238
- featuredPhoto: clinicData.featuredPhotos && clinicData.featuredPhotos.length > 0 ? typeof clinicData.featuredPhotos[0] === "string" ? clinicData.featuredPhotos[0] : "" : (typeof clinicData.coverPhoto === "string" ? clinicData.coverPhoto : "") || "",
4239
- description: clinicData.description || null
4240
- });
4241
- }
4242
- }
4243
- const finalClinicsInfo = validatedData.clinicsInfo && validatedData.clinicsInfo.length > 0 ? validatedData.clinicsInfo : clinicsInfo;
4244
- const proceduresInfo = [];
4245
- const practitionerData = {
4246
- id: practitionerId,
4247
- userRef: "",
4248
- // Prazno - biće popunjeno kada korisnik kreira nalog
4249
- basicInfo: validatedData.basicInfo,
4250
- certification: validatedData.certification,
4251
- clinics,
4252
- clinicWorkingHours: validatedData.clinicWorkingHours || [],
4253
- clinicsInfo: finalClinicsInfo,
4254
- procedures: [],
4255
- proceduresInfo,
4256
- reviewInfo: defaultReviewInfo,
4257
- isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
4258
- isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
4259
- status: "draft" /* DRAFT */,
4260
- createdAt: serverTimestamp9(),
4261
- updatedAt: serverTimestamp9()
4262
- };
4263
- practitionerSchema.parse({
4264
- ...practitionerData,
4265
- userRef: "temp-for-validation",
4266
- createdAt: Timestamp9.now(),
4267
- updatedAt: Timestamp9.now()
4268
- });
4269
- await setDoc7(
4270
- doc8(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
4271
- practitionerData
4272
- );
4273
- const savedPractitioner = await this.getPractitioner(practitionerData.id);
4274
- if (!savedPractitioner) {
4275
- throw new Error("Failed to create draft practitioner profile");
4061
+ const querySnapshot = await getDocs6(q);
4062
+ if (!querySnapshot.empty) {
4063
+ const metadata = querySnapshot.docs[0].data();
4064
+ console.log("[MediaService] Metadata found by URL:", metadata);
4065
+ return metadata;
4276
4066
  }
4277
- const tokenString = this.generateId().slice(0, 6).toUpperCase();
4278
- const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
4279
- const token = {
4280
- id: this.generateId(),
4281
- token: tokenString,
4282
- practitionerId,
4283
- email: practitionerData.basicInfo.email,
4284
- clinicId,
4285
- status: "active" /* ACTIVE */,
4286
- createdBy,
4287
- createdAt: Timestamp9.now(),
4288
- expiresAt: Timestamp9.fromDate(expiration)
4289
- };
4290
- practitionerTokenSchema.parse(token);
4291
- const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
4292
- await setDoc7(doc8(this.db, tokenPath), token);
4293
- return { practitioner: savedPractitioner, token };
4067
+ console.log("[MediaService] No metadata found for URL:", url);
4068
+ return null;
4294
4069
  } catch (error) {
4295
- if (error instanceof z15.ZodError) {
4296
- throw new Error("Invalid practitioner data: " + error.message);
4297
- }
4070
+ console.error("[MediaService] Error fetching metadata by URL:", error);
4298
4071
  throw error;
4299
4072
  }
4300
4073
  }
4301
4074
  /**
4302
- * Creates a token for inviting practitioner to claim their profile
4303
- * @param data Data for creating token
4304
- * @param createdBy ID of the user creating the token
4305
- * @returns Created token
4075
+ * Delete media from storage and remove metadata from Firestore.
4076
+ * @param mediaId - ID of the media to delete.
4306
4077
  */
4307
- async createPractitionerToken(data, createdBy) {
4308
- try {
4309
- const validatedData = createPractitionerTokenSchema.parse(data);
4310
- const practitioner = await this.getPractitioner(
4311
- validatedData.practitionerId
4078
+ async deleteMedia(mediaId) {
4079
+ console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
4080
+ const metadata = await this.getMediaMetadata(mediaId);
4081
+ if (!metadata) {
4082
+ console.warn(
4083
+ `[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
4312
4084
  );
4313
- if (!practitioner) {
4314
- throw new Error("Practitioner not found");
4315
- }
4316
- if (practitioner.status !== "draft" /* DRAFT */) {
4317
- throw new Error(
4318
- "Can only create tokens for practitioners in DRAFT status"
4319
- );
4320
- }
4321
- const clinic = await this.getClinicService().getClinic(
4322
- validatedData.clinicId
4085
+ return;
4086
+ }
4087
+ const storageFileRef = ref2(this.storage, metadata.path);
4088
+ try {
4089
+ await deleteObject2(storageFileRef);
4090
+ console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
4091
+ const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
4092
+ await deleteDoc2(metadataDocRef);
4093
+ console.log(
4094
+ `[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
4323
4095
  );
4324
- if (!clinic) {
4325
- throw new Error(`Clinic ${validatedData.clinicId} not found`);
4326
- }
4327
- if (!practitioner.clinics.includes(validatedData.clinicId)) {
4328
- throw new Error("Practitioner is not associated with this clinic");
4329
- }
4330
- const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
4331
- const tokenString = this.generateId().slice(0, 6).toUpperCase();
4332
- const token = {
4333
- id: this.generateId(),
4334
- token: tokenString,
4335
- practitionerId: validatedData.practitionerId,
4336
- email: validatedData.email,
4337
- clinicId: validatedData.clinicId,
4338
- status: "active" /* ACTIVE */,
4339
- createdBy,
4340
- createdAt: Timestamp9.now(),
4341
- expiresAt: Timestamp9.fromDate(expiration)
4342
- };
4343
- practitionerTokenSchema.parse(token);
4344
- const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
4345
- await setDoc7(doc8(this.db, tokenPath), token);
4346
- return token;
4347
4096
  } catch (error) {
4348
- if (error instanceof z15.ZodError) {
4349
- throw new Error("Invalid token data: " + error.message);
4350
- }
4097
+ console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
4351
4098
  throw error;
4352
4099
  }
4353
4100
  }
4354
4101
  /**
4355
- * Gets active tokens for a practitioner
4356
- * @param practitionerId ID of the practitioner
4357
- * @returns Array of active tokens
4102
+ * Update media access level. This involves moving the file in Firebase Storage
4103
+ * to a new path reflecting the new access level, and updating its metadata.
4104
+ * @param mediaId - ID of the media to update.
4105
+ * @param newAccessLevel - New access level.
4106
+ * @returns Promise with the updated media metadata, or null if metadata not found.
4358
4107
  */
4359
- async getPractitionerActiveTokens(practitionerId) {
4360
- const tokensRef = collection6(
4361
- this.db,
4362
- `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
4363
- );
4364
- const q = query6(
4365
- tokensRef,
4366
- where6("status", "==", "active" /* ACTIVE */),
4367
- where6("expiresAt", ">", Timestamp9.now())
4108
+ async updateMediaAccessLevel(mediaId, newAccessLevel) {
4109
+ var _a;
4110
+ console.log(
4111
+ `[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
4368
4112
  );
4369
- const querySnapshot = await getDocs6(q);
4370
- return querySnapshot.docs.map((doc34) => doc34.data());
4371
- }
4372
- /**
4373
- * Gets a token by its string value and validates it
4374
- * @param tokenString The token string to find
4375
- * @returns The token if found and valid, null otherwise
4376
- */
4377
- async validateToken(tokenString) {
4378
- const practitionersRef = collection6(this.db, PRACTITIONERS_COLLECTION);
4379
- const practitionersSnapshot = await getDocs6(practitionersRef);
4380
- for (const practitionerDoc of practitionersSnapshot.docs) {
4381
- const practitionerId = practitionerDoc.id;
4382
- const tokensRef = collection6(
4383
- this.db,
4384
- `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
4113
+ const metadata = await this.getMediaMetadata(mediaId);
4114
+ if (!metadata) {
4115
+ console.warn(
4116
+ `[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
4385
4117
  );
4118
+ return null;
4119
+ }
4120
+ if (metadata.accessLevel === newAccessLevel) {
4386
4121
  console.log(
4387
- `[PRACTITIONER] Validating token for practitioner ${practitionerId}`,
4388
- {
4389
- tokenString,
4390
- timestamp: Timestamp9.now().toDate()
4391
- }
4392
- );
4393
- const q = query6(
4394
- tokensRef,
4395
- where6("token", "==", tokenString),
4396
- where6("status", "==", "active" /* ACTIVE */),
4397
- where6("expiresAt", ">", Timestamp9.now())
4122
+ `[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
4398
4123
  );
4124
+ const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
4399
4125
  try {
4400
- const tokenSnapshot = await getDocs6(q);
4401
- console.log(
4402
- `[PRACTITIONER] Token query results for practitioner ${practitionerId}`,
4403
- {
4404
- found: !tokenSnapshot.empty,
4405
- count: tokenSnapshot.size
4406
- }
4407
- );
4408
- if (!tokenSnapshot.empty) {
4409
- const tokenData = tokenSnapshot.docs[0].data();
4410
- console.log(`[PRACTITIONER] Valid token found`, {
4411
- tokenId: tokenData.id,
4412
- expiresAt: tokenData.expiresAt.toDate()
4413
- });
4414
- return tokenData;
4415
- }
4126
+ await updateDoc8(metadataDocRef, { updatedAt: Timestamp8.now() });
4127
+ return { ...metadata, updatedAt: Timestamp8.now() };
4416
4128
  } catch (error) {
4417
4129
  console.error(
4418
- `[PRACTITIONER] Error validating token for practitioner ${practitionerId}:`,
4130
+ `[MediaService] Error updating timestamp for media ID ${mediaId}:`,
4419
4131
  error
4420
4132
  );
4421
4133
  throw error;
4422
4134
  }
4423
4135
  }
4424
- return null;
4425
- }
4426
- /**
4427
- * Marks a token as used
4428
- * @param tokenId ID of the token
4429
- * @param practitionerId ID of the practitioner
4430
- * @param userId ID of the user using the token
4431
- */
4432
- async markTokenAsUsed(tokenId, practitionerId, userId) {
4433
- const tokenRef = doc8(
4434
- this.db,
4435
- `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
4436
- );
4437
- await updateDoc8(tokenRef, {
4438
- status: "used" /* USED */,
4439
- usedBy: userId,
4440
- usedAt: Timestamp9.now()
4441
- });
4442
- }
4443
- /**
4444
- * Dohvata zdravstvenog radnika po ID-u
4445
- */
4446
- async getPractitioner(practitionerId) {
4447
- const practitionerDoc = await getDoc11(
4448
- doc8(this.db, PRACTITIONERS_COLLECTION, practitionerId)
4449
- );
4450
- if (!practitionerDoc.exists()) {
4451
- return null;
4452
- }
4453
- return practitionerDoc.data();
4454
- }
4455
- /**
4456
- * Dohvata zdravstvenog radnika po User ID-u
4457
- */
4458
- async getPractitionerByUserRef(userRef) {
4459
- const q = query6(
4460
- collection6(this.db, PRACTITIONERS_COLLECTION),
4461
- where6("userRef", "==", userRef)
4462
- );
4463
- const querySnapshot = await getDocs6(q);
4464
- if (querySnapshot.empty) {
4465
- return null;
4466
- }
4467
- return querySnapshot.docs[0].data();
4468
- }
4469
- /**
4470
- * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
4471
- */
4472
- async getPractitionersByClinic(clinicId) {
4473
- const q = query6(
4474
- collection6(this.db, PRACTITIONERS_COLLECTION),
4475
- where6("clinics", "array-contains", clinicId),
4476
- where6("isActive", "==", true),
4477
- where6("status", "==", "active" /* ACTIVE */)
4478
- );
4479
- const querySnapshot = await getDocs6(q);
4480
- return querySnapshot.docs.map((doc34) => doc34.data());
4481
- }
4482
- /**
4483
- * Dohvata sve zdravstvene radnike za određenu kliniku
4484
- */
4485
- async getAllPractitionersByClinic(clinicId) {
4486
- const q = query6(
4487
- collection6(this.db, PRACTITIONERS_COLLECTION),
4488
- where6("clinics", "array-contains", clinicId),
4489
- where6("isActive", "==", true)
4490
- );
4491
- const querySnapshot = await getDocs6(q);
4492
- return querySnapshot.docs.map((doc34) => doc34.data());
4493
- }
4494
- /**
4495
- * Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
4496
- */
4497
- async getDraftPractitionersByClinic(clinicId) {
4498
- const q = query6(
4499
- collection6(this.db, PRACTITIONERS_COLLECTION),
4500
- where6("clinics", "array-contains", clinicId),
4501
- where6("status", "==", "draft" /* DRAFT */)
4136
+ const oldStoragePath = metadata.path;
4137
+ const fileNamePart = `${metadata.id}-${metadata.name}`;
4138
+ const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
4139
+ console.log(
4140
+ `[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
4502
4141
  );
4503
- const querySnapshot = await getDocs6(q);
4504
- return querySnapshot.docs.map((doc34) => doc34.data());
4505
- }
4506
- /**
4507
- * Updates a practitioner
4508
- */
4509
- async updatePractitioner(practitionerId, data) {
4142
+ const oldStorageFileRef = ref2(this.storage, oldStoragePath);
4143
+ const newStorageFileRef = ref2(this.storage, newStoragePath);
4510
4144
  try {
4511
- const validData = data;
4512
- const practitionerRef = doc8(
4513
- this.db,
4514
- PRACTITIONERS_COLLECTION,
4515
- practitionerId
4145
+ console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
4146
+ const fileBytes = await getBytes(oldStorageFileRef);
4147
+ console.log(
4148
+ `[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
4149
+ );
4150
+ console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
4151
+ await uploadBytes2(newStorageFileRef, fileBytes, {
4152
+ contentType: metadata.contentType
4153
+ });
4154
+ console.log(
4155
+ `[MediaService] Successfully uploaded bytes to ${newStoragePath}`
4156
+ );
4157
+ const newDownloadURL = await getDownloadURL2(newStorageFileRef);
4158
+ console.log(
4159
+ `[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
4516
4160
  );
4517
- const practitionerDoc = await getDoc11(practitionerRef);
4518
- if (!practitionerDoc.exists()) {
4519
- throw new Error(`Practitioner ${practitionerId} not found`);
4520
- }
4521
- const currentPractitioner = practitionerDoc.data();
4522
4161
  const updateData = {
4523
- ...validData,
4524
- updatedAt: serverTimestamp9()
4162
+ accessLevel: newAccessLevel,
4163
+ path: newStoragePath,
4164
+ url: newDownloadURL,
4165
+ updatedAt: Timestamp8.now()
4525
4166
  };
4526
- await updateDoc8(practitionerRef, updateData);
4527
- const updatedPractitioner = await this.getPractitioner(practitionerId);
4528
- if (!updatedPractitioner) {
4529
- throw new Error(
4530
- `Failed to retrieve updated practitioner ${practitionerId}`
4531
- );
4532
- }
4533
- return updatedPractitioner;
4534
- } catch (error) {
4535
- if (error instanceof z15.ZodError) {
4536
- throw new Error(`Invalid practitioner update data: ${error.message}`);
4537
- }
4538
- console.error(`Error updating practitioner ${practitionerId}:`, error);
4539
- throw error;
4540
- }
4541
- }
4542
- /**
4543
- * Adds a clinic to a practitioner
4544
- */
4545
- async addClinic(practitionerId, clinicId) {
4546
- var _a;
4547
- try {
4548
- const practitionerRef = doc8(
4549
- this.db,
4550
- PRACTITIONERS_COLLECTION,
4551
- practitionerId
4167
+ const metadataDocRef = doc8(this.db, MEDIA_METADATA_COLLECTION, mediaId);
4168
+ console.log(
4169
+ `[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
4170
+ updateData
4552
4171
  );
4553
- const practitionerDoc = await getDoc11(practitionerRef);
4554
- if (!practitionerDoc.exists()) {
4555
- throw new Error(`Practitioner ${practitionerId} not found`);
4556
- }
4557
- const practitioner = practitionerDoc.data();
4558
- if ((_a = practitioner.clinics) == null ? void 0 : _a.includes(clinicId)) {
4172
+ await updateDoc8(metadataDocRef, updateData);
4173
+ console.log(
4174
+ `[MediaService] Successfully updated Firestore metadata for ${mediaId}`
4175
+ );
4176
+ try {
4177
+ console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
4178
+ await deleteObject2(oldStorageFileRef);
4559
4179
  console.log(
4560
- `Clinic ${clinicId} already added to practitioner ${practitionerId}`
4180
+ `[MediaService] Successfully deleted old file from ${oldStoragePath}`
4181
+ );
4182
+ } catch (deleteError) {
4183
+ console.error(
4184
+ `[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
4185
+ deleteError
4561
4186
  );
4562
- return;
4563
4187
  }
4564
- await updateDoc8(practitionerRef, {
4565
- clinics: arrayUnion5(clinicId),
4566
- updatedAt: serverTimestamp9()
4567
- });
4188
+ return { ...metadata, ...updateData };
4568
4189
  } catch (error) {
4569
4190
  console.error(
4570
- `Error adding clinic ${clinicId} to practitioner ${practitionerId}:`,
4191
+ `[MediaService] Error updating media access level and moving file for ${mediaId}:`,
4571
4192
  error
4572
4193
  );
4194
+ if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
4195
+ console.warn(
4196
+ `[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
4197
+ );
4198
+ try {
4199
+ await deleteObject2(newStorageFileRef);
4200
+ console.warn(
4201
+ `[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
4202
+ );
4203
+ } catch (cleanupError) {
4204
+ console.error(
4205
+ `[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
4206
+ cleanupError
4207
+ );
4208
+ }
4209
+ }
4573
4210
  throw error;
4574
4211
  }
4575
4212
  }
4576
4213
  /**
4577
- * Removes a clinic from a practitioner
4214
+ * List all media for an owner, optionally filtered by collection and access level.
4215
+ * @param ownerId - ID of the owner.
4216
+ * @param collectionName - Optional: Filter by collection name.
4217
+ * @param accessLevel - Optional: Filter by access level.
4218
+ * @param count - Optional: Number of items to fetch.
4219
+ * @param startAfterId - Optional: ID of the document to start after (for pagination).
4578
4220
  */
4579
- async removeClinic(practitionerId, clinicId) {
4221
+ async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
4222
+ console.log(`[MediaService] Listing media for owner: ${ownerId}`);
4223
+ let qConstraints = [where6("ownerId", "==", ownerId)];
4224
+ if (collectionName) {
4225
+ qConstraints.push(where6("collectionName", "==", collectionName));
4226
+ }
4227
+ if (accessLevel) {
4228
+ qConstraints.push(where6("accessLevel", "==", accessLevel));
4229
+ }
4230
+ qConstraints.push(orderBy("createdAt", "desc"));
4231
+ if (count) {
4232
+ qConstraints.push(limit4(count));
4233
+ }
4234
+ if (startAfterId) {
4235
+ const startAfterDoc = await this.getMediaMetadata(startAfterId);
4236
+ if (startAfterDoc) {
4237
+ }
4238
+ }
4239
+ const finalQuery = query6(
4240
+ collection6(this.db, MEDIA_METADATA_COLLECTION),
4241
+ ...qConstraints
4242
+ );
4580
4243
  try {
4581
- const practitionerRef = doc8(
4582
- this.db,
4583
- PRACTITIONERS_COLLECTION,
4584
- practitionerId
4244
+ const querySnapshot = await getDocs6(finalQuery);
4245
+ const mediaList = querySnapshot.docs.map(
4246
+ (doc34) => doc34.data()
4585
4247
  );
4586
- const practitionerDoc = await getDoc11(practitionerRef);
4587
- if (!practitionerDoc.exists()) {
4588
- throw new Error(`Practitioner ${practitionerId} not found`);
4589
- }
4590
- await updateDoc8(practitionerRef, {
4591
- clinics: arrayRemove4(clinicId),
4592
- updatedAt: serverTimestamp9()
4593
- });
4248
+ console.log(`[MediaService] Found ${mediaList.length} media items.`);
4249
+ return mediaList;
4594
4250
  } catch (error) {
4595
- console.error(
4596
- `Error removing clinic ${clinicId} from practitioner ${practitionerId}:`,
4597
- error
4598
- );
4251
+ console.error("[MediaService] Error listing media:", error);
4599
4252
  throw error;
4600
4253
  }
4601
4254
  }
4602
4255
  /**
4603
- * Deaktivira profil zdravstvenog radnika
4256
+ * Get download URL for media. (Convenience, as URL is in metadata)
4257
+ * @param mediaId - ID of the media.
4604
4258
  */
4605
- async deactivatePractitioner(practitionerId) {
4606
- await this.updatePractitioner(practitionerId, {
4607
- isActive: false
4608
- });
4259
+ async getMediaDownloadUrl(mediaId) {
4260
+ console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
4261
+ const metadata = await this.getMediaMetadata(mediaId);
4262
+ if (metadata && metadata.url) {
4263
+ console.log(`[MediaService] URL found: ${metadata.url}`);
4264
+ return metadata.url;
4265
+ }
4266
+ console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
4267
+ return null;
4609
4268
  }
4610
- /**
4611
- * Aktivira profil zdravstvenog radnika
4612
- */
4613
- async activatePractitioner(practitionerId) {
4614
- await this.updatePractitioner(practitionerId, {
4615
- isActive: true
4616
- });
4269
+ };
4270
+
4271
+ // src/validations/practitioner.schema.ts
4272
+ import { z as z14 } from "zod";
4273
+ import { Timestamp as Timestamp9 } from "firebase/firestore";
4274
+
4275
+ // src/backoffice/types/static/certification.types.ts
4276
+ var CertificationLevel = /* @__PURE__ */ ((CertificationLevel2) => {
4277
+ CertificationLevel2["AESTHETICIAN"] = "aesthetician";
4278
+ CertificationLevel2["NURSE_ASSISTANT"] = "nurse_assistant";
4279
+ CertificationLevel2["NURSE"] = "nurse";
4280
+ CertificationLevel2["NURSE_PRACTITIONER"] = "nurse_practitioner";
4281
+ CertificationLevel2["PHYSICIAN_ASSISTANT"] = "physician_assistant";
4282
+ CertificationLevel2["DOCTOR"] = "doctor";
4283
+ CertificationLevel2["SPECIALIST"] = "specialist";
4284
+ CertificationLevel2["PLASTIC_SURGEON"] = "plastic_surgeon";
4285
+ return CertificationLevel2;
4286
+ })(CertificationLevel || {});
4287
+ var CertificationSpecialty = /* @__PURE__ */ ((CertificationSpecialty3) => {
4288
+ CertificationSpecialty3["LASER"] = "laser";
4289
+ CertificationSpecialty3["INJECTABLES"] = "injectables";
4290
+ CertificationSpecialty3["CHEMICAL_PEELS"] = "chemical_peels";
4291
+ CertificationSpecialty3["MICRODERMABRASION"] = "microdermabrasion";
4292
+ CertificationSpecialty3["BODY_CONTOURING"] = "body_contouring";
4293
+ CertificationSpecialty3["SKIN_CARE"] = "skin_care";
4294
+ CertificationSpecialty3["WOUND_CARE"] = "wound_care";
4295
+ CertificationSpecialty3["ANESTHESIA"] = "anesthesia";
4296
+ return CertificationSpecialty3;
4297
+ })(CertificationSpecialty || {});
4298
+
4299
+ // src/validations/practitioner.schema.ts
4300
+ var practitionerBasicInfoSchema = z14.object({
4301
+ firstName: z14.string().min(2).max(50),
4302
+ lastName: z14.string().min(2).max(50),
4303
+ title: z14.string().min(2).max(100),
4304
+ email: z14.string().email(),
4305
+ phoneNumber: z14.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
4306
+ dateOfBirth: z14.instanceof(Timestamp9).or(z14.date()),
4307
+ gender: z14.enum(["male", "female", "other"]),
4308
+ profileImageUrl: mediaResourceSchema.optional(),
4309
+ bio: z14.string().max(1e3).optional(),
4310
+ languages: z14.array(z14.string()).min(1)
4311
+ });
4312
+ var practitionerCertificationSchema = z14.object({
4313
+ level: z14.nativeEnum(CertificationLevel),
4314
+ specialties: z14.array(z14.nativeEnum(CertificationSpecialty)),
4315
+ licenseNumber: z14.string().min(3).max(50),
4316
+ issuingAuthority: z14.string().min(2).max(100),
4317
+ issueDate: z14.instanceof(Timestamp9).or(z14.date()),
4318
+ expiryDate: z14.instanceof(Timestamp9).or(z14.date()).optional(),
4319
+ verificationStatus: z14.enum(["pending", "verified", "rejected"])
4320
+ });
4321
+ var timeSlotSchema = z14.object({
4322
+ start: z14.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format"),
4323
+ end: z14.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/, "Invalid time format")
4324
+ }).nullable();
4325
+ var practitionerWorkingHoursSchema = z14.object({
4326
+ practitionerId: z14.string().min(1),
4327
+ clinicId: z14.string().min(1),
4328
+ monday: timeSlotSchema,
4329
+ tuesday: timeSlotSchema,
4330
+ wednesday: timeSlotSchema,
4331
+ thursday: timeSlotSchema,
4332
+ friday: timeSlotSchema,
4333
+ saturday: timeSlotSchema,
4334
+ sunday: timeSlotSchema,
4335
+ createdAt: z14.instanceof(Timestamp9).or(z14.date()),
4336
+ updatedAt: z14.instanceof(Timestamp9).or(z14.date())
4337
+ });
4338
+ var practitionerClinicWorkingHoursSchema = z14.object({
4339
+ clinicId: z14.string().min(1),
4340
+ workingHours: z14.object({
4341
+ monday: timeSlotSchema,
4342
+ tuesday: timeSlotSchema,
4343
+ wednesday: timeSlotSchema,
4344
+ thursday: timeSlotSchema,
4345
+ friday: timeSlotSchema,
4346
+ saturday: timeSlotSchema,
4347
+ sunday: timeSlotSchema
4348
+ }),
4349
+ isActive: z14.boolean(),
4350
+ createdAt: z14.instanceof(Timestamp9).or(z14.date()),
4351
+ updatedAt: z14.instanceof(Timestamp9).or(z14.date())
4352
+ });
4353
+ var practitionerSchema = z14.object({
4354
+ id: z14.string().min(1),
4355
+ userRef: z14.string().min(1),
4356
+ basicInfo: practitionerBasicInfoSchema,
4357
+ certification: practitionerCertificationSchema,
4358
+ clinics: z14.array(z14.string()),
4359
+ clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema),
4360
+ clinicsInfo: z14.array(clinicInfoSchema),
4361
+ procedures: z14.array(z14.string()),
4362
+ proceduresInfo: z14.array(procedureSummaryInfoSchema),
4363
+ reviewInfo: practitionerReviewInfoSchema,
4364
+ isActive: z14.boolean(),
4365
+ isVerified: z14.boolean(),
4366
+ status: z14.nativeEnum(PractitionerStatus),
4367
+ createdAt: z14.instanceof(Timestamp9).or(z14.date()),
4368
+ updatedAt: z14.instanceof(Timestamp9).or(z14.date())
4369
+ });
4370
+ var createPractitionerSchema = z14.object({
4371
+ userRef: z14.string().min(1),
4372
+ basicInfo: practitionerBasicInfoSchema,
4373
+ certification: practitionerCertificationSchema,
4374
+ clinics: z14.array(z14.string()).optional(),
4375
+ clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema).optional(),
4376
+ clinicsInfo: z14.array(clinicInfoSchema).optional(),
4377
+ proceduresInfo: z14.array(procedureSummaryInfoSchema).optional(),
4378
+ isActive: z14.boolean(),
4379
+ isVerified: z14.boolean(),
4380
+ status: z14.nativeEnum(PractitionerStatus).optional()
4381
+ });
4382
+ var createDraftPractitionerSchema = z14.object({
4383
+ basicInfo: practitionerBasicInfoSchema,
4384
+ certification: practitionerCertificationSchema,
4385
+ clinics: z14.array(z14.string()).optional(),
4386
+ clinicWorkingHours: z14.array(practitionerClinicWorkingHoursSchema).optional(),
4387
+ clinicsInfo: z14.array(clinicInfoSchema).optional(),
4388
+ proceduresInfo: z14.array(procedureSummaryInfoSchema).optional(),
4389
+ isActive: z14.boolean().optional().default(false),
4390
+ isVerified: z14.boolean().optional().default(false)
4391
+ });
4392
+ var practitionerTokenSchema = z14.object({
4393
+ id: z14.string().min(1),
4394
+ token: z14.string().min(6),
4395
+ practitionerId: z14.string().min(1),
4396
+ email: z14.string().email(),
4397
+ clinicId: z14.string().min(1),
4398
+ status: z14.nativeEnum(PractitionerTokenStatus),
4399
+ createdBy: z14.string().min(1),
4400
+ createdAt: z14.instanceof(Timestamp9).or(z14.date()),
4401
+ expiresAt: z14.instanceof(Timestamp9).or(z14.date()),
4402
+ usedBy: z14.string().optional(),
4403
+ usedAt: z14.instanceof(Timestamp9).or(z14.date()).optional()
4404
+ });
4405
+ var createPractitionerTokenSchema = z14.object({
4406
+ practitionerId: z14.string().min(1),
4407
+ email: z14.string().email(),
4408
+ clinicId: z14.string().min(1),
4409
+ expiresAt: z14.date().optional()
4410
+ });
4411
+ var practitionerSignupSchema = z14.object({
4412
+ email: z14.string().email(),
4413
+ password: z14.string().min(8),
4414
+ firstName: z14.string().min(2).max(50).optional(),
4415
+ lastName: z14.string().min(2).max(50).optional(),
4416
+ token: z14.string().optional(),
4417
+ profileData: z14.object({
4418
+ basicInfo: z14.object({
4419
+ phoneNumber: z14.string().optional(),
4420
+ profileImageUrl: mediaResourceSchema.optional(),
4421
+ gender: z14.enum(["male", "female", "other"]).optional(),
4422
+ bio: z14.string().optional()
4423
+ }).optional(),
4424
+ certification: z14.any().optional()
4425
+ }).optional()
4426
+ });
4427
+
4428
+ // src/services/practitioner/practitioner.service.ts
4429
+ import { z as z15 } from "zod";
4430
+ import { distanceBetween } from "geofire-common";
4431
+ var PractitionerService = class extends BaseService {
4432
+ constructor(db, auth, app, clinicService) {
4433
+ super(db, auth, app);
4434
+ this.clinicService = clinicService;
4435
+ this.mediaService = new MediaService(db, auth, app);
4436
+ }
4437
+ getClinicService() {
4438
+ if (!this.clinicService) {
4439
+ throw new Error("Clinic service not initialized!");
4440
+ }
4441
+ return this.clinicService;
4442
+ }
4443
+ setClinicService(clinicService) {
4444
+ this.clinicService = clinicService;
4445
+ }
4446
+ /**
4447
+ * Handles profile photo upload for practitioners
4448
+ * @param profilePhoto - MediaResource (File, Blob, or URL string)
4449
+ * @param practitionerId - ID of the practitioner
4450
+ * @returns URL string of the uploaded or existing photo
4451
+ */
4452
+ async handleProfilePhotoUpload(profilePhoto, practitionerId) {
4453
+ if (!profilePhoto) {
4454
+ return void 0;
4455
+ }
4456
+ if (typeof profilePhoto === "string") {
4457
+ return profilePhoto;
4458
+ }
4459
+ if (profilePhoto instanceof File || profilePhoto instanceof Blob) {
4460
+ console.log(
4461
+ `[PractitionerService] Uploading profile photo for practitioner ${practitionerId}`
4462
+ );
4463
+ const mediaMetadata = await this.mediaService.uploadMedia(
4464
+ profilePhoto,
4465
+ practitionerId,
4466
+ // Using practitionerId as ownerId
4467
+ "public" /* PUBLIC */,
4468
+ // Profile photos should be public
4469
+ "practitioner_profile_photos",
4470
+ profilePhoto instanceof File ? profilePhoto.name : `profile_photo_${practitionerId}`
4471
+ );
4472
+ return mediaMetadata.url;
4473
+ }
4474
+ return void 0;
4475
+ }
4476
+ /**
4477
+ * Processes BasicPractitionerInfo to handle profile photo uploads
4478
+ * @param basicInfo - The basic info containing potential MediaResource profile photo
4479
+ * @param practitionerId - ID of the practitioner
4480
+ * @returns Processed basic info with URL string for profileImageUrl
4481
+ */
4482
+ async processBasicInfo(basicInfo, practitionerId) {
4483
+ const processedBasicInfo = { ...basicInfo };
4484
+ if (basicInfo.profileImageUrl) {
4485
+ const uploadedUrl = await this.handleProfilePhotoUpload(
4486
+ basicInfo.profileImageUrl,
4487
+ practitionerId
4488
+ );
4489
+ processedBasicInfo.profileImageUrl = uploadedUrl;
4490
+ }
4491
+ return processedBasicInfo;
4492
+ }
4493
+ /**
4494
+ * Creates a new practitioner
4495
+ */
4496
+ async createPractitioner(data) {
4497
+ try {
4498
+ const validData = createPractitionerSchema.parse(data);
4499
+ const practitionerId = this.generateId();
4500
+ const reviewInfo = {
4501
+ totalReviews: 0,
4502
+ averageRating: 0,
4503
+ knowledgeAndExpertise: 0,
4504
+ communicationSkills: 0,
4505
+ bedSideManner: 0,
4506
+ thoroughness: 0,
4507
+ trustworthiness: 0,
4508
+ recommendationPercentage: 0
4509
+ };
4510
+ const practitioner = {
4511
+ id: practitionerId,
4512
+ userRef: validData.userRef,
4513
+ basicInfo: await this.processBasicInfo(
4514
+ validData.basicInfo,
4515
+ practitionerId
4516
+ ),
4517
+ certification: validData.certification,
4518
+ clinics: validData.clinics || [],
4519
+ clinicWorkingHours: validData.clinicWorkingHours || [],
4520
+ clinicsInfo: [],
4521
+ procedures: [],
4522
+ proceduresInfo: [],
4523
+ reviewInfo,
4524
+ isActive: validData.isActive !== void 0 ? validData.isActive : true,
4525
+ isVerified: validData.isVerified !== void 0 ? validData.isVerified : false,
4526
+ status: validData.status || "active" /* ACTIVE */,
4527
+ createdAt: serverTimestamp9(),
4528
+ updatedAt: serverTimestamp9()
4529
+ };
4530
+ practitionerSchema.parse({
4531
+ ...practitioner,
4532
+ createdAt: Timestamp10.now(),
4533
+ updatedAt: Timestamp10.now()
4534
+ });
4535
+ const practitionerRef = doc9(
4536
+ this.db,
4537
+ PRACTITIONERS_COLLECTION,
4538
+ practitionerId
4539
+ );
4540
+ await setDoc8(practitionerRef, practitioner);
4541
+ const createdPractitioner = await this.getPractitioner(practitionerId);
4542
+ if (!createdPractitioner) {
4543
+ throw new Error(
4544
+ `Failed to retrieve created practitioner ${practitionerId}`
4545
+ );
4546
+ }
4547
+ return createdPractitioner;
4548
+ } catch (error) {
4549
+ if (error instanceof z15.ZodError) {
4550
+ throw new Error(`Invalid practitioner data: ${error.message}`);
4551
+ }
4552
+ console.error("Error creating practitioner:", error);
4553
+ throw error;
4554
+ }
4555
+ }
4556
+ /**
4557
+ * Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
4558
+ * Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
4559
+ * @param data Podaci za kreiranje draft profila
4560
+ * @param createdBy ID administratora koji kreira profil
4561
+ * @param clinicId ID klinike za koju se kreira profil
4562
+ * @returns Objekt koji sadrži kreirani draft profil i token za registraciju
4563
+ */
4564
+ async createDraftPractitioner(data, createdBy, clinicId) {
4565
+ try {
4566
+ const validatedData = createDraftPractitionerSchema.parse(data);
4567
+ const clinic = await this.getClinicService().getClinic(clinicId);
4568
+ if (!clinic) {
4569
+ throw new Error(`Clinic ${clinicId} not found`);
4570
+ }
4571
+ const clinicsToAdd = /* @__PURE__ */ new Set([clinicId]);
4572
+ if (data.clinics && data.clinics.length > 0) {
4573
+ for (const cId of data.clinics) {
4574
+ if (cId !== clinicId) {
4575
+ const otherClinic = await this.getClinicService().getClinic(cId);
4576
+ if (!otherClinic) {
4577
+ throw new Error(`Clinic ${cId} not found`);
4578
+ }
4579
+ }
4580
+ clinicsToAdd.add(cId);
4581
+ }
4582
+ }
4583
+ const clinics = Array.from(clinicsToAdd);
4584
+ const defaultReviewInfo = {
4585
+ totalReviews: 0,
4586
+ averageRating: 0,
4587
+ knowledgeAndExpertise: 0,
4588
+ communicationSkills: 0,
4589
+ bedSideManner: 0,
4590
+ thoroughness: 0,
4591
+ trustworthiness: 0,
4592
+ recommendationPercentage: 0
4593
+ };
4594
+ const practitionerId = this.generateId();
4595
+ const clinicsInfo = [];
4596
+ for (const cId of clinics) {
4597
+ const clinicData = await this.getClinicService().getClinic(cId);
4598
+ if (clinicData) {
4599
+ clinicsInfo.push({
4600
+ id: clinicData.id,
4601
+ name: clinicData.name,
4602
+ location: clinicData.location,
4603
+ contactInfo: clinicData.contactInfo,
4604
+ // Make sure we're using the right property for featuredPhoto
4605
+ featuredPhoto: clinicData.featuredPhotos && clinicData.featuredPhotos.length > 0 ? typeof clinicData.featuredPhotos[0] === "string" ? clinicData.featuredPhotos[0] : "" : (typeof clinicData.coverPhoto === "string" ? clinicData.coverPhoto : "") || "",
4606
+ description: clinicData.description || null
4607
+ });
4608
+ }
4609
+ }
4610
+ const finalClinicsInfo = validatedData.clinicsInfo && validatedData.clinicsInfo.length > 0 ? validatedData.clinicsInfo : clinicsInfo;
4611
+ const proceduresInfo = [];
4612
+ const practitionerData = {
4613
+ id: practitionerId,
4614
+ userRef: "",
4615
+ // Prazno - biće popunjeno kada korisnik kreira nalog
4616
+ basicInfo: await this.processBasicInfo(
4617
+ validatedData.basicInfo,
4618
+ practitionerId
4619
+ ),
4620
+ certification: validatedData.certification,
4621
+ clinics,
4622
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
4623
+ clinicsInfo: finalClinicsInfo,
4624
+ procedures: [],
4625
+ proceduresInfo,
4626
+ reviewInfo: defaultReviewInfo,
4627
+ isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
4628
+ isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
4629
+ status: "draft" /* DRAFT */,
4630
+ createdAt: serverTimestamp9(),
4631
+ updatedAt: serverTimestamp9()
4632
+ };
4633
+ practitionerSchema.parse({
4634
+ ...practitionerData,
4635
+ userRef: "temp-for-validation",
4636
+ createdAt: Timestamp10.now(),
4637
+ updatedAt: Timestamp10.now()
4638
+ });
4639
+ await setDoc8(
4640
+ doc9(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
4641
+ practitionerData
4642
+ );
4643
+ const savedPractitioner = await this.getPractitioner(practitionerData.id);
4644
+ if (!savedPractitioner) {
4645
+ throw new Error("Failed to create draft practitioner profile");
4646
+ }
4647
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
4648
+ const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
4649
+ const token = {
4650
+ id: this.generateId(),
4651
+ token: tokenString,
4652
+ practitionerId,
4653
+ email: practitionerData.basicInfo.email,
4654
+ clinicId,
4655
+ status: "active" /* ACTIVE */,
4656
+ createdBy,
4657
+ createdAt: Timestamp10.now(),
4658
+ expiresAt: Timestamp10.fromDate(expiration)
4659
+ };
4660
+ practitionerTokenSchema.parse(token);
4661
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
4662
+ await setDoc8(doc9(this.db, tokenPath), token);
4663
+ return { practitioner: savedPractitioner, token };
4664
+ } catch (error) {
4665
+ if (error instanceof z15.ZodError) {
4666
+ throw new Error("Invalid practitioner data: " + error.message);
4667
+ }
4668
+ throw error;
4669
+ }
4670
+ }
4671
+ /**
4672
+ * Creates a token for inviting practitioner to claim their profile
4673
+ * @param data Data for creating token
4674
+ * @param createdBy ID of the user creating the token
4675
+ * @returns Created token
4676
+ */
4677
+ async createPractitionerToken(data, createdBy) {
4678
+ try {
4679
+ const validatedData = createPractitionerTokenSchema.parse(data);
4680
+ const practitioner = await this.getPractitioner(
4681
+ validatedData.practitionerId
4682
+ );
4683
+ if (!practitioner) {
4684
+ throw new Error("Practitioner not found");
4685
+ }
4686
+ if (practitioner.status !== "draft" /* DRAFT */) {
4687
+ throw new Error(
4688
+ "Can only create tokens for practitioners in DRAFT status"
4689
+ );
4690
+ }
4691
+ const clinic = await this.getClinicService().getClinic(
4692
+ validatedData.clinicId
4693
+ );
4694
+ if (!clinic) {
4695
+ throw new Error(`Clinic ${validatedData.clinicId} not found`);
4696
+ }
4697
+ if (!practitioner.clinics.includes(validatedData.clinicId)) {
4698
+ throw new Error("Practitioner is not associated with this clinic");
4699
+ }
4700
+ const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
4701
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
4702
+ const token = {
4703
+ id: this.generateId(),
4704
+ token: tokenString,
4705
+ practitionerId: validatedData.practitionerId,
4706
+ email: validatedData.email,
4707
+ clinicId: validatedData.clinicId,
4708
+ status: "active" /* ACTIVE */,
4709
+ createdBy,
4710
+ createdAt: Timestamp10.now(),
4711
+ expiresAt: Timestamp10.fromDate(expiration)
4712
+ };
4713
+ practitionerTokenSchema.parse(token);
4714
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
4715
+ await setDoc8(doc9(this.db, tokenPath), token);
4716
+ return token;
4717
+ } catch (error) {
4718
+ if (error instanceof z15.ZodError) {
4719
+ throw new Error("Invalid token data: " + error.message);
4720
+ }
4721
+ throw error;
4722
+ }
4723
+ }
4724
+ /**
4725
+ * Gets active tokens for a practitioner
4726
+ * @param practitionerId ID of the practitioner
4727
+ * @returns Array of active tokens
4728
+ */
4729
+ async getPractitionerActiveTokens(practitionerId) {
4730
+ const tokensRef = collection7(
4731
+ this.db,
4732
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
4733
+ );
4734
+ const q = query7(
4735
+ tokensRef,
4736
+ where7("status", "==", "active" /* ACTIVE */),
4737
+ where7("expiresAt", ">", Timestamp10.now())
4738
+ );
4739
+ const querySnapshot = await getDocs7(q);
4740
+ return querySnapshot.docs.map((doc34) => doc34.data());
4741
+ }
4742
+ /**
4743
+ * Gets a token by its string value and validates it
4744
+ * @param tokenString The token string to find
4745
+ * @returns The token if found and valid, null otherwise
4746
+ */
4747
+ async validateToken(tokenString) {
4748
+ const practitionersRef = collection7(this.db, PRACTITIONERS_COLLECTION);
4749
+ const practitionersSnapshot = await getDocs7(practitionersRef);
4750
+ for (const practitionerDoc of practitionersSnapshot.docs) {
4751
+ const practitionerId = practitionerDoc.id;
4752
+ const tokensRef = collection7(
4753
+ this.db,
4754
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
4755
+ );
4756
+ console.log(
4757
+ `[PRACTITIONER] Validating token for practitioner ${practitionerId}`,
4758
+ {
4759
+ tokenString,
4760
+ timestamp: Timestamp10.now().toDate()
4761
+ }
4762
+ );
4763
+ const q = query7(
4764
+ tokensRef,
4765
+ where7("token", "==", tokenString),
4766
+ where7("status", "==", "active" /* ACTIVE */),
4767
+ where7("expiresAt", ">", Timestamp10.now())
4768
+ );
4769
+ try {
4770
+ const tokenSnapshot = await getDocs7(q);
4771
+ console.log(
4772
+ `[PRACTITIONER] Token query results for practitioner ${practitionerId}`,
4773
+ {
4774
+ found: !tokenSnapshot.empty,
4775
+ count: tokenSnapshot.size
4776
+ }
4777
+ );
4778
+ if (!tokenSnapshot.empty) {
4779
+ const tokenData = tokenSnapshot.docs[0].data();
4780
+ console.log(`[PRACTITIONER] Valid token found`, {
4781
+ tokenId: tokenData.id,
4782
+ expiresAt: tokenData.expiresAt.toDate()
4783
+ });
4784
+ return tokenData;
4785
+ }
4786
+ } catch (error) {
4787
+ console.error(
4788
+ `[PRACTITIONER] Error validating token for practitioner ${practitionerId}:`,
4789
+ error
4790
+ );
4791
+ throw error;
4792
+ }
4793
+ }
4794
+ return null;
4795
+ }
4796
+ /**
4797
+ * Marks a token as used
4798
+ * @param tokenId ID of the token
4799
+ * @param practitionerId ID of the practitioner
4800
+ * @param userId ID of the user using the token
4801
+ */
4802
+ async markTokenAsUsed(tokenId, practitionerId, userId) {
4803
+ const tokenRef = doc9(
4804
+ this.db,
4805
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
4806
+ );
4807
+ await updateDoc9(tokenRef, {
4808
+ status: "used" /* USED */,
4809
+ usedBy: userId,
4810
+ usedAt: Timestamp10.now()
4811
+ });
4812
+ }
4813
+ /**
4814
+ * Dohvata zdravstvenog radnika po ID-u
4815
+ */
4816
+ async getPractitioner(practitionerId) {
4817
+ const practitionerDoc = await getDoc12(
4818
+ doc9(this.db, PRACTITIONERS_COLLECTION, practitionerId)
4819
+ );
4820
+ if (!practitionerDoc.exists()) {
4821
+ return null;
4822
+ }
4823
+ return practitionerDoc.data();
4824
+ }
4825
+ /**
4826
+ * Dohvata zdravstvenog radnika po User ID-u
4827
+ */
4828
+ async getPractitionerByUserRef(userRef) {
4829
+ const q = query7(
4830
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4831
+ where7("userRef", "==", userRef)
4832
+ );
4833
+ const querySnapshot = await getDocs7(q);
4834
+ if (querySnapshot.empty) {
4835
+ return null;
4836
+ }
4837
+ return querySnapshot.docs[0].data();
4838
+ }
4839
+ /**
4840
+ * Dohvata sve zdravstvene radnike za određenu kliniku sa statusom ACTIVE
4841
+ */
4842
+ async getPractitionersByClinic(clinicId) {
4843
+ const q = query7(
4844
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4845
+ where7("clinics", "array-contains", clinicId),
4846
+ where7("isActive", "==", true),
4847
+ where7("status", "==", "active" /* ACTIVE */)
4848
+ );
4849
+ const querySnapshot = await getDocs7(q);
4850
+ return querySnapshot.docs.map((doc34) => doc34.data());
4851
+ }
4852
+ /**
4853
+ * Dohvata sve zdravstvene radnike za određenu kliniku
4854
+ */
4855
+ async getAllPractitionersByClinic(clinicId) {
4856
+ const q = query7(
4857
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4858
+ where7("clinics", "array-contains", clinicId),
4859
+ where7("isActive", "==", true)
4860
+ );
4861
+ const querySnapshot = await getDocs7(q);
4862
+ return querySnapshot.docs.map((doc34) => doc34.data());
4863
+ }
4864
+ /**
4865
+ * Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
4866
+ */
4867
+ async getDraftPractitionersByClinic(clinicId) {
4868
+ const q = query7(
4869
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4870
+ where7("clinics", "array-contains", clinicId),
4871
+ where7("status", "==", "draft" /* DRAFT */)
4872
+ );
4873
+ const querySnapshot = await getDocs7(q);
4874
+ return querySnapshot.docs.map((doc34) => doc34.data());
4875
+ }
4876
+ /**
4877
+ * Updates a practitioner
4878
+ */
4879
+ async updatePractitioner(practitionerId, data) {
4880
+ try {
4881
+ const validData = data;
4882
+ const practitionerRef = doc9(
4883
+ this.db,
4884
+ PRACTITIONERS_COLLECTION,
4885
+ practitionerId
4886
+ );
4887
+ const practitionerDoc = await getDoc12(practitionerRef);
4888
+ if (!practitionerDoc.exists()) {
4889
+ throw new Error(`Practitioner ${practitionerId} not found`);
4890
+ }
4891
+ const currentPractitioner = practitionerDoc.data();
4892
+ let processedData = { ...validData };
4893
+ if (validData.basicInfo) {
4894
+ processedData.basicInfo = await this.processBasicInfo(
4895
+ validData.basicInfo,
4896
+ practitionerId
4897
+ );
4898
+ }
4899
+ const updateData = {
4900
+ ...processedData,
4901
+ updatedAt: serverTimestamp9()
4902
+ };
4903
+ await updateDoc9(practitionerRef, updateData);
4904
+ const updatedPractitioner = await this.getPractitioner(practitionerId);
4905
+ if (!updatedPractitioner) {
4906
+ throw new Error(
4907
+ `Failed to retrieve updated practitioner ${practitionerId}`
4908
+ );
4909
+ }
4910
+ return updatedPractitioner;
4911
+ } catch (error) {
4912
+ if (error instanceof z15.ZodError) {
4913
+ throw new Error(`Invalid practitioner update data: ${error.message}`);
4914
+ }
4915
+ console.error(`Error updating practitioner ${practitionerId}:`, error);
4916
+ throw error;
4917
+ }
4918
+ }
4919
+ /**
4920
+ * Adds a clinic to a practitioner
4921
+ */
4922
+ async addClinic(practitionerId, clinicId) {
4923
+ var _a;
4924
+ try {
4925
+ const practitionerRef = doc9(
4926
+ this.db,
4927
+ PRACTITIONERS_COLLECTION,
4928
+ practitionerId
4929
+ );
4930
+ const practitionerDoc = await getDoc12(practitionerRef);
4931
+ if (!practitionerDoc.exists()) {
4932
+ throw new Error(`Practitioner ${practitionerId} not found`);
4933
+ }
4934
+ const practitioner = practitionerDoc.data();
4935
+ if ((_a = practitioner.clinics) == null ? void 0 : _a.includes(clinicId)) {
4936
+ console.log(
4937
+ `Clinic ${clinicId} already added to practitioner ${practitionerId}`
4938
+ );
4939
+ return;
4940
+ }
4941
+ await updateDoc9(practitionerRef, {
4942
+ clinics: arrayUnion5(clinicId),
4943
+ updatedAt: serverTimestamp9()
4944
+ });
4945
+ } catch (error) {
4946
+ console.error(
4947
+ `Error adding clinic ${clinicId} to practitioner ${practitionerId}:`,
4948
+ error
4949
+ );
4950
+ throw error;
4951
+ }
4952
+ }
4953
+ /**
4954
+ * Removes a clinic from a practitioner
4955
+ */
4956
+ async removeClinic(practitionerId, clinicId) {
4957
+ try {
4958
+ const practitionerRef = doc9(
4959
+ this.db,
4960
+ PRACTITIONERS_COLLECTION,
4961
+ practitionerId
4962
+ );
4963
+ const practitionerDoc = await getDoc12(practitionerRef);
4964
+ if (!practitionerDoc.exists()) {
4965
+ throw new Error(`Practitioner ${practitionerId} not found`);
4966
+ }
4967
+ await updateDoc9(practitionerRef, {
4968
+ clinics: arrayRemove4(clinicId),
4969
+ updatedAt: serverTimestamp9()
4970
+ });
4971
+ } catch (error) {
4972
+ console.error(
4973
+ `Error removing clinic ${clinicId} from practitioner ${practitionerId}:`,
4974
+ error
4975
+ );
4976
+ throw error;
4977
+ }
4978
+ }
4979
+ /**
4980
+ * Deaktivira profil zdravstvenog radnika
4981
+ */
4982
+ async deactivatePractitioner(practitionerId) {
4983
+ await this.updatePractitioner(practitionerId, {
4984
+ isActive: false
4985
+ });
4986
+ }
4987
+ /**
4988
+ * Aktivira profil zdravstvenog radnika
4989
+ */
4990
+ async activatePractitioner(practitionerId) {
4991
+ await this.updatePractitioner(practitionerId, {
4992
+ isActive: true
4993
+ });
4617
4994
  }
4618
4995
  /**
4619
4996
  * Briše profil zdravstvenog radnika
@@ -4623,7 +5000,7 @@ var PractitionerService = class extends BaseService {
4623
5000
  if (!practitioner) {
4624
5001
  throw new Error("Practitioner not found");
4625
5002
  }
4626
- await deleteDoc2(doc8(this.db, PRACTITIONERS_COLLECTION, practitionerId));
5003
+ await deleteDoc3(doc9(this.db, PRACTITIONERS_COLLECTION, practitionerId));
4627
5004
  }
4628
5005
  /**
4629
5006
  * Validates a registration token and claims the associated draft practitioner profile
@@ -4692,21 +5069,21 @@ var PractitionerService = class extends BaseService {
4692
5069
  try {
4693
5070
  const constraints = [];
4694
5071
  if (!(options == null ? void 0 : options.includeDraftPractitioners)) {
4695
- constraints.push(where6("status", "==", "active" /* ACTIVE */));
5072
+ constraints.push(where7("status", "==", "active" /* ACTIVE */));
4696
5073
  }
4697
- constraints.push(orderBy("basicInfo.lastName", "asc"));
4698
- constraints.push(orderBy("basicInfo.firstName", "asc"));
5074
+ constraints.push(orderBy2("basicInfo.lastName", "asc"));
5075
+ constraints.push(orderBy2("basicInfo.firstName", "asc"));
4699
5076
  if ((options == null ? void 0 : options.pagination) && options.pagination > 0) {
4700
5077
  if (options.lastDoc) {
4701
5078
  constraints.push(startAfter4(options.lastDoc));
4702
5079
  }
4703
- constraints.push(limit4(options.pagination));
5080
+ constraints.push(limit5(options.pagination));
4704
5081
  }
4705
- const q = query6(
4706
- collection6(this.db, PRACTITIONERS_COLLECTION),
5082
+ const q = query7(
5083
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4707
5084
  ...constraints
4708
5085
  );
4709
- const querySnapshot = await getDocs6(q);
5086
+ const querySnapshot = await getDocs7(q);
4710
5087
  const practitioners = querySnapshot.docs.map(
4711
5088
  (doc34) => doc34.data()
4712
5089
  );
@@ -4751,31 +5128,31 @@ var PractitionerService = class extends BaseService {
4751
5128
  );
4752
5129
  const constraints = [];
4753
5130
  if (!filters.includeDraftPractitioners) {
4754
- constraints.push(where6("status", "==", "active" /* ACTIVE */));
5131
+ constraints.push(where7("status", "==", "active" /* ACTIVE */));
4755
5132
  }
4756
- constraints.push(where6("isActive", "==", true));
5133
+ constraints.push(where7("isActive", "==", true));
4757
5134
  if (filters.certifications && filters.certifications.length > 0) {
4758
5135
  constraints.push(
4759
- where6(
5136
+ where7(
4760
5137
  "certification.certifications",
4761
5138
  "array-contains-any",
4762
5139
  filters.certifications
4763
5140
  )
4764
5141
  );
4765
5142
  }
4766
- constraints.push(orderBy("basicInfo.lastName", "asc"));
4767
- constraints.push(orderBy("basicInfo.firstName", "asc"));
5143
+ constraints.push(orderBy2("basicInfo.lastName", "asc"));
5144
+ constraints.push(orderBy2("basicInfo.firstName", "asc"));
4768
5145
  if (filters.pagination && filters.pagination > 0) {
4769
5146
  if (filters.lastDoc) {
4770
5147
  constraints.push(startAfter4(filters.lastDoc));
4771
5148
  }
4772
- constraints.push(limit4(filters.pagination));
5149
+ constraints.push(limit5(filters.pagination));
4773
5150
  }
4774
- const q = query6(
4775
- collection6(this.db, PRACTITIONERS_COLLECTION),
5151
+ const q = query7(
5152
+ collection7(this.db, PRACTITIONERS_COLLECTION),
4776
5153
  ...constraints
4777
5154
  );
4778
- const querySnapshot = await getDocs6(q);
5155
+ const querySnapshot = await getDocs7(q);
4779
5156
  console.log(
4780
5157
  `[PRACTITIONER_SERVICE] Found ${querySnapshot.docs.length} practitioners with base query`
4781
5158
  );
@@ -4901,7 +5278,7 @@ var UserService = class extends BaseService {
4901
5278
  updatedAt: serverTimestamp10(),
4902
5279
  lastLoginAt: serverTimestamp10()
4903
5280
  };
4904
- await setDoc8(doc9(this.db, USERS_COLLECTION, userData.uid), userData);
5281
+ await setDoc9(doc10(this.db, USERS_COLLECTION, userData.uid), userData);
4905
5282
  if (options == null ? void 0 : options.skipProfileCreation) {
4906
5283
  return this.getUserById(userData.uid);
4907
5284
  }
@@ -4910,7 +5287,7 @@ var UserService = class extends BaseService {
4910
5287
  roles,
4911
5288
  options
4912
5289
  );
4913
- await updateDoc9(doc9(this.db, USERS_COLLECTION, userData.uid), profiles);
5290
+ await updateDoc10(doc10(this.db, USERS_COLLECTION, userData.uid), profiles);
4914
5291
  return this.getUserById(userData.uid);
4915
5292
  }
4916
5293
  /**
@@ -4988,7 +5365,7 @@ var UserService = class extends BaseService {
4988
5365
  email: "",
4989
5366
  phoneNumber: "",
4990
5367
  title: "",
4991
- dateOfBirth: Timestamp10.now(),
5368
+ dateOfBirth: Timestamp11.now(),
4992
5369
  gender: "other",
4993
5370
  languages: ["Serbian"]
4994
5371
  },
@@ -4997,7 +5374,7 @@ var UserService = class extends BaseService {
4997
5374
  specialties: [],
4998
5375
  licenseNumber: "",
4999
5376
  issuingAuthority: "",
5000
- issueDate: Timestamp10.now(),
5377
+ issueDate: Timestamp11.now(),
5001
5378
  verificationStatus: "pending"
5002
5379
  },
5003
5380
  isActive: true,
@@ -5013,7 +5390,7 @@ var UserService = class extends BaseService {
5013
5390
  * Dohvata korisnika po ID-u
5014
5391
  */
5015
5392
  async getUserById(uid) {
5016
- const userDoc = await getDoc12(doc9(this.db, USERS_COLLECTION, uid));
5393
+ const userDoc = await getDoc13(doc10(this.db, USERS_COLLECTION, uid));
5017
5394
  if (!userDoc.exists()) {
5018
5395
  throw USER_ERRORS.NOT_FOUND;
5019
5396
  }
@@ -5024,19 +5401,19 @@ var UserService = class extends BaseService {
5024
5401
  * Dohvata korisnika po email-u
5025
5402
  */
5026
5403
  async getUserByEmail(email) {
5027
- const usersRef = collection7(this.db, USERS_COLLECTION);
5028
- const q = query7(usersRef, where7("email", "==", email));
5029
- const querySnapshot = await getDocs7(q);
5404
+ const usersRef = collection8(this.db, USERS_COLLECTION);
5405
+ const q = query8(usersRef, where8("email", "==", email));
5406
+ const querySnapshot = await getDocs8(q);
5030
5407
  if (querySnapshot.empty) return null;
5031
5408
  const userData = querySnapshot.docs[0].data();
5032
5409
  return userSchema.parse(userData);
5033
5410
  }
5034
5411
  async getUsersByRole(role) {
5035
5412
  const constraints = [
5036
- where7("roles", "array-contains", role)
5413
+ where8("roles", "array-contains", role)
5037
5414
  ];
5038
- const q = query7(collection7(this.db, USERS_COLLECTION), ...constraints);
5039
- const querySnapshot = await getDocs7(q);
5415
+ const q = query8(collection8(this.db, USERS_COLLECTION), ...constraints);
5416
+ const querySnapshot = await getDocs8(q);
5040
5417
  const users = querySnapshot.docs.map((doc34) => doc34.data());
5041
5418
  return Promise.all(users.map((userData) => userSchema.parse(userData)));
5042
5419
  }
@@ -5044,24 +5421,24 @@ var UserService = class extends BaseService {
5044
5421
  * Ažurira timestamp poslednjeg logovanja
5045
5422
  */
5046
5423
  async updateUserLoginTimestamp(uid) {
5047
- const userRef = doc9(this.db, USERS_COLLECTION, uid);
5048
- const userDoc = await getDoc12(userRef);
5424
+ const userRef = doc10(this.db, USERS_COLLECTION, uid);
5425
+ const userDoc = await getDoc13(userRef);
5049
5426
  if (!userDoc.exists()) {
5050
5427
  throw AUTH_ERRORS.USER_NOT_FOUND;
5051
5428
  }
5052
- await updateDoc9(userRef, {
5429
+ await updateDoc10(userRef, {
5053
5430
  lastLoginAt: serverTimestamp10(),
5054
5431
  updatedAt: serverTimestamp10()
5055
5432
  });
5056
5433
  return this.getUserById(uid);
5057
5434
  }
5058
5435
  async upgradeAnonymousUser(uid, email) {
5059
- const userRef = doc9(this.db, USERS_COLLECTION, uid);
5060
- const userDoc = await getDoc12(userRef);
5436
+ const userRef = doc10(this.db, USERS_COLLECTION, uid);
5437
+ const userDoc = await getDoc13(userRef);
5061
5438
  if (!userDoc.exists()) {
5062
5439
  throw USER_ERRORS.NOT_FOUND;
5063
5440
  }
5064
- await updateDoc9(userRef, {
5441
+ await updateDoc10(userRef, {
5065
5442
  email,
5066
5443
  isAnonymous: false,
5067
5444
  updatedAt: serverTimestamp10()
@@ -5069,8 +5446,8 @@ var UserService = class extends BaseService {
5069
5446
  return this.getUserById(uid);
5070
5447
  }
5071
5448
  async updateUser(uid, updates) {
5072
- const userRef = doc9(this.db, USERS_COLLECTION, uid);
5073
- const userDoc = await getDoc12(userRef);
5449
+ const userRef = doc10(this.db, USERS_COLLECTION, uid);
5450
+ const userDoc = await getDoc13(userRef);
5074
5451
  if (!userDoc.exists()) {
5075
5452
  throw USER_ERRORS.NOT_FOUND;
5076
5453
  }
@@ -5082,7 +5459,7 @@ var UserService = class extends BaseService {
5082
5459
  updatedAt: serverTimestamp10()
5083
5460
  };
5084
5461
  userSchema.parse(updatedUser);
5085
- await updateDoc9(userRef, {
5462
+ await updateDoc10(userRef, {
5086
5463
  ...updates,
5087
5464
  updatedAt: serverTimestamp10()
5088
5465
  });
@@ -5101,7 +5478,7 @@ var UserService = class extends BaseService {
5101
5478
  const user = await this.getUserById(uid);
5102
5479
  if (user.roles.includes(role)) return;
5103
5480
  const profiles = await this.createProfilesForRoles(uid, [role], options);
5104
- await updateDoc9(doc9(this.db, USERS_COLLECTION, uid), {
5481
+ await updateDoc10(doc10(this.db, USERS_COLLECTION, uid), {
5105
5482
  roles: [...user.roles, role],
5106
5483
  ...profiles,
5107
5484
  updatedAt: serverTimestamp10()
@@ -5136,15 +5513,15 @@ var UserService = class extends BaseService {
5136
5513
  }
5137
5514
  break;
5138
5515
  }
5139
- await updateDoc9(doc9(this.db, USERS_COLLECTION, uid), {
5516
+ await updateDoc10(doc10(this.db, USERS_COLLECTION, uid), {
5140
5517
  roles: user.roles.filter((r) => r !== role),
5141
5518
  updatedAt: serverTimestamp10()
5142
5519
  });
5143
5520
  }
5144
5521
  // Delete operations
5145
5522
  async deleteUser(uid) {
5146
- const userRef = doc9(this.db, USERS_COLLECTION, uid);
5147
- const userDoc = await getDoc12(userRef);
5523
+ const userRef = doc10(this.db, USERS_COLLECTION, uid);
5524
+ const userDoc = await getDoc13(userRef);
5148
5525
  if (!userDoc.exists()) {
5149
5526
  throw USER_ERRORS.NOT_FOUND;
5150
5527
  }
@@ -5165,7 +5542,7 @@ var UserService = class extends BaseService {
5165
5542
  userData.adminProfile
5166
5543
  );
5167
5544
  }
5168
- await deleteDoc3(userRef);
5545
+ await deleteDoc4(userRef);
5169
5546
  } catch (error) {
5170
5547
  throw error;
5171
5548
  }
@@ -5174,15 +5551,15 @@ var UserService = class extends BaseService {
5174
5551
 
5175
5552
  // src/services/clinic/utils/clinic-group.utils.ts
5176
5553
  import {
5177
- collection as collection8,
5178
- doc as doc10,
5179
- getDoc as getDoc13,
5180
- getDocs as getDocs8,
5181
- query as query8,
5182
- where as where8,
5183
- updateDoc as updateDoc10,
5184
- setDoc as setDoc9,
5185
- Timestamp as Timestamp11
5554
+ collection as collection9,
5555
+ doc as doc11,
5556
+ getDoc as getDoc14,
5557
+ getDocs as getDocs9,
5558
+ query as query9,
5559
+ where as where9,
5560
+ updateDoc as updateDoc11,
5561
+ setDoc as setDoc10,
5562
+ Timestamp as Timestamp12
5186
5563
  } from "firebase/firestore";
5187
5564
  import { geohashForLocation as geohashForLocation2 } from "geofire-common";
5188
5565
  import { z as z17 } from "zod";
@@ -5190,10 +5567,10 @@ import { z as z17 } from "zod";
5190
5567
  // src/services/clinic/utils/photos.utils.ts
5191
5568
  import {
5192
5569
  getStorage as getStorage3,
5193
- ref as ref2,
5194
- uploadBytes as uploadBytes2,
5195
- getDownloadURL as getDownloadURL2,
5196
- deleteObject as deleteObject2
5570
+ ref as ref3,
5571
+ uploadBytes as uploadBytes3,
5572
+ getDownloadURL as getDownloadURL3,
5573
+ deleteObject as deleteObject3
5197
5574
  } from "firebase/storage";
5198
5575
  async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName) {
5199
5576
  if (!photo || typeof photo !== "string" || !photo.startsWith("data:")) {
@@ -5205,7 +5582,7 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
5205
5582
  );
5206
5583
  const storage = getStorage3(app);
5207
5584
  const storageFileName = fileName || `${photoType}-${Date.now()}`;
5208
- const storageRef = ref2(
5585
+ const storageRef = ref3(
5209
5586
  storage,
5210
5587
  `${entityType}/${entityId}/${storageFileName}`
5211
5588
  );
@@ -5217,8 +5594,8 @@ async function uploadPhoto(photo, entityType, entityId, photoType, app, fileName
5217
5594
  byteArrays.push(byteCharacters.charCodeAt(i));
5218
5595
  }
5219
5596
  const blob = new Blob([new Uint8Array(byteArrays)], { type: contentType });
5220
- await uploadBytes2(storageRef, blob, { contentType });
5221
- const downloadUrl = await getDownloadURL2(storageRef);
5597
+ await uploadBytes3(storageRef, blob, { contentType });
5598
+ const downloadUrl = await getDownloadURL3(storageRef);
5222
5599
  console.log(`[PHOTO_UTILS] ${photoType} uploaded successfully`, {
5223
5600
  downloadUrl
5224
5601
  });
@@ -5306,9 +5683,9 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
5306
5683
  throw geohashError;
5307
5684
  }
5308
5685
  }
5309
- const now = Timestamp11.now();
5686
+ const now = Timestamp12.now();
5310
5687
  console.log("[CLINIC_GROUP] Preparing clinic group data object");
5311
- const groupId = doc10(collection8(db, CLINIC_GROUPS_COLLECTION)).id;
5688
+ const groupId = doc11(collection9(db, CLINIC_GROUPS_COLLECTION)).id;
5312
5689
  console.log("[CLINIC_GROUP] Logo value:", {
5313
5690
  logoValue: validatedData.logo,
5314
5691
  logoType: validatedData.logo === null ? "null" : typeof validatedData.logo
@@ -5358,7 +5735,7 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
5358
5735
  groupId: groupData.id
5359
5736
  });
5360
5737
  try {
5361
- await setDoc9(doc10(db, CLINIC_GROUPS_COLLECTION, groupData.id), groupData);
5738
+ await setDoc10(doc11(db, CLINIC_GROUPS_COLLECTION, groupData.id), groupData);
5362
5739
  console.log("[CLINIC_GROUP] Clinic group saved successfully");
5363
5740
  } catch (firestoreError) {
5364
5741
  console.error(
@@ -5404,19 +5781,19 @@ async function createClinicGroup(db, data, ownerId, isDefault = false, clinicAdm
5404
5781
  }
5405
5782
  }
5406
5783
  async function getClinicGroup(db, groupId) {
5407
- const docRef = doc10(db, CLINIC_GROUPS_COLLECTION, groupId);
5408
- const docSnap = await getDoc13(docRef);
5784
+ const docRef = doc11(db, CLINIC_GROUPS_COLLECTION, groupId);
5785
+ const docSnap = await getDoc14(docRef);
5409
5786
  if (docSnap.exists()) {
5410
5787
  return docSnap.data();
5411
5788
  }
5412
5789
  return null;
5413
5790
  }
5414
5791
  async function getAllActiveGroups(db) {
5415
- const q = query8(
5416
- collection8(db, CLINIC_GROUPS_COLLECTION),
5417
- where8("isActive", "==", true)
5792
+ const q = query9(
5793
+ collection9(db, CLINIC_GROUPS_COLLECTION),
5794
+ where9("isActive", "==", true)
5418
5795
  );
5419
- const querySnapshot = await getDocs8(q);
5796
+ const querySnapshot = await getDocs9(q);
5420
5797
  return querySnapshot.docs.map((doc34) => doc34.data());
5421
5798
  }
5422
5799
  async function updateClinicGroup(db, groupId, data, app) {
@@ -5445,10 +5822,10 @@ async function updateClinicGroup(db, groupId, data, app) {
5445
5822
  }
5446
5823
  updatedData = {
5447
5824
  ...updatedData,
5448
- updatedAt: Timestamp11.now()
5825
+ updatedAt: Timestamp12.now()
5449
5826
  };
5450
5827
  console.log("[CLINIC_GROUP] Updating clinic group in Firestore");
5451
- await updateDoc10(doc10(db, CLINIC_GROUPS_COLLECTION, groupId), updatedData);
5828
+ await updateDoc11(doc11(db, CLINIC_GROUPS_COLLECTION, groupId), updatedData);
5452
5829
  console.log("[CLINIC_GROUP] Clinic group updated successfully");
5453
5830
  const updatedGroup = await getClinicGroup(db, groupId);
5454
5831
  if (!updatedGroup) {
@@ -5529,10 +5906,10 @@ async function createAdminToken(db, groupId, creatorAdminId, app, data) {
5529
5906
  if (!group.admins.includes(creatorAdminId)) {
5530
5907
  throw new Error("Admin does not belong to this clinic group");
5531
5908
  }
5532
- const now = Timestamp11.now();
5909
+ const now = Timestamp12.now();
5533
5910
  const expiresInDays = (data == null ? void 0 : data.expiresInDays) || 7;
5534
5911
  const email = (data == null ? void 0 : data.email) || null;
5535
- const expiresAt = new Timestamp11(
5912
+ const expiresAt = new Timestamp12(
5536
5913
  now.seconds + expiresInDays * 24 * 60 * 60,
5537
5914
  now.nanoseconds
5538
5915
  );
@@ -5566,7 +5943,7 @@ async function verifyAndUseAdminToken(db, groupId, token, userRef, app) {
5566
5943
  if (adminToken.status !== "active" /* ACTIVE */) {
5567
5944
  throw new Error("Admin token is not active");
5568
5945
  }
5569
- const now = Timestamp11.now();
5946
+ const now = Timestamp12.now();
5570
5947
  if (adminToken.expiresAt.seconds < now.seconds) {
5571
5948
  const updatedTokens2 = group.adminTokens.map(
5572
5949
  (t) => t.id === adminToken.id ? { ...t, status: "expired" /* EXPIRED */ } : t
@@ -5849,16 +6226,16 @@ import { z as z19 } from "zod";
5849
6226
 
5850
6227
  // src/services/clinic/utils/clinic.utils.ts
5851
6228
  import {
5852
- collection as collection9,
5853
- doc as doc11,
5854
- getDoc as getDoc14,
5855
- getDocs as getDocs9,
5856
- query as query9,
5857
- where as where9,
5858
- updateDoc as updateDoc11,
5859
- setDoc as setDoc10,
5860
- Timestamp as Timestamp12,
5861
- limit as limit5,
6229
+ collection as collection10,
6230
+ doc as doc12,
6231
+ getDoc as getDoc15,
6232
+ getDocs as getDocs10,
6233
+ query as query10,
6234
+ where as where10,
6235
+ updateDoc as updateDoc12,
6236
+ setDoc as setDoc11,
6237
+ Timestamp as Timestamp13,
6238
+ limit as limit6,
5862
6239
  startAfter as startAfter5
5863
6240
  } from "firebase/firestore";
5864
6241
  import {
@@ -5868,20 +6245,20 @@ import {
5868
6245
  } from "geofire-common";
5869
6246
  import { z as z18 } from "zod";
5870
6247
  async function getClinic(db, clinicId) {
5871
- const docRef = doc11(db, CLINICS_COLLECTION, clinicId);
5872
- const docSnap = await getDoc14(docRef);
6248
+ const docRef = doc12(db, CLINICS_COLLECTION, clinicId);
6249
+ const docSnap = await getDoc15(docRef);
5873
6250
  if (docSnap.exists()) {
5874
6251
  return docSnap.data();
5875
6252
  }
5876
6253
  return null;
5877
6254
  }
5878
6255
  async function getClinicsByGroup(db, groupId) {
5879
- const q = query9(
5880
- collection9(db, CLINICS_COLLECTION),
5881
- where9("clinicGroupId", "==", groupId),
5882
- where9("isActive", "==", true)
6256
+ const q = query10(
6257
+ collection10(db, CLINICS_COLLECTION),
6258
+ where10("clinicGroupId", "==", groupId),
6259
+ where10("isActive", "==", true)
5883
6260
  );
5884
- const querySnapshot = await getDocs9(q);
6261
+ const querySnapshot = await getDocs10(q);
5885
6262
  return querySnapshot.docs.map((doc34) => doc34.data());
5886
6263
  }
5887
6264
  async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
@@ -6037,11 +6414,11 @@ async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app
6037
6414
  }
6038
6415
  updatedData = {
6039
6416
  ...updatedData,
6040
- updatedAt: Timestamp12.now()
6417
+ updatedAt: Timestamp13.now()
6041
6418
  };
6042
6419
  console.log("[CLINIC] Updating clinic in Firestore");
6043
6420
  try {
6044
- await updateDoc11(doc11(db, CLINICS_COLLECTION, clinicId), updatedData);
6421
+ await updateDoc12(doc12(db, CLINICS_COLLECTION, clinicId), updatedData);
6045
6422
  console.log("[CLINIC] Clinic updated successfully");
6046
6423
  } catch (updateError) {
6047
6424
  console.error("[CLINIC] Error updating clinic in Firestore:", updateError);
@@ -6070,12 +6447,12 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
6070
6447
  if (clinicIds.length === 0) {
6071
6448
  return [];
6072
6449
  }
6073
- const constraints = [where9("id", "in", clinicIds)];
6450
+ const constraints = [where10("id", "in", clinicIds)];
6074
6451
  if (options.isActive !== void 0) {
6075
- constraints.push(where9("isActive", "==", options.isActive));
6452
+ constraints.push(where10("isActive", "==", options.isActive));
6076
6453
  }
6077
- const q = query9(collection9(db, CLINICS_COLLECTION), ...constraints);
6078
- const querySnapshot = await getDocs9(q);
6454
+ const q = query10(collection10(db, CLINICS_COLLECTION), ...constraints);
6455
+ const querySnapshot = await getDocs10(q);
6079
6456
  return querySnapshot.docs.map((doc34) => doc34.data());
6080
6457
  }
6081
6458
  async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
@@ -6089,8 +6466,8 @@ async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGr
6089
6466
  }
6090
6467
  async function getClinicById(db, clinicId) {
6091
6468
  try {
6092
- const clinicRef = doc11(db, CLINICS_COLLECTION, clinicId);
6093
- const clinicSnapshot = await getDoc14(clinicRef);
6469
+ const clinicRef = doc12(db, CLINICS_COLLECTION, clinicId);
6470
+ const clinicSnapshot = await getDoc15(clinicRef);
6094
6471
  if (!clinicSnapshot.exists()) {
6095
6472
  return null;
6096
6473
  }
@@ -6106,20 +6483,20 @@ async function getClinicById(db, clinicId) {
6106
6483
  }
6107
6484
  async function getAllClinics(db, pagination, lastDoc) {
6108
6485
  try {
6109
- const clinicsCollection = collection9(db, CLINICS_COLLECTION);
6110
- let clinicsQuery = query9(clinicsCollection);
6486
+ const clinicsCollection = collection10(db, CLINICS_COLLECTION);
6487
+ let clinicsQuery = query10(clinicsCollection);
6111
6488
  if (pagination && pagination > 0) {
6112
6489
  if (lastDoc) {
6113
- clinicsQuery = query9(
6490
+ clinicsQuery = query10(
6114
6491
  clinicsCollection,
6115
6492
  startAfter5(lastDoc),
6116
- limit5(pagination)
6493
+ limit6(pagination)
6117
6494
  );
6118
6495
  } else {
6119
- clinicsQuery = query9(clinicsCollection, limit5(pagination));
6496
+ clinicsQuery = query10(clinicsCollection, limit6(pagination));
6120
6497
  }
6121
6498
  }
6122
- const clinicsSnapshot = await getDocs9(clinicsQuery);
6499
+ const clinicsSnapshot = await getDocs10(clinicsQuery);
6123
6500
  const lastVisible = clinicsSnapshot.docs[clinicsSnapshot.docs.length - 1];
6124
6501
  const clinics = clinicsSnapshot.docs.map((doc34) => {
6125
6502
  const data = doc34.data();
@@ -6146,12 +6523,12 @@ async function getAllClinicsInRange(db, center, rangeInKm, pagination, lastDoc)
6146
6523
  let lastDocSnapshot = null;
6147
6524
  for (const b of bounds) {
6148
6525
  const constraints = [
6149
- where9("location.geohash", ">=", b[0]),
6150
- where9("location.geohash", "<=", b[1]),
6151
- where9("isActive", "==", true)
6526
+ where10("location.geohash", ">=", b[0]),
6527
+ where10("location.geohash", "<=", b[1]),
6528
+ where10("isActive", "==", true)
6152
6529
  ];
6153
- const q = query9(collection9(db, CLINICS_COLLECTION), ...constraints);
6154
- const querySnapshot = await getDocs9(q);
6530
+ const q = query10(collection10(db, CLINICS_COLLECTION), ...constraints);
6531
+ const querySnapshot = await getDocs10(q);
6155
6532
  for (const doc34 of querySnapshot.docs) {
6156
6533
  const clinic = doc34.data();
6157
6534
  const distance = distanceBetween2(
@@ -6247,10 +6624,10 @@ async function removeTags(db, clinicId, adminId, tagsToRemove, clinicAdminServic
6247
6624
 
6248
6625
  // src/services/clinic/utils/search.utils.ts
6249
6626
  import {
6250
- collection as collection10,
6251
- query as query10,
6252
- where as where10,
6253
- getDocs as getDocs10
6627
+ collection as collection11,
6628
+ query as query11,
6629
+ where as where11,
6630
+ getDocs as getDocs11
6254
6631
  } from "firebase/firestore";
6255
6632
  import { geohashQueryBounds as geohashQueryBounds2, distanceBetween as distanceBetween3 } from "geofire-common";
6256
6633
  async function findClinicsInRadius(db, center, radiusInKm, filters) {
@@ -6261,20 +6638,20 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
6261
6638
  const matchingDocs = [];
6262
6639
  for (const b of bounds) {
6263
6640
  const constraints = [
6264
- where10("location.geohash", ">=", b[0]),
6265
- where10("location.geohash", "<=", b[1]),
6266
- where10("isActive", "==", true)
6641
+ where11("location.geohash", ">=", b[0]),
6642
+ where11("location.geohash", "<=", b[1]),
6643
+ where11("isActive", "==", true)
6267
6644
  ];
6268
6645
  if (filters == null ? void 0 : filters.services) {
6269
6646
  constraints.push(
6270
- where10("services", "array-contains-any", filters.services)
6647
+ where11("services", "array-contains-any", filters.services)
6271
6648
  );
6272
6649
  }
6273
6650
  if ((filters == null ? void 0 : filters.tags) && filters.tags.length > 0) {
6274
- constraints.push(where10("tags", "array-contains-any", filters.tags));
6651
+ constraints.push(where11("tags", "array-contains-any", filters.tags));
6275
6652
  }
6276
- const q = query10(collection10(db, CLINICS_COLLECTION), ...constraints);
6277
- const querySnapshot = await getDocs10(q);
6653
+ const q = query11(collection11(db, CLINICS_COLLECTION), ...constraints);
6654
+ const querySnapshot = await getDocs11(q);
6278
6655
  for (const doc34 of querySnapshot.docs) {
6279
6656
  const clinic = doc34.data();
6280
6657
  const distance = distanceBetween3(
@@ -6302,13 +6679,13 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
6302
6679
 
6303
6680
  // src/services/clinic/utils/filter.utils.ts
6304
6681
  import {
6305
- collection as collection11,
6306
- query as query11,
6307
- where as where11,
6308
- getDocs as getDocs11,
6682
+ collection as collection12,
6683
+ query as query12,
6684
+ where as where12,
6685
+ getDocs as getDocs12,
6309
6686
  startAfter as startAfter6,
6310
- limit as limit6,
6311
- orderBy as orderBy2
6687
+ limit as limit7,
6688
+ orderBy as orderBy3
6312
6689
  } from "firebase/firestore";
6313
6690
  import { geohashQueryBounds as geohashQueryBounds3, distanceBetween as distanceBetween4 } from "geofire-common";
6314
6691
  async function getClinicsByFilters(db, filters) {
@@ -6319,37 +6696,37 @@ async function getClinicsByFilters(db, filters) {
6319
6696
  const isGeoQuery = filters.center && filters.radiusInKm && filters.radiusInKm > 0;
6320
6697
  const constraints = [];
6321
6698
  if (filters.isActive !== void 0) {
6322
- constraints.push(where11("isActive", "==", filters.isActive));
6699
+ constraints.push(where12("isActive", "==", filters.isActive));
6323
6700
  } else {
6324
- constraints.push(where11("isActive", "==", true));
6701
+ constraints.push(where12("isActive", "==", true));
6325
6702
  }
6326
6703
  if (filters.tags && filters.tags.length > 0) {
6327
- constraints.push(where11("tags", "array-contains", filters.tags[0]));
6704
+ constraints.push(where12("tags", "array-contains", filters.tags[0]));
6328
6705
  }
6329
6706
  if (filters.procedureTechnology) {
6330
6707
  constraints.push(
6331
- where11("servicesInfo.technology", "==", filters.procedureTechnology)
6708
+ where12("servicesInfo.technology", "==", filters.procedureTechnology)
6332
6709
  );
6333
6710
  } else if (filters.procedureSubcategory) {
6334
6711
  constraints.push(
6335
- where11("servicesInfo.subCategory", "==", filters.procedureSubcategory)
6712
+ where12("servicesInfo.subCategory", "==", filters.procedureSubcategory)
6336
6713
  );
6337
6714
  } else if (filters.procedureCategory) {
6338
6715
  constraints.push(
6339
- where11("servicesInfo.category", "==", filters.procedureCategory)
6716
+ where12("servicesInfo.category", "==", filters.procedureCategory)
6340
6717
  );
6341
6718
  } else if (filters.procedureFamily) {
6342
6719
  constraints.push(
6343
- where11("servicesInfo.procedureFamily", "==", filters.procedureFamily)
6720
+ where12("servicesInfo.procedureFamily", "==", filters.procedureFamily)
6344
6721
  );
6345
6722
  }
6346
6723
  if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
6347
6724
  constraints.push(startAfter6(filters.lastDoc));
6348
- constraints.push(limit6(filters.pagination));
6725
+ constraints.push(limit7(filters.pagination));
6349
6726
  } else if (filters.pagination && filters.pagination > 0) {
6350
- constraints.push(limit6(filters.pagination));
6727
+ constraints.push(limit7(filters.pagination));
6351
6728
  }
6352
- constraints.push(orderBy2("location.geohash"));
6729
+ constraints.push(orderBy3("location.geohash"));
6353
6730
  let clinicsResult = [];
6354
6731
  let lastVisibleDoc = null;
6355
6732
  if (isGeoQuery) {
@@ -6357,440 +6734,124 @@ async function getClinicsByFilters(db, filters) {
6357
6734
  const radiusInKm = filters.radiusInKm;
6358
6735
  const bounds = geohashQueryBounds3(
6359
6736
  [center.latitude, center.longitude],
6360
- radiusInKm * 1e3
6361
- // Convert to meters
6362
- );
6363
- const matchingClinics = [];
6364
- for (const bound of bounds) {
6365
- const geoConstraints = [
6366
- ...constraints,
6367
- where11("location.geohash", ">=", bound[0]),
6368
- where11("location.geohash", "<=", bound[1])
6369
- ];
6370
- const q = query11(collection11(db, CLINICS_COLLECTION), ...geoConstraints);
6371
- const querySnapshot = await getDocs11(q);
6372
- console.log(
6373
- `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
6374
- );
6375
- for (const doc34 of querySnapshot.docs) {
6376
- const clinic = { ...doc34.data(), id: doc34.id };
6377
- const distance = distanceBetween4(
6378
- [center.latitude, center.longitude],
6379
- [clinic.location.latitude, clinic.location.longitude]
6380
- );
6381
- const distanceInKm = distance / 1e3;
6382
- if (distanceInKm <= radiusInKm) {
6383
- matchingClinics.push({
6384
- ...clinic,
6385
- distance: distanceInKm
6386
- });
6387
- }
6388
- }
6389
- }
6390
- let filteredClinics = matchingClinics;
6391
- if (filters.tags && filters.tags.length > 1) {
6392
- filteredClinics = filteredClinics.filter((clinic) => {
6393
- return filters.tags.every((tag) => clinic.tags.includes(tag));
6394
- });
6395
- }
6396
- if (filters.minRating !== void 0) {
6397
- filteredClinics = filteredClinics.filter(
6398
- (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
6399
- );
6400
- }
6401
- if (filters.maxRating !== void 0) {
6402
- filteredClinics = filteredClinics.filter(
6403
- (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
6404
- );
6405
- }
6406
- filteredClinics.sort((a, b) => a.distance - b.distance);
6407
- if (filters.pagination && filters.pagination > 0) {
6408
- let startIndex = 0;
6409
- if (filters.lastDoc) {
6410
- const lastDocIndex = filteredClinics.findIndex(
6411
- (clinic) => clinic.id === filters.lastDoc.id
6412
- );
6413
- if (lastDocIndex !== -1) {
6414
- startIndex = lastDocIndex + 1;
6415
- }
6416
- }
6417
- const paginatedClinics = filteredClinics.slice(
6418
- startIndex,
6419
- startIndex + filters.pagination
6420
- );
6421
- lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
6422
- clinicsResult = paginatedClinics;
6423
- } else {
6424
- clinicsResult = filteredClinics;
6425
- }
6426
- } else {
6427
- const q = query11(collection11(db, CLINICS_COLLECTION), ...constraints);
6428
- const querySnapshot = await getDocs11(q);
6429
- console.log(
6430
- `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
6431
- );
6432
- const clinics = querySnapshot.docs.map((doc34) => {
6433
- return { ...doc34.data(), id: doc34.id };
6434
- });
6435
- let filteredClinics = clinics;
6436
- if (filters.center) {
6437
- const center = filters.center;
6438
- const clinicsWithDistance = [];
6439
- filteredClinics.forEach((clinic) => {
6440
- const distance = distanceBetween4(
6441
- [center.latitude, center.longitude],
6442
- [clinic.location.latitude, clinic.location.longitude]
6443
- );
6444
- clinicsWithDistance.push({
6445
- ...clinic,
6446
- distance: distance / 1e3
6447
- // Convert to kilometers
6448
- });
6449
- });
6450
- filteredClinics = clinicsWithDistance;
6451
- filteredClinics.sort(
6452
- (a, b) => a.distance - b.distance
6453
- );
6454
- }
6455
- if (filters.tags && filters.tags.length > 1) {
6456
- filteredClinics = filteredClinics.filter((clinic) => {
6457
- return filters.tags.every((tag) => clinic.tags.includes(tag));
6458
- });
6459
- }
6460
- if (filters.minRating !== void 0) {
6461
- filteredClinics = filteredClinics.filter(
6462
- (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
6463
- );
6464
- }
6465
- if (filters.maxRating !== void 0) {
6466
- filteredClinics = filteredClinics.filter(
6467
- (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
6468
- );
6469
- }
6470
- lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6471
- clinicsResult = filteredClinics;
6472
- }
6473
- return {
6474
- clinics: clinicsResult,
6475
- lastDoc: lastVisibleDoc
6476
- };
6477
- }
6478
-
6479
- // src/services/media/media.service.ts
6480
- import { Timestamp as Timestamp13 } from "firebase/firestore";
6481
- import {
6482
- ref as ref3,
6483
- uploadBytes as uploadBytes3,
6484
- getDownloadURL as getDownloadURL3,
6485
- deleteObject as deleteObject3,
6486
- getBytes
6487
- } from "firebase/storage";
6488
- import {
6489
- doc as doc12,
6490
- getDoc as getDoc15,
6491
- setDoc as setDoc11,
6492
- updateDoc as updateDoc12,
6493
- collection as collection12,
6494
- query as query12,
6495
- where as where12,
6496
- limit as limit7,
6497
- getDocs as getDocs12,
6498
- deleteDoc as deleteDoc6,
6499
- orderBy as orderBy3
6500
- } from "firebase/firestore";
6501
- var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
6502
- MediaAccessLevel2["PUBLIC"] = "public";
6503
- MediaAccessLevel2["PRIVATE"] = "private";
6504
- MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
6505
- return MediaAccessLevel2;
6506
- })(MediaAccessLevel || {});
6507
- var MEDIA_METADATA_COLLECTION = "media_metadata";
6508
- var MediaService = class extends BaseService {
6509
- constructor(db, auth, app) {
6510
- super(db, auth, app);
6511
- }
6512
- /**
6513
- * Upload a media file, store its metadata, and return the metadata including the URL.
6514
- * @param file - The file to upload.
6515
- * @param ownerId - ID of the owner (user, patient, clinic, etc.).
6516
- * @param accessLevel - Access level (public, private, confidential).
6517
- * @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
6518
- * @param originalFileName - Optional: the original name of the file, if not using file.name.
6519
- * @returns Promise with the media metadata.
6520
- */
6521
- async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
6522
- const mediaId = this.generateId();
6523
- const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
6524
- const uniqueFileName = `${mediaId}-${fileNameToUse}`;
6525
- const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
6526
- console.log(`[MediaService] Uploading file to: ${filePath}`);
6527
- const storageRef = ref3(this.storage, filePath);
6528
- try {
6529
- const uploadResult = await uploadBytes3(storageRef, file, {
6530
- contentType: file.type
6531
- });
6532
- console.log("[MediaService] File uploaded successfully", uploadResult);
6533
- const downloadURL = await getDownloadURL3(uploadResult.ref);
6534
- console.log("[MediaService] Got download URL:", downloadURL);
6535
- const metadata = {
6536
- id: mediaId,
6537
- name: fileNameToUse,
6538
- url: downloadURL,
6539
- contentType: file.type,
6540
- size: file.size,
6541
- createdAt: Timestamp13.now(),
6542
- accessLevel,
6543
- ownerId,
6544
- collectionName,
6545
- path: filePath
6546
- };
6547
- const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6548
- await setDoc11(metadataDocRef, metadata);
6549
- console.log("[MediaService] Metadata stored in Firestore:", mediaId);
6550
- return metadata;
6551
- } catch (error) {
6552
- console.error("[MediaService] Error during media upload:", error);
6553
- throw error;
6554
- }
6555
- }
6556
- /**
6557
- * Get media metadata from Firestore by its ID.
6558
- * @param mediaId - ID of the media.
6559
- * @returns Promise with the media metadata or null if not found.
6560
- */
6561
- async getMediaMetadata(mediaId) {
6562
- console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
6563
- const docRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6564
- const docSnap = await getDoc15(docRef);
6565
- if (docSnap.exists()) {
6566
- console.log("[MediaService] Metadata found:", docSnap.data());
6567
- return docSnap.data();
6568
- }
6569
- console.log("[MediaService] No metadata found for ID:", mediaId);
6570
- return null;
6571
- }
6572
- /**
6573
- * Get media metadata from Firestore by its public URL.
6574
- * @param url - The public URL of the media file.
6575
- * @returns Promise with the media metadata or null if not found.
6576
- */
6577
- async getMediaMetadataByUrl(url) {
6578
- console.log(`[MediaService] Getting media metadata by URL: ${url}`);
6579
- const q = query12(
6580
- collection12(this.db, MEDIA_METADATA_COLLECTION),
6581
- where12("url", "==", url),
6582
- limit7(1)
6737
+ radiusInKm * 1e3
6738
+ // Convert to meters
6583
6739
  );
6584
- try {
6740
+ const matchingClinics = [];
6741
+ for (const bound of bounds) {
6742
+ const geoConstraints = [
6743
+ ...constraints,
6744
+ where12("location.geohash", ">=", bound[0]),
6745
+ where12("location.geohash", "<=", bound[1])
6746
+ ];
6747
+ const q = query12(collection12(db, CLINICS_COLLECTION), ...geoConstraints);
6585
6748
  const querySnapshot = await getDocs12(q);
6586
- if (!querySnapshot.empty) {
6587
- const metadata = querySnapshot.docs[0].data();
6588
- console.log("[MediaService] Metadata found by URL:", metadata);
6589
- return metadata;
6749
+ console.log(
6750
+ `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
6751
+ );
6752
+ for (const doc34 of querySnapshot.docs) {
6753
+ const clinic = { ...doc34.data(), id: doc34.id };
6754
+ const distance = distanceBetween4(
6755
+ [center.latitude, center.longitude],
6756
+ [clinic.location.latitude, clinic.location.longitude]
6757
+ );
6758
+ const distanceInKm = distance / 1e3;
6759
+ if (distanceInKm <= radiusInKm) {
6760
+ matchingClinics.push({
6761
+ ...clinic,
6762
+ distance: distanceInKm
6763
+ });
6764
+ }
6590
6765
  }
6591
- console.log("[MediaService] No metadata found for URL:", url);
6592
- return null;
6593
- } catch (error) {
6594
- console.error("[MediaService] Error fetching metadata by URL:", error);
6595
- throw error;
6596
6766
  }
6597
- }
6598
- /**
6599
- * Delete media from storage and remove metadata from Firestore.
6600
- * @param mediaId - ID of the media to delete.
6601
- */
6602
- async deleteMedia(mediaId) {
6603
- console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
6604
- const metadata = await this.getMediaMetadata(mediaId);
6605
- if (!metadata) {
6606
- console.warn(
6607
- `[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
6608
- );
6609
- return;
6767
+ let filteredClinics = matchingClinics;
6768
+ if (filters.tags && filters.tags.length > 1) {
6769
+ filteredClinics = filteredClinics.filter((clinic) => {
6770
+ return filters.tags.every((tag) => clinic.tags.includes(tag));
6771
+ });
6610
6772
  }
6611
- const storageFileRef = ref3(this.storage, metadata.path);
6612
- try {
6613
- await deleteObject3(storageFileRef);
6614
- console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
6615
- const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6616
- await deleteDoc6(metadataDocRef);
6617
- console.log(
6618
- `[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
6773
+ if (filters.minRating !== void 0) {
6774
+ filteredClinics = filteredClinics.filter(
6775
+ (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
6619
6776
  );
6620
- } catch (error) {
6621
- console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
6622
- throw error;
6623
6777
  }
6624
- }
6625
- /**
6626
- * Update media access level. This involves moving the file in Firebase Storage
6627
- * to a new path reflecting the new access level, and updating its metadata.
6628
- * @param mediaId - ID of the media to update.
6629
- * @param newAccessLevel - New access level.
6630
- * @returns Promise with the updated media metadata, or null if metadata not found.
6631
- */
6632
- async updateMediaAccessLevel(mediaId, newAccessLevel) {
6633
- var _a;
6634
- console.log(
6635
- `[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
6636
- );
6637
- const metadata = await this.getMediaMetadata(mediaId);
6638
- if (!metadata) {
6639
- console.warn(
6640
- `[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
6778
+ if (filters.maxRating !== void 0) {
6779
+ filteredClinics = filteredClinics.filter(
6780
+ (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
6641
6781
  );
6642
- return null;
6643
6782
  }
6644
- if (metadata.accessLevel === newAccessLevel) {
6645
- console.log(
6646
- `[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
6647
- );
6648
- const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6649
- try {
6650
- await updateDoc12(metadataDocRef, { updatedAt: Timestamp13.now() });
6651
- return { ...metadata, updatedAt: Timestamp13.now() };
6652
- } catch (error) {
6653
- console.error(
6654
- `[MediaService] Error updating timestamp for media ID ${mediaId}:`,
6655
- error
6783
+ filteredClinics.sort((a, b) => a.distance - b.distance);
6784
+ if (filters.pagination && filters.pagination > 0) {
6785
+ let startIndex = 0;
6786
+ if (filters.lastDoc) {
6787
+ const lastDocIndex = filteredClinics.findIndex(
6788
+ (clinic) => clinic.id === filters.lastDoc.id
6656
6789
  );
6657
- throw error;
6790
+ if (lastDocIndex !== -1) {
6791
+ startIndex = lastDocIndex + 1;
6792
+ }
6658
6793
  }
6794
+ const paginatedClinics = filteredClinics.slice(
6795
+ startIndex,
6796
+ startIndex + filters.pagination
6797
+ );
6798
+ lastVisibleDoc = paginatedClinics.length > 0 ? paginatedClinics[paginatedClinics.length - 1] : null;
6799
+ clinicsResult = paginatedClinics;
6800
+ } else {
6801
+ clinicsResult = filteredClinics;
6659
6802
  }
6660
- const oldStoragePath = metadata.path;
6661
- const fileNamePart = `${metadata.id}-${metadata.name}`;
6662
- const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
6803
+ } else {
6804
+ const q = query12(collection12(db, CLINICS_COLLECTION), ...constraints);
6805
+ const querySnapshot = await getDocs12(q);
6663
6806
  console.log(
6664
- `[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
6807
+ `[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
6665
6808
  );
6666
- const oldStorageFileRef = ref3(this.storage, oldStoragePath);
6667
- const newStorageFileRef = ref3(this.storage, newStoragePath);
6668
- try {
6669
- console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
6670
- const fileBytes = await getBytes(oldStorageFileRef);
6671
- console.log(
6672
- `[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
6673
- );
6674
- console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
6675
- await uploadBytes3(newStorageFileRef, fileBytes, {
6676
- contentType: metadata.contentType
6677
- });
6678
- console.log(
6679
- `[MediaService] Successfully uploaded bytes to ${newStoragePath}`
6680
- );
6681
- const newDownloadURL = await getDownloadURL3(newStorageFileRef);
6682
- console.log(
6683
- `[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
6684
- );
6685
- const updateData = {
6686
- accessLevel: newAccessLevel,
6687
- path: newStoragePath,
6688
- url: newDownloadURL,
6689
- updatedAt: Timestamp13.now()
6690
- };
6691
- const metadataDocRef = doc12(this.db, MEDIA_METADATA_COLLECTION, mediaId);
6692
- console.log(
6693
- `[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
6694
- updateData
6695
- );
6696
- await updateDoc12(metadataDocRef, updateData);
6697
- console.log(
6698
- `[MediaService] Successfully updated Firestore metadata for ${mediaId}`
6699
- );
6700
- try {
6701
- console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
6702
- await deleteObject3(oldStorageFileRef);
6703
- console.log(
6704
- `[MediaService] Successfully deleted old file from ${oldStoragePath}`
6705
- );
6706
- } catch (deleteError) {
6707
- console.error(
6708
- `[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
6709
- deleteError
6809
+ const clinics = querySnapshot.docs.map((doc34) => {
6810
+ return { ...doc34.data(), id: doc34.id };
6811
+ });
6812
+ let filteredClinics = clinics;
6813
+ if (filters.center) {
6814
+ const center = filters.center;
6815
+ const clinicsWithDistance = [];
6816
+ filteredClinics.forEach((clinic) => {
6817
+ const distance = distanceBetween4(
6818
+ [center.latitude, center.longitude],
6819
+ [clinic.location.latitude, clinic.location.longitude]
6710
6820
  );
6711
- }
6712
- return { ...metadata, ...updateData };
6713
- } catch (error) {
6714
- console.error(
6715
- `[MediaService] Error updating media access level and moving file for ${mediaId}:`,
6716
- error
6821
+ clinicsWithDistance.push({
6822
+ ...clinic,
6823
+ distance: distance / 1e3
6824
+ // Convert to kilometers
6825
+ });
6826
+ });
6827
+ filteredClinics = clinicsWithDistance;
6828
+ filteredClinics.sort(
6829
+ (a, b) => a.distance - b.distance
6717
6830
  );
6718
- if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
6719
- console.warn(
6720
- `[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
6721
- );
6722
- try {
6723
- await deleteObject3(newStorageFileRef);
6724
- console.warn(
6725
- `[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
6726
- );
6727
- } catch (cleanupError) {
6728
- console.error(
6729
- `[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
6730
- cleanupError
6731
- );
6732
- }
6733
- }
6734
- throw error;
6735
- }
6736
- }
6737
- /**
6738
- * List all media for an owner, optionally filtered by collection and access level.
6739
- * @param ownerId - ID of the owner.
6740
- * @param collectionName - Optional: Filter by collection name.
6741
- * @param accessLevel - Optional: Filter by access level.
6742
- * @param count - Optional: Number of items to fetch.
6743
- * @param startAfterId - Optional: ID of the document to start after (for pagination).
6744
- */
6745
- async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
6746
- console.log(`[MediaService] Listing media for owner: ${ownerId}`);
6747
- let qConstraints = [where12("ownerId", "==", ownerId)];
6748
- if (collectionName) {
6749
- qConstraints.push(where12("collectionName", "==", collectionName));
6750
- }
6751
- if (accessLevel) {
6752
- qConstraints.push(where12("accessLevel", "==", accessLevel));
6753
6831
  }
6754
- qConstraints.push(orderBy3("createdAt", "desc"));
6755
- if (count) {
6756
- qConstraints.push(limit7(count));
6757
- }
6758
- if (startAfterId) {
6759
- const startAfterDoc = await this.getMediaMetadata(startAfterId);
6760
- if (startAfterDoc) {
6761
- }
6832
+ if (filters.tags && filters.tags.length > 1) {
6833
+ filteredClinics = filteredClinics.filter((clinic) => {
6834
+ return filters.tags.every((tag) => clinic.tags.includes(tag));
6835
+ });
6762
6836
  }
6763
- const finalQuery = query12(
6764
- collection12(this.db, MEDIA_METADATA_COLLECTION),
6765
- ...qConstraints
6766
- );
6767
- try {
6768
- const querySnapshot = await getDocs12(finalQuery);
6769
- const mediaList = querySnapshot.docs.map(
6770
- (doc34) => doc34.data()
6837
+ if (filters.minRating !== void 0) {
6838
+ filteredClinics = filteredClinics.filter(
6839
+ (clinic) => clinic.reviewInfo.averageRating >= filters.minRating
6771
6840
  );
6772
- console.log(`[MediaService] Found ${mediaList.length} media items.`);
6773
- return mediaList;
6774
- } catch (error) {
6775
- console.error("[MediaService] Error listing media:", error);
6776
- throw error;
6777
6841
  }
6778
- }
6779
- /**
6780
- * Get download URL for media. (Convenience, as URL is in metadata)
6781
- * @param mediaId - ID of the media.
6782
- */
6783
- async getMediaDownloadUrl(mediaId) {
6784
- console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
6785
- const metadata = await this.getMediaMetadata(mediaId);
6786
- if (metadata && metadata.url) {
6787
- console.log(`[MediaService] URL found: ${metadata.url}`);
6788
- return metadata.url;
6842
+ if (filters.maxRating !== void 0) {
6843
+ filteredClinics = filteredClinics.filter(
6844
+ (clinic) => clinic.reviewInfo.averageRating <= filters.maxRating
6845
+ );
6789
6846
  }
6790
- console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
6791
- return null;
6847
+ lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
6848
+ clinicsResult = filteredClinics;
6792
6849
  }
6793
- };
6850
+ return {
6851
+ clinics: clinicsResult,
6852
+ lastDoc: lastVisibleDoc
6853
+ };
6854
+ }
6794
6855
 
6795
6856
  // src/services/clinic/clinic.service.ts
6796
6857
  var ClinicService = class extends BaseService {
@@ -8522,7 +8583,8 @@ var ProcedureService = class extends BaseService {
8522
8583
  id: practitionerSnapshot.id,
8523
8584
  name: `${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`,
8524
8585
  description: practitioner.basicInfo.bio || "",
8525
- photo: practitioner.basicInfo.profileImageUrl || "",
8586
+ photo: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : "",
8587
+ // Default to empty string if not a processed URL
8526
8588
  rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
8527
8589
  services: practitioner.procedures || []
8528
8590
  };
@@ -8656,7 +8718,8 @@ var ProcedureService = class extends BaseService {
8656
8718
  id: newPractitioner.id,
8657
8719
  name: `${newPractitioner.basicInfo.firstName} ${newPractitioner.basicInfo.lastName}`,
8658
8720
  description: newPractitioner.basicInfo.bio || "",
8659
- photo: newPractitioner.basicInfo.profileImageUrl || "",
8721
+ photo: typeof newPractitioner.basicInfo.profileImageUrl === "string" ? newPractitioner.basicInfo.profileImageUrl : "",
8722
+ // Default to empty string if not a processed URL
8660
8723
  rating: ((_a = newPractitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
8661
8724
  services: newPractitioner.procedures || []
8662
8725
  };