2021-02-22 12:39:58 +01:00
|
|
|
const CouchDB = require("../../../db")
|
|
|
|
const csvParser = require("../../../utilities/csvParser")
|
2021-04-09 18:33:21 +02:00
|
|
|
const {
|
|
|
|
getRowParams,
|
|
|
|
generateRowID,
|
|
|
|
InternalTables,
|
|
|
|
} = require("../../../db/utils")
|
2021-02-22 12:39:58 +01:00
|
|
|
const { isEqual } = require("lodash/fp")
|
2021-05-18 23:14:27 +02:00
|
|
|
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
2021-02-22 12:39:58 +01:00
|
|
|
const { inputProcessing } = require("../../../utilities/rowProcessor")
|
|
|
|
const { USERS_TABLE_SCHEMA } = require("../../../constants")
|
|
|
|
|
|
|
|
exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
|
2021-02-22 13:05:59 +01:00
|
|
|
let updatedRows = []
|
2021-02-22 12:39:58 +01:00
|
|
|
const rename = updatedTable._rename
|
|
|
|
let deletedColumns = []
|
|
|
|
if (oldTable && oldTable.schema && updatedTable.schema) {
|
|
|
|
deletedColumns = Object.keys(oldTable.schema).filter(
|
2021-05-04 12:32:22 +02:00
|
|
|
colName => updatedTable.schema[colName] == null
|
2021-02-22 12:39:58 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
// check for renaming of columns or deleted columns
|
|
|
|
if (rename || deletedColumns.length !== 0) {
|
|
|
|
const rows = await db.allDocs(
|
|
|
|
getRowParams(updatedTable._id, null, {
|
|
|
|
include_docs: true,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
updatedRows = rows.rows.map(({ doc }) => {
|
|
|
|
if (rename) {
|
|
|
|
doc[rename.updated] = doc[rename.old]
|
|
|
|
delete doc[rename.old]
|
|
|
|
} else if (deletedColumns.length !== 0) {
|
2021-05-04 12:32:22 +02:00
|
|
|
deletedColumns.forEach(colName => delete doc[colName])
|
2021-02-22 12:39:58 +01:00
|
|
|
}
|
|
|
|
return doc
|
|
|
|
})
|
|
|
|
delete updatedTable._rename
|
|
|
|
}
|
|
|
|
return { rows: updatedRows, table: updatedTable }
|
|
|
|
}
|
|
|
|
|
|
|
|
// makes sure the passed in table isn't going to reset the auto ID
|
|
|
|
exports.makeSureTableUpToDate = (table, tableToSave) => {
|
|
|
|
if (!table) {
|
|
|
|
return tableToSave
|
|
|
|
}
|
|
|
|
// sure sure rev is up to date
|
|
|
|
tableToSave._rev = table._rev
|
|
|
|
// make sure auto IDs are always updated - these are internal
|
|
|
|
// so the client may not know they have changed
|
|
|
|
for (let [field, column] of Object.entries(table.schema)) {
|
|
|
|
if (
|
|
|
|
column.autocolumn &&
|
|
|
|
column.subtype === AutoFieldSubTypes.AUTO_ID &&
|
|
|
|
tableToSave.schema[field]
|
|
|
|
) {
|
|
|
|
tableToSave.schema[field].lastID = column.lastID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tableToSave
|
|
|
|
}
|
|
|
|
|
2021-03-29 18:32:05 +02:00
|
|
|
exports.handleDataImport = async (appId, user, table, dataImport) => {
|
|
|
|
const db = new CouchDB(appId)
|
2021-02-22 12:39:58 +01:00
|
|
|
if (dataImport && dataImport.csvString) {
|
|
|
|
// Populate the table with rows imported from CSV in a bulk update
|
|
|
|
const data = await csvParser.transform(dataImport)
|
|
|
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
let row = data[i]
|
|
|
|
row._id = generateRowID(table._id)
|
|
|
|
row.tableId = table._id
|
|
|
|
const processed = inputProcessing(user, table, row)
|
2021-05-18 23:14:27 +02:00
|
|
|
table = processed.table
|
2021-02-22 12:39:58 +01:00
|
|
|
row = processed.row
|
|
|
|
for (let [fieldName, schema] of Object.entries(table.schema)) {
|
2021-05-18 23:14:27 +02:00
|
|
|
// check whether the options need to be updated for inclusion as part of the data import
|
2021-02-22 12:39:58 +01:00
|
|
|
if (
|
2021-05-18 23:14:27 +02:00
|
|
|
schema.type === FieldTypes.OPTIONS &&
|
|
|
|
(!schema.constraints.inclusion ||
|
|
|
|
schema.constraints.inclusion.indexOf(row[fieldName]) === -1)
|
2021-02-22 12:39:58 +01:00
|
|
|
) {
|
2021-05-18 23:14:27 +02:00
|
|
|
schema.constraints.inclusion = [
|
|
|
|
...schema.constraints.inclusion,
|
|
|
|
row[fieldName],
|
|
|
|
]
|
2021-02-22 12:39:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
data[i] = row
|
|
|
|
}
|
|
|
|
|
|
|
|
await db.bulkDocs(data)
|
|
|
|
let response = await db.put(table)
|
|
|
|
table._rev = response._rev
|
|
|
|
}
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
2021-03-15 17:36:38 +01:00
|
|
|
exports.handleSearchIndexes = async (appId, table) => {
|
|
|
|
const db = new CouchDB(appId)
|
2021-02-22 12:39:58 +01:00
|
|
|
// create relevant search indexes
|
|
|
|
if (table.indexes && table.indexes.length > 0) {
|
|
|
|
const currentIndexes = await db.getIndexes()
|
|
|
|
const indexName = `search:${table._id}`
|
|
|
|
|
|
|
|
const existingIndex = currentIndexes.indexes.find(
|
2021-05-04 12:32:22 +02:00
|
|
|
existing => existing.name === indexName
|
2021-02-22 12:39:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
if (existingIndex) {
|
|
|
|
const currentFields = existingIndex.def.fields.map(
|
2021-05-04 12:32:22 +02:00
|
|
|
field => Object.keys(field)[0]
|
2021-02-22 12:39:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// if index fields have changed, delete the original index
|
|
|
|
if (!isEqual(currentFields, table.indexes)) {
|
|
|
|
await db.deleteIndex(existingIndex)
|
|
|
|
// create/recreate the index with fields
|
|
|
|
await db.createIndex({
|
|
|
|
index: {
|
|
|
|
fields: table.indexes,
|
|
|
|
name: indexName,
|
|
|
|
ddoc: "search_ddoc",
|
|
|
|
type: "json",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create/recreate the index with fields
|
|
|
|
await db.createIndex({
|
|
|
|
index: {
|
|
|
|
fields: table.indexes,
|
|
|
|
name: indexName,
|
|
|
|
ddoc: "search_ddoc",
|
|
|
|
type: "json",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
2021-05-04 12:32:22 +02:00
|
|
|
exports.checkStaticTables = table => {
|
2021-02-22 12:39:58 +01:00
|
|
|
// check user schema has all required elements
|
2021-04-09 18:33:21 +02:00
|
|
|
if (table._id === InternalTables.USER_METADATA) {
|
2021-02-22 12:39:58 +01:00
|
|
|
for (let [key, schema] of Object.entries(USERS_TABLE_SCHEMA.schema)) {
|
|
|
|
// check if the schema exists on the table to be created/updated
|
|
|
|
if (table.schema[key] == null) {
|
|
|
|
table.schema[key] = schema
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
|
|
|
class TableSaveFunctions {
|
|
|
|
constructor({ db, ctx, oldTable, dataImport }) {
|
|
|
|
this.db = db
|
|
|
|
this.ctx = ctx
|
2021-03-15 17:36:38 +01:00
|
|
|
if (this.ctx && this.ctx.user) {
|
2021-03-29 18:32:05 +02:00
|
|
|
this.appId = this.ctx.appId
|
2021-03-15 17:36:38 +01:00
|
|
|
}
|
2021-02-22 12:39:58 +01:00
|
|
|
this.oldTable = oldTable
|
|
|
|
this.dataImport = dataImport
|
|
|
|
// any rows that need updated
|
|
|
|
this.rows = []
|
|
|
|
}
|
|
|
|
|
|
|
|
// before anything is done
|
|
|
|
async before(table) {
|
|
|
|
if (this.oldTable) {
|
|
|
|
table = exports.makeSureTableUpToDate(this.oldTable, table)
|
|
|
|
}
|
|
|
|
table = exports.checkStaticTables(table)
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
|
|
|
// when confirmed valid
|
|
|
|
async mid(table) {
|
|
|
|
let response = await exports.checkForColumnUpdates(
|
|
|
|
this.db,
|
|
|
|
this.oldTable,
|
|
|
|
table
|
|
|
|
)
|
2021-02-22 13:05:59 +01:00
|
|
|
this.rows = this.rows.concat(response.rows)
|
2021-02-22 12:39:58 +01:00
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
|
|
|
// after saving
|
|
|
|
async after(table) {
|
2021-03-15 17:36:38 +01:00
|
|
|
table = await exports.handleSearchIndexes(this.appId, table)
|
2021-02-22 12:39:58 +01:00
|
|
|
table = await exports.handleDataImport(
|
2021-03-29 18:32:05 +02:00
|
|
|
this.appId,
|
2021-02-22 12:39:58 +01:00
|
|
|
this.ctx.user,
|
|
|
|
table,
|
|
|
|
this.dataImport
|
|
|
|
)
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
|
|
|
getUpdatedRows() {
|
|
|
|
return this.rows
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 20:05:32 +02:00
|
|
|
exports.getAllExternalTables = async (appId, datasourceId) => {
|
2021-06-16 17:27:33 +02:00
|
|
|
const db = new CouchDB(appId)
|
|
|
|
const datasource = await db.get(datasourceId)
|
|
|
|
if (!datasource || !datasource.entities) {
|
|
|
|
throw "Datasource is not configured fully."
|
|
|
|
}
|
2021-06-23 20:05:32 +02:00
|
|
|
return datasource.entities
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getExternalTable = async (appId, datasourceId, tableName) => {
|
|
|
|
const entities = await exports.getAllExternalTables(appId, datasourceId)
|
|
|
|
return entities[tableName]
|
2021-06-16 17:27:33 +02:00
|
|
|
}
|
|
|
|
|
2021-02-22 12:39:58 +01:00
|
|
|
exports.TableSaveFunctions = TableSaveFunctions
|