@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
212 lines (211 loc) • 7.64 kB
JavaScript
import { useEnv } from '@directus/env';
import { ErrorCode, ForbiddenError, isDirectusError, RouteNotFoundError } from '@directus/errors';
import { EXTENSION_TYPES } from '@directus/constants';
import { account, describe, list, } from '@directus/extensions-registry';
import { isIn } from '@directus/utils';
import express from 'express';
import { isNil } from 'lodash-es';
import { UUID_REGEX } from '../constants.js';
import { getExtensionManager } from '../extensions/index.js';
import { respond } from '../middleware/respond.js';
import useCollection from '../middleware/use-collection.js';
import { ExtensionReadError, ExtensionsService } from '../services/extensions.js';
import asyncHandler from '../utils/async-handler.js';
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
import { getMilliseconds } from '../utils/get-milliseconds.js';
const router = express.Router();
const env = useEnv();
router.use(useCollection('directus_extensions'));
router.get('/', asyncHandler(async (req, res, next) => {
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
const extensions = await service.readAll();
res.locals['payload'] = { data: extensions || null };
return next();
}), respond);
router.get('/registry', asyncHandler(async (req, res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
const { search, limit, offset, sort, filter } = req.sanitizedQuery;
const query = {};
if (!isNil(search)) {
query.search = search;
}
if (!isNil(limit)) {
query.limit = limit;
}
if (!isNil(offset)) {
query.offset = offset;
}
if (filter) {
const getFilterValue = (key) => {
const field = filter[key];
if (!field || !('_eq' in field) || typeof field._eq !== 'string')
return;
return field._eq;
};
const by = getFilterValue('by');
const type = getFilterValue('type');
if (by) {
query.by = by;
}
if (type) {
if (isIn(type, EXTENSION_TYPES) === false) {
throw new ForbiddenError();
}
query.type = type;
}
}
if (!isNil(sort) && sort[0] && isIn(sort[0], ['popular', 'recent', 'downloads'])) {
query.sort = sort[0];
}
if (env['MARKETPLACE_TRUST'] === 'sandbox') {
query.sandbox = true;
}
const options = {};
if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
options.registry = env['MARKETPLACE_REGISTRY'];
}
const payload = await list(query, options);
res.locals['payload'] = payload;
return next();
}), respond);
router.get(`/registry/account/:pk(${UUID_REGEX})`, asyncHandler(async (req, res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
if (typeof req.params['pk'] !== 'string') {
throw new ForbiddenError();
}
const options = {};
if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
options.registry = env['MARKETPLACE_REGISTRY'];
}
const payload = await account(req.params['pk'], options);
res.locals['payload'] = payload;
return next();
}), respond);
router.get(`/registry/extension/:pk(${UUID_REGEX})`, asyncHandler(async (req, res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
if (typeof req.params['pk'] !== 'string') {
throw new ForbiddenError();
}
const options = {};
if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
options.registry = env['MARKETPLACE_REGISTRY'];
}
const payload = await describe(req.params['pk'], options);
res.locals['payload'] = payload;
return next();
}), respond);
router.post('/registry/install', asyncHandler(async (req, _res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
const { version, extension } = req.body;
if (!version || !extension) {
throw new ForbiddenError();
}
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
await service.install(extension, version);
return next();
}), respond);
router.post('/registry/reinstall', asyncHandler(async (req, _res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
const { extension } = req.body;
if (!extension) {
throw new ForbiddenError();
}
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
await service.reinstall(extension);
return next();
}), respond);
router.delete(`/registry/uninstall/:pk(${UUID_REGEX})`, asyncHandler(async (req, _res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
const pk = req.params['pk'];
if (typeof pk !== 'string') {
throw new ForbiddenError();
}
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
await service.uninstall(pk);
return next();
}), respond);
router.patch(`/:pk(${UUID_REGEX})`, asyncHandler(async (req, res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
if (typeof req.params['pk'] !== 'string') {
throw new ForbiddenError();
}
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
try {
const result = await service.updateOne(req.params['pk'], req.body);
res.locals['payload'] = { data: result || null };
}
catch (error) {
let finalError = error;
if (error instanceof ExtensionReadError) {
finalError = error.originalError;
if (isDirectusError(finalError, ErrorCode.Forbidden)) {
return next();
}
}
throw finalError;
}
return next();
}), respond);
router.delete(`/:pk(${UUID_REGEX})`, asyncHandler(async (req, _res, next) => {
if (req.accountability && req.accountability.admin !== true) {
throw new ForbiddenError();
}
const service = new ExtensionsService({
accountability: req.accountability,
schema: req.schema,
});
const pk = req.params['pk'];
if (typeof pk !== 'string') {
throw new ForbiddenError();
}
await service.deleteOne(pk);
return next();
}), respond);
router.get('/sources/:chunk', asyncHandler(async (req, res) => {
const chunk = req.params['chunk'];
const extensionManager = getExtensionManager();
let source;
if (chunk === 'index.js') {
source = await extensionManager.getAppExtensionChunk();
}
else {
source = await extensionManager.getAppExtensionChunk(chunk);
}
if (source === null) {
throw new RouteNotFoundError({ path: req.path });
}
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['EXTENSIONS_CACHE_TTL']), false, false));
res.setHeader('Vary', 'Origin, Cache-Control');
source.pipe(res);
}));
export default router;