diff --git a/api/package-lock.json b/api/package-lock.json index cca8a84d4d..0193a75196 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -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", diff --git a/api/package.json b/api/package.json index 70e4e81b4c..2a3f485166 100644 --- a/api/package.json +++ b/api/package.json @@ -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", diff --git a/api/src/app.ts b/api/src/app.ts index b1c2f8236d..cef1c0a8eb 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -10,6 +10,8 @@ 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 activityRouter from './controllers/activity'; @@ -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); diff --git a/api/src/cache.ts b/api/src/cache.ts new file mode 100644 index 0000000000..aa8f21947b --- /dev/null +++ b/api/src/cache.ts @@ -0,0 +1,12 @@ +import env from './env'; +import Keyv from 'keyv'; +import { validateEnv } from './utils/validate-env'; + +let cache: Keyv | null = null; + +if (env.CACHE_ENABLED === true) { + validateEnv(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']); + cache = new Keyv({ namespace: process.env.CACHE_NAMESPACE }); +} + +export default cache; diff --git a/api/src/controllers/activity.ts b/api/src/controllers/activity.ts index 1cd9e9c10e..2478b1bbf9 100644 --- a/api/src/controllers/activity.ts +++ b/api/src/controllers/activity.ts @@ -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; diff --git a/api/src/controllers/assets.ts b/api/src/controllers/assets.ts index 498bc0952a..bbcff8e893 100644 --- a/api/src/controllers/assets.ts +++ b/api/src/controllers/assets.ts @@ -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'; diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index 0c1709baaa..5d09b95f91 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -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; diff --git a/api/src/controllers/collections.ts b/api/src/controllers/collections.ts index f4c2378b74..c31246f9ad 100644 --- a/api/src/controllers/collections.ts +++ b/api/src/controllers/collections.ts @@ -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, {}); - res.json({ data: collections || null, meta }); - }), + const meta = await metaService.getMetaForQuery('directus_collections', {}); + + 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(); }) ); diff --git a/api/src/controllers/extensions.ts b/api/src/controllers/extensions.ts index 7a1946aace..62a3b5831f 100644 --- a/api/src/controllers/extensions.ts +++ b/api/src/controllers/extensions.ts @@ -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; diff --git a/api/src/controllers/fields.ts b/api/src/controllers/fields.ts index 3ddb136478..53b573aa18 100644 --- a/api/src/controllers/fields.ts +++ b/api/src/controllers/fields.ts @@ -6,47 +6,46 @@ import { schemaInspector } from '../database'; import { InvalidPayloadException, ForbiddenException } from '../exceptions'; import Joi from 'joi'; import { Field } from '../types/field'; -import useCollection from '../middleware/use-collection'; import { types } from '../types'; const router = Router(); 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(); }), ); @@ -67,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); @@ -83,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) @@ -107,16 +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'), // @todo: validate field - asyncHandler(async (req, res) => { + asyncHandler(async (req, res, next) => { const service = new FieldsService({ accountability: req.accountability }); const fieldData: Partial & { field: string; type: typeof types[number] } = req.body; @@ -126,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; diff --git a/api/src/controllers/files.ts b/api/src/controllers/files.ts index 3819ffa765..a9b9503433 100644 --- a/api/src/controllers/files.ts +++ b/api/src/controllers/files.ts @@ -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(); }) ); diff --git a/api/src/controllers/folders.ts b/api/src/controllers/folders.ts index e27640a392..c66ef2a8ea 100644 --- a/api/src/controllers/folders.ts +++ b/api/src/controllers/folders.ts @@ -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; diff --git a/api/src/controllers/items.ts b/api/src/controllers/items.ts index cf2aa4ec9f..233d7fe2c5 100644 --- a/api/src/controllers/items.ts +++ b/api/src/controllers/items.ts @@ -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; diff --git a/api/src/controllers/permissions.ts b/api/src/controllers/permissions.ts index 81d9198ca5..2478c6298f 100644 --- a/api/src/controllers/permissions.ts +++ b/api/src/controllers/permissions.ts @@ -1,45 +1,40 @@ 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 { InvalidCredentialsException } from '../exceptions'; -import env from '../env'; 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(); } @@ -56,41 +51,42 @@ 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) => { 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; diff --git a/api/src/controllers/presets.ts b/api/src/controllers/presets.ts index f1e4f69bd5..ec56310c9f 100644 --- a/api/src/controllers/presets.ts +++ b/api/src/controllers/presets.ts @@ -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(); }) ); diff --git a/api/src/controllers/relations.ts b/api/src/controllers/relations.ts index a890ea8926..03f8f2dc12 100644 --- a/api/src/controllers/relations.ts +++ b/api/src/controllers/relations.ts @@ -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; diff --git a/api/src/controllers/revisions.ts b/api/src/controllers/revisions.ts index 1b278e8df7..23d7a08490 100644 --- a/api/src/controllers/revisions.ts +++ b/api/src/controllers/revisions.ts @@ -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; diff --git a/api/src/controllers/roles.ts b/api/src/controllers/roles.ts index d85777b6ae..c7fcc59da2 100644 --- a/api/src/controllers/roles.ts +++ b/api/src/controllers/roles.ts @@ -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; diff --git a/api/src/controllers/server.ts b/api/src/controllers/server.ts index b9b32e3cb6..cd0a5a6244 100644 --- a/api/src/controllers/server.ts +++ b/api/src/controllers/server.ts @@ -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; diff --git a/api/src/controllers/settings.ts b/api/src/controllers/settings.ts index 6903c4ae3c..b886709ed2 100644 --- a/api/src/controllers/settings.ts +++ b/api/src/controllers/settings.ts @@ -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(); }) ); diff --git a/api/src/controllers/users.ts b/api/src/controllers/users.ts index 4ee5f1df21..c7aaeec08f 100644 --- a/api/src/controllers/users.ts +++ b/api/src/controllers/users.ts @@ -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,24 @@ 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) => { 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 +69,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 +88,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 +122,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 +139,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 +158,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 +184,7 @@ router.post( } await service.disableTFA(req.accountability.user); - - return res.status(200).end(); + return next(); }) ); diff --git a/api/src/controllers/webhooks.ts b/api/src/controllers/webhooks.ts index 45e61c02ca..f13c09136f 100644 --- a/api/src/controllers/webhooks.ts +++ b/api/src/controllers/webhooks.ts @@ -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; diff --git a/api/src/env.ts b/api/src/env.ts index f45cc2c8d1..6ca820ccd9 100644 --- a/api/src/env.ts +++ b/api/src/env.ts @@ -30,6 +30,9 @@ const defaults: Record = { CORS_ENABLED: false, CACHE_ENABLED: false, + CACHE_STORE: false, + CACHE_TTL: '30m', + CACHE_NAMESPACE: 'system-cache', OAUTH_PROVIDERS: '', diff --git a/api/src/middleware/cache.ts b/api/src/middleware/cache.ts index 4e9d5ddacf..5d66e428ae 100644 --- a/api/src/middleware/cache.ts +++ b/api/src/middleware/cache.ts @@ -1,12 +1,25 @@ import { RequestHandler } from 'express'; import asyncHandler from 'express-async-handler'; -import CacheService from '../services/cache'; 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(); - return next(); + const key = getCacheKey(req); + const cachedData = await cache.get(key); + + console.log(key); + + if (cachedData) { + return res.json(cachedData); + } else { + return next(); + } }); export default checkCacheMiddleware; diff --git a/api/src/middleware/respond.ts b/api/src/middleware/respond.ts new file mode 100644 index 0000000000..ab41becf7f --- /dev/null +++ b/api/src/middleware/respond.ts @@ -0,0 +1,15 @@ +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 (env.CACHE_ENABLED === true && cache) { + const key = getCacheKey(req); + await cache.set(key, res.locals.payload); + console.log(key); + } + + return res.json(res.locals.payload); +}); diff --git a/api/src/services/cache.ts b/api/src/services/cache.ts deleted file mode 100644 index 023535367f..0000000000 --- a/api/src/services/cache.ts +++ /dev/null @@ -1,5 +0,0 @@ -import env from '../env'; - -export default class CacheService { - -} diff --git a/api/src/utils/get-cache-key.ts b/api/src/utils/get-cache-key.ts new file mode 100644 index 0000000000..bcb8b4038b --- /dev/null +++ b/api/src/utils/get-cache-key.ts @@ -0,0 +1,6 @@ +import { Request } from "express"; + +export function getCacheKey(req: Request) { + const key = `${req.accountability?.user || 'null'}-${req.originalUrl}-${JSON.stringify(req.sanitizedQuery)}`; + return key; +}