diff --git a/packages/client/src/api/rows.js b/packages/client/src/api/rows.js index b1519801da..76bd5c214b 100644 --- a/packages/client/src/api/rows.js +++ b/packages/client/src/api/rows.js @@ -44,7 +44,7 @@ export const updateRow = async row => { return } const res = await API.patch({ - url: `/api/${row.tableId}/rows/${row._id}`, + url: `/api/${row.tableId}/rows`, body: row, }) res.error diff --git a/packages/client/src/api/tables.js b/packages/client/src/api/tables.js index 6d9457cacb..09f77de6ee 100644 --- a/packages/client/src/api/tables.js +++ b/packages/client/src/api/tables.js @@ -45,7 +45,7 @@ export const searchTable = async ({ } } const res = await API.post({ - url: `/api/search/${tableId}/rows`, + url: `/api/${tableId}/search`, body: { query, bookmark, diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index ed1c31d531..e79834b7c5 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -7,6 +7,7 @@ const { } = require("../../db/utils") const { integrations } = require("../../integrations") const plusIntegrations = require("../../integrations/plus") +const { makeExternalQuery } = require("./row/utils") exports.fetch = async function (ctx) { const database = new CouchDB(ctx.appId) @@ -77,15 +78,9 @@ exports.find = async function (ctx) { // dynamic query functionality exports.query = async function (ctx) { const queryJson = ctx.request.body - const datasourceId = queryJson.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) - ctx.body = await integration.query(queryJson) - } else { - ctx.throw(400, "Datasource does not support query.") + try { + ctx.body = await makeExternalQuery(ctx.appId, queryJson) + } catch (err) { + ctx.throw(400, err) } } diff --git a/packages/server/src/api/controllers/row/external.js b/packages/server/src/api/controllers/row/external.js index 03de13a43a..11c11bf9bd 100644 --- a/packages/server/src/api/controllers/row/external.js +++ b/packages/server/src/api/controllers/row/external.js @@ -1,38 +1,115 @@ 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) => { - 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) => { - 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) => { - ctx.body = {} + // TODO: don't know what this does for external } exports.fetchTableRows = async (ctx) => { - ctx.body = {} + // TODO: this is a basic read? } exports.find = async (ctx) => { - ctx.body = {} + // TODO: single find } 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) => { - 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) => { - ctx.body = {} + // can't validate external right now - maybe in future + ctx.body = { valid: true } } exports.fetchEnrichedRow = async (ctx) => { // TODO: should this join? + const appId = ctx.appId ctx.body = {} } diff --git a/packages/server/src/api/controllers/row/index.js b/packages/server/src/api/controllers/row/index.js index 5f4fb10a78..c077a69705 100644 --- a/packages/server/src/api/controllers/row/index.js +++ b/packages/server/src/api/controllers/row/index.js @@ -95,6 +95,16 @@ exports.destroy = async function (ctx) { 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) { const tableId = getTableId(ctx) try { diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 5ce2767257..cfe785058d 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -15,6 +15,7 @@ const { const { FieldTypes } = require("../../../constants") const { isEqual } = require("lodash") const { validate, findRow } = require("./utils") +const { fullSearch, paginatedSearch } = require("./internalSearch") const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` @@ -32,7 +33,7 @@ exports.patch = async ctx => { const isUserTable = tableId === InternalTables.USER_METADATA let dbRow try { - dbRow = await db.get(ctx.params.rowId) + dbRow = await db.get(inputs._id) } catch (err) { if (isUserTable) { // 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 (inputs._id && inputs._rev) { - ctx.params.rowId = inputs._id return exports.patch(ctx) } @@ -281,6 +281,29 @@ exports.bulkDestroy = async ctx => { 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) => { return validate({ appId: ctx.appId, diff --git a/packages/server/src/api/controllers/search/utils.js b/packages/server/src/api/controllers/row/internalSearch.js similarity index 100% rename from packages/server/src/api/controllers/search/utils.js rename to packages/server/src/api/controllers/row/internalSearch.js diff --git a/packages/server/src/api/controllers/row/utils.js b/packages/server/src/api/controllers/row/utils.js index 82fa4bf555..a26ef85988 100644 --- a/packages/server/src/api/controllers/row/utils.js +++ b/packages/server/src/api/controllers/row/utils.js @@ -4,6 +4,7 @@ const CouchDB = require("../../../db") const { InternalTables } = require("../../../db/utils") const userController = require("../user") const { FieldTypes } = require("../../../constants") +const { integrations } = require("../../../integrations") validateJs.extend(validateJs.validators.datetime, { 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) => { let row // TODO remove special user case in future diff --git a/packages/server/src/api/controllers/search/index.js b/packages/server/src/api/controllers/search/index.js deleted file mode 100644 index ede0556e18..0000000000 --- a/packages/server/src/api/controllers/search/index.js +++ /dev/null @@ -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 -} diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 5ea3ddacef..0b09a78bb8 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -23,7 +23,6 @@ const queryRoutes = require("./query") const hostingRoutes = require("./hosting") const backupRoutes = require("./backup") const devRoutes = require("./dev") -const searchRoutes = require("./search") exports.mainRoutes = [ authRoutes, @@ -52,7 +51,6 @@ exports.mainRoutes = [ // this could be breaking as koa may recognise other routes as this tableRoutes, rowRoutes, - searchRoutes, ] exports.staticRoutes = staticRoutes diff --git a/packages/server/src/api/routes/row.js b/packages/server/src/api/routes/row.js index b155552776..dc60a14112 100644 --- a/packages/server/src/api/routes/row.js +++ b/packages/server/src/api/routes/row.js @@ -32,6 +32,13 @@ router authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.find ) + .post( + "/api/:tableId/search", + paramResource("tableId"), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), + rowController.search + ) + .post( "/api/:tableId/rows", paramResource("tableId"), @@ -40,8 +47,8 @@ router rowController.save ) .patch( - "/api/:tableId/rows/:rowId", - paramSubResource("tableId", "rowId"), + "/api/:tableId/rows", + paramResource("tableId"), authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), rowController.patch ) diff --git a/packages/server/src/api/routes/search.js b/packages/server/src/api/routes/search.js deleted file mode 100644 index bd513fd348..0000000000 --- a/packages/server/src/api/routes/search.js +++ /dev/null @@ -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 diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 60796e56f1..69b1031a99 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -201,7 +201,7 @@ describe("/rows", () => { const existing = await config.createRow() const res = await request - .patch(`/api/${table._id}/rows/${existing._id}`) + .patch(`/api/${table._id}/rows`) .send({ _id: existing._id, _rev: existing._rev, @@ -225,7 +225,7 @@ describe("/rows", () => { it("should throw an error when given improper types", async () => { const existing = await config.createRow() await request - .patch(`/api/${table._id}/rows/${existing._id}`) + .patch(`/api/${table._id}/rows`) .send({ _id: existing._id, _rev: existing._rev,