@blackcode_sa/metaestetics-api 1.12.63 → 1.12.65

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.
@@ -1170,10 +1170,86 @@ interface ProcedureProduct {
1170
1170
  isDefault?: boolean;
1171
1171
  }
1172
1172
 
1173
+ /**
1174
+ * Enum for media access levels
1175
+ */
1176
+ declare enum MediaAccessLevel {
1177
+ PUBLIC = "public",
1178
+ PRIVATE = "private",
1179
+ CONFIDENTIAL = "confidential"
1180
+ }
1173
1181
  /**
1174
1182
  * Type that allows a field to be either a URL string or a File object
1175
1183
  */
1176
1184
  type MediaResource = string | File | Blob;
1185
+ /**
1186
+ * Media file metadata interface
1187
+ */
1188
+ interface MediaMetadata {
1189
+ id: string;
1190
+ name: string;
1191
+ url: string;
1192
+ contentType: string;
1193
+ size: number;
1194
+ createdAt: Timestamp;
1195
+ accessLevel: MediaAccessLevel;
1196
+ ownerId: string;
1197
+ collectionName: string;
1198
+ path: string;
1199
+ updatedAt?: Timestamp;
1200
+ }
1201
+ declare class MediaService extends BaseService {
1202
+ constructor(...args: ConstructorParameters<typeof BaseService>);
1203
+ /**
1204
+ * Upload a media file, store its metadata, and return the metadata including the URL.
1205
+ * @param file - The file to upload.
1206
+ * @param ownerId - ID of the owner (user, patient, clinic, etc.).
1207
+ * @param accessLevel - Access level (public, private, confidential).
1208
+ * @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
1209
+ * @param originalFileName - Optional: the original name of the file, if not using file.name.
1210
+ * @returns Promise with the media metadata.
1211
+ */
1212
+ uploadMedia(file: File | Blob, ownerId: string, accessLevel: MediaAccessLevel, collectionName: string, originalFileName?: string): Promise<MediaMetadata>;
1213
+ /**
1214
+ * Get media metadata from Firestore by its ID.
1215
+ * @param mediaId - ID of the media.
1216
+ * @returns Promise with the media metadata or null if not found.
1217
+ */
1218
+ getMediaMetadata(mediaId: string): Promise<MediaMetadata | null>;
1219
+ /**
1220
+ * Get media metadata from Firestore by its public URL.
1221
+ * @param url - The public URL of the media file.
1222
+ * @returns Promise with the media metadata or null if not found.
1223
+ */
1224
+ getMediaMetadataByUrl(url: string): Promise<MediaMetadata | null>;
1225
+ /**
1226
+ * Delete media from storage and remove metadata from Firestore.
1227
+ * @param mediaId - ID of the media to delete.
1228
+ */
1229
+ deleteMedia(mediaId: string): Promise<void>;
1230
+ /**
1231
+ * Update media access level. This involves moving the file in Firebase Storage
1232
+ * to a new path reflecting the new access level, and updating its metadata.
1233
+ * @param mediaId - ID of the media to update.
1234
+ * @param newAccessLevel - New access level.
1235
+ * @returns Promise with the updated media metadata, or null if metadata not found.
1236
+ */
1237
+ updateMediaAccessLevel(mediaId: string, newAccessLevel: MediaAccessLevel): Promise<MediaMetadata | null>;
1238
+ /**
1239
+ * List all media for an owner, optionally filtered by collection and access level.
1240
+ * @param ownerId - ID of the owner.
1241
+ * @param collectionName - Optional: Filter by collection name.
1242
+ * @param accessLevel - Optional: Filter by access level.
1243
+ * @param count - Optional: Number of items to fetch.
1244
+ * @param startAfterId - Optional: ID of the document to start after (for pagination).
1245
+ */
1246
+ listMedia(ownerId: string, collectionName?: string, accessLevel?: MediaAccessLevel, count?: number, startAfterId?: string): Promise<MediaMetadata[]>;
1247
+ /**
1248
+ * Get download URL for media. (Convenience, as URL is in metadata)
1249
+ * @param mediaId - ID of the media.
1250
+ */
1251
+ getMediaDownloadUrl(mediaId: string): Promise<string | null>;
1252
+ }
1177
1253
 
