diff --git a/src/database/schemas/management/documenttemplate.schema.js b/src/database/schemas/management/documenttemplate.schema.js index 48e75ea..1149c66 100644 --- a/src/database/schemas/management/documenttemplate.schema.js +++ b/src/database/schemas/management/documenttemplate.schema.js @@ -2,8 +2,33 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; -const RENDER_DOCUMENT_TEMPLATE_CALL = - /fc\.renderDocumentTemplate\s*\(\s*(['"])([^'"]+)\1/g; +const RESOURCE_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + +function normalizeTemplateResourceName(value) { + return String(value ?? '') + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} + +function validateTemplateResources(resources) { + const names = new Set(); + for (const resource of resources || []) { + if ( + !RESOURCE_NAME_PATTERN.test(resource?.name || '') || + resource?.file == null || + resource.file === '' || + names.has(resource.name) + ) { + return false; + } + names.add(resource.name); + } + return true; +} + +const RENDER_DOCUMENT_TEMPLATE_CALL = /fc\.renderDocumentTemplate\s*\(\s*(['"])([^'"]+)\1/g; function extractRenderDocumentTemplateReferences(content) { if (content == null || typeof content !== 'string' || content === '') { @@ -103,6 +128,27 @@ const documentTemplateSchema = new Schema( refPath: 'objectType', required: false, }, + resources: { + type: [ + new Schema( + { + name: { + type: String, + required: true, + set: normalizeTemplateResourceName, + match: RESOURCE_NAME_PATTERN, + }, + file: { type: Schema.Types.ObjectId, ref: 'file', required: true }, + }, + { _id: true } + ), + ], + default: [], + validate: { + validator: validateTemplateResources, + message: 'Template resource names must be unique.', + }, + }, }, { timestamps: true } ); diff --git a/src/database/schemas/management/emailtemplate.schema.js b/src/database/schemas/management/emailtemplate.schema.js index 612fe8f..844ec1f 100644 --- a/src/database/schemas/management/emailtemplate.schema.js +++ b/src/database/schemas/management/emailtemplate.schema.js @@ -2,6 +2,32 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; +const RESOURCE_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; + +function normalizeTemplateResourceName(value) { + return String(value ?? '') + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, ''); +} + +function validateTemplateResources(resources) { + const names = new Set(); + for (const resource of resources || []) { + if ( + !RESOURCE_NAME_PATTERN.test(resource?.name || '') || + resource?.file == null || + resource.file === '' || + names.has(resource.name) + ) { + return false; + } + names.add(resource.name); + } + return true; +} + const RENDER_EMAIL_TEMPLATE_CALL = /fc\.renderEmailTemplate\s*\(\s*(['"])([^'"]+)\1/g; function extractReferences(content) { @@ -36,13 +62,32 @@ const emailTemplateSchema = new Schema( active: { type: Boolean, required: true, default: true }, global: { type: Boolean, required: true, default: false }, parent: { type: Schema.Types.ObjectId, ref: 'emailTemplate', required: false }, - referencedTemplates: [ - { type: Schema.Types.ObjectId, ref: 'emailTemplate', required: false }, - ], + referencedTemplates: [{ type: Schema.Types.ObjectId, ref: 'emailTemplate', required: false }], subject: { type: String, required: false, default: '' }, content: { type: String, required: false, default: '' }, testObject: { type: Schema.Types.ObjectId, refPath: 'objectType', required: false }, attachments: { type: [emailTemplateAttachmentSchema], default: [] }, + resources: { + type: [ + new Schema( + { + name: { + type: String, + required: true, + set: normalizeTemplateResourceName, + match: RESOURCE_NAME_PATTERN, + }, + file: { type: Schema.Types.ObjectId, ref: 'file', required: true }, + }, + { _id: true } + ), + ], + default: [], + validate: { + validator: validateTemplateResources, + message: 'Template resource names must be unique.', + }, + }, }, { timestamps: true } );