directus
188 строк · 4.6 Кб
1import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
2import argon2 from 'argon2';
3import Busboy from 'busboy';
4import { Router } from 'express';
5import Joi from 'joi';
6import collectionExists from '../middleware/collection-exists.js';
7import { respond } from '../middleware/respond.js';
8import { ExportService, ImportService } from '../services/import-export.js';
9import { RevisionsService } from '../services/revisions.js';
10import { UtilsService } from '../services/utils.js';
11import asyncHandler from '../utils/async-handler.js';
12import { generateHash } from '../utils/generate-hash.js';
13import { sanitizeQuery } from '../utils/sanitize-query.js';
14
15const router = Router();
16
17const randomStringSchema = Joi.object<{ length: number }>({
18length: Joi.number().integer().min(1).max(500).default(32),
19});
20
21router.get(
22'/random/string',
23asyncHandler(async (req, res) => {
24const { nanoid } = await import('nanoid');
25
26const { error, value } = randomStringSchema.validate(req.query, { allowUnknown: true });
27
28if (error) throw new InvalidQueryError({ reason: error.message });
29
30return res.json({ data: nanoid(value.length) });
31}),
32);
33
34router.post(
35'/hash/generate',
36asyncHandler(async (req, res) => {
37if (!req.body?.string) {
38throw new InvalidPayloadError({ reason: `"string" is required` });
39}
40
41const hash = await generateHash(req.body.string);
42
43return res.json({ data: hash });
44}),
45);
46
47router.post(
48'/hash/verify',
49asyncHandler(async (req, res) => {
50if (!req.body?.string) {
51throw new InvalidPayloadError({ reason: `"string" is required` });
52}
53
54if (!req.body?.hash) {
55throw new InvalidPayloadError({ reason: `"hash" is required` });
56}
57
58const result = await argon2.verify(req.body.hash, req.body.string);
59
60return res.json({ data: result });
61}),
62);
63
64const SortSchema = Joi.object({
65item: Joi.alternatives(Joi.string(), Joi.number()).required(),
66to: Joi.alternatives(Joi.string(), Joi.number()).required(),
67});
68
69router.post(
70'/sort/:collection',
71collectionExists,
72asyncHandler(async (req, res) => {
73const { error } = SortSchema.validate(req.body);
74if (error) throw new InvalidPayloadError({ reason: error.message });
75
76const service = new UtilsService({
77accountability: req.accountability,
78schema: req.schema,
79});
80
81await service.sort(req.collection, req.body);
82
83return res.status(200).end();
84}),
85);
86
87router.post(
88'/revert/:revision',
89asyncHandler(async (req, _res, next) => {
90const service = new RevisionsService({
91accountability: req.accountability,
92schema: req.schema,
93});
94
95await service.revert(req.params['revision']!);
96next();
97}),
98respond,
99);
100
101router.post(
102'/import/:collection',
103collectionExists,
104asyncHandler(async (req, res, next) => {
105if (req.is('multipart/form-data') === false) {
106throw new UnsupportedMediaTypeError({ mediaType: req.headers['content-type']!, where: 'Content-Type header' });
107}
108
109const service = new ImportService({
110accountability: req.accountability,
111schema: req.schema,
112});
113
114let headers;
115
116if (req.headers['content-type']) {
117headers = req.headers;
118} else {
119headers = {
120...req.headers,
121'content-type': 'application/octet-stream',
122};
123}
124
125const busboy = Busboy({ headers });
126
127busboy.on('file', async (_fieldname, fileStream, { mimeType }) => {
128try {
129await service.import(req.params['collection']!, mimeType, fileStream);
130} catch (err: any) {
131return next(err);
132}
133
134return res.status(200).end();
135});
136
137busboy.on('error', (err: Error) => next(err));
138
139req.pipe(busboy);
140}),
141);
142
143router.post(
144'/export/:collection',
145collectionExists,
146asyncHandler(async (req, _res, next) => {
147if (!req.body.query) {
148throw new InvalidPayloadError({ reason: `"query" is required` });
149}
150
151if (!req.body.format) {
152throw new InvalidPayloadError({ reason: `"format" is required` });
153}
154
155const service = new ExportService({
156accountability: req.accountability,
157schema: req.schema,
158});
159
160const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability ?? null);
161
162// We're not awaiting this, as it's supposed to run async in the background
163service.exportToFile(req.params['collection']!, sanitizedQuery, req.body.format, {
164file: req.body.file,
165});
166
167return next();
168}),
169respond,
170);
171
172router.post(
173'/cache/clear',
174asyncHandler(async (req, res) => {
175const service = new UtilsService({
176accountability: req.accountability,
177schema: req.schema,
178});
179
180const clearSystemCache = 'system' in req.query && (req.query['system'] === '' || Boolean(req.query['system']));
181
182await service.clearCache({ system: clearSystemCache });
183
184res.status(200).end();
185}),
186);
187
188export default router;
189