@fontoxml/fontoxml-development-tools
Version:
Development tools for Fonto.
187 lines (171 loc) • 5.14 kB
JavaScript
import asyncRouteWithLockCleanupHandler from '../asyncRouteWithLockCleanupHandler.js';
/**
* This endpoint is a copy of configureDocumentGetPostRouteHandler.js.
*
* Please keep both endpoints in sync.
*
* But note the differences:
* - For Output there is never a editSessionToken in the request body or query parameters.
* - Output has no lock capabilities, this property is removed in the Document and DocumentResult types.
* - Output has no documentContext, this property is removed in the Document type.
* - Document metadata is optional.
* - Output can receive an optional Fonto-Correlation-Id.
*/
/** @typedef {import('../../src/getAppConfig.js').DevCmsConfig} DevCmsConfig */
/** @typedef {import('./stores/DevelopmentCms')} DevelopmentCms */
/**
* @typedef DocumentToLoad
*
* @property {string} documentId
* @property {boolean} [includeAdditionalDocuments]
*/
/**
* @typedef Document
*
* @property {string} documentId
* @property {string} [revisionId]
* @property {string} content
* @property {{ [key: string]: unknown }} [metadata]
*/
/**
* @typedef DocumentErrorResult
*
* @property {string} documentId
* @property {number} status
*/
/**
* @typedef DocumentResult
*
* @property {string} documentId
* @property {200} status
* @property {Document} body
*/
/**
* @param {string} documentId
* @param {string} revisionId
* @param {string} content
*
* @return {DocumentResult}
*/
function createDocumentResult(documentId, revisionId, content) {
return {
documentId,
status: 200,
body: {
documentId,
revisionId,
content,
metadata: {},
},
};
}
/**
* @param {DevCmsConfig} config
*/
function configureOutputDocumentGetPostRouteHandler(config) {
/**
* @param {DocumentToLoad} documentToLoad
* @param {DevelopmentCms} cms
* @param {(filePath: string) => Promise<DevCmsFileLock>} acquireLock
*
* @return {Promise<DocumentResult | DocumentErrorResult>}
*/
async function loadDocument(
documentToLoad,
editSessionToken,
cms,
acquireLock,
) {
const documentId = documentToLoad.documentId;
let fileLock;
try {
fileLock = await acquireLock(documentId);
const contentAndLatestRevisionId = await cms.getFileAndLatestRevisionId(
documentId,
editSessionToken,
fileLock,
);
if (!contentAndLatestRevisionId) {
return {
documentId,
status: 404,
};
}
return createDocumentResult(
documentId,
contentAndLatestRevisionId.revisionId,
contentAndLatestRevisionId.content,
);
} catch (_error) {
return {
documentId,
status: 500,
};
} finally {
fileLock?.release();
}
}
return asyncRouteWithLockCleanupHandler(async (acquireLock, req, res) => {
const correlationIdRepository = req.repositories.correlationId;
// Because these requests do not originate from the editor, but from another server, we
// have no edit session token. In some cases, however, we have an correlationId which we
// may resolve to the editSessionToken used by the corresponding call to the proxy.
const editSessionToken =
correlationIdRepository.getEditSessionTokenForRequest(req);
// Collect all explicitly requested documents to load
/** @type {DocumentToLoad[]} */
const documentsToLoad = [];
const documentIds = new Set();
for (const documentToLoad of req.body.documents) {
if (
typeof config.documentLoadBatchResultsLimit === 'number' &&
documentsToLoad.length >= config.documentLoadBatchResultsLimit
) {
break;
}
if (documentIds.has(documentToLoad.documentId)) {
continue;
}
documentsToLoad.push(documentToLoad);
documentIds.add(documentToLoad.documentId);
}
// Collect all additional documents.
const documentIdsWithAdditionalDocuments = new Set();
const addAdditionalDocumentsForDocument = (documentId) => {
if (!config.additionalDocuments?.[documentId]) {
return;
}
for (const additionalDocumentId of config.additionalDocuments[
documentId
]) {
if (!documentIds.has(additionalDocumentId)) {
// Add the document if it is not yet added.
documentsToLoad.push({ documentId: additionalDocumentId });
documentIds.add(additionalDocumentId);
}
if (!documentIdsWithAdditionalDocuments.has(additionalDocumentId)) {
// Recurse the additional document, if not already done so.
addAdditionalDocumentsForDocument(additionalDocumentId);
documentIdsWithAdditionalDocuments.add(additionalDocumentId);
}
}
};
for (const documentToLoad of documentsToLoad) {
if (!documentToLoad.includeAdditionalDocuments) {
continue;
}
addAdditionalDocumentsForDocument(documentToLoad.documentId);
}
/** @type {(DocumentResult | DocumentErrorResult)[]} */
const results = await Promise.all(
documentsToLoad.map((documentToLoad) =>
loadDocument(documentToLoad, editSessionToken, req.cms, acquireLock),
),
);
res
.status(200)
.set('content-type', 'application/json; charset=utf-8')
.json({ results });
});
}
export default configureOutputDocumentGetPostRouteHandler;