Enhance document template schema and service with referenced templates functionality
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good

This commit introduces a new field, `referencedTemplates`, to the document template schema, allowing for the association of other document templates. It also adds utility functions for extracting references from template content and converting filter values to ObjectIds. The document template service is updated to recalculate and populate referenced templates when editing a document template. Additionally, route handlers are modified to support the new referenced templates in their population logic, improving the management of document templates.
This commit is contained in:
Tom Butcher 2026-08-22 16:39:10 +01:00
parent 827f3b7c8c
commit b0943f3fa0
3 changed files with 105 additions and 0 deletions

View File

@ -2,6 +2,53 @@ import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const RENDER_DOCUMENT_TEMPLATE_CALL =
/fc\.renderDocumentTemplate\s*\(\s*(['"])([^'"]+)\1/g;
function extractRenderDocumentTemplateReferences(content) {
if (content == null || typeof content !== 'string' || content === '') {
return [];
}
const references = [];
const seen = new Set();
const regex = new RegExp(RENDER_DOCUMENT_TEMPLATE_CALL.source, 'g');
let match;
while ((match = regex.exec(content)) !== null) {
const reference = match[2]?.trim();
if (!reference || seen.has(reference)) {
continue;
}
seen.add(reference);
references.push(reference);
}
return references;
}
function objectIdsFromFilterValue(value) {
if (value == null) {
return [];
}
if (value instanceof mongoose.Types.ObjectId) {
return [value];
}
if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) {
return [new mongoose.Types.ObjectId(value)];
}
if (Array.isArray(value)) {
return value.flatMap(objectIdsFromFilterValue);
}
if (typeof value === 'object') {
if (Array.isArray(value.$in)) {
return objectIdsFromFilterValue(value.$in);
}
if (value._id != null) {
return objectIdsFromFilterValue(value._id);
}
}
return [];
}
const documentTemplateSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
@ -39,6 +86,13 @@ const documentTemplateSchema = new Schema(
required: false,
},
],
referencedTemplates: [
{
type: Schema.Types.ObjectId,
ref: 'documentTemplate',
required: false,
},
],
content: {
type: String,
required: false,
@ -63,4 +117,50 @@ documentTemplateSchema.virtual('id').get(function () {
// Configure JSON serialization to include virtuals
documentTemplateSchema.set('toJSON', { virtuals: true });
documentTemplateSchema.statics.recalculate = async function (documentTemplate, user) {
const documentTemplateId = documentTemplate?._id || documentTemplate;
if (!documentTemplateId) {
return;
}
const stillExists = await this.exists({ _id: documentTemplateId });
if (!stillExists) {
return;
}
const { getFilter } = await import('../../../utils.js');
const references = extractRenderDocumentTemplateReferences(documentTemplate?.content);
const referencedTemplateIds = [];
const seenIds = new Set();
for (const reference of references) {
const filter = await getFilter({ parent: reference }, ['parent'], true, this);
const ids = objectIdsFromFilterValue(filter.parent);
for (const id of ids) {
const idString = String(id);
if (!idString || seenIds.has(idString)) {
continue;
}
seenIds.add(idString);
referencedTemplateIds.push(id);
}
}
if (documentTemplate && typeof documentTemplate === 'object' && !documentTemplate._bsontype) {
documentTemplate.referencedTemplates = referencedTemplateIds.map((id) => ({
_id: String(id),
}));
}
const { editObject } = await import('../../database.js');
await editObject({
model: this,
id: documentTemplateId,
updateData: { referencedTemplates: referencedTemplateIds },
user,
populate: [{ path: 'referencedTemplates', strictPopulate: false }],
recalculate: false,
});
};
export const documentTemplateModel = mongoose.model('documentTemplate', documentTemplateSchema);

View File

@ -14,6 +14,8 @@ const listAllowedFilters = [
'parent._id',
'documentSize',
'documentSize._id',
'referencedTemplates',
'referencedTemplates._id',
'objectType',
'createdAt',
'updatedAt',

View File

@ -71,6 +71,7 @@ export const listDocumentTemplatesRouteHandler = async (
{ path: 'documentSize' },
{ path: 'parent' },
{ path: 'documentPrinters', strictPopulate: false },
{ path: 'referencedTemplates', strictPopulate: false },
],
});
@ -136,6 +137,7 @@ export const getDocumentTemplateRouteHandler = async (req, res) => {
{ path: 'documentSize' },
{ path: 'parent', strictPopulate: false },
{ path: 'documentPrinters', strictPopulate: false },
{ path: 'referencedTemplates', strictPopulate: false },
{ path: 'testObject', strictPopulate: false },
],
});
@ -186,6 +188,7 @@ export const editDocumentTemplateRouteHandler = async (req, res) => {
{ path: 'documentSize' },
{ path: 'parent', strictPopulate: false },
{ path: 'documentPrinters', strictPopulate: false },
{ path: 'referencedTemplates', strictPopulate: false },
{ path: 'testObject', strictPopulate: false },
],
});