directus

Форк
0
/
extensions.ts 
321 строка · 7.1 Кб
1
import { useEnv } from '@directus/env';
2
import { ErrorCode, ForbiddenError, isDirectusError, RouteNotFoundError } from '@directus/errors';
3
import { EXTENSION_TYPES } from '@directus/extensions';
4
import {
5
	account,
6
	describe,
7
	list,
8
	type AccountOptions,
9
	type DescribeOptions,
10
	type ListOptions,
11
	type ListQuery,
12
} from '@directus/extensions-registry';
13
import type { FieldFilter } from '@directus/types';
14
import { isIn } from '@directus/utils';
15
import express from 'express';
16
import { isNil } from 'lodash-es';
17
import { UUID_REGEX } from '../constants.js';
18
import { getExtensionManager } from '../extensions/index.js';
19
import { respond } from '../middleware/respond.js';
20
import useCollection from '../middleware/use-collection.js';
21
import { ExtensionReadError, ExtensionsService } from '../services/extensions.js';
22
import asyncHandler from '../utils/async-handler.js';
23
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
24
import { getMilliseconds } from '../utils/get-milliseconds.js';
25

26
const router = express.Router();
27
const env = useEnv();
28

29
router.use(useCollection('directus_extensions'));
30

31
router.get(
32
	'/',
33
	asyncHandler(async (req, res, next) => {
34
		const service = new ExtensionsService({
35
			accountability: req.accountability,
36
			schema: req.schema,
37
		});
38

39
		const extensions = await service.readAll();
40
		res.locals['payload'] = { data: extensions || null };
41
		return next();
42
	}),
43
	respond,
44
);
45

46
router.get(
47
	'/registry',
48
	asyncHandler(async (req, res, next) => {
49
		if (req.accountability && req.accountability.admin !== true) {
50
			throw new ForbiddenError();
51
		}
52

53
		const { search, limit, offset, sort, filter } = req.sanitizedQuery;
54

55
		const query: ListQuery = {};
56

57
		if (!isNil(search)) {
58
			query.search = search;
59
		}
60

61
		if (!isNil(limit)) {
62
			query.limit = limit;
63
		}
64

65
		if (!isNil(offset)) {
66
			query.offset = offset;
67
		}
68

69
		if (filter) {
70
			const getFilterValue = (key: string) => {
71
				const field = (filter as FieldFilter)[key];
72
				if (!field || !('_eq' in field) || typeof field._eq !== 'string') return;
73
				return field._eq;
74
			};
75

76
			const by = getFilterValue('by');
77
			const type = getFilterValue('type');
78

79
			if (by) {
80
				query.by = by;
81
			}
82

83
			if (type) {
84
				if (isIn(type, EXTENSION_TYPES) === false) {
85
					throw new ForbiddenError();
86
				}
87

88
				query.type = type;
89
			}
90
		}
91

92
		if (!isNil(sort) && sort[0] && isIn(sort[0], ['popular', 'recent', 'downloads'] as const)) {
93
			query.sort = sort[0];
94
		}
95

96
		if (env['MARKETPLACE_TRUST'] === 'sandbox') {
97
			query.sandbox = true;
98
		}
99

100
		const options: ListOptions = {};
101

102
		if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
103
			options.registry = env['MARKETPLACE_REGISTRY'];
104
		}
105

106
		const payload = await list(query, options);
107

108
		res.locals['payload'] = payload;
109
		return next();
110
	}),
111
	respond,
112
);
113

114
router.get(
115
	`/registry/account/:pk(${UUID_REGEX})`,
116
	asyncHandler(async (req, res, next) => {
117
		if (typeof req.params['pk'] !== 'string') {
118
			throw new ForbiddenError();
119
		}
120

121
		const options: AccountOptions = {};
122

123
		if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
124
			options.registry = env['MARKETPLACE_REGISTRY'];
125
		}
126

127
		const payload = await account(req.params['pk'], options);
128

129
		res.locals['payload'] = payload;
130
		return next();
131
	}),
132
	respond,
133
);
134

135
router.get(
136
	`/registry/extension/:pk(${UUID_REGEX})`,
137
	asyncHandler(async (req, res, next) => {
138
		if (typeof req.params['pk'] !== 'string') {
139
			throw new ForbiddenError();
140
		}
141

142
		const options: DescribeOptions = {};
143

144
		if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
145
			options.registry = env['MARKETPLACE_REGISTRY'];
146
		}
147

148
		const payload = await describe(req.params['pk'], options);
149

150
		res.locals['payload'] = payload;
151
		return next();
152
	}),
