Files
CLProject/src/routes/templateRoutes.js
T
2026-04-22 22:39:26 +02:00

251 lines
6.8 KiB
JavaScript

import { Router } from 'express';
import {
getActiveTemplate,
getAllActiveTemplates,
getTemplateVersion,
listTemplates,
listTemplateVersions,
publishTemplateVersion
} from '../services/templateService.js';
import { logAuditEvent } from '../services/auditService.js';
import { asyncHandler } from '../utils/asyncHandler.js';
import { validateParam, validateNumericParam } from '../middleware/validateParams.js';
import { templateCache } from '../services/cacheService.js';
const router = Router();
/**
* @openapi
* /api/v1/templates:
* get:
* summary: List templates
* tags:
* - Templates
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: include
* in: query
* required: false
* schema:
* type: string
* enum: [definitions]
* responses:
* 200:
* description: List of templates
* /api/v1/templates/{templateCode}:
* get:
* summary: Get active template
* tags:
* - Templates
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: templateCode
* in: path
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Active template details
* 404:
* description: Template not found
* /api/v1/templates/{templateCode}/versions:
* get:
* summary: List template versions
* tags:
* - Templates
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: templateCode
* in: path
* required: true
* schema:
* type: string
* responses:
* 200:
* description: List of template versions
* /api/v1/templates/{templateCode}/versions/{versionNumber}:
* get:
* summary: Get specific template version
* tags:
* - Templates
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: templateCode
* in: path
* required: true
* schema:
* type: string
* - name: versionNumber
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Template version details
* 404:
* description: Template version not found
* /api/v1/templates/{templateCode}/versions/{versionNumber}/publish:
* put:
* summary: Publish template version
* tags:
* - Templates
* security:
* - bearerAuth: []
* - cookieAuth: []
* parameters:
* - name: templateCode
* in: path
* required: true
* schema:
* type: string
* - name: versionNumber
* in: path
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: Template version published
* 404:
* description: Template version not found
*/
router.get(
'/',
asyncHandler(async (req, res) => {
/*
* When ?include=definitions is set the response embeds the full JSON
* definition for every active template. This eliminates the N+1 round-trip
* the old client performed (list → fetch each) and makes initial sync a
* single request. Without the flag the response stays lightweight.
*/
const includeDefinitions = req.query.include === 'definitions';
const cacheKey = `templates-list-${includeDefinitions}`;
const cached = templateCache.get(cacheKey);
if (cached) {
return res.json(cached);
}
if (includeDefinitions) {
const templates = await getAllActiveTemplates();
const payload = { items: templates };
templateCache.set(cacheKey, payload);
return res.json(payload);
}
const templates = await listTemplates();
const payload = { items: templates };
templateCache.set(cacheKey, payload);
return res.json(payload);
})
);
router.get(
'/:templateCode',
validateParam('templateCode'),
asyncHandler(async (req, res) => {
/*
* New reports always use the latest active template, so the primary route is
* optimized for that case. Older versions remain accessible through the
* versioned route so existing drafts can stay bound to the original schema.
*/
const cacheKey = `template-active-${req.params.templateCode}`;
const cached = templateCache.get(cacheKey);
if (cached) {
return res.json(cached);
}
const template = await getActiveTemplate(req.params.templateCode);
if (!template) {
return res.status(404).json({ message: 'Template not found.' });
}
templateCache.set(cacheKey, template);
return res.json(template);
})
);
router.get(
'/:templateCode/versions',
validateParam('templateCode'),
asyncHandler(async (req, res) => {
/*
* Version listing lets the admin workspace display a template's publication
* history and choose which version to activate or review.
*/
const versions = await listTemplateVersions(req.params.templateCode);
return res.json({ items: versions });
})
);
router.get(
'/:templateCode/versions/:versionNumber',
validateParam('templateCode'),
validateNumericParam('versionNumber'),
asyncHandler(async (req, res) => {
/*
* Version-specific access is what allows the frontend to reopen old drafts
* safely even after templates evolve. Without this route, cached reports
* would eventually drift away from the structure they were created against.
*/
const template = await getTemplateVersion(
req.params.templateCode,
req.params.versionNumber
);
if (!template) {
return res.status(404).json({ message: 'Template version not found.' });
}
return res.json(template);
})
);
router.put(
'/:templateCode/versions/:versionNumber/publish',
validateParam('templateCode'),
validateNumericParam('versionNumber'),
asyncHandler(async (req, res) => {
/*
* Publishing a version marks it active and retires the previously active
* version for the same template. This lets the admin promote a draft version
* to production. Existing reports keep their bound version unchanged.
*/
const result = await publishTemplateVersion(
req.params.templateCode,
Number(req.params.versionNumber)
);
if (!result) {
return res.status(404).json({ message: 'Template version not found.' });
}
templateCache.clear();
await logAuditEvent({
entityType: 'template_version',
entityCode: `${req.params.templateCode}::v${req.params.versionNumber}`,
action: 'publish',
newValue: { templateCode: req.params.templateCode, version: Number(req.params.versionNumber) }
});
return res.json(result);
})
);
export default router;