diff --git a/packages/server/specs/resources/application.js b/packages/server/specs/resources/application.js index d3d063b8de..d753a6a45b 100644 --- a/packages/server/specs/resources/application.js +++ b/packages/server/specs/resources/application.js @@ -15,7 +15,15 @@ const application = { lockedBy: userResource.getExamples().user.value.user, } -const applicationSchema = object({}) +const applicationSchema = object({ + name: { + type: "string", + required: true, + }, + url: { + type: "string", + }, +}) module.exports = new Resource() .setExamples({ diff --git a/packages/server/src/api/controllers/public/applications.js b/packages/server/src/api/controllers/public/applications.ts similarity index 52% rename from packages/server/src/api/controllers/public/applications.js rename to packages/server/src/api/controllers/public/applications.ts index c2d63f5ef7..8c65ea122a 100644 --- a/packages/server/src/api/controllers/public/applications.js +++ b/packages/server/src/api/controllers/public/applications.ts @@ -1,40 +1,52 @@ -const { search } = require("./utils") const { getAllApps } = require("@budibase/backend-core/db") const { updateAppId } = require("@budibase/backend-core/context") -const controller = require("../application") +import { search as stringSearch } from "./utils" +import { default as controller } from "../application" +import { Application } from "../../../definitions/common" -async function setResponseApp(ctx) { +function fixAppID(app: Application, params: any) { + if (!params) { + return app + } + if (!app._id && params.appId) { + app._id = params.appId + } + return app +} + +async function setResponseApp(ctx: any) { if (ctx.body && ctx.body.appId && (!ctx.params || !ctx.params.appId)) { ctx.params = { appId: ctx.body.appId } } await controller.fetchAppPackage(ctx) } -exports.search = async ctx => { +export async function search(ctx: any) { const { name } = ctx.request.body const apps = await getAllApps({ all: true }) ctx.body = { - applications: search(apps, name), + applications: stringSearch(apps, name), } } -exports.create = async ctx => { +export async function create(ctx: any) { await controller.create(ctx) await setResponseApp(ctx) } -exports.read = async ctx => { +export async function read(ctx: any) { updateAppId(ctx.params.appId) await setResponseApp(ctx) } -exports.update = async ctx => { +export async function update(ctx: any) { + ctx.request.body = fixAppID(ctx.request.body, ctx.params) updateAppId(ctx.params.appId) await controller.update(ctx) await setResponseApp(ctx) } -exports.delete = async ctx => { +export async function destroy(ctx: any) { updateAppId(ctx.params.appId) // get the app before deleting it await setResponseApp(ctx) @@ -43,3 +55,11 @@ exports.delete = async ctx => { // overwrite the body again ctx.body = body } + +export default { + create, + update, + read, + destroy, + search, +} diff --git a/packages/server/src/api/controllers/public/queries.js b/packages/server/src/api/controllers/public/queries.js deleted file mode 100644 index bf7c73e27f..0000000000 --- a/packages/server/src/api/controllers/public/queries.js +++ /dev/null @@ -1,14 +0,0 @@ -const { search } = require("./utils") -const queryController = require("../query") - -exports.search = async ctx => { - await queryController.fetch(ctx) - const { name } = ctx.request.body - ctx.body = { - queries: search(ctx.body, name), - } -} - -exports.execute = async ctx => { - await queryController.executeV2(ctx) -} diff --git a/packages/server/src/api/controllers/public/queries.ts b/packages/server/src/api/controllers/public/queries.ts new file mode 100644 index 0000000000..6cefa6c7d0 --- /dev/null +++ b/packages/server/src/api/controllers/public/queries.ts @@ -0,0 +1,19 @@ +import { search as stringSearch } from "./utils" +import { default as queryController } from "../query" + +export async function search(ctx: any) { + await queryController.fetch(ctx) + const { name } = ctx.request.body + ctx.body = { + queries: stringSearch(ctx.body, name), + } +} + +export async function execute(ctx: any) { + await queryController.executeV2(ctx) +} + +export default { + search, + execute, +} diff --git a/packages/server/src/api/controllers/public/rows.js b/packages/server/src/api/controllers/public/rows.ts similarity index 56% rename from packages/server/src/api/controllers/public/rows.js rename to packages/server/src/api/controllers/public/rows.ts index ff0f6b2380..7a770dabd1 100644 --- a/packages/server/src/api/controllers/public/rows.js +++ b/packages/server/src/api/controllers/public/rows.ts @@ -1,9 +1,10 @@ -const rowController = require("../row") -const { addRev } = require("./utils") +import { default as rowController } from "../row" +import { addRev } from "./utils" +import { Row } from "../../../definitions/common" // makes sure that the user doesn't need to pass in the type, tableId or _id params for // the call to be correct -function fixRow(row, params) { +function fixRow(row: Row, params: any) { if (!params || !row) { return row } @@ -19,27 +20,41 @@ function fixRow(row, params) { return row } -exports.search = async ctx => { +export async function search(ctx: any) { + let { sort, paginate, bookmark, limit, query } = ctx.request.body + // update the body to the correct format of the internal search + if (!sort) { + sort = {} + } + ctx.request.body = { + sort: sort.column, + sortType: sort.type, + sortOrder: sort.order, + bookmark, + paginate, + limit, + query, + } await rowController.search(ctx) } -exports.create = async ctx => { +export async function create(ctx: any) { ctx.request.body = fixRow(ctx.request.body, ctx.params) await rowController.save(ctx) ctx.body = { row: ctx.body } } -exports.read = async ctx => { +export async function read(ctx: any) { await rowController.find(ctx) ctx.body = { row: ctx.body } } -exports.update = async ctx => { +export async function update(ctx: any) { ctx.request.body = await addRev(fixRow(ctx.request.body, ctx.params.tableId)) ctx.body = { row: ctx.body } } -exports.delete = async ctx => { +export async function destroy(ctx: any) { // set the body as expected, with the _id and _rev fields ctx.request.body = await addRev(fixRow({}, ctx.params.tableId)) await rowController.destroy(ctx) @@ -47,3 +62,11 @@ exports.delete = async ctx => { // in the public API to be correct ctx.body = { row: ctx.row } } + +export default { + create, + read, + update, + destroy, + search, +} diff --git a/packages/server/src/api/controllers/public/tables.js b/packages/server/src/api/controllers/public/tables.ts similarity index 50% rename from packages/server/src/api/controllers/public/tables.js rename to packages/server/src/api/controllers/public/tables.ts index 7e70a58ed8..92c5489784 100644 --- a/packages/server/src/api/controllers/public/tables.js +++ b/packages/server/src/api/controllers/public/tables.ts @@ -1,31 +1,39 @@ -const { search, addRev } = require("./utils") -const controller = require("../table") +import { search as stringSearch, addRev } from "./utils" +import { default as controller } from "../table" -exports.search = async ctx => { +export async function search(ctx: any) { const { name } = ctx.request.body await controller.fetch(ctx) ctx.body = { - tables: search(ctx.body, name), + tables: stringSearch(ctx.body, name), } } -exports.create = async ctx => { +export async function create(ctx: any) { await controller.save(ctx) ctx.body = { table: ctx.body } } -exports.read = async ctx => { +export async function read(ctx: any) { await controller.find(ctx) ctx.body = { table: ctx.body } } -exports.update = async ctx => { +export async function update(ctx: any) { ctx.request.body = await addRev(ctx.request.body, ctx.params.tableId) await controller.save(ctx) ctx.body = { table: ctx.body } } -exports.delete = async ctx => { +export async function destroy(ctx: any) { await controller.destroy(ctx) ctx.body = { table: ctx.table } } + +export default { + create, + read, + update, + destroy, + search, +} diff --git a/packages/server/src/api/controllers/public/users.js b/packages/server/src/api/controllers/public/users.js deleted file mode 100644 index 459e314bbf..0000000000 --- a/packages/server/src/api/controllers/public/users.js +++ /dev/null @@ -1,9 +0,0 @@ -exports.search = () => {} - -exports.create = () => {} - -exports.read = () => {} - -exports.update = () => {} - -exports.delete = () => {} diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts new file mode 100644 index 0000000000..dd65a81a22 --- /dev/null +++ b/packages/server/src/api/controllers/public/users.ts @@ -0,0 +1,17 @@ +export async function search() {} + +export async function create() {} + +export async function read() {} + +export async function update() {} + +export async function destroy() {} + +export default { + create, + read, + update, + destroy, + search, +} diff --git a/packages/server/src/api/controllers/public/utils.js b/packages/server/src/api/controllers/public/utils.ts similarity index 71% rename from packages/server/src/api/controllers/public/utils.js rename to packages/server/src/api/controllers/public/utils.ts index e17c0d0176..7bdd865aa4 100644 --- a/packages/server/src/api/controllers/public/utils.js +++ b/packages/server/src/api/controllers/public/utils.ts @@ -1,8 +1,11 @@ const { getAppDB } = require("@budibase/backend-core/context") -const { isExternalTable } = require("../../../integrations/utils") +import { isExternalTable } from "../../../integrations/utils" -exports.addRev = async (body, tableId) => { - if (!body._id || isExternalTable(tableId)) { +export async function addRev( + body: { _id?: string; _rev?: string }, + tableId?: string +) { + if (!body._id || (tableId && isExternalTable(tableId))) { return body } const db = getAppDB() @@ -16,7 +19,7 @@ exports.addRev = async (body, tableId) => { * provided key and value. This will be a string based search, using the * startsWith function. */ -exports.search = (docs, value, key = "name") => { +export function search(docs: any[], value: any, key = "name") { if (!value || typeof value !== "string") { return docs } diff --git a/packages/server/src/api/routes/public/applications.js b/packages/server/src/api/routes/public/applications.ts similarity index 94% rename from packages/server/src/api/routes/public/applications.js rename to packages/server/src/api/routes/public/applications.ts index 80b69c435f..2c1c5bde9b 100644 --- a/packages/server/src/api/routes/public/applications.js +++ b/packages/server/src/api/routes/public/applications.ts @@ -1,6 +1,6 @@ -const controller = require("../../controllers/public/applications") -const Endpoint = require("./utils/Endpoint") -const { nameValidator } = require("../utils/validators") +import controller from "../../controllers/public/applications" +import Endpoint from "./utils/Endpoint" +const { nameValidator, applicationValidator } = require("../utils/validators") const read = [], write = [] @@ -118,7 +118,7 @@ write.push(new Endpoint("put", "/applications/:appId", controller.update)) * application: * $ref: '#/components/examples/application' */ -write.push(new Endpoint("delete", "/applications/:appId", controller.delete)) +write.push(new Endpoint("delete", "/applications/:appId", controller.destroy)) /** * @openapi @@ -142,4 +142,4 @@ write.push(new Endpoint("delete", "/applications/:appId", controller.delete)) */ read.push(new Endpoint("get", "/applications/:appId", controller.read)) -module.exports = { read, write } +export default { read, write } diff --git a/packages/server/src/api/routes/public/index.js b/packages/server/src/api/routes/public/index.ts similarity index 60% rename from packages/server/src/api/routes/public/index.js rename to packages/server/src/api/routes/public/index.ts index 79162ab66b..6930ce1aae 100644 --- a/packages/server/src/api/routes/public/index.js +++ b/packages/server/src/api/routes/public/index.ts @@ -1,15 +1,13 @@ -const appEndpoints = require("./applications") -const queryEndpoints = require("./queries") -const tableEndpoints = require("./tables") -const rowEndpoints = require("./rows") -const userEndpoints = require("./users") +import appEndpoints from "./applications" +import queryEndpoints from "./queries" +import tableEndpoints from "./tables" +import rowEndpoints from "./rows" +import userEndpoints from "./users" +import usage from "../../../middleware/usageQuota" +import authorized from "../../../middleware/authorized" +import { paramResource, paramSubResource } from "../../../middleware/resourceId" +import { CtxFn } from "./utils/Endpoint" const Router = require("@koa/router") -const usage = require("../../../middleware/usageQuota") -const authorized = require("../../../middleware/authorized") -const { - paramResource, - paramSubResource, -} = require("../../../middleware/resourceId") const { PermissionLevels, PermissionTypes, @@ -21,7 +19,7 @@ const publicRouter = new Router({ prefix: PREFIX, }) -function addMiddleware(endpoints, middleware) { +function addMiddleware(endpoints: any, middleware: CtxFn) { if (!Array.isArray(endpoints)) { endpoints = [endpoints] } @@ -30,13 +28,18 @@ function addMiddleware(endpoints, middleware) { } } -function addToRouter(endpoints) { +function addToRouter(endpoints: any) { for (let endpoint of endpoints) { endpoint.apply(publicRouter) } } -function applyRoutes(endpoints, permType, resource, subResource = null) { +function applyRoutes( + endpoints: any, + permType: string, + resource: string, + subResource?: string +) { const paramMiddleware = subResource ? paramSubResource(resource, subResource) : paramResource(resource) @@ -53,6 +56,7 @@ 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") +// needs to be applied last for routing purposes, don't override other endpoints +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.ts similarity index 93% rename from packages/server/src/api/routes/public/queries.js rename to packages/server/src/api/routes/public/queries.ts index 4650c5a551..e1d7a3e634 100644 --- a/packages/server/src/api/routes/public/queries.js +++ b/packages/server/src/api/routes/public/queries.ts @@ -1,6 +1,6 @@ -const controller = require("../../controllers/public/queries") -const Endpoint = require("./utils/Endpoint") -const { nameValidator } = require("../utils/validators") +import controller from "../../controllers/public/queries" +import Endpoint from "./utils/Endpoint" +import { nameValidator } from "../utils/validators" const read = [], write = [] @@ -89,4 +89,4 @@ read.push( */ write.push(new Endpoint("post", "/queries/:queryId", controller.execute)) -module.exports = { read, write } +export default { read, write } diff --git a/packages/server/src/api/routes/public/rows.js b/packages/server/src/api/routes/public/rows.ts similarity index 96% rename from packages/server/src/api/routes/public/rows.js rename to packages/server/src/api/routes/public/rows.ts index e5214a1959..8b349b2634 100644 --- a/packages/server/src/api/routes/public/rows.js +++ b/packages/server/src/api/routes/public/rows.ts @@ -1,5 +1,6 @@ -const controller = require("../../controllers/public/rows") -const Endpoint = require("./utils/Endpoint") +import controller from "../../controllers/public/rows" +import Endpoint from "./utils/Endpoint" +import { externalSearchValidator } from "../utils/validators" const read = [], write = [] @@ -121,7 +122,11 @@ const read = [], * $ref: '#/components/examples/rows' */ read.push( - new Endpoint("post", "/tables/:tableId/rows/search", controller.search) + new Endpoint( + "post", + "/tables/:tableId/rows/search", + controller.search + ).addMiddleware(externalSearchValidator()) ) /** @@ -215,7 +220,7 @@ write.push( * $ref: '#/components/examples/row' */ write.push( - new Endpoint("delete", "/tables/:tableId/rows/:rowId", controller.delete) + new Endpoint("delete", "/tables/:tableId/rows/:rowId", controller.destroy) ) /** @@ -242,4 +247,4 @@ write.push( */ read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read)) -module.exports = { read, write } +export default { read, write } diff --git a/packages/server/src/api/routes/public/tables.js b/packages/server/src/api/routes/public/tables.ts similarity index 94% rename from packages/server/src/api/routes/public/tables.js rename to packages/server/src/api/routes/public/tables.ts index 666b7275d4..242273dc92 100644 --- a/packages/server/src/api/routes/public/tables.js +++ b/packages/server/src/api/routes/public/tables.ts @@ -1,6 +1,6 @@ -const controller = require("../../controllers/public/tables") -const Endpoint = require("./utils/Endpoint") -const { tableValidator, nameValidator } = require("../utils/validators") +import controller from "../../controllers/public/tables" +import Endpoint from "./utils/Endpoint" +import { tableValidator, nameValidator } from "../utils/validators" const read = [], write = [] @@ -133,7 +133,7 @@ write.push( * table: * $ref: '#/components/examples/table' */ -write.push(new Endpoint("delete", "/tables/:tableId", controller.delete)) +write.push(new Endpoint("delete", "/tables/:tableId", controller.destroy)) /** * @openapi @@ -158,4 +158,4 @@ write.push(new Endpoint("delete", "/tables/:tableId", controller.delete)) */ read.push(new Endpoint("get", "/tables/:tableId", controller.read)) -module.exports = { read, write } +export default { read, write } diff --git a/packages/server/src/api/routes/public/users.js b/packages/server/src/api/routes/public/users.ts similarity index 94% rename from packages/server/src/api/routes/public/users.js rename to packages/server/src/api/routes/public/users.ts index cc2c1cb430..17a98e731e 100644 --- a/packages/server/src/api/routes/public/users.js +++ b/packages/server/src/api/routes/public/users.ts @@ -1,6 +1,6 @@ -const controller = require("../../controllers/public/users") -const Endpoint = require("./utils/Endpoint") -const { nameValidator } = require("../utils/validators") +import controller from "../../controllers/public/users" +import Endpoint from "./utils/Endpoint" +import { nameValidator } from "../utils/validators" const read = [], write = [] @@ -117,7 +117,7 @@ write.push(new Endpoint("put", "/users/:userId", controller.update)) * user: * $ref: '#/components/examples/user' */ -write.push(new Endpoint("delete", "/users/:userId", controller.delete)) +write.push(new Endpoint("delete", "/users/:userId", controller.destroy)) /** * @openapi @@ -142,4 +142,4 @@ write.push(new Endpoint("delete", "/users/:userId", controller.delete)) */ read.push(new Endpoint("get", "/users/:userId", controller.read)) -module.exports = { read, write } +export default { read, write } diff --git a/packages/server/src/api/routes/public/utils/Endpoint.js b/packages/server/src/api/routes/public/utils/Endpoint.ts similarity index 56% rename from packages/server/src/api/routes/public/utils/Endpoint.js rename to packages/server/src/api/routes/public/utils/Endpoint.ts index d163db847b..89b63fb77b 100644 --- a/packages/server/src/api/routes/public/utils/Endpoint.js +++ b/packages/server/src/api/routes/public/utils/Endpoint.ts @@ -1,24 +1,34 @@ +import Router from "koa-router" + +export type CtxFn = (ctx: any) => void + class Endpoint { - constructor(method, url, controller) { + method: string + url: string + controller: CtxFn + middlewares: CtxFn[] + + constructor(method: string, url: string, controller: CtxFn) { this.method = method this.url = url this.controller = controller this.middlewares = [] } - addMiddleware(middleware) { + addMiddleware(middleware: CtxFn) { this.middlewares.push(middleware) return this } - apply(router) { + apply(router: Router) { const method = this.method, url = this.url const middlewares = this.middlewares, controller = this.controller const params = [url, ...middlewares, controller] + // @ts-ignore router[method](...params) } } -module.exports = Endpoint +export default Endpoint diff --git a/packages/server/src/api/routes/row.js b/packages/server/src/api/routes/row.js index ef314ad0a4..2ceddc779a 100644 --- a/packages/server/src/api/routes/row.js +++ b/packages/server/src/api/routes/row.js @@ -10,6 +10,7 @@ const { PermissionLevels, PermissionTypes, } = require("@budibase/backend-core/permissions") +const { internalSearchValidator } = require("./utils/validators") const router = Router() @@ -138,6 +139,7 @@ router */ .post( "/api/:tableId/search", + internalSearchValidator(), paramResource("tableId"), authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.search diff --git a/packages/server/src/api/routes/utils/validators.js b/packages/server/src/api/routes/utils/validators.js index 794f93b70e..4315fba53c 100644 --- a/packages/server/src/api/routes/utils/validators.js +++ b/packages/server/src/api/routes/utils/validators.js @@ -47,42 +47,49 @@ exports.datasourceValidator = () => { }).unknown(true)) } -/** - * * { - * "tableId": "ta_70260ff0b85c467ca74364aefc46f26d", - * "query": { - * "string": {}, - * "fuzzy": {}, - * "range": { - * "columnName": { - * "high": 20, - * "low": 10, - * } - * }, - * "equal": { - * "columnName": "someValue" - * }, - * "notEqual": {}, - * "empty": {}, - * "notEmpty": {}, - * "oneOf": { - * "columnName": ["value"] - * } - * }, - * "limit": 10, - * "sort": "name", - * "sortOrder": "descending", - * "sortType": "string", - * "paginate": true - * } - */ -exports.searchValidator = () => { +function filterObject() { + return Joi.object({ + string: Joi.object().optional(), + fuzzy: Joi.object().optional(), + range: Joi.object().optional(), + equal: Joi.object().optional(), + notEqual: Joi.object().optional(), + empty: Joi.object().optional(), + notEmpty: Joi.object().optional(), + oneOf: Joi.object().optional(), + }) +} + +exports.internalSearchValidator = () => { // prettier-ignore return joiValidator.body(Joi.object({ - tableId: Joi.string() + tableId: Joi.string(), + query: filterObject(), + limit: Joi.number().optional(), + sort: Joi.string().optional(), + sortOrder: Joi.string().optional(), + sortType: Joi.string().optional(), + paginate: Joi.boolean().optional(), + bookmark: Joi.alternatives().try(Joi.string(), Joi.number()).optional(), })) } +exports.externalSearchValidator = () => { + return joiValidator.body( + Joi.object({ + query: filterObject(), + paginate: Joi.boolean().optional(), + bookmark: Joi.alternatives().try(Joi.string(), Joi.number()).optional(), + limit: Joi.number().optional(), + sort: Joi.object({ + column: Joi.string(), + order: Joi.string().optional().valid("ascending", "descending"), + type: Joi.string().valid("string", "number"), + }).optional(), + }) + ) +} + exports.datasourceQueryValidator = () => { // prettier-ignore return joiValidator.body(Joi.object({ @@ -96,14 +103,7 @@ exports.datasourceQueryValidator = () => { }).optional(), body: Joi.object().optional(), sort: Joi.object().optional(), - filters: Joi.object({ - string: Joi.object().optional(), - range: Joi.object().optional(), - equal: Joi.object().optional(), - notEqual: Joi.object().optional(), - empty: Joi.object().optional(), - notEmpty: Joi.object().optional(), - }).optional(), + filters: filterObject().optional(), paginate: Joi.object({ page: Joi.string().alphanum().optional(), limit: Joi.number().optional(), @@ -201,3 +201,5 @@ exports.automationValidator = (existing = false) => { }).required().unknown(true), }).unknown(true)) } + +exports.applicationValidator = () => {} diff --git a/packages/server/src/definitions/common.ts b/packages/server/src/definitions/common.ts index 16885973f5..b7333bfe82 100644 --- a/packages/server/src/definitions/common.ts +++ b/packages/server/src/definitions/common.ts @@ -5,6 +5,10 @@ export interface Base { _rev?: string } +export interface Application extends Base { + appId?: string +} + export interface FieldSchema { // TODO: replace with field types enum when done type: string