mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add support for SEARCH method (#5183)
* Add search method support for advanced get * Add docs for SEARCH
This commit is contained in:
@@ -90,8 +90,8 @@ export default async function createApp() {
|
||||
return next();
|
||||
});
|
||||
});
|
||||
|
||||
app.use(cookieParser())
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(extractToken);
|
||||
|
||||
|
||||
@@ -6,36 +6,45 @@ import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
import Joi from 'joi';
|
||||
import { validateBatch } from '../middleware/validate-batch';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_activity'));
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new ActivityService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
res.locals.payload = {
|
||||
data: records || null,
|
||||
meta,
|
||||
};
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
const meta = await metaService.getMetaForQuery('directus_activity', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = {
|
||||
data: result,
|
||||
meta,
|
||||
};
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
router.get('/', readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -3,6 +3,8 @@ import asyncHandler from '../utils/async-handler';
|
||||
import { CollectionsService, MetaService } from '../services';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { validateBatch } from '../middleware/validate-batch';
|
||||
import { Item } from '../types';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -29,26 +31,33 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const collectionsService = new CollectionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const collections = await collectionsService.readByQuery();
|
||||
const meta = await metaService.getMetaForQuery('directus_collections', {});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
res.locals.payload = { data: collections || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
let result: Item[] = [];
|
||||
|
||||
if (req.body.keys) {
|
||||
result = await collectionsService.readMany(req.body.keys);
|
||||
} else {
|
||||
result = await collectionsService.readByQuery();
|
||||
}
|
||||
|
||||
const meta = await metaService.getMetaForQuery('directus_collections', {});
|
||||
|
||||
res.locals.payload = { data: result, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:collection',
|
||||
|
||||
@@ -6,12 +6,7 @@ import { File, PrimaryKey } from '../types';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import env from '../env';
|
||||
import Joi from 'joi';
|
||||
import {
|
||||
InvalidPayloadException,
|
||||
ForbiddenException,
|
||||
FailedValidationException,
|
||||
ServiceUnavailableException,
|
||||
} from '../exceptions';
|
||||
import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import path from 'path';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
@@ -178,27 +173,35 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FilesService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new FilesService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: result, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import express from 'express';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
import { FoldersService, MetaService } from '../services';
|
||||
import { ForbiddenException, InvalidPayloadException, FailedValidationException } from '../exceptions';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { PrimaryKey } from '../types';
|
||||
import Joi from 'joi';
|
||||
import { validateBatch } from '../middleware/validate-batch';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -51,26 +50,34 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new FoldersService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
const meta = await metaService.getMetaForQuery('directus_files', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: result, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import express from 'express';
|
||||
import express, { RequestHandler } from 'express';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
import collectionExists from '../middleware/collection-exists';
|
||||
import { ItemsService, MetaService } from '../services';
|
||||
import { RouteNotFoundException, ForbiddenException, FailedValidationException } from '../exceptions';
|
||||
import { RouteNotFoundException, ForbiddenException } from '../exceptions';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { InvalidPayloadException } from '../exceptions';
|
||||
import { PrimaryKey } from '../types';
|
||||
import Joi from 'joi';
|
||||
import { validateBatch } from '../middleware/validate-batch';
|
||||
|
||||
const router = express.Router();
|
||||
@@ -57,37 +55,41 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:collection',
|
||||
collectionExists,
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
if (req.params.collection.startsWith('directus_')) throw new ForbiddenException();
|
||||
|
||||
const service = new ItemsService(req.collection, {
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const service = new ItemsService(req.collection, {
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = req.singleton
|
||||
? await service.readSingleton(req.sanitizedQuery)
|
||||
: await service.readByQuery(req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
res.locals.payload = {
|
||||
meta: meta,
|
||||
data: records || null,
|
||||
};
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = {
|
||||
meta: meta,
|
||||
data: result,
|
||||
};
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.search('/:collection', collectionExists, validateBatch('read'), readHandler, respond);
|
||||
router.get('/:collection', collectionExists, readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:collection/:pk',
|
||||
|
||||
@@ -49,27 +49,35 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new PermissionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const item = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
res.locals.payload = { data: item || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
const meta = await metaService.getMetaForQuery('directus_permissions', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: result, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -50,26 +50,34 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new PresetsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
|
||||
let result;
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
if (req.singleton) {
|
||||
result = await service.readSingleton(req.sanitizedQuery);
|
||||
} else if (req.body.keys) {
|
||||
result = await service.readMany(req.body.keys, req.sanitizedQuery);
|
||||
} else {
|
||||
result = await service.readByQuery(req.sanitizedQuery);
|
||||
}
|
||||
|
||||
const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: result, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -50,27 +50,26 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new RelationsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -3,31 +3,31 @@ import asyncHandler from '../utils/async-handler';
|
||||
import { RevisionsService, MetaService } from '../services';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { validateBatch } from '../middleware/validate-batch';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use(useCollection('directus_revisions'));
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RevisionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new RevisionsService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_revisions', req.sanitizedQuery);
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_revisions', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -50,26 +50,25 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new RolesService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_roles', req.sanitizedQuery);
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_roles', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -51,26 +51,25 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new UsersService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const item = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_users', req.sanitizedQuery);
|
||||
const item = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery('directus_users', req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: item || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = { data: item || null, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/me',
|
||||
|
||||
@@ -50,26 +50,25 @@ router.post(
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const readHandler = asyncHandler(async (req, res, next) => {
|
||||
const service = new WebhooksService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
const metaService = new MetaService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
const records = await service.readByQuery(req.sanitizedQuery);
|
||||
const meta = await metaService.getMetaForQuery(req.collection, req.sanitizedQuery);
|
||||
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
res.locals.payload = { data: records || null, meta };
|
||||
return next();
|
||||
});
|
||||
|
||||
router.get('/', validateBatch('read'), readHandler, respond);
|
||||
router.search('/', validateBatch('read'), readHandler, respond);
|
||||
|
||||
router.get(
|
||||
'/:pk',
|
||||
|
||||
@@ -2,25 +2,36 @@ import { RequestHandler } from 'express';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
import Joi from 'joi';
|
||||
import { FailedValidationException, InvalidPayloadException } from '../exceptions';
|
||||
import { sanitizeQuery } from '../utils/sanitize-query';
|
||||
|
||||
export const validateBatch = (scope: 'update' | 'delete'): RequestHandler =>
|
||||
export const validateBatch = (scope: 'read' | 'update' | 'delete'): RequestHandler =>
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (req.method.toLowerCase() === 'get') {
|
||||
req.body = {};
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!req.body) throw new InvalidPayloadException('Payload in body is required');
|
||||
|
||||
let batchSchema = Joi.object()
|
||||
.keys({
|
||||
keys: Joi.array().items(Joi.alternatives(Joi.string(), Joi.number())),
|
||||
query: Joi.object().unknown(),
|
||||
})
|
||||
.xor('query', 'keys');
|
||||
// Every cRUD action has either keys or query
|
||||
let batchSchema = Joi.object().keys({
|
||||
keys: Joi.array().items(Joi.alternatives(Joi.string(), Joi.number())),
|
||||
query: Joi.object().unknown(),
|
||||
});
|
||||
|
||||
// In reads, you can't combine the two, and 1 of the two at least is required
|
||||
if (scope !== 'read') {
|
||||
batchSchema = batchSchema.xor('query', 'keys');
|
||||
}
|
||||
|
||||
// In updates, we add a required `data` that holds the update payload
|
||||
if (scope === 'update') {
|
||||
batchSchema = batchSchema.keys({
|
||||
data: Joi.object().unknown().required(),
|
||||
});
|
||||
}
|
||||
|
||||
// Accept an array of primary keys as batch payload for deletes
|
||||
// In deletes, we want to keep supporting an array of just primary keys
|
||||
if (scope === 'delete' && Array.isArray(req.body)) {
|
||||
return next();
|
||||
}
|
||||
@@ -31,5 +42,10 @@ export const validateBatch = (scope: 'update' | 'delete'): RequestHandler =>
|
||||
throw new FailedValidationException(error.details[0]);
|
||||
}
|
||||
|
||||
// In reads, the query in the body should override the query params for searching
|
||||
if (scope === 'read' && req.body.query) {
|
||||
req.sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
@@ -307,10 +307,14 @@ export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractSer
|
||||
const queryWithKeys = {
|
||||
...query,
|
||||
filter: {
|
||||
...(query.filter || {}),
|
||||
[primaryKeyField]: {
|
||||
_in: keys,
|
||||
},
|
||||
_and: [
|
||||
query.filter || {},
|
||||
{
|
||||
[primaryKeyField]: {
|
||||
_in: keys,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@ 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)}`;
|
||||
|
||||
let key: string;
|
||||
|
||||
if (path?.includes('/graphql')) {
|
||||
key = `${req.accountability?.user || 'null'}-${path}-${JSON.stringify(req.params.query)}`;
|
||||
} else {
|
||||
key = `${req.accountability?.user || 'null'}-${path}-${JSON.stringify(req.sanitizedQuery)}`;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import logger from '../logger';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { flatten, set, merge, get } from 'lodash';
|
||||
|
||||
export function sanitizeQuery(rawQuery: Record<string, any>, accountability: Accountability | null) {
|
||||
export function sanitizeQuery(rawQuery: Record<string, any>, accountability?: Accountability | null) {
|
||||
const query: Query = {};
|
||||
|
||||
if (rawQuery.limit !== undefined) {
|
||||
@@ -129,7 +129,7 @@ function sanitizeMeta(rawMeta: any) {
|
||||
return [rawMeta];
|
||||
}
|
||||
|
||||
function sanitizeDeep(deep: Record<string, any>, accountability: Accountability | null) {
|
||||
function sanitizeDeep(deep: Record<string, any>, accountability?: Accountability | null) {
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
if (typeof deep === 'string') {
|
||||
|
||||
@@ -107,6 +107,10 @@ pre
|
||||
pre,
|
||||
pre[class*="language-"]
|
||||
margin-top 0
|
||||
div[class*="language"] + p
|
||||
text-align right
|
||||
margin-top -11px
|
||||
font-size 14px
|
||||
|
||||
@media (min-width: 1000px)
|
||||
.two-up
|
||||
|
||||
@@ -145,3 +145,47 @@ foreign key. This means that your data will never suddenly disappear, but it als
|
||||
orphaned items.
|
||||
|
||||
:::
|
||||
|
||||
## SEARCH HTTP Method
|
||||
|
||||
When using the REST API to read multiple items by (very) advanced filters, you might run into the issue where the URL
|
||||
simply can't hold enough data to include the full query structure. In those cases, you can use the SEARCH http method as
|
||||
a drop-in replacement for GET, where you're allowed to put the query into the request body as follows:
|
||||
|
||||
**Before:**
|
||||
|
||||
```
|
||||
GET /items/articles?filter[title][_eq]=Hello World
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```json
|
||||
SEARCH /items/articles
|
||||
|
||||
{
|
||||
"query": {
|
||||
"filter": {
|
||||
"title": {
|
||||
"_eq": "Hello World"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There's a lot of discussion around whether or not to put a body in a GET request, to use POSTs to create search queries,
|
||||
or to rely on a different method altogether. As of right now, we've chosen
|
||||
[to align with IETF's _HTTP SEARCH Method_ specification](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/).
|
||||
While we recognize this is still a draft spec, the SEARCH method has been used extensively before in the WebDAV world
|
||||
([spec](https://tools.ietf.org/html/rfc5323)), and compared to the other available options, it feels like the "cleanest"
|
||||
and most correct to handle this moving forward. As with everything else, if you have any ideas, opinions, or concerns,
|
||||
[we'd love to hear your thoughts](http://github.com/directus/directus/discussions/new).
|
||||
|
||||
Useful reading:
|
||||
|
||||
- [_HTTP SEARCH Method_ (IETF, 2021)](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/)
|
||||
- [_Defining a new HTTP method: HTTP SEARCH_ (Tim Perry, 2021)](https://httptoolkit.tech/blog/http-search-method/)
|
||||
- [_HTTP GET with request body_ (StackOverflow, 2009 and ongoing)](https://stackoverflow.com/questions/978061/http-get-with-request-body)
|
||||
- [_Elastic Search GET body usage_ (elastic, n.d.)](https://www.elastic.co/guide/en/elasticsearch/guide/current/_empty_search.html)
|
||||
- [_Dropbox starts using POST, and why this is poor API design._ (Evert Pot, 2015)](https://evertpot.com/dropbox-post-api/)
|
||||
|
||||
@@ -72,7 +72,8 @@ will be an empty array.
|
||||
|
||||
#### Singleton
|
||||
|
||||
If your collection is a singleton, this endpoint will return the item.
|
||||
If your collection is a singleton, this endpoint will return the item. If the item doesn't exist in the database, the
|
||||
default values will be returned.
|
||||
|
||||
</div>
|
||||
<div class="right">
|
||||
@@ -81,8 +82,11 @@ If your collection is a singleton, this endpoint will return the item.
|
||||
|
||||
```
|
||||
GET /items/:collection
|
||||
SEARCH /items/:collection
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
##### Example
|
||||
|
||||
```
|
||||
|
||||
@@ -103,8 +103,11 @@ available, data will be an empty array.
|
||||
|
||||
```
|
||||
GET /activity
|
||||
SEARCH /activity
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -152,8 +152,11 @@ An array of [collection objects](#the-collection-object).
|
||||
|
||||
```
|
||||
GET /collections
|
||||
SEARCH /collections
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -150,8 +150,11 @@ will be an empty array.
|
||||
|
||||
```
|
||||
GET /files
|
||||
SEARCH /files
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -77,8 +77,11 @@ data will be an empty array.
|
||||
|
||||
```
|
||||
GET /folders
|
||||
SEARCH /folders
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -112,8 +112,11 @@ available, data will be an empty array.
|
||||
|
||||
```
|
||||
GET /permissions
|
||||
SEARCH /permissions
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -126,8 +126,11 @@ data will be an empty array.
|
||||
|
||||
```
|
||||
GET /presets
|
||||
SEARCH /presets
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -102,8 +102,11 @@ available, data will be an empty array.
|
||||
|
||||
```
|
||||
GET /relations
|
||||
SEARCH /relations
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -102,8 +102,11 @@ available, data will be an empty array.
|
||||
|
||||
```
|
||||
GET /revisions
|
||||
SEARCH /revisions
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -107,8 +107,11 @@ will be an empty array.
|
||||
|
||||
```
|
||||
GET /roles
|
||||
SEARCH /roles
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -121,8 +121,11 @@ will be an empty array.
|
||||
|
||||
```
|
||||
GET /users
|
||||
SEARCH /users
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
@@ -97,8 +97,11 @@ available, data will be an empty array.
|
||||
|
||||
```
|
||||
GET /webhooks
|
||||
SEARCH /webhooks
|
||||
```
|
||||
|
||||
[Learn more about SEARCH ->](/reference/api/introduction/#search-http-method)
|
||||
|
||||
### GraphQL
|
||||
|
||||
```graphql
|
||||
|
||||
Reference in New Issue
Block a user