From 592ab925dbda1e2d9d87fd85a8fbbfa152c5ccaf Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 15 Oct 2020 18:00:27 -0400 Subject: [PATCH] Add toArray util --- api/src/controllers/auth.ts | 3 ++- api/src/database/run-ast.ts | 9 +++++---- api/src/middleware/error-handler.ts | 3 ++- api/src/services/authorization.ts | 3 ++- api/src/services/collections.ts | 11 ++++++----- api/src/services/files.ts | 5 +++-- api/src/services/items.ts | 17 +++++++++-------- api/src/services/payload.ts | 5 +++-- api/src/services/relations.ts | 3 ++- api/src/services/roles.ts | 3 ++- api/src/services/users.ts | 5 +++-- api/src/storage.ts | 3 ++- api/src/utils/to-array.ts | 3 +++ 13 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 api/src/utils/to-array.ts diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index 7fbc62edad..ac02da283c 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -12,6 +12,7 @@ import { UsersService, AuthenticationService } from '../services'; import grantConfig from '../grant'; import { RouteNotFoundException } from '../exceptions'; import { respond } from '../middleware/respond'; +import { toArray } from '../utils/to-array'; const router = Router(); @@ -213,7 +214,7 @@ router.post( router.get( '/oauth', asyncHandler(async (req, res, next) => { - const providers = env.OAUTH_PROVIDERS as string[]; + const providers = toArray(env.OAUTH_PROVIDERS); res.locals.payload = { data: providers.length > 0 ? providers : null }; return next(); }), diff --git a/api/src/database/run-ast.ts b/api/src/database/run-ast.ts index 02cd54078f..41d300864a 100644 --- a/api/src/database/run-ast.ts +++ b/api/src/database/run-ast.ts @@ -6,6 +6,7 @@ import { Query, Item } from '../types'; import { PayloadService } from '../services/payload'; import applyQuery from '../utils/apply-query'; import Knex, { QueryBuilder } from 'knex'; +import { toArray } from '../utils/to-array'; type RunASTOptions = { query?: AST['query']; @@ -172,7 +173,7 @@ function applyParentFilters( nestedCollectionNodes: NestedCollectionNode[], parentItem: Item | Item[] ) { - const parentItems = Array.isArray(parentItem) ? parentItem : [parentItem]; + const parentItems = toArray(parentItem); for (const nestedNode of nestedCollectionNodes) { if (!nestedNode.relation) continue; @@ -245,8 +246,8 @@ function mergeWithParentItems( nestedNode: NestedCollectionNode, o2mLimit?: number | null ) { - const nestedItems = Array.isArray(nestedItem) ? nestedItem : [nestedItem]; - const parentItems = clone(Array.isArray(parentItem) ? parentItem : [parentItem]); + const nestedItems = toArray(nestedItem); + const parentItems = clone(toArray(parentItem)); if (nestedNode.type === 'm2o') { for (const parentItem of parentItems) { @@ -307,7 +308,7 @@ function removeTemporaryFields( primaryKeyField: string, parentItem?: Item ): null | Item | Item[] { - const rawItems = cloneDeep(Array.isArray(rawItem) ? rawItem : [rawItem]); + const rawItems = cloneDeep(toArray(rawItem)); const items: Item[] = []; if (ast.type === 'm2a') { diff --git a/api/src/middleware/error-handler.ts b/api/src/middleware/error-handler.ts index dbb896f6ee..c2b71b9857 100644 --- a/api/src/middleware/error-handler.ts +++ b/api/src/middleware/error-handler.ts @@ -2,13 +2,14 @@ import { ErrorRequestHandler } from 'express'; import { BaseException } from '../exceptions'; import logger from '../logger'; import env from '../env'; +import { toArray } from '../utils/to-array'; const errorHandler: ErrorRequestHandler = (err, req, res, next) => { let payload: any = { errors: [], }; - const errors = Array.isArray(err) ? err : [err]; + const errors = toArray(err); if (errors.some((err) => err instanceof BaseException === false)) { res.status(500); diff --git a/api/src/services/authorization.ts b/api/src/services/authorization.ts index e6f46ca3a0..0b526b4385 100644 --- a/api/src/services/authorization.ts +++ b/api/src/services/authorization.ts @@ -18,6 +18,7 @@ import { uniq, merge, flatten } from 'lodash'; import generateJoi from '../utils/generate-joi'; import { ItemsService } from './items'; import { parseFilter } from '../utils/parse-filter'; +import { toArray } from '../utils/to-array'; export class AuthorizationService { knex: Knex; @@ -199,7 +200,7 @@ export class AuthorizationService { ): Promise[] | Partial> { const validationErrors: FailedValidationException[] = []; - let payloads = Array.isArray(payload) ? payload : [payload]; + let payloads = toArray(payload); let permission: Permission | undefined; diff --git a/api/src/services/collections.ts b/api/src/services/collections.ts index f06adabe63..cb0718cc2d 100644 --- a/api/src/services/collections.ts +++ b/api/src/services/collections.ts @@ -6,6 +6,7 @@ import SchemaInspector from 'knex-schema-inspector'; import { FieldsService } from '../services/fields'; import { ItemsService } from '../services/items'; import cache from '../cache'; +import { toArray } from '../utils/to-array'; export class CollectionsService { knex: Knex; @@ -23,7 +24,7 @@ export class CollectionsService { throw new ForbiddenException('Only admins can perform this action.'); } - const payloads = (Array.isArray(data) ? data : [data]).map((collection) => { + const payloads = toArray(data).map((collection) => { if (!collection.fields) collection.fields = []; collection.fields = collection.fields.map((field) => { @@ -105,7 +106,7 @@ export class CollectionsService { knex: this.knex, accountability: this.accountability, }); - const collectionKeys = Array.isArray(collection) ? collection : [collection]; + const collectionKeys = toArray(collection); if (this.accountability && this.accountability.admin !== true) { const permissions = await this.knex @@ -212,7 +213,7 @@ export class CollectionsService { throw new InvalidPayloadException(`"meta" key is required`); } - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); for (const key of keys) { const exists = @@ -232,7 +233,7 @@ export class CollectionsService { return key; } - const payloads = Array.isArray(data) ? data : [data]; + const payloads = toArray(data); const collectionUpdates = payloads.map((collection) => { return { @@ -264,7 +265,7 @@ export class CollectionsService { const tablesInDatabase = await schemaInspector.tables(); - const collectionKeys = Array.isArray(collection) ? collection : [collection]; + const collectionKeys = toArray(collection); for (const collectionKey of collectionKeys) { if (tablesInDatabase.includes(collectionKey) === false) { diff --git a/api/src/services/files.ts b/api/src/services/files.ts index 550a09fd5a..829877e379 100644 --- a/api/src/services/files.ts +++ b/api/src/services/files.ts @@ -9,6 +9,7 @@ import { AbstractServiceOptions, File, PrimaryKey } from '../types'; import { clone } from 'lodash'; import cache from '../cache'; import { ForbiddenException } from '../exceptions'; +import { toArray } from '../utils/to-array'; export class FilesService extends ItemsService { constructor(options?: AbstractServiceOptions) { @@ -89,14 +90,14 @@ export class FilesService extends ItemsService { delete(key: PrimaryKey): Promise; delete(keys: PrimaryKey[]): Promise; async delete(key: PrimaryKey | PrimaryKey[]): Promise { - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); let files = await super.readByKey(keys, { fields: ['id', 'storage'] }); if (!files) { throw new ForbiddenException(); } - files = Array.isArray(files) ? files : [files]; + files = toArray(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 e8ff95a797..515f5a0cda 100644 --- a/api/src/services/items.ts +++ b/api/src/services/items.ts @@ -16,6 +16,7 @@ import Knex from 'knex'; import cache from '../cache'; import emitter from '../emitter'; import logger from '../logger'; +import { toArray } from '../utils/to-array'; import { PayloadService } from './payload'; import { AuthorizationService } from './authorization'; @@ -50,7 +51,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(Array.isArray(data) ? data : [data]); + let payloads = clone(toArray(data)); const savedPrimaryKeys = await this.knex.transaction(async (trx) => { const payloadService = new PayloadService(this.collection, { @@ -224,7 +225,7 @@ export class ItemsService implements AbstractService { ): Promise { query = clone(query); const primaryKeyField = await this.schemaInspector.primary(this.collection); - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); if (keys.length === 1) { query.single = true; @@ -270,7 +271,7 @@ export class ItemsService implements AbstractService { // Updating one or more items to the same payload if (data && key) { - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); let payload = clone(data); @@ -404,7 +405,7 @@ export class ItemsService implements AbstractService { knex: trx, }); - const payloads = Array.isArray(data) ? data : [data]; + const payloads = toArray(data); for (const single of payloads as Partial[]) { let payload = clone(single); @@ -432,7 +433,7 @@ export class ItemsService implements AbstractService { const itemsService = new ItemsService(this.collection, { knex: this.knex }); let itemsToUpdate = await itemsService.readByQuery(readQuery); - itemsToUpdate = Array.isArray(itemsToUpdate) ? itemsToUpdate : [itemsToUpdate]; + itemsToUpdate = toArray(itemsToUpdate); const keys: PrimaryKey[] = itemsToUpdate.map( (item: Partial) => item[primaryKeyField] @@ -445,7 +446,7 @@ export class ItemsService implements AbstractService { upsert(data: Partial): Promise; async upsert(data: Partial | Partial[]): Promise { const primaryKeyField = await this.schemaInspector.primary(this.collection); - const payloads = Array.isArray(data) ? data : [data]; + const payloads = toArray(data); const primaryKeys: PrimaryKey[] = []; for (const payload of payloads) { @@ -473,7 +474,7 @@ export class ItemsService implements AbstractService { delete(key: PrimaryKey): Promise; delete(keys: PrimaryKey[]): Promise; async delete(key: PrimaryKey | PrimaryKey[]): Promise { - const keys = (Array.isArray(key) ? key : [key]) as PrimaryKey[]; + const keys = toArray(key); const primaryKeyField = await this.schemaInspector.primary(this.collection); if (this.accountability && this.accountability.admin !== true) { @@ -537,7 +538,7 @@ export class ItemsService implements AbstractService { const itemsService = new ItemsService(this.collection); let itemsToDelete = await itemsService.readByQuery(readQuery); - itemsToDelete = Array.isArray(itemsToDelete) ? itemsToDelete : [itemsToDelete]; + itemsToDelete = toArray(itemsToDelete); const keys: PrimaryKey[] = itemsToDelete.map( (item: Partial) => item[primaryKeyField] diff --git a/api/src/services/payload.ts b/api/src/services/payload.ts index 0927a07b64..a97993673b 100644 --- a/api/src/services/payload.ts +++ b/api/src/services/payload.ts @@ -16,6 +16,7 @@ import SchemaInspector from 'knex-schema-inspector'; import getLocalType from '../utils/get-local-type'; import { format, formatISO } from 'date-fns'; import { ForbiddenException } from '../exceptions'; +import { toArray } from '../utils/to-array'; type Action = 'create' | 'read' | 'update'; @@ -141,7 +142,7 @@ export class PayloadService { action: Action, payload: Partial | Partial[] ): Promise | Partial[]> { - let processedPayload = (Array.isArray(payload) ? payload : [payload]) as Partial[]; + let processedPayload = toArray(payload); if (processedPayload.length === 0) return []; @@ -338,7 +339,7 @@ export class PayloadService { .from('directus_relations') .where({ one_collection: this.collection }); - const payloads = clone(Array.isArray(payload) ? payload : [payload]); + const payloads = clone(toArray(payload)); for (let i = 0; i < payloads.length; i++) { let payload = payloads[i]; diff --git a/api/src/services/relations.ts b/api/src/services/relations.ts index 75ff2fb619..c8c17a182f 100644 --- a/api/src/services/relations.ts +++ b/api/src/services/relations.ts @@ -1,6 +1,7 @@ import { ItemsService } from './items'; import { AbstractServiceOptions, Query, PrimaryKey, PermissionsAction, Relation } from '../types'; import { PermissionsService } from './permissions'; +import { toArray } from '../utils/to-array'; /** * @TODO update foreign key constraints when relations are updated @@ -62,7 +63,7 @@ export class RelationsService extends ItemsService { 'read' ); - relations = Array.isArray(relations) ? relations : [relations]; + relations = toArray(relations); return relations.filter((relation) => { let collectionsAllowed = true; diff --git a/api/src/services/roles.ts b/api/src/services/roles.ts index e3556eb3f8..7bf9bd4f54 100644 --- a/api/src/services/roles.ts +++ b/api/src/services/roles.ts @@ -4,6 +4,7 @@ import { PermissionsService } from './permissions'; import { UsersService } from './users'; import { PresetsService } from './presets'; import { UnprocessableEntityException } from '../exceptions'; +import { toArray } from '../utils/to-array'; export class RolesService extends ItemsService { constructor(options?: AbstractServiceOptions) { @@ -13,7 +14,7 @@ export class RolesService extends ItemsService { delete(key: PrimaryKey): Promise; delete(keys: PrimaryKey[]): Promise; async delete(key: PrimaryKey | PrimaryKey[]): Promise { - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); // Make sure there's at least one admin role left after this deletion is done const otherAdminRoles = await this.knex diff --git a/api/src/services/users.ts b/api/src/services/users.ts index d2ccdcd6bb..52022996e8 100644 --- a/api/src/services/users.ts +++ b/api/src/services/users.ts @@ -13,6 +13,7 @@ import { Accountability, PrimaryKey, Item, AbstractServiceOptions } from '../typ import Knex from 'knex'; import env from '../env'; import cache from '../cache'; +import { toArray } from '../utils/to-array'; export class UsersService extends ItemsService { knex: Knex; @@ -39,7 +40,7 @@ export class UsersService extends ItemsService { * This is just an extra bit of hardcoded security. We don't want anybody to be able to disable 2fa through * the regular /users endpoint. Period. You should only be able to manage the 2fa status through the /tfa endpoint. */ - const payloads = Array.isArray(data) ? data : [data]; + const payloads = toArray(data); for (const payload of payloads) { if (payload.hasOwnProperty('tfa_secret')) { @@ -57,7 +58,7 @@ export class UsersService extends ItemsService { delete(key: PrimaryKey): Promise; delete(keys: PrimaryKey[]): Promise; async delete(key: PrimaryKey | PrimaryKey[]): Promise { - const keys = Array.isArray(key) ? key : [key]; + const keys = toArray(key); // Make sure there's at least one admin user left after this deletion is done const otherAdminUsers = await this.knex diff --git a/api/src/storage.ts b/api/src/storage.ts index bfb14ad532..074221e2ba 100644 --- a/api/src/storage.ts +++ b/api/src/storage.ts @@ -7,6 +7,7 @@ import { import env from './env'; import { validateEnv } from './utils/validate-env'; import { getConfigFromEnv } from './utils/get-config-from-env'; +import { toArray } from './utils/to-array'; /** @todo dynamically load these storage adapters */ import { AmazonWebServicesS3Storage } from '@slynova/flydrive-s3'; @@ -25,7 +26,7 @@ function getStorageConfig(): StorageManagerConfig { disks: {}, }; - const locations = env.STORAGE_LOCATIONS as string[]; + const locations = toArray(env.STORAGE_LOCATIONS); locations.forEach((location: string) => { location = location.trim(); diff --git a/api/src/utils/to-array.ts b/api/src/utils/to-array.ts new file mode 100644 index 0000000000..77cf11fae2 --- /dev/null +++ b/api/src/utils/to-array.ts @@ -0,0 +1,3 @@ +export function toArray(val: T | T[]): T[] { + return Array.isArray(val) ? val : [val]; +}