Some of the functionality required for external SQL rows API.

This commit is contained in:
mike12345567 2021-06-14 19:05:39 +01:00
parent 146a72f61d
commit ce8d06df06
13 changed files with 153 additions and 73 deletions

View File

@ -44,7 +44,7 @@ export const updateRow = async row => {
return return
} }
const res = await API.patch({ const res = await API.patch({
url: `/api/${row.tableId}/rows/${row._id}`, url: `/api/${row.tableId}/rows`,
body: row, body: row,
}) })
res.error res.error

View File

@ -45,7 +45,7 @@ export const searchTable = async ({
} }
} }
const res = await API.post({ const res = await API.post({
url: `/api/search/${tableId}/rows`, url: `/api/${tableId}/search`,
body: { body: {
query, query,
bookmark, bookmark,

View File

@ -7,6 +7,7 @@ const {
} = require("../../db/utils") } = require("../../db/utils")
const { integrations } = require("../../integrations") const { integrations } = require("../../integrations")
const plusIntegrations = require("../../integrations/plus") const plusIntegrations = require("../../integrations/plus")
const { makeExternalQuery } = require("./row/utils")
exports.fetch = async function (ctx) { exports.fetch = async function (ctx) {
const database = new CouchDB(ctx.appId) const database = new CouchDB(ctx.appId)
@ -77,15 +78,9 @@ exports.find = async function (ctx) {
// dynamic query functionality // dynamic query functionality
exports.query = async function (ctx) { exports.query = async function (ctx) {
const queryJson = ctx.request.body const queryJson = ctx.request.body
const datasourceId = queryJson.endpoint.datasourceId try {
const database = new CouchDB(ctx.appId) ctx.body = await makeExternalQuery(ctx.appId, queryJson)
const datasource = await database.get(datasourceId) } catch (err) {
const Integration = integrations[datasource.source] ctx.throw(400, err)
// query is the opinionated function
if (Integration.prototype.query) {
const integration = new Integration(datasource.config)
ctx.body = await integration.query(queryJson)
} else {
ctx.throw(400, "Datasource does not support query.")
} }
} }

View File

@ -1,38 +1,115 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const { makeExternalQuery } = require("./utils")
const { DataSourceOperation, SortDirection } = require("../../../constants")
async function buildIDFilter(id) {
if (!id) {
return {}
}
// TODO: work out how to use the schema to get filter
return {
equal: {
id: id,
}
}
}
async function handleRequest(appId, operation, tableId, { id, row, filters, sort, paginate }) {
let [datasourceId, tableName] = tableId.split("/")
let idFilter = buildIDFilter(id)
let json = {
endpoint: {
datasourceId,
entityId: tableName,
operation,
},
filters: {
...filters,
...idFilter,
},
sort,
paginate,
body: row,
}
return makeExternalQuery(appId, json)
}
exports.patch = async (ctx) => { exports.patch = async (ctx) => {
ctx.body = {} const appId = ctx.appId
const inputs = ctx.request.body
const tableId = ctx.params.tableId
const id = inputs._id
// don't save the ID to db
delete inputs._id
ctx.body = await handleRequest(appId, DataSourceOperation.UPDATE, tableId, { id, row: inputs })
} }
exports.save = async (ctx) => { exports.save = async (ctx) => {
ctx.body = {} const appId = ctx.appId
const inputs = ctx.request.body
if (inputs._id) {
return exports.patch(ctx)
}
const tableId = ctx.params.tableId
ctx.body = await handleRequest(appId, DataSourceOperation.CREATE, tableId, { row: inputs })
} }
exports.fetchView = async (ctx) => { exports.fetchView = async (ctx) => {
ctx.body = {} // TODO: don't know what this does for external
} }
exports.fetchTableRows = async (ctx) => { exports.fetchTableRows = async (ctx) => {
ctx.body = {} // TODO: this is a basic read?
} }
exports.find = async (ctx) => { exports.find = async (ctx) => {
ctx.body = {} // TODO: single find
} }
exports.destroy = async (ctx) => { exports.destroy = async (ctx) => {
ctx.body = {} const appId = ctx.appId
const tableId = ctx.params.tableId
ctx.body = await handleRequest(appId, DataSourceOperation.DELETE, tableId, { id: ctx.request.body._id })
} }
exports.bulkDestroy = async (ctx) => { exports.bulkDestroy = async (ctx) => {
ctx.body = {} // TODO: iterate through rows, build a large OR filter?
}
exports.search = async (ctx) => {
const appId = ctx.appId
const tableId = ctx.params.tableId
const { paginate, query, ...params } = ctx.request.body
let paginateObj = {}
if (paginate) {
paginateObj = {
limit: params.limit,
// todo: need to handle bookmarks
page: params.bookmark,
}
}
let sort
if (params.sort) {
sort = {
[params.sort]: params.sortOrder === "descending" ? SortDirection.DESCENDING : SortDirection.ASCENDING
}
}
ctx.body = await handleRequest(appId, DataSourceOperation.READ, tableId,
{
filters: query,
sort,
paginate: paginateObj,
}
)
} }
exports.validate = async (ctx) => { exports.validate = async (ctx) => {
ctx.body = {} // can't validate external right now - maybe in future
ctx.body = { valid: true }
} }
exports.fetchEnrichedRow = async (ctx) => { exports.fetchEnrichedRow = async (ctx) => {
// TODO: should this join? // TODO: should this join?
const appId = ctx.appId
ctx.body = {} ctx.body = {}
} }

View File

@ -95,6 +95,16 @@ exports.destroy = async function (ctx) {
ctx.body = response ctx.body = response
} }
exports.search = async ctx => {
const tableId = getTableId(ctx)
try {
ctx.body = await pickApi(tableId).search(ctx)
} catch (err) {
ctx.throw(400, err)
}
}
exports.validate = async function (ctx) { exports.validate = async function (ctx) {
const tableId = getTableId(ctx) const tableId = getTableId(ctx)
try { try {

View File

@ -15,6 +15,7 @@ const {
const { FieldTypes } = require("../../../constants") const { FieldTypes } = require("../../../constants")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { validate, findRow } = require("./utils") const { validate, findRow } = require("./utils")
const { fullSearch, paginatedSearch } = require("./internalSearch")
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}`
@ -32,7 +33,7 @@ exports.patch = async ctx => {
const isUserTable = tableId === InternalTables.USER_METADATA const isUserTable = tableId === InternalTables.USER_METADATA
let dbRow let dbRow
try { try {
dbRow = await db.get(ctx.params.rowId) dbRow = await db.get(inputs._id)
} catch (err) { } catch (err) {
if (isUserTable) { if (isUserTable) {
// don't include the rev, it'll be the global rev // don't include the rev, it'll be the global rev
@ -96,7 +97,6 @@ exports.save = async function (ctx) {
// if the row obj had an _id then it will have been retrieved // if the row obj had an _id then it will have been retrieved
if (inputs._id && inputs._rev) { if (inputs._id && inputs._rev) {
ctx.params.rowId = inputs._id
return exports.patch(ctx) return exports.patch(ctx)
} }
@ -281,6 +281,29 @@ exports.bulkDestroy = async ctx => {
return { response: { ok: true }, rows } return { response: { ok: true }, rows }
} }
exports.search = async ctx => {
const appId = ctx.appId
const { tableId } = ctx.params
const db = new CouchDB(appId)
const { paginate, query, ...params } = ctx.request.body
params.tableId = tableId
let response
if (paginate) {
response = await paginatedSearch(appId, query, params)
} else {
response = await fullSearch(appId, query, params)
}
// Enrich search results with relationships
if (response.rows && response.rows.length) {
const table = await db.get(tableId)
response.rows = await outputProcessing(appId, table, response.rows)
}
ctx.body = response
}
exports.validate = async (ctx) => { exports.validate = async (ctx) => {
return validate({ return validate({
appId: ctx.appId, appId: ctx.appId,

View File

@ -4,6 +4,7 @@ const CouchDB = require("../../../db")
const { InternalTables } = require("../../../db/utils") const { InternalTables } = require("../../../db/utils")
const userController = require("../user") const userController = require("../user")
const { FieldTypes } = require("../../../constants") const { FieldTypes } = require("../../../constants")
const { integrations } = require("../../../integrations")
validateJs.extend(validateJs.validators.datetime, { validateJs.extend(validateJs.validators.datetime, {
parse: function (value) { parse: function (value) {
@ -15,6 +16,20 @@ validateJs.extend(validateJs.validators.datetime, {
}, },
}) })
exports.makeExternalQuery = async (appId, json) => {
const datasourceId = json.endpoint.datasourceId
const database = new CouchDB(ctx.appId)
const datasource = await database.get(datasourceId)
const Integration = integrations[datasource.source]
// query is the opinionated function
if (Integration.prototype.query) {
const integration = new Integration(datasource.config)
return integration.query(json)
} else {
throw "Datasource does not support query."
}
}
exports.findRow = async (ctx, db, tableId, rowId) => { exports.findRow = async (ctx, db, tableId, rowId) => {
let row let row
// TODO remove special user case in future // TODO remove special user case in future

View File

@ -1,26 +0,0 @@
const { fullSearch, paginatedSearch } = require("./utils")
const CouchDB = require("../../../db")
const { outputProcessing } = require("../../../utilities/rowProcessor")
exports.rowSearch = async ctx => {
const appId = ctx.appId
const { tableId } = ctx.params
const db = new CouchDB(appId)
const { paginate, query, ...params } = ctx.request.body
params.tableId = tableId
let response
if (paginate) {
response = await paginatedSearch(appId, query, params)
} else {
response = await fullSearch(appId, query, params)
}
// Enrich search results with relationships
if (response.rows && response.rows.length) {
const table = await db.get(tableId)
response.rows = await outputProcessing(appId, table, response.rows)
}
ctx.body = response
}

View File

@ -23,7 +23,6 @@ const queryRoutes = require("./query")
const hostingRoutes = require("./hosting") const hostingRoutes = require("./hosting")
const backupRoutes = require("./backup") const backupRoutes = require("./backup")
const devRoutes = require("./dev") const devRoutes = require("./dev")
const searchRoutes = require("./search")
exports.mainRoutes = [ exports.mainRoutes = [
authRoutes, authRoutes,
@ -52,7 +51,6 @@ exports.mainRoutes = [
// this could be breaking as koa may recognise other routes as this // this could be breaking as koa may recognise other routes as this
tableRoutes, tableRoutes,
rowRoutes, rowRoutes,
searchRoutes,
] ]
exports.staticRoutes = staticRoutes exports.staticRoutes = staticRoutes

View File

@ -32,6 +32,13 @@ router
authorized(PermissionTypes.TABLE, PermissionLevels.READ), authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.find rowController.find
) )
.post(
"/api/:tableId/search",
paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.search
)
.post( .post(
"/api/:tableId/rows", "/api/:tableId/rows",
paramResource("tableId"), paramResource("tableId"),
@ -40,8 +47,8 @@ router
rowController.save rowController.save
) )
.patch( .patch(
"/api/:tableId/rows/:rowId", "/api/:tableId/rows",
paramSubResource("tableId", "rowId"), paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
rowController.patch rowController.patch
) )

View File

@ -1,19 +0,0 @@
const Router = require("@koa/router")
const controller = require("../controllers/search")
const {
PermissionTypes,
PermissionLevels,
} = require("@budibase/auth/permissions")
const authorized = require("../../middleware/authorized")
const { paramResource } = require("../../middleware/resourceId")
const router = Router()
router.post(
"/api/search/:tableId/rows",
paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
controller.rowSearch
)
module.exports = router

View File

@ -201,7 +201,7 @@ describe("/rows", () => {
const existing = await config.createRow() const existing = await config.createRow()
const res = await request const res = await request
.patch(`/api/${table._id}/rows/${existing._id}`) .patch(`/api/${table._id}/rows`)
.send({ .send({
_id: existing._id, _id: existing._id,
_rev: existing._rev, _rev: existing._rev,
@ -225,7 +225,7 @@ describe("/rows", () => {
it("should throw an error when given improper types", async () => { it("should throw an error when given improper types", async () => {
const existing = await config.createRow() const existing = await config.createRow()
await request await request
.patch(`/api/${table._id}/rows/${existing._id}`) .patch(`/api/${table._id}/rows`)
.send({ .send({
_id: existing._id, _id: existing._id,
_rev: existing._rev, _rev: existing._rev,