mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
185 lines
4.4 KiB
TypeScript
185 lines
4.4 KiB
TypeScript
import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
|
|
import argon2 from 'argon2';
|
|
import Busboy from 'busboy';
|
|
import { Router } from 'express';
|
|
import Joi from 'joi';
|
|
import collectionExists from '../middleware/collection-exists.js';
|
|
import { respond } from '../middleware/respond.js';
|
|
import { ExportService, ImportService } from '../services/import-export.js';
|
|
import { RevisionsService } from '../services/revisions.js';
|
|
import { UtilsService } from '../services/utils.js';
|
|
import asyncHandler from '../utils/async-handler.js';
|
|
import { generateHash } from '../utils/generate-hash.js';
|
|
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
|
|
const router = Router();
|
|
|
|
router.get(
|
|
'/random/string',
|
|
asyncHandler(async (req, res) => {
|
|
const { nanoid } = await import('nanoid');
|
|
|
|
if (req.query && req.query['length'] && Number(req.query['length']) > 500) {
|
|
throw new InvalidQueryError({ reason: `"length" can't be more than 500 characters` });
|
|
}
|
|
|
|
const string = nanoid(req.query?.['length'] ? Number(req.query['length']) : 32);
|
|
|
|
return res.json({ data: string });
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/hash/generate',
|
|
asyncHandler(async (req, res) => {
|
|
if (!req.body?.string) {
|
|
throw new InvalidPayloadError({ reason: `"string" is required` });
|
|
}
|
|
|
|
const hash = await generateHash(req.body.string);
|
|
|
|
return res.json({ data: hash });
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/hash/verify',
|
|
asyncHandler(async (req, res) => {
|
|
if (!req.body?.string) {
|
|
throw new InvalidPayloadError({ reason: `"string" is required` });
|
|
}
|
|
|
|
if (!req.body?.hash) {
|
|
throw new InvalidPayloadError({ reason: `"hash" is required` });
|
|
}
|
|
|
|
const result = await argon2.verify(req.body.hash, req.body.string);
|
|
|
|
return res.json({ data: result });
|
|
}),
|
|
);
|
|
|
|
const SortSchema = Joi.object({
|
|
item: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
|
to: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
|
});
|
|
|
|
router.post(
|
|
'/sort/:collection',
|
|
collectionExists,
|
|
asyncHandler(async (req, res) => {
|
|
const { error } = SortSchema.validate(req.body);
|
|
if (error) throw new InvalidPayloadError({ reason: error.message });
|
|
|
|
const service = new UtilsService({
|
|
accountability: req.accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
await service.sort(req.collection, req.body);
|
|
|
|
return res.status(200).end();
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/revert/:revision',
|
|
asyncHandler(async (req, _res, next) => {
|
|
const service = new RevisionsService({
|
|
accountability: req.accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
await service.revert(req.params['revision']!);
|
|
next();
|
|
}),
|
|
respond,
|
|
);
|
|
|
|
router.post(
|
|
'/import/:collection',
|
|
collectionExists,
|
|
asyncHandler(async (req, res, next) => {
|
|
if (req.is('multipart/form-data') === false) {
|
|
throw new UnsupportedMediaTypeError({ mediaType: req.headers['content-type']!, where: 'Content-Type header' });
|
|
}
|
|
|
|
const service = new ImportService({
|
|
accountability: req.accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
let headers;
|
|
|
|
if (req.headers['content-type']) {
|
|
headers = req.headers;
|
|
} else {
|
|
headers = {
|
|
...req.headers,
|
|
'content-type': 'application/octet-stream',
|
|
};
|
|
}
|
|
|
|
const busboy = Busboy({ headers });
|
|
|
|
busboy.on('file', async (_fieldname, fileStream, { mimeType }) => {
|
|
try {
|
|
await service.import(req.params['collection']!, mimeType, fileStream);
|
|
} catch (err: any) {
|
|
return next(err);
|
|
}
|
|
|
|
return res.status(200).end();
|
|
});
|
|
|
|
busboy.on('error', (err: Error) => next(err));
|
|
|
|
req.pipe(busboy);
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
'/export/:collection',
|
|
collectionExists,
|
|
asyncHandler(async (req, _res, next) => {
|
|
if (!req.body.query) {
|
|
throw new InvalidPayloadError({ reason: `"query" is required` });
|
|
}
|
|
|
|
if (!req.body.format) {
|
|
throw new InvalidPayloadError({ reason: `"format" is required` });
|
|
}
|
|
|
|
const service = new ExportService({
|
|
accountability: req.accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability ?? null);
|
|
|
|
// We're not awaiting this, as it's supposed to run async in the background
|
|
service.exportToFile(req.params['collection']!, sanitizedQuery, req.body.format, {
|
|
file: req.body.file,
|
|
});
|
|
|
|
return next();
|
|
}),
|
|
respond,
|
|
);
|
|
|
|
router.post(
|
|
'/cache/clear',
|
|
asyncHandler(async (req, res) => {
|
|
const service = new UtilsService({
|
|
accountability: req.accountability,
|
|
schema: req.schema,
|
|
});
|
|
|
|
await service.clearCache();
|
|
|
|
res.status(200).end();
|
|
}),
|
|
);
|
|
|
|
export default router;
|