Some of the functionality required for external SQL rows API.
This commit is contained in:
parent
bf47a66442
commit
564cdd8102
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue