overleaf/services/web/app/src/infrastructure/Modules.js
Jakob Ackermann 947eda54e7 Merge pull request #4312 from overleaf/jpa-module-import-sequence-from-config
[Modules] source list of modules and their import sequence from config

GitOrigin-RevId: 73a552e03f8e9ec6ae8fce3a664625095dc095fc
2021-07-14 12:41:02 +00:00

158 lines
3.6 KiB
JavaScript

const fs = require('fs')
const Path = require('path')
const pug = require('pug')
const async = require('async')
const { promisify } = require('util')
const Settings = require('@overleaf/settings')
const MODULE_BASE_PATH = Path.resolve(__dirname + '/../../../modules')
const _modules = []
const _hooks = {}
let _viewIncludes = {}
function loadModules() {
const settingsCheckModule = Path.join(
MODULE_BASE_PATH,
'settings-check',
'index.js'
)
if (fs.existsSync(settingsCheckModule)) {
require(settingsCheckModule)
}
for (const moduleName of Settings.moduleImportSequence) {
const loadedModule = require(Path.join(
MODULE_BASE_PATH,
moduleName,
'index.js'
))
loadedModule.name = moduleName
_modules.push(loadedModule)
}
attachHooks()
}
function applyRouter(webRouter, privateApiRouter, publicApiRouter) {
for (const module of _modules) {
if (module.router && module.router.apply) {
module.router.apply(webRouter, privateApiRouter, publicApiRouter)
}
}
}
function applyNonCsrfRouter(webRouter, privateApiRouter, publicApiRouter) {
for (const module of _modules) {
if (module.nonCsrfRouter != null) {
module.nonCsrfRouter.apply(webRouter, privateApiRouter, publicApiRouter)
}
if (module.router && module.router.applyNonCsrfRouter) {
module.router.applyNonCsrfRouter(
webRouter,
privateApiRouter,
publicApiRouter
)
}
}
}
function loadViewIncludes(app) {
_viewIncludes = {}
for (const module of _modules) {
const object = module.viewIncludes || {}
for (const view in object) {
const partial = object[view]
if (!_viewIncludes[view]) {
_viewIncludes[view] = []
}
const filePath = Path.join(
MODULE_BASE_PATH,
module.name,
'app/views',
partial + '.pug'
)
_viewIncludes[view].push(
pug.compileFile(filePath, {
doctype: 'html',
compileDebug: Settings.debugPugTemplates,
})
)
}
}
}
function moduleIncludes(view, locals) {
const compiledPartials = _viewIncludes[view] || []
let html = ''
for (const compiledPartial of compiledPartials) {
html += compiledPartial(locals)
}
return html
}
function moduleIncludesAvailable(view) {
return (_viewIncludes[view] || []).length > 0
}
function linkedFileAgentsIncludes() {
const agents = {}
for (const module of _modules) {
for (const name in module.linkedFileAgents) {
const agentFunction = module.linkedFileAgents[name]
agents[name] = agentFunction()
}
}
return agents
}
function attachHooks() {
for (var module of _modules) {
if (module.hooks != null) {
for (const hook in module.hooks) {
const method = module.hooks[hook]
attachHook(hook, method)
}
}
}
}
function attachHook(name, method) {
if (_hooks[name] == null) {
_hooks[name] = []
}
_hooks[name].push(method)
}
function fireHook(name, ...rest) {
const adjustedLength = Math.max(rest.length, 1)
const args = rest.slice(0, adjustedLength - 1)
const callback = rest[adjustedLength - 1]
const methods = _hooks[name] || []
const callMethods = methods.map(method => cb => method(...args, cb))
async.series(callMethods, function (error, results) {
if (error) {
return callback(error)
}
callback(null, results)
})
}
module.exports = {
applyNonCsrfRouter,
applyRouter,
linkedFileAgentsIncludes,
loadViewIncludes,
moduleIncludes,
moduleIncludesAvailable,
hooks: {
attach: attachHook,
fire: fireHook,
},
promises: {
hooks: {
fire: promisify(fireHook),
},
},
}
loadModules()