@getanthill/datastore
Version:
Event-Sourced Datastore
153 lines (121 loc) • 3.73 kB
text/typescript
import type { ResponseError, Services } from '../typings';
import express, { ErrorRequestHandler, RequestHandler } from 'express';
import bodyParser from 'body-parser';
import {
authenticate,
OpenAPIMiddleware,
errorHandler,
getTokensByRole,
authz,
obligations,
} from './middleware';
import { build as buildSpec } from './spec';
import modelsRoutes from './models';
import adminRoutes from './admin';
import streamRoutes from './stream';
import aggregateRoutes from './aggregate';
async function initOpenApi(services: Services) {
const cacheEntry = await services.models.getFromCache(
'open_api_specification',
);
const oas = cacheEntry
? JSON.parse(cacheEntry.oas)
: buildSpec(services.models);
if (!cacheEntry) {
services.models.setToCache('open_api_specification', {
oas: JSON.stringify(oas),
});
}
services.telemetry.logger.debug(
'[API] Building Open API 3.0 Specification...',
);
return new OpenAPIMiddleware(
{
secret: services.config.security.apiSecret,
specification: oas,
warnOnInvalidSpecificationOnly:
services.config.features.api.openAPI.warnOnInvalidSpecificationOnly,
telemetry: services.telemetry,
services,
},
services.config.mode === 'development' ||
services.config.features.api.updateSpecOnModelsChange === true
? async () => {
const _oas = buildSpec(services.models);
services.models.setToCache('open_api_specification', {
oas: JSON.stringify(_oas),
});
return _oas;
}
: null,
);
}
async function routes(services: Services) {
const app = express.Router({ mergeParams: true });
let openApi;
app
.use(bodyParser.urlencoded({ extended: false }))
.use(bodyParser.json({ limit: services.config.features.api.json.limit }));
if (services.config.authz.isEnabled === true) {
app.use(authz(services));
}
if (services.config.features.api.openAPI.isEnabled === true) {
openApi = await initOpenApi(services);
app.use(openApi.registerInputValidation());
}
app.use(obligations(services));
if (services.config.features.api.aggregate === true) {
services.telemetry.logger.info('[Feature] Aggregation API enabled');
app.use(
'/aggregate',
(req, res, next) => {
req.setTimeout(services.config.features.api.timeout.aggregate);
next();
},
authenticate(getTokensByRole(services.config.security.tokens, 'read')),
aggregateRoutes(services),
);
}
app
.use('/admin', adminRoutes(services, openApi))
.use(
'/stream',
authenticate(getTokensByRole(services.config.security.tokens, 'read')),
streamRoutes(services),
)
.use(
'/:model',
(req, res, next) => {
if (req.params.model.startsWith('internal_')) {
const err: ResponseError = new Error('Protected model');
err.status = 403;
return next(err);
}
req.setTimeout(services.config.features.api.timeout.models);
next();
},
modelsRoutes(services),
);
app.use(obligations(services));
if (openApi && services.config.features.api.openAPI.isEnabled === true) {
app.use(openApi.registerOutputValidation());
}
/* istanbul ignore next */
const jsonMiddleware = (
_req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
// @ts-ignore
if (res.body) {
// @ts-ignore
return res.json(res.body);
}
/* istanbul ignore next */
next();
};
app.use(jsonMiddleware as RequestHandler);
app.use(errorHandler(services) as ErrorRequestHandler);
return app;
}
export default routes;