From 8e5aaacf89a3cf1bfae21365681f629837eea1cc Mon Sep 17 00:00:00 2001 From: it2022031 Date: Thu, 7 May 2026 12:20:56 +0300 Subject: [PATCH 1/3] refactor(authentication): remove ts-ignore cleanup suppressions --- modules/authentication/src/Authentication.ts | 7 ++-- .../authentication/src/handlers/metamask.ts | 22 +++++++----- modules/authentication/src/handlers/team.ts | 34 +++++++++---------- .../database/src/adapters/DatabaseAdapter.ts | 27 +++++++++++---- .../utils/database-transform-utils.ts | 5 ++- modules/database/src/utils/utilities.ts | 22 +++++++----- 6 files changed, 70 insertions(+), 47 deletions(-) diff --git a/modules/authentication/src/Authentication.ts b/modules/authentication/src/Authentication.ts index 1fd96dc1c..c783fd72b 100644 --- a/modules/authentication/src/Authentication.ts +++ b/modules/authentication/src/Authentication.ts @@ -787,11 +787,12 @@ export default class Authentication extends ManagedModule { const { email, teamId } = call.request; try { - const deletedToken = await Token.getInstance().deleteOne({ - // @ts-expect-error Unsafe nested property access + const invitationQuery: Indexable = { 'data.teamId': teamId, 'data.email': email, - }); + }; + + const deletedToken = await Token.getInstance().deleteOne(invitationQuery); if (deletedToken.deletedCount === 0) { return callback({ diff --git a/modules/authentication/src/handlers/metamask.ts b/modules/authentication/src/handlers/metamask.ts index 68aa672b8..9150c040c 100644 --- a/modules/authentication/src/handlers/metamask.ts +++ b/modules/authentication/src/handlers/metamask.ts @@ -5,6 +5,7 @@ import { ConduitRouteActions, ConduitRouteReturnDefinition, GrpcError, + Indexable, ParsedRouterRequest, UnparsedRouterResponse, } from '@conduitplatform/grpc-sdk'; @@ -79,10 +80,11 @@ export class MetamaskHandlers implements IAuthenticationStrategy { const { ethPublicAddress } = call.request.params; const normalizedEthPublicAddress = ethPublicAddress.toLowerCase(); - const existingUser: User | null = await User.getInstance().findOne({ - // @ts-expect-error Unsafe nested property access + const metamaskQuery: Indexable = { 'metamask.ethPublicAddress': normalizedEthPublicAddress, - }); + }; + + const existingUser: User | null = await User.getInstance().findOne(metamaskQuery); if (existingUser) { return { nonce: existingUser.metamask!.nonce }; @@ -109,10 +111,11 @@ export class MetamaskHandlers implements IAuthenticationStrategy { throw new GrpcError(status.UNAUTHENTICATED, 'No headers provided'); } - const user = await User.getInstance().findOne({ - // @ts-expect-error Unsafe nested property access + const metamaskQuery: Indexable = { 'metamask.ethPublicAddress': normalizedEthPublicAddress, - }); + }; + + const user = await User.getInstance().findOne(metamaskQuery); if (isNil(user)) { throw new GrpcError( @@ -154,10 +157,11 @@ export class MetamaskHandlers implements IAuthenticationStrategy { ); } - await User.getInstance().findByIdAndUpdate(user._id, { - // @ts-expect-error Unsafe nested property access + const nonceUpdate: Indexable = { 'metamask.nonce': uuid(), - }); + }; + + await User.getInstance().findByIdAndUpdate(user._id, nonceUpdate); const config = ConfigController.getInstance().config; return TokenProvider.getInstance().provideUserTokens({ diff --git a/modules/authentication/src/handlers/team.ts b/modules/authentication/src/handlers/team.ts index f45cf9892..7189fa8bd 100644 --- a/modules/authentication/src/handlers/team.ts +++ b/modules/authentication/src/handlers/team.ts @@ -145,11 +145,12 @@ export class TeamsHandler implements IAuthenticationStrategy { } async getUserInvites(call: ParsedRouterRequest): Promise { - const invites = await Token.getInstance().findMany({ + const userInvitesQuery: Indexable = { tokenType: TokenType.TEAM_INVITE_TOKEN, - // @ts-ignore 'data.email': call.request.context.user.email, - }); + }; + + const invites = await Token.getInstance().findMany(userInvitesQuery); return { invites: invites.map(invite => ({ teamId: invite.data.teamId, @@ -595,12 +596,13 @@ export class TeamsHandler implements IAuthenticationStrategy { } // Delete any existing invite for the same email and team - await Token.getInstance().deleteOne({ + const existingInviteQuery: Indexable = { tokenType: TokenType.TEAM_INVITE_TOKEN, - // @ts-expect-error Unsafe nested property access 'data.teamId': teamId, 'data.email': email, - }); + }; + + await Token.getInstance().deleteOne(existingInviteQuery); const invitation = await this.createUserInvitation({ teamId, @@ -661,12 +663,13 @@ export class TeamsHandler implements IAuthenticationStrategy { } // Delete any existing invite for the same email and team - await Token.getInstance().deleteOne({ + const invitationQuery: Indexable = { tokenType: TokenType.TEAM_INVITE_TOKEN, - // @ts-expect-error Unsafe nested property access 'data.teamId': teamId, 'data.email': email, - }); + }; + + await Token.getInstance().deleteOne(invitationQuery); return 'OK'; } @@ -698,17 +701,14 @@ export class TeamsHandler implements IAuthenticationStrategy { ); } - const invites = await Token.getInstance().findMany({ + const teamInvitesQuery: Indexable = { tokenType: TokenType.TEAM_INVITE_TOKEN, - // @ts-expect-error Unsafe nested property access 'data.teamId': teamId, - }); + }; - const count = await Token.getInstance().countDocuments({ - tokenType: TokenType.TEAM_INVITE_TOKEN, - // @ts-expect-error Unsafe nested property access - 'data.teamId': teamId, - }); + const invites = await Token.getInstance().findMany(teamInvitesQuery); + + const count = await Token.getInstance().countDocuments(teamInvitesQuery); return { invites, diff --git a/modules/database/src/adapters/DatabaseAdapter.ts b/modules/database/src/adapters/DatabaseAdapter.ts index 7e5f898c9..cac304f8f 100644 --- a/modules/database/src/adapters/DatabaseAdapter.ts +++ b/modules/database/src/adapters/DatabaseAdapter.ts @@ -588,16 +588,29 @@ export abstract class DatabaseAdapter { canModify: 'Everything', canDelete: true, } as const; + + const setDefaultPermission = ( + permissions: NonNullable< + NonNullable['permissions'] + >, + key: K, + ) => { + if (!Object.prototype.hasOwnProperty.call(permissions, key)) { + permissions[key] = defaultPermissions[key]; + } + }; + if (isNil(schema.modelOptions.conduit)) schema.modelOptions.conduit = {}; + if (isNil(schema.modelOptions.conduit.permissions)) { - schema.modelOptions.conduit!.permissions = defaultPermissions; + schema.modelOptions.conduit.permissions = { ...defaultPermissions }; } else { - Object.keys(defaultPermissions).forEach(perm => { - if (!schema.modelOptions.conduit!.permissions!.hasOwnProperty(perm)) { - // @ts-ignore - schema.modelOptions.conduit!.permissions![perm] = - defaultPermissions[perm as keyof typeof defaultPermissions]; - } + const permissions = schema.modelOptions.conduit.permissions; + const defaultPermissionKeys = Object.keys(defaultPermissions) as Array< + keyof typeof defaultPermissions + >; + defaultPermissionKeys.forEach(perm => { + setDefaultPermission(permissions, perm); }); } return schema; diff --git a/modules/database/src/adapters/utils/database-transform-utils.ts b/modules/database/src/adapters/utils/database-transform-utils.ts index 6ec5768d0..fc7dbffd9 100644 --- a/modules/database/src/adapters/utils/database-transform-utils.ts +++ b/modules/database/src/adapters/utils/database-transform-utils.ts @@ -120,9 +120,8 @@ export function extractFieldProperties( } else if (objectField.hasOwnProperty('unique') && objectField.unique) { res.unique = objectField.unique ?? false; res.allowNull = false; - } else if (objectField.hasOwnProperty('required') && objectField.required) { - // @ts-expect-error - res.allowNull = !objectField.required ?? true; + } else if (objectField.hasOwnProperty('required') && isBoolean(objectField.required)) { + res.allowNull = !objectField.required; } return res; diff --git a/modules/database/src/utils/utilities.ts b/modules/database/src/utils/utilities.ts index ce6cd7328..9899f70ba 100644 --- a/modules/database/src/utils/utilities.ts +++ b/modules/database/src/utils/utilities.ts @@ -222,22 +222,28 @@ function validateCrudOperations(crudOperations: CrudOperations) { throw new Error(`CMS field 'crudOperations' must be of type Object`); Object.keys(crudOperations).forEach(op => { - if (!allowedCrudOperations.includes(op as keyof CrudOperations)) { + const crudOperation = op as keyof CrudOperations; + + if (!allowedCrudOperations.includes(crudOperation)) { throw new Error(`Unrecognized CRUD operation '${op}' provided`); } - // @ts-ignore - if (!isObject(crudOperations[op])) { + + const operationConfig = crudOperations[crudOperation]; + + if (!isObject(operationConfig)) { throw new Error(`Crud operation field '${op}' must be of type Object`); } - // @ts-ignore - Object.keys(crudOperations[op]).forEach(opField => { - if (!['enabled', 'authenticated'].includes(opField)) { + + Object.keys(operationConfig).forEach(opField => { + const crudOperationField = opField as keyof typeof operationConfig; + + if (!['enabled', 'authenticated'].includes(crudOperationField)) { throw new Error( `Unrecognized crud operation field '${opField}' for operation '${op}' provided`, ); } - // @ts-ignore - if (!isBoolean(crudOperations[op][opField])) { + + if (!isBoolean(operationConfig[crudOperationField])) { throw new Error( `Crud operation field '${opField}' for operation '${op}' must be of type Boolean`, ); From a653dcd12224ff6800648e6f55db103ad590f261 Mon Sep 17 00:00:00 2001 From: Christina Papadogianni <59121443+ChrisPdgn@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:51:07 +0000 Subject: [PATCH 2/3] refactor(auth,database,communications): address review on ts-ignore cleanup Use Query for dotted-key queries and updates, keep extractFieldProperties allowNull on the original required-truthy path, and declare Mandrill package types in communications. Co-authored-by: Christina Papadogianni --- modules/authentication/src/Authentication.ts | 7 +++-- .../authentication/src/handlers/metamask.ts | 20 +++++--------- modules/authentication/src/handlers/team.ts | 22 ++++++--------- .../email/mandrill/MandrilProvider.ts | 4 --- .../src/types/mailchimp-transactional.d.ts | 27 +++++++++++++++++++ .../types/nodemailer-mandrill-transport.d.ts | 13 +++++++++ .../utils/database-transform-utils.ts | 4 +-- 7 files changed, 60 insertions(+), 37 deletions(-) create mode 100644 modules/communications/src/types/mailchimp-transactional.d.ts create mode 100644 modules/communications/src/types/nodemailer-mandrill-transport.d.ts diff --git a/modules/authentication/src/Authentication.ts b/modules/authentication/src/Authentication.ts index c783fd72b..3eecf4408 100644 --- a/modules/authentication/src/Authentication.ts +++ b/modules/authentication/src/Authentication.ts @@ -6,6 +6,7 @@ import { GrpcRequest, HealthCheckStatus, Indexable, + Query, } from '@conduitplatform/grpc-sdk'; import path from 'path'; import { isEmpty, isNil } from 'lodash-es'; @@ -787,12 +788,10 @@ export default class Authentication extends ManagedModule { const { email, teamId } = call.request; try { - const invitationQuery: Indexable = { + const deletedToken = await Token.getInstance().deleteOne({ 'data.teamId': teamId, 'data.email': email, - }; - - const deletedToken = await Token.getInstance().deleteOne(invitationQuery); + } as Query); if (deletedToken.deletedCount === 0) { return callback({ diff --git a/modules/authentication/src/handlers/metamask.ts b/modules/authentication/src/handlers/metamask.ts index 9150c040c..735211e60 100644 --- a/modules/authentication/src/handlers/metamask.ts +++ b/modules/authentication/src/handlers/metamask.ts @@ -5,8 +5,8 @@ import { ConduitRouteActions, ConduitRouteReturnDefinition, GrpcError, - Indexable, ParsedRouterRequest, + Query, UnparsedRouterResponse, } from '@conduitplatform/grpc-sdk'; import { User } from '../models/index.js'; @@ -80,11 +80,9 @@ export class MetamaskHandlers implements IAuthenticationStrategy { const { ethPublicAddress } = call.request.params; const normalizedEthPublicAddress = ethPublicAddress.toLowerCase(); - const metamaskQuery: Indexable = { + const existingUser: User | null = await User.getInstance().findOne({ 'metamask.ethPublicAddress': normalizedEthPublicAddress, - }; - - const existingUser: User | null = await User.getInstance().findOne(metamaskQuery); + } as Query); if (existingUser) { return { nonce: existingUser.metamask!.nonce }; @@ -111,11 +109,9 @@ export class MetamaskHandlers implements IAuthenticationStrategy { throw new GrpcError(status.UNAUTHENTICATED, 'No headers provided'); } - const metamaskQuery: Indexable = { + const user = await User.getInstance().findOne({ 'metamask.ethPublicAddress': normalizedEthPublicAddress, - }; - - const user = await User.getInstance().findOne(metamaskQuery); + } as Query); if (isNil(user)) { throw new GrpcError( @@ -157,11 +153,9 @@ export class MetamaskHandlers implements IAuthenticationStrategy { ); } - const nonceUpdate: Indexable = { + await User.getInstance().findByIdAndUpdate(user._id, { 'metamask.nonce': uuid(), - }; - - await User.getInstance().findByIdAndUpdate(user._id, nonceUpdate); + } as Query); const config = ConfigController.getInstance().config; return TokenProvider.getInstance().provideUserTokens({ diff --git a/modules/authentication/src/handlers/team.ts b/modules/authentication/src/handlers/team.ts index 7189fa8bd..ee2a24c5c 100644 --- a/modules/authentication/src/handlers/team.ts +++ b/modules/authentication/src/handlers/team.ts @@ -145,12 +145,10 @@ export class TeamsHandler implements IAuthenticationStrategy { } async getUserInvites(call: ParsedRouterRequest): Promise { - const userInvitesQuery: Indexable = { + const invites = await Token.getInstance().findMany({ tokenType: TokenType.TEAM_INVITE_TOKEN, 'data.email': call.request.context.user.email, - }; - - const invites = await Token.getInstance().findMany(userInvitesQuery); + } as Query); return { invites: invites.map(invite => ({ teamId: invite.data.teamId, @@ -596,13 +594,11 @@ export class TeamsHandler implements IAuthenticationStrategy { } // Delete any existing invite for the same email and team - const existingInviteQuery: Indexable = { + await Token.getInstance().deleteOne({ tokenType: TokenType.TEAM_INVITE_TOKEN, 'data.teamId': teamId, 'data.email': email, - }; - - await Token.getInstance().deleteOne(existingInviteQuery); + } as Query); const invitation = await this.createUserInvitation({ teamId, @@ -663,13 +659,11 @@ export class TeamsHandler implements IAuthenticationStrategy { } // Delete any existing invite for the same email and team - const invitationQuery: Indexable = { + await Token.getInstance().deleteOne({ tokenType: TokenType.TEAM_INVITE_TOKEN, 'data.teamId': teamId, 'data.email': email, - }; - - await Token.getInstance().deleteOne(invitationQuery); + } as Query); return 'OK'; } @@ -701,10 +695,10 @@ export class TeamsHandler implements IAuthenticationStrategy { ); } - const teamInvitesQuery: Indexable = { + const teamInvitesQuery = { tokenType: TokenType.TEAM_INVITE_TOKEN, 'data.teamId': teamId, - }; + } as Query; const invites = await Token.getInstance().findMany(teamInvitesQuery); diff --git a/modules/communications/src/providers/email/mandrill/MandrilProvider.ts b/modules/communications/src/providers/email/mandrill/MandrilProvider.ts index 3be1fe3ad..7f37be2ae 100644 --- a/modules/communications/src/providers/email/mandrill/MandrilProvider.ts +++ b/modules/communications/src/providers/email/mandrill/MandrilProvider.ts @@ -7,11 +7,7 @@ import { MandrillBuilder } from './mandrillBuilder.js'; import { getHandleBarsValues } from '../utils/index.js'; import { MandrillTemplate } from '../interfaces/mandrill/MandrillTemplate.js'; -// @ts-expect-error — CJS package without bundled types import mailchimpFactory from '@mailchimp/mailchimp_transactional'; - -// @ts-expect-error -// missing typings for nodemailer-mandrill-transport import mandrillTransport from 'nodemailer-mandrill-transport'; import { Indexable } from '@conduitplatform/grpc-sdk'; diff --git a/modules/communications/src/types/mailchimp-transactional.d.ts b/modules/communications/src/types/mailchimp-transactional.d.ts new file mode 100644 index 000000000..fa9f735d0 --- /dev/null +++ b/modules/communications/src/types/mailchimp-transactional.d.ts @@ -0,0 +1,27 @@ +declare module '@mailchimp/mailchimp_transactional' { + interface MailchimpTransactional { + templates: { + list(body: Record): Promise; + info(body: { name: string }): Promise; + add(body: { + name: string; + subject?: string; + code?: string; + publish?: boolean; + }): Promise<{ slug: string }>; + update(body: { + name: string; + code?: string; + subject?: string; + }): Promise<{ slug: string }>; + delete(body: { name: string }): Promise; + }; + messages: { + info(body: { id: string }): Promise; + }; + } + + function mailchimpFactory(apiKey: string): MailchimpTransactional; + + export default mailchimpFactory; +} diff --git a/modules/communications/src/types/nodemailer-mandrill-transport.d.ts b/modules/communications/src/types/nodemailer-mandrill-transport.d.ts new file mode 100644 index 000000000..38818e933 --- /dev/null +++ b/modules/communications/src/types/nodemailer-mandrill-transport.d.ts @@ -0,0 +1,13 @@ +declare module 'nodemailer-mandrill-transport' { + import type { Transport } from 'nodemailer'; + + interface MandrillTransportOptions { + auth: { + apiKey: string; + }; + } + + function mandrillTransport(options: MandrillTransportOptions): Transport; + + export default mandrillTransport; +} diff --git a/modules/database/src/adapters/utils/database-transform-utils.ts b/modules/database/src/adapters/utils/database-transform-utils.ts index fc7dbffd9..d2f6b86e5 100644 --- a/modules/database/src/adapters/utils/database-transform-utils.ts +++ b/modules/database/src/adapters/utils/database-transform-utils.ts @@ -120,8 +120,8 @@ export function extractFieldProperties( } else if (objectField.hasOwnProperty('unique') && objectField.unique) { res.unique = objectField.unique ?? false; res.allowNull = false; - } else if (objectField.hasOwnProperty('required') && isBoolean(objectField.required)) { - res.allowNull = !objectField.required; + } else if (objectField.hasOwnProperty('required') && objectField.required) { + res.allowNull = !objectField.required ?? true; } return res; From 1f905c2afba3171970d1c6d17007fa749a795790 Mon Sep 17 00:00:00 2001 From: Christina Papadogianni <59121443+ChrisPdgn@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:57:03 +0000 Subject: [PATCH 3/3] fix(database,communications): make ts-ignore cleanup typecheck Drop the dead `?? true` on allowNull (TS2869), merge default permissions with a typed spread instead of a union-key write, and keep Mandrill ambient types loose enough for existing assignments. Co-authored-by: Christina Papadogianni --- .../src/types/mailchimp-transactional.d.ts | 16 ++++------ .../types/nodemailer-mandrill-transport.d.ts | 10 +------ .../database/src/adapters/DatabaseAdapter.ts | 30 +++++-------------- .../utils/database-transform-utils.ts | 2 +- modules/database/src/utils/utilities.ts | 26 ++++++++-------- 5 files changed, 28 insertions(+), 56 deletions(-) diff --git a/modules/communications/src/types/mailchimp-transactional.d.ts b/modules/communications/src/types/mailchimp-transactional.d.ts index fa9f735d0..ff52683e5 100644 --- a/modules/communications/src/types/mailchimp-transactional.d.ts +++ b/modules/communications/src/types/mailchimp-transactional.d.ts @@ -1,23 +1,19 @@ declare module '@mailchimp/mailchimp_transactional' { interface MailchimpTransactional { templates: { - list(body: Record): Promise; - info(body: { name: string }): Promise; + list(body?: Record): Promise; + info(body: { name: string }): Promise; add(body: { name: string; subject?: string; code?: string; publish?: boolean; - }): Promise<{ slug: string }>; - update(body: { - name: string; - code?: string; - subject?: string; - }): Promise<{ slug: string }>; - delete(body: { name: string }): Promise; + }): Promise; + update(body: { name: string; code?: string; subject?: string }): Promise; + delete(body: { name: string }): Promise; }; messages: { - info(body: { id: string }): Promise; + info(body: { id: string }): Promise; }; } diff --git a/modules/communications/src/types/nodemailer-mandrill-transport.d.ts b/modules/communications/src/types/nodemailer-mandrill-transport.d.ts index 38818e933..68e9c366c 100644 --- a/modules/communications/src/types/nodemailer-mandrill-transport.d.ts +++ b/modules/communications/src/types/nodemailer-mandrill-transport.d.ts @@ -1,13 +1,5 @@ declare module 'nodemailer-mandrill-transport' { - import type { Transport } from 'nodemailer'; - - interface MandrillTransportOptions { - auth: { - apiKey: string; - }; - } - - function mandrillTransport(options: MandrillTransportOptions): Transport; + function mandrillTransport(options: { auth: { apiKey: string } }): any; export default mandrillTransport; } diff --git a/modules/database/src/adapters/DatabaseAdapter.ts b/modules/database/src/adapters/DatabaseAdapter.ts index cac304f8f..6345c6413 100644 --- a/modules/database/src/adapters/DatabaseAdapter.ts +++ b/modules/database/src/adapters/DatabaseAdapter.ts @@ -582,37 +582,21 @@ export abstract class DatabaseAdapter { } protected addSchemaPermissions(schema: ConduitSchema) { - const defaultPermissions = { + const defaultPermissions: NonNullable< + NonNullable['permissions'] + > = { extendable: true, canCreate: true, canModify: 'Everything', canDelete: true, - } as const; - - const setDefaultPermission = ( - permissions: NonNullable< - NonNullable['permissions'] - >, - key: K, - ) => { - if (!Object.prototype.hasOwnProperty.call(permissions, key)) { - permissions[key] = defaultPermissions[key]; - } }; if (isNil(schema.modelOptions.conduit)) schema.modelOptions.conduit = {}; - if (isNil(schema.modelOptions.conduit.permissions)) { - schema.modelOptions.conduit.permissions = { ...defaultPermissions }; - } else { - const permissions = schema.modelOptions.conduit.permissions; - const defaultPermissionKeys = Object.keys(defaultPermissions) as Array< - keyof typeof defaultPermissions - >; - defaultPermissionKeys.forEach(perm => { - setDefaultPermission(permissions, perm); - }); - } + schema.modelOptions.conduit.permissions = { + ...defaultPermissions, + ...schema.modelOptions.conduit.permissions, + }; return schema; } diff --git a/modules/database/src/adapters/utils/database-transform-utils.ts b/modules/database/src/adapters/utils/database-transform-utils.ts index d2f6b86e5..72956f938 100644 --- a/modules/database/src/adapters/utils/database-transform-utils.ts +++ b/modules/database/src/adapters/utils/database-transform-utils.ts @@ -121,7 +121,7 @@ export function extractFieldProperties( res.unique = objectField.unique ?? false; res.allowNull = false; } else if (objectField.hasOwnProperty('required') && objectField.required) { - res.allowNull = !objectField.required ?? true; + res.allowNull = !objectField.required; } return res; diff --git a/modules/database/src/utils/utilities.ts b/modules/database/src/utils/utilities.ts index 9899f70ba..15f6e4d4f 100644 --- a/modules/database/src/utils/utilities.ts +++ b/modules/database/src/utils/utilities.ts @@ -18,11 +18,13 @@ import { import * as deepdash from 'deepdash-es/standalone'; +type CrudOperationConfig = { enabled?: boolean; authenticated?: boolean }; + type CrudOperations = { - create?: { enabled?: boolean; authenticated?: boolean }; - read?: { enabled?: boolean; authenticated?: boolean }; - update?: { enabled?: boolean; authenticated?: boolean }; - delete?: { enabled?: boolean; authenticated?: boolean }; + create?: CrudOperationConfig; + read?: CrudOperationConfig; + update?: CrudOperationConfig; + delete?: CrudOperationConfig; }; interface Permissions { @@ -221,29 +223,27 @@ function validateCrudOperations(crudOperations: CrudOperations) { if (!isObject(crudOperations)) throw new Error(`CMS field 'crudOperations' must be of type Object`); - Object.keys(crudOperations).forEach(op => { - const crudOperation = op as keyof CrudOperations; - - if (!allowedCrudOperations.includes(crudOperation)) { + (Object.keys(crudOperations) as Array).forEach(op => { + if (!allowedCrudOperations.includes(op)) { throw new Error(`Unrecognized CRUD operation '${op}' provided`); } - const operationConfig = crudOperations[crudOperation]; + const operationConfig = crudOperations[op]; if (!isObject(operationConfig)) { throw new Error(`Crud operation field '${op}' must be of type Object`); } - Object.keys(operationConfig).forEach(opField => { - const crudOperationField = opField as keyof typeof operationConfig; + const config = operationConfig as CrudOperationConfig; - if (!['enabled', 'authenticated'].includes(crudOperationField)) { + Object.keys(config).forEach(opField => { + if (opField !== 'enabled' && opField !== 'authenticated') { throw new Error( `Unrecognized crud operation field '${opField}' for operation '${op}' provided`, ); } - if (!isBoolean(operationConfig[crudOperationField])) { + if (!isBoolean(config[opField])) { throw new Error( `Crud operation field '${opField}' for operation '${op}' must be of type Boolean`, );