diff --git a/api/src/controllers/files.ts b/api/src/controllers/files.ts index 673f3daf91..9ec98cd222 100644 --- a/api/src/controllers/files.ts +++ b/api/src/controllers/files.ts @@ -113,7 +113,7 @@ router.post( try { const record = await service.readByKey(keys as any, req.sanitizedQuery); res.locals.payload = { - data: res.locals.savedFiles.length === 1 ? record[0] : record || null, + data: res.locals.savedFiles.length === 1 ? record![0] : record || null, }; } catch (error) { if (error instanceof ForbiddenException) { diff --git a/api/src/database/run-ast.ts b/api/src/database/run-ast.ts index eff85c8fe0..cdff8bd06e 100644 --- a/api/src/database/run-ast.ts +++ b/api/src/database/run-ast.ts @@ -13,7 +13,7 @@ type RunASTOptions = { child?: boolean; }; -export default async function runAST(originalAST: AST, options?: RunASTOptions): Promise { +export default async function runAST(originalAST: AST, options?: RunASTOptions): Promise { const ast = cloneDeep(originalAST); const query = options?.query || ast.query; @@ -27,6 +27,8 @@ export default async function runAST(originalAST: AST, options?: RunASTOptions): const rawItems: Item | Item[] = await dbQuery; + if (!rawItems || (Array.isArray(rawItems) && rawItems.length === 0)) return null; + // Run the items through the special transforms const payloadService = new PayloadService(ast.name, { knex }); let items = await payloadService.processValues('read', rawItems); @@ -48,8 +50,10 @@ export default async function runAST(originalAST: AST, options?: RunASTOptions): let nestedItems = await runAST(nestedAST, { knex, child: true }); - // Merge all fetched nested records with the parent items - items = mergeWithParentItems(nestedItems, items, nestedAST, tempLimit); + if (nestedItems) { + // Merge all fetched nested records with the parent items + items = mergeWithParentItems(nestedItems, items, nestedAST, tempLimit); + } } // During the fetching of data, we have to inject a couple of required fields for the child nesting diff --git a/api/src/exceptions/item-not-found.ts b/api/src/exceptions/item-not-found.ts index 9449f55028..88d3417b9d 100644 --- a/api/src/exceptions/item-not-found.ts +++ b/api/src/exceptions/item-not-found.ts @@ -1,7 +1,7 @@ import { BaseException } from './base'; export class ItemNotFoundException extends BaseException { - constructor(id: string | number, collection: string) { + constructor(id: string | number | (string | number)[], collection: string) { super(`Item "${id}" doesn't exist in "${collection}".`, 404, 'ITEM_NOT_FOUND'); } } diff --git a/api/src/services/files.ts b/api/src/services/files.ts index 7651aef04d..79fb86b28f 100644 --- a/api/src/services/files.ts +++ b/api/src/services/files.ts @@ -8,6 +8,8 @@ import path from 'path'; import { AbstractServiceOptions, File, PrimaryKey } from '../types'; import { clone } from 'lodash'; import cache from '../cache'; +import notFound from '../controllers/not-found'; +import { ItemNotFoundException } from '../exceptions'; export class FilesService extends ItemsService { constructor(options?: AbstractServiceOptions) { @@ -91,6 +93,10 @@ export class FilesService extends ItemsService { const keys = Array.isArray(key) ? key : [key]; const files = await super.readByKey(keys, { fields: ['id', 'storage'] }); + if (!files) { + throw new ItemNotFoundException(key, 'directus_files'); + } + for (const file of files) { const disk = storage.disk(file.storage); diff --git a/api/src/services/items.ts b/api/src/services/items.ts index 688f0a4e75..a50da786cc 100644 --- a/api/src/services/items.ts +++ b/api/src/services/items.ts @@ -188,10 +188,11 @@ export class ItemsService implements AbstractService { return Array.isArray(data) ? savedPrimaryKeys : savedPrimaryKeys[0]; } - async readByQuery(query: Query): Promise { + async readByQuery(query: Query): Promise { const authorizationService = new AuthorizationService({ accountability: this.accountability, }); + let ast = await getASTFromQuery(this.collection, query, { accountability: this.accountability, knex: this.knex, @@ -205,13 +206,13 @@ export class ItemsService implements AbstractService { return records; } - readByKey(keys: PrimaryKey[], query?: Query, action?: PermissionsAction): Promise; - readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise; + readByKey(keys: PrimaryKey[], query?: Query, action?: PermissionsAction): Promise; + readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise; async readByKey( key: PrimaryKey | PrimaryKey[], query: Query = {}, action: PermissionsAction = 'read' - ): Promise { + ): Promise { query = clone(query); const schemaInspector = SchemaInspector(this.knex); const primaryKeyField = await schemaInspector.primary(this.collection); @@ -354,7 +355,7 @@ export class ItemsService implements AbstractService { activity: key, collection: this.collection, item: keys[index], - data: JSON.stringify(snapshots[index]), + data: JSON.stringify(snapshots?.[index]), delta: JSON.stringify(payloadWithoutAliases), })); diff --git a/api/src/types/services.ts b/api/src/types/services.ts index 441a896ac7..2234917f2e 100644 --- a/api/src/types/services.ts +++ b/api/src/types/services.ts @@ -16,10 +16,10 @@ export interface AbstractService { create(data: Partial[]): Promise; create(data: Partial): Promise; - readByQuery(query: Query): Promise; + readByQuery(query: Query): Promise; - readByKey(keys: PrimaryKey[], query: Query, action: PermissionsAction): Promise; - readByKey(key: PrimaryKey, query: Query, action: PermissionsAction): Promise; + readByKey(keys: PrimaryKey[], query: Query, action: PermissionsAction): Promise; + readByKey(key: PrimaryKey, query: Query, action: PermissionsAction): Promise; update(data: Partial, keys: PrimaryKey[]): Promise; update(data: Partial, key: PrimaryKey): Promise; diff --git a/app/src/modules/settings/routes/data-model/fields/fields.vue b/app/src/modules/settings/routes/data-model/fields/fields.vue index d5e9acd597..691fcbde48 100644 --- a/app/src/modules/settings/routes/data-model/fields/fields.vue +++ b/app/src/modules/settings/routes/data-model/fields/fields.vue @@ -175,6 +175,7 @@ export default defineComponent({ .collections-detail { padding: var(--content-padding); + padding-top: 0; padding-bottom: var(--content-padding-bottom); }