@blackcode_sa/metaestetics-api 1.13.5 → 1.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.mts +20 -1
- package/dist/admin/index.d.ts +20 -1
- package/dist/admin/index.js +217 -1
- package/dist/admin/index.mjs +217 -1
- package/package.json +121 -121
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1984 -1984
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -703
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +81 -81
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/index.ts +3 -3
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +710 -710
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1151 -1151
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +240 -240
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2142 -2142
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2558 -2558
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1683 -1683
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +708 -708
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +14 -14
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2200 -2200
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +498 -498
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -494
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +20 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Index file for clinic service utilities
|
|
3
|
-
* Exports all utility functions from the other files
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export * from "./clinic.utils";
|
|
7
|
-
export * from "./clinic-group.utils";
|
|
8
|
-
export * from "./admin.utils";
|
|
9
|
-
export * from "./review.utils";
|
|
10
|
-
export * from "./tag.utils";
|
|
11
|
-
export * from "./search.utils";
|
|
1
|
+
/**
|
|
2
|
+
* Index file for clinic service utilities
|
|
3
|
+
* Exports all utility functions from the other files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from "./clinic.utils";
|
|
7
|
+
export * from "./clinic-group.utils";
|
|
8
|
+
export * from "./admin.utils";
|
|
9
|
+
export * from "./review.utils";
|
|
10
|
+
export * from "./tag.utils";
|
|
11
|
+
export * from "./search.utils";
|
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getStorage,
|
|
3
|
-
ref,
|
|
4
|
-
uploadBytes,
|
|
5
|
-
getDownloadURL,
|
|
6
|
-
deleteObject,
|
|
7
|
-
} from "firebase/storage";
|
|
8
|
-
import { FirebaseApp } from "firebase/app";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Uploads a photo to Firebase Storage
|
|
12
|
-
* @param photo - The photo data URL or URL
|
|
13
|
-
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
14
|
-
* @param entityId - The ID of the entity
|
|
15
|
-
* @param photoType - The type of photo (logo, featured, etc.)
|
|
16
|
-
* @param app - Firebase app instance
|
|
17
|
-
* @param fileName - Optional custom file name
|
|
18
|
-
* @returns The URL of the uploaded photo or null if upload failed
|
|
19
|
-
*/
|
|
20
|
-
export async function uploadPhoto(
|
|
21
|
-
photo: string | null,
|
|
22
|
-
entityType: string,
|
|
23
|
-
entityId: string,
|
|
24
|
-
photoType: string,
|
|
25
|
-
app: FirebaseApp,
|
|
26
|
-
fileName?: string
|
|
27
|
-
): Promise<string | null> {
|
|
28
|
-
// If photo is null or not a data URL, return it as is
|
|
29
|
-
if (!photo || typeof photo !== "string" || !photo.startsWith("data:")) {
|
|
30
|
-
return photo;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
console.log(
|
|
35
|
-
`[PHOTO_UTILS] Uploading ${photoType} for ${entityType}/${entityId}`
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
// Get Firebase Storage instance
|
|
39
|
-
const storage = getStorage(app);
|
|
40
|
-
|
|
41
|
-
// Create a reference to the storage location
|
|
42
|
-
const storageFileName = fileName || `${photoType}-${Date.now()}`;
|
|
43
|
-
const storageRef = ref(
|
|
44
|
-
storage,
|
|
45
|
-
`${entityType}/${entityId}/${storageFileName}`
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Extract base64 data from data URL
|
|
49
|
-
const base64Data = photo.split(",")[1];
|
|
50
|
-
const contentType = photo.split(";")[0].split(":")[1];
|
|
51
|
-
|
|
52
|
-
// Convert base64 to Blob
|
|
53
|
-
const byteCharacters = atob(base64Data);
|
|
54
|
-
const byteArrays = [];
|
|
55
|
-
for (let i = 0; i < byteCharacters.length; i++) {
|
|
56
|
-
byteArrays.push(byteCharacters.charCodeAt(i));
|
|
57
|
-
}
|
|
58
|
-
const blob = new Blob([new Uint8Array(byteArrays)], { type: contentType });
|
|
59
|
-
|
|
60
|
-
// Upload the file
|
|
61
|
-
await uploadBytes(storageRef, blob, { contentType });
|
|
62
|
-
|
|
63
|
-
// Get the download URL
|
|
64
|
-
const downloadUrl = await getDownloadURL(storageRef);
|
|
65
|
-
|
|
66
|
-
console.log(`[PHOTO_UTILS] ${photoType} uploaded successfully`, {
|
|
67
|
-
downloadUrl,
|
|
68
|
-
});
|
|
69
|
-
return downloadUrl;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error(`[PHOTO_UTILS] Error uploading ${photoType}:`, error);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Deletes a photo from Firebase Storage
|
|
78
|
-
* @param photoUrl - The URL of the photo to delete
|
|
79
|
-
* @param app - Firebase app instance
|
|
80
|
-
* @returns Whether the deletion was successful
|
|
81
|
-
*/
|
|
82
|
-
export async function deletePhoto(
|
|
83
|
-
photoUrl: string,
|
|
84
|
-
app: FirebaseApp
|
|
85
|
-
): Promise<boolean> {
|
|
86
|
-
if (!photoUrl || !photoUrl.includes("firebasestorage.googleapis.com")) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
console.log(`[PHOTO_UTILS] Deleting photo`, { photoUrl });
|
|
92
|
-
|
|
93
|
-
// Get Firebase Storage instance
|
|
94
|
-
const storage = getStorage(app);
|
|
95
|
-
|
|
96
|
-
// Extract the storage path from the URL
|
|
97
|
-
const storageRef = ref(storage, photoUrl);
|
|
98
|
-
|
|
99
|
-
// Delete the file
|
|
100
|
-
await deleteObject(storageRef);
|
|
101
|
-
|
|
102
|
-
console.log(`[PHOTO_UTILS] Photo deleted successfully`);
|
|
103
|
-
return true;
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error(`[PHOTO_UTILS] Error deleting photo:`, error);
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Uploads multiple photos to Firebase Storage
|
|
112
|
-
* @param photos - Array of photo data URLs or URLs
|
|
113
|
-
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
114
|
-
* @param entityId - The ID of the entity
|
|
115
|
-
* @param photoType - The type of photos (featured, gallery, etc.)
|
|
116
|
-
* @param app - Firebase app instance
|
|
117
|
-
* @returns Array of URLs of the uploaded photos
|
|
118
|
-
*/
|
|
119
|
-
export async function uploadMultiplePhotos(
|
|
120
|
-
photos: string[],
|
|
121
|
-
entityType: string,
|
|
122
|
-
entityId: string,
|
|
123
|
-
photoType: string,
|
|
124
|
-
app: FirebaseApp
|
|
125
|
-
): Promise<string[]> {
|
|
126
|
-
if (!photos || !Array.isArray(photos) || photos.length === 0) {
|
|
127
|
-
return [];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const uploadPromises = photos.map((photo, index) =>
|
|
131
|
-
uploadPhoto(photo, entityType, entityId, `${photoType}-${index}`, app)
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const results = await Promise.all(uploadPromises);
|
|
136
|
-
return results.filter((url): url is string => url !== null);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error(`[PHOTO_UTILS] Error uploading multiple photos:`, error);
|
|
139
|
-
return photos.filter((photo) => !photo.startsWith("data:"));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Processes photos in an entity object, uploading any data URLs
|
|
145
|
-
* @param entity - The entity object containing photo fields
|
|
146
|
-
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
147
|
-
* @param entityId - The ID of the entity
|
|
148
|
-
* @param photoFields - Array of field names that contain photos
|
|
149
|
-
* @param app - Firebase app instance
|
|
150
|
-
* @returns The entity with updated photo URLs
|
|
151
|
-
*/
|
|
152
|
-
export async function processEntityPhotos<T extends Record<string, any>>(
|
|
153
|
-
entity: T,
|
|
154
|
-
entityType: string,
|
|
155
|
-
entityId: string,
|
|
156
|
-
photoFields: { field: keyof T; type: string }[],
|
|
157
|
-
app: FirebaseApp
|
|
158
|
-
): Promise<T> {
|
|
159
|
-
const updatedEntity = { ...entity };
|
|
160
|
-
|
|
161
|
-
for (const { field, type } of photoFields) {
|
|
162
|
-
const value = entity[field];
|
|
163
|
-
|
|
164
|
-
if (!value) continue;
|
|
165
|
-
|
|
166
|
-
if (Array.isArray(value)) {
|
|
167
|
-
// Handle array of photos
|
|
168
|
-
updatedEntity[field] = (await uploadMultiplePhotos(
|
|
169
|
-
value,
|
|
170
|
-
entityType,
|
|
171
|
-
entityId,
|
|
172
|
-
type,
|
|
173
|
-
app
|
|
174
|
-
)) as any;
|
|
175
|
-
} else if (typeof value === "string") {
|
|
176
|
-
// Handle single photo
|
|
177
|
-
updatedEntity[field] = (await uploadPhoto(
|
|
178
|
-
value,
|
|
179
|
-
entityType,
|
|
180
|
-
entityId,
|
|
181
|
-
type,
|
|
182
|
-
app
|
|
183
|
-
)) as any;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return updatedEntity;
|
|
188
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
getStorage,
|
|
3
|
+
ref,
|
|
4
|
+
uploadBytes,
|
|
5
|
+
getDownloadURL,
|
|
6
|
+
deleteObject,
|
|
7
|
+
} from "firebase/storage";
|
|
8
|
+
import { FirebaseApp } from "firebase/app";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Uploads a photo to Firebase Storage
|
|
12
|
+
* @param photo - The photo data URL or URL
|
|
13
|
+
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
14
|
+
* @param entityId - The ID of the entity
|
|
15
|
+
* @param photoType - The type of photo (logo, featured, etc.)
|
|
16
|
+
* @param app - Firebase app instance
|
|
17
|
+
* @param fileName - Optional custom file name
|
|
18
|
+
* @returns The URL of the uploaded photo or null if upload failed
|
|
19
|
+
*/
|
|
20
|
+
export async function uploadPhoto(
|
|
21
|
+
photo: string | null,
|
|
22
|
+
entityType: string,
|
|
23
|
+
entityId: string,
|
|
24
|
+
photoType: string,
|
|
25
|
+
app: FirebaseApp,
|
|
26
|
+
fileName?: string
|
|
27
|
+
): Promise<string | null> {
|
|
28
|
+
// If photo is null or not a data URL, return it as is
|
|
29
|
+
if (!photo || typeof photo !== "string" || !photo.startsWith("data:")) {
|
|
30
|
+
return photo;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
console.log(
|
|
35
|
+
`[PHOTO_UTILS] Uploading ${photoType} for ${entityType}/${entityId}`
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Get Firebase Storage instance
|
|
39
|
+
const storage = getStorage(app);
|
|
40
|
+
|
|
41
|
+
// Create a reference to the storage location
|
|
42
|
+
const storageFileName = fileName || `${photoType}-${Date.now()}`;
|
|
43
|
+
const storageRef = ref(
|
|
44
|
+
storage,
|
|
45
|
+
`${entityType}/${entityId}/${storageFileName}`
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Extract base64 data from data URL
|
|
49
|
+
const base64Data = photo.split(",")[1];
|
|
50
|
+
const contentType = photo.split(";")[0].split(":")[1];
|
|
51
|
+
|
|
52
|
+
// Convert base64 to Blob
|
|
53
|
+
const byteCharacters = atob(base64Data);
|
|
54
|
+
const byteArrays = [];
|
|
55
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
56
|
+
byteArrays.push(byteCharacters.charCodeAt(i));
|
|
57
|
+
}
|
|
58
|
+
const blob = new Blob([new Uint8Array(byteArrays)], { type: contentType });
|
|
59
|
+
|
|
60
|
+
// Upload the file
|
|
61
|
+
await uploadBytes(storageRef, blob, { contentType });
|
|
62
|
+
|
|
63
|
+
// Get the download URL
|
|
64
|
+
const downloadUrl = await getDownloadURL(storageRef);
|
|
65
|
+
|
|
66
|
+
console.log(`[PHOTO_UTILS] ${photoType} uploaded successfully`, {
|
|
67
|
+
downloadUrl,
|
|
68
|
+
});
|
|
69
|
+
return downloadUrl;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(`[PHOTO_UTILS] Error uploading ${photoType}:`, error);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deletes a photo from Firebase Storage
|
|
78
|
+
* @param photoUrl - The URL of the photo to delete
|
|
79
|
+
* @param app - Firebase app instance
|
|
80
|
+
* @returns Whether the deletion was successful
|
|
81
|
+
*/
|
|
82
|
+
export async function deletePhoto(
|
|
83
|
+
photoUrl: string,
|
|
84
|
+
app: FirebaseApp
|
|
85
|
+
): Promise<boolean> {
|
|
86
|
+
if (!photoUrl || !photoUrl.includes("firebasestorage.googleapis.com")) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
console.log(`[PHOTO_UTILS] Deleting photo`, { photoUrl });
|
|
92
|
+
|
|
93
|
+
// Get Firebase Storage instance
|
|
94
|
+
const storage = getStorage(app);
|
|
95
|
+
|
|
96
|
+
// Extract the storage path from the URL
|
|
97
|
+
const storageRef = ref(storage, photoUrl);
|
|
98
|
+
|
|
99
|
+
// Delete the file
|
|
100
|
+
await deleteObject(storageRef);
|
|
101
|
+
|
|
102
|
+
console.log(`[PHOTO_UTILS] Photo deleted successfully`);
|
|
103
|
+
return true;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`[PHOTO_UTILS] Error deleting photo:`, error);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Uploads multiple photos to Firebase Storage
|
|
112
|
+
* @param photos - Array of photo data URLs or URLs
|
|
113
|
+
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
114
|
+
* @param entityId - The ID of the entity
|
|
115
|
+
* @param photoType - The type of photos (featured, gallery, etc.)
|
|
116
|
+
* @param app - Firebase app instance
|
|
117
|
+
* @returns Array of URLs of the uploaded photos
|
|
118
|
+
*/
|
|
119
|
+
export async function uploadMultiplePhotos(
|
|
120
|
+
photos: string[],
|
|
121
|
+
entityType: string,
|
|
122
|
+
entityId: string,
|
|
123
|
+
photoType: string,
|
|
124
|
+
app: FirebaseApp
|
|
125
|
+
): Promise<string[]> {
|
|
126
|
+
if (!photos || !Array.isArray(photos) || photos.length === 0) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const uploadPromises = photos.map((photo, index) =>
|
|
131
|
+
uploadPhoto(photo, entityType, entityId, `${photoType}-${index}`, app)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const results = await Promise.all(uploadPromises);
|
|
136
|
+
return results.filter((url): url is string => url !== null);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`[PHOTO_UTILS] Error uploading multiple photos:`, error);
|
|
139
|
+
return photos.filter((photo) => !photo.startsWith("data:"));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Processes photos in an entity object, uploading any data URLs
|
|
145
|
+
* @param entity - The entity object containing photo fields
|
|
146
|
+
* @param entityType - The type of entity (clinic-groups, clinics, etc.)
|
|
147
|
+
* @param entityId - The ID of the entity
|
|
148
|
+
* @param photoFields - Array of field names that contain photos
|
|
149
|
+
* @param app - Firebase app instance
|
|
150
|
+
* @returns The entity with updated photo URLs
|
|
151
|
+
*/
|
|
152
|
+
export async function processEntityPhotos<T extends Record<string, any>>(
|
|
153
|
+
entity: T,
|
|
154
|
+
entityType: string,
|
|
155
|
+
entityId: string,
|
|
156
|
+
photoFields: { field: keyof T; type: string }[],
|
|
157
|
+
app: FirebaseApp
|
|
158
|
+
): Promise<T> {
|
|
159
|
+
const updatedEntity = { ...entity };
|
|
160
|
+
|
|
161
|
+
for (const { field, type } of photoFields) {
|
|
162
|
+
const value = entity[field];
|
|
163
|
+
|
|
164
|
+
if (!value) continue;
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
// Handle array of photos
|
|
168
|
+
updatedEntity[field] = (await uploadMultiplePhotos(
|
|
169
|
+
value,
|
|
170
|
+
entityType,
|
|
171
|
+
entityId,
|
|
172
|
+
type,
|
|
173
|
+
app
|
|
174
|
+
)) as any;
|
|
175
|
+
} else if (typeof value === "string") {
|
|
176
|
+
// Handle single photo
|
|
177
|
+
updatedEntity[field] = (await uploadPhoto(
|
|
178
|
+
value,
|
|
179
|
+
entityType,
|
|
180
|
+
entityId,
|
|
181
|
+
type,
|
|
182
|
+
app
|
|
183
|
+
)) as any;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return updatedEntity;
|
|
188
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
query,
|
|
4
|
-
where,
|
|
5
|
-
getDocs,
|
|
6
|
-
Firestore,
|
|
7
|
-
QueryConstraint,
|
|
8
|
-
} from "firebase/firestore";
|
|
9
|
-
import { Clinic, ClinicTag, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
10
|
-
import { geohashQueryBounds, distanceBetween } from "geofire-common";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Finds clinics within a specified radius
|
|
14
|
-
* @param db - Firestore database instance
|
|
15
|
-
* @param center - Center coordinates
|
|
16
|
-
* @param radiusInKm - Radius in kilometers
|
|
17
|
-
* @param filters - Optional filters for services and tags
|
|
18
|
-
* @returns Array of clinics within the radius
|
|
19
|
-
*/
|
|
20
|
-
export async function findClinicsInRadius(
|
|
21
|
-
db: Firestore,
|
|
22
|
-
center: { latitude: number; longitude: number },
|
|
23
|
-
radiusInKm: number,
|
|
24
|
-
filters?: {
|
|
25
|
-
services?: string[];
|
|
26
|
-
tags?: ClinicTag[];
|
|
27
|
-
}
|
|
28
|
-
): Promise<Clinic[]> {
|
|
29
|
-
const bounds = geohashQueryBounds(
|
|
30
|
-
[center.latitude, center.longitude],
|
|
31
|
-
radiusInKm * 1000
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const matchingDocs: Clinic[] = [];
|
|
35
|
-
|
|
36
|
-
for (const b of bounds) {
|
|
37
|
-
const constraints: QueryConstraint[] = [
|
|
38
|
-
where("location.geohash", ">=", b[0]),
|
|
39
|
-
where("location.geohash", "<=", b[1]),
|
|
40
|
-
where("isActive", "==", true),
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
if (filters?.services) {
|
|
44
|
-
constraints.push(
|
|
45
|
-
where("services", "array-contains-any", filters.services)
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (filters?.tags && filters.tags.length > 0) {
|
|
50
|
-
constraints.push(where("tags", "array-contains-any", filters.tags));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
|
|
54
|
-
const querySnapshot = await getDocs(q);
|
|
55
|
-
|
|
56
|
-
for (const doc of querySnapshot.docs) {
|
|
57
|
-
const clinic = doc.data() as Clinic;
|
|
58
|
-
const distance = distanceBetween(
|
|
59
|
-
[center.latitude, center.longitude],
|
|
60
|
-
[clinic.location.latitude, clinic.location.longitude]
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// Konvertujemo u kilometre
|
|
64
|
-
const distanceInKm = distance / 1000;
|
|
65
|
-
|
|
66
|
-
if (distanceInKm <= radiusInKm) {
|
|
67
|
-
matchingDocs.push(clinic);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Sortiramo po udaljenosti
|
|
73
|
-
return matchingDocs.sort((a, b) => {
|
|
74
|
-
const distanceA = distanceBetween(
|
|
75
|
-
[center.latitude, center.longitude],
|
|
76
|
-
[a.location.latitude, a.location.longitude]
|
|
77
|
-
);
|
|
78
|
-
const distanceB = distanceBetween(
|
|
79
|
-
[center.latitude, center.longitude],
|
|
80
|
-
[b.location.latitude, b.location.longitude]
|
|
81
|
-
);
|
|
82
|
-
return distanceA - distanceB;
|
|
83
|
-
});
|
|
84
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
query,
|
|
4
|
+
where,
|
|
5
|
+
getDocs,
|
|
6
|
+
Firestore,
|
|
7
|
+
QueryConstraint,
|
|
8
|
+
} from "firebase/firestore";
|
|
9
|
+
import { Clinic, ClinicTag, CLINICS_COLLECTION } from "../../../types/clinic";
|
|
10
|
+
import { geohashQueryBounds, distanceBetween } from "geofire-common";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Finds clinics within a specified radius
|
|
14
|
+
* @param db - Firestore database instance
|
|
15
|
+
* @param center - Center coordinates
|
|
16
|
+
* @param radiusInKm - Radius in kilometers
|
|
17
|
+
* @param filters - Optional filters for services and tags
|
|
18
|
+
* @returns Array of clinics within the radius
|
|
19
|
+
*/
|
|
20
|
+
export async function findClinicsInRadius(
|
|
21
|
+
db: Firestore,
|
|
22
|
+
center: { latitude: number; longitude: number },
|
|
23
|
+
radiusInKm: number,
|
|
24
|
+
filters?: {
|
|
25
|
+
services?: string[];
|
|
26
|
+
tags?: ClinicTag[];
|
|
27
|
+
}
|
|
28
|
+
): Promise<Clinic[]> {
|
|
29
|
+
const bounds = geohashQueryBounds(
|
|
30
|
+
[center.latitude, center.longitude],
|
|
31
|
+
radiusInKm * 1000
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const matchingDocs: Clinic[] = [];
|
|
35
|
+
|
|
36
|
+
for (const b of bounds) {
|
|
37
|
+
const constraints: QueryConstraint[] = [
|
|
38
|
+
where("location.geohash", ">=", b[0]),
|
|
39
|
+
where("location.geohash", "<=", b[1]),
|
|
40
|
+
where("isActive", "==", true),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
if (filters?.services) {
|
|
44
|
+
constraints.push(
|
|
45
|
+
where("services", "array-contains-any", filters.services)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (filters?.tags && filters.tags.length > 0) {
|
|
50
|
+
constraints.push(where("tags", "array-contains-any", filters.tags));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const q = query(collection(db, CLINICS_COLLECTION), ...constraints);
|
|
54
|
+
const querySnapshot = await getDocs(q);
|
|
55
|
+
|
|
56
|
+
for (const doc of querySnapshot.docs) {
|
|
57
|
+
const clinic = doc.data() as Clinic;
|
|
58
|
+
const distance = distanceBetween(
|
|
59
|
+
[center.latitude, center.longitude],
|
|
60
|
+
[clinic.location.latitude, clinic.location.longitude]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Konvertujemo u kilometre
|
|
64
|
+
const distanceInKm = distance / 1000;
|
|
65
|
+
|
|
66
|
+
if (distanceInKm <= radiusInKm) {
|
|
67
|
+
matchingDocs.push(clinic);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Sortiramo po udaljenosti
|
|
73
|
+
return matchingDocs.sort((a, b) => {
|
|
74
|
+
const distanceA = distanceBetween(
|
|
75
|
+
[center.latitude, center.longitude],
|
|
76
|
+
[a.location.latitude, a.location.longitude]
|
|
77
|
+
);
|
|
78
|
+
const distanceB = distanceBetween(
|
|
79
|
+
[center.latitude, center.longitude],
|
|
80
|
+
[b.location.latitude, b.location.longitude]
|
|
81
|
+
);
|
|
82
|
+
return distanceA - distanceB;
|
|
83
|
+
});
|
|
84
|
+
}
|