Merge pull request #412 from directus/deep

Add `deep` query param support
This commit is contained in:
Rijk van Zanten
2020-09-23 18:00:17 -04:00
committed by GitHub
4 changed files with 63 additions and 38 deletions

View File

@@ -41,8 +41,8 @@ export default async function runAST(originalAST: AST, options?: RunASTOptions)
// all nested items for all parent items at once. Because of this, we can't limit that query
// to the "standard" item limit. Instead of _n_ nested items per parent item, it would mean
// that there's _n_ items, which are then divided on the parent items. (no good)
if (isO2M(nestedAST) && typeof query.limit === 'number') {
tempLimit = query.limit;
if (isO2M(nestedAST) && typeof nestedAST.query.limit === 'number') {
tempLimit = nestedAST.query.limit;
nestedAST.query.limit = -1;
}

View File

@@ -12,62 +12,84 @@ const sanitizeQuery: RequestHandler = (req, res, next) => {
req.sanitizedQuery = {};
if (!req.query) return;
const query: Query = {
fields: sanitizeFields(req.query.fields) || ['*'],
};
req.sanitizedQuery = sanitize(
{
fields: req.query.fields || '*',
...req.query
},
req.accountability || null
);
if (req.query.limit !== undefined) {
const limit = sanitizeLimit(req.query.limit);
Object.freeze(req.sanitizedQuery);
return next();
};
function sanitize(rawQuery: Record<string, any>, accountability: Accountability | null) {
const query: Query = {};
if (rawQuery.limit !== undefined) {
const limit = sanitizeLimit(rawQuery.limit);
if (typeof limit === 'number') {
query.limit = limit;
}
}
if (req.query.sort) {
query.sort = sanitizeSort(req.query.sort);
if (rawQuery.fields) {
query.fields = sanitizeFields(rawQuery.fields);
}
if (req.query.filter) {
query.filter = sanitizeFilter(req.query.filter, req.accountability || null);
if (rawQuery.sort) {
query.sort = sanitizeSort(rawQuery.sort);
}
if (req.query.limit == '-1') {
if (rawQuery.filter) {
query.filter = sanitizeFilter(rawQuery.filter, accountability || null);
}
if (rawQuery.limit == '-1') {
delete query.limit;
}
if (req.query.offset) {
query.offset = sanitizeOffset(req.query.offset);
if (rawQuery.offset) {
query.offset = sanitizeOffset(rawQuery.offset);
}
if (req.query.page) {
query.page = sanitizePage(req.query.page);
if (rawQuery.page) {
query.page = sanitizePage(rawQuery.page);
}
if (req.query.single) {
query.single = sanitizeSingle(req.query.single);
if (rawQuery.single) {
query.single = sanitizeSingle(rawQuery.single);
}
if (req.query.meta) {
query.meta = sanitizeMeta(req.query.meta);
if (rawQuery.meta) {
query.meta = sanitizeMeta(rawQuery.meta);
}
if (req.query.search && typeof req.query.search === 'string') {
query.search = req.query.search;
if (rawQuery.search && typeof rawQuery.search === 'string') {
query.search = rawQuery.search;
}
if (
req.query.export &&
typeof req.query.export === 'string' &&
['json', 'csv'].includes(req.query.export)
rawQuery.export &&
typeof rawQuery.export === 'string' &&
['json', 'csv'].includes(rawQuery.export)
) {
query.export = req.query.export as 'json' | 'csv';
query.export = rawQuery.export as 'json' | 'csv';
}
req.sanitizedQuery = query;
Object.freeze(req.sanitizedQuery);
return next();
};
if (rawQuery.deep as Record<string, any>) {
if (!query.deep) query.deep = {};
for (const [field, deepRawQuery] of Object.entries(rawQuery.deep)) {
query.deep[field] = sanitize(deepRawQuery as any, accountability);
}
}
return query;
}
export default sanitizeQuery;

View File

@@ -11,6 +11,7 @@ export type Query = {
meta?: Meta[];
search?: string;
export?: 'json' | 'csv';
deep?: Record<string, Query>;
};
export type Sort = {

View File

@@ -14,6 +14,7 @@ import {
import database from '../database';
import { clone } from 'lodash';
import Knex from 'knex';
import SchemaInspector from 'knex-schema-inspector';
type GetASTOptions = {
accountability?: Accountability | null;
@@ -25,14 +26,13 @@ export default async function getASTFromQuery(
collection: string,
query: Query,
options?: GetASTOptions
// accountability?: Accountability | null,
// action?: PermissionsAction
): Promise<AST> {
query = clone(query);
const accountability = options?.accountability;
const action = options?.action || 'read';
const knex = options?.knex || database;
const schemaInspector = SchemaInspector(knex);
/**
* we might not need al this info at all times, but it's easier to fetch it all once, than trying to fetch it for every
@@ -56,11 +56,13 @@ export default async function getASTFromQuery(
};
const fields = query.fields || ['*'];
const deep = query.deep || {};
// Prevent fields from showing up in the query object
// Prevent fields/deep from showing up in the query object in further use
delete query.fields;
delete query.deep;
ast.children = parseFields(collection, fields).filter(filterEmptyChildCollections);
ast.children = (await parseFields(collection, fields, deep)).filter(filterEmptyChildCollections);
return ast;
@@ -120,7 +122,7 @@ export default async function getASTFromQuery(
return fields;
}
function parseFields(parentCollection: string, fields: string[]) {
async function parseFields(parentCollection: string, fields: string[], deep?: Record<string, Query>) {
fields = convertWildcards(parentCollection, fields);
if (!fields) return [];
@@ -157,10 +159,10 @@ export default async function getASTFromQuery(
type: 'collection',
name: relatedCollection,
fieldKey: relationalField,
parentKey: 'id' /** @todo this needs to come from somewhere real */,
parentKey: await schemaInspector.primary(parentCollection),
relation: relation,
query: {} /** @todo inject nested query here: ?deep[foo]=bar */,
children: parseFields(relatedCollection, nestedFields).filter(
query: deep?.[relationalField] || {},
children: (await parseFields(relatedCollection, nestedFields)).filter(
filterEmptyChildCollections
),
};