diff --git a/packages/server/src/api/routes/row.ts b/packages/server/src/api/routes/row.ts index ac0cd2b4a4..3227df98ed 100644 --- a/packages/server/src/api/routes/row.ts +++ b/packages/server/src/api/routes/row.ts @@ -4,9 +4,7 @@ import authorized from "../../middleware/authorized" import { paramResource, paramSubResource } from "../../middleware/resourceId" import { permissions } from "@budibase/backend-core" import { internalSearchValidator } from "./utils/validators" -import noViewData from "../../middleware/noViewData" import trimViewRowInfo from "../../middleware/trimViewRowInfo" -import * as utils from "../../db/utils" const { PermissionType, PermissionLevel } = permissions const router: Router = new Router() @@ -177,7 +175,7 @@ router "/api/:tableId/rows", paramResource("tableId"), authorized(PermissionType.TABLE, PermissionLevel.WRITE), - noViewData, + trimViewRowInfo, rowController.save ) /** @@ -192,7 +190,7 @@ router "/api/:tableId/rows", paramResource("tableId"), authorized(PermissionType.TABLE, PermissionLevel.WRITE), - noViewData, + trimViewRowInfo, rowController.patch ) /** @@ -245,6 +243,7 @@ router "/api/:tableId/rows", paramResource("tableId"), authorized(PermissionType.TABLE, PermissionLevel.WRITE), + trimViewRowInfo, rowController.destroy ) @@ -267,92 +266,10 @@ router authorized(PermissionType.TABLE, PermissionLevel.WRITE), rowController.exportRows ) - -router .get( "/api/v2/views/:viewId/search", - authorized(PermissionType.VIEW, PermissionLevel.READ), + authorized(PermissionType.TABLE, PermissionLevel.READ), rowController.views.searchView ) - /** - * @api {post} /api/:tableId/rows Creates a new row - * @apiName Creates a new row - * @apiGroup rows - * @apiPermission table write access - * @apiDescription This API will create a new row based on the supplied body. If the - * body includes an "_id" field then it will update an existing row if the field - * links to one. Please note that "_id", "_rev" and "tableId" are fields that are - * already used by Budibase tables and cannot be used for columns. - * - * @apiParam {string} tableId The ID of the table to save a row to. - * - * @apiParam (Body) {string} [_id] If the row exists already then an ID for the row must be provided. - * @apiParam (Body) {string} [_rev] If working with an existing row for an internal table its revision - * must also be provided. - * @apiParam (Body) {string} _viewId The ID of the view should be specified in the row body itself. - * @apiParam (Body) {string} tableId The ID of the table should also be specified in the row body itself. - * @apiParam (Body) {any} [any] Any field supplied in the body will be assessed to see if it matches - * a column in the specified table. All other fields will be dropped and not stored. - * - * @apiSuccess {string} _id The ID of the row that was just saved, if it was just created this - * is the rows new ID. - * @apiSuccess {string} [_rev] If saving to an internal table a revision will also be returned. - * @apiSuccess {object} body The contents of the row that was saved will be returned as well. - */ - .post( - "/api/v2/views/:viewId/rows", - paramResource("viewId"), - authorized(PermissionType.VIEW, PermissionLevel.WRITE), - trimViewRowInfo, - rowController.save - ) - /** - * @api {patch} /api/v2/views/:viewId/rows/:rowId Updates a row - * @apiName Update a row - * @apiGroup rows - * @apiPermission table write access - * @apiDescription This endpoint is identical to the row creation endpoint but instead it will - * error if an _id isn't provided, it will only function for existing rows. - */ - .patch( - "/api/v2/views/:viewId/rows/:rowId", - paramResource("viewId"), - authorized(PermissionType.VIEW, PermissionLevel.WRITE), - trimViewRowInfo, - rowController.patch - ) - /** - * @api {delete} /api/v2/views/:viewId/rows Delete rows for a view - * @apiName Delete rows for a view - * @apiGroup rows - * @apiPermission table write access - * @apiDescription This endpoint can delete a single row, or delete them in a bulk - * fashion. - * - * @apiParam {string} tableId The ID of the table the row is to be deleted from. - * - * @apiParam (Body) {object[]} [rows] If bulk deletion is desired then provide the rows in this - * key of the request body that are to be deleted. - * @apiParam (Body) {string} [_id] If deleting a single row then provide its ID in this field. - * @apiParam (Body) {string} [_rev] If deleting a single row from an internal table then provide its - * revision here. - * - * @apiSuccess {object[]|object} body If deleting bulk then the response body will be an array - * of the deleted rows, if deleting a single row then the body will contain a "row" property which - * is the deleted row. - */ - .delete( - "/api/v2/views/:viewId/rows", - paramResource("viewId"), - authorized(PermissionType.VIEW, PermissionLevel.WRITE), - // This is required as the implementation relies on the table id - (ctx, next) => { - ctx.params.tableId = utils.extractViewInfoFromID( - ctx.params.viewId - ).tableId - return next() - }, - rowController.destroy - ) export default router diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index b986ffb945..86c41b8503 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -24,7 +24,6 @@ import { structures, } from "@budibase/backend-core/tests" import trimViewRowInfoMiddleware from "../../../middleware/trimViewRowInfo" -import noViewDataMiddleware from "../../../middleware/noViewData" import router from "../row" describe("/rows", () => { @@ -406,14 +405,6 @@ describe("/rows", () => { "Table row endpoints cannot contain view info" ) }) - - it("should setup the noViewData middleware", async () => { - const route = router.stack.find( - r => r.methods.includes("POST") && r.path === "/api/:tableId/rows" - ) - expect(route).toBeDefined() - expect(route?.stack).toContainEqual(noViewDataMiddleware) - }) }) describe("patch", () => { @@ -482,14 +473,6 @@ describe("/rows", () => { "Table row endpoints cannot contain view info" ) }) - - it("should setup the noViewData middleware", async () => { - const route = router.stack.find( - r => r.methods.includes("PATCH") && r.path === "/api/:tableId/rows" - ) - expect(route).toBeDefined() - expect(route?.stack).toContainEqual(noViewDataMiddleware) - }) }) describe("destroy", () => { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index dda14a9187..e8dc3b3f6c 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -284,10 +284,20 @@ export function getMultiIDParams(ids: string[]) { * @returns {string} The new view ID which the view doc can be stored under. */ export function generateViewID(tableId: string) { - return `${tableId}${SEPARATOR}${newid()}` + return `${DocumentType.VIEW}${SEPARATOR}${tableId}${SEPARATOR}${newid()}` +} + +export function isViewID(viewId: string) { + return viewId?.split(SEPARATOR)[0] === DocumentType.VIEW } export function extractViewInfoFromID(viewId: string) { + if (!isViewID(viewId)) { + throw new Error("Unable to extract table ID, is not a view ID") + } + const split = viewId.split(SEPARATOR) + split.shift() + viewId = split.join(SEPARATOR) const regex = new RegExp(`^(?.+)${SEPARATOR}([^${SEPARATOR}]+)$`) const res = regex.exec(viewId) return { diff --git a/packages/server/src/middleware/noViewData.ts b/packages/server/src/middleware/noViewData.ts deleted file mode 100644 index 809424b6bf..0000000000 --- a/packages/server/src/middleware/noViewData.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Ctx, Row } from "@budibase/types" - -export default async (ctx: Ctx, next: any) => { - if (ctx.request.body._viewId) { - return ctx.throw(400, "Table row endpoints cannot contain view info") - } - - return next() -} diff --git a/packages/server/src/middleware/tests/noViewData.spec.ts b/packages/server/src/middleware/tests/noViewData.spec.ts deleted file mode 100644 index 54b0ca8ff8..0000000000 --- a/packages/server/src/middleware/tests/noViewData.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { generator } from "@budibase/backend-core/tests" -import { BBRequest, FieldType, Row, Table } from "@budibase/types" -import { Next } from "koa" -import * as utils from "../../db/utils" -import noViewDataMiddleware from "../noViewData" - -class TestConfiguration { - next: Next - throw: jest.Mock<(status: number, message: string) => never> - middleware: typeof noViewDataMiddleware - params: Record - request?: Pick, "body"> - - constructor() { - this.next = jest.fn() - this.throw = jest.fn() - this.params = {} - - this.middleware = noViewDataMiddleware - } - - executeMiddleware(ctxRequestBody: Row) { - this.request = { - body: ctxRequestBody, - } - return this.middleware( - { - request: this.request as any, - throw: this.throw as any, - params: this.params, - } as any, - this.next - ) - } - - afterEach() { - jest.clearAllMocks() - } -} - -describe("noViewData middleware", () => { - let config: TestConfiguration - - beforeEach(() => { - config = new TestConfiguration() - }) - - afterEach(() => { - config.afterEach() - }) - - const getRandomData = () => ({ - _id: generator.guid(), - name: generator.name(), - age: generator.age(), - address: generator.address(), - }) - - it("it should pass without view id data", async () => { - const data = getRandomData() - await config.executeMiddleware({ - ...data, - }) - - expect(config.next).toBeCalledTimes(1) - expect(config.throw).not.toBeCalled() - }) - - it("it should throw an error if _viewid is provided", async () => { - const data = getRandomData() - await config.executeMiddleware({ - _viewId: generator.guid(), - ...data, - }) - - expect(config.throw).toBeCalledTimes(1) - expect(config.throw).toBeCalledWith( - 400, - "Table row endpoints cannot contain view info" - ) - expect(config.next).not.toBeCalled() - }) -}) diff --git a/packages/server/src/middleware/trimViewRowInfo.ts b/packages/server/src/middleware/trimViewRowInfo.ts index 2e3ded27f5..763552c3d7 100644 --- a/packages/server/src/middleware/trimViewRowInfo.ts +++ b/packages/server/src/middleware/trimViewRowInfo.ts @@ -7,15 +7,15 @@ import { Next } from "koa" export default async (ctx: Ctx, next: Next) => { const { body } = ctx.request const { _viewId: viewId } = body - if (!viewId) { - return ctx.throw(400, "_viewId is required") + + const possibleViewId = ctx.params.tableId + + // nothing to do, it is not a view (just a table ID) + if (!viewId || !utils.isViewID(possibleViewId)) { + return next() } - if (!ctx.params.viewId) { - return ctx.throw(400, "viewId path is required") - } - - const { tableId } = utils.extractViewInfoFromID(ctx.params.viewId) + const { tableId } = utils.extractViewInfoFromID(possibleViewId) const { _viewId, ...trimmedView } = await trimViewFields( viewId, tableId, diff --git a/packages/server/src/sdk/app/views/index.ts b/packages/server/src/sdk/app/views/index.ts index 7e75f22060..637caa06ee 100644 --- a/packages/server/src/sdk/app/views/index.ts +++ b/packages/server/src/sdk/app/views/index.ts @@ -1,17 +1,14 @@ -import { HTTPError, context } from "@budibase/backend-core" +import { context, HTTPError } from "@budibase/backend-core" import { FieldSchema, TableSchema, View, ViewV2 } from "@budibase/types" import sdk from "../../../sdk" import * as utils from "../../../db/utils" -import merge from "lodash/merge" export async function get(viewId: string): Promise { const { tableId } = utils.extractViewInfoFromID(viewId) const table = await sdk.tables.getTable(tableId) const views = Object.values(table.views!) - const view = views.find(v => isV2(v) && v.id === viewId) as ViewV2 | undefined - - return view + return views.find(v => isV2(v) && v.id === viewId) as ViewV2 | undefined } export async function create( diff --git a/packages/types/src/documents/document.ts b/packages/types/src/documents/document.ts index 6ba318269b..164aa79ac9 100644 --- a/packages/types/src/documents/document.ts +++ b/packages/types/src/documents/document.ts @@ -37,6 +37,7 @@ export enum DocumentType { USER_FLAG = "flag", AUTOMATION_METADATA = "meta_au", AUDIT_LOG = "al", + VIEW = "view", } export interface Document { diff --git a/packages/types/src/sdk/permissions.ts b/packages/types/src/sdk/permissions.ts index 9e51e4c7b4..9fe1970e44 100644 --- a/packages/types/src/sdk/permissions.ts +++ b/packages/types/src/sdk/permissions.ts @@ -14,6 +14,5 @@ export enum PermissionType { WEBHOOK = "webhook", BUILDER = "builder", GLOBAL_BUILDER = "globalBuilder", - VIEW = "view", QUERY = "query", }