From 8f6e55e65bc12d604fc004f644c1475b640129b0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 23 Feb 2022 18:31:32 +0000 Subject: [PATCH] Adding controllers for row, query and applications public APIs. --- packages/backend-core/src/cache/user.js | 9 +-- packages/backend-core/src/db/utils.js | 1 + packages/server/specs/openapi.json | 74 +++++++++++++++++-- packages/server/specs/openapi.yaml | 53 +++++++++++-- packages/server/specs/resources/query.js | 40 ++++++++++ .../api/controllers/public/applications.js | 46 ++++++++++-- .../src/api/controllers/public/queries.js | 15 +++- .../server/src/api/controllers/public/rows.js | 22 +++++- .../src/api/controllers/public/utils.js | 47 ++++++++++++ .../server/src/api/routes/public/index.js | 2 +- .../server/src/api/routes/public/queries.js | 32 ++++++-- .../server/src/utilities/workerRequests.js | 2 +- 12 files changed, 310 insertions(+), 33 deletions(-) create mode 100644 packages/server/src/api/controllers/public/utils.js diff --git a/packages/backend-core/src/cache/user.js b/packages/backend-core/src/cache/user.js index 60a2d341a8..b10f854002 100644 --- a/packages/backend-core/src/cache/user.js +++ b/packages/backend-core/src/cache/user.js @@ -32,11 +32,10 @@ const populateFromDB = async (userId, tenantId) => { * @param {*} populateUser function to provide the user for re-caching. default to couch db * @returns */ -exports.getUser = async ( - userId, - tenantId = null, - populateUser = populateFromDB -) => { +exports.getUser = async (userId, tenantId = null, populateUser = null) => { + if (!populateUser) { + populateUser = populateFromDB + } if (!tenantId) { try { tenantId = getTenantId() diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js index 957d5332c8..6d6f9a782b 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -68,6 +68,7 @@ function getDocParams(docType, docId = null, otherProps = {}) { endkey: `${docType}${SEPARATOR}${docId}${UNICODE_MAX}`, } } +exports.getDocParams = getDocParams /** * Generates a new workspace ID. diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index 6bf9f5a78f..9bb37b129e 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -335,6 +335,42 @@ ] } }, + "restResponse": { + "value": { + "data": [ + { + "value": "" + } + ], + "pagination": { + "cursor": "2" + }, + "raw": "", + "headers": { + "content-type": "text/html; charset=ISO-8859-1" + } + } + }, + "sqlResponse": { + "value": { + "data": [ + { + "personid": 1, + "lastname": "Hughes", + "firstname": "Mike", + "address": "123 Fake Street", + "city": "Belfast" + }, + { + "personid": 2, + "lastname": "Smith", + "firstname": "John", + "address": "64 Updown Road", + "city": "Dublin" + } + ] + } + }, "user": { "value": { "user": { @@ -1144,14 +1180,42 @@ "content": { "application/json": { "schema": { - "type": "array", - "items": { - "type": "object" + "type": "object", + "properties": { + "data": { + "type": "array", + "description": "The data retrieved from the query.", + "items": { + "type": "object", + "description": "The structure of the returned data will be an object, if it is just a string then this will be an object containing \"value\"." + } + }, + "pagination": { + "type": "object", + "description": "For supported query types this returns pagination information.", + "properties": { + "cursor": { + "type": "string", + "description": "The pagination cursor location." + } + } + }, + "raw": { + "type": "string", + "description": "The raw query response." + }, + "headers": { + "type": "object", + "description": "For REST queries the headers in the response will be returned here." + } } }, "examples": { - "query": { - "$ref": "#/components/examples/query" + "REST": { + "$ref": "#/components/examples/restResponse" + }, + "SQL": { + "$ref": "#/components/examples/sqlResponse" } } } diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index a38e91aa39..1fad6ac6b4 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -242,6 +242,28 @@ components: type: string transformer: return data readable: true + restResponse: + value: + data: + - value: + pagination: + cursor: "2" + raw: + headers: + content-type: text/html; charset=ISO-8859-1 + sqlResponse: + value: + data: + - personid: 1 + lastname: Hughes + firstname: Mike + address: 123 Fake Street + city: Belfast + - personid: 2 + lastname: Smith + firstname: John + address: 64 Updown Road + city: Dublin user: value: user: @@ -804,12 +826,33 @@ paths: content: application/json: schema: - type: array - items: - type: object + type: object + properties: + data: + type: array + description: The data retrieved from the query. + items: + type: object + description: The structure of the returned data will be an object, if it is just + a string then this will be an object containing "value". + pagination: + type: object + description: For supported query types this returns pagination information. + properties: + cursor: + type: string + description: The pagination cursor location. + raw: + type: string + description: The raw query response. + headers: + type: object + description: For REST queries the headers in the response will be returned here. examples: - query: - $ref: "#/components/examples/query" + REST: + $ref: "#/components/examples/restResponse" + SQL: + $ref: "#/components/examples/sqlResponse" "/tables/{tableId}/rows/search": post: summary: Used to search for rows within a table. diff --git a/packages/server/specs/resources/query.js b/packages/server/specs/resources/query.js index c1bbaf0185..c1f9f1ff83 100644 --- a/packages/server/specs/resources/query.js +++ b/packages/server/specs/resources/query.js @@ -36,6 +36,44 @@ const query = { readable: true, } +const restResponse = { + value: { + data: [ + { + value: "", + }, + ], + pagination: { + cursor: "2", + }, + raw: "", + headers: { + "content-type": "text/html; charset=ISO-8859-1", + }, + }, +} + +const sqlResponse = { + value: { + data: [ + { + personid: 1, + lastname: "Hughes", + firstname: "Mike", + address: "123 Fake Street", + city: "Belfast", + }, + { + personid: 2, + lastname: "Smith", + firstname: "John", + address: "64 Updown Road", + city: "Dublin", + }, + ], + }, +} + const querySchema = object({}) module.exports = new Resource() @@ -50,6 +88,8 @@ module.exports = new Resource() queries: [query], }, }, + restResponse, + sqlResponse, }) .setSchemas({ query: querySchema, diff --git a/packages/server/src/api/controllers/public/applications.js b/packages/server/src/api/controllers/public/applications.js index 459e314bbf..faf534af2b 100644 --- a/packages/server/src/api/controllers/public/applications.js +++ b/packages/server/src/api/controllers/public/applications.js @@ -1,9 +1,45 @@ -exports.search = () => {} +const { search } = require("./utils") +const { getAllApps } = require("@budibase/backend-core/db") +const { updateAppId } = require("@budibase/backend-core/context") +const controller = require("../application") -exports.create = () => {} +async function setResponseApp(ctx) { + if (ctx.body && ctx.body.appId && (!ctx.params || !ctx.params.appId)) { + ctx.params = { appId: ctx.body.appId } + } + await controller.fetchAppPackage(ctx) +} -exports.read = () => {} +exports.search = async ctx => { + const { name } = ctx.request.body + const apps = await getAllApps({ all: true }) + ctx.body = { + applications: search(apps, "name", name), + } +} -exports.update = () => {} +exports.create = async ctx => { + await controller.create(ctx) + await setResponseApp(ctx) +} -exports.delete = () => {} +exports.read = async ctx => { + updateAppId(ctx.params.appId) + await setResponseApp(ctx) +} + +exports.update = async ctx => { + updateAppId(ctx.params.appId) + await controller.update(ctx) + await setResponseApp(ctx) +} + +exports.delete = async ctx => { + updateAppId(ctx.params.appId) + // get the app before deleting it + await setResponseApp(ctx) + const body = ctx.body + await controller.delete(ctx) + // overwrite the body again + ctx.body = body +} diff --git a/packages/server/src/api/controllers/public/queries.js b/packages/server/src/api/controllers/public/queries.js index 09775340aa..b35899f77c 100644 --- a/packages/server/src/api/controllers/public/queries.js +++ b/packages/server/src/api/controllers/public/queries.js @@ -1,3 +1,14 @@ -exports.search = () => {} +const { searchDocs } = require("./utils") +const { DocumentTypes } = require("../../../db/utils") +const queryController = require("../query") -exports.execute = () => {} +exports.search = async ctx => { + const { name } = ctx.request.body + ctx.body = { + queries: await searchDocs(DocumentTypes.QUERY, "name", name), + } +} + +exports.execute = async ctx => { + await queryController.executeV2(ctx) +} diff --git a/packages/server/src/api/controllers/public/rows.js b/packages/server/src/api/controllers/public/rows.js index f0d6577162..c642b882ca 100644 --- a/packages/server/src/api/controllers/public/rows.js +++ b/packages/server/src/api/controllers/public/rows.js @@ -1,4 +1,5 @@ const rowController = require("../row") +const { addRev } = require("./utils") // makes sure that the user doesn't need to pass in the type, tableId or _id params for // the call to be correct @@ -22,14 +23,27 @@ exports.search = async ctx => { await rowController.search(ctx) } -exports.create = ctx => { +exports.create = async ctx => { ctx.request.body = fixRow(ctx.request.body, ctx.params) + await rowController.save(ctx) + ctx.body = { row: ctx.body } } -exports.read = () => {} +exports.read = async ctx => { + await rowController.find(ctx) + ctx.body = { row: ctx.body } +} exports.update = async ctx => { - ctx.request.body = fixRow(ctx.request.body, ctx.params) + ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params)) + ctx.body = { row: ctx.body } } -exports.delete = () => {} +exports.delete = async ctx => { + // set the body as expected, with the _id and _rev fields + ctx.request.body = await addRev({ _id: ctx.params.rowId }) + await rowController.destroy(ctx) + // destroy controller doesn't currently return the row as the body, need to adjust this + // in the public API to be correct + ctx.body = { row: ctx.row } +} diff --git a/packages/server/src/api/controllers/public/utils.js b/packages/server/src/api/controllers/public/utils.js new file mode 100644 index 0000000000..7afd1332e1 --- /dev/null +++ b/packages/server/src/api/controllers/public/utils.js @@ -0,0 +1,47 @@ +const { getAppDB } = require("@budibase/backend-core/context") +const { getDocParams } = require("@budibase/backend-core/db") + +exports.addRev = async body => { + if (!body._id) { + return body + } + const db = getAppDB() + const dbDoc = await db.get(body._id) + body._rev = dbDoc._rev + return body +} + +exports.search = (docs, key, value) => { + if (!value || typeof value !== "string") { + return docs + } + value = value.toLowerCase() + const filtered = [] + for (let doc of docs) { + if (typeof doc[key] !== "string") { + continue + } + const toTest = doc[key].toLowerCase() + if (toTest.startsWith(value)) { + filtered.push(doc) + } + } + return filtered +} + +/** + * Performs a case insensitive search on a document type, using the + * provided key and value. This will be a string based search, + * using the startsWith function. + */ +exports.searchDocs = async (docType, key, value) => { + const db = getAppDB() + const docs = ( + await db.allDocs( + getDocParams(docType, null, { + include_docs: true, + }) + ) + ).rows.map(row => row.doc) + return exports.search(docs, key, value) +} diff --git a/packages/server/src/api/routes/public/index.js b/packages/server/src/api/routes/public/index.js index 1ec311b855..6523c8e448 100644 --- a/packages/server/src/api/routes/public/index.js +++ b/packages/server/src/api/routes/public/index.js @@ -46,10 +46,10 @@ function applyRoutes(endpoints, permType, resource, subResource = null) { addToRouter(endpoints.write) } -applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId") applyRoutes(appEndpoints, PermissionTypes.APP, "appId") applyRoutes(tableEndpoints, PermissionTypes.TABLE, "tableId") applyRoutes(userEndpoints, PermissionTypes.USER, "userId") applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId") +//applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId") module.exports = publicRouter diff --git a/packages/server/src/api/routes/public/queries.js b/packages/server/src/api/routes/public/queries.js index b44edf9b44..edf4cbf668 100644 --- a/packages/server/src/api/routes/public/queries.js +++ b/packages/server/src/api/routes/public/queries.js @@ -53,12 +53,34 @@ read.push(new Endpoint("post", "/queries/search", controller.search)) * content: * application/json: * schema: - * type: array - * items: - * type: object + * type: object + * properties: + * data: + * type: array + * description: The data retrieved from the query. + * items: + * type: object + * description: The structure of the returned data will be an object, + * if it is just a string then this will be an object containing "value". + * pagination: + * type: object + * description: For supported query types this returns pagination information. + * properties: + * cursor: + * type: string + * description: The pagination cursor location. + * raw: + * type: string + * description: The raw query response. + * headers: + * type: object + * description: For REST queries the headers in the response will be returned here. * examples: - * query: - * $ref: '#/components/examples/query' + * REST: + * $ref: '#/components/examples/restResponse' + * SQL: + * $ref: '#/components/examples/sqlResponse' + * */ write.push(new Endpoint("post", "/queries/:queryId", controller.execute)) diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index 5c333a48ca..8c81ad161f 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -26,7 +26,7 @@ function request(ctx, request) { delete request.body } if (ctx && ctx.headers) { - request.headers.cookie = ctx.headers.cookie + request.headers = ctx.headers } return request }