mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' into assets
This commit is contained in:
@@ -20,6 +20,7 @@ import { ItemsService } from './items';
|
||||
import { PayloadService } from './payload';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
|
||||
export class AuthorizationService {
|
||||
knex: Knex;
|
||||
@@ -268,13 +269,18 @@ export class AuthorizationService {
|
||||
let requiredColumns: string[] = [];
|
||||
|
||||
for (const column of columns) {
|
||||
const field = await this.knex
|
||||
.select<{ special: string }>('special')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: column.name })
|
||||
.first();
|
||||
const field =
|
||||
(await this.knex
|
||||
.select<{ special: string }>('special')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: column.name })
|
||||
.first()) ||
|
||||
systemFieldRows.find(
|
||||
(fieldMeta) =>
|
||||
fieldMeta.field === column.name && fieldMeta.collection === collection
|
||||
);
|
||||
|
||||
const specials = (field?.special || '').split(',');
|
||||
const specials = field?.special ? toArray(field.special) : [];
|
||||
|
||||
const hasGenerateSpecial = [
|
||||
'uuid',
|
||||
@@ -326,9 +332,11 @@ export class AuthorizationService {
|
||||
}
|
||||
|
||||
validateJoi(
|
||||
validation: Record<string, any>,
|
||||
validation: null | Record<string, any>,
|
||||
payloads: Partial<Record<string, any>>[]
|
||||
): FailedValidationException[] {
|
||||
if (!validation) return [];
|
||||
|
||||
const errors: FailedValidationException[] = [];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { AbstractServiceOptions, Accountability, Collection, Relation } from '../types';
|
||||
import {
|
||||
AbstractServiceOptions,
|
||||
Accountability,
|
||||
Collection,
|
||||
CollectionMeta,
|
||||
Relation,
|
||||
} from '../types';
|
||||
import Knex from 'knex';
|
||||
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
@@ -7,6 +13,7 @@ import { FieldsService } from '../services/fields';
|
||||
import { ItemsService } from '../services/items';
|
||||
import cache from '../cache';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class CollectionsService {
|
||||
knex: Knex;
|
||||
@@ -106,6 +113,7 @@ export class CollectionsService {
|
||||
knex: this.knex,
|
||||
accountability: this.accountability,
|
||||
});
|
||||
|
||||
const collectionKeys = toArray(collection);
|
||||
|
||||
if (this.accountability && this.accountability.admin !== true) {
|
||||
@@ -135,7 +143,9 @@ export class CollectionsService {
|
||||
const tables = tablesInDatabase.filter((table) => collectionKeys.includes(table.name));
|
||||
const meta = (await collectionItemsService.readByQuery({
|
||||
filter: { collection: { _in: collectionKeys } },
|
||||
})) as Collection['meta'][];
|
||||
})) as CollectionMeta[];
|
||||
|
||||
meta.push(...systemCollectionRows);
|
||||
|
||||
const collections: Collection[] = [];
|
||||
|
||||
@@ -173,7 +183,9 @@ export class CollectionsService {
|
||||
const tablesToFetchInfoFor = tablesInDatabase.map((table) => table.name);
|
||||
const meta = (await collectionItemsService.readByQuery({
|
||||
filter: { collection: { _in: tablesToFetchInfoFor } },
|
||||
})) as Collection['meta'][];
|
||||
})) as CollectionMeta[];
|
||||
|
||||
meta.push(...systemCollectionRows);
|
||||
|
||||
const collections: Collection[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ import { PayloadService } from '../services/payload';
|
||||
import getDefaultValue from '../utils/get-default-value';
|
||||
import cache from '../cache';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
import { systemFieldRows } from '../database/system-data/fields/';
|
||||
|
||||
type RawField = Partial<Field> & { field: string; type: typeof types[number] };
|
||||
|
||||
@@ -38,8 +41,13 @@ export class FieldsService {
|
||||
filter: { collection: { _eq: collection } },
|
||||
limit: -1,
|
||||
})) as FieldMeta[];
|
||||
|
||||
fields.push(
|
||||
...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === collection)
|
||||
);
|
||||
} else {
|
||||
fields = (await nonAuthorizedItemsService.readByQuery({ limit: -1 })) as FieldMeta[];
|
||||
fields.push(...systemFieldRows);
|
||||
}
|
||||
|
||||
let columns = await schemaInspector.columnInfo(collection);
|
||||
@@ -73,12 +81,15 @@ export class FieldsService {
|
||||
aliasQuery.andWhere('collection', collection);
|
||||
}
|
||||
|
||||
let aliasFields = await aliasQuery;
|
||||
let aliasFields = [
|
||||
...((await this.payloadService.processValues('read', await aliasQuery)) as FieldMeta[]),
|
||||
...systemFieldRows,
|
||||
];
|
||||
|
||||
const aliasTypes = ['alias', 'o2m', 'm2m', 'files', 'files', 'translations'];
|
||||
|
||||
aliasFields = aliasFields.filter((field) => {
|
||||
const specials = (field.special || '').split(',');
|
||||
const specials = toArray(field.special);
|
||||
|
||||
for (const type of aliasTypes) {
|
||||
if (specials.includes(type)) return true;
|
||||
@@ -87,19 +98,17 @@ export class FieldsService {
|
||||
return false;
|
||||
});
|
||||
|
||||
aliasFields = (await this.payloadService.processValues('read', aliasFields)) as FieldMeta[];
|
||||
|
||||
const aliasFieldsAsField = aliasFields.map((field) => {
|
||||
const data = {
|
||||
collection: field.collection,
|
||||
field: field.field,
|
||||
type: field.special[0],
|
||||
type: field.special?.[0],
|
||||
schema: null,
|
||||
meta: field,
|
||||
};
|
||||
|
||||
return data;
|
||||
});
|
||||
}) as Field[];
|
||||
|
||||
const result = [...columnsWithSystem, ...aliasFieldsAsField];
|
||||
|
||||
@@ -163,6 +172,12 @@ export class FieldsService {
|
||||
fieldInfo = (await this.payloadService.processValues('read', fieldInfo)) as FieldMeta[];
|
||||
}
|
||||
|
||||
fieldInfo =
|
||||
fieldInfo ||
|
||||
systemFieldRows.find(
|
||||
(fieldMeta) => fieldMeta.collection === collection && fieldMeta.field === field
|
||||
);
|
||||
|
||||
try {
|
||||
column = await schemaInspector.columnInfo(collection, field);
|
||||
column.default_value = getDefaultValue(column);
|
||||
|
||||
@@ -41,7 +41,7 @@ export class FilesService extends ItemsService {
|
||||
const fileExtension =
|
||||
(payload.type && extension(payload.type)) || path.extname(payload.filename_download);
|
||||
|
||||
payload.filename_disk = primaryKey + fileExtension;
|
||||
payload.filename_disk = primaryKey + '.' + fileExtension;
|
||||
|
||||
if (!payload.type) {
|
||||
payload.type = 'application/octet-stream';
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Field,
|
||||
Relation,
|
||||
Query,
|
||||
AbstractService,
|
||||
} from '../types';
|
||||
import {
|
||||
GraphQLString,
|
||||
@@ -49,6 +48,7 @@ import { UsersService } from './users';
|
||||
import { WebhooksService } from './webhooks';
|
||||
|
||||
import { getRelationType } from '../utils/get-relation-type';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class GraphQLService {
|
||||
accountability: Accountability | null;
|
||||
@@ -504,11 +504,16 @@ export class GraphQLService {
|
||||
});
|
||||
}
|
||||
|
||||
const collectionInfo = await this.knex
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: collection })
|
||||
.first();
|
||||
const collectionInfo =
|
||||
(await this.knex
|
||||
.select('singleton')
|
||||
.from('directus_collections')
|
||||
.where({ collection: collection })
|
||||
.first()) ||
|
||||
systemCollectionRows.find(
|
||||
(collectionMeta) => collectionMeta?.collection === collection
|
||||
);
|
||||
|
||||
const result =
|
||||
collectionInfo?.singleton === true
|
||||
? await service.readSingleton(query)
|
||||
|
||||
@@ -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) {
|
||||
await applyFilter(dbQuery, query.filter, collection);
|
||||
await applyFilter(this.knex, dbQuery, query.filter, collection);
|
||||
}
|
||||
|
||||
const records = await dbQuery;
|
||||
|
||||
@@ -17,6 +17,9 @@ import getLocalType from '../utils/get-local-type';
|
||||
import { format, formatISO } from 'date-fns';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { FieldMeta } from '../types';
|
||||
import { systemFieldRows } from '../database/system-data/fields';
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
type Action = 'create' | 'read' | 'update';
|
||||
|
||||
@@ -148,17 +151,21 @@ export class PayloadService {
|
||||
|
||||
const fieldsInPayload = Object.keys(processedPayload[0]);
|
||||
|
||||
const specialFieldsQuery = this.knex
|
||||
let specialFieldsInCollection: FieldMeta[] = await this.knex
|
||||
.select('field', 'special')
|
||||
.from('directus_fields')
|
||||
.where({ collection: this.collection })
|
||||
.whereNotNull('special');
|
||||
|
||||
if (action === 'read') {
|
||||
specialFieldsQuery.whereIn('field', fieldsInPayload);
|
||||
}
|
||||
specialFieldsInCollection.push(
|
||||
...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === this.collection)
|
||||
);
|
||||
|
||||
const specialFieldsInCollection = await specialFieldsQuery;
|
||||
if (action === 'read') {
|
||||
specialFieldsInCollection = specialFieldsInCollection.filter((fieldMeta) => {
|
||||
return fieldsInPayload.includes(fieldMeta.field);
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
processedPayload.map(async (record: any) => {
|
||||
@@ -203,13 +210,13 @@ export class PayloadService {
|
||||
}
|
||||
|
||||
async processField(
|
||||
field: { field: string; special: string },
|
||||
field: FieldMeta,
|
||||
payload: Partial<Item>,
|
||||
action: Action,
|
||||
accountability: Accountability | null
|
||||
) {
|
||||
if (!field.special) return payload[field.field];
|
||||
const fieldSpecials = field.special.split(',').map((s) => s.trim());
|
||||
const fieldSpecials = field.special ? toArray(field.special) : [];
|
||||
|
||||
let value = clone(payload[field.field]);
|
||||
|
||||
@@ -284,10 +291,15 @@ export class PayloadService {
|
||||
async processM2O(
|
||||
payload: Partial<Item> | Partial<Item>[]
|
||||
): Promise<Partial<Item> | Partial<Item>[]> {
|
||||
const relations = await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ many_collection: this.collection });
|
||||
const relations = [
|
||||
...(await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ many_collection: this.collection })),
|
||||
...systemRelationRows.filter(
|
||||
(systemRelation) => systemRelation.many_collection === this.collection
|
||||
),
|
||||
];
|
||||
|
||||
const payloads = clone(Array.isArray(payload) ? payload : [payload]);
|
||||
|
||||
@@ -334,10 +346,15 @@ export class PayloadService {
|
||||
* Recursively save/update all nested related o2m items
|
||||
*/
|
||||
async processO2M(payload: Partial<Item> | Partial<Item>[], parent?: PrimaryKey) {
|
||||
const relations = await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ one_collection: this.collection });
|
||||
const relations = [
|
||||
...(await this.knex
|
||||
.select<Relation[]>('*')
|
||||
.from('directus_relations')
|
||||
.where({ one_collection: this.collection })),
|
||||
...systemRelationRows.filter(
|
||||
(systemRelation) => systemRelation.one_collection === this.collection
|
||||
),
|
||||
];
|
||||
|
||||
const payloads = clone(toArray(payload));
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { AbstractServiceOptions, Query, PrimaryKey, PermissionsAction, Relation
|
||||
import { PermissionsService } from './permissions';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
import { systemRelationRows } from '../database/system-data/relations';
|
||||
|
||||
/**
|
||||
* @TODO update foreign key constraints when relations are updated
|
||||
*/
|
||||
@@ -26,6 +28,10 @@ export class RelationsService extends ItemsService {
|
||||
| ParsedRelation[]
|
||||
| null;
|
||||
|
||||
if (results && Array.isArray(results)) {
|
||||
results.push(...(systemRelationRows as ParsedRelation[]));
|
||||
}
|
||||
|
||||
const filteredResults = await this.filterForbidden(results);
|
||||
|
||||
return filteredResults;
|
||||
@@ -48,6 +54,9 @@ export class RelationsService extends ItemsService {
|
||||
| ParsedRelation[]
|
||||
| null;
|
||||
|
||||
// No need to merge system relations here. They don't have PKs so can never be directly
|
||||
// targetted
|
||||
|
||||
const filteredResults = await this.filterForbidden(results);
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import database from '../database';
|
||||
import Knex from 'knex';
|
||||
import { InvalidPayloadException, ForbiddenException } from '../exceptions';
|
||||
import SchemaInspector from 'knex-schema-inspector';
|
||||
import { systemCollectionRows } from '../database/system-data/collections';
|
||||
|
||||
export class UtilsService {
|
||||
knex: Knex;
|
||||
@@ -16,11 +17,12 @@ export class UtilsService {
|
||||
async sort(collection: string, { item, to }: { item: PrimaryKey; to: PrimaryKey }) {
|
||||
const schemaInspector = SchemaInspector(this.knex);
|
||||
|
||||
const sortFieldResponse = await this.knex
|
||||
.select('sort_field')
|
||||
.from('directus_collections')
|
||||
.where({ collection })
|
||||
.first();
|
||||
const sortFieldResponse =
|
||||
(await this.knex
|
||||
.select('sort_field')
|
||||
.from('directus_collections')
|
||||
.where({ collection })
|
||||
.first()) || systemCollectionRows;
|
||||
|
||||
const sortField = sortFieldResponse?.sort_field;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user