diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts index 22ade7522d..d26157bece 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts @@ -1946,7 +1946,7 @@ class BaseModelSqlv2 { public async afterUpdate(data: any, _trx: any, req): Promise { const id = this._extractPksValues(data); - Audit.insert({ + await Audit.insert({ fk_model_id: this.model.id, row_id: id, op_type: AuditOperationTypes.DATA, diff --git a/packages/nocodb/src/lib/meta/api/columnApis.ts b/packages/nocodb/src/lib/meta/api/columnApis.ts index 5ffd06f414..705a810d14 100644 --- a/packages/nocodb/src/lib/meta/api/columnApis.ts +++ b/packages/nocodb/src/lib/meta/api/columnApis.ts @@ -510,10 +510,14 @@ export async function columnAdd(req: Request, res: Response) { colBody.dtxs = '4'; } - if ([UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)) { + if ( + [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt) + ) { const dbDriver = NcConnectionMgrv2.get(base); const driverType = dbDriver.clientType(); - const optionTitles = colBody.colOptions.options.map(el => el.title.replace(/'/g, "''")); + const optionTitles = colBody.colOptions.options.map((el) => + el.title.replace(/'/g, "''") + ); // this is not used for select columns and cause issue for MySQL colBody.dtxs = ''; @@ -521,12 +525,16 @@ export async function columnAdd(req: Request, res: Response) { if (colBody.cdf) { if (colBody.uidt === UITypes.SingleSelect) { if (!optionTitles.includes(colBody.cdf)) { - NcError.badRequest(`Default value '${colBody.cdf}' is not a select option.`); + NcError.badRequest( + `Default value '${colBody.cdf}' is not a select option.` + ); } } else { for (const cdf of colBody.cdf.split(',')) { if (!optionTitles.includes(cdf)) { - NcError.badRequest(`Default value '${cdf}' is not a select option.`); + NcError.badRequest( + `Default value '${cdf}' is not a select option.` + ); } } } @@ -536,9 +544,9 @@ export async function columnAdd(req: Request, res: Response) { } // Restrict duplicates - const titles = colBody.colOptions.options.map(el => el.title) - if (titles - .some( function(item) { + const titles = colBody.colOptions.options.map((el) => el.title); + if ( + titles.some(function (item) { return titles.indexOf(item) !== titles.lastIndexOf(item); }) ) { @@ -546,8 +554,8 @@ export async function columnAdd(req: Request, res: Response) { } // Restrict empty options - if (titles - .some( function(item) { + if ( + titles.some(function (item) { return item === ''; }) ) { @@ -557,33 +565,39 @@ export async function columnAdd(req: Request, res: Response) { // Trim end of enum/set if (colBody.dt === 'enum' || colBody.dt === 'set') { for (const opt of colBody.colOptions.options) { - opt.title = opt.title.trimEnd() + opt.title = opt.title.trimEnd(); } } - + if (colBody.uidt === UITypes.SingleSelect) { - colBody.dtxp = (colBody.colOptions?.options.length) - ? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` + colBody.dtxp = colBody.colOptions?.options.length + ? `${colBody.colOptions.options + .map((o) => `'${o.title.replace(/'/gi, "''")}'`) + .join(',')}` : ''; - } else if (colBody.uidt === UITypes.MultiSelect){ - colBody.dtxp = (colBody.colOptions?.options.length) - ? `${colBody.colOptions.options.map((o) => { - if(o.title.includes(',')) { - NcError.badRequest('Illegal char(\',\') for MultiSelect'); - } - return `'${o.title.replace(/'/gi, '\'\'')}'`; - }).join(',')}` + } else if (colBody.uidt === UITypes.MultiSelect) { + colBody.dtxp = colBody.colOptions?.options.length + ? `${colBody.colOptions.options + .map((o) => { + if (o.title.includes(',')) { + NcError.badRequest("Illegal char(',') for MultiSelect"); + } + return `'${o.title.replace(/'/gi, "''")}'`; + }) + .join(',')}` : ''; } // Handle empty enum/set for mysql (we restrict empty user options beforehand) if (driverType === 'mysql' || driverType === 'mysql2') { - if (!colBody.colOptions.options.length && (!colBody.dtxp || colBody.dtxp === '')) { - colBody.dtxp = '\'\''; + if ( + !colBody.colOptions.options.length && + (!colBody.dtxp || colBody.dtxp === '') + ) { + colBody.dtxp = "''"; } } } - const tableUpdateBody = { ...table, @@ -632,7 +646,7 @@ export async function columnAdd(req: Request, res: Response) { await table.getColumns(); - Audit.insert({ + await Audit.insert({ project_id: base.project_id, op_type: AuditOperationTypes.TABLE_COLUMN, op_sub_type: AuditOperationSubTypes.CREATED, @@ -722,17 +736,14 @@ export async function columnUpdate(req: Request, res: Response) { NcError.notImplemented( `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented` ); - } else if( - [ - UITypes.SingleSelect, - UITypes.MultiSelect - ].includes(colBody.uidt) + } else if ( + [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt) ) { colBody = getColumnPropsFromUIDT(colBody, base); - + const baseModel = await Model.getBaseModelSQL({ id: table.id, - dbDriver: NcConnectionMgrv2.get(base) + dbDriver: NcConnectionMgrv2.get(base), }); if (colBody.colOptions?.options) { @@ -741,36 +752,77 @@ export async function columnUpdate(req: Request, res: Response) { const driverType = dbDriver.clientType(); // MultiSelect to SingleSelect - if (column.uidt === UITypes.MultiSelect && colBody.uidt === UITypes.SingleSelect) { + if ( + column.uidt === UITypes.MultiSelect && + colBody.uidt === UITypes.SingleSelect + ) { if (driverType === 'mysql' || driverType === 'mysql2') { - await dbDriver.raw(`UPDATE ?? SET ?? = SUBSTRING_INDEX(??, ',', 1) WHERE ?? LIKE '%,%';`, [table.table_name, column.column_name, column.column_name, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = SUBSTRING_INDEX(??, ',', 1) WHERE ?? LIKE '%,%';`, + [ + table.table_name, + column.column_name, + column.column_name, + column.column_name, + ] + ); } else if (driverType === 'pg') { - await dbDriver.raw(`UPDATE ?? SET ?? = split_part(??, ',', 1);`, [table.table_name, column.column_name, column.column_name]); + await dbDriver.raw(`UPDATE ?? SET ?? = split_part(??, ',', 1);`, [ + table.table_name, + column.column_name, + column.column_name, + ]); } else if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = LEFT(cast(?? as varchar(max)), CHARINDEX(',', ??) - 1) WHERE CHARINDEX(',', ??) > 0;`, [table.table_name, column.column_name, column.column_name, column.column_name, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = LEFT(cast(?? as varchar(max)), CHARINDEX(',', ??) - 1) WHERE CHARINDEX(',', ??) > 0;`, + [ + table.table_name, + column.column_name, + column.column_name, + column.column_name, + column.column_name, + ] + ); } else if (driverType === 'sqlite3') { - await dbDriver.raw(`UPDATE ?? SET ?? = substr(??, 1, instr(??, ',') - 1) WHERE ?? LIKE '%,%';`, [table.table_name, column.column_name, column.column_name, column.column_name, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = substr(??, 1, instr(??, ',') - 1) WHERE ?? LIKE '%,%';`, + [ + table.table_name, + column.column_name, + column.column_name, + column.column_name, + column.column_name, + ] + ); } } // Handle migrations if (column.colOptions?.options) { - for (const op of column.colOptions.options.filter(el => el.order === null)) { - op.title = op.title.replace(/^'/, '').replace(/'$/, '') + for (const op of column.colOptions.options.filter( + (el) => el.order === null + )) { + op.title = op.title.replace(/^'/, '').replace(/'$/, ''); } } // Handle default values - const optionTitles = colBody.colOptions.options.map(el => el.title.replace(/'/g, "''")); + const optionTitles = colBody.colOptions.options.map((el) => + el.title.replace(/'/g, "''") + ); if (colBody.cdf) { if (colBody.uidt === UITypes.SingleSelect) { if (!optionTitles.includes(colBody.cdf)) { - NcError.badRequest(`Default value '${colBody.cdf}' is not a select option.`); + NcError.badRequest( + `Default value '${colBody.cdf}' is not a select option.` + ); } } else { for (const cdf of colBody.cdf.split(',')) { if (!optionTitles.includes(cdf)) { - NcError.badRequest(`Default value '${cdf}' is not a select option.`); + NcError.badRequest( + `Default value '${cdf}' is not a select option.` + ); } } } @@ -778,12 +830,11 @@ export async function columnUpdate(req: Request, res: Response) { colBody.cdf = `'${colBody.cdf}'`; } } - // Restrict duplicates - const titles = colBody.colOptions.options.map(el => el.title) - if (titles - .some( function(item) { + const titles = colBody.colOptions.options.map((el) => el.title); + if ( + titles.some(function (item) { return titles.indexOf(item) !== titles.lastIndexOf(item); }) ) { @@ -791,8 +842,8 @@ export async function columnUpdate(req: Request, res: Response) { } // Restrict empty options - if (titles - .some( function(item) { + if ( + titles.some(function (item) { return item === ''; }) ) { @@ -802,99 +853,178 @@ export async function columnUpdate(req: Request, res: Response) { // Trim end of enum/set if (colBody.dt === 'enum' || colBody.dt === 'set') { for (const opt of colBody.colOptions.options) { - opt.title = opt.title.trimEnd() + opt.title = opt.title.trimEnd(); } } if (colBody.uidt === UITypes.SingleSelect) { - colBody.dtxp = (colBody.colOptions?.options.length) - ? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` + colBody.dtxp = colBody.colOptions?.options.length + ? `${colBody.colOptions.options + .map((o) => `'${o.title.replace(/'/gi, "''")}'`) + .join(',')}` : ''; - } else if (colBody.uidt === UITypes.MultiSelect){ - colBody.dtxp = (colBody.colOptions?.options.length) - ? `${colBody.colOptions.options.map((o) => { - if(o.title.includes(',')) { - NcError.badRequest('Illegal char(\',\') for MultiSelect'); - } - return `'${o.title.replace(/'/gi, '\'\'')}'`; - }).join(',')}` + } else if (colBody.uidt === UITypes.MultiSelect) { + colBody.dtxp = colBody.colOptions?.options.length + ? `${colBody.colOptions.options + .map((o) => { + if (o.title.includes(',')) { + NcError.badRequest("Illegal char(',') for MultiSelect"); + } + return `'${o.title.replace(/'/gi, "''")}'`; + }) + .join(',')}` : ''; } // Handle empty enum/set for mysql (we restrict empty user options beforehand) if (driverType === 'mysql' || driverType === 'mysql2') { - if (!colBody.colOptions.options.length && (!colBody.dtxp || colBody.dtxp === '')) { - colBody.dtxp = '\'\''; + if ( + !colBody.colOptions.options.length && + (!colBody.dtxp || colBody.dtxp === '') + ) { + colBody.dtxp = "''"; } } // Handle option delete if (column.colOptions?.options) { - for (const option of column.colOptions.options.filter(oldOp => colBody.colOptions.options.find(newOp => newOp.id === oldOp.id) ? false : true)) { - if (!supportedDrivers.includes(driverType) && column.uidt === UITypes.MultiSelect) { - NcError.badRequest('Your database not yet supported for this operation. Please remove option from records manually before dropping.'); + for (const option of column.colOptions.options.filter((oldOp) => + colBody.colOptions.options.find((newOp) => newOp.id === oldOp.id) + ? false + : true + )) { + if ( + !supportedDrivers.includes(driverType) && + column.uidt === UITypes.MultiSelect + ) { + NcError.badRequest( + 'Your database not yet supported for this operation. Please remove option from records manually before dropping.' + ); } - if (column.uidt === UITypes.SingleSelect) { + if (column.uidt === UITypes.SingleSelect) { if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = NULL WHERE ?? LIKE ?`, [table.table_name, column.column_name, column.column_name, option.title]); + await dbDriver.raw(`UPDATE ?? SET ?? = NULL WHERE ?? LIKE ?`, [ + table.table_name, + column.column_name, + column.column_name, + option.title, + ]); } else { - await baseModel.bulkUpdateAll({ where: `(${column.column_name},eq,${option.title})` }, { [column.column_name]: null }, { cookie: req}); + await baseModel.bulkUpdateAll( + { where: `(${column.column_name},eq,${option.title})` }, + { [column.column_name]: null }, + { cookie: req } + ); } } else if (column.uidt === UITypes.MultiSelect) { if (driverType === 'mysql' || driverType === 'mysql2') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ',')) WHERE FIND_IN_SET(?, ??)`, [table.table_name, column.column_name, column.column_name, option.title, option.title, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), ',')) WHERE FIND_IN_SET(?, ??)`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + option.title, + column.column_name, + ] + ); } else if (driverType === 'pg') { - await dbDriver.raw(`UPDATE ?? SET ?? = array_to_string(array_remove(string_to_array(??, ','), ?), ',')`, [table.table_name, column.column_name, column.column_name, option.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = array_to_string(array_remove(string_to_array(??, ','), ?), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + ] + ); } else if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), ','), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), ',')) - 2)`, [table.table_name, column.column_name, column.column_name, option.title, column.column_name, option.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), ','), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), ',')) - 2)`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + column.column_name, + option.title, + ] + ); } else if (driverType === 'sqlite3') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ','), ',')`, [table.table_name, column.column_name, column.column_name, option.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ','), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + ] + ); } } } } - let interchange = []; + const interchange = []; // Handle option update if (column.colOptions?.options) { - const old_titles = column.colOptions.options.map(el => el.title); - for (const option of column.colOptions.options.filter(oldOp => colBody.colOptions.options.find(newOp => newOp.id === oldOp.id && newOp.title !== oldOp.title))) { - if (!supportedDrivers.includes(driverType) && column.uidt === UITypes.MultiSelect) { - NcError.badRequest('Your database not yet supported for this operation. Please remove option from records manually before updating.'); + const old_titles = column.colOptions.options.map((el) => el.title); + for (const option of column.colOptions.options.filter((oldOp) => + colBody.colOptions.options.find( + (newOp) => newOp.id === oldOp.id && newOp.title !== oldOp.title + ) + )) { + if ( + !supportedDrivers.includes(driverType) && + column.uidt === UITypes.MultiSelect + ) { + NcError.badRequest( + 'Your database not yet supported for this operation. Please remove option from records manually before updating.' + ); } - let newOp = { ...colBody.colOptions.options.find(el => option.id === el.id) }; + const newOp = { + ...colBody.colOptions.options.find((el) => option.id === el.id), + }; if (old_titles.includes(newOp.title)) { - let def_option = { ...newOp }; + const def_option = { ...newOp }; let title_counter = 1; while (old_titles.includes(newOp.title)) { newOp.title = `${def_option.title}_${title_counter++}`; } - interchange.push( { + interchange.push({ def_option, - temp_title: newOp.title - } ); + temp_title: newOp.title, + }); } - + // Append new option before editing - if ((driverType === 'mysql' || driverType === 'mysql2') && (column.dt === 'enum' || column.dt === 'set')) { + if ( + (driverType === 'mysql' || driverType === 'mysql2') && + (column.dt === 'enum' || column.dt === 'set') + ) { column.colOptions.options.push({ title: newOp.title }); let temp_dtxp = ''; if (column.uidt === UITypes.SingleSelect) { - temp_dtxp = (column.colOptions.options.length) - ? `${column.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` + temp_dtxp = column.colOptions.options.length + ? `${column.colOptions.options + .map((o) => `'${o.title.replace(/'/gi, "''")}'`) + .join(',')}` : ''; - } else if (column.uidt === UITypes.MultiSelect){ - temp_dtxp = (column.colOptions.options.length) - ? `${column.colOptions.options.map((o) => { - if(o.title.includes(',')) { - NcError.badRequest('Illegal char(\',\') for MultiSelect'); - throw new Error(''); - } - return `'${o.title.replace(/'/gi, '\'\'')}'`; - }).join(',')}` + } else if (column.uidt === UITypes.MultiSelect) { + temp_dtxp = column.colOptions.options.length + ? `${column.colOptions.options + .map((o) => { + if (o.title.includes(',')) { + NcError.badRequest("Illegal char(',') for MultiSelect"); + throw new Error(''); + } + return `'${o.title.replace(/'/gi, "''")}'`; + }) + .join(',')}` : ''; } @@ -926,51 +1056,155 @@ export async function columnUpdate(req: Request, res: Response) { ), }; - const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id }); + const sqlMgr = await ProjectMgrv2.getSqlMgr({ + id: base.project_id, + }); await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody); - + await Column.update(req.params.columnId, { ...column, }); } - if (column.uidt === UITypes.SingleSelect) { + if (column.uidt === UITypes.SingleSelect) { if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [table.table_name, column.column_name, newOp.title, column.column_name, option.title]); + await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [ + table.table_name, + column.column_name, + newOp.title, + column.column_name, + option.title, + ]); } else { - await baseModel.bulkUpdateAll({ where: `(${column.column_name},eq,${option.title})` }, { [column.column_name]: newOp.title }, { cookie: req}); + await baseModel.bulkUpdateAll( + { where: `(${column.column_name},eq,${option.title})` }, + { [column.column_name]: newOp.title }, + { cookie: req } + ); } } else if (column.uidt === UITypes.MultiSelect) { if (driverType === 'mysql' || driverType === 'mysql2') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`, [table.table_name, column.column_name, column.column_name, option.title, newOp.title, option.title, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + newOp.title, + option.title, + column.column_name, + ] + ); } else if (driverType === 'pg') { - await dbDriver.raw(`UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`, [table.table_name, column.column_name, column.column_name, option.title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + newOp.title, + ] + ); } else if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`, [table.table_name, column.column_name, column.column_name, option.title, newOp.title, column.column_name, option.title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + newOp.title, + column.column_name, + option.title, + newOp.title, + ] + ); } else if (driverType === 'sqlite3') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`, [table.table_name, column.column_name, column.column_name, option.title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + option.title, + newOp.title, + ] + ); } } } } for (const ch of interchange) { - let newOp = ch.def_option; - if (column.uidt === UITypes.SingleSelect) { + const newOp = ch.def_option; + if (column.uidt === UITypes.SingleSelect) { if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [table.table_name, column.column_name, newOp.title, column.column_name, ch.temp_title]); + await dbDriver.raw(`UPDATE ?? SET ?? = ? WHERE ?? LIKE ?`, [ + table.table_name, + column.column_name, + newOp.title, + column.column_name, + ch.temp_title, + ]); } else { - await baseModel.bulkUpdateAll({ where: `(${column.column_name},eq,${ch.temp_title})` }, { [column.column_name]: newOp.title }, { cookie: req}); + await baseModel.bulkUpdateAll( + { where: `(${column.column_name},eq,${ch.temp_title})` }, + { [column.column_name]: newOp.title }, + { cookie: req } + ); } } else if (column.uidt === UITypes.MultiSelect) { if (driverType === 'mysql' || driverType === 'mysql2') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`, [table.table_name, column.column_name, column.column_name, ch.temp_title, newOp.title, ch.temp_title, column.column_name]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(BOTH ',' FROM REPLACE(CONCAT(',', ??, ','), CONCAT(',', ?, ','), CONCAT(',', ?, ','))) WHERE FIND_IN_SET(?, ??)`, + [ + table.table_name, + column.column_name, + column.column_name, + ch.temp_title, + newOp.title, + ch.temp_title, + column.column_name, + ] + ); } else if (driverType === 'pg') { - await dbDriver.raw(`UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`, [table.table_name, column.column_name, column.column_name, ch.temp_title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = array_to_string(array_replace(string_to_array(??, ','), ?, ?), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + ch.temp_title, + newOp.title, + ] + ); } else if (driverType === 'mssql') { - await dbDriver.raw(`UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`, [table.table_name, column.column_name, column.column_name, ch.temp_title, newOp.title, column.column_name, ch.temp_title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = substring(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ',')), 2, len(replace(concat(',', ??, ','), concat(',', ?, ','), concat(',', ?, ','))) - 2)`, + [ + table.table_name, + column.column_name, + column.column_name, + ch.temp_title, + newOp.title, + column.column_name, + ch.temp_title, + newOp.title, + ] + ); } else if (driverType === 'sqlite3') { - await dbDriver.raw(`UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`, [table.table_name, column.column_name, column.column_name, ch.temp_title, newOp.title]); + await dbDriver.raw( + `UPDATE ?? SET ?? = TRIM(REPLACE(',' || ?? || ',', ',' || ? || ',', ',' || ? || ','), ',')`, + [ + table.table_name, + column.column_name, + column.column_name, + ch.temp_title, + newOp.title, + ] + ); } } } @@ -1029,7 +1263,7 @@ export async function columnUpdate(req: Request, res: Response) { const sqlMgr = await ProjectMgrv2.getSqlMgr({ id: base.project_id }); await sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody); - + await Column.update(req.params.columnId, { ...colBody, }); @@ -1093,7 +1327,7 @@ export async function columnUpdate(req: Request, res: Response) { ...colBody, }); } - Audit.insert({ + await Audit.insert({ project_id: base.project_id, op_type: AuditOperationTypes.TABLE_COLUMN, op_sub_type: AuditOperationSubTypes.UPDATED, @@ -1292,7 +1526,7 @@ export async function columnDelete(req: Request, res: Response) { } } - Audit.insert({ + await Audit.insert({ project_id: base.project_id, op_type: AuditOperationTypes.TABLE_COLUMN, op_sub_type: AuditOperationSubTypes.DELETED, diff --git a/packages/nocodb/src/lib/meta/api/projectUserApis.ts b/packages/nocodb/src/lib/meta/api/projectUserApis.ts index 99e93e8607..de4cf2871a 100644 --- a/packages/nocodb/src/lib/meta/api/projectUserApis.ts +++ b/packages/nocodb/src/lib/meta/api/projectUserApis.ts @@ -87,7 +87,7 @@ async function userInvite(req, res, next): Promise { ); } - Audit.insert({ + await Audit.insert({ project_id: req.params.projectId, op_type: 'AUTHENTICATION', op_sub_type: 'INVITE', @@ -188,7 +188,7 @@ async function projectUserUpdate(req, res, next): Promise { req.body.roles ); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'ROLES_MANAGEMENT', user: req.user.email, @@ -255,7 +255,7 @@ async function projectUserInviteResend(req, res): Promise { await sendInviteEmail(user.email, invite_token, req); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'RESEND_INVITE', user: user.email, diff --git a/packages/nocodb/src/lib/meta/api/tableApis.ts b/packages/nocodb/src/lib/meta/api/tableApis.ts index 50fae6beb5..001b8683c7 100644 --- a/packages/nocodb/src/lib/meta/api/tableApis.ts +++ b/packages/nocodb/src/lib/meta/api/tableApis.ts @@ -178,7 +178,7 @@ export async function tableCreate(req: Request, res) { base_id: base.id, }); - Audit.insert({ + await Audit.insert({ project_id: project.id, op_type: AuditOperationTypes.TABLE, op_sub_type: AuditOperationSubTypes.CREATED, @@ -348,7 +348,7 @@ export async function tableDelete(req: Request, res: Response) { }); } - Audit.insert({ + await Audit.insert({ project_id: project.id, op_type: AuditOperationTypes.TABLE, op_sub_type: AuditOperationSubTypes.DELETED, diff --git a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts index 3238599359..b4ab59de0c 100644 --- a/packages/nocodb/src/lib/meta/api/userApi/userApis.ts +++ b/packages/nocodb/src/lib/meta/api/userApi/userApis.ts @@ -146,7 +146,7 @@ export async function signup(req: Request, res: Response) { user = (req as any).user; - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'SIGNUP', user: user.email, @@ -192,7 +192,7 @@ async function successfulSignIn({ }); setTokenCookie(res, refreshToken); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'SIGNIN', user: user.email, @@ -291,7 +291,7 @@ async function passwordChange(req: Request, res): Promise { token_version: null, }); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'PASSWORD_CHANGE', user: user.email, @@ -341,7 +341,7 @@ async function passwordForgot(req: Request, res): Promise { ); } - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'PASSWORD_FORGOT', user: user.email, @@ -405,7 +405,7 @@ async function passwordReset(req, res): Promise { token_version: null, }); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'PASSWORD_RESET', user: user.email, @@ -433,7 +433,7 @@ async function emailVerification(req, res): Promise { email_verified: true, }); - Audit.insert({ + await Audit.insert({ op_type: 'AUTHENTICATION', op_sub_type: 'EMAIL_VERIFICATION', user: user.email, diff --git a/packages/nocodb/src/lib/models/Audit.ts b/packages/nocodb/src/lib/models/Audit.ts index 2bb95f8eac..ae2c9f2f6a 100644 --- a/packages/nocodb/src/lib/models/Audit.ts +++ b/packages/nocodb/src/lib/models/Audit.ts @@ -31,6 +31,7 @@ export default class Audit implements AuditType { return audit && new Audit(audit); } + // Will only await for Audit insertion if `forceAwait` is true, which will be true in test environment by default public static async insert( audit: Partial< Audit & { @@ -38,30 +39,40 @@ export default class Audit implements AuditType { updated_at?; } >, - ncMeta = Noco.ncMeta - ) { - if (!audit.project_id && audit.fk_model_id) { - audit.project_id = ( - await Model.getByIdOrName({ id: audit.fk_model_id }, ncMeta) - ).project_id; + ncMeta = Noco.ncMeta, + { forceAwait }: { forceAwait: boolean } = { + forceAwait: process.env['TEST'] === 'true', } - const auditRec = await ncMeta.metaInsert2(null, null, MetaTable.AUDIT, { - user: audit.user, - ip: audit.ip, - base_id: audit.base_id, - project_id: audit.project_id, - row_id: audit.row_id, - fk_model_id: audit.fk_model_id, - op_type: audit.op_type, - op_sub_type: audit.op_sub_type, - status: audit.status, - description: audit.description, - details: audit.details, - created_at: audit.created_at, - updated_at: audit.updated_at, - }); + ) { + const insertAudit = async () => { + if (!audit.project_id && audit.fk_model_id) { + audit.project_id = ( + await Model.getByIdOrName({ id: audit.fk_model_id }, ncMeta) + ).project_id; + } + + return await ncMeta.metaInsert2(null, null, MetaTable.AUDIT, { + user: audit.user, + ip: audit.ip, + base_id: audit.base_id, + project_id: audit.project_id, + row_id: audit.row_id, + fk_model_id: audit.fk_model_id, + op_type: audit.op_type, + op_sub_type: audit.op_sub_type, + status: audit.status, + description: audit.description, + details: audit.details, + created_at: audit.created_at, + updated_at: audit.updated_at, + }); + }; - return auditRec; + if (forceAwait) { + return await insertAudit(); + } else { + insertAudit(); + } } public static async commentsCount(args: { diff --git a/packages/nocodb/tests/unit/index.test.ts b/packages/nocodb/tests/unit/index.test.ts index 2e7d0d2edc..0449a759f7 100644 --- a/packages/nocodb/tests/unit/index.test.ts +++ b/packages/nocodb/tests/unit/index.test.ts @@ -6,7 +6,7 @@ import TestDbMngr from './TestDbMngr' import dotenv from 'dotenv'; process.env.NODE_ENV = 'test'; -process.env.TEST = 'test'; +process.env.TEST = 'true'; process.env.NC_DISABLE_CACHE = 'true'; process.env.NC_DISABLE_TELE = 'true';