mirror of
https://github.com/directus/directus.git
synced 2026-01-24 16:57:55 -05:00
Merge pull request #412 from directus/deep
Add `deep` query param support
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export type Query = {
|
||||
meta?: Meta[];
|
||||
search?: string;
|
||||
export?: 'json' | 'csv';
|
||||
deep?: Record<string, Query>;
|
||||
};
|
||||
|
||||
export type Sort = {
|
||||
|
||||
@@ -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
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user