Refactoring to TS on public endpoints.

This commit is contained in:
mike12345567 2022-02-24 15:13:14 +00:00
parent 812f69815f
commit 74dcce6b1d
19 changed files with 237 additions and 135 deletions

View File

@ -15,7 +15,15 @@ const application = {
lockedBy: userResource.getExamples().user.value.user, lockedBy: userResource.getExamples().user.value.user,
} }
const applicationSchema = object({}) const applicationSchema = object({
name: {
type: "string",
required: true,
},
url: {
type: "string",
},
})
module.exports = new Resource() module.exports = new Resource()
.setExamples({ .setExamples({

View File

@ -1,40 +1,52 @@
const { search } = require("./utils")
const { getAllApps } = require("@budibase/backend-core/db") const { getAllApps } = require("@budibase/backend-core/db")
const { updateAppId } = require("@budibase/backend-core/context") 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)) { if (ctx.body && ctx.body.appId && (!ctx.params || !ctx.params.appId)) {
ctx.params = { appId: ctx.body.appId } ctx.params = { appId: ctx.body.appId }
} }
await controller.fetchAppPackage(ctx) await controller.fetchAppPackage(ctx)
} }
exports.search = async ctx => { export async function search(ctx: any) {
const { name } = ctx.request.body const { name } = ctx.request.body
const apps = await getAllApps({ all: true }) const apps = await getAllApps({ all: true })
ctx.body = { 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 controller.create(ctx)
await setResponseApp(ctx) await setResponseApp(ctx)
} }
exports.read = async ctx => { export async function read(ctx: any) {
updateAppId(ctx.params.appId) updateAppId(ctx.params.appId)
await setResponseApp(ctx) 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) updateAppId(ctx.params.appId)
await controller.update(ctx) await controller.update(ctx)
await setResponseApp(ctx) await setResponseApp(ctx)
} }
exports.delete = async ctx => { export async function destroy(ctx: any) {
updateAppId(ctx.params.appId) updateAppId(ctx.params.appId)
// get the app before deleting it // get the app before deleting it
await setResponseApp(ctx) await setResponseApp(ctx)
@ -43,3 +55,11 @@ exports.delete = async ctx => {
// overwrite the body again // overwrite the body again
ctx.body = body ctx.body = body
} }
export default {
create,
update,
read,
destroy,
search,
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -1,9 +1,10 @@
const rowController = require("../row") import { default as rowController } from "../row"
const { addRev } = require("./utils") 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 // makes sure that the user doesn't need to pass in the type, tableId or _id params for
// the call to be correct // the call to be correct
function fixRow(row, params) { function fixRow(row: Row, params: any) {
if (!params || !row) { if (!params || !row) {
return row return row
} }
@ -19,27 +20,41 @@ function fixRow(row, params) {
return row 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) await rowController.search(ctx)
} }
exports.create = async ctx => { export async function create(ctx: any) {
ctx.request.body = fixRow(ctx.request.body, ctx.params) ctx.request.body = fixRow(ctx.request.body, ctx.params)
await rowController.save(ctx) await rowController.save(ctx)
ctx.body = { row: ctx.body } ctx.body = { row: ctx.body }
} }
exports.read = async ctx => { export async function read(ctx: any) {
await rowController.find(ctx) await rowController.find(ctx)
ctx.body = { row: ctx.body } 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.request.body = await addRev(fixRow(ctx.request.body, ctx.params.tableId))
ctx.body = { row: ctx.body } 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 // set the body as expected, with the _id and _rev fields
ctx.request.body = await addRev(fixRow({}, ctx.params.tableId)) ctx.request.body = await addRev(fixRow({}, ctx.params.tableId))
await rowController.destroy(ctx) await rowController.destroy(ctx)
@ -47,3 +62,11 @@ exports.delete = async ctx => {
// in the public API to be correct // in the public API to be correct
ctx.body = { row: ctx.row } ctx.body = { row: ctx.row }
} }
export default {
create,
read,
update,
destroy,
search,
}

View File

@ -1,31 +1,39 @@
const { search, addRev } = require("./utils") import { search as stringSearch, addRev } from "./utils"
const controller = require("../table") import { default as controller } from "../table"
exports.search = async ctx => { export async function search(ctx: any) {
const { name } = ctx.request.body const { name } = ctx.request.body
await controller.fetch(ctx) await controller.fetch(ctx)
ctx.body = { 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) await controller.save(ctx)
ctx.body = { table: ctx.body } ctx.body = { table: ctx.body }
} }
exports.read = async ctx => { export async function read(ctx: any) {
await controller.find(ctx) await controller.find(ctx)
ctx.body = { table: ctx.body } 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) ctx.request.body = await addRev(ctx.request.body, ctx.params.tableId)
await controller.save(ctx) await controller.save(ctx)
ctx.body = { table: ctx.body } ctx.body = { table: ctx.body }
} }
exports.delete = async ctx => { export async function destroy(ctx: any) {
await controller.destroy(ctx) await controller.destroy(ctx)
ctx.body = { table: ctx.table } ctx.body = { table: ctx.table }
} }
export default {
create,
read,
update,
destroy,
search,
}

View File

@ -1,9 +0,0 @@
exports.search = () => {}
exports.create = () => {}
exports.read = () => {}
exports.update = () => {}
exports.delete = () => {}

View File

@ -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,
}

View File

@ -1,8 +1,11 @@
const { getAppDB } = require("@budibase/backend-core/context") const { getAppDB } = require("@budibase/backend-core/context")
const { isExternalTable } = require("../../../integrations/utils") import { isExternalTable } from "../../../integrations/utils"
exports.addRev = async (body, tableId) => { export async function addRev(
if (!body._id || isExternalTable(tableId)) { body: { _id?: string; _rev?: string },
tableId?: string
) {
if (!body._id || (tableId && isExternalTable(tableId))) {
return body return body
} }
const db = getAppDB() 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 * provided key and value. This will be a string based search, using the
* startsWith function. * startsWith function.
*/ */
exports.search = (docs, value, key = "name") => { export function search(docs: any[], value: any, key = "name") {
if (!value || typeof value !== "string") { if (!value || typeof value !== "string") {
return docs return docs
} }

View File

@ -1,6 +1,6 @@
const controller = require("../../controllers/public/applications") import controller from "../../controllers/public/applications"
const Endpoint = require("./utils/Endpoint") import Endpoint from "./utils/Endpoint"
const { nameValidator } = require("../utils/validators") const { nameValidator, applicationValidator } = require("../utils/validators")
const read = [], const read = [],
write = [] write = []
@ -118,7 +118,7 @@ write.push(new Endpoint("put", "/applications/:appId", controller.update))
* application: * application:
* $ref: '#/components/examples/application' * $ref: '#/components/examples/application'
*/ */
write.push(new Endpoint("delete", "/applications/:appId", controller.delete)) write.push(new Endpoint("delete", "/applications/:appId", controller.destroy))
/** /**
* @openapi * @openapi
@ -142,4 +142,4 @@ write.push(new Endpoint("delete", "/applications/:appId", controller.delete))
*/ */
read.push(new Endpoint("get", "/applications/:appId", controller.read)) read.push(new Endpoint("get", "/applications/:appId", controller.read))
module.exports = { read, write } export default { read, write }

View File

@ -1,15 +1,13 @@
const appEndpoints = require("./applications") import appEndpoints from "./applications"
const queryEndpoints = require("./queries") import queryEndpoints from "./queries"
const tableEndpoints = require("./tables") import tableEndpoints from "./tables"
const rowEndpoints = require("./rows") import rowEndpoints from "./rows"
const userEndpoints = require("./users") 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 Router = require("@koa/router")
const usage = require("../../../middleware/usageQuota")
const authorized = require("../../../middleware/authorized")
const {
paramResource,
paramSubResource,
} = require("../../../middleware/resourceId")
const { const {
PermissionLevels, PermissionLevels,
PermissionTypes, PermissionTypes,
@ -21,7 +19,7 @@ const publicRouter = new Router({
prefix: PREFIX, prefix: PREFIX,
}) })
function addMiddleware(endpoints, middleware) { function addMiddleware(endpoints: any, middleware: CtxFn) {
if (!Array.isArray(endpoints)) { if (!Array.isArray(endpoints)) {
endpoints = [endpoints] endpoints = [endpoints]
} }
@ -30,13 +28,18 @@ function addMiddleware(endpoints, middleware) {
} }
} }
function addToRouter(endpoints) { function addToRouter(endpoints: any) {
for (let endpoint of endpoints) { for (let endpoint of endpoints) {
endpoint.apply(publicRouter) endpoint.apply(publicRouter)
} }
} }
function applyRoutes(endpoints, permType, resource, subResource = null) { function applyRoutes(
endpoints: any,
permType: string,
resource: string,
subResource?: string
) {
const paramMiddleware = subResource const paramMiddleware = subResource
? paramSubResource(resource, subResource) ? paramSubResource(resource, subResource)
: paramResource(resource) : paramResource(resource)
@ -53,6 +56,7 @@ applyRoutes(appEndpoints, PermissionTypes.APP, "appId")
applyRoutes(tableEndpoints, PermissionTypes.TABLE, "tableId") applyRoutes(tableEndpoints, PermissionTypes.TABLE, "tableId")
applyRoutes(userEndpoints, PermissionTypes.USER, "userId") applyRoutes(userEndpoints, PermissionTypes.USER, "userId")
applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId") 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 module.exports = publicRouter

View File

@ -1,6 +1,6 @@
const controller = require("../../controllers/public/queries") import controller from "../../controllers/public/queries"
const Endpoint = require("./utils/Endpoint") import Endpoint from "./utils/Endpoint"
const { nameValidator } = require("../utils/validators") import { nameValidator } from "../utils/validators"
const read = [], const read = [],
write = [] write = []
@ -89,4 +89,4 @@ read.push(
*/ */
write.push(new Endpoint("post", "/queries/:queryId", controller.execute)) write.push(new Endpoint("post", "/queries/:queryId", controller.execute))
module.exports = { read, write } export default { read, write }

View File

@ -1,5 +1,6 @@
const controller = require("../../controllers/public/rows") import controller from "../../controllers/public/rows"
const Endpoint = require("./utils/Endpoint") import Endpoint from "./utils/Endpoint"
import { externalSearchValidator } from "../utils/validators"
const read = [], const read = [],
write = [] write = []
@ -121,7 +122,11 @@ const read = [],
* $ref: '#/components/examples/rows' * $ref: '#/components/examples/rows'
*/ */
read.push( 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' * $ref: '#/components/examples/row'
*/ */
write.push( 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)) read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read))
module.exports = { read, write } export default { read, write }

View File

@ -1,6 +1,6 @@
const controller = require("../../controllers/public/tables") import controller from "../../controllers/public/tables"
const Endpoint = require("./utils/Endpoint") import Endpoint from "./utils/Endpoint"
const { tableValidator, nameValidator } = require("../utils/validators") import { tableValidator, nameValidator } from "../utils/validators"
const read = [], const read = [],
write = [] write = []
@ -133,7 +133,7 @@ write.push(
* table: * table:
* $ref: '#/components/examples/table' * $ref: '#/components/examples/table'
*/ */
write.push(new Endpoint("delete", "/tables/:tableId", controller.delete)) write.push(new Endpoint("delete", "/tables/:tableId", controller.destroy))
/** /**
* @openapi * @openapi
@ -158,4 +158,4 @@ write.push(new Endpoint("delete", "/tables/:tableId", controller.delete))
*/ */
read.push(new Endpoint("get", "/tables/:tableId", controller.read)) read.push(new Endpoint("get", "/tables/:tableId", controller.read))
module.exports = { read, write } export default { read, write }

View File

@ -1,6 +1,6 @@
const controller = require("../../controllers/public/users") import controller from "../../controllers/public/users"
const Endpoint = require("./utils/Endpoint") import Endpoint from "./utils/Endpoint"
const { nameValidator } = require("../utils/validators") import { nameValidator } from "../utils/validators"
const read = [], const read = [],
write = [] write = []
@ -117,7 +117,7 @@ write.push(new Endpoint("put", "/users/:userId", controller.update))
* user: * user:
* $ref: '#/components/examples/user' * $ref: '#/components/examples/user'
*/ */
write.push(new Endpoint("delete", "/users/:userId", controller.delete)) write.push(new Endpoint("delete", "/users/:userId", controller.destroy))
/** /**
* @openapi * @openapi
@ -142,4 +142,4 @@ write.push(new Endpoint("delete", "/users/:userId", controller.delete))
*/ */
read.push(new Endpoint("get", "/users/:userId", controller.read)) read.push(new Endpoint("get", "/users/:userId", controller.read))
module.exports = { read, write } export default { read, write }

View File

@ -1,24 +1,34 @@
import Router from "koa-router"
export type CtxFn = (ctx: any) => void
class Endpoint { 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.method = method
this.url = url this.url = url
this.controller = controller this.controller = controller
this.middlewares = [] this.middlewares = []
} }
addMiddleware(middleware) { addMiddleware(middleware: CtxFn) {
this.middlewares.push(middleware) this.middlewares.push(middleware)
return this return this
} }
apply(router) { apply(router: Router) {
const method = this.method, const method = this.method,
url = this.url url = this.url
const middlewares = this.middlewares, const middlewares = this.middlewares,
controller = this.controller controller = this.controller
const params = [url, ...middlewares, controller] const params = [url, ...middlewares, controller]
// @ts-ignore
router[method](...params) router[method](...params)
} }
} }
module.exports = Endpoint export default Endpoint

View File

@ -10,6 +10,7 @@ const {
PermissionLevels, PermissionLevels,
PermissionTypes, PermissionTypes,
} = require("@budibase/backend-core/permissions") } = require("@budibase/backend-core/permissions")
const { internalSearchValidator } = require("./utils/validators")
const router = Router() const router = Router()
@ -138,6 +139,7 @@ router
*/ */
.post( .post(
"/api/:tableId/search", "/api/:tableId/search",
internalSearchValidator(),
paramResource("tableId"), paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.READ), authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.search rowController.search

View File

@ -47,42 +47,49 @@ exports.datasourceValidator = () => {
}).unknown(true)) }).unknown(true))
} }
/** function filterObject() {
* * { return Joi.object({
* "tableId": "ta_70260ff0b85c467ca74364aefc46f26d", string: Joi.object().optional(),
* "query": { fuzzy: Joi.object().optional(),
* "string": {}, range: Joi.object().optional(),
* "fuzzy": {}, equal: Joi.object().optional(),
* "range": { notEqual: Joi.object().optional(),
* "columnName": { empty: Joi.object().optional(),
* "high": 20, notEmpty: Joi.object().optional(),
* "low": 10, oneOf: Joi.object().optional(),
* } })
* }, }
* "equal": {
* "columnName": "someValue" exports.internalSearchValidator = () => {
* },
* "notEqual": {},
* "empty": {},
* "notEmpty": {},
* "oneOf": {
* "columnName": ["value"]
* }
* },
* "limit": 10,
* "sort": "name",
* "sortOrder": "descending",
* "sortType": "string",
* "paginate": true
* }
*/
exports.searchValidator = () => {
// prettier-ignore // prettier-ignore
return joiValidator.body(Joi.object({ 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 = () => { exports.datasourceQueryValidator = () => {
// prettier-ignore // prettier-ignore
return joiValidator.body(Joi.object({ return joiValidator.body(Joi.object({
@ -96,14 +103,7 @@ exports.datasourceQueryValidator = () => {
}).optional(), }).optional(),
body: Joi.object().optional(), body: Joi.object().optional(),
sort: Joi.object().optional(), sort: Joi.object().optional(),
filters: Joi.object({ filters: filterObject().optional(),
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(),
paginate: Joi.object({ paginate: Joi.object({
page: Joi.string().alphanum().optional(), page: Joi.string().alphanum().optional(),
limit: Joi.number().optional(), limit: Joi.number().optional(),
@ -201,3 +201,5 @@ exports.automationValidator = (existing = false) => {
}).required().unknown(true), }).required().unknown(true),
}).unknown(true)) }).unknown(true))
} }
exports.applicationValidator = () => {}

View File

@ -5,6 +5,10 @@ export interface Base {
_rev?: string _rev?: string
} }
export interface Application extends Base {
appId?: string
}
export interface FieldSchema { export interface FieldSchema {
// TODO: replace with field types enum when done // TODO: replace with field types enum when done
type: string type: string