1
0
Fork 0
mirror of synced 2024-06-22 16:10:40 +12:00
budibase/packages/server/src/api/controllers/datasource.js

284 lines
7.4 KiB
JavaScript
Raw Normal View History

2021-01-13 05:49:11 +13:00
const {
generateDatasourceID,
getDatasourceParams,
getQueryParams,
DocumentType,
BudibaseInternalDB,
getTableParams,
2021-01-13 05:49:11 +13:00
} = require("../../db/utils")
const { BuildSchemaErrors, InvalidColumns } = require("../../constants")
2022-08-16 05:38:09 +12:00
const { getIntegration } = require("../../integrations")
2021-10-29 07:39:42 +13:00
const { getDatasourceAndQuery } = require("./row/utils")
const { invalidateDynamicVariables } = require("../../threads/utils")
const { getAppDB } = require("@budibase/backend-core/context")
2022-04-02 09:29:44 +13:00
const { events } = require("@budibase/backend-core")
2020-12-19 07:19:43 +13:00
2021-05-03 19:31:09 +12:00
exports.fetch = async function (ctx) {
// Get internal tables
const db = getAppDB()
const internalTables = await db.allDocs(
getTableParams(null, {
include_docs: true,
})
)
const internal = internalTables.rows.map(row => row.doc)
const bbInternalDb = {
...BudibaseInternalDB,
entities: internal,
}
// Get external datasources
const datasources = (
await db.allDocs(
2020-12-19 07:19:43 +13:00
getDatasourceParams(null, {
include_docs: true,
})
)
2021-05-04 22:32:22 +12:00
).rows.map(row => row.doc)
2022-01-19 05:41:38 +13:00
for (let datasource of datasources) {
if (datasource.config && datasource.config.auth) {
// strip secrets from response so they don't show in the network request
delete datasource.config.auth
}
}
ctx.body = [bbInternalDb, ...datasources]
2020-12-19 07:19:43 +13:00
}
2021-06-17 02:45:57 +12:00
exports.buildSchemaFromDb = async function (ctx) {
const db = getAppDB()
const datasource = await db.get(ctx.params.datasourceId)
const tablesFilter = ctx.request.body.tablesFilter
2021-06-17 02:45:57 +12:00
let { tables, error } = await buildSchemaHelper(datasource)
if (tablesFilter) {
if (!datasource.entities) {
datasource.entities = {}
}
for (let key in tables) {
2022-09-13 21:55:38 +12:00
if (
tablesFilter.some(filter => filter.toLowerCase() === key.toLowerCase())
) {
datasource.entities[key] = tables[key]
}
}
} else {
datasource.entities = tables
}
2021-06-17 02:45:57 +12:00
2022-09-21 22:53:18 +12:00
setDefaultDisplayColumns(datasource)
const dbResp = await db.put(datasource)
datasource._rev = dbResp.rev
2021-06-17 02:45:57 +12:00
const response = { datasource }
if (error) {
response.error = error
}
ctx.body = response
2021-06-17 02:45:57 +12:00
}
2022-09-21 22:53:18 +12:00
/**
* Make sure all datasource entities have a display name selected
*/
const setDefaultDisplayColumns = datasource => {
//
for (let entity of Object.values(datasource.entities)) {
if (entity.primaryDisplay) {
continue
}
const notAutoColumn = Object.values(entity.schema).find(
schema => !schema.autocolumn
)
if (notAutoColumn) {
entity.primaryDisplay = notAutoColumn.name
}
}
}
/**
* Check for variables that have been updated or removed and invalidate them.
*/
const invalidateVariables = async (existingDatasource, updatedDatasource) => {
const existingVariables = existingDatasource.config.dynamicVariables
const updatedVariables = updatedDatasource.config.dynamicVariables
const toInvalidate = []
if (!existingVariables) {
return
}
if (!updatedVariables) {
// invalidate all
toInvalidate.push(...existingVariables)
} else {
// invaldate changed / removed
existingVariables.forEach(existing => {
const unchanged = updatedVariables.find(
updated =>
existing.name === updated.name &&
existing.queryId === updated.queryId &&
existing.value === updated.value
)
if (!unchanged) {
toInvalidate.push(existing)
}
})
}
await invalidateDynamicVariables(toInvalidate)
}
2021-08-18 09:57:11 +12:00
exports.update = async function (ctx) {
const db = getAppDB()
2021-08-18 09:57:11 +12:00
const datasourceId = ctx.params.datasourceId
let datasource = await db.get(datasourceId)
const auth = datasource.config.auth
await invalidateVariables(datasource, ctx.request.body)
datasource = { ...datasource, ...ctx.request.body }
if (auth && !ctx.request.body.auth) {
// don't strip auth config from DB
datasource.config.auth = auth
}
2021-08-18 09:57:11 +12:00
const response = await db.put(datasource)
2022-06-01 08:04:41 +12:00
await events.datasource.updated(datasource)
2021-08-18 09:57:11 +12:00
datasource._rev = response.rev
// Drain connection pools when configuration is changed
if (datasource.source) {
2022-08-16 05:38:09 +12:00
const source = await getIntegration(datasource.source)
2021-08-18 09:57:11 +12:00
if (source && source.pool) {
await source.pool.end()
}
}
ctx.status = 200
ctx.message = "Datasource saved successfully."
ctx.body = { datasource }
2021-08-18 09:57:11 +12:00
}
2021-05-03 19:31:09 +12:00
exports.save = async function (ctx) {
const db = getAppDB()
const plus = ctx.request.body.datasource.plus
const fetchSchema = ctx.request.body.fetchSchema
2020-12-19 07:19:43 +13:00
const datasource = {
_id: generateDatasourceID({ plus }),
type: plus ? DocumentType.DATASOURCE_PLUS : DocumentType.DATASOURCE,
...ctx.request.body.datasource,
}
let schemaError = null
if (fetchSchema) {
const { tables, error } = await buildSchemaHelper(datasource)
schemaError = error
datasource.entities = tables
2022-09-21 22:53:18 +12:00
setDefaultDisplayColumns(datasource)
2020-12-19 07:19:43 +13:00
}
const dbResp = await db.put(datasource)
2022-05-31 08:46:08 +12:00
await events.datasource.created(datasource)
datasource._rev = dbResp.rev
2020-12-19 07:19:43 +13:00
// Drain connection pools when configuration is changed
2021-06-04 04:56:04 +12:00
if (datasource.source) {
2022-08-16 05:38:09 +12:00
const source = await getIntegration(datasource.source)
2021-06-04 04:56:04 +12:00
if (source && source.pool) {
await source.pool.end()
}
}
const response = { datasource }
if (schemaError) {
response.error = schemaError
}
ctx.body = response
2020-12-19 07:19:43 +13:00
}
2021-05-03 19:31:09 +12:00
exports.destroy = async function (ctx) {
const db = getAppDB()
2022-06-01 08:04:41 +12:00
const datasourceId = ctx.params.datasourceId
2021-01-13 05:49:11 +13:00
2022-06-01 08:04:41 +12:00
const datasource = await db.get(datasourceId)
2021-01-13 05:49:11 +13:00
// Delete all queries for the datasource
2022-06-01 08:04:41 +12:00
const queries = await db.allDocs(getQueryParams(datasourceId, null))
await db.bulkDocs(
queries.rows.map(row => ({
_id: row.id,
_rev: row.value.rev,
_deleted: true,
}))
)
2021-01-13 05:49:11 +13:00
// delete the datasource
2022-06-01 08:04:41 +12:00
await db.remove(datasourceId, ctx.params.revId)
await events.datasource.deleted(datasource)
2021-01-13 05:49:11 +13:00
2020-12-19 07:19:43 +13:00
ctx.message = `Datasource deleted.`
ctx.status = 200
}
2021-05-03 19:31:09 +12:00
exports.find = async function (ctx) {
const database = getAppDB()
ctx.body = await database.get(ctx.params.datasourceId)
2020-12-19 07:19:43 +13:00
}
2021-06-04 04:56:04 +12:00
2021-06-04 05:48:04 +12:00
// dynamic query functionality
exports.query = async function (ctx) {
const queryJson = ctx.request.body
try {
ctx.body = await getDatasourceAndQuery(queryJson)
} catch (err) {
ctx.throw(400, err)
}
2021-06-04 05:48:04 +12:00
}
function getErrorTables(errors, errorType) {
return Object.entries(errors)
.filter(entry => entry[1] === errorType)
.map(([name]) => name)
}
function updateError(error, newError, tables) {
if (!error) {
error = ""
}
if (error.length > 0) {
error += "\n"
}
error += `${newError} ${tables.join(", ")}`
return error
}
const buildSchemaHelper = async datasource => {
2022-08-16 05:38:09 +12:00
const Connector = await getIntegration(datasource.source)
// Connect to the DB and build the schema
const connector = new Connector(datasource.config)
await connector.buildSchema(datasource._id, datasource.entities)
const errors = connector.schemaErrors
let error = null
if (errors && Object.keys(errors).length > 0) {
const noKey = getErrorTables(errors, BuildSchemaErrors.NO_KEY)
const invalidCol = getErrorTables(errors, BuildSchemaErrors.INVALID_COLUMN)
if (noKey.length) {
error = updateError(
error,
"No primary key constraint found for the following:",
noKey
)
}
if (invalidCol.length) {
const invalidCols = Object.values(InvalidColumns).join(", ")
error = updateError(
error,
`Cannot use columns ${invalidCols} found in following:`,
invalidCol
)
}
}
return { tables: connector.tables, error }
}