153
	respond,
154
);
155

156
router.post(
157
	'/registry/install',
158
	asyncHandler(async (req, _res, next) => {
159
		if (req.accountability && req.accountability.admin !== true) {
160
			throw new ForbiddenError();
161
		}
162

163
		const { version, extension } = req.body;
164

165
		if (!version || !extension) {
166
			throw new ForbiddenError();
167
		}
168

169
		const service = new ExtensionsService({
170
			accountability: req.accountability,
171
			schema: req.schema,
172
		});
173

174
		await service.install(extension, version);
175
		return next();
176
	}),
177
	respond,
178
);
179

180
router.post(
181
	'/registry/reinstall',
182
	asyncHandler(async (req, _res, next) => {
183
		if (req.accountability && req.accountability.admin !== true) {
184
			throw new ForbiddenError();
185
		}
186

187
		const { extension } = req.body;
188

189
		if (!extension) {
190
			throw new ForbiddenError();
191
		}
192

193
		const service = new ExtensionsService({
194
			accountability: req.accountability,
195
			schema: req.schema,
196
		});
197

198
		await service.reinstall(extension);
199
		return next();
200
	}),
201
	respond,
202
);
203

204
router.delete(
205
	`/registry/uninstall/:pk(${UUID_REGEX})`,
206
	asyncHandler(async (req, _res, next) => {
207
		if (req.accountability && req.accountability.admin !== true) {
208
			throw new ForbiddenError();
209
		}
210

211
		const pk = req.params['pk'];
212

213
		if (typeof pk !== 'string') {
214
			throw new ForbiddenError();
215
		}
216

217
		const service = new ExtensionsService({
218
			accountability: req.accountability,
219
			schema: req.schema,
220
		});
221

222
		await service.uninstall(pk);
223
		return next();
224
	}),
225
	respond,
226
);
227

228
router.patch(
229
	`/:pk(${UUID_REGEX})`,
230
	asyncHandler(async (req, res, next) => {
231
		if (req.accountability && req.accountability.admin !== true) {
232
			throw new ForbiddenError();
233
		}
234

235
		if (typeof req.params['pk'] !== 'string') {
236
			throw new ForbiddenError();
237
		}
238

239
		const service = new ExtensionsService({
240
			accountability: req.accountability,
241
			schema: req.schema,
242
		});
243

244
		try {
245
			const result = await service.updateOne(req.params['pk'], req.body);
246
			res.locals['payload'] = { data: result || null };
247
		} catch (error) {
248
			let finalError = error;
249

250
			if (error instanceof ExtensionReadError) {
251
				finalError = error.originalError;
252

253
				if (isDirectusError(finalError, ErrorCode.Forbidden)) {
254
					return next();
255
				}
256
			}
257

258
			throw finalError;
259
		}
260

261
		return next();
262
	}),
263
	respond,
264
);
265

266
router.delete(
267
	`/:pk(${UUID_REGEX})`,
268
	asyncHandler(async (req, _res, next) => {
269
		if (req.accountability && req.accountability.admin !== true) {
270
			throw new ForbiddenError();
271
		}
272

273
		const service = new ExtensionsService({
274
			accountability: req.accountability,
275
			schema: req.schema,
276
		});
277

278
		const pk = req.params['pk'];
279

280
		if (typeof pk !== 'string') {
281
			throw new ForbiddenError();
282
		}
283

284
		await service.deleteOne(pk);
285

286
		return next();
287
	}),
288
	respond,
289
);
290

291
router.get(
292
	'/sources/:chunk',
293
	asyncHandler(async (req, res) => {
294
		const chunk = req.params['chunk'] as string;
295
		const extensionManager = getExtensionManager();
296

297
		let source: string | null;
298

299
		if (chunk === 'index.js') {
300
			source = extensionManager.getAppExtensionsBundle();
301
		} else {
302
			source = extensionManager.getAppExtensionChunk(chunk);
303
		}
304

305
		if (source === null) {
306
			throw new RouteNotFoundError({ path: req.path });
307
		}
308

309
		res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
310

311
		res.setHeader(
312
			'Cache-Control',
313
			getCacheControlHeader(req, getMilliseconds(env['EXTENSIONS_CACHE_TTL']), false, false),
314
		);
315

316
		res.setHeader('Vary', 'Origin, Cache-Control');
317
		res.end(source);
318
	}),
319
);
320

321
export default router;
322

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.