1178
1254
  /**
1179
1255
  * Aggregated summary information for a procedure.
@@ -6530,4 +6606,4 @@ declare class InvalidTreatmentBenefitError extends TreatmentBenefitError {
6530
6606
  constructor(benefit: string);
6531
6607
  }
6532
6608
 
6533
- export { BRANDS_COLLECTION, BackofficeError, BlockingCondition, BlockingConditionError, type Brand, BrandService, CATEGORIES_COLLECTION, type Category, CategoryError, CategoryNotFoundError, CategoryService, CertificationLevel, type CertificationRequirement, CertificationSpecialty, CircularReferenceError, ConstantsService, Contraindication, type ContraindicationDynamic, ContraindicationError, type ContraindicationsDocument, type CreateDocumentTemplateData, Currency, DEFAULT_CERTIFICATION_REQUIREMENT, DOCUMENTATION_TEMPLATES_COLLECTION, type DocumentElement, DocumentElementType, type DocumentTemplate, DocumentationTemplateServiceBackoffice, DynamicVariable, FILLED_DOCUMENTS_COLLECTION, HeadingLevel, type ICategoryService, type IProductService, type ITechnologyService, InvalidBlockingConditionError, InvalidCategoryDataError, InvalidContraindicationError, InvalidHierarchyError, InvalidRequirementDataError, InvalidSubcategoryDataError, InvalidTechnologyDataError, InvalidTimeframeError, InvalidTreatmentBenefitError, ListType, PRODUCTS_COLLECTION, PricingMeasure, ProcedureFamily, type ProcedureProduct, type Product, ProductService, REQUIREMENTS_COLLECTION, RelationshipError, type Requirement, RequirementError, type RequirementImportance, RequirementNotFoundError, RequirementService, RequirementType, SUBCATEGORIES_COLLECTION, type Subcategory, SubcategoryError, SubcategoryNotFoundError, SubcategoryService, TECHNOLOGIES_COLLECTION, type Technology, type TechnologyDocumentationTemplate, TechnologyError, TechnologyNotFoundError, type TechnologyRequirements, TechnologyService, type TimeFrame, TimeUnit, TreatmentBenefit, type TreatmentBenefitDynamic, TreatmentBenefitError, type TreatmentBenefitsDocument, type UpdateDocumentTemplateData, blockingConditionSchemaBackoffice, categorySchema, categoryUpdateSchema, certificationLevelSchema, certificationRequirementSchema, certificationSpecialtySchema, contraindicationDynamicSchema, contraindicationSchemaBackoffice, createDocumentTemplateSchema, documentElementSchema, documentElementWithoutIdSchema, documentTemplateSchema, procedureFamilySchemaBackoffice, requirementSchema, requirementTypeSchema, requirementUpdateSchema, subcategorySchema, subcategoryUpdateSchema, technologyRequirementsSchema, technologySchema, technologyUpdateSchema, timeUnitSchemaBackoffice, timeframeSchema, treatmentBenefitDynamicSchema, treatmentBenefitSchemaBackoffice, updateDocumentTemplateSchema };
6609
+ export { BRANDS_COLLECTION, BackofficeError, BlockingCondition, BlockingConditionError, type Brand, BrandService, CATEGORIES_COLLECTION, type Category, CategoryError, CategoryNotFoundError, CategoryService, CertificationLevel, type CertificationRequirement, CertificationSpecialty, CircularReferenceError, ConstantsService, Contraindication, type ContraindicationDynamic, ContraindicationError, type ContraindicationsDocument, type CreateDocumentTemplateData, Currency, DEFAULT_CERTIFICATION_REQUIREMENT, DOCUMENTATION_TEMPLATES_COLLECTION, type DocumentElement, DocumentElementType, type DocumentTemplate, DocumentationTemplateServiceBackoffice, DynamicVariable, FILLED_DOCUMENTS_COLLECTION, HeadingLevel, type ICategoryService, type IProductService, type ITechnologyService, InvalidBlockingConditionError, InvalidCategoryDataError, InvalidContraindicationError, InvalidHierarchyError, InvalidRequirementDataError, InvalidSubcategoryDataError, InvalidTechnologyDataError, InvalidTimeframeError, InvalidTreatmentBenefitError, ListType, MediaAccessLevel, type MediaMetadata, type MediaResource, MediaService, PRODUCTS_COLLECTION, PricingMeasure, ProcedureFamily, type ProcedureProduct, type Product, ProductService, REQUIREMENTS_COLLECTION, RelationshipError, type Requirement, RequirementError, type RequirementImportance, RequirementNotFoundError, RequirementService, RequirementType, SUBCATEGORIES_COLLECTION, type Subcategory, SubcategoryError, SubcategoryNotFoundError, SubcategoryService, TECHNOLOGIES_COLLECTION, type Technology, type TechnologyDocumentationTemplate, TechnologyError, TechnologyNotFoundError, type TechnologyRequirements, TechnologyService, type TimeFrame, TimeUnit, TreatmentBenefit, type TreatmentBenefitDynamic, TreatmentBenefitError, type TreatmentBenefitsDocument, type UpdateDocumentTemplateData, blockingConditionSchemaBackoffice, categorySchema, categoryUpdateSchema, certificationLevelSchema, certificationRequirementSchema, certificationSpecialtySchema, contraindicationDynamicSchema, contraindicationSchemaBackoffice, createDocumentTemplateSchema, documentElementSchema, documentElementWithoutIdSchema, documentTemplateSchema, procedureFamilySchemaBackoffice, requirementSchema, requirementTypeSchema, requirementUpdateSchema, subcategorySchema, subcategoryUpdateSchema, technologyRequirementsSchema, technologySchema, technologyUpdateSchema, timeUnitSchemaBackoffice, timeframeSchema, treatmentBenefitDynamicSchema, treatmentBenefitSchemaBackoffice, updateDocumentTemplateSchema };
@@ -1170,10 +1170,86 @@ interface ProcedureProduct {
1170
1170
  isDefault?: boolean;
1171
1171
  }
1172
1172
 
1173
+ /**
1174
+ * Enum for media access levels
1175
+ */
1176
+ declare enum MediaAccessLevel {
1177
+ PUBLIC = "public",
1178
+ PRIVATE = "private",
1179
+ CONFIDENTIAL = "confidential"
1180
+ }
1173
1181
  /**
1174
1182
  * Type that allows a field to be either a URL string or a File object
1175
1183
  */
