@blackcode_sa/metaestetics-api 1.12.62 → 1.12.64
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 +4 -2
- package/dist/admin/index.d.ts +4 -2
- package/dist/admin/index.js +4 -45
- package/dist/admin/index.mjs +4 -45
- package/dist/backoffice/index.d.mts +86 -1
- package/dist/backoffice/index.d.ts +86 -1
- package/dist/backoffice/index.js +308 -0
- package/dist/backoffice/index.mjs +306 -0
- package/dist/index.d.mts +99 -3
- package/dist/index.d.ts +99 -3
- package/dist/index.js +545 -281
- package/dist/index.mjs +867 -603
- package/package.json +119 -119
- 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 +1844 -1844
- 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 +703 -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 +641 -689
- 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 +75 -75
- 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 +40 -40
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +318 -318
- 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 +11 -8
- 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 +395 -395
- package/src/backoffice/services/technology.service.ts +1083 -1070
- 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 +62 -62
- 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 +163 -161
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -163
- 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/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2505 -2082
- 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 +13 -13
- 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 +1682 -1682
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +636 -683
- 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/appointment/index.ts +481 -453
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +489 -489
- 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 +44 -44
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +265 -265
- 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 -273
- 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 +130 -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 +493 -493
- 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 -216
- 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 +189 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
package/dist/backoffice/index.js
CHANGED
|
@@ -53,6 +53,8 @@ __export(index_exports, {
|
|
|
53
53
|
InvalidTimeframeError: () => InvalidTimeframeError,
|
|
54
54
|
InvalidTreatmentBenefitError: () => InvalidTreatmentBenefitError,
|
|
55
55
|
ListType: () => ListType,
|
|
56
|
+
MediaAccessLevel: () => MediaAccessLevel,
|
|
57
|
+
MediaService: () => MediaService,
|
|
56
58
|
PRODUCTS_COLLECTION: () => PRODUCTS_COLLECTION,
|
|
57
59
|
PricingMeasure: () => PricingMeasure,
|
|
58
60
|
ProcedureFamily: () => ProcedureFamily,
|
|
@@ -1185,6 +1187,299 @@ var import_firestore7 = require("firebase/firestore");
|
|
|
1185
1187
|
var import_firestore5 = require("firebase/firestore");
|
|
1186
1188
|
var import_storage2 = require("firebase/storage");
|
|
1187
1189
|
var import_firestore6 = require("firebase/firestore");
|
|
1190
|
+
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
1191
|
+
MediaAccessLevel2["PUBLIC"] = "public";
|
|
1192
|
+
MediaAccessLevel2["PRIVATE"] = "private";
|
|
1193
|
+
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
1194
|
+
return MediaAccessLevel2;
|
|
1195
|
+
})(MediaAccessLevel || {});
|
|
1196
|
+
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
1197
|
+
var MediaService = class extends BaseService {
|
|
1198
|
+
constructor(...args) {
|
|
1199
|
+
super(...args);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
1203
|
+
* @param file - The file to upload.
|
|
1204
|
+
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
1205
|
+
* @param accessLevel - Access level (public, private, confidential).
|
|
1206
|
+
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
1207
|
+
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
1208
|
+
* @returns Promise with the media metadata.
|
|
1209
|
+
*/
|
|
1210
|
+
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
1211
|
+
const mediaId = this.generateId();
|
|
1212
|
+
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
1213
|
+
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
1214
|
+
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
1215
|
+
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
1216
|
+
const storageRef = (0, import_storage2.ref)(this.storage, filePath);
|
|
1217
|
+
try {
|
|
1218
|
+
const uploadResult = await (0, import_storage2.uploadBytes)(storageRef, file, {
|
|
1219
|
+
contentType: file.type
|
|
1220
|
+
});
|
|
1221
|
+
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
1222
|
+
const downloadURL = await (0, import_storage2.getDownloadURL)(uploadResult.ref);
|
|
1223
|
+
console.log("[MediaService] Got download URL:", downloadURL);
|
|
1224
|
+
const metadata = {
|
|
1225
|
+
id: mediaId,
|
|
1226
|
+
name: fileNameToUse,
|
|
1227
|
+
url: downloadURL,
|
|
1228
|
+
contentType: file.type,
|
|
1229
|
+
size: file.size,
|
|
1230
|
+
createdAt: import_firestore5.Timestamp.now(),
|
|
1231
|
+
accessLevel,
|
|
1232
|
+
ownerId,
|
|
1233
|
+
collectionName,
|
|
1234
|
+
path: filePath
|
|
1235
|
+
};
|
|
1236
|
+
const metadataDocRef = (0, import_firestore6.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1237
|
+
await (0, import_firestore6.setDoc)(metadataDocRef, metadata);
|
|
1238
|
+
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
1239
|
+
return metadata;
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
console.error("[MediaService] Error during media upload:", error);
|
|
1242
|
+
throw error;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Get media metadata from Firestore by its ID.
|
|
1247
|
+
* @param mediaId - ID of the media.
|
|
1248
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1249
|
+
*/
|
|
1250
|
+
async getMediaMetadata(mediaId) {
|
|
1251
|
+
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
1252
|
+
const docRef = (0, import_firestore6.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1253
|
+
const docSnap = await (0, import_firestore6.getDoc)(docRef);
|
|
1254
|
+
if (docSnap.exists()) {
|
|
1255
|
+
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
1256
|
+
return docSnap.data();
|
|
1257
|
+
}
|
|
1258
|
+
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Get media metadata from Firestore by its public URL.
|
|
1263
|
+
* @param url - The public URL of the media file.
|
|
1264
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1265
|
+
*/
|
|
1266
|
+
async getMediaMetadataByUrl(url) {
|
|
1267
|
+
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
1268
|
+
const q = (0, import_firestore6.query)(
|
|
1269
|
+
(0, import_firestore6.collection)(this.db, MEDIA_METADATA_COLLECTION),
|
|
1270
|
+
(0, import_firestore6.where)("url", "==", url),
|
|
1271
|
+
(0, import_firestore6.limit)(1)
|
|
1272
|
+
);
|
|
1273
|
+
try {
|
|
1274
|
+
const querySnapshot = await (0, import_firestore6.getDocs)(q);
|
|
1275
|
+
if (!querySnapshot.empty) {
|
|
1276
|
+
const metadata = querySnapshot.docs[0].data();
|
|
1277
|
+
console.log("[MediaService] Metadata found by URL:", metadata);
|
|
1278
|
+
return metadata;
|
|
1279
|
+
}
|
|
1280
|
+
console.log("[MediaService] No metadata found for URL:", url);
|
|
1281
|
+
return null;
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
1284
|
+
throw error;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Delete media from storage and remove metadata from Firestore.
|
|
1289
|
+
* @param mediaId - ID of the media to delete.
|
|
1290
|
+
*/
|
|
1291
|
+
async deleteMedia(mediaId) {
|
|
1292
|
+
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
1293
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1294
|
+
if (!metadata) {
|
|
1295
|
+
console.warn(
|
|
1296
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
1297
|
+
);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const storageFileRef = (0, import_storage2.ref)(this.storage, metadata.path);
|
|
1301
|
+
try {
|
|
1302
|
+
await (0, import_storage2.deleteObject)(storageFileRef);
|
|
1303
|
+
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
1304
|
+
const metadataDocRef = (0, import_firestore6.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1305
|
+
await (0, import_firestore6.deleteDoc)(metadataDocRef);
|
|
1306
|
+
console.log(
|
|
1307
|
+
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
1308
|
+
);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Update media access level. This involves moving the file in Firebase Storage
|
|
1316
|
+
* to a new path reflecting the new access level, and updating its metadata.
|
|
1317
|
+
* @param mediaId - ID of the media to update.
|
|
1318
|
+
* @param newAccessLevel - New access level.
|
|
1319
|
+
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
1320
|
+
*/
|
|
1321
|
+
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
1322
|
+
var _a;
|
|
1323
|
+
console.log(
|
|
1324
|
+
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
1325
|
+
);
|
|
1326
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1327
|
+
if (!metadata) {
|
|
1328
|
+
console.warn(
|
|
1329
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
1330
|
+
);
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
if (metadata.accessLevel === newAccessLevel) {
|
|
1334
|
+
console.log(
|
|
1335
|
+
`[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
|
|
1336
|
+
);
|
|
1337
|
+
const metadataDocRef = (0, import_firestore6.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1338
|
+
try {
|
|
1339
|
+
await (0, import_firestore6.updateDoc)(metadataDocRef, { updatedAt: import_firestore5.Timestamp.now() });
|
|
1340
|
+
return { ...metadata, updatedAt: import_firestore5.Timestamp.now() };
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
console.error(
|
|
1343
|
+
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
1344
|
+
error
|
|
1345
|
+
);
|
|
1346
|
+
throw error;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const oldStoragePath = metadata.path;
|
|
1350
|
+
const fileNamePart = `${metadata.id}-${metadata.name}`;
|
|
1351
|
+
const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
|
|
1352
|
+
console.log(
|
|
1353
|
+
`[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
|
|
1354
|
+
);
|
|
1355
|
+
const oldStorageFileRef = (0, import_storage2.ref)(this.storage, oldStoragePath);
|
|
1356
|
+
const newStorageFileRef = (0, import_storage2.ref)(this.storage, newStoragePath);
|
|
1357
|
+
try {
|
|
1358
|
+
console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
|
|
1359
|
+
const fileBytes = await (0, import_storage2.getBytes)(oldStorageFileRef);
|
|
1360
|
+
console.log(
|
|
1361
|
+
`[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
|
|
1362
|
+
);
|
|
1363
|
+
console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
|
|
1364
|
+
await (0, import_storage2.uploadBytes)(newStorageFileRef, fileBytes, {
|
|
1365
|
+
contentType: metadata.contentType
|
|
1366
|
+
});
|
|
1367
|
+
console.log(
|
|
1368
|
+
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
1369
|
+
);
|
|
1370
|
+
const newDownloadURL = await (0, import_storage2.getDownloadURL)(newStorageFileRef);
|
|
1371
|
+
console.log(
|
|
1372
|
+
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
1373
|
+
);
|
|
1374
|
+
const updateData = {
|
|
1375
|
+
accessLevel: newAccessLevel,
|
|
1376
|
+
path: newStoragePath,
|
|
1377
|
+
url: newDownloadURL,
|
|
1378
|
+
updatedAt: import_firestore5.Timestamp.now()
|
|
1379
|
+
};
|
|
1380
|
+
const metadataDocRef = (0, import_firestore6.doc)(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1381
|
+
console.log(
|
|
1382
|
+
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
1383
|
+
updateData
|
|
1384
|
+
);
|
|
1385
|
+
await (0, import_firestore6.updateDoc)(metadataDocRef, updateData);
|
|
1386
|
+
console.log(
|
|
1387
|
+
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
1388
|
+
);
|
|
1389
|
+
try {
|
|
1390
|
+
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
1391
|
+
await (0, import_storage2.deleteObject)(oldStorageFileRef);
|
|
1392
|
+
console.log(
|
|
1393
|
+
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
1394
|
+
);
|
|
1395
|
+
} catch (deleteError) {
|
|
1396
|
+
console.error(
|
|
1397
|
+
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
1398
|
+
deleteError
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
return { ...metadata, ...updateData };
|
|
1402
|
+
} catch (error) {
|
|
1403
|
+
console.error(
|
|
1404
|
+
`[MediaService] Error updating media access level and moving file for ${mediaId}:`,
|
|
1405
|
+
error
|
|
1406
|
+
);
|
|
1407
|
+
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
1408
|
+
console.warn(
|
|
1409
|
+
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
1410
|
+
);
|
|
1411
|
+
try {
|
|
1412
|
+
await (0, import_storage2.deleteObject)(newStorageFileRef);
|
|
1413
|
+
console.warn(
|
|
1414
|
+
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
1415
|
+
);
|
|
1416
|
+
} catch (cleanupError) {
|
|
1417
|
+
console.error(
|
|
1418
|
+
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
1419
|
+
cleanupError
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
throw error;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* List all media for an owner, optionally filtered by collection and access level.
|
|
1428
|
+
* @param ownerId - ID of the owner.
|
|
1429
|
+
* @param collectionName - Optional: Filter by collection name.
|
|
1430
|
+
* @param accessLevel - Optional: Filter by access level.
|
|
1431
|
+
* @param count - Optional: Number of items to fetch.
|
|
1432
|
+
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
1433
|
+
*/
|
|
1434
|
+
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
1435
|
+
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
1436
|
+
let qConstraints = [(0, import_firestore6.where)("ownerId", "==", ownerId)];
|
|
1437
|
+
if (collectionName) {
|
|
1438
|
+
qConstraints.push((0, import_firestore6.where)("collectionName", "==", collectionName));
|
|
1439
|
+
}
|
|
1440
|
+
if (accessLevel) {
|
|
1441
|
+
qConstraints.push((0, import_firestore6.where)("accessLevel", "==", accessLevel));
|
|
1442
|
+
}
|
|
1443
|
+
qConstraints.push((0, import_firestore6.orderBy)("createdAt", "desc"));
|
|
1444
|
+
if (count) {
|
|
1445
|
+
qConstraints.push((0, import_firestore6.limit)(count));
|
|
1446
|
+
}
|
|
1447
|
+
if (startAfterId) {
|
|
1448
|
+
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
1449
|
+
if (startAfterDoc) {
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const finalQuery = (0, import_firestore6.query)(
|
|
1453
|
+
(0, import_firestore6.collection)(this.db, MEDIA_METADATA_COLLECTION),
|
|
1454
|
+
...qConstraints
|
|
1455
|
+
);
|
|
1456
|
+
try {
|
|
1457
|
+
const querySnapshot = await (0, import_firestore6.getDocs)(finalQuery);
|
|
1458
|
+
const mediaList = querySnapshot.docs.map(
|
|
1459
|
+
(doc11) => doc11.data()
|
|
1460
|
+
);
|
|
1461
|
+
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
1462
|
+
return mediaList;
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
console.error("[MediaService] Error listing media:", error);
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Get download URL for media. (Convenience, as URL is in metadata)
|
|
1470
|
+
* @param mediaId - ID of the media.
|
|
1471
|
+
*/
|
|
1472
|
+
async getMediaDownloadUrl(mediaId) {
|
|
1473
|
+
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
1474
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1475
|
+
if (metadata && metadata.url) {
|
|
1476
|
+
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
1477
|
+
return metadata.url;
|
|
1478
|
+
}
|
|
1479
|
+
console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1188
1483
|
|
|
1189
1484
|
// src/backoffice/services/documentation-template.service.ts
|
|
1190
1485
|
var DocumentationTemplateServiceBackoffice = class {
|
|
@@ -2285,6 +2580,9 @@ var TechnologyService = class extends BaseService {
|
|
|
2285
2580
|
if (technology.technicalDetails) {
|
|
2286
2581
|
newTechnology.technicalDetails = technology.technicalDetails;
|
|
2287
2582
|
}
|
|
2583
|
+
if (technology.photoTemplate) {
|
|
2584
|
+
newTechnology.photoTemplate = technology.photoTemplate;
|
|
2585
|
+
}
|
|
2288
2586
|
const docRef = await (0, import_firestore11.addDoc)(this.technologiesRef, newTechnology);
|
|
2289
2587
|
return { id: docRef.id, ...newTechnology };
|
|
2290
2588
|
}
|
|
@@ -2407,6 +2705,13 @@ var TechnologyService = class extends BaseService {
|
|
|
2407
2705
|
delete updateData[key];
|
|
2408
2706
|
}
|
|
2409
2707
|
});
|
|
2708
|
+
if ("photoTemplate" in technology) {
|
|
2709
|
+
if (technology.photoTemplate === null || technology.photoTemplate === "") {
|
|
2710
|
+
updateData.photoTemplate = null;
|
|
2711
|
+
} else if (technology.photoTemplate !== void 0) {
|
|
2712
|
+
updateData.photoTemplate = technology.photoTemplate;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2410
2715
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
2411
2716
|
const docRef = (0, import_firestore11.doc)(this.technologiesRef, id);
|
|
2412
2717
|
const beforeTech = await this.getById(id);
|
|
@@ -3510,6 +3815,7 @@ var technologySchema = import_zod2.z.object({
|
|
|
3510
3815
|
documentationTemplates: import_zod2.z.array(documentTemplateSchema),
|
|
3511
3816
|
benefits: import_zod2.z.array(treatmentBenefitSchemaBackoffice),
|
|
3512
3817
|
certificationRequirement: certificationRequirementSchema,
|
|
3818
|
+
photoTemplate: import_zod2.z.string().url("Photo template must be a valid URL").optional(),
|
|
3513
3819
|
isActive: import_zod2.z.boolean().default(true)
|
|
3514
3820
|
});
|
|
3515
3821
|
var categorySchema = import_zod2.z.object({
|
|
@@ -3709,6 +4015,8 @@ var InvalidTreatmentBenefitError = class extends TreatmentBenefitError {
|
|
|
3709
4015
|
InvalidTimeframeError,
|
|
3710
4016
|
InvalidTreatmentBenefitError,
|
|
3711
4017
|
ListType,
|
|
4018
|
+
MediaAccessLevel,
|
|
4019
|
+
MediaService,
|
|
3712
4020
|
PRODUCTS_COLLECTION,
|
|
3713
4021
|
PricingMeasure,
|
|
3714
4022
|
ProcedureFamily,
|
|
@@ -1148,6 +1148,299 @@ import {
|
|
|
1148
1148
|
deleteDoc as deleteDoc2,
|
|
1149
1149
|
orderBy as orderBy4
|
|
1150
1150
|
} from "firebase/firestore";
|
|
1151
|
+
var MediaAccessLevel = /* @__PURE__ */ ((MediaAccessLevel2) => {
|
|
1152
|
+
MediaAccessLevel2["PUBLIC"] = "public";
|
|
1153
|
+
MediaAccessLevel2["PRIVATE"] = "private";
|
|
1154
|
+
MediaAccessLevel2["CONFIDENTIAL"] = "confidential";
|
|
1155
|
+
return MediaAccessLevel2;
|
|
1156
|
+
})(MediaAccessLevel || {});
|
|
1157
|
+
var MEDIA_METADATA_COLLECTION = "media_metadata";
|
|
1158
|
+
var MediaService = class extends BaseService {
|
|
1159
|
+
constructor(...args) {
|
|
1160
|
+
super(...args);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Upload a media file, store its metadata, and return the metadata including the URL.
|
|
1164
|
+
* @param file - The file to upload.
|
|
1165
|
+
* @param ownerId - ID of the owner (user, patient, clinic, etc.).
|
|
1166
|
+
* @param accessLevel - Access level (public, private, confidential).
|
|
1167
|
+
* @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
|
|
1168
|
+
* @param originalFileName - Optional: the original name of the file, if not using file.name.
|
|
1169
|
+
* @returns Promise with the media metadata.
|
|
1170
|
+
*/
|
|
1171
|
+
async uploadMedia(file, ownerId, accessLevel, collectionName, originalFileName) {
|
|
1172
|
+
const mediaId = this.generateId();
|
|
1173
|
+
const fileNameToUse = originalFileName || (file instanceof File ? file.name : file.toString());
|
|
1174
|
+
const uniqueFileName = `${mediaId}-${fileNameToUse}`;
|
|
1175
|
+
const filePath = `media/${accessLevel}/${ownerId}/${collectionName}/${uniqueFileName}`;
|
|
1176
|
+
console.log(`[MediaService] Uploading file to: ${filePath}`);
|
|
1177
|
+
const storageRef = ref(this.storage, filePath);
|
|
1178
|
+
try {
|
|
1179
|
+
const uploadResult = await uploadBytes(storageRef, file, {
|
|
1180
|
+
contentType: file.type
|
|
1181
|
+
});
|
|
1182
|
+
console.log("[MediaService] File uploaded successfully", uploadResult);
|
|
1183
|
+
const downloadURL = await getDownloadURL(uploadResult.ref);
|
|
1184
|
+
console.log("[MediaService] Got download URL:", downloadURL);
|
|
1185
|
+
const metadata = {
|
|
1186
|
+
id: mediaId,
|
|
1187
|
+
name: fileNameToUse,
|
|
1188
|
+
url: downloadURL,
|
|
1189
|
+
contentType: file.type,
|
|
1190
|
+
size: file.size,
|
|
1191
|
+
createdAt: Timestamp2.now(),
|
|
1192
|
+
accessLevel,
|
|
1193
|
+
ownerId,
|
|
1194
|
+
collectionName,
|
|
1195
|
+
path: filePath
|
|
1196
|
+
};
|
|
1197
|
+
const metadataDocRef = doc4(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1198
|
+
await setDoc2(metadataDocRef, metadata);
|
|
1199
|
+
console.log("[MediaService] Metadata stored in Firestore:", mediaId);
|
|
1200
|
+
return metadata;
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
console.error("[MediaService] Error during media upload:", error);
|
|
1203
|
+
throw error;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Get media metadata from Firestore by its ID.
|
|
1208
|
+
* @param mediaId - ID of the media.
|
|
1209
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1210
|
+
*/
|
|
1211
|
+
async getMediaMetadata(mediaId) {
|
|
1212
|
+
console.log(`[MediaService] Getting media metadata for ID: ${mediaId}`);
|
|
1213
|
+
const docRef = doc4(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1214
|
+
const docSnap = await getDoc4(docRef);
|
|
1215
|
+
if (docSnap.exists()) {
|
|
1216
|
+
console.log("[MediaService] Metadata found:", docSnap.data());
|
|
1217
|
+
return docSnap.data();
|
|
1218
|
+
}
|
|
1219
|
+
console.log("[MediaService] No metadata found for ID:", mediaId);
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Get media metadata from Firestore by its public URL.
|
|
1224
|
+
* @param url - The public URL of the media file.
|
|
1225
|
+
* @returns Promise with the media metadata or null if not found.
|
|
1226
|
+
*/
|
|
1227
|
+
async getMediaMetadataByUrl(url) {
|
|
1228
|
+
console.log(`[MediaService] Getting media metadata by URL: ${url}`);
|
|
1229
|
+
const q = query4(
|
|
1230
|
+
collection4(this.db, MEDIA_METADATA_COLLECTION),
|
|
1231
|
+
where4("url", "==", url),
|
|
1232
|
+
limit4(1)
|
|
1233
|
+
);
|
|
1234
|
+
try {
|
|
1235
|
+
const querySnapshot = await getDocs4(q);
|
|
1236
|
+
if (!querySnapshot.empty) {
|
|
1237
|
+
const metadata = querySnapshot.docs[0].data();
|
|
1238
|
+
console.log("[MediaService] Metadata found by URL:", metadata);
|
|
1239
|
+
return metadata;
|
|
1240
|
+
}
|
|
1241
|
+
console.log("[MediaService] No metadata found for URL:", url);
|
|
1242
|
+
return null;
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
console.error("[MediaService] Error fetching metadata by URL:", error);
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Delete media from storage and remove metadata from Firestore.
|
|
1250
|
+
* @param mediaId - ID of the media to delete.
|
|
1251
|
+
*/
|
|
1252
|
+
async deleteMedia(mediaId) {
|
|
1253
|
+
console.log(`[MediaService] Deleting media with ID: ${mediaId}`);
|
|
1254
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1255
|
+
if (!metadata) {
|
|
1256
|
+
console.warn(
|
|
1257
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot delete.`
|
|
1258
|
+
);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const storageFileRef = ref(this.storage, metadata.path);
|
|
1262
|
+
try {
|
|
1263
|
+
await deleteObject(storageFileRef);
|
|
1264
|
+
console.log(`[MediaService] File deleted from Storage: ${metadata.path}`);
|
|
1265
|
+
const metadataDocRef = doc4(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1266
|
+
await deleteDoc2(metadataDocRef);
|
|
1267
|
+
console.log(
|
|
1268
|
+
`[MediaService] Metadata deleted from Firestore for ID: ${mediaId}`
|
|
1269
|
+
);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
console.error(`[MediaService] Error deleting media ${mediaId}:`, error);
|
|
1272
|
+
throw error;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Update media access level. This involves moving the file in Firebase Storage
|
|
1277
|
+
* to a new path reflecting the new access level, and updating its metadata.
|
|
1278
|
+
* @param mediaId - ID of the media to update.
|
|
1279
|
+
* @param newAccessLevel - New access level.
|
|
1280
|
+
* @returns Promise with the updated media metadata, or null if metadata not found.
|
|
1281
|
+
*/
|
|
1282
|
+
async updateMediaAccessLevel(mediaId, newAccessLevel) {
|
|
1283
|
+
var _a;
|
|
1284
|
+
console.log(
|
|
1285
|
+
`[MediaService] Attempting to update access level for media ID: ${mediaId} to ${newAccessLevel}`
|
|
1286
|
+
);
|
|
1287
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1288
|
+
if (!metadata) {
|
|
1289
|
+
console.warn(
|
|
1290
|
+
`[MediaService] Metadata not found for media ID ${mediaId}. Cannot update access level.`
|
|
1291
|
+
);
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
if (metadata.accessLevel === newAccessLevel) {
|
|
1295
|
+
console.log(
|
|
1296
|
+
`[MediaService] Media ID ${mediaId} already has access level ${newAccessLevel}. Updating timestamp only.`
|
|
1297
|
+
);
|
|
1298
|
+
const metadataDocRef = doc4(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1299
|
+
try {
|
|
1300
|
+
await updateDoc4(metadataDocRef, { updatedAt: Timestamp2.now() });
|
|
1301
|
+
return { ...metadata, updatedAt: Timestamp2.now() };
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
console.error(
|
|
1304
|
+
`[MediaService] Error updating timestamp for media ID ${mediaId}:`,
|
|
1305
|
+
error
|
|
1306
|
+
);
|
|
1307
|
+
throw error;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
const oldStoragePath = metadata.path;
|
|
1311
|
+
const fileNamePart = `${metadata.id}-${metadata.name}`;
|
|
1312
|
+
const newStoragePath = `media/${newAccessLevel}/${metadata.ownerId}/${metadata.collectionName}/${fileNamePart}`;
|
|
1313
|
+
console.log(
|
|
1314
|
+
`[MediaService] Moving file for ${mediaId} from ${oldStoragePath} to ${newStoragePath}`
|
|
1315
|
+
);
|
|
1316
|
+
const oldStorageFileRef = ref(this.storage, oldStoragePath);
|
|
1317
|
+
const newStorageFileRef = ref(this.storage, newStoragePath);
|
|
1318
|
+
try {
|
|
1319
|
+
console.log(`[MediaService] Downloading bytes from ${oldStoragePath}`);
|
|
1320
|
+
const fileBytes = await getBytes(oldStorageFileRef);
|
|
1321
|
+
console.log(
|
|
1322
|
+
`[MediaService] Successfully downloaded ${fileBytes.byteLength} bytes from ${oldStoragePath}`
|
|
1323
|
+
);
|
|
1324
|
+
console.log(`[MediaService] Uploading bytes to ${newStoragePath}`);
|
|
1325
|
+
await uploadBytes(newStorageFileRef, fileBytes, {
|
|
1326
|
+
contentType: metadata.contentType
|
|
1327
|
+
});
|
|
1328
|
+
console.log(
|
|
1329
|
+
`[MediaService] Successfully uploaded bytes to ${newStoragePath}`
|
|
1330
|
+
);
|
|
1331
|
+
const newDownloadURL = await getDownloadURL(newStorageFileRef);
|
|
1332
|
+
console.log(
|
|
1333
|
+
`[MediaService] Got new download URL for ${newStoragePath}: ${newDownloadURL}`
|
|
1334
|
+
);
|
|
1335
|
+
const updateData = {
|
|
1336
|
+
accessLevel: newAccessLevel,
|
|
1337
|
+
path: newStoragePath,
|
|
1338
|
+
url: newDownloadURL,
|
|
1339
|
+
updatedAt: Timestamp2.now()
|
|
1340
|
+
};
|
|
1341
|
+
const metadataDocRef = doc4(this.db, MEDIA_METADATA_COLLECTION, mediaId);
|
|
1342
|
+
console.log(
|
|
1343
|
+
`[MediaService] Updating Firestore metadata for ${mediaId} with new data:`,
|
|
1344
|
+
updateData
|
|
1345
|
+
);
|
|
1346
|
+
await updateDoc4(metadataDocRef, updateData);
|
|
1347
|
+
console.log(
|
|
1348
|
+
`[MediaService] Successfully updated Firestore metadata for ${mediaId}`
|
|
1349
|
+
);
|
|
1350
|
+
try {
|
|
1351
|
+
console.log(`[MediaService] Deleting old file from ${oldStoragePath}`);
|
|
1352
|
+
await deleteObject(oldStorageFileRef);
|
|
1353
|
+
console.log(
|
|
1354
|
+
`[MediaService] Successfully deleted old file from ${oldStoragePath}`
|
|
1355
|
+
);
|
|
1356
|
+
} catch (deleteError) {
|
|
1357
|
+
console.error(
|
|
1358
|
+
`[MediaService] Failed to delete old file from ${oldStoragePath} for media ID ${mediaId}. This file is now orphaned. Error:`,
|
|
1359
|
+
deleteError
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
return { ...metadata, ...updateData };
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error(
|
|
1365
|
+
`[MediaService] Error updating media access level and moving file for ${mediaId}:`,
|
|
1366
|
+
error
|
|
1367
|
+
);
|
|
1368
|
+
if (newStorageFileRef && error.code !== "storage/object-not-found" && ((_a = error.message) == null ? void 0 : _a.includes("uploadBytes"))) {
|
|
1369
|
+
console.warn(
|
|
1370
|
+
`[MediaService] Attempting to delete partially uploaded file at ${newStoragePath} due to error.`
|
|
1371
|
+
);
|
|
1372
|
+
try {
|
|
1373
|
+
await deleteObject(newStorageFileRef);
|
|
1374
|
+
console.warn(
|
|
1375
|
+
`[MediaService] Cleaned up partially uploaded file at ${newStoragePath}.`
|
|
1376
|
+
);
|
|
1377
|
+
} catch (cleanupError) {
|
|
1378
|
+
console.error(
|
|
1379
|
+
`[MediaService] Failed to cleanup partially uploaded file at ${newStoragePath}:`,
|
|
1380
|
+
cleanupError
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
throw error;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* List all media for an owner, optionally filtered by collection and access level.
|
|
1389
|
+
* @param ownerId - ID of the owner.
|
|
1390
|
+
* @param collectionName - Optional: Filter by collection name.
|
|
1391
|
+
* @param accessLevel - Optional: Filter by access level.
|
|
1392
|
+
* @param count - Optional: Number of items to fetch.
|
|
1393
|
+
* @param startAfterId - Optional: ID of the document to start after (for pagination).
|
|
1394
|
+
*/
|
|
1395
|
+
async listMedia(ownerId, collectionName, accessLevel, count, startAfterId) {
|
|
1396
|
+
console.log(`[MediaService] Listing media for owner: ${ownerId}`);
|
|
1397
|
+
let qConstraints = [where4("ownerId", "==", ownerId)];
|
|
1398
|
+
if (collectionName) {
|
|
1399
|
+
qConstraints.push(where4("collectionName", "==", collectionName));
|
|
1400
|
+
}
|
|
1401
|
+
if (accessLevel) {
|
|
1402
|
+
qConstraints.push(where4("accessLevel", "==", accessLevel));
|
|
1403
|
+
}
|
|
1404
|
+
qConstraints.push(orderBy4("createdAt", "desc"));
|
|
1405
|
+
if (count) {
|
|
1406
|
+
qConstraints.push(limit4(count));
|
|
1407
|
+
}
|
|
1408
|
+
if (startAfterId) {
|
|
1409
|
+
const startAfterDoc = await this.getMediaMetadata(startAfterId);
|
|
1410
|
+
if (startAfterDoc) {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
const finalQuery = query4(
|
|
1414
|
+
collection4(this.db, MEDIA_METADATA_COLLECTION),
|
|
1415
|
+
...qConstraints
|
|
1416
|
+
);
|
|
1417
|
+
try {
|
|
1418
|
+
const querySnapshot = await getDocs4(finalQuery);
|
|
1419
|
+
const mediaList = querySnapshot.docs.map(
|
|
1420
|
+
(doc11) => doc11.data()
|
|
1421
|
+
);
|
|
1422
|
+
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
1423
|
+
return mediaList;
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
console.error("[MediaService] Error listing media:", error);
|
|
1426
|
+
throw error;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Get download URL for media. (Convenience, as URL is in metadata)
|
|
1431
|
+
* @param mediaId - ID of the media.
|
|
1432
|
+
*/
|
|
1433
|
+
async getMediaDownloadUrl(mediaId) {
|
|
1434
|
+
console.log(`[MediaService] Getting download URL for media ID: ${mediaId}`);
|
|
1435
|
+
const metadata = await this.getMediaMetadata(mediaId);
|
|
1436
|
+
if (metadata && metadata.url) {
|
|
1437
|
+
console.log(`[MediaService] URL found: ${metadata.url}`);
|
|
1438
|
+
return metadata.url;
|
|
1439
|
+
}
|
|
1440
|
+
console.log(`[MediaService] URL not found for media ID: ${mediaId}`);
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1151
1444
|
|
|
1152
1445
|
// src/backoffice/services/documentation-template.service.ts
|
|
1153
1446
|
var DocumentationTemplateServiceBackoffice = class {
|
|
@@ -2305,6 +2598,9 @@ var TechnologyService = class extends BaseService {
|
|
|
2305
2598
|
if (technology.technicalDetails) {
|
|
2306
2599
|
newTechnology.technicalDetails = technology.technicalDetails;
|
|
2307
2600
|
}
|
|
2601
|
+
if (technology.photoTemplate) {
|
|
2602
|
+
newTechnology.photoTemplate = technology.photoTemplate;
|
|
2603
|
+
}
|
|
2308
2604
|
const docRef = await addDoc6(this.technologiesRef, newTechnology);
|
|
2309
2605
|
return { id: docRef.id, ...newTechnology };
|
|
2310
2606
|
}
|
|
@@ -2427,6 +2723,13 @@ var TechnologyService = class extends BaseService {
|
|
|
2427
2723
|
delete updateData[key];
|
|
2428
2724
|
}
|
|
2429
2725
|
});
|
|
2726
|
+
if ("photoTemplate" in technology) {
|
|
2727
|
+
if (technology.photoTemplate === null || technology.photoTemplate === "") {
|
|
2728
|
+
updateData.photoTemplate = null;
|
|
2729
|
+
} else if (technology.photoTemplate !== void 0) {
|
|
2730
|
+
updateData.photoTemplate = technology.photoTemplate;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2430
2733
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
2431
2734
|
const docRef = doc9(this.technologiesRef, id);
|
|
2432
2735
|
const beforeTech = await this.getById(id);
|
|
@@ -3537,6 +3840,7 @@ var technologySchema = z2.object({
|
|
|
3537
3840
|
documentationTemplates: z2.array(documentTemplateSchema),
|
|
3538
3841
|
benefits: z2.array(treatmentBenefitSchemaBackoffice),
|
|
3539
3842
|
certificationRequirement: certificationRequirementSchema,
|
|
3843
|
+
photoTemplate: z2.string().url("Photo template must be a valid URL").optional(),
|
|
3540
3844
|
isActive: z2.boolean().default(true)
|
|
3541
3845
|
});
|
|
3542
3846
|
var categorySchema = z2.object({
|
|
@@ -3735,6 +4039,8 @@ export {
|
|
|
3735
4039
|
InvalidTimeframeError,
|
|
3736
4040
|
InvalidTreatmentBenefitError,
|
|
3737
4041
|
ListType,
|
|
4042
|
+
MediaAccessLevel,
|
|
4043
|
+
MediaService,
|
|
3738
4044
|
PRODUCTS_COLLECTION,
|
|
3739
4045
|
PricingMeasure,
|
|
3740
4046
|
ProcedureFamily,
|