@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.
- package/dist/backoffice/index.d.mts +77 -1
- package/dist/backoffice/index.d.ts +77 -1
- package/dist/backoffice/index.js +297 -0
- package/dist/backoffice/index.mjs +295 -0
- package/dist/index.js +27 -0
- package/dist/index.mjs +27 -0
- package/package.json +1 -1
- package/src/backoffice/services/index.ts +3 -0
- package/src/services/procedure/procedure.service.ts +33 -0
|
@@ -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 };
|
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 {
|
|
@@ -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
|
@@ -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(
|