mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' into sdk
This commit is contained in:
@@ -12,6 +12,7 @@ import url from 'url';
|
||||
import path from 'path';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -32,7 +33,7 @@ const multipartHandler = asyncHandler(async (req, res, next) => {
|
||||
* the row in directus_files async during the upload of the actual file.
|
||||
*/
|
||||
|
||||
let disk: string = (env.STORAGE_LOCATIONS as string).split(',')[0].trim();
|
||||
let disk: string = toArray(env.STORAGE_LOCATIONS)[0];
|
||||
let payload: Partial<File> = {};
|
||||
let fileCount = 0;
|
||||
|
||||
@@ -155,7 +156,7 @@ router.post(
|
||||
|
||||
const payload = {
|
||||
filename_download: filename,
|
||||
storage: (env.STORAGE_LOCATIONS as string).split(',')[0].trim(),
|
||||
storage: toArray(env.STORAGE_LOCATIONS)[0],
|
||||
type: fileResponse.headers['content-type'],
|
||||
title: formatTitle(filename),
|
||||
...(req.body.data || {}),
|
||||
|
||||
@@ -20,12 +20,12 @@ router.get('/ping', (req, res) => res.send('pong'));
|
||||
|
||||
router.get(
|
||||
'/info',
|
||||
(req, res, next) => {
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new ServerService({ accountability: req.accountability });
|
||||
const data = service.serverInfo();
|
||||
const data = await service.serverInfo();
|
||||
res.locals.payload = { data };
|
||||
return next();
|
||||
},
|
||||
}),
|
||||
respond
|
||||
);
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
table: directus_permissions
|
||||
|
||||
defaults:
|
||||
role: null
|
||||
collection: null
|
||||
action: null
|
||||
permissions: null
|
||||
validation: null
|
||||
presets: null
|
||||
fields: null
|
||||
limit: null
|
||||
|
||||
data:
|
||||
- collection: directus_settings
|
||||
action: read
|
||||
permissions: {}
|
||||
fields: 'project_name,project_logo,project_color,public_foreground,public_background,public_note,custom_css'
|
||||
@@ -63,11 +63,6 @@ function processValues(env: Record<string, any>) {
|
||||
if (value === 'false') env[key] = false;
|
||||
if (value === 'null') env[key] = null;
|
||||
if (isNaN(value) === false && value.length > 0) env[key] = Number(value);
|
||||
if (typeof value === 'string' && value.includes(','))
|
||||
env[key] = value
|
||||
.split(',')
|
||||
.map((val) => val.trim())
|
||||
.filter((val) => val);
|
||||
}
|
||||
|
||||
return env;
|
||||
|
||||
@@ -60,18 +60,28 @@ export async function sendInviteMail(email: string, url: string) {
|
||||
/**
|
||||
* @TODO pull this from directus_settings
|
||||
*/
|
||||
const projectName = 'directus';
|
||||
const projectName = 'Directus';
|
||||
|
||||
const html = await liquidEngine.renderFile('user-invitation', { email, url, projectName });
|
||||
await transporter.sendMail({ from: env.EMAIL_FROM, to: email, html: html });
|
||||
await transporter.sendMail({
|
||||
from: env.EMAIL_FROM,
|
||||
to: email,
|
||||
html: html,
|
||||
subject: `[${projectName}] You've been invited`,
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendPasswordResetMail(email: string, url: string) {
|
||||
/**
|
||||
* @TODO pull this from directus_settings
|
||||
*/
|
||||
const projectName = 'directus';
|
||||
const projectName = 'Directus';
|
||||
|
||||
const html = await liquidEngine.renderFile('password-reset', { email, url, projectName });
|
||||
await transporter.sendMail({ from: env.EMAIL_FROM, to: email, html: html });
|
||||
await transporter.sendMail({
|
||||
from: env.EMAIL_FROM,
|
||||
to: email,
|
||||
html: html,
|
||||
subject: `[${projectName}] Password Reset Request`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Action,
|
||||
Accountability,
|
||||
PermissionsAction,
|
||||
Item,
|
||||
Item as AnyItem,
|
||||
Query,
|
||||
PrimaryKey,
|
||||
AbstractService,
|
||||
@@ -26,7 +26,7 @@ import getDefaultValue from '../utils/get-default-value';
|
||||
import { InvalidPayloadException } from '../exceptions';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
|
||||
export class ItemsService implements AbstractService {
|
||||
export class ItemsService<Item extends AnyItem = AnyItem> implements AbstractService {
|
||||
collection: string;
|
||||
knex: Knex;
|
||||
accountability: Accountability | null;
|
||||
@@ -52,7 +52,7 @@ export class ItemsService implements AbstractService {
|
||||
const primaryKeyField = await this.schemaInspector.primary(this.collection);
|
||||
const columns = await this.schemaInspector.columns(this.collection);
|
||||
|
||||
let payloads = clone(toArray(data));
|
||||
let payloads: AnyItem[] = clone(toArray(data));
|
||||
|
||||
const savedPrimaryKeys = await this.knex.transaction(async (trx) => {
|
||||
const payloadService = new PayloadService(this.collection, {
|
||||
@@ -194,7 +194,7 @@ export class ItemsService implements AbstractService {
|
||||
return Array.isArray(data) ? savedPrimaryKeys : savedPrimaryKeys[0];
|
||||
}
|
||||
|
||||
async readByQuery(query: Query): Promise<null | Item | Item[]> {
|
||||
async readByQuery(query: Query): Promise<null | Partial<Item> | Partial<Item>[]> {
|
||||
const authorizationService = new AuthorizationService({
|
||||
accountability: this.accountability,
|
||||
knex: this.knex,
|
||||
@@ -210,20 +210,24 @@ export class ItemsService implements AbstractService {
|
||||
}
|
||||
|
||||
const records = await runAST(ast, { knex: this.knex });
|
||||
return records;
|
||||
return records as Partial<Item> | Partial<Item>[] | null;
|
||||
}
|
||||
|
||||
readByKey(
|
||||
keys: PrimaryKey[],
|
||||
query?: Query,
|
||||
action?: PermissionsAction
|
||||
): Promise<null | Item[]>;
|
||||
readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise<null | Item>;
|
||||
): Promise<null | Partial<Item>[]>;
|
||||
readByKey(
|
||||
key: PrimaryKey,
|
||||
query?: Query,
|
||||
action?: PermissionsAction
|
||||
): Promise<null | Partial<Item>>;
|
||||
async readByKey(
|
||||
key: PrimaryKey | PrimaryKey[],
|
||||
query: Query = {},
|
||||
action: PermissionsAction = 'read'
|
||||
): Promise<null | Item | Item[]> {
|
||||
): Promise<null | Partial<Item> | Partial<Item>[]> {
|
||||
query = clone(query);
|
||||
const primaryKeyField = await this.schemaInspector.primary(this.collection);
|
||||
const keys = toArray(key);
|
||||
@@ -260,7 +264,7 @@ export class ItemsService implements AbstractService {
|
||||
|
||||
if (result === null) throw new ForbiddenException();
|
||||
|
||||
return result;
|
||||
return result as Partial<Item> | Partial<Item>[] | null;
|
||||
}
|
||||
|
||||
update(data: Partial<Item>, keys: PrimaryKey[]): Promise<PrimaryKey[]>;
|
||||
@@ -277,7 +281,7 @@ export class ItemsService implements AbstractService {
|
||||
if (data && key) {
|
||||
const keys = toArray(key);
|
||||
|
||||
let payload = clone(data);
|
||||
let payload: Partial<AnyItem> | Partial<AnyItem>[] = clone(data);
|
||||
|
||||
const customProcessed = await emitter.emitAsync(
|
||||
`${this.eventScope}.update.before`,
|
||||
@@ -550,11 +554,11 @@ export class ItemsService implements AbstractService {
|
||||
return await this.delete(keys);
|
||||
}
|
||||
|
||||
async readSingleton(query: Query) {
|
||||
async readSingleton(query: Query): Promise<Partial<Item>> {
|
||||
query = clone(query);
|
||||
query.single = true;
|
||||
|
||||
const record = (await this.readByQuery(query)) as Item;
|
||||
const record = (await this.readByQuery(query)) as Partial<Item>;
|
||||
|
||||
if (!record) {
|
||||
const columns = await this.schemaInspector.columnInfo(this.collection);
|
||||
@@ -564,7 +568,7 @@ export class ItemsService implements AbstractService {
|
||||
defaults[column.name] = getDefaultValue(column);
|
||||
}
|
||||
|
||||
return defaults;
|
||||
return defaults as Partial<Item>;
|
||||
}
|
||||
|
||||
return record;
|
||||
|
||||
@@ -40,7 +40,7 @@ export class MetaService {
|
||||
const dbQuery = database(collection).count('*', { as: 'count' });
|
||||
|
||||
if (query.filter) {
|
||||
applyFilter(dbQuery, query.filter, collection);
|
||||
await applyFilter(dbQuery, query.filter, collection);
|
||||
}
|
||||
|
||||
const records = await dbQuery;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { AbstractServiceOptions, Accountability } from '../types';
|
||||
import Knex from 'knex';
|
||||
import database from '../database';
|
||||
import os from 'os';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
// @ts-ignore
|
||||
import { version } from '../../package.json';
|
||||
import macosRelease from 'macos-release';
|
||||
@@ -16,31 +15,57 @@ export class ServerService {
|
||||
this.accountability = options?.accountability || null;
|
||||
}
|
||||
|
||||
serverInfo() {
|
||||
if (this.accountability?.admin !== true) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
async serverInfo() {
|
||||
const info: Record<string, any> = {};
|
||||
|
||||
const osType = os.type() === 'Darwin' ? 'macOS' : os.type();
|
||||
const osVersion =
|
||||
osType === 'macOS'
|
||||
? `${macosRelease().name} (${macosRelease().version})`
|
||||
: os.release();
|
||||
const projectInfo = await this.knex
|
||||
.select(
|
||||
'project_name',
|
||||
'project_logo',
|
||||
'project_color',
|
||||
'public_foreground',
|
||||
'public_background',
|
||||
'public_note',
|
||||
'custom_css'
|
||||
)
|
||||
.from('directus_settings')
|
||||
.first();
|
||||
|
||||
return {
|
||||
directus: {
|
||||
info.project = projectInfo
|
||||
? {
|
||||
project_name: projectInfo.project_name,
|
||||
project_logo: projectInfo.project_logo,
|
||||
project_color: projectInfo.project_color,
|
||||
public_foreground: projectInfo.public_foreground,
|
||||
public_background: projectInfo.public_background,
|
||||
public_note: projectInfo.public_note,
|
||||
custom_css: projectInfo.custom_css,
|
||||
}
|
||||
: null;
|
||||
|
||||
if (this.accountability?.admin === true) {
|
||||
const osType = os.type() === 'Darwin' ? 'macOS' : os.type();
|
||||
|
||||
const osVersion =
|
||||
osType === 'macOS'
|
||||
? `${macosRelease().name} (${macosRelease().version})`
|
||||
: os.release();
|
||||
|
||||
info.directus = {
|
||||
version,
|
||||
},
|
||||
node: {
|
||||
};
|
||||
info.node = {
|
||||
version: process.versions.node,
|
||||
uptime: Math.round(process.uptime()),
|
||||
},
|
||||
os: {
|
||||
};
|
||||
info.os = {
|
||||
type: osType,
|
||||
version: osVersion,
|
||||
uptime: Math.round(os.uptime()),
|
||||
totalmem: os.totalmem(),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { QueryBuilder } from 'knex';
|
||||
import { Query, Filter } from '../types';
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { clone } from 'lodash';
|
||||
import { clone, isPlainObject } from 'lodash';
|
||||
|
||||
export default async function applyQuery(collection: string, dbQuery: QueryBuilder, query: Query) {
|
||||
if (query.filter) {
|
||||
@@ -46,116 +46,244 @@ export default async function applyQuery(collection: string, dbQuery: QueryBuild
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyFilter(dbQuery: QueryBuilder, filter: Filter, collection: string) {
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (key === '_or') {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
dbQuery.orWhere((subQuery) => applyFilter(subQuery, subFilter, collection));
|
||||
});
|
||||
export async function applyFilter(rootQuery: QueryBuilder, rootFilter: Filter, collection: string) {
|
||||
const relations = await database.select('*').from('directus_relations');
|
||||
|
||||
continue;
|
||||
addWhereClauses(rootQuery, rootFilter, collection);
|
||||
addJoins(rootQuery, rootFilter, collection);
|
||||
|
||||
function addWhereClauses(dbQuery: QueryBuilder, filter: Filter, collection: string) {
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (key === '_or') {
|
||||
/** @NOTE these callback functions aren't called until Knex runs the query */
|
||||
dbQuery.orWhere((subQuery) => {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
addWhereClauses(subQuery, subFilter, collection);
|
||||
});
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === '_and') {
|
||||
/** @NOTE these callback functions aren't called until Knex runs the query */
|
||||
dbQuery.andWhere((subQuery) => {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
addWhereClauses(subQuery, subFilter, collection);
|
||||
});
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const filterPath = getFilterPath(key, value);
|
||||
const { operator: filterOperator, value: filterValue } = getOperation(key, value);
|
||||
|
||||
if (filterPath.length > 1) {
|
||||
const columnName = getWhereColumn(filterPath, collection);
|
||||
applyFilterToQuery(columnName, filterOperator, filterValue);
|
||||
} else {
|
||||
applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (key === '_and') {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
dbQuery.andWhere((subQuery) => applyFilter(subQuery, subFilter, collection));
|
||||
});
|
||||
function applyFilterToQuery(key: string, operator: string, compareValue: any) {
|
||||
if (operator === '_eq') {
|
||||
dbQuery.where({ [key]: compareValue });
|
||||
}
|
||||
|
||||
continue;
|
||||
if (operator === '_neq') {
|
||||
dbQuery.whereNot({ [key]: compareValue });
|
||||
}
|
||||
|
||||
if (operator === '_contains') {
|
||||
dbQuery.where(key, 'like', `%${compareValue}%`);
|
||||
}
|
||||
|
||||
if (operator === '_ncontains') {
|
||||
dbQuery.where(key, 'like', `%${compareValue}%`);
|
||||
}
|
||||
|
||||
if (operator === '_gt') {
|
||||
dbQuery.where(key, '>', compareValue);
|
||||
}
|
||||
|
||||
if (operator === '_gte') {
|
||||
dbQuery.where(key, '>=', compareValue);
|
||||
}
|
||||
|
||||
if (operator === '_lt') {
|
||||
dbQuery.where(key, '<', compareValue);
|
||||
}
|
||||
|
||||
if (operator === '_lte') {
|
||||
dbQuery.where(key, '<=', compareValue);
|
||||
}
|
||||
|
||||
if (operator === '_in') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereIn(key, value as string[]);
|
||||
}
|
||||
|
||||
if (operator === '_nin') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereNotIn(key, value as string[]);
|
||||
}
|
||||
|
||||
if (operator === '_null') {
|
||||
dbQuery.whereNull(key);
|
||||
}
|
||||
|
||||
if (operator === '_nnull') {
|
||||
dbQuery.whereNotNull(key);
|
||||
}
|
||||
|
||||
if (operator === '_empty') {
|
||||
dbQuery.andWhere((query) => {
|
||||
query.whereNull(key);
|
||||
query.orWhere(key, '=', '');
|
||||
});
|
||||
}
|
||||
|
||||
if (operator === '_nempty') {
|
||||
dbQuery.andWhere((query) => {
|
||||
query.whereNotNull(key);
|
||||
query.orWhere(key, '!=', '');
|
||||
});
|
||||
}
|
||||
|
||||
if (operator === '_between') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereBetween(key, value);
|
||||
}
|
||||
|
||||
if (operator === '_nbetween') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereNotBetween(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const filterPath = getFilterPath(key, value);
|
||||
const { operator: filterOperator, value: filterValue } = getOperation(key, value);
|
||||
function getWhereColumn(path: string[], collection: string) {
|
||||
path = clone(path);
|
||||
|
||||
const column =
|
||||
filterPath.length > 1
|
||||
? await applyJoins(dbQuery, filterPath, collection)
|
||||
: `${collection}.${filterPath[0]}`;
|
||||
let columnName = '';
|
||||
|
||||
applyFilterToQuery(column, filterOperator, filterValue);
|
||||
followRelation(path);
|
||||
|
||||
return columnName;
|
||||
|
||||
function followRelation(pathParts: string[], parentCollection: string = collection) {
|
||||
const relation = relations.find((relation) => {
|
||||
return (
|
||||
(relation.many_collection === parentCollection &&
|
||||
relation.many_field === pathParts[0]) ||
|
||||
(relation.one_collection === parentCollection &&
|
||||
relation.one_field === pathParts[0])
|
||||
);
|
||||
});
|
||||
|
||||
if (!relation) return;
|
||||
|
||||
const isM2O =
|
||||
relation.many_collection === parentCollection &&
|
||||
relation.many_field === pathParts[0];
|
||||
|
||||
pathParts.shift();
|
||||
|
||||
const parent = isM2O ? relation.one_collection! : relation.many_collection;
|
||||
|
||||
if (pathParts.length === 1) {
|
||||
columnName = `${parent}.${pathParts[0]}`;
|
||||
}
|
||||
|
||||
if (pathParts.length) {
|
||||
followRelation(pathParts, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilterToQuery(key: string, operator: string, compareValue: any) {
|
||||
if (operator === '_eq') {
|
||||
dbQuery.where({ [key]: compareValue });
|
||||
/**
|
||||
* @NOTE Yes this is very similar in structure and functionality as the other loop. However,
|
||||
* due to the order of execution that Knex has in the nested andWhere / orWhere structures,
|
||||
* joins that are added in there aren't added in time
|
||||
*/
|
||||
function addJoins(dbQuery: QueryBuilder, filter: Filter, collection: string) {
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (key === '_or') {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
addJoins(dbQuery, subFilter, collection);
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === '_and') {
|
||||
value.forEach((subFilter: Record<string, any>) => {
|
||||
addJoins(dbQuery, subFilter, collection);
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const filterPath = getFilterPath(key, value);
|
||||
|
||||
if (filterPath.length > 1) {
|
||||
addJoin(filterPath, collection);
|
||||
}
|
||||
}
|
||||
|
||||
if (operator === '_neq') {
|
||||
dbQuery.whereNot({ [key]: compareValue });
|
||||
}
|
||||
function addJoin(path: string[], collection: string) {
|
||||
path = clone(path);
|
||||
|
||||
if (operator === '_contains') {
|
||||
dbQuery.where(key, 'like', `%${compareValue}%`);
|
||||
}
|
||||
followRelation(path);
|
||||
|
||||
if (operator === '_ncontains') {
|
||||
dbQuery.where(key, 'like', `%${compareValue}%`);
|
||||
}
|
||||
function followRelation(pathParts: string[], parentCollection: string = collection) {
|
||||
const relation = relations.find((relation) => {
|
||||
return (
|
||||
(relation.many_collection === parentCollection &&
|
||||
relation.many_field === pathParts[0]) ||
|
||||
(relation.one_collection === parentCollection &&
|
||||
relation.one_field === pathParts[0])
|
||||
);
|
||||
});
|
||||
|
||||
if (operator === '_gt') {
|
||||
dbQuery.where(key, '>', compareValue);
|
||||
}
|
||||
if (!relation) return;
|
||||
|
||||
if (operator === '_gte') {
|
||||
dbQuery.where(key, '>=', compareValue);
|
||||
}
|
||||
const isM2O =
|
||||
relation.many_collection === parentCollection &&
|
||||
relation.many_field === pathParts[0];
|
||||
|
||||
if (operator === '_lt') {
|
||||
dbQuery.where(key, '<', compareValue);
|
||||
}
|
||||
if (isM2O) {
|
||||
dbQuery.leftJoin(
|
||||
relation.one_collection!,
|
||||
`${parentCollection}.${relation.many_field}`,
|
||||
`${relation.one_collection}.${relation.one_primary}`
|
||||
);
|
||||
} else {
|
||||
dbQuery.leftJoin(
|
||||
relation.many_collection,
|
||||
`${parentCollection}.${relation.one_primary}`,
|
||||
`${relation.many_collection}.${relation.many_field}`
|
||||
);
|
||||
}
|
||||
|
||||
if (operator === '_lte') {
|
||||
dbQuery.where(key, '<=', compareValue);
|
||||
}
|
||||
pathParts.shift();
|
||||
|
||||
if (operator === '_in') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
const parent = isM2O ? relation.one_collection! : relation.many_collection;
|
||||
|
||||
dbQuery.whereIn(key, value as string[]);
|
||||
}
|
||||
|
||||
if (operator === '_nin') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereNotIn(key, value as string[]);
|
||||
}
|
||||
|
||||
if (operator === '_null') {
|
||||
dbQuery.whereNull(key);
|
||||
}
|
||||
|
||||
if (operator === '_nnull') {
|
||||
dbQuery.whereNotNull(key);
|
||||
}
|
||||
|
||||
if (operator === '_empty') {
|
||||
dbQuery.andWhere((query) => {
|
||||
query.whereNull(key);
|
||||
query.orWhere(key, '=', '');
|
||||
});
|
||||
}
|
||||
|
||||
if (operator === '_nempty') {
|
||||
dbQuery.andWhere((query) => {
|
||||
query.whereNotNull(key);
|
||||
query.orWhere(key, '!=', '');
|
||||
});
|
||||
}
|
||||
|
||||
if (operator === '_between') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereBetween(key, value);
|
||||
}
|
||||
|
||||
if (operator === '_nbetween') {
|
||||
let value = compareValue;
|
||||
if (typeof value === 'string') value = value.split(',');
|
||||
|
||||
dbQuery.whereNotBetween(key, value);
|
||||
if (pathParts.length) {
|
||||
followRelation(pathParts, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +291,11 @@ export async function applyFilter(dbQuery: QueryBuilder, filter: Filter, collect
|
||||
function getFilterPath(key: string, value: Record<string, any>) {
|
||||
const path = [key];
|
||||
|
||||
if (Object.keys(value)[0].startsWith('_') === false) {
|
||||
if (Object.keys(value)[0].startsWith('_') === true) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
path.push(...getFilterPath(Object.keys(value)[0], Object.values(value)[0]));
|
||||
}
|
||||
|
||||
@@ -171,57 +303,11 @@ function getFilterPath(key: string, value: Record<string, any>) {
|
||||
}
|
||||
|
||||
function getOperation(key: string, value: Record<string, any>): { operator: string; value: any } {
|
||||
if (key.startsWith('_') && key !== '_and' && key !== '_or')
|
||||
if (key.startsWith('_') && key !== '_and' && key !== '_or') {
|
||||
return { operator: key as string, value };
|
||||
} else if (isPlainObject(value) === false) {
|
||||
return { operator: '_eq', value };
|
||||
}
|
||||
|
||||
return getOperation(Object.keys(value)[0], Object.values(value)[0]);
|
||||
}
|
||||
|
||||
async function applyJoins(dbQuery: QueryBuilder, path: string[], collection: string) {
|
||||
path = clone(path);
|
||||
|
||||
let keyName = '';
|
||||
|
||||
await addJoins(path);
|
||||
|
||||
return keyName;
|
||||
|
||||
async function addJoins(pathParts: string[], parentCollection: string = collection) {
|
||||
const relation = await database
|
||||
.select('*')
|
||||
.from('directus_relations')
|
||||
.where({ one_collection: parentCollection, one_field: pathParts[0] })
|
||||
.orWhere({ many_collection: parentCollection, many_field: pathParts[0] })
|
||||
.first();
|
||||
|
||||
if (!relation) return;
|
||||
|
||||
const isM2O =
|
||||
relation.many_collection === parentCollection && relation.many_field === pathParts[0];
|
||||
|
||||
if (isM2O) {
|
||||
dbQuery.leftJoin(
|
||||
relation.one_collection,
|
||||
`${parentCollection}.${relation.many_field}`,
|
||||
`${relation.one_collection}.${relation.one_primary}`
|
||||
);
|
||||
} else {
|
||||
dbQuery.leftJoin(
|
||||
relation.many_collection,
|
||||
`${relation.one_collection}.${relation.one_primary}`,
|
||||
`${relation.many_collection}.${relation.many_field}`
|
||||
);
|
||||
}
|
||||
|
||||
pathParts.shift();
|
||||
|
||||
const parent = isM2O ? relation.one_collection : relation.many_collection;
|
||||
|
||||
if (pathParts.length === 1) {
|
||||
keyName = `${parent}.${pathParts[0]}`;
|
||||
}
|
||||
|
||||
if (pathParts.length) {
|
||||
await addJoins(pathParts, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export function toArray<T = any>(val: T | T[]): T[] {
|
||||
if (typeof val === 'string') {
|
||||
return (val.split(',') as unknown) as T[];
|
||||
}
|
||||
|
||||
return Array.isArray(val) ? val : [val];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user