|
|
|
@ -43,6 +43,7 @@ import genRollupSelectv2 from './genRollupSelectv2';
|
|
|
|
|
import conditionV2 from './conditionV2'; |
|
|
|
|
import sortV2 from './sortV2'; |
|
|
|
|
import { customValidators } from './util/customValidators'; |
|
|
|
|
import Transaction = Knex.Transaction; |
|
|
|
|
import type { XKnex } from './CustomKnex'; |
|
|
|
|
import type { |
|
|
|
|
XcFilter, |
|
|
|
@ -58,7 +59,6 @@ import type {
|
|
|
|
|
SelectOption, |
|
|
|
|
} from '../models'; |
|
|
|
|
import type { SortType } from 'nocodb-sdk'; |
|
|
|
|
import Transaction = Knex.Transaction; |
|
|
|
|
|
|
|
|
|
dayjs.extend(utc); |
|
|
|
|
dayjs.extend(timezone); |
|
|
|
@ -149,13 +149,18 @@ class BaseModelSqlv2 {
|
|
|
|
|
id?: any, |
|
|
|
|
validateFormula = false, |
|
|
|
|
query: any = {}, |
|
|
|
|
{ |
|
|
|
|
ignoreView = false, |
|
|
|
|
}: { |
|
|
|
|
ignoreView?: boolean; |
|
|
|
|
} = {}, |
|
|
|
|
): Promise<any> { |
|
|
|
|
const qb = this.dbDriver(this.tnPath); |
|
|
|
|
|
|
|
|
|
const { ast, dependencyFields } = await getAst({ |
|
|
|
|
query, |
|
|
|
|
model: this.model, |
|
|
|
|
view: this.viewId && (await View.get(this.viewId)), |
|
|
|
|
view: ignoreView ? null : this.viewId && (await View.get(this.viewId)), |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
await this.selectObject({ |
|
|
|
@ -1385,7 +1390,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
|
|
|
|
|
if (colOptions?.type === 'hm') { |
|
|
|
|
const listLoader = new DataLoader(async (ids: string[]) => { |
|
|
|
|
try { |
|
|
|
|
if (ids.length > 1) { |
|
|
|
|
const data = await this.multipleHmList( |
|
|
|
|
{ |
|
|
|
@ -1406,10 +1410,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
), |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
return []; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
const self: BaseModelSqlv2 = this; |
|
|
|
|
|
|
|
|
@ -1429,7 +1429,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
// });
|
|
|
|
|
} else if (colOptions.type === 'mm') { |
|
|
|
|
const listLoader = new DataLoader(async (ids: string[]) => { |
|
|
|
|
try { |
|
|
|
|
if (ids?.length > 1) { |
|
|
|
|
const data = await this.multipleMmList( |
|
|
|
|
{ |
|
|
|
@ -1451,10 +1450,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
), |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
return []; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
const self: BaseModelSqlv2 = this; |
|
|
|
@ -1476,7 +1471,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
colId: colOptions.fk_child_column_id, |
|
|
|
|
}); |
|
|
|
|
const readLoader = new DataLoader(async (ids: string[]) => { |
|
|
|
|
try { |
|
|
|
|
const data = await ( |
|
|
|
|
await Model.getBaseModelSQL({ |
|
|
|
|
id: pCol.fk_model_id, |
|
|
|
@ -1492,10 +1486,6 @@ class BaseModelSqlv2 {
|
|
|
|
|
); |
|
|
|
|
const gs = groupBy(data, pCol.title); |
|
|
|
|
return ids.map(async (id: string) => gs?.[id]?.[0]); |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
return []; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// defining HasMany count method within GQL Type class
|
|
|
|
@ -1836,7 +1826,12 @@ class BaseModelSqlv2 {
|
|
|
|
|
// handle if autogenerated primary key is used
|
|
|
|
|
if (ag) { |
|
|
|
|
if (!response) await this.execAndParse(query); |
|
|
|
|
response = await this.readByPk(data[ag.title]); |
|
|
|
|
response = await this.readByPk( |
|
|
|
|
data[ag.title], |
|
|
|
|
false, |
|
|
|
|
{}, |
|
|
|
|
{ ignoreView: true }, |
|
|
|
|
); |
|
|
|
|
} else if ( |
|
|
|
|
!response || |
|
|
|
|
(typeof response?.[0] !== 'object' && response?.[0] !== null) |
|
|
|
@ -1864,7 +1859,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
})) as any |
|
|
|
|
)[0].id; |
|
|
|
|
} |
|
|
|
|
response = await this.readByPk(id); |
|
|
|
|
response = await this.readByPk(id, false, {}, { ignoreView: true }); |
|
|
|
|
} else { |
|
|
|
|
response = data; |
|
|
|
|
} |
|
|
|
@ -1873,6 +1868,9 @@ class BaseModelSqlv2 {
|
|
|
|
|
Array.isArray(response) |
|
|
|
|
? response?.[0]?.[ai.title] |
|
|
|
|
: response?.[ai.title], |
|
|
|
|
false, |
|
|
|
|
{}, |
|
|
|
|
{ ignoreView: true }, |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1889,7 +1887,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
let trx: Transaction = _trx; |
|
|
|
|
try { |
|
|
|
|
// retrieve data for handling params in hook
|
|
|
|
|
const data = await this.readByPk(id); |
|
|
|
|
const data = await this.readByPk(id, false, {}, { ignoreView: true }); |
|
|
|
|
await this.beforeDelete(id, trx, cookie); |
|
|
|
|
|
|
|
|
|
const execQueries: ((trx: Transaction) => Promise<any>)[] = []; |
|
|
|
@ -2018,7 +2016,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
|
|
|
|
|
await this.beforeUpdate(data, trx, cookie); |
|
|
|
|
|
|
|
|
|
const prevData = await this.readByPk(id); |
|
|
|
|
const prevData = await this.readByPk(id, false, {}, { ignoreView: true }); |
|
|
|
|
|
|
|
|
|
const query = this.dbDriver(this.tnPath) |
|
|
|
|
.update(updateObj) |
|
|
|
@ -2026,7 +2024,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
|
|
|
|
|
await this.execAndParse(query); |
|
|
|
|
|
|
|
|
|
const newData = await this.readByPk(id); |
|
|
|
|
const newData = await this.readByPk(id, false, {}, { ignoreView: true }); |
|
|
|
|
await this.afterUpdate(prevData, newData, trx, cookie, updateObj); |
|
|
|
|
return newData; |
|
|
|
|
} catch (e) { |
|
|
|
@ -2210,7 +2208,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
})) as any |
|
|
|
|
).rows[0].id; |
|
|
|
|
} |
|
|
|
|
response = await this.readByPk(id); |
|
|
|
|
response = await this.readByPk(id, false, {}, { ignoreView: true }); |
|
|
|
|
} else { |
|
|
|
|
response = data; |
|
|
|
|
} |
|
|
|
@ -2478,7 +2476,9 @@ class BaseModelSqlv2 {
|
|
|
|
|
|
|
|
|
|
if (!raw) { |
|
|
|
|
for (const pkValues of updatePkValues) { |
|
|
|
|
newData.push(await this.readByPk(pkValues)); |
|
|
|
|
newData.push( |
|
|
|
|
await this.readByPk(pkValues, false, {}, { ignoreView: true }), |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -2570,7 +2570,9 @@ class BaseModelSqlv2 {
|
|
|
|
|
// pk not specified - bypass
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
deleted.push(await this.readByPk(pkValues)); |
|
|
|
|
deleted.push( |
|
|
|
|
await this.readByPk(pkValues, false, {}, { ignoreView: true }), |
|
|
|
|
); |
|
|
|
|
res.push(d); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -2704,18 +2706,16 @@ class BaseModelSqlv2 {
|
|
|
|
|
await parentTable.getColumns(); |
|
|
|
|
|
|
|
|
|
const childTn = this.getTnPath(childTable); |
|
|
|
|
const parentTn = this.getTnPath(parentTable); |
|
|
|
|
|
|
|
|
|
switch (colOptions.type) { |
|
|
|
|
case 'mm': |
|
|
|
|
{ |
|
|
|
|
const vChildCol = await colOptions.getMMChildColumn(); |
|
|
|
|
const vParentCol = await colOptions.getMMParentColumn(); |
|
|
|
|
const vTable = await colOptions.getMMModel(); |
|
|
|
|
|
|
|
|
|
const vTn = this.getTnPath(vTable); |
|
|
|
|
|
|
|
|
|
execQueries.push((trx, qb) => |
|
|
|
|
execQueries.push(() => |
|
|
|
|
this.dbDriver(vTn) |
|
|
|
|
.where({ |
|
|
|
|
[vChildCol.column_name]: this.dbDriver(childTn) |
|
|
|
@ -2975,104 +2975,12 @@ class BaseModelSqlv2 {
|
|
|
|
|
modelId: this.model.id, |
|
|
|
|
tnPath: this.tnPath, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
const view = await View.get(this.viewId); |
|
|
|
|
|
|
|
|
|
// handle form view data submission
|
|
|
|
|
if ( |
|
|
|
|
(hookName === 'after.insert' || hookName === 'after.bulkInsert') && |
|
|
|
|
view.type === ViewTypes.FORM |
|
|
|
|
) { |
|
|
|
|
try { |
|
|
|
|
const formView = await view.getView<FormView>(); |
|
|
|
|
const { columns } = await FormView.getWithInfo(formView.fk_view_id); |
|
|
|
|
const allColumns = await this.model.getColumns(); |
|
|
|
|
const fieldById = columns.reduce( |
|
|
|
|
(o: Record<string, any>, f: Record<string, any>) => ({ |
|
|
|
|
...o, |
|
|
|
|
[f.fk_column_id]: f, |
|
|
|
|
}), |
|
|
|
|
{}, |
|
|
|
|
); |
|
|
|
|
let order = 1; |
|
|
|
|
const filteredColumns = allColumns |
|
|
|
|
?.map((c: Record<string, any>) => ({ |
|
|
|
|
...c, |
|
|
|
|
fk_column_id: c.id, |
|
|
|
|
fk_view_id: formView.fk_view_id, |
|
|
|
|
...(fieldById[c.id] ? fieldById[c.id] : {}), |
|
|
|
|
order: (fieldById[c.id] && fieldById[c.id].order) || order++, |
|
|
|
|
id: fieldById[c.id] && fieldById[c.id].id, |
|
|
|
|
})) |
|
|
|
|
.sort( |
|
|
|
|
(a: Record<string, any>, b: Record<string, any>) => |
|
|
|
|
a.order - b.order, |
|
|
|
|
) |
|
|
|
|
.filter( |
|
|
|
|
(f: Record<string, any>) => |
|
|
|
|
f.show && |
|
|
|
|
f.uidt !== UITypes.Rollup && |
|
|
|
|
f.uidt !== UITypes.Lookup && |
|
|
|
|
f.uidt !== UITypes.Formula && |
|
|
|
|
f.uidt !== UITypes.QrCode && |
|
|
|
|
f.uidt !== UITypes.Barcode && |
|
|
|
|
f.uidt !== UITypes.SpecificDBType, |
|
|
|
|
) |
|
|
|
|
.sort( |
|
|
|
|
(a: Record<string, any>, b: Record<string, any>) => |
|
|
|
|
a.order - b.order, |
|
|
|
|
) |
|
|
|
|
.map((c: Record<string, any>) => ({ |
|
|
|
|
...c, |
|
|
|
|
required: !!(c.required || 0), |
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
const emails = Object.entries(JSON.parse(formView?.email) || {}) |
|
|
|
|
.filter((a) => a[1]) |
|
|
|
|
.map((a) => a[0]); |
|
|
|
|
if (emails?.length) { |
|
|
|
|
const transformedData = _transformSubmittedFormDataForEmail( |
|
|
|
|
newData, |
|
|
|
|
formView, |
|
|
|
|
filteredColumns, |
|
|
|
|
); |
|
|
|
|
(await NcPluginMgrv2.emailAdapter(false))?.mailSend({ |
|
|
|
|
to: emails.join(','), |
|
|
|
|
subject: 'NocoDB Form', |
|
|
|
|
html: ejs.render(formSubmissionEmailTemplate, { |
|
|
|
|
data: transformedData, |
|
|
|
|
tn: this.tnPath, |
|
|
|
|
_tn: this.model.title, |
|
|
|
|
}), |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log(e); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
const [event, operation] = hookName.split('.'); |
|
|
|
|
const hooks = await Hook.list({ |
|
|
|
|
fk_model_id: this.model.id, |
|
|
|
|
event, |
|
|
|
|
operation, |
|
|
|
|
}); |
|
|
|
|
for (const hook of hooks) { |
|
|
|
|
if (hook.active) { |
|
|
|
|
invokeWebhook(hook, this.model, view, prevData, newData, req?.user); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log('hooks :: error', hookName, e); |
|
|
|
|
}*/ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
protected async errorInsert(e, data, trx, cookie) {} |
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
protected async errorUpdate(e, data, trx, cookie) {} |
|
|
|
|
|
|
|
|
|
// todo: handle composite primary key
|
|
|
|
@ -3086,7 +2994,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
|
protected async errorDelete(e, id, trx, cookie) {} |
|
|
|
|
|
|
|
|
|
async validate(columns) { |
|
|
|
@ -3227,7 +3135,12 @@ class BaseModelSqlv2 {
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const response = await this.readByPk(rowId); |
|
|
|
|
const response = await this.readByPk( |
|
|
|
|
rowId, |
|
|
|
|
false, |
|
|
|
|
{}, |
|
|
|
|
{ ignoreView: true }, |
|
|
|
|
); |
|
|
|
|
await this.afterInsert(response, this.dbDriver, cookie); |
|
|
|
|
await this.afterAddChild(rowId, childId, cookie); |
|
|
|
|
} |
|
|
|
@ -3276,7 +3189,12 @@ class BaseModelSqlv2 {
|
|
|
|
|
const childTn = this.getTnPath(childTable); |
|
|
|
|
const parentTn = this.getTnPath(parentTable); |
|
|
|
|
|
|
|
|
|
const prevData = await this.readByPk(rowId); |
|
|
|
|
const prevData = await this.readByPk( |
|
|
|
|
rowId, |
|
|
|
|
false, |
|
|
|
|
{}, |
|
|
|
|
{ ignoreView: true }, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
switch (colOptions.type) { |
|
|
|
|
case RelationTypes.MANY_TO_MANY: |
|
|
|
@ -3329,7 +3247,7 @@ class BaseModelSqlv2 {
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const newData = await this.readByPk(rowId); |
|
|
|
|
const newData = await this.readByPk(rowId, false, {}, { ignoreView: true }); |
|
|
|
|
await this.afterUpdate(prevData, newData, this.dbDriver, cookie); |
|
|
|
|
await this.afterRemoveChild(rowId, childId, cookie); |
|
|
|
|
} |
|
|
|
|