From 37658802b74519d7864db843fcd33d2b2e5aa717 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Fri, 14 Apr 2023 17:30:56 -0400 Subject: [PATCH] One more --- .eslintrc.js | 5 +++++ api/src/__utils__/items-utils.ts | 2 ++ api/src/auth/drivers/oauth2.ts | 1 + api/src/auth/drivers/openid.ts | 2 ++ api/src/cli/commands/schema/apply.ts | 2 ++ api/src/cli/commands/users/passwd.ts | 1 + api/src/controllers/assets.ts | 1 + api/src/controllers/fields.ts | 1 + api/src/controllers/not-found.ts | 1 + api/src/database/helpers/schema/dialects/cockroachdb.ts | 1 + api/src/database/index.ts | 3 +++ .../migrations/20220322A-rename-field-typecast-flags.ts | 1 + api/src/database/migrations/run.ts | 1 + api/src/database/run-ast.ts | 2 ++ api/src/emitter.ts | 1 + api/src/env.ts | 1 + api/src/extensions.ts | 5 +++++ api/src/logger.ts | 2 ++ api/src/middleware/check-ip.ts | 1 + api/src/middleware/rate-limiter-global.ts | 2 ++ api/src/middleware/rate-limiter-ip.ts | 1 + api/src/services/authorization.ts | 1 + api/src/services/collections.ts | 1 + api/src/services/fields.ts | 1 + api/src/services/files.ts | 2 ++ api/src/services/graphql/index.ts | 5 +++++ api/src/services/import-export.ts | 1 + api/src/services/payload.ts | 5 +++++ api/src/utils/get-ast-from-query.ts | 1 + api/src/utils/should-skip-cache.ts | 2 ++ api/src/utils/validate-snapshot.ts | 1 + app/src/components/v-checkbox-tree/v-checkbox-tree.vue | 2 ++ app/src/components/v-field-template/v-field-template.vue | 1 + app/src/components/v-form/form-field.vue | 1 + app/src/components/v-form/v-form.vue | 4 ++++ app/src/components/v-template-input.vue | 6 ++++++ app/src/composables/use-form-fields.ts | 1 + app/src/composables/use-item.ts | 2 ++ app/src/composables/use-permissions.ts | 1 + app/src/composables/use-relation-multiple.ts | 2 ++ app/src/composables/use-tfa-setup.ts | 1 + app/src/composables/use-translation-strings.ts | 1 + app/src/displays/datetime/datetime.vue | 1 + app/src/idle.ts | 1 + .../_system/system-collection/system-collection.vue | 1 + .../_system/system-collections/system-collections.vue | 1 + app/src/interfaces/_system/system-filter/input-group.vue | 3 +++ app/src/interfaces/_system/system-filter/nodes.vue | 1 + .../interfaces/_system/system-filter/system-filter.vue | 1 + .../_system/system-raw-editor/system-raw-editor.vue | 1 + app/src/interfaces/files/files.vue | 1 + app/src/interfaces/group-accordion/accordion-section.vue | 1 + app/src/interfaces/group-accordion/group-accordion.vue | 1 + app/src/interfaces/input-rich-text-html/useLink.ts | 1 + app/src/interfaces/list-m2m/list-m2m.vue | 1 + app/src/interfaces/list-o2m/list-o2m.vue | 1 + app/src/interfaces/map/map.vue | 8 ++++++++ app/src/layouts/calendar/index.ts | 2 ++ app/src/layouts/map/components/map.vue | 6 ++++++ app/src/layouts/map/index.ts | 3 +++ app/src/layouts/tabular/index.ts | 1 + app/src/modules/content/index.ts | 1 + app/src/modules/content/routes/item.vue | 1 + app/src/modules/files/routes/item.vue | 1 + app/src/modules/insights/routes/dashboard.vue | 1 + app/src/modules/settings/index.ts | 1 + .../field-detail-advanced-display.vue | 2 ++ .../field-detail-advanced-interface.vue | 2 ++ .../field-detail-advanced-relationship-m2a.vue | 1 + .../field-detail-advanced-relationship-m2m.vue | 1 + .../field-detail-advanced-relationship-o2m.vue | 1 + .../data-model/field-detail/store/alterations/global.ts | 1 + .../data-model/field-detail/store/alterations/standard.ts | 1 + .../field-detail/store/alterations/translations.ts | 1 + .../routes/data-model/field-detail/store/index.ts | 3 +++ .../modules/settings/routes/flows/components/arrows.vue | 4 ++++ .../settings/routes/flows/components/operation.vue | 1 + app/src/modules/settings/routes/flows/constants.ts | 1 + app/src/modules/settings/routes/flows/flow.vue | 2 ++ .../roles/item/composables/use-update-permissions.ts | 1 + .../translation-strings/translation-strings-drawer.vue | 2 ++ app/src/modules/users/routes/item.vue | 1 + app/src/panels/metric/panel-metric.vue | 2 ++ .../relational-variable/panel-relational-variable.vue | 2 ++ app/src/stores/fields.ts | 1 + app/src/stores/insights.ts | 2 ++ app/src/utils/geometry/basemap.ts | 4 ++++ app/src/utils/geometry/index.ts | 2 ++ app/src/utils/get-js-type.test.ts | 8 ++++++++ app/src/utils/get-special-for-type.test.ts | 3 +++ app/src/utils/get-vue-component-name.ts | 1 + app/src/views/private/components/comment-input.vue | 1 + packages/composables/src/use-filter-fields.ts | 1 + packages/schema/src/dialects/cockroachdb.ts | 1 + packages/schema/src/dialects/mssql.ts | 1 + packages/schema/src/dialects/mysql.ts | 2 ++ packages/schema/src/dialects/postgres.ts | 1 + packages/schema/src/dialects/sqlite.ts | 1 + packages/utils/node/ensure-extension-dirs.ts | 1 + packages/utils/shared/define-extension.test.ts | 2 ++ packages/utils/shared/generate-joi.ts | 2 ++ packages/utils/shared/parse-filter-function-path.ts | 1 + packages/utils/shared/parse-filter.ts | 1 + packages/utils/shared/validate-payload.ts | 1 + tests/blackbox/common/seed-functions.ts | 1 + tests/blackbox/query/filter/index.ts | 3 +++ tests/blackbox/routes/collections/crud.test.ts | 1 + tests/blackbox/routes/fields/crud.test.ts | 3 +++ tests/blackbox/routes/items/m2a.test.ts | 6 ++++++ tests/blackbox/routes/items/m2m.test.ts | 6 ++++++ tests/blackbox/routes/items/m2o.test.ts | 1 + tests/blackbox/routes/items/no-relation.test.ts | 3 +++ tests/blackbox/routes/items/o2m.test.ts | 6 ++++++ tests/blackbox/routes/items/seed-all-field-types.ts | 2 ++ tests/blackbox/routes/items/seed-relational-fields.ts | 1 + tests/blackbox/routes/items/singleton.test.ts | 1 + tests/blackbox/routes/schema/schema.test.ts | 1 + tests/blackbox/setup/setup.ts | 2 ++ tests/blackbox/utils/prepare-request.ts | 1 + 119 files changed, 224 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 0a11117cf3..140d1c8bef 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,11 @@ const defaultRules = { prev: ['block', 'block-like', 'cjs-export', 'class', 'export', 'import'], next: '*', }, + { + blankLine: 'always', + prev: ['const', 'let'], + next: ['block', 'block-like', 'cjs-export', 'class', 'export', 'import'], + }, { blankLine: 'any', prev: ['export', 'import'], next: ['export', 'import'] }, ], 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }], diff --git a/api/src/__utils__/items-utils.ts b/api/src/__utils__/items-utils.ts index e37348ee60..5955527e83 100644 --- a/api/src/__utils__/items-utils.ts +++ b/api/src/__utils__/items-utils.ts @@ -1,6 +1,7 @@ // dynamically adds fields to the sql strings as the schema grows export const sqlFieldFormatter = (schema: Record, table: string) => { const fields = []; + // Exclude alias fields, unable to selected in DB for (const field of Object.keys(schema['collections'][table].fields)) { if (schema['collections'][table].fields[field].type !== 'alias') { @@ -20,6 +21,7 @@ export const sqlFieldFormatter = (schema: Record, table: string) => export const sqlFieldList = (schema: Record, table: string) => { const fields = []; + // Exclude alias fields, unable to selected in DB for (const field of Object.keys(schema['collections'][table].fields)) { if (schema['collections'][table].fields[field].type !== 'alias') { diff --git a/api/src/auth/drivers/oauth2.ts b/api/src/auth/drivers/oauth2.ts index bd341c3d80..40ecea8c2a 100644 --- a/api/src/auth/drivers/oauth2.ts +++ b/api/src/auth/drivers/oauth2.ts @@ -223,6 +223,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver { if (authData?.['refreshToken']) { try { const tokenSet = await this.client.refresh(authData['refreshToken']); + // Update user refreshToken if provided if (tokenSet.refresh_token) { await this.usersService.updateOne(user.id, { diff --git a/api/src/auth/drivers/openid.ts b/api/src/auth/drivers/openid.ts index 6ceeeb0ad4..998ee6f493 100644 --- a/api/src/auth/drivers/openid.ts +++ b/api/src/auth/drivers/openid.ts @@ -59,6 +59,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver { Issuer.discover(issuerUrl) .then((issuer) => { const supportedTypes = issuer.metadata['response_types_supported'] as string[] | undefined; + if (!supportedTypes?.includes('code')) { reject( new InvalidConfigException('OpenID provider does not support required code flow', { @@ -249,6 +250,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver { try { const client = await this.client; const tokenSet = await client.refresh(authData['refreshToken']); + // Update user refreshToken if provided if (tokenSet.refresh_token) { await this.usersService.updateOne(user.id, { diff --git a/api/src/cli/commands/schema/apply.ts b/api/src/cli/commands/schema/apply.ts index 618f3d8852..fa6cbc1802 100644 --- a/api/src/cli/commands/schema/apply.ts +++ b/api/src/cli/commands/schema/apply.ts @@ -51,6 +51,7 @@ export async function apply(snapshotPath: string, options?: { yes: boolean; dryR const dryRun = options?.dryRun === true; const promptForChanges = !dryRun && options?.yes !== true; + if (dryRun || promptForChanges) { let message = ''; @@ -86,6 +87,7 @@ export async function apply(snapshotPath: string, options?: { yes: boolean; dryR for (const change of diff) { const path = change.path!.slice(1).join('.'); + if (change.kind === DiffKind.EDIT) { message += `\n - Set ${path} to ${change.rhs}`; } else if (change.kind === DiffKind.DELETE) { diff --git a/api/src/cli/commands/users/passwd.ts b/api/src/cli/commands/users/passwd.ts index d1dbba1eaf..14e53ce580 100644 --- a/api/src/cli/commands/users/passwd.ts +++ b/api/src/cli/commands/users/passwd.ts @@ -22,6 +22,7 @@ export default async function usersPasswd({ email, password }: { email?: string; .from('directus_users') .whereRaw('LOWER(??) = ?', ['email', email.toLowerCase()]) .first(); + if (user) { await service.knex('directus_users').update({ password: passwordHashed }).where({ id: user.id }); logger.info(`Password is updated for user ${user.id}`); diff --git a/api/src/controllers/assets.ts b/api/src/controllers/assets.ts index 6bfc3eba79..6319d70d7a 100644 --- a/api/src/controllers/assets.ts +++ b/api/src/controllers/assets.ts @@ -186,6 +186,7 @@ router.get( res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['ASSETS_CACHE_TTL']), false, true)); const unixTime = Date.parse(file.modified_on); + if (!Number.isNaN(unixTime)) { const lastModifiedDate = new Date(unixTime); res.setHeader('Last-Modified', lastModifiedDate.toUTCString()); diff --git a/api/src/controllers/fields.ts b/api/src/controllers/fields.ts index a080d68e82..4073762ea9 100644 --- a/api/src/controllers/fields.ts +++ b/api/src/controllers/fields.ts @@ -133,6 +133,7 @@ router.patch( try { const results: any = []; + for (const field of req.body) { const updatedField = await service.readOne(req.params['collection']!, field.field); results.push(updatedField); diff --git a/api/src/controllers/not-found.ts b/api/src/controllers/not-found.ts index a38897d569..7de3d7735f 100644 --- a/api/src/controllers/not-found.ts +++ b/api/src/controllers/not-found.ts @@ -26,6 +26,7 @@ const notFound: RequestHandler = async (req, res, next) => { accountability: req.accountability ?? null, } ); + if (hooksResult) { return next(); } diff --git a/api/src/database/helpers/schema/dialects/cockroachdb.ts b/api/src/database/helpers/schema/dialects/cockroachdb.ts index 07a8df2b84..bde511190f 100644 --- a/api/src/database/helpers/schema/dialects/cockroachdb.ts +++ b/api/src/database/helpers/schema/dialects/cockroachdb.ts @@ -13,6 +13,7 @@ export class SchemaHelperCockroachDb extends SchemaHelper { override constraintName(existingName: string): string { const suffix = '_replaced'; + // CockroachDB does not allow for dropping/creating constraints with the same // name in a single transaction. reference issue #14873 if (existingName.endsWith(suffix)) { diff --git a/api/src/database/index.ts b/api/src/database/index.ts index 9631196ace..cd0ae7e7de 100644 --- a/api/src/database/index.ts +++ b/api/src/database/index.ts @@ -284,6 +284,7 @@ export async function validateDatabaseExtensions(): Promise { const client = getDatabaseClient(database); const helpers = getHelpers(database); const geometrySupport = await helpers.st.supported(); + if (!geometrySupport) { switch (client) { case 'postgres': @@ -314,9 +315,11 @@ async function validateDatabaseCharset(database?: Knex): Promise { .whereNot({ COLLATION_NAME: collation }); let inconsistencies = ''; + for (const table of tables) { const tableColumns = columns.filter((column) => column.table_name === table.name); const tableHasInvalidCollation = table.collation !== collation; + if (tableHasInvalidCollation || tableColumns.length > 0) { inconsistencies += `\t\t- Table "${table.name}": "${table.collation}"\n`; diff --git a/api/src/database/migrations/20220322A-rename-field-typecast-flags.ts b/api/src/database/migrations/20220322A-rename-field-typecast-flags.ts index 7fb92616ed..99b3f39805 100644 --- a/api/src/database/migrations/20220322A-rename-field-typecast-flags.ts +++ b/api/src/database/migrations/20220322A-rename-field-typecast-flags.ts @@ -51,6 +51,7 @@ export async function down(knex: Knex): Promise { for (const { id, special } of fields) { let parsedSpecial; + try { parsedSpecial = toArray(special); } catch { diff --git a/api/src/database/migrations/run.ts b/api/src/database/migrations/run.ts index 515cf7b5fa..fe302aef99 100644 --- a/api/src/database/migrations/run.ts +++ b/api/src/database/migrations/run.ts @@ -29,6 +29,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la ].sort((a, b) => (a.version! > b.version! ? 1 : -1)); const migrationKeys = new Set(migrations.map((m) => m.version)); + if (migrations.length > migrationKeys.size) { throw new Error('Migration keys collide! Please ensure that every migration uses a unique key.'); } diff --git a/api/src/database/run-ast.ts b/api/src/database/run-ast.ts index 0afee0d330..05da453f27 100644 --- a/api/src/database/run-ast.ts +++ b/api/src/database/run-ast.ts @@ -266,6 +266,7 @@ async function getDBQuery( if (queryCopy.sort) { const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, table, aliasMap, true); + if (sortResult) { sortRecords = sortResult.sortRecords; hasMultiRelationalSort = sortResult.hasMultiRelationalSort; @@ -300,6 +301,7 @@ async function getDBQuery( } const sortAlias = `sort_${generateAlias()}`; + if (sortRecord.column.includes('.')) { const [alias, field] = sortRecord.column.split('.'); const originalCollectionName = getCollectionFromAlias(alias!, aliasMap); diff --git a/api/src/emitter.ts b/api/src/emitter.ts index d4bf26a12c..31097ab068 100644 --- a/api/src/emitter.ts +++ b/api/src/emitter.ts @@ -35,6 +35,7 @@ export class Emitter { })); let updatedPayload = payload; + for (const { event, listeners } of eventListeners) { for (const listener of listeners) { const result = await listener(updatedPayload, { event, ...meta }, context); diff --git a/api/src/env.ts b/api/src/env.ts index c833d6394c..41a2597c6a 100644 --- a/api/src/env.ts +++ b/api/src/env.ts @@ -440,6 +440,7 @@ function processValues(env: Record) { // If key ends with '_FILE', try to get the value from the file defined in this variable // and store it in the variable with the same name but without '_FILE' at the end let newKey: string | undefined; + if (key.length > 5 && key.endsWith('_FILE')) { newKey = key.slice(0, -5); if (allowedEnvironmentVars.some((pattern) => pattern.test(newKey as string))) { diff --git a/api/src/extensions.ts b/api/src/extensions.ts index dc69c19564..4a26497c5f 100644 --- a/api/src/extensions.ts +++ b/api/src/extensions.ts @@ -147,6 +147,7 @@ class ExtensionManager { await this.load(); const loadedExtensions = this.getExtensionsList(); + if (loadedExtensions.length > 0) { logger.info(`Loaded extensions: ${loadedExtensions.map((ext) => ext.name).join(', ')}`); } @@ -178,6 +179,7 @@ class ExtensionManager { const addedExtensions = added.map((extension) => extension.name); const removedExtensions = removed.map((extension) => extension.name); + if (addedExtensions.length > 0) { logger.info(`Added extensions: ${addedExtensions.join(', ')}`); } @@ -391,6 +393,7 @@ class ExtensionManager { const appDir = await readdir(path.join(resolvePackage('@directus/app', __dirname), 'dist', 'assets')); const depsMapping: Record = {}; + for (const dep of deps) { const depRegex = new RegExp(`${escapeRegExp(dep.replace(/\//g, '_'))}\\.[0-9a-f]{8}\\.entry\\.js`); const depName = appDir.find((file) => depRegex.test(file)); @@ -409,6 +412,7 @@ class ExtensionManager { private async registerHooks(): Promise { const hooks = this.extensions.filter((extension): extension is ApiExtension => extension.type === 'hook'); + for (const hook of hooks) { try { const hookPath = path.resolve(hook.path, hook.entrypoint); @@ -563,6 +567,7 @@ class ExtensionManager { }, embed: (position: 'head' | 'body', code: string | EmbedHandler) => { const content = typeof code === 'function' ? code() : code; + if (content.trim().length === 0) { logger.warn(`Couldn't register embed hook. Provided code is empty!`); return; diff --git a/api/src/logger.ts b/api/src/logger.ts index 669e27fa6f..2c89c89095 100644 --- a/api/src/logger.ts +++ b/api/src/logger.ts @@ -16,6 +16,7 @@ const pinoOptions: LoggerOptions = { censor: REDACT_TEXT, }, }; + export const httpLoggerOptions: LoggerOptions = { level: env['LOG_LEVEL'] || 'info', redact: { @@ -51,6 +52,7 @@ if (env['LOG_STYLE'] === 'raw') { paths: ['req.headers.authorization', 'req.headers.cookie', 'res.headers'], censor: (value, pathParts) => { const path = pathParts.join('.'); + if (path === 'res.headers') { if ('set-cookie' in value) { value['set-cookie'] = REDACT_TEXT; diff --git a/api/src/middleware/check-ip.ts b/api/src/middleware/check-ip.ts index 53aeb3fb42..202c24ba03 100644 --- a/api/src/middleware/check-ip.ts +++ b/api/src/middleware/check-ip.ts @@ -7,6 +7,7 @@ export const checkIP: RequestHandler = asyncHandler(async (req, _res, next) => { const database = getDatabase(); const query = database.select('ip_access').from('directus_roles'); + if (req.accountability!.role) { query.where({ id: req.accountability!.role }); } else { diff --git a/api/src/middleware/rate-limiter-global.ts b/api/src/middleware/rate-limiter-global.ts index 189b234e7f..1b2ffc970a 100644 --- a/api/src/middleware/rate-limiter-global.ts +++ b/api/src/middleware/rate-limiter-global.ts @@ -11,6 +11,7 @@ import { validateEnv } from '../utils/validate-env.js'; const RATE_LIMITER_GLOBAL_KEY = 'global-rate-limit'; let checkRateLimit: RequestHandler = (_req, _res, next) => next(); + export let rateLimiterGlobal: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory; if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) { @@ -47,6 +48,7 @@ function validateConfiguration() { const globalPointsPerSec = Number(env['RATE_LIMITER_GLOBAL_POINTS']) / Math.max(Number(env['RATE_LIMITER_GLOBAL_DURATION']), 1); const regularPointsPerSec = Number(env['RATE_LIMITER_POINTS']) / Math.max(Number(env['RATE_LIMITER_DURATION']), 1); + if (globalPointsPerSec <= regularPointsPerSec) { logger.error(`The global rate limiter needs to allow more requests per second than the IP based rate limiter.`); process.exit(1); diff --git a/api/src/middleware/rate-limiter-ip.ts b/api/src/middleware/rate-limiter-ip.ts index 7e9d91d2b3..64979df622 100644 --- a/api/src/middleware/rate-limiter-ip.ts +++ b/api/src/middleware/rate-limiter-ip.ts @@ -9,6 +9,7 @@ import { getIPFromReq } from '../utils/get-ip-from-req.js'; import { validateEnv } from '../utils/validate-env.js'; let checkRateLimit: RequestHandler = (_req, _res, next) => next(); + export let rateLimiter: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory; if (env['RATE_LIMITER_ENABLED'] === true) { diff --git a/api/src/services/authorization.ts b/api/src/services/authorization.ts index 768271da0a..86f5b29d93 100644 --- a/api/src/services/authorization.ts +++ b/api/src/services/authorization.ts @@ -251,6 +251,7 @@ export class AuthorizationService { (result[collection] || (result[collection] = new Set())).add(filterKey); // add virtual relation to the required permissions const { relation } = getRelationInfo([], collection, filterKey); + if (relation?.collection && relation?.field) { (result[relation.collection] || (result[relation.collection] = new Set())).add(relation.field); } diff --git a/api/src/services/collections.ts b/api/src/services/collections.ts index f5876fcb15..15fd9f1915 100644 --- a/api/src/services/collections.ts +++ b/api/src/services/collections.ts @@ -116,6 +116,7 @@ export class CollectionsService { // Add flag for specific database type overrides const flagToAdd = this.helpers.date.fieldFlagForField(field.type); + if (flagToAdd) { addFieldFlag(field, flagToAdd); } diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index 4719156b61..25ab8bf0e2 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -268,6 +268,7 @@ export class FieldsService { // Add flag for specific database type overrides const flagToAdd = this.helpers.date.fieldFlagForField(field.type); + if (flagToAdd) { addFieldFlag(field, flagToAdd); } diff --git a/api/src/services/files.ts b/api/src/services/files.ts index 0c0ea6d500..719a767cd6 100644 --- a/api/src/services/files.ts +++ b/api/src/services/files.ts @@ -170,9 +170,11 @@ export class FilesService extends ItemsService { iptc?: Record; xmp?: Record; } = {}; + if (sharpMetadata.exif) { try { const { image, thumbnail, interoperability, ...rest } = exif(sharpMetadata.exif); + if (image) { fullMetadata.ifd0 = image; } diff --git a/api/src/services/graphql/index.ts b/api/src/services/graphql/index.ts index 3008a1dc14..befeb6d822 100644 --- a/api/src/services/graphql/index.ts +++ b/api/src/services/graphql/index.ts @@ -2066,6 +2066,7 @@ export class GraphQLService { schema: this.schema, }); const result = await authenticationService.login(DEFAULT_AUTH_PROVIDER, args, args?.otp); + if (args['mode'] === 'cookie') { res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], { httpOnly: true, @@ -2105,11 +2106,13 @@ export class GraphQLService { schema: this.schema, }); const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; + if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); } const result = await authenticationService.refresh(currentRefreshToken); + if (args['mode'] === 'cookie') { res?.cookie(env['REFRESH_TOKEN_COOKIE_NAME'], result['refreshToken'], { httpOnly: true, @@ -2148,6 +2151,7 @@ export class GraphQLService { schema: this.schema, }); const currentRefreshToken = args['refresh_token'] || req?.cookies[env['REFRESH_TOKEN_COOKIE_NAME']]; + if (!currentRefreshToken) { throw new InvalidPayloadException(`"refresh_token" is required in either the JSON payload or Cookie`); } @@ -2262,6 +2266,7 @@ export class GraphQLService { schema: this.schema, }); const otpValid = await service.verifyOTP(this.accountability.user, args['otp']); + if (otpValid === false) { throw new InvalidPayloadException(`"otp" is invalid`); } diff --git a/api/src/services/import-export.ts b/api/src/services/import-export.ts index 41125586ca..42dbeb974c 100644 --- a/api/src/services/import-export.ts +++ b/api/src/services/import-export.ts @@ -140,6 +140,7 @@ export class ImportService { } else { try { const parsedJson = parseJSON(value); + if (typeof parsedJson === 'number') { set(result, key, value); } else { diff --git a/api/src/services/payload.ts b/api/src/services/payload.ts index ed89097a5b..963bcbde7c 100644 --- a/api/src/services/payload.ts +++ b/api/src/services/payload.ts @@ -205,6 +205,7 @@ export class PayloadService { processAggregates(payload: Partial[]) { const aggregateKeys = Object.keys(payload[0]!).filter((key) => key.includes('->')); + if (aggregateKeys.length) { for (const item of payload) { Object.assign(item, flat.unflatten(pick(item, aggregateKeys), { delimiter: '->' })); @@ -329,6 +330,7 @@ export class PayloadService { if (value instanceof Date === false && typeof value === 'string') { if (dateColumn.type === 'date') { const parsedDate = parseISO(value); + if (!isValid(parsedDate)) { throw new InvalidPayloadException(`Invalid Date format in field "${dateColumn.field}"`); } @@ -338,6 +340,7 @@ export class PayloadService { if (dateColumn.type === 'dateTime') { const parsedDate = parseISO(value); + if (!isValid(parsedDate)) { throw new InvalidPayloadException(`Invalid DateTime format in field "${dateColumn.field}"`); } @@ -595,8 +598,10 @@ export class PayloadService { // Nested array of individual items const field = payload[relation.meta!.one_field!]; + if (!field || Array.isArray(field)) { const updates = field || []; // treat falsey values as removing all children + for (let i = 0; i < updates.length; i++) { const relatedRecord = updates[i]; diff --git a/api/src/utils/get-ast-from-query.ts b/api/src/utils/get-ast-from-query.ts index 0f063ac1da..77c576c43f 100644 --- a/api/src/utils/get-ast-from-query.ts +++ b/api/src/utils/get-ast-from-query.ts @@ -310,6 +310,7 @@ export default async function getASTFromQuery( if (fieldKey === '*') { const aliases = Object.keys(query.alias ?? {}); + // Set to all fields in collection if (allowedFields.includes('*')) { fields.splice(index, 1, ...fieldsInCollection, ...aliases); diff --git a/api/src/utils/should-skip-cache.ts b/api/src/utils/should-skip-cache.ts index 6e543d1df9..228a49b3ff 100644 --- a/api/src/utils/should-skip-cache.ts +++ b/api/src/utils/should-skip-cache.ts @@ -13,8 +13,10 @@ export function shouldSkipCache(req: Request): boolean { // Always skip cache for requests coming from the data studio based on Referer header const referer = req.get('Referer'); + if (referer) { const adminUrl = new Url(env['PUBLIC_URL']).addPath('admin'); + if (adminUrl.isRootRelative()) { const refererUrl = new Url(referer); if (refererUrl.path.join('/').startsWith(adminUrl.path.join('/'))) return true; diff --git a/api/src/utils/validate-snapshot.ts b/api/src/utils/validate-snapshot.ts index 18241b201f..784b6fad9a 100644 --- a/api/src/utils/validate-snapshot.ts +++ b/api/src/utils/validate-snapshot.ts @@ -72,6 +72,7 @@ export function validateSnapshot(snapshot: Snapshot, force = false) { } const currentVendor = getDatabaseClient(); + if (snapshot.vendor !== currentVendor) { throw new InvalidPayloadException( `Provided snapshot's vendor ${snapshot.vendor} does not match the current instance's vendor ${currentVendor}. You can bypass this check by passing the "force" query parameter.` diff --git a/app/src/components/v-checkbox-tree/v-checkbox-tree.vue b/app/src/components/v-checkbox-tree/v-checkbox-tree.vue index 070d2f3da5..4f914d5343 100644 --- a/app/src/components/v-checkbox-tree/v-checkbox-tree.vue +++ b/app/src/components/v-checkbox-tree/v-checkbox-tree.vue @@ -142,8 +142,10 @@ function findSelectedChoices(choices: Record[], checked: (string | if (item[props.itemChildren]) { const children = item[props.itemChildren]; + if (Array.isArray(children) && children.length > 0) { const nestedResult = children.flatMap((child) => selectedChoices(child)); + if (nestedResult.length > 0) { result.push(...nestedResult, itemValue); } diff --git a/app/src/components/v-field-template/v-field-template.vue b/app/src/components/v-field-template/v-field-template.vue index c0f4bfaa6c..66dd7e9124 100644 --- a/app/src/components/v-field-template/v-field-template.vue +++ b/app/src/components/v-field-template/v-field-template.vue @@ -146,6 +146,7 @@ function onSelect() { for (let i = 0; i < contentEl.value.childNodes.length || !textSpan; i++) { const child = contentEl.value.children[i]; + if (child.classList.contains('text')) { textSpan = child; } diff --git a/app/src/components/v-form/form-field.vue b/app/src/components/v-form/form-field.vue index 1d5f985912..36091779b5 100644 --- a/app/src/components/v-form/form-field.vue +++ b/app/src/components/v-form/form-field.vue @@ -202,6 +202,7 @@ function useComputedValues() { () => props.modelValue, () => { const newVal = getInternalValue(); + if (!isEqual(internalValue.value, newVal)) { internalValue.value = newVal; } diff --git a/app/src/components/v-form/v-form.vue b/app/src/components/v-form/v-form.vue index a75cade19c..ca19276f1c 100644 --- a/app/src/components/v-form/v-form.vue +++ b/app/src/components/v-form/v-form.vue @@ -179,6 +179,7 @@ const { toggleRawField, rawActiveFields } = useRawEditor(); const firstEditableFieldIndex = computed(() => { for (let i = 0; i < fieldNames.value.length; i++) { const field = fieldsMap.value[fieldNames.value[i]]; + if (field?.meta && !field.meta?.readonly && !field.meta?.hidden) { return i; } @@ -190,6 +191,7 @@ const firstEditableFieldIndex = computed(() => { const firstVisibleFieldIndex = computed(() => { for (let i = 0; i < fieldNames.value.length; i++) { const field = fieldsMap.value[fieldNames.value[i]]; + if (field?.meta && !field.meta?.hidden) { return i; } @@ -223,6 +225,7 @@ function useForm() { () => props.fields, () => { const newVal = getFields(); + if (!isEqual(fields.value, newVal)) { fields.value = newVal; } @@ -278,6 +281,7 @@ function useForm() { for (const field of fieldsInGroup) { const meta = fieldsMap.value?.[field.field]?.meta; + if (meta?.special?.includes('group') && !passed.includes(meta!.field)) { passed.push(meta!.field); fieldsInGroup.push(...getFieldsForGroup(meta!.field, passed)); diff --git a/app/src/components/v-template-input.vue b/app/src/components/v-template-input.vue index e388be5d20..f000e69211 100644 --- a/app/src/components/v-template-input.vue +++ b/app/src/components/v-template-input.vue @@ -102,6 +102,7 @@ function checkKeyDown(event: any) { } } else if (event.code === 'ArrowLeft' && !event.shiftKey) { const checkCaretPos = matchedPositions.indexOf(caretPos - 1); + if (checkCaretPos !== -1 && checkCaretPos % 2 === 1) { event.preventDefault(); @@ -109,6 +110,7 @@ function checkKeyDown(event: any) { } } else if (event.code === 'ArrowRight' && !event.shiftKey) { const checkCaretPos = matchedPositions.indexOf(caretPos + 1); + if (checkCaretPos !== -1 && checkCaretPos % 2 === 0) { event.preventDefault(); @@ -116,6 +118,7 @@ function checkKeyDown(event: any) { } } else if (event.code === 'Backspace') { const checkCaretPos = matchedPositions.indexOf(caretPos - 1); + if (checkCaretPos !== -1 && checkCaretPos % 2 === 1) { event.preventDefault(); @@ -132,6 +135,7 @@ function checkKeyDown(event: any) { } } else if (event.code === 'Delete') { const checkCaretPos = matchedPositions.indexOf(caretPos + 1); + if (checkCaretPos !== -1 && checkCaretPos % 2 === 0) { event.preventDefault(); @@ -153,6 +157,7 @@ function checkKeyUp(event: any) { if ((event.code === 'ArrowUp' || event.code === 'ArrowDown') && !event.shiftKey) { const checkCaretPos = matchedPositions.indexOf(caretPos); + if (checkCaretPos !== -1 && checkCaretPos % 2 === 1) { position(input.value!, matchedPositions[checkCaretPos] + 1); } else if (checkCaretPos !== -1 && checkCaretPos % 2 === 0) { @@ -165,6 +170,7 @@ function checkClick(event: any) { const caretPos = window.getSelection()?.rangeCount ? position(input.value as Element).pos : 0; const checkCaretPos = matchedPositions.indexOf(caretPos); + if (checkCaretPos !== -1) { if (checkCaretPos % 2 === 0) { position(input.value!, caretPos - 1); diff --git a/app/src/composables/use-form-fields.ts b/app/src/composables/use-form-fields.ts index b68c89d8d7..3f90d20ccc 100644 --- a/app/src/composables/use-form-fields.ts +++ b/app/src/composables/use-form-fields.ts @@ -25,6 +25,7 @@ export function useFormFields(fields: Ref): { formFields: ComputedRef { @@ -320,6 +321,7 @@ export function useItem( ) { if (item[relatedPrimaryKeyField!.field] === updatedItem[relatedPrimaryKeyField!.field]) { const columns = fields.filter((s) => s.startsWith(relation.meta!.one_field!)); + for (const col of columns) { const colName = col.split('.')[1]; item[colName] = updatedItem[colName]; diff --git a/app/src/composables/use-permissions.ts b/app/src/composables/use-permissions.ts index 5e09c9905b..d720170cd5 100644 --- a/app/src/composables/use-permissions.ts +++ b/app/src/composables/use-permissions.ts @@ -61,6 +61,7 @@ export function usePermissions(collection: Ref, item: Ref, isNew: R // remove fields without read permissions so they don't show up in the DOM const readableFields = permissionsStore.getPermissionsForUser(collection.value, 'read')?.fields; + if (readableFields && readableFields.includes('*') === false) { fields = fields.filter((field) => readableFields.includes(field.field)); } diff --git a/app/src/composables/use-relation-multiple.ts b/app/src/composables/use-relation-multiple.ts index b0b93ce3e3..7bdff15ed5 100644 --- a/app/src/composables/use-relation-multiple.ts +++ b/app/src/composables/use-relation-multiple.ts @@ -339,6 +339,7 @@ export function useRelationMultiple( if (itemId.value !== '+') { const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] }; + if (previewQuery.value.filter) { filter._and.push(previewQuery.value.filter); } @@ -410,6 +411,7 @@ export function useRelationMultiple( } const filter: Filter = { _and: [{ [reverseJunctionField]: itemId.value } as Filter] }; + if (previewQuery.value.filter) { filter._and.push(previewQuery.value.filter); } diff --git a/app/src/composables/use-tfa-setup.ts b/app/src/composables/use-tfa-setup.ts index 8250e4e3e7..4517ea68ed 100644 --- a/app/src/composables/use-tfa-setup.ts +++ b/app/src/composables/use-tfa-setup.ts @@ -60,6 +60,7 @@ export function useTFASetup(initialEnabled: boolean) { loading.value = true; let success = false; + try { await api.post('/users/me/tfa/enable', { otp: otp.value, secret: secret.value }); success = true; diff --git a/app/src/composables/use-translation-strings.ts b/app/src/composables/use-translation-strings.ts index e7b17abfb9..e6731e697c 100644 --- a/app/src/composables/use-translation-strings.ts +++ b/app/src/composables/use-translation-strings.ts @@ -117,6 +117,7 @@ export function useTranslationStrings(): UsableTranslationStrings { })); const { currentUser } = useUserStore(); + if (currentUser && 'language' in currentUser && currentUser.language) { mergeTranslationStringsForLanguage(currentUser.language); } else { diff --git a/app/src/displays/datetime/datetime.vue b/app/src/displays/datetime/datetime.vue index 3e912aabbf..c41d3cffd4 100644 --- a/app/src/displays/datetime/datetime.vue +++ b/app/src/displays/datetime/datetime.vue @@ -73,6 +73,7 @@ watch( displayValue.value = relativeFormat(newValue); } else { let format; + if (props.format === 'long') { format = `${t('date-fns_date')} ${t('date-fns_time')}`; if (props.type === 'date') format = String(t('date-fns_date')); diff --git a/app/src/idle.ts b/app/src/idle.ts index e63b524aff..f76c555999 100644 --- a/app/src/idle.ts +++ b/app/src/idle.ts @@ -2,6 +2,7 @@ import { throttle } from 'lodash'; import mitt from 'mitt'; const events = ['pointermove', 'pointerdown', 'keydown']; + export const time = 5 * 60 * 1000; // 5 min in ms let timeout: number | null; diff --git a/app/src/interfaces/_system/system-collection/system-collection.vue b/app/src/interfaces/_system/system-collection/system-collection.vue index 789535b4f3..cf1fdc55fe 100644 --- a/app/src/interfaces/_system/system-collection/system-collection.vue +++ b/app/src/interfaces/_system/system-collection/system-collection.vue @@ -40,6 +40,7 @@ export default defineComponent({ const collections = computed(() => { let collections = collectionsStore.collections; + if (!props.includeSingleton) { collections = collections.filter((collection) => collection?.meta?.singleton === false); } diff --git a/app/src/interfaces/_system/system-collections/system-collections.vue b/app/src/interfaces/_system/system-collections/system-collections.vue index 89f9335cf3..6d1c9737e8 100644 --- a/app/src/interfaces/_system/system-collections/system-collections.vue +++ b/app/src/interfaces/_system/system-collections/system-collections.vue @@ -43,6 +43,7 @@ export default defineComponent({ const collections = computed(() => { let collections = collectionsStore.collections; + if (!props.includeSingleton) { collections = collections.filter((collection) => collection?.meta?.singleton === false); } diff --git a/app/src/interfaces/_system/system-filter/input-group.vue b/app/src/interfaces/_system/system-filter/input-group.vue index b207aa168b..be2f7003a2 100644 --- a/app/src/interfaces/_system/system-filter/input-group.vue +++ b/app/src/interfaces/_system/system-filter/input-group.vue @@ -101,6 +101,7 @@ export default defineComponent({ // Alias uses the foreign key type if (fieldInfo?.type === 'alias') { const relations = relationsStore.getRelationsForField(props.collection, getField(props.field)); + if (relations[0]) { return fieldsStore.getField(relations[0].collection, relations[0].field); } @@ -144,6 +145,7 @@ export default defineComponent({ const fieldPath = getField(props.field); const value = get(props.field, `${fieldPath}.${comparator.value}`); + if (['_in', '_nin'].includes(comparator.value)) { return [...(value as string[]).filter((val) => val !== null && val !== ''), null]; } else { @@ -180,6 +182,7 @@ export default defineComponent({ function setListValue(index: number, newVal: any) { if (typeof newVal === 'string' && newVal.includes(',')) { const parts = newVal.split(','); + for (let i = 0; i < parts.length; i++) { setValueAt(index + i, parts[i]); } diff --git a/app/src/interfaces/_system/system-filter/nodes.vue b/app/src/interfaces/_system/system-filter/nodes.vue index 6d607a6088..cd9a80a553 100644 --- a/app/src/interfaces/_system/system-filter/nodes.vue +++ b/app/src/interfaces/_system/system-filter/nodes.vue @@ -339,6 +339,7 @@ function getCompareOptions(name: string) { // Alias uses the foreign key type if (type === 'alias') { const relations = relationsStore.getRelationsForField(props.collection, name); + if (relations[0]) { type = fieldsStore.getField(relations[0].collection, relations[0].field)?.type || 'unknown'; } diff --git a/app/src/interfaces/_system/system-filter/system-filter.vue b/app/src/interfaces/_system/system-filter/system-filter.vue index ab2404bc5b..be0a1d3bb4 100644 --- a/app/src/interfaces/_system/system-filter/system-filter.vue +++ b/app/src/interfaces/_system/system-filter/system-filter.vue @@ -188,6 +188,7 @@ function addNode(key: string) { // Alias uses the foreign key type if (type === 'alias') { const relations = relationsStore.getRelationsForField(collection.value, key); + if (relations[0]) { type = fieldsStore.getField(relations[0].collection, relations[0].field)?.type || 'unknown'; } diff --git a/app/src/interfaces/_system/system-raw-editor/system-raw-editor.vue b/app/src/interfaces/_system/system-raw-editor/system-raw-editor.vue index 498c0ab9d8..e75e04f64f 100644 --- a/app/src/interfaces/_system/system-raw-editor/system-raw-editor.vue +++ b/app/src/interfaces/_system/system-raw-editor/system-raw-editor.vue @@ -74,6 +74,7 @@ onMounted(async () => { if (typedNewLine) return cancel(); const pastedNewLine = origin === 'paste' && typeof text === 'object' && text.length > 1; + if (pastedNewLine) { const newText = text.join(' '); if (!update) return; diff --git a/app/src/interfaces/files/files.vue b/app/src/interfaces/files/files.vue index 30e9765710..c367dd6259 100644 --- a/app/src/interfaces/files/files.vue +++ b/app/src/interfaces/files/files.vue @@ -185,6 +185,7 @@ const templateWithDefaults = computed(() => { return relationInfo.value.junctionCollection.meta?.display_template; let relatedDisplayTemplate = relationInfo.value.relatedCollection.meta?.display_template; + if (relatedDisplayTemplate) { const regex = /({{.*?}})/g; const parts = relatedDisplayTemplate.split(regex).filter((p) => p); diff --git a/app/src/interfaces/group-accordion/accordion-section.vue b/app/src/interfaces/group-accordion/accordion-section.vue index 39e68630d5..0a533dcb90 100644 --- a/app/src/interfaces/group-accordion/accordion-section.vue +++ b/app/src/interfaces/group-accordion/accordion-section.vue @@ -106,6 +106,7 @@ export default defineComponent({ const fieldsInSection = computed(() => { let fields: Field[] = [merge({}, props.field, { hideLabel: true })]; + if (props.field.meta?.special?.includes('group')) { fields.push(...getFieldsForGroup(props.field.meta?.field)); } diff --git a/app/src/interfaces/group-accordion/group-accordion.vue b/app/src/interfaces/group-accordion/group-accordion.vue index 2b5e0da3b9..aae3b743b0 100644 --- a/app/src/interfaces/group-accordion/group-accordion.vue +++ b/app/src/interfaces/group-accordion/group-accordion.vue @@ -148,6 +148,7 @@ export default defineComponent({ () => props.fields, () => { const newVal = limitFields(); + if (!isEqual(groupFields.value, newVal)) { groupFields.value = newVal; } diff --git a/app/src/interfaces/input-rich-text-html/useLink.ts b/app/src/interfaces/input-rich-text-html/useLink.ts index acdcfcbd2e..1a56cf17d5 100644 --- a/app/src/interfaces/input-rich-text-html/useLink.ts +++ b/app/src/interfaces/input-rich-text-html/useLink.ts @@ -110,6 +110,7 @@ export default function useLink(editor: Ref): UsableLink { editor.value.fire('focus'); const link = linkSelection.value; + if (link.url === null) { if (linkNode.value) { editor.value.selection.setContent(linkNode.value.innerText); diff --git a/app/src/interfaces/list-m2m/list-m2m.vue b/app/src/interfaces/list-m2m/list-m2m.vue index cb4c2e2116..66b06574a3 100644 --- a/app/src/interfaces/list-m2m/list-m2m.vue +++ b/app/src/interfaces/list-m2m/list-m2m.vue @@ -278,6 +278,7 @@ const templateWithDefaults = computed(() => { return relationInfo.value.junctionCollection.meta.display_template; let relatedDisplayTemplate = relationInfo.value.relatedCollection.meta?.display_template; + if (relatedDisplayTemplate) { const regex = /({{.*?}})/g; const parts = relatedDisplayTemplate.split(regex).filter((p) => p); diff --git a/app/src/interfaces/list-o2m/list-o2m.vue b/app/src/interfaces/list-o2m/list-o2m.vue index f3d4a34c4f..5cea196e32 100644 --- a/app/src/interfaces/list-o2m/list-o2m.vue +++ b/app/src/interfaces/list-o2m/list-o2m.vue @@ -275,6 +275,7 @@ const fields = computed(() => { if (!relationInfo.value) return []; let displayFields: string[] = []; + if (props.layout === LAYOUTS.TABLE) { displayFields = adjustFieldsForDisplays(props.fields, relationInfo.value.relatedCollection.collection); } else { diff --git a/app/src/interfaces/map/map.vue b/app/src/interfaces/map/map.vue index 724210eaa3..6d687ee03c 100644 --- a/app/src/interfaces/map/map.vue +++ b/app/src/interfaces/map/map.vue @@ -160,6 +160,7 @@ export default defineComponent({ let parse: GeoJSONParser; let serialize: GeoJSONSerializer; + try { parse = getParser({ geometryFormat, geometryField: 'value' }); serialize = getSerializer({ geometryFormat, geometryField: 'value' }); @@ -171,6 +172,7 @@ export default defineComponent({ const location = ref(); const projection = ref<{ x: number; y: number } | null>(); + function updateProjection() { projection.value = !location.value ? null : map.project(location.value as any); } @@ -189,6 +191,7 @@ export default defineComponent({ }), geocoder: undefined as MapboxGeocoder | undefined, }; + if (mapboxKey) { controls.geocoder = new MapboxGeocoder({ accessToken: mapboxKey, @@ -210,6 +213,7 @@ export default defineComponent({ const updateTooltipDebounce = debounce((event: any) => { const feature = event.features?.[0]; + if (feature && feature.properties!.active === 'false') { tooltipMessage.value = t('interfaces.map.click_to_select', { geometry: feature.geometry.type }); tooltipVisible.value = true; @@ -357,6 +361,7 @@ export default defineComponent({ static: StaticMode, }), } as any; + if (props.disabled) { return options; } @@ -391,6 +396,7 @@ export default defineComponent({ try { controls.draw.deleteAll(); const initialValue = parse(props); + if (!initialValue) { return; } @@ -403,6 +409,7 @@ export default defineComponent({ } const flattened = flatten(initialValue); + for (const geometry of flattened) { controls.draw.add(geometry); } @@ -425,6 +432,7 @@ export default defineComponent({ const features = controls.draw.getAll().features; const geometries = features.map((f) => f.geometry) as (SimpleGeometry | MultiGeometry)[]; let result: Geometry; + if (geometries.length == 0) { return null; } else if (!geometryType) { diff --git a/app/src/layouts/calendar/index.ts b/app/src/layouts/calendar/index.ts index 2c9c00a92c..fe5d3ca9de 100644 --- a/app/src/layouts/calendar/index.ts +++ b/app/src/layouts/calendar/index.ts @@ -60,6 +60,7 @@ export default defineLayout({ const start = formatISO(calendar.value.view.activeStart); const end = formatISO(calendar.value.view.activeEnd); const startsHere = { [startDateField.value]: { _between: [start, end] } }; + if (!endDateField.value) { return startsHere; } @@ -295,6 +296,7 @@ export default defineLayout({ if (endDateField.value) { const date = parse(item[endDateField.value], 'yyyy-MM-dd', new Date()); + if (allDay && isValid(date)) { // FullCalendar uses exclusive end moments, so we'll have to increment the end date by 1 to get the // expected result in the calendar diff --git a/app/src/layouts/map/components/map.vue b/app/src/layouts/map/components/map.vue index 10af4bd548..1739a0bebb 100644 --- a/app/src/layouts/map/components/map.vue +++ b/app/src/layouts/map/components/map.vue @@ -101,6 +101,7 @@ export default defineComponent({ layers: ['__directus_polygons', '__directus_points', '__directus_lines'], }); let geocoderControl: MapboxGeocoder | undefined; + if (mapboxKey) { const marker = document.createElement('div'); marker.className = 'mapboxgl-user-location-dot mapboxgl-search-location-dot'; @@ -147,6 +148,7 @@ export default defineComponent({ watch(() => style.value, updateStyle); watch(() => props.bounds, fitBounds); const activeLayers = ['__directus_polygons', '__directus_points', '__directus_lines']; + for (const layer of activeLayers) { map.on('click', layer, onFeatureClick); map.on('mousemove', layer, updatePopup); @@ -186,6 +188,7 @@ export default defineComponent({ function fitBounds() { const bbox = props.data.bbox; + if (map && bbox) { map.fitBounds(bbox as LngLatBoundsLike, { padding: 100, @@ -219,6 +222,7 @@ export default defineComponent({ function updateSource(newSource: GeoJSONSource) { const layersId = new Set(map.getStyle().layers?.map(({ id }) => id)); + for (const layer of props.layers) { if (layersId.has(layer.id)) { map.removeLayer(layer.id); @@ -264,6 +268,7 @@ export default defineComponent({ function onFeatureClick(event: MapLayerMouseEvent) { const feature = event.features?.[0]; const replace = props.showSelect === 'multiple' ? false : !event.originalEvent.altKey; + if (feature && props.featureId) { if (boxSelectControl.active()) { emit('featureselect', { ids: [feature.id], replace }); @@ -280,6 +285,7 @@ export default defineComponent({ const previousId = hoveredFeature.value?.id; const featureChanged = previousId !== feature?.id; + if (previousId && featureChanged) { map.setFeatureState({ id: previousId, source: '__directus' }, { hovered: false }); } diff --git a/app/src/layouts/map/index.ts b/app/src/layouts/map/index.ts index a9e232382a..318d39ced7 100644 --- a/app/src/layouts/map/index.ts +++ b/app/src/layouts/map/index.ts @@ -68,6 +68,7 @@ export default defineLayout({ const geometryOptions = computed(() => { const field = geometryFieldData.value; + if (!field) { return; } @@ -75,6 +76,7 @@ export default defineLayout({ const geometryField = field.field; const geometryFormat = getGeometryFormatForType(field.type); const geometryType = field.type.split('.')[1] ?? field.meta?.options?.geometryType; + if (!geometryFormat) { return; } @@ -239,6 +241,7 @@ export default defineLayout({ type ItemPopup = { item?: any; position?: { x: number; y: number } }; const itemPopup = ref({ item: null }); + function updateItemPopup(update: Partial) { if ('item' in update) { const field = primaryKeyField.value?.field; diff --git a/app/src/layouts/tabular/index.ts b/app/src/layouts/tabular/index.ts index c40700f790..9c2632f5cd 100644 --- a/app/src/layouts/tabular/index.ts +++ b/app/src/layouts/tabular/index.ts @@ -348,6 +348,7 @@ export default defineLayout({ } let sortString = newSort.by; + if (newSort.desc === true) { sortString = '-' + sortString; } diff --git a/app/src/modules/content/index.ts b/app/src/modules/content/index.ts index 21eb3190e0..8e344b5e49 100644 --- a/app/src/modules/content/index.ts +++ b/app/src/modules/content/index.ts @@ -99,6 +99,7 @@ export default defineModule({ ); const { data } = useLocalStorage('last-accessed-collection'); + if ( data.value && collectionsStore.visibleCollections.find((visibleCollection) => visibleCollection.collection === data.value) diff --git a/app/src/modules/content/routes/item.vue b/app/src/modules/content/routes/item.vue index c2577f4c3d..ca5e3d0cfa 100644 --- a/app/src/modules/content/routes/item.vue +++ b/app/src/modules/content/routes/item.vue @@ -367,6 +367,7 @@ const disabledOptions = computed(() => { function navigateBack() { const backState = router.options.history.state.back; + if (typeof backState !== 'string' || !backState.startsWith('/login')) { router.back(); return; diff --git a/app/src/modules/files/routes/item.vue b/app/src/modules/files/routes/item.vue index b8987a6ee0..3ea3bc801e 100644 --- a/app/src/modules/files/routes/item.vue +++ b/app/src/modules/files/routes/item.vue @@ -290,6 +290,7 @@ const fieldsFiltered = computed(() => { function navigateBack() { const backState = router.options.history.state.back; + if (typeof backState !== 'string' || !backState.startsWith('/login')) { router.back(); return; diff --git a/app/src/modules/insights/routes/dashboard.vue b/app/src/modules/insights/routes/dashboard.vue index 63318677b6..de091af96d 100644 --- a/app/src/modules/insights/routes/dashboard.vue +++ b/app/src/modules/insights/routes/dashboard.vue @@ -337,6 +337,7 @@ const cancelChanges = (force = false) => { const copyPanelTo = ref(insightsStore.dashboards.find((dashboard) => dashboard.id !== props.primaryKey)?.id); const copyPanelID = ref(); + const copyPanel = () => { insightsStore.stagePanelDuplicate(unref(copyPanelID)!, { dashboard: unref(copyPanelTo) }); copyPanelID.value = null; diff --git a/app/src/modules/settings/index.ts b/app/src/modules/settings/index.ts index a1e7a8ad19..4755d1c4f0 100644 --- a/app/src/modules/settings/index.ts +++ b/app/src/modules/settings/index.ts @@ -199,6 +199,7 @@ export default defineModule({ async beforeEnter(to) { const { flows } = useFlowsStore(); const existingFlow = flows.find((flow) => flow.id === to.params.primaryKey); + if (!existingFlow) { return { name: 'settings-not-found', diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-display.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-display.vue index 7a4161b5c0..00cd0665dd 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-display.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-display.vue @@ -70,6 +70,7 @@ export default defineComponent({ const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = []; const recommendedList = recommended.map((key: any) => displayItems.find((item) => item.value === key)); + if (recommendedList !== undefined) { recommendedItems.push(...recommendedList.filter((i: any) => i)); } @@ -79,6 +80,7 @@ export default defineComponent({ } const displayList = displayItems.filter((item) => recommended.includes(item.value as string) === false); + if (displayList !== undefined) { recommendedItems.push(...displayList.filter((i) => i)); } diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-interface.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-interface.vue index e11002444f..30091b4765 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-interface.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-interface.vue @@ -81,6 +81,7 @@ export default defineComponent({ const recommendedItems: (FancySelectItem | { divider: boolean } | undefined)[] = []; const recommendedList = recommended.map((key) => interfaceItems.find((item) => item.value === key)); + if (recommendedList !== undefined) { recommendedItems.push(...recommendedList.filter((i) => i)); } @@ -90,6 +91,7 @@ export default defineComponent({ } const interfaceList = interfaceItems.filter((item) => recommended.includes(item.value as string) === false); + if (interfaceList !== undefined) { recommendedItems.push(...interfaceList.filter((i) => i)); } diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2a.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2a.vue index 273637cd0d..9438f3f0ff 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2a.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2a.vue @@ -210,6 +210,7 @@ export default defineComponent({ const unsortableJunctionFields = computed(() => { let fields = ['item', 'collection']; + if (junctionCollection.value) { const relations = relationsStore.getRelationsForCollection(junctionCollection.value); fields.push(...relations.map((field) => field.field)); diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2m.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2m.vue index 1bd434ba2c..da4bd2fd32 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2m.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-m2m.vue @@ -270,6 +270,7 @@ export default defineComponent({ const unsortableJunctionFields = computed(() => { let fields = []; + if (junctionCollection.value) { const relations = relationsStore.getRelationsForCollection(junctionCollection.value); fields.push(...relations.map((field) => field.field)); diff --git a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-o2m.vue b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-o2m.vue index 43aaf71049..f7369f3319 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-o2m.vue +++ b/app/src/modules/settings/routes/data-model/field-detail/field-detail-advanced/field-detail-advanced-relationship-o2m.vue @@ -144,6 +144,7 @@ export default defineComponent({ const unsortableJunctionFields = computed(() => { let fields = []; + if (relatedCollection.value) { const relations = relationsStore.getRelationsForCollection(relatedCollection.value); fields.push(...relations.map((field) => field.field)); diff --git a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/global.ts b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/global.ts index 307ccd5c9e..7de5661684 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/global.ts +++ b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/global.ts @@ -52,6 +52,7 @@ export function setTypeForInterface(updates: StateUpdates, state: State) { */ export function setSpecialForLocalType(updates: StateUpdates) { const localType = updates?.localType; + switch (localType) { case 'o2m': case 'm2m': diff --git a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/standard.ts b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/standard.ts index 74ac6d5fc8..85fbc1af98 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/standard.ts +++ b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/standard.ts @@ -23,6 +23,7 @@ function updateInterface(updates: StateUpdates, fn: HelperFunctions) { const inter = useExtension('interface', fn.getCurrent('field.meta.interface')); const type = updates.field?.type; + if (type && !inter.value?.types.includes(type)) { set(updates, 'field.meta.interface', undefined); } diff --git a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/translations.ts b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/translations.ts index f27f3f2677..27e8c66d23 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/store/alterations/translations.ts +++ b/app/src/modules/settings/routes/data-model/field-detail/store/alterations/translations.ts @@ -226,6 +226,7 @@ export function generateCollections(updates: StateUpdates, state: State, { getCu }); const previousName = get(state, 'relations.m2o.related_collection'); + if (state.items && state.items[previousName]) { delete state.items[previousName]; } diff --git a/app/src/modules/settings/routes/data-model/field-detail/store/index.ts b/app/src/modules/settings/routes/data-model/field-detail/store/index.ts index cca3ae8042..f9cb06642a 100644 --- a/app/src/modules/settings/routes/data-model/field-detail/store/index.ts +++ b/app/src/modules/settings/routes/data-model/field-detail/store/index.ts @@ -154,6 +154,7 @@ export const useFieldDetailStore = defineStore({ } const localType = getCurrent('localType') as (typeof LOCAL_TYPES)[number] | undefined; + if (localType) { alterations[localType].applyChanges(updates, this, helperFn); } @@ -165,6 +166,7 @@ export const useFieldDetailStore = defineStore({ // Validation to prevent cyclic relation const aliasesFromRelation: string[] = []; + for (const relation of Object.values(this.relations)) { if (!relation || !relation.collection || !relation.field) continue; if ( @@ -189,6 +191,7 @@ export const useFieldDetailStore = defineStore({ const addedFields = Object.values(this.fields) .map((field) => (field && field.collection && field.field ? `${field.collection}:${field.field}` : null)) .filter((field) => field); + if (addedFields.some((field) => addedFields.indexOf(field) !== addedFields.lastIndexOf(field))) { throw new Error('Duplicate fields cannot be created'); } diff --git a/app/src/modules/settings/routes/flows/components/arrows.vue b/app/src/modules/settings/routes/flows/components/arrows.vue index a167cbecea..8b778212e5 100644 --- a/app/src/modules/settings/routes/flows/components/arrows.vue +++ b/app/src/modules/settings/routes/flows/components/arrows.vue @@ -44,6 +44,7 @@ const endOffset = 13; const size = computed(() => { let width = 0, height = 0; + for (const panel of props.panels) { width = Math.max(width, (panel.x + PANEL_WIDTH) * 20); height = Math.max(height, (panel.y + PANEL_HEIGHT) * 20); @@ -130,6 +131,7 @@ const arrows = computed(() => { function getPoints(panel: Record, offset: Vector2, to?: Record) { const x = (panel.x - 1) * 20 + offset.x; const y = (panel.y - 1) * 20 + offset.y; + if (to) { const toX = (to.x - 1) * 20 + ATTACHMENT_OFFSET.x; const toY = (to.y - 1) * 20 + ATTACHMENT_OFFSET.y; @@ -213,6 +215,7 @@ const arrows = computed(() => { } let pointer = Math.floor(possiblePlaces.length / 2); + for (let i = 0; i < possiblePlaces.length; i++) { pointer += i * (i % 2 == 0 ? -1 : 1); if (possiblePlaces[pointer]) return min[axis] + pointer * 20; @@ -223,6 +226,7 @@ const arrows = computed(() => { function range(min: number, max: number, step: number) { const points: number[] = []; + for (let i = min; i < max; i += step) { points.push(i); } diff --git a/app/src/modules/settings/routes/flows/components/operation.vue b/app/src/modules/settings/routes/flows/components/operation.vue index 7f65a136cd..ad9eb3d5d1 100644 --- a/app/src/modules/settings/routes/flows/components/operation.vue +++ b/app/src/modules/settings/routes/flows/components/operation.vue @@ -223,6 +223,7 @@ function pointerdown(target: Target | 'parent') { down = target; const rect = document.getElementsByClassName('workspace').item(0)?.getBoundingClientRect(); + if (rect) { workspaceOffset = new Vector2(rect.left, rect.top); } diff --git a/app/src/modules/settings/routes/flows/constants.ts b/app/src/modules/settings/routes/flows/constants.ts index b0d2049277..7708a42680 100644 --- a/app/src/modules/settings/routes/flows/constants.ts +++ b/app/src/modules/settings/routes/flows/constants.ts @@ -5,4 +5,5 @@ const PANEL_HEIGHT = 14; const ATTACHMENT_OFFSET = new Vector2(0, 3 * 20); const RESOLVE_OFFSET = new Vector2(PANEL_WIDTH * 20, 10 * 20); const REJECT_OFFSET = new Vector2(PANEL_WIDTH * 20, 12 * 20); + export { PANEL_HEIGHT, PANEL_WIDTH, ATTACHMENT_OFFSET, RESOLVE_OFFSET, REJECT_OFFSET }; diff --git a/app/src/modules/settings/routes/flows/flow.vue b/app/src/modules/settings/routes/flows/flow.vue index 469aa22127..0acaed85a9 100644 --- a/app/src/modules/settings/routes/flows/flow.vue +++ b/app/src/modules/settings/routes/flows/flow.vue @@ -385,6 +385,7 @@ const parentPanels = computed(() => { function connectedToTrigger(id: string) { let parent = parents[id]; + while (parent?.id !== '$trigger') { if (parent === undefined) return false; parent = parents[parent.id]; @@ -672,6 +673,7 @@ function arrowStop() { function isLoop(currentId: string, attachTo: string) { let parent = currentId; + while (parent !== undefined) { if (parent === attachTo) return true; parent = parentPanels.value[parent]?.id ?? undefined; diff --git a/app/src/modules/settings/routes/roles/item/composables/use-update-permissions.ts b/app/src/modules/settings/routes/roles/item/composables/use-update-permissions.ts index f596ef5fe1..4c8dffa54c 100644 --- a/app/src/modules/settings/routes/roles/item/composables/use-update-permissions.ts +++ b/app/src/modules/settings/routes/roles/item/composables/use-update-permissions.ts @@ -110,6 +110,7 @@ export default function useUpdatePermissions( await Promise.all( ACTIONS.map(async (action) => { const permission = getPermission(action); + if (permission) { try { await api.patch(`/permissions/${permission.id}`, { diff --git a/app/src/modules/settings/routes/translation-strings/translation-strings-drawer.vue b/app/src/modules/settings/routes/translation-strings/translation-strings-drawer.vue index 4b36b16d14..c7c138c55a 100644 --- a/app/src/modules/settings/routes/translation-strings/translation-strings-drawer.vue +++ b/app/src/modules/settings/routes/translation-strings/translation-strings-drawer.vue @@ -184,6 +184,7 @@ function closeDialog() { async function saveNewTranslationString() { const newTranslationStrings = translationStrings.value ? [...translationStrings.value, values.value] : [values.value]; + try { await update(newTranslationStrings); emit('savedKey', values.value.key); @@ -197,6 +198,7 @@ async function deleteCurrentTranslationString() { const newTranslationStrings = translationStrings.value ? translationStrings.value.filter((val) => val.key !== values.value.key) : []; + try { await update(newTranslationStrings); confirmDelete.value = false; diff --git a/app/src/modules/users/routes/item.vue b/app/src/modules/users/routes/item.vue index a4800b7003..8eb7660ec4 100644 --- a/app/src/modules/users/routes/item.vue +++ b/app/src/modules/users/routes/item.vue @@ -364,6 +364,7 @@ export default defineComponent({ function navigateBack() { const backState = router.options.history.state.back; + if (typeof backState !== 'string' || !backState.startsWith('/login')) { router.back(); return; diff --git a/app/src/panels/metric/panel-metric.vue b/app/src/panels/metric/panel-metric.vue index 04b6e3b45c..93fb2629d9 100644 --- a/app/src/panels/metric/panel-metric.vue +++ b/app/src/panels/metric/panel-metric.vue @@ -97,6 +97,7 @@ const color = computed(() => { if (typeof metric.value === 'string') { const value = metric.value; const compareValue = format.value ?? ''; + switch (format.operator || '>=') { case '=': return value === compareValue; @@ -106,6 +107,7 @@ const color = computed(() => { } else { const value = Number(metric.value); const compareValue = Number(format.value ?? 0); + switch (format.operator || '>=') { case '=': return value === compareValue; diff --git a/app/src/panels/relational-variable/panel-relational-variable.vue b/app/src/panels/relational-variable/panel-relational-variable.vue index a92de4e2d6..fb0ef1961a 100644 --- a/app/src/panels/relational-variable/panel-relational-variable.vue +++ b/app/src/panels/relational-variable/panel-relational-variable.vue @@ -73,6 +73,7 @@ const value = computed({ }); const selectModalOpen = ref(false); + function onSelection(data: (number | string)[]) { selectModalOpen.value = false; if (!Array.isArray(data) || data.length === 0) { @@ -82,6 +83,7 @@ function onSelection(data: (number | string)[]) { if (props.multiple) { const items = Array.from(new Set(data.concat(value.value))); + if (items.length > props.limit) { unexpectedError(new Error('More items selected than the allowed limit')); value.value = items.slice(0, props.limit); diff --git a/app/src/stores/fields.ts b/app/src/stores/fields.ts index 984182248e..8cf57debc4 100644 --- a/app/src/stores/fields.ts +++ b/app/src/stores/fields.ts @@ -343,6 +343,7 @@ export const useFieldsStore = defineStore({ getRelationalField(collection: string, fields: string) { const relationsStore = useRelationsStore(); const [field, ...path] = fields.split('.'); + if (field.includes(':')) { const [_, collection] = field.split(':'); return this.getField(collection, path.join('.')); diff --git a/app/src/stores/insights.ts b/app/src/stores/insights.ts index d8f8668edc..f83982ad20 100644 --- a/app/src/stores/insights.ts +++ b/app/src/stores/insights.ts @@ -192,6 +192,7 @@ export const useInsightsStore = defineStore('insightsStore', () => { function hasEmptyRelation(panel: Pick) { const stringOptions = JSON.stringify(panel.options); + for (const relationVar of emptyRelations()) { const fieldRegex = new RegExp(`{{\\s*?${escapeStringRegexp(relationVar)}\\s*?}}`); if (fieldRegex.test(stringOptions)) return true; @@ -340,6 +341,7 @@ export const useInsightsStore = defineStore('insightsStore', () => { * decide whether or not to reload the data */ let oldQuery; + if ('options' in panelEdits) { // Edits not yet applied const panel = unref(panelsWithEdits).find((panel) => panel.id === id); diff --git a/app/src/utils/geometry/basemap.ts b/app/src/utils/geometry/basemap.ts index 5e056dea92..62eaae2c8c 100644 --- a/app/src/utils/geometry/basemap.ts +++ b/app/src/utils/geometry/basemap.ts @@ -58,11 +58,13 @@ export function getStyleFromBasemapSource(basemap: BasemapSource): Style | strin function expandUrl(url: string): string[] { const urls = []; let match = /\{([a-z])-([a-z])\}/.exec(url); + if (match) { // char range const startCharCode = match[1].charCodeAt(0); const stopCharCode = match[2].charCodeAt(0); let charCode; + for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) { urls.push(url.replace(match[0], String.fromCharCode(charCode))); } @@ -74,6 +76,7 @@ function expandUrl(url: string): string[] { if (match) { // number range const stop = parseInt(match[2], 10); + for (let i = parseInt(match[1], 10); i <= stop; i++) { urls.push(url.replace(match[0], i.toString())); } @@ -85,6 +88,7 @@ function expandUrl(url: string): string[] { if (match) { // csv const subdomains = match[1].split(','); + for (const subdomain of subdomains) { urls.push(url.replace(match[0], subdomain)); } diff --git a/app/src/utils/geometry/index.ts b/app/src/utils/geometry/index.ts index 8fed8ab84a..b92d41a853 100644 --- a/app/src/utils/geometry/index.ts +++ b/app/src/utils/geometry/index.ts @@ -47,6 +47,7 @@ export function getGeometryFormatForType(type: Type): GeometryFormat | undefined export function getSerializer(options: GeometryOptions): GeoJSONSerializer { const { geometryFormat } = options; + switch (geometryFormat) { case 'native': case 'geojson': @@ -62,6 +63,7 @@ export function getSerializer(options: GeometryOptions): GeoJSONSerializer { export function getGeometryParser(options: GeometryOptions): (geom: any) => AnyGeometry { const { geometryFormat } = options; + switch (geometryFormat) { case 'native': case 'geojson': diff --git a/app/src/utils/get-js-type.test.ts b/app/src/utils/get-js-type.test.ts index d861ad9952..5b49c4505c 100644 --- a/app/src/utils/get-js-type.test.ts +++ b/app/src/utils/get-js-type.test.ts @@ -4,6 +4,7 @@ import { getJSType } from './get-js-type'; test('Returns object for relational fields', () => { const relationTypes = ['m2o', 'o2m', 'm2m', 'm2a', 'files', 'translations']; + for (const special of relationTypes) { expect( getJSType({ @@ -21,6 +22,7 @@ test('Returns object for relational fields', () => { test('Returns number for numeric fields', () => { const numericTypes = ['bigInteger', 'integer', 'float', 'decimal']; + for (const fieldType of numericTypes) { expect( getJSType({ @@ -35,6 +37,7 @@ test('Returns number for numeric fields', () => { test('Returns string for string fields', () => { const stringTypes = ['string', 'text', 'uuid', 'hash']; + for (const fieldType of stringTypes) { expect( getJSType({ @@ -49,6 +52,7 @@ test('Returns string for string fields', () => { test('Returns boolean for boolean fields', () => { const booleanTypes = ['boolean']; + for (const fieldType of booleanTypes) { expect( getJSType({ @@ -63,6 +67,7 @@ test('Returns boolean for boolean fields', () => { test('Returns string for datetime fields', () => { const dateTypes = ['time', 'timestamp', 'date', 'dateTime']; + for (const fieldType of dateTypes) { expect( getJSType({ @@ -77,6 +82,7 @@ test('Returns string for datetime fields', () => { test('Returns object for json and csv fields', () => { const objectTypes = ['json', 'csv']; + for (const fieldType of objectTypes) { expect( getJSType({ @@ -91,6 +97,7 @@ test('Returns object for json and csv fields', () => { test('Returns object for geometry fields', () => { const geometryTypes = ['geometryPoint', 'geometryPolygon', 'geometryLineString']; + for (const fieldType of geometryTypes) { expect( getJSType({ @@ -105,6 +112,7 @@ test('Returns object for geometry fields', () => { test('Returns undefined as fallback', () => { const errorTypes = ['non-existent', 'should also error', '🦄']; + for (const fieldType of errorTypes) { expect( getJSType({ diff --git a/app/src/utils/get-special-for-type.test.ts b/app/src/utils/get-special-for-type.test.ts index 5adac4a24e..f5523000c8 100644 --- a/app/src/utils/get-special-for-type.test.ts +++ b/app/src/utils/get-special-for-type.test.ts @@ -7,6 +7,7 @@ const nonPrefixedSpecials = ['uuid', 'hash', 'geometry']; test('Returns cast-prefixed special array for json, csv, and boolean field types', () => { const types = TYPES.filter((type) => castPrefixedSpecials.includes(type)); + for (const type of types) { expect(getSpecialForType(type)).toStrictEqual(['cast-' + type]); } @@ -14,6 +15,7 @@ test('Returns cast-prefixed special array for json, csv, and boolean field types test('Returns special array for uuid, hash, and geometry field types', () => { const types = TYPES.filter((type) => nonPrefixedSpecials.includes(type)); + for (const type of types) { expect(getSpecialForType(type)).toStrictEqual([type]); } @@ -22,6 +24,7 @@ test('Returns special array for uuid, hash, and geometry field types', () => { test('Returns null for other field types', () => { const specials = [...castPrefixedSpecials, ...nonPrefixedSpecials]; const types = TYPES.filter((type) => !specials.includes(type)); + for (const type of types) { expect(getSpecialForType(type)).toStrictEqual(null); } diff --git a/app/src/utils/get-vue-component-name.ts b/app/src/utils/get-vue-component-name.ts index 5fcab38c82..163da71255 100644 --- a/app/src/utils/get-vue-component-name.ts +++ b/app/src/utils/get-vue-component-name.ts @@ -11,6 +11,7 @@ export function getVueComponentName(vm: ComponentPublicInstance | null): string const options = typeof vm === 'function' && (vm as any).cid != null ? (vm as any)?.options : vm?.$options; let name = options.name || options.__name || options._componentTag; const file = options.__file; + if (!name && file) { const match = file.match(/([^/\\]+)\.vue$/); name = match && match[1]; diff --git a/app/src/views/private/components/comment-input.vue b/app/src/views/private/components/comment-input.vue index f6eac137d1..317a8485dc 100644 --- a/app/src/views/private/components/comment-input.vue +++ b/app/src/views/private/components/comment-input.vue @@ -213,6 +213,7 @@ function cancel() { function saveCursorPosition() { if (document.getSelection) { const selection = document.getSelection(); + if (selection) { lastCaretOffset = selection.anchorOffset; diff --git a/packages/composables/src/use-filter-fields.ts b/packages/composables/src/use-filter-fields.ts index 6b2fd60c06..5a9b280de0 100644 --- a/packages/composables/src/use-filter-fields.ts +++ b/packages/composables/src/use-filter-fields.ts @@ -7,6 +7,7 @@ export function useFilterFields( ): { fieldGroups: ComputedRef, Field[]>> } { const fieldGroups = computed(() => { const acc = {} as Record, Field[]>; + for (const name in filters) { acc[name] = []; } diff --git a/packages/schema/src/dialects/cockroachdb.ts b/packages/schema/src/dialects/cockroachdb.ts index 8418197837..0df6ef5053 100644 --- a/packages/schema/src/dialects/cockroachdb.ts +++ b/packages/schema/src/dialects/cockroachdb.ts @@ -60,6 +60,7 @@ export default class CockroachDB implements SchemaInspector { constructor(knex: Knex) { this.knex = knex; const config = knex.client.config; + if (!config.searchPath) { this.schema = 'public'; this.explodedSchema = [this.schema]; diff --git a/packages/schema/src/dialects/mssql.ts b/packages/schema/src/dialects/mssql.ts index 4e40dcfca7..3a516ba5b9 100644 --- a/packages/schema/src/dialects/mssql.ts +++ b/packages/schema/src/dialects/mssql.ts @@ -47,6 +47,7 @@ export function rawColumnToColumn(rawColumn: RawColumn): Column { function parseMaxLength(rawColumn: RawColumn) { const max_length = Number(rawColumn.max_length); + if (Number.isNaN(max_length) || rawColumn.max_length === null || rawColumn.max_length === undefined) { return null; } diff --git a/packages/schema/src/dialects/mysql.ts b/packages/schema/src/dialects/mysql.ts index e20c35082e..27467d626c 100644 --- a/packages/schema/src/dialects/mysql.ts +++ b/packages/schema/src/dialects/mysql.ts @@ -37,6 +37,7 @@ type RawColumn = { export function rawColumnToColumn(rawColumn: RawColumn): Column { let dataType = rawColumn.COLUMN_TYPE.replace(/\(.*?\)/, ''); + if (rawColumn.COLUMN_TYPE.startsWith('tinyint(1)')) { dataType = 'boolean'; } @@ -116,6 +117,7 @@ export default class MySQL implements SchemaInspector { } let dataType = column.data_type.replace(/\(.*?\)/, ''); + if (column.data_type.startsWith('tinyint(1)')) { dataType = 'boolean'; } diff --git a/packages/schema/src/dialects/postgres.ts b/packages/schema/src/dialects/postgres.ts index 39e0d991ed..8ccc83e456 100644 --- a/packages/schema/src/dialects/postgres.ts +++ b/packages/schema/src/dialects/postgres.ts @@ -53,6 +53,7 @@ export default class Postgres implements SchemaInspector { constructor(knex: Knex) { this.knex = knex; const config = knex.client.config; + if (!config.searchPath) { this.schema = 'public'; this.explodedSchema = [this.schema]; diff --git a/packages/schema/src/dialects/sqlite.ts b/packages/schema/src/dialects/sqlite.ts index f227ea434e..11dbf194bc 100644 --- a/packages/schema/src/dialects/sqlite.ts +++ b/packages/schema/src/dialects/sqlite.ts @@ -226,6 +226,7 @@ export default class SQLite implements SchemaInspector { `SELECT COUNT(*) AS ct FROM pragma_table_xinfo('${table}') WHERE name='${column}'` ); const resultsVal = results[0]['ct']; + if (resultsVal !== 0) { isColumn = true; } diff --git a/packages/utils/node/ensure-extension-dirs.ts b/packages/utils/node/ensure-extension-dirs.ts index f0c7c0d69d..20dfa3f291 100644 --- a/packages/utils/node/ensure-extension-dirs.ts +++ b/packages/utils/node/ensure-extension-dirs.ts @@ -9,6 +9,7 @@ export async function ensureExtensionDirs( ): Promise { for (const extensionType of types) { const dirPath = path.resolve(extensionsPath, pluralize(extensionType)); + try { await fse.ensureDir(dirPath); } catch { diff --git a/packages/utils/shared/define-extension.test.ts b/packages/utils/shared/define-extension.test.ts index 7fcb334200..0640a66c87 100644 --- a/packages/utils/shared/define-extension.test.ts +++ b/packages/utils/shared/define-extension.test.ts @@ -14,12 +14,14 @@ import { } from './define-extension.js'; const mockComponent = defineComponent({}); + const mockHandler = () => { return ''; }; describe('define-extensions', () => { const types = [] as readonly Type[]; + const mockRecord = () => { return { test: 'test' }; }; diff --git a/packages/utils/shared/generate-joi.ts b/packages/utils/shared/generate-joi.ts index 400045f8fc..f76c59b038 100644 --- a/packages/utils/shared/generate-joi.ts +++ b/packages/utils/shared/generate-joi.ts @@ -109,6 +109,7 @@ export function generateJoi(filter: FieldFilter | null, options?: JoiOptions): A compareValue === null || compareValue === '' || compareValue === true || compareValue === false ? NaN : Number(compareValue); + if (isNaN(numericValue)) { schema[key] = getAnySchema().equal(compareValue); } else { @@ -121,6 +122,7 @@ export function generateJoi(filter: FieldFilter | null, options?: JoiOptions): A compareValue === null || compareValue === '' || compareValue === true || compareValue === false ? NaN : Number(compareValue); + if (isNaN(numericValue)) { schema[key] = getAnySchema().not(compareValue); } else { diff --git a/packages/utils/shared/parse-filter-function-path.ts b/packages/utils/shared/parse-filter-function-path.ts index 277771dcd2..cb76af9138 100644 --- a/packages/utils/shared/parse-filter-function-path.ts +++ b/packages/utils/shared/parse-filter-function-path.ts @@ -11,6 +11,7 @@ export function parseFilterFunctionPath(path: string): string { const functionName = preHasColumns ? pre.slice(pre.lastIndexOf('.') + 1) : pre; const matched = path.match(REGEX_BETWEEN_PARENS); + if (matched) { const fields = matched[1]!; const fieldsHasColumns = fields.includes('.'); diff --git a/packages/utils/shared/parse-filter.ts b/packages/utils/shared/parse-filter.ts index fa123f9a36..c57a59ed5f 100644 --- a/packages/utils/shared/parse-filter.ts +++ b/packages/utils/shared/parse-filter.ts @@ -19,6 +19,7 @@ export function parseFilter( context: ParseFilterContext = {} ): Filter | null { let parsedFilter = parseFilterRecursive(filter, accountability, context); + if (parsedFilter) { parsedFilter = shiftLogicalOperatorsUp(parsedFilter); } diff --git a/packages/utils/shared/validate-payload.ts b/packages/utils/shared/validate-payload.ts index 7e13de8203..bb5ea02870 100644 --- a/packages/utils/shared/validate-payload.ts +++ b/packages/utils/shared/validate-payload.ts @@ -40,6 +40,7 @@ export function validatePayload( const pass = subValidation.some((subObj: Record) => { const nestedErrors = validatePayload(subObj, payload, options); + if (nestedErrors.length > 0) { swallowErrors.push(...nestedErrors); return false; diff --git a/tests/blackbox/common/seed-functions.ts b/tests/blackbox/common/seed-functions.ts index 2196fc2ca3..ff9fcc6ea3 100644 --- a/tests/blackbox/common/seed-functions.ts +++ b/tests/blackbox/common/seed-functions.ts @@ -173,6 +173,7 @@ function generateString(options: OptionsSeedGenerateString) { for (let i = 0; i < options.quantity; i++) { let value = uuid(String((options.seed ?? 'default') + i), SEED_UUID_NAMESPACE).replace(/-/g, ''); + if (options.startsWith) { value = options.startsWith + value; } diff --git a/tests/blackbox/query/filter/index.ts b/tests/blackbox/query/filter/index.ts index c879ec9b96..421b647b75 100644 --- a/tests/blackbox/query/filter/index.ts +++ b/tests/blackbox/query/filter/index.ts @@ -202,6 +202,7 @@ function processValidation( if (keys.length === 1) { if (Array.isArray(data)) { let found = false; + for (const item of data) { try { if (filter.validatorFunction(get(item, keys[0]), filter.value)) { @@ -231,6 +232,7 @@ function processValidation( } } else { let validationResult; + try { validationResult = filter.validatorFunction(get(data, keys[0]), filter.value); } catch (_err) { @@ -250,6 +252,7 @@ function processValidation( if (Array.isArray(data)) { let found = false; + for (const item of data) { if (processValidation(get(item, currentKey), keys.join('.'), filter, possibleValues, false)) { found = true; diff --git a/tests/blackbox/routes/collections/crud.test.ts b/tests/blackbox/routes/collections/crud.test.ts index e147d92395..2e946e6025 100644 --- a/tests/blackbox/routes/collections/crud.test.ts +++ b/tests/blackbox/routes/collections/crud.test.ts @@ -232,6 +232,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/collections', (pkType) => { afterEach(async () => { const db = databases.get(currentVendor)!; + for (const collection of collectionNames) { await db.schema.dropTableIfExists(collection); await db('directus_collections').del().where({ collection }); diff --git a/tests/blackbox/routes/fields/crud.test.ts b/tests/blackbox/routes/fields/crud.test.ts index 92c453b59d..21f6be0529 100644 --- a/tests/blackbox/routes/fields/crud.test.ts +++ b/tests/blackbox/routes/fields/crud.test.ts @@ -122,6 +122,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/fields', (pkType) => { afterEach(async () => { const db = databases.get(currentVendor)!; + if (await db.schema.hasColumn(TEST_COLLECTION_NAME, TEST_FIELD_NAME)) { await db.schema.alterTable(TEST_COLLECTION_NAME, (table) => { table.dropColumn(TEST_FIELD_NAME); @@ -223,6 +224,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/fields', (pkType) => { beforeEach(async () => { const db = databases.get(currentVendor)!; + if (!(await db.schema.hasColumn(TEST_COLLECTION_NAME, TEST_FIELD_NAME))) { await db.schema.alterTable(TEST_COLLECTION_NAME, (table) => { table.string(TEST_FIELD_NAME); @@ -395,6 +397,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/fields', (pkType) => { afterEach(async () => { const db = databases.get(currentVendor)!; + if (!(await db.schema.hasColumn(TEST_COLLECTION_NAME, TEST_FIELD_NAME))) { await db.schema.alterTable(TEST_COLLECTION_NAME, (table) => { table.string(TEST_FIELD_NAME); diff --git a/tests/blackbox/routes/items/m2a.test.ts b/tests/blackbox/routes/items/m2a.test.ts index 4a586b8851..15ee4b3214 100644 --- a/tests/blackbox/routes/items/m2a.test.ts +++ b/tests/blackbox/routes/items/m2a.test.ts @@ -1031,6 +1031,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -1155,6 +1156,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf(parseInt(item.children[0].item.name.slice(-1))); @@ -1173,6 +1175,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf(parseInt(item.children[0].item.name.slice(-1))); @@ -1479,6 +1482,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -1607,6 +1611,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.children[0].item.test_datetime_year.toString().slice(-1)) @@ -1627,6 +1632,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.children[0].item.test_datetime_func.year.toString().slice(-1)) diff --git a/tests/blackbox/routes/items/m2m.test.ts b/tests/blackbox/routes/items/m2m.test.ts index 82dd17aaa3..60c8a12bac 100644 --- a/tests/blackbox/routes/items/m2m.test.ts +++ b/tests/blackbox/routes/items/m2m.test.ts @@ -835,6 +835,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -951,6 +952,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.foods[0][`${localCollectionFoods}_id`].name.slice(-1)) @@ -971,6 +973,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.foods[0][`${localCollectionFoods}_id`].name.slice(-1)) @@ -1277,6 +1280,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -1397,6 +1401,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.foods[0][`${localCollectionFoods}_id`].test_datetime_year.toString().slice(-1)) @@ -1417,6 +1422,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.foods[0][`${localCollectionFoods}_id`].test_datetime_func.year.toString().slice(-1)) diff --git a/tests/blackbox/routes/items/m2o.test.ts b/tests/blackbox/routes/items/m2o.test.ts index 746398ece5..b87e8a827e 100644 --- a/tests/blackbox/routes/items/m2o.test.ts +++ b/tests/blackbox/routes/items/m2o.test.ts @@ -1196,6 +1196,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { const count = Number(config.envs[vendor].MAX_BATCH_MUTATION) / 2 + 1; const states: any[] = []; const states2: any[] = []; + for (let i = 0; i < count; i++) { states.push(createState(pkType)); states[i].country_id = createCountry(pkType); diff --git a/tests/blackbox/routes/items/no-relation.test.ts b/tests/blackbox/routes/items/no-relation.test.ts index cc4b32b49f..6a4a8db8f5 100644 --- a/tests/blackbox/routes/items/no-relation.test.ts +++ b/tests/blackbox/routes/items/no-relation.test.ts @@ -263,6 +263,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Setup const artists = []; const artistsCount = 50; + for (let i = 0; i < artistsCount; i++) { artists.push(createArtist(pkType)); } @@ -445,6 +446,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Setup const artists = []; const artistsCount = 5; + for (let i = 0; i < artistsCount; i++) { artists.push(createArtist(pkType)); } @@ -508,6 +510,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { const artists = []; const artists2 = []; const artistsCount = 10; + for (let i = 0; i < artistsCount; i++) { artists.push(createArtist(pkType)); artists2.push(createArtist(pkType)); diff --git a/tests/blackbox/routes/items/o2m.test.ts b/tests/blackbox/routes/items/o2m.test.ts index 9697c24ccd..5164c4b344 100644 --- a/tests/blackbox/routes/items/o2m.test.ts +++ b/tests/blackbox/routes/items/o2m.test.ts @@ -806,6 +806,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -916,6 +917,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf(parseInt(item.states[0].name.slice(-1))); @@ -934,6 +936,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf(parseInt(item.states[0].name.slice(-1))); @@ -1234,6 +1237,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { // Oddity in MySQL5, looks to be indexing delays resulting in missing values if (vendor === 'mysql5') { let lastIndex = -1; + for (const item of response2.body.data.reverse()) { const foundIndex = findIndex(response.body.data, { id: item.id }); if (foundIndex === -1) continue; @@ -1347,6 +1351,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.states[0].test_datetime_year.toString().slice(-1)) @@ -1367,6 +1372,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { expect(data.response.length).toBeLessThanOrEqual(expectedLength); let lastIndex = -1; + for (const item of data.response) { const foundIndex = data.expected.indexOf( parseInt(item.states[0].test_datetime_func.year.toString().slice(-1)) diff --git a/tests/blackbox/routes/items/seed-all-field-types.ts b/tests/blackbox/routes/items/seed-all-field-types.ts index e82b1ccfeb..bef860be2a 100644 --- a/tests/blackbox/routes/items/seed-all-field-types.ts +++ b/tests/blackbox/routes/items/seed-all-field-types.ts @@ -68,6 +68,7 @@ export const seedAllFieldTypesValues = async (vendor: string, collection: string // Create items let generatedStringIdCounter = 0; + for (const key of Object.keys(fieldSchema)) { // Oracle does not have a time datatype if (vendor === 'oracle' && fieldSchema[key].type === 'time') { @@ -129,6 +130,7 @@ export const seedO2MAliasAllFieldTypesValues = async ( for (const aliasKey of possibleKeys) { for (let i = 0; i < valuesQuantity; i++) { const item: any = {}; + if (pkType === 'string') { item['id'] = SeedFunctions.generateValues.string({ quantity: 1, diff --git a/tests/blackbox/routes/items/seed-relational-fields.ts b/tests/blackbox/routes/items/seed-relational-fields.ts index b60c93904c..d3b8a0ed6d 100644 --- a/tests/blackbox/routes/items/seed-relational-fields.ts +++ b/tests/blackbox/routes/items/seed-relational-fields.ts @@ -13,6 +13,7 @@ export const seedRelationalFields = async ( try { // Create items let generatedStringIdCounter = 0; + for (const key of Object.keys(testsSchema)) { // Oracle does not have a time datatype if (vendor === 'oracle' && testsSchema[key].type === 'time') { diff --git a/tests/blackbox/routes/items/singleton.test.ts b/tests/blackbox/routes/items/singleton.test.ts index 07e4bee541..c9f98c7095 100644 --- a/tests/blackbox/routes/items/singleton.test.ts +++ b/tests/blackbox/routes/items/singleton.test.ts @@ -182,6 +182,7 @@ describe.each(common.PRIMARY_KEY_TYPES)('/items', (pkType) => { const updatedO2M = gqlResponsePre.body.data[localCollectionSingleton].o2m; const newO2mItem: any = { name: o2mNameNew2 }; + if (pkType === 'string') { newO2mItem.id = SeedFunctions.generatePrimaryKeys(pkType, { quantity: 1, diff --git a/tests/blackbox/routes/schema/schema.test.ts b/tests/blackbox/routes/schema/schema.test.ts index 1ee65b4e38..9a6df235fd 100644 --- a/tests/blackbox/routes/schema/schema.test.ts +++ b/tests/blackbox/routes/schema/schema.test.ts @@ -596,6 +596,7 @@ describe('Schema Snapshots', () => { // Setup const childrenIDs: Record = {}; const tempRelationalField = 'temp_relational'; + for (const pkType of PRIMARY_KEY_TYPES) { const item = await common.CreateItem(vendor, { collection: `${collectionAll}_${pkType}`, diff --git a/tests/blackbox/setup/setup.ts b/tests/blackbox/setup/setup.ts index d80fc853b9..b2b85515ed 100644 --- a/tests/blackbox/setup/setup.ts +++ b/tests/blackbox/setup/setup.ts @@ -42,6 +42,7 @@ export default async (): Promise => { cwd: paths.cwd, env: config.envs[vendor], }); + if (bootstrap.stderr.length > 0) { throw new Error(`Directus-${vendor} bootstrap failed: \n ${bootstrap.stderr.toString()}`); } @@ -110,6 +111,7 @@ export default async (): Promise => { title: 'Testing server connectivity and bootstrap tests flow', task: async () => { const totalTestsCount = Number(process.env.totalTestsCount); + if (isNaN(totalTestsCount)) { throw new Error('Unable to read totalTestsCount'); } diff --git a/tests/blackbox/utils/prepare-request.ts b/tests/blackbox/utils/prepare-request.ts index 4d340d27b4..8e1943957a 100644 --- a/tests/blackbox/utils/prepare-request.ts +++ b/tests/blackbox/utils/prepare-request.ts @@ -12,6 +12,7 @@ export type RequestOptions = { export const PrepareRequest = (vendor: string, requestOptions: RequestOptions) => { const req = request(getUrl(vendor))[requestOptions.method](requestOptions.path); + if (requestOptions.token) { req.set('Authorization', `Bearer ${requestOptions.token}`); }