Browse Source

Merge pull request #3715 from nocodb/fix/add-await-in-saving-audit-in-updatepk-basesqlmodel

Fix(nocodb): Audit insert will always await in test env while in normal environments will by default not await
pull/3815/head
Muhammed Mustafa 2 years ago committed by GitHub
parent
commit
8f26c28bb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  2. 462
      packages/nocodb/src/lib/meta/api/columnApis.ts
  3. 6
      packages/nocodb/src/lib/meta/api/projectUserApis.ts
  4. 4
      packages/nocodb/src/lib/meta/api/tableApis.ts
  5. 12
      packages/nocodb/src/lib/meta/api/userApi/userApis.ts
  6. 55
      packages/nocodb/src/lib/models/Audit.ts
  7. 2
      packages/nocodb/tests/unit/index.test.ts

2
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<void> { public async afterUpdate(data: any, _trx: any, req): Promise<void> {
const id = this._extractPksValues(data); const id = this._extractPksValues(data);
Audit.insert({ await Audit.insert({
fk_model_id: this.model.id, fk_model_id: this.model.id,
row_id: id, row_id: id,
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,

462
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -510,10 +510,14 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
colBody.dtxs = '4'; 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 dbDriver = NcConnectionMgrv2.get(base);
const driverType = dbDriver.clientType(); 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 // this is not used for select columns and cause issue for MySQL
colBody.dtxs = ''; colBody.dtxs = '';
@ -521,12 +525,16 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
if (colBody.cdf) { if (colBody.cdf) {
if (colBody.uidt === UITypes.SingleSelect) { if (colBody.uidt === UITypes.SingleSelect) {
if (!optionTitles.includes(colBody.cdf)) { 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 { } else {
for (const cdf of colBody.cdf.split(',')) { for (const cdf of colBody.cdf.split(',')) {
if (!optionTitles.includes(cdf)) { 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<TableType>) {
} }
// Restrict duplicates // Restrict duplicates
const titles = colBody.colOptions.options.map(el => el.title) const titles = colBody.colOptions.options.map((el) => el.title);
if (titles if (
.some( function(item) { titles.some(function (item) {
return titles.indexOf(item) !== titles.lastIndexOf(item); return titles.indexOf(item) !== titles.lastIndexOf(item);
}) })
) { ) {
@ -546,8 +554,8 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
} }
// Restrict empty options // Restrict empty options
if (titles if (
.some( function(item) { titles.some(function (item) {
return item === ''; return item === '';
}) })
) { ) {
@ -557,33 +565,39 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
// Trim end of enum/set // Trim end of enum/set
if (colBody.dt === 'enum' || colBody.dt === 'set') { if (colBody.dt === 'enum' || colBody.dt === 'set') {
for (const opt of colBody.colOptions.options) { for (const opt of colBody.colOptions.options) {
opt.title = opt.title.trimEnd() opt.title = opt.title.trimEnd();
} }
} }
if (colBody.uidt === UITypes.SingleSelect) { if (colBody.uidt === UITypes.SingleSelect) {
colBody.dtxp = (colBody.colOptions?.options.length) colBody.dtxp = colBody.colOptions?.options.length
? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` ? `${colBody.colOptions.options
.map((o) => `'${o.title.replace(/'/gi, "''")}'`)
.join(',')}`
: ''; : '';
} else if (colBody.uidt === UITypes.MultiSelect){ } else if (colBody.uidt === UITypes.MultiSelect) {
colBody.dtxp = (colBody.colOptions?.options.length) colBody.dtxp = colBody.colOptions?.options.length
? `${colBody.colOptions.options.map((o) => { ? `${colBody.colOptions.options
if(o.title.includes(',')) { .map((o) => {
NcError.badRequest('Illegal char(\',\') for MultiSelect'); if (o.title.includes(',')) {
} NcError.badRequest("Illegal char(',') for MultiSelect");
return `'${o.title.replace(/'/gi, '\'\'')}'`; }
}).join(',')}` return `'${o.title.replace(/'/gi, "''")}'`;
})
.join(',')}`
: ''; : '';
} }
// Handle empty enum/set for mysql (we restrict empty user options beforehand) // Handle empty enum/set for mysql (we restrict empty user options beforehand)
if (driverType === 'mysql' || driverType === 'mysql2') { if (driverType === 'mysql' || driverType === 'mysql2') {
if (!colBody.colOptions.options.length && (!colBody.dtxp || colBody.dtxp === '')) { if (
colBody.dtxp = '\'\''; !colBody.colOptions.options.length &&
(!colBody.dtxp || colBody.dtxp === '')
) {
colBody.dtxp = "''";
} }
} }
} }
const tableUpdateBody = { const tableUpdateBody = {
...table, ...table,
@ -632,7 +646,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
await table.getColumns(); await table.getColumns();
Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.CREATED, op_sub_type: AuditOperationSubTypes.CREATED,
@ -722,17 +736,14 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
NcError.notImplemented( NcError.notImplemented(
`Updating ${colBody.uidt} => ${colBody.uidt} is not implemented` `Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`
); );
} else if( } else if (
[ [UITypes.SingleSelect, UITypes.MultiSelect].includes(colBody.uidt)
UITypes.SingleSelect,
UITypes.MultiSelect
].includes(colBody.uidt)
) { ) {
colBody = getColumnPropsFromUIDT(colBody, base); colBody = getColumnPropsFromUIDT(colBody, base);
const baseModel = await Model.getBaseModelSQL({ const baseModel = await Model.getBaseModelSQL({
id: table.id, id: table.id,
dbDriver: NcConnectionMgrv2.get(base) dbDriver: NcConnectionMgrv2.get(base),
}); });
if (colBody.colOptions?.options) { if (colBody.colOptions?.options) {
@ -741,36 +752,77 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
const driverType = dbDriver.clientType(); const driverType = dbDriver.clientType();
// MultiSelect to SingleSelect // 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') { 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') { } 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') { } 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') { } 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 // Handle migrations
if (column.colOptions?.options) { if (column.colOptions?.options) {
for (const op of column.colOptions.options.filter(el => el.order === null)) { for (const op of column.colOptions.options.filter(
op.title = op.title.replace(/^'/, '').replace(/'$/, '') (el) => el.order === null
)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '');
} }
} }
// Handle default values // 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.cdf) {
if (colBody.uidt === UITypes.SingleSelect) { if (colBody.uidt === UITypes.SingleSelect) {
if (!optionTitles.includes(colBody.cdf)) { 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 { } else {
for (const cdf of colBody.cdf.split(',')) { for (const cdf of colBody.cdf.split(',')) {
if (!optionTitles.includes(cdf)) { 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<TableType>) {
colBody.cdf = `'${colBody.cdf}'`; colBody.cdf = `'${colBody.cdf}'`;
} }
} }
// Restrict duplicates // Restrict duplicates
const titles = colBody.colOptions.options.map(el => el.title) const titles = colBody.colOptions.options.map((el) => el.title);
if (titles if (
.some( function(item) { titles.some(function (item) {
return titles.indexOf(item) !== titles.lastIndexOf(item); return titles.indexOf(item) !== titles.lastIndexOf(item);
}) })
) { ) {
@ -791,8 +842,8 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
} }
// Restrict empty options // Restrict empty options
if (titles if (
.some( function(item) { titles.some(function (item) {
return item === ''; return item === '';
}) })
) { ) {
@ -802,99 +853,178 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
// Trim end of enum/set // Trim end of enum/set
if (colBody.dt === 'enum' || colBody.dt === 'set') { if (colBody.dt === 'enum' || colBody.dt === 'set') {
for (const opt of colBody.colOptions.options) { for (const opt of colBody.colOptions.options) {
opt.title = opt.title.trimEnd() opt.title = opt.title.trimEnd();
} }
} }
if (colBody.uidt === UITypes.SingleSelect) { if (colBody.uidt === UITypes.SingleSelect) {
colBody.dtxp = (colBody.colOptions?.options.length) colBody.dtxp = colBody.colOptions?.options.length
? `${colBody.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` ? `${colBody.colOptions.options
.map((o) => `'${o.title.replace(/'/gi, "''")}'`)
.join(',')}`
: ''; : '';
} else if (colBody.uidt === UITypes.MultiSelect){ } else if (colBody.uidt === UITypes.MultiSelect) {
colBody.dtxp = (colBody.colOptions?.options.length) colBody.dtxp = colBody.colOptions?.options.length
? `${colBody.colOptions.options.map((o) => { ? `${colBody.colOptions.options
if(o.title.includes(',')) { .map((o) => {
NcError.badRequest('Illegal char(\',\') for MultiSelect'); if (o.title.includes(',')) {
} NcError.badRequest("Illegal char(',') for MultiSelect");
return `'${o.title.replace(/'/gi, '\'\'')}'`; }
}).join(',')}` return `'${o.title.replace(/'/gi, "''")}'`;
})
.join(',')}`
: ''; : '';
} }
// Handle empty enum/set for mysql (we restrict empty user options beforehand) // Handle empty enum/set for mysql (we restrict empty user options beforehand)
if (driverType === 'mysql' || driverType === 'mysql2') { if (driverType === 'mysql' || driverType === 'mysql2') {
if (!colBody.colOptions.options.length && (!colBody.dtxp || colBody.dtxp === '')) { if (
colBody.dtxp = '\'\''; !colBody.colOptions.options.length &&
(!colBody.dtxp || colBody.dtxp === '')
) {
colBody.dtxp = "''";
} }
} }
// Handle option delete // Handle option delete
if (column.colOptions?.options) { if (column.colOptions?.options) {
for (const option of column.colOptions.options.filter(oldOp => colBody.colOptions.options.find(newOp => newOp.id === oldOp.id) ? false : true)) { for (const option of column.colOptions.options.filter((oldOp) =>
if (!supportedDrivers.includes(driverType) && column.uidt === UITypes.MultiSelect) { colBody.colOptions.options.find((newOp) => newOp.id === oldOp.id)
NcError.badRequest('Your database not yet supported for this operation. Please remove option from records manually before dropping.'); ? 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') { 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 { } 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) { } else if (column.uidt === UITypes.MultiSelect) {
if (driverType === 'mysql' || driverType === 'mysql2') { 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') { } 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') { } 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') { } 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 // Handle option update
if (column.colOptions?.options) { if (column.colOptions?.options) {
const old_titles = column.colOptions.options.map(el => el.title); 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))) { for (const option of column.colOptions.options.filter((oldOp) =>
if (!supportedDrivers.includes(driverType) && column.uidt === UITypes.MultiSelect) { colBody.colOptions.options.find(
NcError.badRequest('Your database not yet supported for this operation. Please remove option from records manually before updating.'); (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)) { if (old_titles.includes(newOp.title)) {
let def_option = { ...newOp }; const def_option = { ...newOp };
let title_counter = 1; let title_counter = 1;
while (old_titles.includes(newOp.title)) { while (old_titles.includes(newOp.title)) {
newOp.title = `${def_option.title}_${title_counter++}`; newOp.title = `${def_option.title}_${title_counter++}`;
} }
interchange.push( { interchange.push({
def_option, def_option,
temp_title: newOp.title temp_title: newOp.title,
} ); });
} }
// Append new option before editing // 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 }); column.colOptions.options.push({ title: newOp.title });
let temp_dtxp = ''; let temp_dtxp = '';
if (column.uidt === UITypes.SingleSelect) { if (column.uidt === UITypes.SingleSelect) {
temp_dtxp = (column.colOptions.options.length) temp_dtxp = column.colOptions.options.length
? `${column.colOptions.options.map(o => `'${o.title.replace(/'/gi, '\'\'')}'`).join(',')}` ? `${column.colOptions.options
.map((o) => `'${o.title.replace(/'/gi, "''")}'`)
.join(',')}`
: ''; : '';
} else if (column.uidt === UITypes.MultiSelect){ } else if (column.uidt === UITypes.MultiSelect) {
temp_dtxp = (column.colOptions.options.length) temp_dtxp = column.colOptions.options.length
? `${column.colOptions.options.map((o) => { ? `${column.colOptions.options
if(o.title.includes(',')) { .map((o) => {
NcError.badRequest('Illegal char(\',\') for MultiSelect'); if (o.title.includes(',')) {
throw new Error(''); NcError.badRequest("Illegal char(',') for MultiSelect");
} throw new Error('');
return `'${o.title.replace(/'/gi, '\'\'')}'`; }
}).join(',')}` return `'${o.title.replace(/'/gi, "''")}'`;
})
.join(',')}`
: ''; : '';
} }
@ -926,51 +1056,155 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
), ),
}; };
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 sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
await Column.update(req.params.columnId, { await Column.update(req.params.columnId, {
...column, ...column,
}); });
} }
if (column.uidt === UITypes.SingleSelect) { if (column.uidt === UITypes.SingleSelect) {
if (driverType === 'mssql') { 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 { } 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) { } else if (column.uidt === UITypes.MultiSelect) {
if (driverType === 'mysql' || driverType === 'mysql2') { 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') { } 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') { } 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') { } 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) { for (const ch of interchange) {
let newOp = ch.def_option; const newOp = ch.def_option;
if (column.uidt === UITypes.SingleSelect) { if (column.uidt === UITypes.SingleSelect) {
if (driverType === 'mssql') { 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 { } 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) { } else if (column.uidt === UITypes.MultiSelect) {
if (driverType === 'mysql' || driverType === 'mysql2') { 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') { } 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') { } 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') { } 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<TableType>) {
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 sqlMgr.sqlOpPlus(base, 'tableUpdate', tableUpdateBody);
await Column.update(req.params.columnId, { await Column.update(req.params.columnId, {
...colBody, ...colBody,
}); });
@ -1093,7 +1327,7 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
...colBody, ...colBody,
}); });
} }
Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.UPDATED, op_sub_type: AuditOperationSubTypes.UPDATED,
@ -1292,7 +1526,7 @@ export async function columnDelete(req: Request, res: Response<TableType>) {
} }
} }
Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.DELETED, op_sub_type: AuditOperationSubTypes.DELETED,

6
packages/nocodb/src/lib/meta/api/projectUserApis.ts

@ -87,7 +87,7 @@ async function userInvite(req, res, next): Promise<any> {
); );
} }
Audit.insert({ await Audit.insert({
project_id: req.params.projectId, project_id: req.params.projectId,
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE', op_sub_type: 'INVITE',
@ -188,7 +188,7 @@ async function projectUserUpdate(req, res, next): Promise<any> {
req.body.roles req.body.roles
); );
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'ROLES_MANAGEMENT', op_sub_type: 'ROLES_MANAGEMENT',
user: req.user.email, user: req.user.email,
@ -255,7 +255,7 @@ async function projectUserInviteResend(req, res): Promise<any> {
await sendInviteEmail(user.email, invite_token, req); await sendInviteEmail(user.email, invite_token, req);
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'RESEND_INVITE', op_sub_type: 'RESEND_INVITE',
user: user.email, user: user.email,

4
packages/nocodb/src/lib/meta/api/tableApis.ts

@ -178,7 +178,7 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
base_id: base.id, base_id: base.id,
}); });
Audit.insert({ await Audit.insert({
project_id: project.id, project_id: project.id,
op_type: AuditOperationTypes.TABLE, op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.CREATED, 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, project_id: project.id,
op_type: AuditOperationTypes.TABLE, op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.DELETED, op_sub_type: AuditOperationSubTypes.DELETED,

12
packages/nocodb/src/lib/meta/api/userApi/userApis.ts

@ -146,7 +146,7 @@ export async function signup(req: Request, res: Response<TableType>) {
user = (req as any).user; user = (req as any).user;
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'SIGNUP', op_sub_type: 'SIGNUP',
user: user.email, user: user.email,
@ -192,7 +192,7 @@ async function successfulSignIn({
}); });
setTokenCookie(res, refreshToken); setTokenCookie(res, refreshToken);
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'SIGNIN', op_sub_type: 'SIGNIN',
user: user.email, user: user.email,
@ -291,7 +291,7 @@ async function passwordChange(req: Request<any, any>, res): Promise<any> {
token_version: null, token_version: null,
}); });
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_CHANGE', op_sub_type: 'PASSWORD_CHANGE',
user: user.email, user: user.email,
@ -341,7 +341,7 @@ async function passwordForgot(req: Request<any, any>, res): Promise<any> {
); );
} }
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_FORGOT', op_sub_type: 'PASSWORD_FORGOT',
user: user.email, user: user.email,
@ -405,7 +405,7 @@ async function passwordReset(req, res): Promise<any> {
token_version: null, token_version: null,
}); });
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'PASSWORD_RESET', op_sub_type: 'PASSWORD_RESET',
user: user.email, user: user.email,
@ -433,7 +433,7 @@ async function emailVerification(req, res): Promise<any> {
email_verified: true, email_verified: true,
}); });
Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
op_sub_type: 'EMAIL_VERIFICATION', op_sub_type: 'EMAIL_VERIFICATION',
user: user.email, user: user.email,

55
packages/nocodb/src/lib/models/Audit.ts

@ -31,6 +31,7 @@ export default class Audit implements AuditType {
return audit && new Audit(audit); 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( public static async insert(
audit: Partial< audit: Partial<
Audit & { Audit & {
@ -38,30 +39,40 @@ export default class Audit implements AuditType {
updated_at?; updated_at?;
} }
>, >,
ncMeta = Noco.ncMeta ncMeta = Noco.ncMeta,
) { { forceAwait }: { forceAwait: boolean } = {
if (!audit.project_id && audit.fk_model_id) { forceAwait: process.env['TEST'] === 'true',
audit.project_id = (
await Model.getByIdOrName({ id: audit.fk_model_id }, ncMeta)
).project_id;
} }
const auditRec = await ncMeta.metaInsert2(null, null, MetaTable.AUDIT, { ) {
user: audit.user, const insertAudit = async () => {
ip: audit.ip, if (!audit.project_id && audit.fk_model_id) {
base_id: audit.base_id, audit.project_id = (
project_id: audit.project_id, await Model.getByIdOrName({ id: audit.fk_model_id }, ncMeta)
row_id: audit.row_id, ).project_id;
fk_model_id: audit.fk_model_id, }
op_type: audit.op_type,
op_sub_type: audit.op_sub_type, return await ncMeta.metaInsert2(null, null, MetaTable.AUDIT, {
status: audit.status, user: audit.user,
description: audit.description, ip: audit.ip,
details: audit.details, base_id: audit.base_id,
created_at: audit.created_at, project_id: audit.project_id,
updated_at: audit.updated_at, 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: { public static async commentsCount(args: {

2
packages/nocodb/tests/unit/index.test.ts

@ -6,7 +6,7 @@ import TestDbMngr from './TestDbMngr'
import dotenv from 'dotenv'; import dotenv from 'dotenv';
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
process.env.TEST = 'test'; process.env.TEST = 'true';
process.env.NC_DISABLE_CACHE = 'true'; process.env.NC_DISABLE_CACHE = 'true';
process.env.NC_DISABLE_TELE = 'true'; process.env.NC_DISABLE_TELE = 'true';

Loading…
Cancel
Save