1176
1184
  type MediaResource = string | File | Blob;
1185
+ /**
1186
+ * Media file metadata interface
1187
+ */
1188
+ interface MediaMetadata {
1189
+ id: string;
1190
+ name: string;
1191
+ url: string;
1192
+ contentType: string;
1193
+ size: number;
1194
+ createdAt: Timestamp;
1195
+ accessLevel: MediaAccessLevel;
1196
+ ownerId: string;
1197
+ collectionName: string;
1198
+ path: string;
1199
+ updatedAt?: Timestamp;
1200
+ }
1201
+ declare class MediaService extends BaseService {
1202
+ constructor(...args: ConstructorParameters<typeof BaseService>);
1203
+ /**
1204
+ * Upload a media file, store its metadata, and return the metadata including the URL.
1205
+ * @param file - The file to upload.
1206
+ * @param ownerId - ID of the owner (user, patient, clinic, etc.).
1207
+ * @param accessLevel - Access level (public, private, confidential).
1208
+ * @param collectionName - The logical collection name this media belongs to (e.g., 'patient_profile_pictures', 'clinic_logos').
1209
+ * @param originalFileName - Optional: the original name of the file, if not using file.name.
1210
+ * @returns Promise with the media metadata.
1211
+ */
1212
+ uploadMedia(file: File | Blob, ownerId: string, accessLevel: MediaAccessLevel, collectionName: string, originalFileName?: string): Promise<MediaMetadata>;
1213
+ /**
1214
+ * Get media metadata from Firestore by its ID.
1215
+ * @param mediaId - ID of the media.
1216
+ * @returns Promise with the media metadata or null if not found.
1217
+ */
1218
+ getMediaMetadata(mediaId: string): Promise<MediaMetadata | null>;
1219
+ /**
1220
+ * Get media metadata from Firestore by its public URL.
1221
+ * @param url - The public URL of the media file.
1222
+ * @returns Promise with the media metadata or null if not found.
1223
+ */
1224
+ getMediaMetadataByUrl(url: string): Promise<MediaMetadata | null>;
1225
+ /**
1226
+ * Delete media from storage and remove metadata from Firestore.
1227
+ * @param mediaId - ID of the media to delete.
1228
+ */
1229
+ deleteMedia(mediaId: string): Promise<void>;
1230
+ /**
1231
+ * Update media access level. This involves moving the file in Firebase Storage
1232
+ * to a new path reflecting the new access level, and updating its metadata.
1233
+ * @param mediaId - ID of the media to update.
1234
+ * @param newAccessLevel - New access level.
1235
+ * @returns Promise with the updated media metadata, or null if metadata not found.
1236
+ */
1237
+ updateMediaAccessLevel(mediaId: string, newAccessLevel: MediaAccessLevel): Promise<MediaMetadata | null>;
1238
+ /**
1239
+ * List all media for an owner, optionally filtered by collection and access level.
1240
+ * @param ownerId - ID of the owner.
1241
+ * @param collectionName - Optional: Filter by collection name.
1242
+ * @param accessLevel - Optional: Filter by access level.
1243
+ * @param count - Optional: Number of items to fetch.
1244
+ * @param startAfterId - Optional: ID of the document to start after (for pagination).
1245
+ */
1246
+ listMedia(ownerId: string, collectionName?: string, accessLevel?: MediaAccessLevel, count?: number, startAfterId?: string): Promise<MediaMetadata[]>;
1247
+ /**
1248
+ * Get download URL for media. (Convenience, as URL is in metadata)
1249
+ * @param mediaId - ID of the media.
1250
+ */
1251
+ getMediaDownloadUrl(mediaId: string): Promise<string | null>;
1252
+ }
1177
1253
 
