Browse Source

Merge pull request #8571 from nocodb/nc-refactor/meta-service

refactor: Meta service
pull/8592/head
Pranav C 6 months ago committed by GitHub
parent
commit
fd501ed758
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 51
      packages/nocodb/src/db/CustomKnex.ts
  2. 5
      packages/nocodb/src/filters/global-exception/global-exception.filter.ts
  3. 11
      packages/nocodb/src/helpers/catchError.ts
  4. 629
      packages/nocodb/src/meta/meta.service.ts

51
packages/nocodb/src/db/CustomKnex.ts

@ -492,23 +492,27 @@ const appendWhereCondition = function (
return knexRef; 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]: type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> &
| XcXonditionObj U[keyof U];
| XcXonditionObj[]
| XcConditionObjVal export type ConditionVal = AtLeastOne<{
| XcConditionObjVal[]; 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' { declare module 'knex' {
@ -527,7 +531,7 @@ declare module 'knex' {
): Knex.QueryBuilder<TRecord, TResult>; ): Knex.QueryBuilder<TRecord, TResult>;
condition<TRecord, TResult>( condition<TRecord, TResult>(
conditionObj: XcXonditionObj, conditionObj: Condition,
columnAliases?: { columnAliases?: {
[columnAlias: string]: string; [columnAlias: string]: string;
}, },
@ -542,7 +546,7 @@ declare module 'knex' {
): Knex.QueryBuilder<TRecord, TResult>; ): Knex.QueryBuilder<TRecord, TResult>;
conditionGraph<TRecord, TResult>(condition: { conditionGraph<TRecord, TResult>(condition: {
condition: XcXonditionObj; condition: Condition;
models: { [key: string]: BaseModelSql }; models: { [key: string]: BaseModelSql };
}): Knex.QueryBuilder<TRecord, TResult>; }): Knex.QueryBuilder<TRecord, TResult>;
@ -552,6 +556,8 @@ declare module 'knex' {
[columnAlias: string]: string; [columnAlias: string]: string;
}, },
): Knex.QueryBuilder<TRecord, TResult>; ): Knex.QueryBuilder<TRecord, TResult>;
hasWhere(): boolean;
} }
} }
} }
@ -1274,8 +1280,15 @@ function parseNestedConditionv2(obj, qb, pKey?, table?, tableAlias?) {
return qb; 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 * Append custom where condition(nested object) to knex query builder
*/ */

5
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 // try to extract db error for unknown errors
const dbError = !(exception instanceof NcBaseError) const dbError =
? extractDBError(exception) exception instanceof NcBaseError ? null : extractDBError(exception);
: null;
// skip unnecessary error logging // skip unnecessary error logging
if ( if (

11
packages/nocodb/src/helpers/catchError.ts

@ -415,6 +415,13 @@ export class NotFound extends NcBaseError {}
export class SsoError 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 { export class ExternalError extends NcBaseError {
constructor(error: Error) { constructor(error: Error) {
super(error.message); super(error.message);
@ -747,4 +754,8 @@ export class NcError {
`Email domain ${domain} is not allowed for this organization`, `Email domain ${domain} is not allowed for this organization`,
); );
} }
static metaError(param: { message: string; sql: string }) {
throw new MetaError(param);
}
} }

629
packages/nocodb/src/meta/meta.service.ts

@ -6,20 +6,17 @@ import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import type * as knex from 'knex'; import type * as knex from 'knex';
import type { Knex } from 'knex'; import type { Knex } from 'knex';
import type { Condition } from '~/db/CustomKnex';
import XcMigrationSource from '~/meta/migrations/XcMigrationSource'; import XcMigrationSource from '~/meta/migrations/XcMigrationSource';
import XcMigrationSourcev2 from '~/meta/migrations/XcMigrationSourcev2'; import XcMigrationSourcev2 from '~/meta/migrations/XcMigrationSourcev2';
import { XKnex } from '~/db/CustomKnex'; import { XKnex } from '~/db/CustomKnex';
import { NcConfig } from '~/utils/nc-config'; import { NcConfig } from '~/utils/nc-config';
import { MetaTable } from '~/utils/globals'; import { MetaTable } from '~/utils/globals';
import { NcError } from '~/helpers/catchError';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
// todo: tobe fixed
const META_TABLES = [];
const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14); const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14);
@Injectable() @Injectable()
@ -56,6 +53,14 @@ export class MetaService {
return this.knexConnection; 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( public async metaGet(
base_id: string, base_id: string,
dbAlias: string, dbAlias: string,
@ -96,6 +101,14 @@ export class MetaService {
return query.first(); 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( public async metaInsert2(
base_id: string, base_id: string,
source_id: string, source_id: string,
@ -119,6 +132,15 @@ export class MetaService {
}); });
return insertObj; 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( public async bulkMetaInsert(
base_id: string, base_id: string,
source_id: string, source_id: string,
@ -156,120 +178,67 @@ export class MetaService {
return insertObj; return insertObj;
} }
/***
* Generate nanoid for the given target
* @param target - Table name
* @returns {string} - Generated nanoid
* */
public async genNanoid(target: string) { public async genNanoid(target: string) {
let prefix; const prefixMap: { [key: string]: string } = {
switch (target) { [MetaTable.PROJECT]: 'p',
case MetaTable.PROJECT: [MetaTable.BASES]: 'b',
prefix = 'p'; [MetaTable.MODELS]: 'm',
break; [MetaTable.COLUMNS]: 'c',
case MetaTable.BASES: [MetaTable.COL_RELATIONS]: 'l',
prefix = 'b'; [MetaTable.COL_SELECT_OPTIONS]: 's',
break; [MetaTable.COL_LOOKUP]: 'lk',
case MetaTable.MODELS: [MetaTable.COL_ROLLUP]: 'rl',
prefix = 'm'; [MetaTable.COL_FORMULA]: 'f',
break; [MetaTable.FILTER_EXP]: 'fi',
case MetaTable.COLUMNS: [MetaTable.SORT]: 'so',
prefix = 'c'; [MetaTable.SHARED_VIEWS]: 'sv',
break; [MetaTable.ACL]: 'ac',
case MetaTable.COL_RELATIONS: [MetaTable.FORM_VIEW]: 'fv',
prefix = 'l'; [MetaTable.FORM_VIEW_COLUMNS]: 'fvc',
break; [MetaTable.GALLERY_VIEW]: 'gv',
case MetaTable.COL_SELECT_OPTIONS: [MetaTable.GALLERY_VIEW_COLUMNS]: 'gvc',
prefix = 's'; [MetaTable.KANBAN_VIEW]: 'kv',
break; [MetaTable.KANBAN_VIEW_COLUMNS]: 'kvc',
case MetaTable.COL_LOOKUP: [MetaTable.CALENDAR_VIEW]: 'cv',
prefix = 'lk'; [MetaTable.CALENDAR_VIEW_COLUMNS]: 'cvc',
break; [MetaTable.CALENDAR_VIEW_RANGE]: 'cvr',
case MetaTable.COL_ROLLUP: [MetaTable.USERS]: 'us',
prefix = 'rl'; [MetaTable.ORGS_OLD]: 'org',
break; [MetaTable.TEAMS]: 'tm',
case MetaTable.COL_FORMULA: [MetaTable.VIEWS]: 'vw',
prefix = 'f'; [MetaTable.HOOKS]: 'hk',
break; [MetaTable.HOOK_LOGS]: 'hkl',
case MetaTable.FILTER_EXP: [MetaTable.AUDIT]: 'adt',
prefix = 'fi'; [MetaTable.API_TOKENS]: 'tkn',
break; [MetaTable.EXTENSIONS]: 'ext',
case MetaTable.SORT: [MetaTable.COMMENTS]: 'com',
prefix = 'so'; [MetaTable.COMMENTS_REACTIONS]: 'cre',
break; [MetaTable.USER_COMMENTS_NOTIFICATIONS_PREFERENCE]: 'cnp',
case MetaTable.SHARED_VIEWS: };
prefix = 'sv';
break; const prefix = prefixMap[target] || 'nc';
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;
}
// using nanoid to avoid collision with existing ids when duplicating // using nanoid to avoid collision with existing ids when duplicating
return `${prefix}${nanoidv2()}`; 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( public async metaPaginatedList(
baseId: string, baseId: string,
dbAlias: string, dbAlias: string,
@ -278,7 +247,7 @@ export class MetaService {
condition?: { [key: string]: any }; condition?: { [key: string]: any };
limit?: number; limit?: number;
offset?: number; offset?: number;
xcCondition?; xcCondition?: Condition;
fields?: string[]; fields?: string[];
sort?: { field: string; desc?: boolean }; sort?: { field: string; desc?: boolean };
}, },
@ -327,50 +296,22 @@ export class MetaService {
// todo: need to fix // todo: need to fix
private trx: Knex.Transaction; private trx: Knex.Transaction;
// constructor(app: Noco, config: NcConfig, trx = null) { /***
// super(app, config); * Delete meta data
// * @param base_id - Base id
// if (this.config?.meta?.db) { * @param dbAlias - Database alias
// this.connection = trx || XKnex(this.config?.meta?.db); * @param target - Table name
// } else { * @param idOrCondition - If string, will delete the record with the given id. If object, will delete the record with the given condition.
// let dbIndex = this.config.envs?.[this.config.workingEnv]?.db.findIndex( * @param xcCondition - Additional nested or complex condition to be added to the query.
// (c) => c.meta.dbAlias === this.config?.auth?.jwt?.dbAlias * @param force - If true, will not check if a condition is present in the query builder and will execute the query as is.
// ); */
// 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<boolean> {
// 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;
// }
public async metaDelete( public async metaDelete(
base_id: string, base_id: string,
dbAlias: string, dbAlias: string,
target: string, target: string,
idOrCondition: string | { [p: string]: any }, idOrCondition: string | { [p: string]: any },
xcCondition?, xcCondition?: Condition,
force = false,
): Promise<void> { ): Promise<void> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
@ -391,16 +332,30 @@ export class MetaService {
query.condition(xcCondition, {}); 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(); 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( public async metaGet2(
base_id: string, base_id: string,
sourceId: string, sourceId: string,
target: string, target: string,
idOrCondition: string | { [p: string]: any }, idOrCondition: string | { [p: string]: any },
fields?: string[], fields?: string[],
xcCondition?, xcCondition?: Condition,
): Promise<any> { ): Promise<any> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
@ -431,6 +386,12 @@ export class MetaService {
return query.first(); return query.first();
} }
/***
* Get order value for the next record
* @param target - Table name
* @param condition - Condition to be applied
* @returns {Promise<number>} - Order value
* */
public async metaGetNextOrder( public async metaGetNextOrder(
target: string, target: string,
condition: { [key: string]: any }, condition: { [key: string]: any },
@ -466,7 +427,7 @@ export class MetaService {
condition?: { [p: string]: any }; condition?: { [p: string]: any };
limit?: number; limit?: number;
offset?: number; offset?: number;
xcCondition?; xcCondition?: Condition;
fields?: string[]; fields?: string[];
orderBy?: { [key: string]: 'asc' | 'desc' }; orderBy?: { [key: string]: 'asc' | 'desc' };
}, },
@ -505,6 +466,19 @@ export class MetaService {
return query; 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<any[]>} - List of records
* */
public async metaList2( public async metaList2(
base_id: string, base_id: string,
dbAlias: string, dbAlias: string,
@ -513,7 +487,7 @@ export class MetaService {
condition?: { [p: string]: any }; condition?: { [p: string]: any };
limit?: number; limit?: number;
offset?: number; offset?: number;
xcCondition?; xcCondition?: Condition;
fields?: string[]; fields?: string[];
orderBy?: { [key: string]: 'asc' | 'desc' }; orderBy?: { [key: string]: 'asc' | 'desc' };
}, },
@ -552,13 +526,23 @@ export class MetaService {
return query; 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<number>} - Count of records
* */
public async metaCount( public async metaCount(
base_id: string, base_id: string,
dbAlias: string, dbAlias: string,
target: string, target: string,
args?: { args?: {
condition?: { [p: string]: any }; condition?: { [p: string]: any };
xcCondition?; xcCondition?: Condition;
aggField?: string; aggField?: string;
}, },
): Promise<number> { ): Promise<number> {
@ -584,13 +568,24 @@ export class MetaService {
return +(await query)?.['count'] || 0; 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( public async metaUpdate(
base_id: string, base_id: string,
dbAlias: string, dbAlias: string,
target: string, target: string,
data: any, data: any,
idOrCondition?: string | { [p: string]: any }, idOrCondition?: string | { [p: string]: any },
xcCondition?, xcCondition?: Condition,
force = false,
): Promise<any> { ): Promise<any> {
const query = this.knexConnection(target); const query = this.knexConnection(target);
if (base_id !== null && base_id !== undefined) { if (base_id !== null && base_id !== undefined) {
@ -612,34 +607,12 @@ export class MetaService {
query.condition(xcCondition); query.condition(xcCondition);
} }
return await query; // Check if a condition is present in the query builder and throw an error if not.
} if (!force) {
this.checkConditionPresent(query, 'update');
public async metaDeleteAll(
_project_id: string,
_dbAlias: string,
): Promise<void> {
// 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<boolean> {
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);
} }
const data = await query.first();
return !!data; return await query;
} }
async commit() { async commit() {
@ -669,78 +642,19 @@ export class MetaService {
return new MetaService(this.config, trx); return new MetaService(this.config, trx);
} }
async metaReset( /***
base_id: string, * Update base config
dbAlias: string, * @param baseId - Base id
apiType?: string, * @param config - Base config
): Promise<void> { * */
// const apiType: string = this.config?.envs?.[this.config.env || this.config.workingEnv]?.db.find(d => { public async baseUpdate(baseId: string, config: any): Promise<any> {
// return d.meta.dbAlias === dbAlias; if (!baseId) {
// })?.meta?.api?.type; NcError.metaError({
message: 'Base Id is required to update base config',
if (apiType) { sql: '',
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<any> {
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,
}); });
base.prefix = config.prefix;
return base;
} catch (e) {
console.log(e);
} }
}
public async baseUpdate(baseId: string, config: any): Promise<any> {
try { try {
const base = { const base = {
config: CryptoJS.AES.encrypt( config: CryptoJS.AES.encrypt(
@ -757,6 +671,10 @@ export class MetaService {
} }
} }
/***
* Get base list with decrypted config
* @returns {Promise<any[]>} - List of bases
* */
public async baseList(): Promise<any[]> { public async baseList(): Promise<any[]> {
return (await this.knexConnection('nc_projects').select()).map((p) => { return (await this.knexConnection('nc_projects').select()).map((p) => {
p.config = CryptoJS.AES.decrypt( p.config = CryptoJS.AES.decrypt(
@ -767,171 +685,6 @@ export class MetaService {
}); });
} }
public async userProjectList(userId: any): Promise<any[]> {
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<boolean> {
return !!(await this.knexConnection('nc_projects_users')
.where({
base_id: baseId,
user_id: userId,
})
.first());
}
public async baseGet(baseName: string, encrypt?): Promise<any> {
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<any> {
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<any> {
return this.knexConnection('nc_projects')
.where({
title,
})
.delete();
}
public baseDeleteById(id: string): Promise<any> {
return this.knexConnection('nc_projects')
.where({
id,
})
.delete();
}
public async baseStatusUpdate(baseId: string, status: string): Promise<any> {
return this.knexConnection('nc_projects')
.update({
status,
})
.where({
id: baseId,
});
}
public async baseAddUser(
baseId: string,
userId: any,
roles: string,
): Promise<any> {
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<any> {
return this.knexConnection('nc_projects_users')
.where({
user_id: userId,
base_id: baseId,
})
.delete();
}
public removeXcUser(userId: any): Promise<any> {
return this.knexConnection('xc_users')
.where({
id: userId,
})
.delete();
}
private getNanoId() { private getNanoId() {
return nanoid(); return nanoid();
} }
@ -949,18 +702,6 @@ export class MetaService {
.format(this.isMySQL() ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'); .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<any> {
if (['DATA', 'COMMENT'].includes(data?.op_type)) {
return Promise.resolve(undefined);
}
return this.metaInsert(base_id, dbAlias, target, data);
}
public async init(): Promise<boolean> { public async init(): Promise<boolean> {
await this.connection.migrate.latest({ await this.connection.migrate.latest({
migrationSource: new XcMigrationSource(), migrationSource: new XcMigrationSource(),
@ -972,4 +713,30 @@ export class MetaService {
}); });
return true; 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,
});
}
} }

Loading…
Cancel
Save