@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
99 lines (98 loc) • 4.13 kB
JavaScript
import { InvalidPayloadError, UnsupportedMediaTypeError } from '@directus/errors';
import { parseJSON } from '@directus/utils';
import Busboy from 'busboy';
import express from 'express';
import { load as loadYaml } from 'js-yaml';
import { useLogger } from '../logger/index.js';
import { respond } from '../middleware/respond.js';
import { SchemaService } from '../services/schema.js';
import asyncHandler from '../utils/async-handler.js';
import { getVersionedHash } from '../utils/get-versioned-hash.js';
const router = express.Router();
router.get('/snapshot', asyncHandler(async (req, res, next) => {
const service = new SchemaService({ accountability: req.accountability });
const currentSnapshot = await service.snapshot();
res.locals['payload'] = { data: currentSnapshot };
return next();
}), respond);
const schemaMultipartHandler = (req, res, next) => {
if (req.is('application/json')) {
if (Object.keys(req.body).length === 0) {
throw new InvalidPayloadError({ reason: `No data was included in the body` });
}
res.locals['upload'] = req.body;
return next();
}
if (!req.is('multipart/form-data')) {
throw new UnsupportedMediaTypeError({ mediaType: req.headers['content-type'], where: 'Content-Type header' });
}
const headers = req.headers['content-type']
? req.headers
: {
...req.headers,
'content-type': 'application/octet-stream',
};
const busboy = Busboy({ headers });
let isFileIncluded = false;
let upload = null;
busboy.on('file', async (_, fileStream, { mimeType }) => {
const logger = useLogger();
if (isFileIncluded)
return next(new InvalidPayloadError({ reason: `More than one file was included in the body` }));
isFileIncluded = true;
const { readableStreamToString } = await import('@directus/utils/node');
try {
const uploadedString = await readableStreamToString(fileStream);
if (mimeType === 'application/json') {
try {
upload = parseJSON(uploadedString);
}
catch (err) {
logger.warn(err);
throw new InvalidPayloadError({ reason: 'The provided JSON is invalid' });
}
}
else {
try {
upload = await loadYaml(uploadedString);
}
catch (err) {
logger.warn(err);
throw new InvalidPayloadError({ reason: 'The provided YAML is invalid' });
}
}
if (!upload) {
throw new InvalidPayloadError({ reason: `No file was included in the body` });
}
res.locals['upload'] = upload;
return next();
}
catch (error) {
busboy.emit('error', error);
}
});
busboy.on('error', (error) => next(error));
busboy.on('close', () => {
if (!isFileIncluded)
return next(new InvalidPayloadError({ reason: `No file was included in the body` }));
});
req.pipe(busboy);
};
router.post('/diff', asyncHandler(schemaMultipartHandler), asyncHandler(async (req, res, next) => {
const service = new SchemaService({ accountability: req.accountability });
const snapshot = res.locals['upload'];
const currentSnapshot = await service.snapshot();
const snapshotDiff = await service.diff(snapshot, { currentSnapshot, force: 'force' in req.query });
if (!snapshotDiff)
return next();
const currentSnapshotHash = getVersionedHash(currentSnapshot);
res.locals['payload'] = { data: { hash: currentSnapshotHash, diff: snapshotDiff } };
return next();
}), respond);
router.post('/apply', asyncHandler(schemaMultipartHandler), asyncHandler(async (req, res, next) => {
const service = new SchemaService({ accountability: req.accountability });
const diff = res.locals['upload'];
await service.apply(diff);
return next();
}), respond);
export default router;