1178
1254
  /**
1179
1255
  * Aggregated summary information for a procedure.
@@ -6530,4 +6606,4 @@ declare class InvalidTreatmentBenefitError extends TreatmentBenefitError {
6530
6606
  constructor(benefit: string);
6531
6607
  }
6532
6608
 
6533
- export { BRANDS_COLLECTION, BackofficeError, BlockingCondition, BlockingConditionError, type Brand, BrandService, CATEGORIES_COLLECTION, type Category, CategoryError, CategoryNotFoundError, CategoryService, CertificationLevel, type CertificationRequirement, CertificationSpecialty, CircularReferenceError, ConstantsService, Contraindication, type ContraindicationDynamic, ContraindicationError, type ContraindicationsDocument, type CreateDocumentTemplateData, Currency, DEFAULT_CERTIFICATION_REQUIREMENT, DOCUMENTATION_TEMPLATES_COLLECTION, type DocumentElement, DocumentElementType, type DocumentTemplate, DocumentationTemplateServiceBackoffice, DynamicVariable, FILLED_DOCUMENTS_COLLECTION, HeadingLevel, type ICategoryService, type IProductService, type ITechnologyService, InvalidBlockingConditionError, InvalidCategoryDataError, InvalidContraindicationError, InvalidHierarchyError, InvalidRequirementDataError, InvalidSubcategoryDataError, InvalidTechnologyDataError, InvalidTimeframeError, InvalidTreatmentBenefitError, ListType, PRODUCTS_COLLECTION, PricingMeasure, ProcedureFamily, type ProcedureProduct, type Product, ProductService, REQUIREMENTS_COLLECTION, RelationshipError, type Requirement, RequirementError, type RequirementImportance, RequirementNotFoundError, RequirementService, RequirementType, SUBCATEGORIES_COLLECTION, type Subcategory, SubcategoryError, SubcategoryNotFoundError, SubcategoryService, TECHNOLOGIES_COLLECTION, type Technology, type TechnologyDocumentationTemplate, TechnologyError, TechnologyNotFoundError, type TechnologyRequirements, TechnologyService, type TimeFrame, TimeUnit, TreatmentBenefit, type TreatmentBenefitDynamic, TreatmentBenefitError, type TreatmentBenefitsDocument, type UpdateDocumentTemplateData, blockingConditionSchemaBackoffice, categorySchema, categoryUpdateSchema, certificationLevelSchema, certificationRequirementSchema, certificationSpecialtySchema, contraindicationDynamicSchema, contraindicationSchemaBackoffice, createDocumentTemplateSchema, documentElementSchema, documentElementWithoutIdSchema, documentTemplateSchema, procedureFamilySchemaBackoffice, requirementSchema, requirementTypeSchema, requirementUpdateSchema, subcategorySchema, subcategoryUpdateSchema, technologyRequirementsSchema, technologySchema, technologyUpdateSchema, timeUnitSchemaBackoffice, timeframeSchema, treatmentBenefitDynamicSchema, treatmentBenefitSchemaBackoffice, updateDocumentTemplateSchema };
6609
+ export { BRANDS_COLLECTION, BackofficeError, BlockingCondition, BlockingConditionError, type Brand, BrandService, CATEGORIES_COLLECTION, type Category, CategoryError, CategoryNotFoundError, CategoryService, CertificationLevel, type CertificationRequirement, CertificationSpecialty, CircularReferenceError, ConstantsService, Contraindication, type ContraindicationDynamic, ContraindicationError, type ContraindicationsDocument, type CreateDocumentTemplateData, Currency, DEFAULT_CERTIFICATION_REQUIREMENT, DOCUMENTATION_TEMPLATES_COLLECTION, type DocumentElement, DocumentElementType, type DocumentTemplate, DocumentationTemplateServiceBackoffice, DynamicVariable, FILLED_DOCUMENTS_COLLECTION, HeadingLevel, type ICategoryService, type IProductService, type ITechnologyService, InvalidBlockingConditionError, InvalidCategoryDataError, InvalidContraindicationError, InvalidHierarchyError, InvalidRequirementDataError, InvalidSubcategoryDataError, InvalidTechnologyDataError, InvalidTimeframeError, InvalidTreatmentBenefitError, ListType, MediaAccessLevel, type MediaMetadata, type MediaResource, MediaService, PRODUCTS_COLLECTION, PricingMeasure, ProcedureFamily, type ProcedureProduct, type Product, ProductService, REQUIREMENTS_COLLECTION, RelationshipError, type Requirement, RequirementError, type RequirementImportance, RequirementNotFoundError, RequirementService, RequirementType, SUBCATEGORIES_COLLECTION, type Subcategory, SubcategoryError, SubcategoryNotFoundError, SubcategoryService, TECHNOLOGIES_COLLECTION, type Technology, type TechnologyDocumentationTemplate, TechnologyError, TechnologyNotFoundError, type TechnologyRequirements, TechnologyService, type TimeFrame, TimeUnit, TreatmentBenefit, type TreatmentBenefitDynamic, TreatmentBenefitError, type TreatmentBenefitsDocument, type UpdateDocumentTemplateData, blockingConditionSchemaBackoffice, categorySchema, categoryUpdateSchema, certificationLevelSchema, certificationRequirementSchema, certificationSpecialtySchema, contraindicationDynamicSchema, contraindicationSchemaBackoffice, createDocumentTemplateSchema, documentElementSchema, documentElementWithoutIdSchema, documentTemplateSchema, procedureFamilySchemaBackoffice, requirementSchema, requirementTypeSchema, requirementUpdateSchema, subcategorySchema, subcategoryUpdateSchema, technologyRequirementsSchema, technologySchema, technologyUpdateSchema, timeUnitSchemaBackoffice, timeframeSchema, treatmentBenefitDynamicSchema, treatmentBenefitSchemaBackoffice, updateDocumentTemplateSchema };
@@ -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 {
@@ -3720,6 +4015,8 @@ var InvalidTreatmentBenefitError = class extends TreatmentBenefitError {
3720
4015
  InvalidTimeframeError,
3721
4016
  InvalidTreatmentBenefitError,
3722
4017
  ListType,
4018
+ MediaAccessLevel,
4019
+ MediaService,
3723
4020
  PRODUCTS_COLLECTION,
3724
4021
  PricingMeasure,
3725
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 {
@@ -3746,6 +4039,8 @@ export {
3746
4039
  InvalidTimeframeError,
3747
4040
  InvalidTreatmentBenefitError,
3748
4041
  ListType,
4042
+ MediaAccessLevel,
4043
+ MediaService,
3749
4044
  PRODUCTS_COLLECTION,
3750
4045
  PricingMeasure,
3751
4046
  ProcedureFamily,
package/dist/index.js CHANGED
@@ -16839,6 +16839,15 @@ var ProcedureService = class extends BaseService {
16839
16839
  "procedure-photos"
16840
16840
  );
16841
16841
  }
16842
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
16843
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo: ${technology.photoTemplate}`);
16844
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
16845
+ if (photoTemplateUrl) {
16846
+ processedPhotos.push(photoTemplateUrl);
16847
+ } else {
16848
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
16849
+ }
16850
+ }
16842
16851
  const transformedProductsMetadata = await this.transformProductsMetadata(
16843
16852
  validatedData.productsMetadata,
16844
16853
  validatedData.technologyId
@@ -16978,6 +16987,15 @@ var ProcedureService = class extends BaseService {
16978
16987
  "procedure-photos-batch"
16979
16988
  );
16980
16989
  }
16990
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
16991
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for bulk create: ${technology.photoTemplate}`);
16992
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
16993
+ if (photoTemplateUrl) {
16994
+ processedPhotos.push(photoTemplateUrl);
16995
+ } else {
16996
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
16997
+ }
16998
+ }
16981
16999
  const transformedProductsMetadata = await this.transformProductsMetadata(
16982
17000
  validatedData.productsMetadata,
16983
17001
  validatedData.technologyId
@@ -17814,6 +17832,15 @@ var ProcedureService = class extends BaseService {
17814
17832
  if (data.photos && data.photos.length > 0) {
17815
17833
  processedPhotos = await this.processMediaArray(data.photos, procedureId, "procedure-photos");
17816
17834
  }
17835
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
17836
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for consultation: ${technology.photoTemplate}`);
17837
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
17838
+ if (photoTemplateUrl) {
17839
+ processedPhotos.push(photoTemplateUrl);
17840
+ } else {
17841
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
17842
+ }
17843
+ }
17817
17844
  const transformedProductsMetadata = await this.transformProductsMetadata(
17818
17845
  data.productsMetadata,
17819
17846
  data.technologyId
package/dist/index.mjs CHANGED
@@ -17087,6 +17087,15 @@ var ProcedureService = class extends BaseService {
17087
17087
  "procedure-photos"
17088
17088
  );
17089
17089
  }
17090
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
17091
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo: ${technology.photoTemplate}`);
17092
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
17093
+ if (photoTemplateUrl) {
17094
+ processedPhotos.push(photoTemplateUrl);
17095
+ } else {
17096
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
17097
+ }
17098
+ }
17090
17099
  const transformedProductsMetadata = await this.transformProductsMetadata(
17091
17100
  validatedData.productsMetadata,
17092
17101
  validatedData.technologyId
@@ -17226,6 +17235,15 @@ var ProcedureService = class extends BaseService {
17226
17235
  "procedure-photos-batch"
17227
17236
  );
17228
17237
  }
17238
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
17239
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for bulk create: ${technology.photoTemplate}`);
17240
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
17241
+ if (photoTemplateUrl) {
17242
+ processedPhotos.push(photoTemplateUrl);
17243
+ } else {
17244
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
17245
+ }
17246
+ }
17229
17247
  const transformedProductsMetadata = await this.transformProductsMetadata(
17230
17248
  validatedData.productsMetadata,
17231
17249
  validatedData.technologyId
@@ -18062,6 +18080,15 @@ var ProcedureService = class extends BaseService {
18062
18080
  if (data.photos && data.photos.length > 0) {
18063
18081
  processedPhotos = await this.processMediaArray(data.photos, procedureId, "procedure-photos");
18064
18082
  }
18083
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
18084
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for consultation: ${technology.photoTemplate}`);
18085
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
18086
+ if (photoTemplateUrl) {
18087
+ processedPhotos.push(photoTemplateUrl);
18088
+ } else {
18089
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
18090
+ }
18091
+ }
18065
18092
  const transformedProductsMetadata = await this.transformProductsMetadata(
18066
18093
  data.productsMetadata,
18067
18094
  data.technologyId
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.12.63",
4
+ "version": "1.12.65",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -6,3 +6,6 @@ export * from "./requirement.service";
6
6
  export * from "./subcategory.service";
7
7
  export * from "./technology.service";
8
8
  export * from "./constants.service";
9
+
10
+ // Re-export MediaService from main services for backoffice use
11
+ export { MediaService, MediaAccessLevel, type MediaMetadata, type MediaResource } from "../../services/media/media.service";
@@ -254,6 +254,17 @@ export class ProcedureService extends BaseService {
254
254
  );
255
255
  }
256
256
 
257
+ // If no photos provided and technology has a photoTemplate, use it as default photo
258
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
259
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo: ${technology.photoTemplate}`);
260
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
261
+ if (photoTemplateUrl) {
262
+ processedPhotos.push(photoTemplateUrl);
263
+ } else {
264
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
265
+ }
266
+ }
267
+
257
268
  // Transform productsMetadata from validation format to ProcedureProduct format
258
269
  const transformedProductsMetadata = await this.transformProductsMetadata(
259
270
  validatedData.productsMetadata,
@@ -435,6 +446,17 @@ export class ProcedureService extends BaseService {
435
446
  );
436
447
  }
437
448
 
449
+ // If no photos provided and technology has a photoTemplate, use it as default photo
450
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
451
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for bulk create: ${technology.photoTemplate}`);
452
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
453
+ if (photoTemplateUrl) {
454
+ processedPhotos.push(photoTemplateUrl);
455
+ } else {
456
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
457
+ }
458
+ }
459
+
438
460
  // Transform productsMetadata from validation format to ProcedureProduct format
439
461
  const transformedProductsMetadata = await this.transformProductsMetadata(
440
462
  validatedData.productsMetadata,
@@ -1505,6 +1527,17 @@ export class ProcedureService extends BaseService {
1505
1527
  processedPhotos = await this.processMediaArray(data.photos, procedureId, 'procedure-photos');
1506
1528
  }
1507
1529
 
1530
+ // If no photos provided and technology has a photoTemplate, use it as default photo
1531
+ if (processedPhotos.length === 0 && technology.photoTemplate) {
1532
+ console.log(`[ProcedureService] Using technology photoTemplate as default photo for consultation: ${technology.photoTemplate}`);
1533
+ const photoTemplateUrl = await this.mediaService.getMediaDownloadUrl(technology.photoTemplate);
1534
+ if (photoTemplateUrl) {
1535
+ processedPhotos.push(photoTemplateUrl);
1536
+ } else {
1537
+ console.warn(`[ProcedureService] Could not fetch photoTemplate URL for media ID: ${technology.photoTemplate}`);
1538
+ }
1539
+ }
1540
+
1508
1541
  // Transform productsMetadata from validation format to ProcedureProduct format
1509
1542
  // For consultations, this will return empty array since no products are provided
1510
1543
  const transformedProductsMetadata = await this.transformProductsMetadata(