@dlillyatx/scaffold
Version:
309 lines (269 loc) • 10.7 kB
JavaScript
const express = require('express')
const router = express.Router()
const fs = require('fs-extra')
const _ = require('lodash')
const CT = require('/Users/dave/work/ctvault');
// const CT = require('ctvault');
const pluralize = require('pluralize')
const utils = require('./utils')
const locale = require('locale-code')
const pathresolver = require('path')
const logger = require('/Users/dave/work/ctvault/lib/logger');
const jobManager = require('./jobManager');
let routes = {
extensions: [],
microservices: [],
admin_microservices: [],
subscriptions: [],
ui: [],
storefront: [],
mc: [],
scheduled_jobs: []
}
let services = []
let localPath = require('path').dirname(require.main.filename)
router.getServices = () => services
router.getService = key =>
_.ff(router.getServices(), service => service.key === key)
router.getHooks = () => _.flatten(_.concat([], Object.values(routes)))
router.getHook = key => _.ff(router.getHooks(), hook => hook.key === key)
let loadDir = async dir => {
const subscriberManager = require('./subscriptionManager')
logger.info(`\tLoading services subdirectory at ${dir}`)
const jobManager = require('./jobManager')
let service = require(dir)
service.key = _.last(dir.split('/'))
services.push(service)
if (service.asyncInit) {
await service.asyncInit()
}
_.each(Object.keys(service), key => {
if (key === 'asyncInit' || key === 'key' || key === 'model' || key === 'typeDefs' || key === 'resolvers') {
return
}
let objects = Array.isArray(service[key]) ? service[key] : [service[key]]
_.each(objects, obj => {
obj.type = key
obj.service = service.key
routes[key].push(obj)
switch (key) {
case 'extensions':
router.post(obj.path, async (req, res, next) => {
let h = _.get(
obj,
`triggers.${req.body.resource.typeId}.${req.body.action}`
)
if (h) {
let actions = await h(req, res, next)
if (!res.headersSent) {
return res.status(200).json({ actions: _.filter(actions, x => x) })
}
} else {
next({
error: `Handler not found for ${req.body.resource.typeId} / ${req.body.action} under path ${req.path}`,
})
}
})
break
case 'microservices':
case 'admin_microservices':
let paths = Array.isArray(obj.path) ? obj.path : [obj.path]
_.each(paths, path => {
let method = obj.method || 'get'
router[method](
path,
async (req, res, next) => {
req.getHook = router.getHook
try {
let response = await obj.handle(req, res, next)
if (!res.headersSent) {
res.status(200).json(response)
}
} catch (error) {
next(error)
}
}
)
})
break
case 'subscriptions':
subscriberManager.subscribe(obj)
break
case 'ui':
router.use(
obj.path,
express.static(
`${localPath}/${obj.localPath}`
)
)
break
case "storefront":
router.use('/:country/:language/*', (req, res, next) => {
let path = `${localPath}/${obj.localPath}`
if (locale.validateLanguageCode(`${req.params.language}-${req.params.country}`)) {
return res.sendFile(pathresolver.resolve(`${path}/index.html`))
}
else {
next()
}
})
router.use('/', async (req, res, next) => {
let path = `${localPath}/${obj.localPath}`
if (req.path === '/') {
res.redirect('/US/en/')
}
else {
let l = pathresolver.resolve(`${path}${req.path}`)
if (fs.existsSync(l)) {
return res.sendFile(l)
}
else {
next()
}
}
})
break;
case 'mc':
router.use(`/`, express.static(`${localPath}/${obj.localPath}`))
router.use(`/:projectKey/${obj.name}`, (req, res) => {
let indexPath = pathresolver.resolve(`${localPath}/${obj.localPath}/index.html`)
console.log(indexPath)
res.sendFile(indexPath)
})
break
case 'scheduled_jobs':
jobManager.addJob(obj)
break
default:
logger.warn(`Unknown service loader key [ ${key} ]`)
break
}
})
})
}
let compareModel = opts => ct => async model => {
let dataModel = await ct[opts.typeKey].ensure(model)
let newDataModel = model
let dmKeys = _.map(dataModel[pluralize(opts.attributeKey)], 'name')
let ndmKeys = _.map(newDataModel[pluralize(opts.attributeKey)], 'name')
let addActions = _.map(_.difference(ndmKeys, dmKeys), key => {
let action = {
action: `add${opts.actionKey}`,
}
action[opts.attributeKey] = _.find(
newDataModel[pluralize(opts.attributeKey)],
fd => fd.name === key
)
return action
})
let removeActions = _.map(_.difference(dmKeys, ndmKeys), key => {
let action = {
action: `remove${opts.actionKey}`,
}
action[opts.fieldKey] = key
return action
})
let actions = _.concat(addActions, removeActions)
if (actions.length > 0) {
await ct[opts.typeKey].update(dataModel, actions)
if (addActions.length > 0) {
logger.info(
`Data model [ ${
model.key
} ] updated: added fields [ ${_.difference(
ndmKeys,
dmKeys
)} ] for project [ ${ct.projectKey} ]`
)
}
if (removeActions.length > 0) {
logger.info(
`Data model [ ${
model.key
} ] updated: removed fields [ ${_.difference(
dmKeys,
ndmKeys
)} ] for project [ ${ct.projectKey} ]`
)
}
} else {
logger.info(
`Data model [ ${model.key} ] up to date for project [ ${ct.projectKey} ]`
)
}
}
let compareDataModel = compareModel({
typeKey: 'types',
attributeKey: 'fieldDefinition',
actionKey: 'FieldDefinition',
fieldKey: 'fieldName',
})
let compareProductDataModel = compareModel({
typeKey: 'productTypes',
attributeKey: 'attribute',
actionKey: 'AttributeDefinition',
fieldKey: 'name',
})
let compareDataModels = async (ct, service) =>
await Promise.all(
Object.values(service.model.types || {}).map(await compareDataModel(ct))
)
let compareProductDataModels = async (ct, service) =>
await Promise.all(
Object.values(service.model.productTypes || {}).map(
await compareProductDataModel(ct)
)
)
module.exports = async (serviceDir, app) => {
logger.info(`Loading services directory at ${serviceDir}`)
if (fs.existsSync(serviceDir)) {
await Promise.all(utils.file.getSubdirectories(serviceDir).map(loadDir))
let graphqlServices = _.filter(services, 'typeDefs')
if (graphqlServices.length > 0) {
const { ApolloServer } = require('apollo-server-express');
const { buildFederatedSchema } = require('@apollo/federation');
const server = new ApolloServer({
schema: buildFederatedSchema(_.map(graphqlServices, s => ({
typeDefs: s.typeDefs,
resolvers: s.resolvers
}))),
context: ({ req }) => ({ ct: req.ct })
// dataSources: () => ({ CompanyApi: new Company({ url: companyMsUrl }) })
});
server.applyMiddleware({ app })
}
}
// load the common services directory
await loadDir('./services')
let cts = await CT.getClients()
await Promise.all(
cts.map(async ctinfo => {
let ct = await CT.getClient(ctinfo.projectKey)
if (!ct.expired) {
let scheduledJobs = await ct.customObjects.get({
container: "config",
key: "scheduled-jobs"
}) || []
_.each(scheduledJobs, jobName => {
jobManager.schedule(jobName, ct)
})
let extensions = await ct.extensions.all()
await Promise.all(
extensions.map(async ext => {
let hook = router.getHook(ext.key)
if (hook && hook.service) {
let service = router.getService(hook.service)
await compareDataModels(ct, service)
await compareProductDataModels(ct, service)
}
})
)
}
})
)
// load the /api route
router.get('/api', (req, res) => res.json(router.getHooks()))
router.get('/foo', (req, res) => res.json({ bar: 'baz' }))
router.use('/docs', express.static(`${__dirname}/../../docs`))
return router
}