diff --git a/packages/nocodb/src/db/CustomKnex.ts b/packages/nocodb/src/db/CustomKnex.ts index cda64ad395..9077ba8109 100644 --- a/packages/nocodb/src/db/CustomKnex.ts +++ b/packages/nocodb/src/db/CustomKnex.ts @@ -492,23 +492,27 @@ const appendWhereCondition = function ( return knexRef; }; -type XcConditionObjVal = { - [key in 'eq' | 'neq' | 'lt' | 'gt' | 'ge' | 'le' | 'like' | 'nlike']: - | string - | number - | any; -}; - -interface XcXonditionObj { - _or: XcXonditionObj[]; - _and: XcXonditionObj[]; - _not: XcXonditionObj; - [key: string]: - | XcXonditionObj - | XcXonditionObj[] - | XcConditionObjVal - | XcConditionObjVal[]; +type AtLeastOne }> = Partial & + U[keyof U]; + +export type ConditionVal = AtLeastOne<{ + eq: string | number | boolean | Date; + neq: string | number | boolean | Date; + lt: number | string | Date; + gt: number | string | Date; + ge: number | string | Date; + le: number | string | Date; + like: string; + nlike: string; +}>; + +export interface Condition { + _or?: Condition[]; + _and?: Condition[]; + _not?: Condition; + + [key: string]: ConditionVal | Condition | Condition[]; } declare module 'knex' { @@ -527,7 +531,7 @@ declare module 'knex' { ): Knex.QueryBuilder; condition( - conditionObj: XcXonditionObj, + conditionObj: Condition, columnAliases?: { [columnAlias: string]: string; }, @@ -542,7 +546,7 @@ declare module 'knex' { ): Knex.QueryBuilder; conditionGraph(condition: { - condition: XcXonditionObj; + condition: Condition; models: { [key: string]: BaseModelSql }; }): Knex.QueryBuilder; @@ -552,6 +556,8 @@ declare module 'knex' { [columnAlias: string]: string; }, ): Knex.QueryBuilder; + + hasWhere(): boolean; } } } @@ -1274,8 +1280,15 @@ function parseNestedConditionv2(obj, qb, pKey?, table?, tableAlias?) { return qb; } -// Conditionv2 +// extend the knex query builder with a method to check if a where clause exists +knex.QueryBuilder.extend('hasWhere', function () { + // Inspect the _statements array for 'where' clauses + return ( + this as unknown as { _statements: { grouping: string }[] } + )._statements.some((statement) => statement.grouping === 'where') as any; +}); +// Conditionv2 /** * Append custom where condition(nested object) to knex query builder */ diff --git a/packages/nocodb/src/filters/global-exception/global-exception.filter.ts b/packages/nocodb/src/filters/global-exception/global-exception.filter.ts index 2c4a7e4c46..352b4e160f 100644 --- a/packages/nocodb/src/filters/global-exception/global-exception.filter.ts +++ b/packages/nocodb/src/filters/global-exception/global-exception.filter.ts @@ -41,9 +41,8 @@ export class GlobalExceptionFilter implements ExceptionFilter { } // try to extract db error for unknown errors - const dbError = !(exception instanceof NcBaseError) - ? extractDBError(exception) - : null; + const dbError = + exception instanceof NcBaseError ? null : extractDBError(exception); // skip unnecessary error logging if ( diff --git a/packages/nocodb/src/helpers/catchError.ts b/packages/nocodb/src/helpers/catchError.ts index 0c7c6e2a4f..86269874c3 100644 --- a/packages/nocodb/src/helpers/catchError.ts +++ b/packages/nocodb/src/helpers/catchError.ts @@ -415,6 +415,13 @@ export class NotFound extends NcBaseError {} export class SsoError extends NcBaseError {} +export class MetaError extends NcBaseError { + constructor(param: { message: string; sql: string }) { + super(param.message); + Object.assign(this, param); + } +} + export class ExternalError extends NcBaseError { constructor(error: Error) { super(error.message); @@ -747,4 +754,8 @@ export class NcError { `Email domain ${domain} is not allowed for this organization`, ); } + + static metaError(param: { message: string; sql: string }) { + throw new MetaError(param); + } } diff --git a/packages/nocodb/src/meta/meta.service.ts b/packages/nocodb/src/meta/meta.service.ts index 758c4a7934..bdbb378173 100644 --- a/packages/nocodb/src/meta/meta.service.ts +++ b/packages/nocodb/src/meta/meta.service.ts @@ -6,20 +6,17 @@ import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import type * as knex from 'knex'; import type { Knex } from 'knex'; +import type { Condition } from '~/db/CustomKnex'; import XcMigrationSource from '~/meta/migrations/XcMigrationSource'; import XcMigrationSourcev2 from '~/meta/migrations/XcMigrationSourcev2'; import { XKnex } from '~/db/CustomKnex'; import { NcConfig } from '~/utils/nc-config'; import { MetaTable } from '~/utils/globals'; - +import { NcError } from '~/helpers/catchError'; dayjs.extend(utc); dayjs.extend(timezone); const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); - -// todo: tobe fixed -const META_TABLES = []; - const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14); @Injectable() @@ -56,6 +53,14 @@ export class MetaService { return this.knexConnection; } + /*** + * Get single record from meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param idOrCondition - If string, will get the record with the given id. If object, will get the record with the given condition. + * @param fields - Fields to be selected + */ public async metaGet( base_id: string, dbAlias: string, @@ -96,6 +101,14 @@ export class MetaService { return query.first(); } + /*** + * Insert record into meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param data - Data to be inserted + * @param ignoreIdGeneration - If true, will not generate id for the record + */ public async metaInsert2( base_id: string, source_id: string, @@ -119,6 +132,15 @@ export class MetaService { }); return insertObj; } + + /*** + * Insert multiple records into meta data + * @param base_id - Base id + * @param source_id - Source id + * @param target - Table name + * @param data - Data to be inserted + * @param ignoreIdGeneration - If true, will not generate id for the record + */ public async bulkMetaInsert( base_id: string, source_id: string, @@ -156,120 +178,67 @@ export class MetaService { return insertObj; } + /*** + * Generate nanoid for the given target + * @param target - Table name + * @returns {string} - Generated nanoid + * */ public async genNanoid(target: string) { - let prefix; - switch (target) { - case MetaTable.PROJECT: - prefix = 'p'; - break; - case MetaTable.BASES: - prefix = 'b'; - break; - case MetaTable.MODELS: - prefix = 'm'; - break; - case MetaTable.COLUMNS: - prefix = 'c'; - break; - case MetaTable.COL_RELATIONS: - prefix = 'l'; - break; - case MetaTable.COL_SELECT_OPTIONS: - prefix = 's'; - break; - case MetaTable.COL_LOOKUP: - prefix = 'lk'; - break; - case MetaTable.COL_ROLLUP: - prefix = 'rl'; - break; - case MetaTable.COL_FORMULA: - prefix = 'f'; - break; - case MetaTable.FILTER_EXP: - prefix = 'fi'; - break; - case MetaTable.SORT: - prefix = 'so'; - break; - case MetaTable.SHARED_VIEWS: - prefix = 'sv'; - break; - case MetaTable.ACL: - prefix = 'ac'; - break; - case MetaTable.FORM_VIEW: - prefix = 'fv'; - break; - case MetaTable.FORM_VIEW_COLUMNS: - prefix = 'fvc'; - break; - case MetaTable.GALLERY_VIEW: - prefix = 'gv'; - break; - case MetaTable.GALLERY_VIEW_COLUMNS: - prefix = 'gvc'; - break; - case MetaTable.KANBAN_VIEW: - prefix = 'kv'; - break; - case MetaTable.KANBAN_VIEW_COLUMNS: - prefix = 'kvc'; - break; - case MetaTable.CALENDAR_VIEW: - prefix = 'cv'; - break; - case MetaTable.CALENDAR_VIEW_COLUMNS: - prefix = 'cvc'; - break; - case MetaTable.CALENDAR_VIEW_RANGE: - prefix = 'cvr'; - break; - case MetaTable.USERS: - prefix = 'us'; - break; - case MetaTable.ORGS_OLD: - prefix = 'org'; - break; - case MetaTable.TEAMS: - prefix = 'tm'; - break; - case MetaTable.VIEWS: - prefix = 'vw'; - break; - case MetaTable.HOOKS: - prefix = 'hk'; - break; - case MetaTable.HOOK_LOGS: - prefix = 'hkl'; - break; - case MetaTable.AUDIT: - prefix = 'adt'; - break; - case MetaTable.API_TOKENS: - prefix = 'tkn'; - break; - case MetaTable.EXTENSIONS: - prefix = 'ext'; - break; - case MetaTable.COMMENTS: - prefix = 'com'; - break; - case MetaTable.COMMENTS_REACTIONS: - prefix = 'cre'; - break; - case MetaTable.USER_COMMENTS_NOTIFICATIONS_PREFERENCE: - prefix = 'cnp'; - break; - default: - prefix = 'nc'; - break; - } + const prefixMap: { [key: string]: string } = { + [MetaTable.PROJECT]: 'p', + [MetaTable.BASES]: 'b', + [MetaTable.MODELS]: 'm', + [MetaTable.COLUMNS]: 'c', + [MetaTable.COL_RELATIONS]: 'l', + [MetaTable.COL_SELECT_OPTIONS]: 's', + [MetaTable.COL_LOOKUP]: 'lk', + [MetaTable.COL_ROLLUP]: 'rl', + [MetaTable.COL_FORMULA]: 'f', + [MetaTable.FILTER_EXP]: 'fi', + [MetaTable.SORT]: 'so', + [MetaTable.SHARED_VIEWS]: 'sv', + [MetaTable.ACL]: 'ac', + [MetaTable.FORM_VIEW]: 'fv', + [MetaTable.FORM_VIEW_COLUMNS]: 'fvc', + [MetaTable.GALLERY_VIEW]: 'gv', + [MetaTable.GALLERY_VIEW_COLUMNS]: 'gvc', + [MetaTable.KANBAN_VIEW]: 'kv', + [MetaTable.KANBAN_VIEW_COLUMNS]: 'kvc', + [MetaTable.CALENDAR_VIEW]: 'cv', + [MetaTable.CALENDAR_VIEW_COLUMNS]: 'cvc', + [MetaTable.CALENDAR_VIEW_RANGE]: 'cvr', + [MetaTable.USERS]: 'us', + [MetaTable.ORGS_OLD]: 'org', + [MetaTable.TEAMS]: 'tm', + [MetaTable.VIEWS]: 'vw', + [MetaTable.HOOKS]: 'hk', + [MetaTable.HOOK_LOGS]: 'hkl', + [MetaTable.AUDIT]: 'adt', + [MetaTable.API_TOKENS]: 'tkn', + [MetaTable.EXTENSIONS]: 'ext', + [MetaTable.COMMENTS]: 'com', + [MetaTable.COMMENTS_REACTIONS]: 'cre', + [MetaTable.USER_COMMENTS_NOTIFICATIONS_PREFERENCE]: 'cnp', + }; + + const prefix = prefixMap[target] || 'nc'; // using nanoid to avoid collision with existing ids when duplicating return `${prefix}${nanoidv2()}`; } - + /*** + * Get paginated list of meta data + * @param baseId - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param args.condition - Condition to be applied + * @param args.limit - Limit of records + * @param args.offset - Offset of records + * @param args.xcCondition - Additional nested or complex condition to be added to the query. + * @param args.fields - Fields to be selected + * @param args.sort - Sort field and direction + * @returns {Promise<{list: any[]; count: number}>} - List of records and count + * */ public async metaPaginatedList( baseId: string, dbAlias: string, @@ -278,7 +247,7 @@ export class MetaService { condition?: { [key: string]: any }; limit?: number; offset?: number; - xcCondition?; + xcCondition?: Condition; fields?: string[]; sort?: { field: string; desc?: boolean }; }, @@ -327,50 +296,22 @@ export class MetaService { // todo: need to fix private trx: Knex.Transaction; - // constructor(app: Noco, config: NcConfig, trx = null) { - // super(app, config); - // - // if (this.config?.meta?.db) { - // this.connection = trx || XKnex(this.config?.meta?.db); - // } else { - // let dbIndex = this.config.envs?.[this.config.workingEnv]?.db.findIndex( - // (c) => c.meta.dbAlias === this.config?.auth?.jwt?.dbAlias - // ); - // dbIndex = dbIndex === -1 ? 0 : dbIndex; - // this.connection = XKnex( - // this.config.envs?.[this.config.workingEnv]?.db[dbIndex] as any - // ); - // } - // this.trx = trx; - // NcConnectionMgr.setXcMeta(this); - // } - - // public get knexConnection(): XKnex { - // return (this.trx || this.connection) as any; - // } - - // public updateKnex(connectionConfig): void { - // this.connection = XKnex(connectionConfig); - // } - - // public async metaInit(): Promise { - // await this.connection.migrate.latest({ - // migrationSource: new XcMigrationSource(), - // tableName: 'xc_knex_migrations', - // }); - // await this.connection.migrate.latest({ - // migrationSource: new XcMigrationSourcev2(), - // tableName: 'xc_knex_migrationsv2', - // }); - // return true; - // } - + /*** + * Delete meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param idOrCondition - If string, will delete the record with the given id. If object, will delete the record with the given condition. + * @param xcCondition - Additional nested or complex condition to be added to the query. + * @param force - If true, will not check if a condition is present in the query builder and will execute the query as is. + */ public async metaDelete( base_id: string, dbAlias: string, target: string, idOrCondition: string | { [p: string]: any }, - xcCondition?, + xcCondition?: Condition, + force = false, ): Promise { const query = this.knexConnection(target); @@ -391,16 +332,30 @@ export class MetaService { query.condition(xcCondition, {}); } + // Check if a condition is present in the query builder and throw an error if not. + if (!force) { + this.checkConditionPresent(query, 'delete'); + } + return query.del(); } + /*** + * Get meta data + * @param base_id - Base id + * @param sourceId - Source id + * @param target - Table name + * @param idOrCondition - If string, will get the record with the given id. If object, will get the record with the given condition. + * @param fields - Fields to be selected + * @param xcCondition - Additional nested or complex condition to be added to the query. + */ public async metaGet2( base_id: string, sourceId: string, target: string, idOrCondition: string | { [p: string]: any }, fields?: string[], - xcCondition?, + xcCondition?: Condition, ): Promise { const query = this.knexConnection(target); @@ -431,6 +386,12 @@ export class MetaService { return query.first(); } + /*** + * Get order value for the next record + * @param target - Table name + * @param condition - Condition to be applied + * @returns {Promise} - Order value + * */ public async metaGetNextOrder( target: string, condition: { [key: string]: any }, @@ -466,7 +427,7 @@ export class MetaService { condition?: { [p: string]: any }; limit?: number; offset?: number; - xcCondition?; + xcCondition?: Condition; fields?: string[]; orderBy?: { [key: string]: 'asc' | 'desc' }; }, @@ -505,6 +466,19 @@ export class MetaService { return query; } + /*** + * Get list of meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param args.condition - Condition to be applied + * @param args.limit - Limit of records + * @param args.offset - Offset of records + * @param args.xcCondition - Additional nested or complex condition to be added to the query. + * @param args.fields - Fields to be selected + * @param args.orderBy - Order by fields + * @returns {Promise} - List of records + * */ public async metaList2( base_id: string, dbAlias: string, @@ -513,7 +487,7 @@ export class MetaService { condition?: { [p: string]: any }; limit?: number; offset?: number; - xcCondition?; + xcCondition?: Condition; fields?: string[]; orderBy?: { [key: string]: 'asc' | 'desc' }; }, @@ -552,13 +526,23 @@ export class MetaService { return query; } + /*** + * Get count of meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param args.condition - Condition to be applied + * @param args.xcCondition - Additional nested or complex condition to be added to the query. + * @param args.aggField - Field to be aggregated + * @returns {Promise} - Count of records + * */ public async metaCount( base_id: string, dbAlias: string, target: string, args?: { condition?: { [p: string]: any }; - xcCondition?; + xcCondition?: Condition; aggField?: string; }, ): Promise { @@ -584,13 +568,24 @@ export class MetaService { return +(await query)?.['count'] || 0; } + /*** + * Update meta data + * @param base_id - Base id + * @param dbAlias - Database alias + * @param target - Table name + * @param data - Data to be updated + * @param idOrCondition - If string, will update the record with the given id. If object, will update the record with the given condition. + * @param xcCondition - Additional nested or complex condition to be added to the query. + * @param force - If true, will not check if a condition is present in the query builder and will execute the query as is. + */ public async metaUpdate( base_id: string, dbAlias: string, target: string, data: any, idOrCondition?: string | { [p: string]: any }, - xcCondition?, + xcCondition?: Condition, + force = false, ): Promise { const query = this.knexConnection(target); if (base_id !== null && base_id !== undefined) { @@ -612,34 +607,12 @@ export class MetaService { query.condition(xcCondition); } - return await query; - } - - public async metaDeleteAll( - _project_id: string, - _dbAlias: string, - ): Promise { - // await this.knexConnection..dropTableIfExists('nc_roles').; - // await this.knexConnection.schema.dropTableIfExists('nc_store').; - // await this.knexConnection.schema.dropTableIfExists('nc_hooks').; - // await this.knexConnection.schema.dropTableIfExists('nc_cron').; - // await this.knexConnection.schema.dropTableIfExists('nc_acl').; - } - - public async isMetaDataExists( - base_id: string, - dbAlias: string, - ): Promise { - const query = this.knexConnection('nc_models'); - if (base_id !== null && base_id !== undefined) { - query.where('base_id', base_id); - } - if (dbAlias !== null && dbAlias !== undefined) { - query.where('db_alias', dbAlias); + // Check if a condition is present in the query builder and throw an error if not. + if (!force) { + this.checkConditionPresent(query, 'update'); } - const data = await query.first(); - return !!data; + return await query; } async commit() { @@ -669,78 +642,19 @@ export class MetaService { return new MetaService(this.config, trx); } - async metaReset( - base_id: string, - dbAlias: string, - apiType?: string, - ): Promise { - // const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => { - // return d.meta.dbAlias === dbAlias; - // })?.meta?.api?.type; - - if (apiType) { - await Promise.all( - META_TABLES?.[apiType]?.map((table) => { - return (async () => { - try { - await this.knexConnection(table) - .where({ db_alias: dbAlias, base_id }) - .del(); - } catch (e) { - console.warn(`Error: ${table} reset failed`); - } - })(); - }), - ); - } - } - - public async baseCreate( - baseName: string, - config: any, - description?: string, - meta?: boolean, - ): Promise { - try { - const ranId = this.getNanoId(); - const id = `${baseName.toLowerCase().replace(/\W+/g, '_')}_${ranId}`; - if (meta) { - config.prefix = `nc_${ranId}__`; - // if(config.envs._noco?.db?.[0]?.meta?.tn){ - // config.envs._noco.db[0].meta.tn += `_${prefix}` - // } - } - config.id = id; - const base: any = { - id, - title: baseName, - description, - config: CryptoJS.AES.encrypt( - JSON.stringify(config), - 'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret - ).toString(), - }; - // todo: check base name used or not - await this.knexConnection('nc_projects').insert({ - ...base, - created_at: this.now(), - updated_at: this.now(), - }); - - // todo - await this.knexConnection(MetaTable.PROJECT).insert({ - id, - title: baseName, + /*** + * Update base config + * @param baseId - Base id + * @param config - Base config + * */ + public async baseUpdate(baseId: string, config: any): Promise { + if (!baseId) { + NcError.metaError({ + message: 'Base Id is required to update base config', + sql: '', }); - - base.prefix = config.prefix; - return base; - } catch (e) { - console.log(e); } - } - public async baseUpdate(baseId: string, config: any): Promise { try { const base = { config: CryptoJS.AES.encrypt( @@ -757,6 +671,10 @@ export class MetaService { } } + /*** + * Get base list with decrypted config + * @returns {Promise} - List of bases + * */ public async baseList(): Promise { return (await this.knexConnection('nc_projects').select()).map((p) => { p.config = CryptoJS.AES.decrypt( @@ -767,171 +685,6 @@ export class MetaService { }); } - public async userProjectList(userId: any): Promise { - return ( - await this.knexConnection('nc_projects') - .leftJoin( - this.knexConnection('nc_projects_users') - .where(`nc_projects_users.user_id`, userId) - .as('user'), - 'user.base_id', - 'nc_projects.id', - ) - .select('nc_projects.*') - .select('user.user_id') - .select( - this.knexConnection('xc_users') - .select('xc_users.email') - .innerJoin( - 'nc_projects_users', - 'nc_projects_users.user_id', - '=', - 'xc_users.id', - ) - .whereRaw('nc_projects.id = nc_projects_users.base_id') - .where('nc_projects_users.roles', 'like', '%owner%') - .first() - .as('owner'), - ) - .select( - this.knexConnection('xc_users') - .count('xc_users.id') - .innerJoin( - 'nc_projects_users', - 'nc_projects_users.user_id', - '=', - 'xc_users.id', - ) - .where((qb) => { - qb.where('nc_projects_users.roles', 'like', '%creator%').orWhere( - 'nc_projects_users.roles', - 'like', - '%owner%', - ); - }) - .whereRaw('nc_projects.id = nc_projects_users.base_id') - .andWhere('xc_users.id', userId) - .first() - .as('is_creator'), - ) - ).map((p) => { - p.allowed = p.user_id === userId; - p.config = CryptoJS.AES.decrypt( - p.config, - 'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret - ).toString(CryptoJS.enc.Utf8); - return p; - }); - } - - public async isUserHaveAccessToProject( - baseId: string, - userId: any, - ): Promise { - return !!(await this.knexConnection('nc_projects_users') - .where({ - base_id: baseId, - user_id: userId, - }) - .first()); - } - - public async baseGet(baseName: string, encrypt?): Promise { - const base = await this.knexConnection('nc_projects') - .where({ - title: baseName, - }) - .first(); - - if (base && !encrypt) { - base.config = CryptoJS.AES.decrypt( - base.config, - 'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret - ).toString(CryptoJS.enc.Utf8); - } - return base; - } - - public async baseGetById(baseId: string, encrypt?): Promise { - const base = await this.knexConnection('nc_projects') - .where({ - id: baseId, - }) - .first(); - if (base && !encrypt) { - base.config = CryptoJS.AES.decrypt( - base.config, - 'secret', // todo: tobe replaced - this.config?.auth?.jwt?.secret - ).toString(CryptoJS.enc.Utf8); - } - return base; - } - - public baseDelete(title: string): Promise { - return this.knexConnection('nc_projects') - .where({ - title, - }) - .delete(); - } - - public baseDeleteById(id: string): Promise { - return this.knexConnection('nc_projects') - .where({ - id, - }) - .delete(); - } - - public async baseStatusUpdate(baseId: string, status: string): Promise { - return this.knexConnection('nc_projects') - .update({ - status, - }) - .where({ - id: baseId, - }); - } - - public async baseAddUser( - baseId: string, - userId: any, - roles: string, - ): Promise { - if ( - await this.knexConnection('nc_projects_users') - .where({ - user_id: userId, - base_id: baseId, - }) - .first() - ) { - return {}; - } - return this.knexConnection('nc_projects_users').insert({ - user_id: userId, - base_id: baseId, - roles, - }); - } - - public baseRemoveUser(baseId: string, userId: any): Promise { - return this.knexConnection('nc_projects_users') - .where({ - user_id: userId, - base_id: baseId, - }) - .delete(); - } - - public removeXcUser(userId: any): Promise { - return this.knexConnection('xc_users') - .where({ - id: userId, - }) - .delete(); - } - private getNanoId() { return nanoid(); } @@ -949,18 +702,6 @@ export class MetaService { .format(this.isMySQL() ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'); } - public async audit( - base_id: string, - dbAlias: string, - target: string, - data: any, - ): Promise { - if (['DATA', 'COMMENT'].includes(data?.op_type)) { - return Promise.resolve(undefined); - } - return this.metaInsert(base_id, dbAlias, target, data); - } - public async init(): Promise { await this.connection.migrate.latest({ migrationSource: new XcMigrationSource(), @@ -972,4 +713,30 @@ export class MetaService { }); return true; } + + /** + * Checks if a condition is present in the query builder and throws an error if not. + * + * @param queryBuilder - The Knex QueryBuilder instance to check. + */ + private checkConditionPresent( + queryBuilder: Knex.QueryBuilder, + operation: 'delete' | 'update', + ) { + // Convert the query builder to a SQL string to inspect the presence of a WHERE clause. + const sql = queryBuilder.toString(); + + // Ensure that a WHERE condition is present in the query builder. + // Note: The `hasWhere` method alone is not sufficient since it can indicate an empty nested WHERE group. + // Therefore, also check the SQL string for the presence of the 'WHERE' keyword. + if (queryBuilder.hasWhere() && /\bWHERE\b/i.test(sql)) { + return; + } + + // Throw an error if no condition is found in the query builder. + NcError.metaError({ + message: 'A condition is required to ' + operation + ' records.', + sql, + }); + } }