mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Merge pull request #151 from directus/feature-redis-cache
Feature redis cache
This commit is contained in:
@@ -46,6 +46,25 @@ RATE_LIMITER_STORE=memory # memory | redis | memcache
|
||||
## https://www.npmjs.com/package/memcached)
|
||||
# RATE_LIMITER_MEMCACHE='localhost:11211'
|
||||
|
||||
####################################################################################################
|
||||
# Caching
|
||||
|
||||
CACHE_ENABLED=true
|
||||
CACHE_TTL="30m"
|
||||
CACHE_NAMESPACE="directus-cache"
|
||||
CACHE_STORE=memory # memory | redis | memcache
|
||||
|
||||
# CACHE_REDIS="redis://:authpassword@127.0.0.1:6380/4"
|
||||
# --OR--
|
||||
# CACHE_REDIS_HOST="127.0.0.1"
|
||||
# CACHE_REDIS_PORT="127.0.0.1"
|
||||
# CACHE_REDIS_PASSWORD="127.0.0.1"
|
||||
# CACHE_REDIS_DB="127.0.0.1"
|
||||
|
||||
## Memcache (see https://github.com/animir/node-rate-limiter-flexible/wiki/Memcache and
|
||||
## https://www.npmjs.com/package/memcached)
|
||||
# CACHE_MEMCACHE='localhost:11211'
|
||||
|
||||
####################################################################################################
|
||||
# File Storage
|
||||
|
||||
|
||||
120
api/package-lock.json
generated
120
api/package-lock.json
generated
@@ -25723,6 +25723,15 @@
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@keyv/redis": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.1.2.tgz",
|
||||
"integrity": "sha512-D6vNOuyH/5cBNfHcyxzck1l7V+Qd4RAT7uz2SHYAjutbXQ03o3SSneRyvrp76H4/uvHyutPWTJ1Za3EpGSVe5g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ioredis": "~4.17.1"
|
||||
}
|
||||
},
|
||||
"@otplib/core": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz",
|
||||
@@ -25948,6 +25957,15 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/keyv": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
|
||||
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.161",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.161.tgz",
|
||||
@@ -26327,6 +26345,12 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"argv": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz",
|
||||
"integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=",
|
||||
"optional": true
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@@ -26998,6 +27022,66 @@
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"codecov": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.2.tgz",
|
||||
"integrity": "sha512-fmCjAkTese29DUX3GMIi4EaKGflHa4K51EoMc29g8fBHawdk/+KEq5CWOeXLdd9+AT7o1wO4DIpp/Z1KCqCz1g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"argv": "0.0.2",
|
||||
"ignore-walk": "3.0.3",
|
||||
"js-yaml": "3.13.1",
|
||||
"teeny-request": "6.0.1",
|
||||
"urlgrey": "0.4.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
|
||||
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
|
||||
"optional": true
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
|
||||
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"agent-base": "5",
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"teeny-request": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz",
|
||||
"integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"http-proxy-agent": "^4.0.0",
|
||||
"https-proxy-agent": "^4.0.0",
|
||||
"node-fetch": "^2.2.0",
|
||||
"stream-events": "^1.0.5",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||
@@ -29255,6 +29339,11 @@
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
|
||||
},
|
||||
"json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
@@ -29378,6 +29467,25 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"keyv": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz",
|
||||
"integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==",
|
||||
"requires": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"keyv-memcache": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/keyv-memcache/-/keyv-memcache-0.8.0.tgz",
|
||||
"integrity": "sha512-303ARXs6vv7v8Z12L9LK58dq8oc1TGcpeOt+CLbh0V1JCQkXLxuqt89F9Owgm3W+K3EqPTrn4NOI0f92vrsFUA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"codecov": "^3.5.0",
|
||||
"json-buffer": "^3.0.1",
|
||||
"memjs": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
@@ -29812,6 +29920,12 @@
|
||||
"jackpot": ">=0.0.6"
|
||||
}
|
||||
},
|
||||
"memjs": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/memjs/-/memjs-1.2.2.tgz",
|
||||
"integrity": "sha512-j6I5cQsjT8izm0FcBZrwga4VmlhTMsBTPKdyKolQenLulHNvKuNcDgDmBhQvScqNLy4tjpCCFwiqFK+5l6J20g==",
|
||||
"optional": true
|
||||
},
|
||||
"meow": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
|
||||
@@ -32842,6 +32956,12 @@
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"urlgrey": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz",
|
||||
"integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=",
|
||||
"optional": true
|
||||
},
|
||||
"use": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
"joi": "^17.1.1",
|
||||
"js-yaml": "^3.14.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"keyv": "^4.0.1",
|
||||
"knex": "^0.21.4",
|
||||
"knex-schema-inspector": "0.0.11",
|
||||
"liquidjs": "^9.14.1",
|
||||
@@ -112,7 +113,9 @@
|
||||
"uuid-validate": "0.0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@keyv/redis": "^2.1.2",
|
||||
"ioredis": "^4.17.3",
|
||||
"keyv-memcache": "^0.8.0",
|
||||
"memcached": "^2.2.2",
|
||||
"mssql": "^6.2.0",
|
||||
"mysql": "^2.18.1",
|
||||
@@ -134,6 +137,7 @@
|
||||
"@types/joi": "^14.3.4",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/keyv": "^3.1.1",
|
||||
"@types/lodash": "^4.14.159",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
|
||||
@@ -9,9 +9,11 @@ import env from './env';
|
||||
|
||||
import errorHandler from './middleware/error-handler';
|
||||
import cors from './middleware/cors';
|
||||
import rateLimiter from './middleware/rate-limiter';
|
||||
import { respond } from './middleware/respond';
|
||||
import cache from './middleware/cache';
|
||||
import extractToken from './middleware/extract-token';
|
||||
import authenticate from './middleware/authenticate';
|
||||
import rateLimiter from './middleware/rate-limiter';
|
||||
import activityRouter from './controllers/activity';
|
||||
import assetsRouter from './controllers/assets';
|
||||
import authRouter from './controllers/auth';
|
||||
@@ -33,6 +35,7 @@ import utilsRouter from './controllers/utils';
|
||||
import webhooksRouter from './controllers/webhooks';
|
||||
|
||||
import notFoundHandler from './controllers/not-found';
|
||||
import sanitizeQuery from './middleware/sanitize-query';
|
||||
|
||||
validateEnv(['KEY', 'SECRET']);
|
||||
|
||||
@@ -70,6 +73,8 @@ if (env.RATE_LIMITER_ENABLED === true) {
|
||||
app.use('/auth', authRouter);
|
||||
app.use(authenticate);
|
||||
|
||||
app.use(sanitizeQuery);
|
||||
app.use(cache);
|
||||
app.use('/activity', activityRouter);
|
||||
app.use('/assets', assetsRouter);
|
||||
app.use('/collections', collectionsRouter);
|
||||
@@ -89,6 +94,8 @@ app.use('/users', usersRouter);
|
||||
app.use('/utils', utilsRouter);
|
||||
app.use('/webhooks', webhooksRouter);
|
||||
|
||||
app.use(respond);
|
||||
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
|
||||
50
api/src/cache.ts
Normal file
50
api/src/cache.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import env from './env';
|
||||
import Keyv, { Options } from 'keyv';
|
||||
import { validateEnv } from './utils/validate-env';
|
||||
import { getConfigFromEnv } from './utils/get-config-from-env';
|
||||
import ms from 'ms';
|
||||
import logger from './logger';
|
||||
|
||||
let cache: Keyv | null = null;
|
||||
|
||||
if (env.CACHE_ENABLED === true) {
|
||||
validateEnv(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']);
|
||||
cache = getKevyInstance();
|
||||
cache.on('error', logger.error);
|
||||
}
|
||||
|
||||
export default cache;
|
||||
|
||||
function getKevyInstance() {
|
||||
switch (env.CACHE_STORE) {
|
||||
case 'redis':
|
||||
return new Keyv(getConfig('redis'));
|
||||
case 'memcache':
|
||||
return new Keyv(getConfig('memcache'));
|
||||
case 'memory':
|
||||
default:
|
||||
return new Keyv(getConfig());
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig(
|
||||
store: 'memory' | 'redis' | 'memcache' = 'memory'
|
||||
): Options<any> {
|
||||
const config: Options<any> = { namespace: env.CACHE_NAMESPACE, ttl: ms(env.CACHE_TTL as string) };
|
||||
|
||||
if (store === 'redis') {
|
||||
const Redis = require('ioredis');
|
||||
const KeyvRedis = require('@keyv/redis');
|
||||
|
||||
config.store = new KeyvRedis(new Redis(
|
||||
env.CACHE_REDIS || getConfigFromEnv('CACHE_REDIS_')
|
||||
));
|
||||
}
|
||||
|
||||
if (store === 'memcache') {
|
||||
const KeyvMemcache = require('keyv-memcache');
|
||||
config.store = new KeyvMemcache(env.CACHE_MEMCACHE);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -30,10 +30,10 @@ STORAGE_LOCAL_ROOT="./uploads"
|
||||
|
||||
{{ security }}
|
||||
|
||||
ACCESS_TOKEN_TTL="15m",
|
||||
REFRESH_TOKEN_TTL="7d",
|
||||
REFRESH_TOKEN_COOKIE_SECURE=false,
|
||||
REFRESH_TOKEN_COOKIE_SAME_SITE="lax",
|
||||
ACCESS_TOKEN_TTL="15m"
|
||||
REFRESH_TOKEN_TTL="7d"
|
||||
REFRESH_TOKEN_COOKIE_SECURE=false
|
||||
REFRESH_TOKEN_COOKIE_SAME_SITE="lax"
|
||||
|
||||
####################################################################################################
|
||||
## SSO (OAuth) Providers
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import ActivityService from '../services/activity';
|
||||
import MetaService from '../services/meta';
|
||||
import { Action } from '../types';
|
||||
@@ -10,41 +8,39 @@ const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
useCollection('directus_activity'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: records || null,
|
||||
meta,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
useCollection('directus_activity'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: record || null,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/comment',
|
||||
useCollection('directus_activity'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({ accountability: req.accountability });
|
||||
|
||||
const primaryKey = await service.create({
|
||||
@@ -57,36 +53,37 @@ router.post(
|
||||
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: record || null,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/comment/:pk',
|
||||
useCollection('directus_activity'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: record || null,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/comment/:pk',
|
||||
useCollection('directus_activity'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SYSTEM_ASSET_ALLOW_LIST, ASSET_TRANSFORM_QUERY_KEYS } from '../constant
|
||||
import { InvalidQueryException, ForbiddenException } from '../exceptions';
|
||||
import AssetsService from '../services/assets';
|
||||
import validate from 'uuid-validate';
|
||||
import { pick, merge } from 'lodash';
|
||||
import { pick } from 'lodash';
|
||||
import { Transformation } from '../types/assets';
|
||||
import storage from '../storage';
|
||||
import PayloadService from '../services/payload';
|
||||
|
||||
@@ -23,7 +23,7 @@ const loginSchema = Joi.object({
|
||||
|
||||
router.post(
|
||||
'/login',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const accountability = {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
@@ -72,14 +72,15 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json(payload);
|
||||
})
|
||||
res.locals.payload = payload;
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/refresh',
|
||||
cookieParser(),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const accountability = {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
@@ -122,14 +123,15 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json(payload);
|
||||
})
|
||||
res.locals.payload = payload;
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/logout',
|
||||
cookieParser(),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const accountability = {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
@@ -149,14 +151,13 @@ router.post(
|
||||
}
|
||||
|
||||
await authenticationService.logout(currentRefreshToken);
|
||||
|
||||
res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/password/request',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.body.email) {
|
||||
throw new InvalidPayloadException(`"email" field is required.`);
|
||||
}
|
||||
@@ -175,14 +176,14 @@ router.post(
|
||||
// We don't want to give away what email addresses exist, so we'll always return a 200
|
||||
// from this endpoint
|
||||
} finally {
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/password/reset',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.body.token) {
|
||||
throw new InvalidPayloadException(`"token" field is required.`);
|
||||
}
|
||||
@@ -199,8 +200,8 @@ router.post(
|
||||
|
||||
const service = new UsersService({ accountability });
|
||||
await service.resetPassword(req.body.token, req.body.password);
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.use(
|
||||
@@ -215,7 +216,7 @@ router.use(grant.express()(getGrantConfig()));
|
||||
*/
|
||||
router.get(
|
||||
'/sso/:provider/callback',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const accountability = {
|
||||
ip: req.ip,
|
||||
userAgent: req.get('user-agent'),
|
||||
@@ -232,10 +233,12 @@ router.get(
|
||||
email
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
res.locals.payload = {
|
||||
data: { access_token: accessToken, refresh_token: refreshToken, expires },
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,78 +1,75 @@
|
||||
import { Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import CollectionsService from '../services/collections';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import CollectionsService from '../services/collections'
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
useCollection('directus_collections'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({ accountability: req.accountability });
|
||||
|
||||
const collectionKey = await collectionsService.create(req.body);
|
||||
const record = await collectionsService.readByKey(collectionKey);
|
||||
|
||||
res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
useCollection('directus_collections'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const collections = await collectionsService.readByQuery();
|
||||
const meta = await metaService.getMetaForQuery(req.collection, {});
|
||||
const meta = await metaService.getMetaForQuery('directus_collections', {});
|
||||
|
||||
res.json({ data: collections || null, meta });
|
||||
res.locals.payload = { data: collections || null, meta };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection',
|
||||
useCollection('directus_collections'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({ accountability: req.accountability });
|
||||
const collectionKey = req.params.collection.includes(',')
|
||||
? req.params.collection.split(',')
|
||||
: req.params.collection;
|
||||
const collection = await collectionsService.readByKey(collectionKey as any);
|
||||
res.json({ data: collection || null });
|
||||
|
||||
res.locals.payload = { data: collection || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:collection',
|
||||
useCollection('directus_collections'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({ accountability: req.accountability });
|
||||
const collectionKey = req.params.collection.includes(',')
|
||||
? req.params.collection.split(',')
|
||||
: req.params.collection;
|
||||
await collectionsService.update(req.body, collectionKey as any);
|
||||
const collection = await collectionsService.readByKey(collectionKey as any);
|
||||
res.json({ data: collection || null });
|
||||
res.locals.payload = { data: collection || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:collection',
|
||||
useCollection('directus_collections'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({ accountability: req.accountability });
|
||||
const collectionKey = req.params.collection.includes(',')
|
||||
? req.params.collection.split(',')
|
||||
: req.params.collection;
|
||||
await collectionsService.delete(collectionKey as any);
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const router = Router();
|
||||
|
||||
router.get(
|
||||
'/:type',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const typeAllowList = ['interfaces', 'layouts', 'displays', 'modules'];
|
||||
|
||||
if (typeAllowList.includes(req.params.type) === false) {
|
||||
@@ -16,10 +16,12 @@ router.get(
|
||||
|
||||
const interfaces = await ExtensionsService.listExtensions(req.params.type);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: interfaces,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -3,56 +3,50 @@ import asyncHandler from 'express-async-handler';
|
||||
import FieldsService from '../services/fields';
|
||||
import validateCollection from '../middleware/collection-exists';
|
||||
import { schemaInspector } from '../database';
|
||||
import { FieldNotFoundException, InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import Joi from 'joi';
|
||||
import { Field } from '../types/field';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { Accountability, types } from '../types';
|
||||
import { types } from '../types';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* @TODO
|
||||
*
|
||||
* Add accountability / permissions handling to fields
|
||||
*/
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const fields = await service.readAll();
|
||||
return res.json({ data: fields || null });
|
||||
})
|
||||
|
||||
res.locals.payload = { data: fields || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const fields = await service.readAll(req.params.collection);
|
||||
return res.json({ data: fields || null });
|
||||
})
|
||||
|
||||
res.locals.payload = { data: fields || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection/:field',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const exists = await schemaInspector.hasColumn(req.collection, req.params.field);
|
||||
const exists = await schemaInspector.hasColumn(req.params.collection, req.params.field);
|
||||
if (exists === false) throw new ForbiddenException();
|
||||
|
||||
const field = await service.readOne(req.params.collection, req.params.field);
|
||||
return res.json({ data: field || null });
|
||||
})
|
||||
|
||||
res.locals.payload = { data: field || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
const newFieldSchema = Joi.object({
|
||||
@@ -72,8 +66,7 @@ const newFieldSchema = Joi.object({
|
||||
router.post(
|
||||
'/:collection',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const { error } = newFieldSchema.validate(req.body);
|
||||
@@ -88,15 +81,15 @@ router.post(
|
||||
|
||||
const createdField = await service.readOne(req.params.collection, field.field);
|
||||
|
||||
return res.json({ data: createdField || null });
|
||||
})
|
||||
res.locals.payload = { data: createdField || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:collection',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
if (Array.isArray(req.body) === false)
|
||||
@@ -112,15 +105,16 @@ router.patch(
|
||||
results.push(updatedField);
|
||||
}
|
||||
|
||||
return res.json({ data: results || null });
|
||||
})
|
||||
res.locals.payload = { data: results || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:collection/:field',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
// @todo: validate field
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
const fieldData: Partial<Field> & { field: string; type: typeof types[number] } = req.body;
|
||||
|
||||
@@ -130,20 +124,19 @@ router.patch(
|
||||
|
||||
const updatedField = await service.readOne(req.params.collection, req.params.field);
|
||||
|
||||
return res.json({ data: updatedField || null });
|
||||
})
|
||||
res.locals.payload = { data: updatedField || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:collection/:field',
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
await service.deleteField(req.params.collection, req.params.field);
|
||||
|
||||
res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import Busboy from 'busboy';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import FilesService from '../services/files';
|
||||
import MetaService from '../services/meta';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { File, PrimaryKey } from '../types';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import env from '../env';
|
||||
@@ -16,8 +14,6 @@ import path from 'path';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_files'));
|
||||
|
||||
const multipartHandler = asyncHandler(async (req, res, next) => {
|
||||
if (req.is('multipart/form-data') === false) return next();
|
||||
|
||||
@@ -100,9 +96,8 @@ const multipartHandler = asyncHandler(async (req, res, next) => {
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
multipartHandler,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FilesService({ accountability: req.accountability });
|
||||
let keys: PrimaryKey | PrimaryKey[] = [];
|
||||
|
||||
@@ -115,7 +110,8 @@ router.post(
|
||||
|
||||
const record = await service.readByKey(keys as any, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: res.locals.savedFiles.length === 1 ? record[0] : record || null });
|
||||
res.locals.payload = { data: res.locals.savedFiles.length === 1 ? record[0] : record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -125,8 +121,7 @@ const importSchema = Joi.object({
|
||||
|
||||
router.post(
|
||||
'/import',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const { error } = importSchema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
@@ -154,40 +149,40 @@ router.post(
|
||||
|
||||
const primaryKey = await service.upload(fileResponse.data, payload);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FilesService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const keys = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
|
||||
const service = new FilesService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(keys as any, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
multipartHandler,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FilesService({ accountability: req.accountability });
|
||||
let keys: PrimaryKey | PrimaryKey[] = [];
|
||||
|
||||
@@ -199,17 +194,18 @@ router.patch(
|
||||
}
|
||||
|
||||
const record = await service.readByKey(keys as any, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const keys = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
|
||||
const service = new FilesService({ accountability: req.accountability });
|
||||
await service.delete(keys as any);
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,70 +1,66 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import FoldersService from '../services/folders';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_folders'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import ItemsService from '../services/items';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import collectionExists from '../middleware/collection-exists';
|
||||
import MetaService from '../services/meta';
|
||||
import { RouteNotFoundException } from '../exceptions';
|
||||
@@ -11,8 +10,7 @@ const router = express.Router();
|
||||
router.post(
|
||||
'/:collection',
|
||||
collectionExists,
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.singleton) {
|
||||
throw new RouteNotFoundException(req.path);
|
||||
}
|
||||
@@ -21,15 +19,15 @@ router.post(
|
||||
const primaryKey = await service.create(req.body);
|
||||
const result = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
res.json({ data: result || null });
|
||||
})
|
||||
res.locals.payload = { data: result || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection',
|
||||
collectionExists,
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ItemsService(req.collection, { accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
@@ -39,18 +37,18 @@ router.get(
|
||||
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
meta: meta,
|
||||
data: records || null,
|
||||
});
|
||||
})
|
||||
};
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection/:pk',
|
||||
collectionExists,
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.singleton) {
|
||||
throw new RouteNotFoundException(req.path);
|
||||
}
|
||||
@@ -59,37 +57,38 @@ router.get(
|
||||
const primaryKey = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
|
||||
const result = await service.readByKey(primaryKey as any, req.sanitizedQuery);
|
||||
|
||||
return res.json({
|
||||
res.locals.payload = {
|
||||
data: result || null,
|
||||
});
|
||||
})
|
||||
};
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:collection',
|
||||
collectionExists,
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ItemsService(req.collection, { accountability: req.accountability });
|
||||
|
||||
if (req.singleton === true) {
|
||||
await service.upsertSingleton(req.body);
|
||||
const item = await service.readSingleton(req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}
|
||||
|
||||
const primaryKeys = await service.update(req.body);
|
||||
const result = await service.readByKey(primaryKeys, req.sanitizedQuery);
|
||||
return res.json({ data: result || null });
|
||||
})
|
||||
res.locals.payload = { data: result || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:collection/:pk',
|
||||
collectionExists,
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.singleton) {
|
||||
throw new RouteNotFoundException(req.path);
|
||||
}
|
||||
@@ -100,20 +99,20 @@ router.patch(
|
||||
const updatedPrimaryKey = await service.update(req.body, primaryKey as any);
|
||||
const result = await service.readByKey(updatedPrimaryKey, req.sanitizedQuery);
|
||||
|
||||
res.json({ data: result || null });
|
||||
})
|
||||
res.locals.payload = { data: result || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:collection/:pk',
|
||||
collectionExists,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ItemsService(req.collection, { accountability: req.accountability });
|
||||
const pk = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
|
||||
await service.delete(pk as any);
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,50 +1,47 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import PermissionsService from '../services/permissions';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import MetaService from '../services/meta';
|
||||
import { clone } from 'lodash';
|
||||
import { InvalidCredentialsException } from '../exceptions';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_permissions'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const item = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: item || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/me',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user || !req.accountability?.role) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const service = new PermissionsService();
|
||||
const query = req.sanitizedQuery || {};
|
||||
const query = clone(req.sanitizedQuery || {});
|
||||
|
||||
query.filter = {
|
||||
...(query.filter || {}),
|
||||
@@ -55,40 +52,43 @@ router.get(
|
||||
|
||||
const items = await service.readByQuery(req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: items || null });
|
||||
})
|
||||
res.locals.payload = { data: items || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.path.endsWith('me')) return next();
|
||||
const service = new PermissionsService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(Number(req.params.pk), req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, Number(req.params.pk));
|
||||
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({ accountability: req.accountability });
|
||||
await service.delete(Number(req.params.pk));
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,69 +1,65 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import PresetsService from '../services/presets';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_presets'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const record = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,68 +1,63 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import RelationsService from '../services/relations';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_relations'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({ accountability: req.accountability });
|
||||
await service.delete(Number(req.params.pk));
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import RevisionsService from '../services/revisions';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
@@ -9,28 +7,26 @@ const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
useCollection('directus_revisions'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RevisionsService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_revisions', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
useCollection('directus_revisions'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RevisionsService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,68 +1,63 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import RolesService from '../services/roles';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_roles'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_roles', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -5,10 +5,11 @@ const router = Router();
|
||||
|
||||
router.get('/ping', (req, res) => res.send('pong'));
|
||||
|
||||
router.get('/info', (req, res) => {
|
||||
router.get('/info', (req, res, next) => {
|
||||
const service = new ServerService({ accountability: req.accountability });
|
||||
const data = service.serverInfo();
|
||||
res.json({ data });
|
||||
res.locals.payload = data;
|
||||
return next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import SettingsService from '../services/settings';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
useCollection('directus_settings'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new SettingsService({ accountability: req.accountability });
|
||||
const records = await service.readSingleton(req.sanitizedQuery);
|
||||
return res.json({ data: records || null });
|
||||
res.locals.payload = { data: records || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/',
|
||||
useCollection('directus_settings'),
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new SettingsService({ accountability: req.accountability });
|
||||
await service.upsertSingleton(req.body);
|
||||
const record = await service.readSingleton(req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
res.locals.payload = { data: record || null };
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,46 +1,41 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import Joi from 'joi';
|
||||
import { InvalidPayloadException, InvalidCredentialsException } from '../exceptions';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import UsersService from '../services/users';
|
||||
import MetaService from '../services/meta';
|
||||
import AuthService from '../services/authentication';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_users'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const item = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_users', req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: item || null, meta };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/me',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
@@ -48,24 +43,25 @@ router.get(
|
||||
|
||||
const item = await service.readByKey(req.accountability.user, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.path.endsWith('me')) return next();
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
const items = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
return res.json({ data: items || null });
|
||||
})
|
||||
res.locals.payload = { data: items || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/me',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
@@ -74,14 +70,14 @@ router.patch(
|
||||
const primaryKey = await service.update(req.body, req.accountability.user);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/me/track/page',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
@@ -93,30 +89,30 @@ router.patch(
|
||||
const service = new UsersService();
|
||||
await service.update({ last_page: req.body.last_page }, req.accountability.user);
|
||||
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -127,13 +123,13 @@ const inviteSchema = Joi.object({
|
||||
|
||||
router.post(
|
||||
'/invite',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const { error } = inviteSchema.validate(req.body);
|
||||
if (error) throw new InvalidPayloadException(error.message);
|
||||
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
await service.inviteUser(req.body.email, req.body.role);
|
||||
res.end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -144,18 +140,18 @@ const acceptInviteSchema = Joi.object({
|
||||
|
||||
router.post(
|
||||
'/invite/accept',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const { error } = acceptInviteSchema.validate(req.body);
|
||||
if (error) throw new InvalidPayloadException(error.message);
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
await service.acceptInvite(req.body.token, req.body.password);
|
||||
res.end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/me/tfa/enable/',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
@@ -163,13 +159,14 @@ router.post(
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
const { url, secret } = await service.enableTFA(req.accountability.user);
|
||||
|
||||
return res.json({ data: { secret, otpauth_url: url } });
|
||||
})
|
||||
res.locals.payload = { data: { secret, otpauth_url: url } };
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/me/tfa/disable',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
@@ -188,8 +185,7 @@ router.post(
|
||||
}
|
||||
|
||||
await service.disableTFA(req.accountability.user);
|
||||
|
||||
return res.status(200).end();
|
||||
return next();
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1,69 +1,63 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import sanitizeQuery from '../middleware/sanitize-query';
|
||||
import WebhooksService from '../services/webhooks';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import MetaService from '../services/meta';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_webhooks'));
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({ accountability: req.accountability });
|
||||
const primaryKey = await service.create(req.body);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({ accountability: req.accountability });
|
||||
const metaService = new MetaService({ accountability: req.accountability });
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: records || null, meta });
|
||||
})
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
sanitizeQuery,
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({ accountability: req.accountability });
|
||||
const record = await service.readByKey(req.params.pk, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: record || null });
|
||||
})
|
||||
res.locals.payload = { data: record || null };
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({ accountability: req.accountability });
|
||||
const primaryKey = await service.update(req.body, req.params.pk);
|
||||
const item = await service.readByKey(primaryKey, req.sanitizedQuery);
|
||||
|
||||
return res.json({ data: item || null });
|
||||
})
|
||||
res.locals.payload = { data: item || null };
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:pk',
|
||||
asyncHandler(async (req, res) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({ accountability: req.accountability });
|
||||
await service.delete(req.params.pk);
|
||||
|
||||
return res.status(200).end();
|
||||
})
|
||||
return next();
|
||||
}),
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -29,6 +29,11 @@ const defaults: Record<string, any> = {
|
||||
|
||||
CORS_ENABLED: false,
|
||||
|
||||
CACHE_ENABLED: false,
|
||||
CACHE_STORE: false,
|
||||
CACHE_TTL: '30m',
|
||||
CACHE_NAMESPACE: 'system-cache',
|
||||
|
||||
OAUTH_PROVIDERS: '',
|
||||
|
||||
EXTENSIONS_PATH: './extensions',
|
||||
|
||||
23
api/src/middleware/cache.ts
Normal file
23
api/src/middleware/cache.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import env from '../env';
|
||||
import { getCacheKey } from '../utils/get-cache-key';
|
||||
|
||||
import cache from '../cache';
|
||||
|
||||
const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) => {
|
||||
if (req.method.toLowerCase() !== 'get') return next();
|
||||
if (env.CACHE_ENABLED !== true) return next();
|
||||
if (!cache) return next();
|
||||
|
||||
const key = getCacheKey(req);
|
||||
const cachedData = await cache.get(key);
|
||||
|
||||
if (cachedData) {
|
||||
return res.json(cachedData);
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
export default checkCacheMiddleware;
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
RateLimiterMemcache,
|
||||
IRateLimiterOptions,
|
||||
IRateLimiterStoreOptions,
|
||||
RateLimiterStoreAbstract,
|
||||
} from 'rate-limiter-flexible';
|
||||
import env from '../env';
|
||||
import { getConfigFromEnv } from '../utils/get-config-from-env';
|
||||
|
||||
14
api/src/middleware/respond.ts
Normal file
14
api/src/middleware/respond.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { RequestHandler } from "express";
|
||||
import asyncHandler from "express-async-handler";
|
||||
import env from "../env";
|
||||
import { getCacheKey } from "../utils/get-cache-key";
|
||||
import cache from '../cache';
|
||||
|
||||
export const respond: RequestHandler = asyncHandler(async (req, res) => {
|
||||
if (req.method.toLowerCase() === 'get' && env.CACHE_ENABLED === true && cache) {
|
||||
const key = getCacheKey(req);
|
||||
await cache.set(key, res.locals.payload);
|
||||
}
|
||||
|
||||
return res.json(res.locals.payload);
|
||||
});
|
||||
@@ -57,6 +57,7 @@ const sanitizeQuery: RequestHandler = (req, res, next) => {
|
||||
}
|
||||
|
||||
req.sanitizedQuery = query;
|
||||
Object.freeze(req.sanitizedQuery);
|
||||
return next();
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import SchemaInspector from 'knex-schema-inspector';
|
||||
import FieldsService from '../services/fields';
|
||||
import { omit } from 'lodash';
|
||||
import ItemsService from '../services/items';
|
||||
import cache from '../cache';
|
||||
|
||||
export default class CollectionsService {
|
||||
knex: Knex;
|
||||
@@ -85,6 +86,10 @@ export default class CollectionsService {
|
||||
}
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return Array.isArray(data) ? createdCollections : createdCollections[0];
|
||||
}
|
||||
|
||||
@@ -233,6 +238,10 @@ export default class CollectionsService {
|
||||
|
||||
await collectionItemsService.update(collectionUpdates);
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return key!;
|
||||
}
|
||||
|
||||
@@ -296,6 +305,10 @@ export default class CollectionsService {
|
||||
await this.knex.schema.dropTable(collectionKey);
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import ItemsService from '../services/items';
|
||||
import { ColumnBuilder } from 'knex';
|
||||
import getLocalType from '../utils/get-local-type';
|
||||
import { types } from '../types';
|
||||
import { FieldNotFoundException, ForbiddenException } from '../exceptions';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import Knex, { CreateTableBuilder } from 'knex';
|
||||
import PayloadService from '../services/payload';
|
||||
import getDefaultValue from '../utils/get-default-value';
|
||||
import cache from '../cache';
|
||||
|
||||
type RawField = Partial<Field> & { field: string; type: typeof types[number] };
|
||||
|
||||
@@ -205,6 +206,10 @@ export default class FieldsService {
|
||||
field: field.field,
|
||||
});
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** @todo research how to make this happen in SQLite / Redshift */
|
||||
@@ -272,6 +277,10 @@ export default class FieldsService {
|
||||
}
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return field.field;
|
||||
}
|
||||
|
||||
@@ -309,6 +318,10 @@ export default class FieldsService {
|
||||
.where({ one_collection: collection, one_field: field });
|
||||
}
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public addColumnToTable(table: CreateTableBuilder, field: Field) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import parseIPTC from '../utils/parse-iptc';
|
||||
import path from 'path';
|
||||
import { AbstractServiceOptions, File, PrimaryKey } from '../types';
|
||||
import { clone } from 'lodash';
|
||||
import cache from '../cache';
|
||||
|
||||
export default class FilesService extends ItemsService {
|
||||
constructor(options?: AbstractServiceOptions) {
|
||||
@@ -77,6 +78,10 @@ export default class FilesService extends ItemsService {
|
||||
const sudoService = new ItemsService('directus_files');
|
||||
await sudoService.update(payload, primaryKey);
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return primaryKey;
|
||||
}
|
||||
|
||||
@@ -97,6 +102,10 @@ export default class FilesService extends ItemsService {
|
||||
|
||||
await super.delete(keys);
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
AbstractServiceOptions,
|
||||
} from '../types';
|
||||
import Knex from 'knex';
|
||||
import cache from '../cache';
|
||||
|
||||
import PayloadService from './payload';
|
||||
import AuthorizationService from './authorization';
|
||||
@@ -145,6 +146,10 @@ export default class ItemsService implements AbstractService {
|
||||
await trx.insert(revisionRecords).into('directus_revisions');
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return primaryKeys;
|
||||
});
|
||||
|
||||
@@ -172,6 +177,7 @@ export default class ItemsService implements AbstractService {
|
||||
query: Query = {},
|
||||
action: PermissionsAction = 'read'
|
||||
): Promise<Item | Item[]> {
|
||||
query = clone(query);
|
||||
const schemaInspector = SchemaInspector(this.knex);
|
||||
const primaryKeyField = await schemaInspector.primary(this.collection);
|
||||
const keys = Array.isArray(key) ? key : [key];
|
||||
@@ -301,6 +307,10 @@ export default class ItemsService implements AbstractService {
|
||||
}
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -359,10 +369,15 @@ export default class ItemsService implements AbstractService {
|
||||
}
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
async readSingleton(query: Query) {
|
||||
query = clone(query);
|
||||
const schemaInspector = SchemaInspector(this.knex);
|
||||
query.limit = 1;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import { Accountability, PrimaryKey, Item, AbstractServiceOptions } from '../types';
|
||||
import Knex from 'knex';
|
||||
import env from '../env';
|
||||
import cache from '../cache';
|
||||
|
||||
export default class UsersService extends ItemsService {
|
||||
knex: Knex;
|
||||
@@ -42,6 +43,10 @@ export default class UsersService extends ItemsService {
|
||||
}
|
||||
}
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
|
||||
return this.service.update(data, key as any);
|
||||
}
|
||||
|
||||
@@ -78,6 +83,10 @@ export default class UsersService extends ItemsService {
|
||||
await this.knex('directus_users')
|
||||
.update({ password: passwordHashed, status: 'active' })
|
||||
.where({ id: user.id });
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async requestPasswordReset(email: string) {
|
||||
@@ -114,6 +123,10 @@ export default class UsersService extends ItemsService {
|
||||
await this.knex('directus_users')
|
||||
.update({ password: passwordHashed, status: 'active' })
|
||||
.where({ id: user.id });
|
||||
|
||||
if (cache) {
|
||||
await cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async enableTFA(pk: string) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Accountability,
|
||||
} from '../types';
|
||||
import database from '../database';
|
||||
import { clone } from 'lodash';
|
||||
|
||||
export default async function getASTFromQuery(
|
||||
collection: string,
|
||||
@@ -19,6 +20,7 @@ export default async function getASTFromQuery(
|
||||
accountability?: Accountability | null,
|
||||
action?: PermissionsAction
|
||||
): Promise<AST> {
|
||||
query = clone(query);
|
||||
/**
|
||||
* we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every
|
||||
* requested field. @todo look into utilizing graphql/dataloader for this purpose
|
||||
|
||||
8
api/src/utils/get-cache-key.ts
Normal file
8
api/src/utils/get-cache-key.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Request } from "express";
|
||||
import url from 'url';
|
||||
|
||||
export function getCacheKey(req: Request) {
|
||||
const path = url.parse(req.originalUrl).pathname;
|
||||
const key = `${req.accountability?.user || 'null'}-${path}-${JSON.stringify(req.sanitizedQuery)}`;
|
||||
return key;
|
||||
}
|
||||
@@ -158,6 +158,7 @@ body {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
flex-basis: 220px;
|
||||
flex-shrink: 0;
|
||||
width: 220px;
|
||||
@@ -165,7 +166,6 @@ body {
|
||||
background-color: var(--background-normal);
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--slow) var(--transition-out);
|
||||
z-index: 2;
|
||||
|
||||
&.active {
|
||||
transform: translateX(0);
|
||||
@@ -183,6 +183,7 @@ body {
|
||||
|
||||
@include breakpoint(medium) {
|
||||
--v-overlay-z-index: none;
|
||||
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
width: 'half',
|
||||
},
|
||||
schema: {
|
||||
default_value: 'en-US'
|
||||
default_value: 'en-US',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -90,7 +90,7 @@
|
||||
interface: 'text-input',
|
||||
width: 'half',
|
||||
options: {
|
||||
placeholder: 'Enter a translation...'
|
||||
placeholder: 'Enter a translation...',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user