@getanthill/datastore
Version:
Event-Sourced Datastore
236 lines (190 loc) • 5.66 kB
text/typescript
import type { NextFunction, Request, Response } from 'express';
import type { ModelConfig, Services } from '../../typings';
import type { OpenAPIMiddleware } from '../middleware';
export async function updateApiDefinition(
services: Services,
openApi?: OpenAPIMiddleware,
) {
const { config, models } = services;
if (
config.mode === 'development' ||
config.features.api.updateSpecOnModelsChange === true
) {
await models.reset().reload(true);
await openApi?.update();
}
}
export function getModels(services: Services) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
services.metrics.incrementApiAdminGetModels();
const response: { [key: string]: ModelConfig } = {};
const model = req.query.model;
for (const [modelName, Model] of services.models.MODELS.entries()) {
if (services.models.isInternalModel(modelName) === true) {
continue;
}
if (model && modelName !== model) {
continue;
}
response[modelName] = Model.getModelConfig();
}
res.json(response);
} catch (err: any) {
next(err);
}
};
}
export function getGraph(services: Services) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
services.metrics.incrementApiAdminGetGraph();
const graph = services.models.getGraph(req.query);
res.json(graph);
} catch (err: any) {
next(err);
}
};
}
export function getSchema(services: Services) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
services.metrics.incrementApiAdminGetSchema();
const Model = services.models.getModel(req.params.model);
/**
* Adding the correlation field to the response headers
* @todo need to apply this to every request and in the API documentation
*/
res.set({
'correlation-field': Model.getModelConfig().correlation_field,
});
res.json(Model.getOriginalSchema());
} catch (err: any) {
if (err.message === 'Invalid Model') {
err.status = 400;
return next(err);
}
next(err);
}
};
}
export function create(services: Services, openApi?: OpenAPIMiddleware) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
const modelConfig = req.body;
services.metrics.incrementApiAdminCreateModel({
model: modelConfig.name,
});
const internalModel = await services.models.createModel(modelConfig);
await updateApiDefinition(services, openApi);
return res.json(internalModel.state);
} catch (err: any) {
if (err.message === 'Event schema validation error') {
err.status = 400;
return next(err);
}
if (err.message === 'Model already exists') {
err.status = 409;
return next(err);
}
next(err);
}
};
}
export function update(services: Services, openApi?: OpenAPIMiddleware) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
const modelConfig = req.body;
services.metrics.incrementApiAdminUpdateModel({
model: modelConfig.name,
});
const updatedModel = await services.models.updateModel(
req.params.model,
modelConfig,
);
await updateApiDefinition(services, openApi);
return res.json(updatedModel.state);
} catch (err: any) {
if (err.message === 'Invalid Model') {
err.status = 400;
return next(err);
}
if (err.message === 'Event schema validation error') {
err.status = 400;
return next(err);
}
next(err);
}
};
}
export function createModelIndexes(services: Services) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
const modelConfig =
'indexes' in req.body
? req.body
: services.models.getModel(req.params.model).getModelConfig();
services.metrics.incrementApiAdminUpdateModelIndexes({
model: req.params.model,
});
const modelIndexes = await services.models.createModelIndexes({
name: req.params.model,
...modelConfig,
});
res.json(modelIndexes);
} catch (err: any) {
if (err.message === 'Invalid Model') {
err.status = 400;
return next(err);
}
next(err);
}
};
}
export function rotateEncryptionKeys(services: Services) {
return async (req: Request, res: Response, next: NextFunction) => {
// @ts-ignore
if (res.body) {
return next();
}
try {
services.metrics.incrementApiAdminRotateEncryptionKeys();
res.status(202).end();
const onlyModels = JSON.parse((req.query?.q as string) ?? '{}').models as
| string[]
| undefined;
services.telemetry.logger.info('[admin] Encryption key rotation started');
await services.models.rotateEncryptionKey(onlyModels);
services.telemetry.logger.info('[admin] Encryption key rotation ended');
} catch (err: any) {
services.telemetry.logger.error(
'[admin] Encryption key rotation failed',
{ err },
);
}
};
}