Files
directus/api/src/controllers/utils.ts
Rijk van Zanten 86dd593f91 Use node16 module-resolution (#16696)
* Use node16 module-resolution

* Lets not mess with apply-query rn

* I love CJS. Death to CJS.

* Use CJS require in hook import

* Fix type export in schema

* Clean up defaults usage

* Use require instead of import for migrations

* Use a vitest compatible dynamic import

* Uno mas

* Cleanup type export
2022-12-02 15:53:15 -05:00

181 lines
4.1 KiB
TypeScript

import argon2 from 'argon2';
import { Router } from 'express';
import Joi from 'joi';
import {
ForbiddenException,
InvalidPayloadException,
InvalidQueryException,
UnsupportedMediaTypeException,
} from '../exceptions';
import collectionExists from '../middleware/collection-exists';
import { respond } from '../middleware/respond';
import { RevisionsService, UtilsService, ImportService, ExportService } from '../services';
import asyncHandler from '../utils/async-handler';
import Busboy from 'busboy';
import { flushCaches } from '../cache';
import { generateHash } from '../utils/generate-hash';
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 InvalidQueryException(`"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 InvalidPayloadException(`"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 InvalidPayloadException(`"string" is required`);
}
if (!req.body?.hash) {
throw new InvalidPayloadException(`"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 InvalidPayloadException(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 UnsupportedMediaTypeException(`Unsupported 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 InvalidPayloadException(`"query" is required.`);
}
if (!req.body.format) {
throw new InvalidPayloadException(`"format" is required.`);
}
const service = new ExportService({
accountability: req.accountability,
schema: req.schema,
});
// We're not awaiting this, as it's supposed to run async in the background
service.exportToFile(req.params.collection, req.body.query, req.body.format, {
file: req.body.file,
});
return next();
}),
respond
);
router.post(
'/cache/clear',
asyncHandler(async (req, res) => {
if (req.accountability?.admin !== true) {
throw new ForbiddenException();
}
await flushCaches(true);
res.status(200).end();
})
);
export default router;