@fontoxml/fontoxml-development-tools
Version:
Development tools for Fonto.
202 lines (185 loc) • 5.11 kB
JavaScript
import asyncRouteWithLockCleanupHandler from '../asyncRouteWithLockCleanupHandler.js';
/** @typedef {import('../../src/getAppConfig.js').DevCmsConfig} DevCmsConfig */
/** @typedef {import('./stores/DevelopmentCms')} DevelopmentCms */
/**
* @typedef DocumentToLoad
*
* @property {string} documentId
* @property {boolean} [includeAdditionalDocuments]
*/
/**
* @typedef Lock
*
* @property {boolean} isLockAcquired
* @property {boolean} isLockAvailable
*/
/**
* @typedef DocumentContext
*
* @property {{ [key: string]: unknown }} [documentMetadata]
* @property {boolean} [isLockAcquired]
*/
/**
* @typedef Document
*
* @property {string} documentId
* @property {string} revisionId
* @property {string} content
* @property {Lock} lock
* @property {{ [key: string]: unknown }} metadata
* @property {DocumentContext} documentContext
*/
/**
* @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
* @param {Lock} lock
*
* @return {DocumentResult}
*/
function createDocumentResult(documentId, revisionId, content, lock) {
return {
documentId,
status: 200,
body: {
documentId,
revisionId,
content,
lock: {
isLockAcquired: lock.isLockAcquired,
isLockAvailable: lock.isLockAvailable,
reason: !lock.isLockAvailable ? lock.lockReason : undefined,
},
metadata: {},
documentContext: {
documentMetadata: {},
isLockAcquired: lock.isLockAcquired,
},
},
};
}
/**
* @param {DevCmsConfig} config
*/
function configureDocumentGetPostRouteHandler(config) {
/**
* @param {DocumentToLoad} documentToLoad
* @param {string} editSessionToken
* @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,
};
}
/** @type {Lock} */
const documentLoadLock = {
...config.documentLoadLock,
...config.documentLoadLockOverrides[documentId],
};
return createDocumentResult(
documentId,
contentAndLatestRevisionId.revisionId,
contentAndLatestRevisionId.content,
documentLoadLock,
);
} catch (_error) {
return {
documentId,
status: 500,
};
} finally {
fileLock?.release();
}
}
return asyncRouteWithLockCleanupHandler(async (acquireLock, req, res) => {
const editSessionToken = req.body?.context?.editSessionToken;
// 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 configureDocumentGetPostRouteHandler;