From 5792c7cdb8058117a3b424c85ccc7de777c64b94 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan Date: Thu, 27 Jun 2024 15:23:51 +0100 Subject: [PATCH 01/43] Script to verify ethereal --- scripts/verifyEtherealSmtp.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 scripts/verifyEtherealSmtp.js diff --git a/scripts/verifyEtherealSmtp.js b/scripts/verifyEtherealSmtp.js new file mode 100644 index 0000000000..aecf1d6593 --- /dev/null +++ b/scripts/verifyEtherealSmtp.js @@ -0,0 +1,31 @@ +const nodemailer = require("nodemailer") + +const options = { + port: 587, + host: "smtp.ethereal.email", + secure: false, + auth: { + user: "seamus99@ethereal.email", + pass: "5ghVteZAqj6jkKJF9R", + }, +} + +const transporter = nodemailer.createTransport(options) +transporter.verify(function (error) { + if (error) { + console.log(error) + } else { + console.log("Ethereal server is ready to take our messages") + } +}) + +const message = { + from: "from@example.com", + to: "to@example.com", + subject: "Did this email arrive?", + html: "Hello World!", +} + +transporter.sendMail(message).then(response => { + console.log("Test email URL: " + nodemailer.getTestMessageUrl(response)) +}) From 107bd08e21dfefa5f6f82cb1a7ab41778a727a21 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 10:46:15 +0200 Subject: [PATCH 02/43] Endpoint scaffolding --- .../src/api/controllers/rowAction/crud.ts | 15 +++++++ .../src/api/controllers/rowAction/index.ts | 2 + .../src/api/controllers/rowAction/run.ts | 3 ++ packages/server/src/api/routes/rowAction.ts | 41 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 packages/server/src/api/controllers/rowAction/crud.ts create mode 100644 packages/server/src/api/controllers/rowAction/index.ts create mode 100644 packages/server/src/api/controllers/rowAction/run.ts create mode 100644 packages/server/src/api/routes/rowAction.ts diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts new file mode 100644 index 0000000000..3bea2de73c --- /dev/null +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -0,0 +1,15 @@ +export function find() { + throw new Error("Function not implemented.") +} + +export function create() { + throw new Error("Function not implemented.") +} + +export function update() { + throw new Error("Function not implemented.") +} + +export function remove() { + throw new Error("Function not implemented.") +} diff --git a/packages/server/src/api/controllers/rowAction/index.ts b/packages/server/src/api/controllers/rowAction/index.ts new file mode 100644 index 0000000000..bf65119f1a --- /dev/null +++ b/packages/server/src/api/controllers/rowAction/index.ts @@ -0,0 +1,2 @@ +export * from "./crud" +export * from "./run" diff --git a/packages/server/src/api/controllers/rowAction/run.ts b/packages/server/src/api/controllers/rowAction/run.ts new file mode 100644 index 0000000000..06c4b36f86 --- /dev/null +++ b/packages/server/src/api/controllers/rowAction/run.ts @@ -0,0 +1,3 @@ +export function run() { + throw new Error("Function not implemented.") +} diff --git a/packages/server/src/api/routes/rowAction.ts b/packages/server/src/api/routes/rowAction.ts new file mode 100644 index 0000000000..7bc50377b8 --- /dev/null +++ b/packages/server/src/api/routes/rowAction.ts @@ -0,0 +1,41 @@ +import Router from "@koa/router" +import * as rowActionController from "../controllers/rowAction" +import { authorizedResource } from "../../middleware/authorized" + +import { permissions } from "@budibase/backend-core" + +const { PermissionLevel, PermissionType } = permissions + +const router: Router = new Router() + +// CRUD endpoints +router + .get( + "/api/tables/:tableId/actions", + authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionController.find + ) + .post( + "/api/tables/:tableId/actions", + authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionController.create + ) + .put( + "/api/tables/:tableId/actions/:actionId", + authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionController.update + ) + .delete( + "/api/tables/:tableId/actions/:actionId", + authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionController.remove + ) + + // Other endpoints + .post( + "/api/tables/:tableId/actions/:actionId/run", + authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionController.run + ) + +export default router From dfda2f0f548909bf67cd63d729b11612aa964b52 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 13:22:28 +0200 Subject: [PATCH 03/43] Register router --- packages/server/src/api/routes/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/server/src/api/routes/index.ts b/packages/server/src/api/routes/index.ts index 5a42c258cf..2079eb01fd 100644 --- a/packages/server/src/api/routes/index.ts +++ b/packages/server/src/api/routes/index.ts @@ -28,6 +28,7 @@ import opsRoutes from "./ops" import debugRoutes from "./debug" import Router from "@koa/router" import { api as pro } from "@budibase/pro" +import rowActionRoutes from "./rowAction" export { default as staticRoutes } from "./static" export { default as publicRoutes } from "./public" @@ -65,6 +66,7 @@ export const mainRoutes: Router[] = [ opsRoutes, debugRoutes, environmentVariableRoutes, + rowActionRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this tableRoutes, From 38718968b003079fa5cbfc89b0d8858c95a75971 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 13:24:00 +0200 Subject: [PATCH 04/43] Basic implementation --- .../server/src/api/controllers/rowAction/crud.ts | 16 ++++++++++++++-- packages/types/src/api/web/app/index.ts | 1 + packages/types/src/api/web/app/rowAction.ts | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 packages/types/src/api/web/app/rowAction.ts diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 3bea2de73c..79a86233a8 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -1,9 +1,21 @@ +import { CreateRowActionRequest, Ctx, RowAction } from "@budibase/types" +import sdk from "../../../sdk" + export function find() { throw new Error("Function not implemented.") } -export function create() { - throw new Error("Function not implemented.") +export async function create(ctx: Ctx) { + const { tableId } = ctx.params + + const table = await sdk.tables.getTable(tableId) + if (!table) { + ctx.throw(404) + } + + // TODO + + ctx.status = 201 } export function update() { diff --git a/packages/types/src/api/web/app/index.ts b/packages/types/src/api/web/app/index.ts index cb1cea2b08..55e1428fb9 100644 --- a/packages/types/src/api/web/app/index.ts +++ b/packages/types/src/api/web/app/index.ts @@ -7,3 +7,4 @@ export * from "./table" export * from "./permission" export * from "./attachment" export * from "./user" +export * from "./rowAction" diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts new file mode 100644 index 0000000000..9a7f2a85b1 --- /dev/null +++ b/packages/types/src/api/web/app/rowAction.ts @@ -0,0 +1,3 @@ +export interface CreateRowActionRequest {} + +export interface RowAction {} From de2938799b67ee11ba6bdcaadf04185c33f266d1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 13:24:25 +0200 Subject: [PATCH 05/43] Initial test --- .../src/api/routes/tests/rowAction.spec.ts | 65 +++++++++++++++++++ .../server/src/tests/utilities/api/index.ts | 3 + .../src/tests/utilities/api/rowAction.ts | 17 +++++ 3 files changed, 85 insertions(+) create mode 100644 packages/server/src/api/routes/tests/rowAction.spec.ts create mode 100644 packages/server/src/tests/utilities/api/rowAction.ts diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts new file mode 100644 index 0000000000..46abadd397 --- /dev/null +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -0,0 +1,65 @@ +import _ from "lodash" +import { Table } from "@budibase/types" +import * as setup from "./utilities" +import { generator } from "@budibase/backend-core/tests" + +describe("/rowsActions", () => { + const config = setup.getConfig() + + let table: Table + + beforeAll(async () => { + await config.init() + + table = await config.api.table.save(setup.structures.basicTable()) + }) + + afterAll(setup.afterAll) + + beforeAll(async () => { + table = await config.api.table.save(setup.structures.basicTable()) + }) + + function unauthorisedTests() { + it("returns unauthorised (401) for unauthenticated requests", async () => { + await config.api.rowAction.save( + table._id!, + {}, + { + status: 401, + body: { + message: "Session not authenticated", + }, + }, + { publicUser: true } + ) + }) + + it("returns forbidden (403) for non-builder users", async () => { + const user = await config.createUser({ + builder: {}, + }) + await config.withUser(user, async () => { + await config.api.rowAction.save(generator.guid(), {}, { status: 403 }) + }) + }) + } + + describe("create", () => { + unauthorisedTests() + + it("rejects when using a non-existing table", async () => { + const res = await config.api.rowAction.save( + table._id!, + {}, + { status: 201 } + ) + + expect(res).toEqual({}) + }) + + it("rejects (404) for a non-existing table", async () => { + await config.api.rowAction.save(generator.guid(), {}, { status: 404 }) + }) + }) +}) diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index 554fa36588..a19b68a872 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -13,6 +13,7 @@ import { UserAPI } from "./user" import { QueryAPI } from "./query" import { RoleAPI } from "./role" import { TemplateAPI } from "./template" +import { RowActionAPI } from "./rowAction" export default class API { table: TableAPI @@ -29,6 +30,7 @@ export default class API { query: QueryAPI roles: RoleAPI templates: TemplateAPI + rowAction: RowActionAPI constructor(config: TestConfiguration) { this.table = new TableAPI(config) @@ -45,5 +47,6 @@ export default class API { this.query = new QueryAPI(config) this.roles = new RoleAPI(config) this.templates = new TemplateAPI(config) + this.rowAction = new RowActionAPI(config) } } diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts new file mode 100644 index 0000000000..c6b8df5d12 --- /dev/null +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -0,0 +1,17 @@ +import { CreateRowActionRequest, Row, RowAction } from "@budibase/types" +import { Expectations, TestAPI } from "./base" + +export class RowActionAPI extends TestAPI { + save = async ( + tableId: string, + rowAction: CreateRowActionRequest, + expectations?: Expectations, + config?: { publicUser?: boolean } + ): Promise => { + return await this._post(`/api/tables/${tableId}/actions`, { + body: rowAction, + expectations, + ...config, + }) + } +} From 0c2024bf6a5186de2ff4f12ef29f37ef935d5f68 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 13:56:41 +0200 Subject: [PATCH 06/43] Basic get --- .../src/api/controllers/rowAction/crud.ts | 27 ++++++++++++++----- .../src/api/routes/tests/rowAction.spec.ts | 20 +++++++++----- .../src/tests/utilities/api/rowAction.ts | 24 +++++++++++++++-- packages/types/src/api/web/app/rowAction.ts | 4 +++ 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 79a86233a8..1e6b4f4671 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -1,17 +1,30 @@ -import { CreateRowActionRequest, Ctx, RowAction } from "@budibase/types" +import { + CreateRowActionRequest, + Ctx, + RowAction, + RowActionsResponse, +} from "@budibase/types" import sdk from "../../../sdk" -export function find() { - throw new Error("Function not implemented.") -} - -export async function create(ctx: Ctx) { +async function getTable(ctx: Ctx) { const { tableId } = ctx.params - const table = await sdk.tables.getTable(tableId) if (!table) { ctx.throw(404) } + return table +} + +export async function find(ctx: Ctx) { + const table = await getTable(ctx) + + // TODO + + ctx.body = { actions: [] } +} + +export async function create(ctx: Ctx) { + const table = await getTable(ctx) // TODO diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 46abadd397..edf6d64393 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -16,10 +16,6 @@ describe("/rowsActions", () => { afterAll(setup.afterAll) - beforeAll(async () => { - table = await config.api.table.save(setup.structures.basicTable()) - }) - function unauthorisedTests() { it("returns unauthorised (401) for unauthenticated requests", async () => { await config.api.rowAction.save( @@ -43,12 +39,16 @@ describe("/rowsActions", () => { await config.api.rowAction.save(generator.guid(), {}, { status: 403 }) }) }) + + it("rejects (404) for a non-existing table", async () => { + await config.api.rowAction.save(generator.guid(), {}, { status: 404 }) + }) } describe("create", () => { unauthorisedTests() - it("rejects when using a non-existing table", async () => { + it("accepts creating new row actions", async () => { const res = await config.api.rowAction.save( table._id!, {}, @@ -57,9 +57,15 @@ describe("/rowsActions", () => { expect(res).toEqual({}) }) + }) - it("rejects (404) for a non-existing table", async () => { - await config.api.rowAction.save(generator.guid(), {}, { status: 404 }) + describe("find", () => { + unauthorisedTests() + + it("returns empty for tables without row actions", async () => { + const res = await config.api.rowAction.find(table._id!, {}) + + expect(res).toEqual({ actions: [] }) }) }) }) diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index c6b8df5d12..7a85db21e9 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -1,4 +1,8 @@ -import { CreateRowActionRequest, Row, RowAction } from "@budibase/types" +import { + CreateRowActionRequest, + RowAction, + RowActionsResponse, +} from "@budibase/types" import { Expectations, TestAPI } from "./base" export class RowActionAPI extends TestAPI { @@ -7,11 +11,27 @@ export class RowActionAPI extends TestAPI { rowAction: CreateRowActionRequest, expectations?: Expectations, config?: { publicUser?: boolean } - ): Promise => { + ) => { return await this._post(`/api/tables/${tableId}/actions`, { body: rowAction, expectations, ...config, }) } + + find = async ( + tableId: string, + rowAction: CreateRowActionRequest, + expectations?: Expectations, + config?: { publicUser?: boolean } + ) => { + return await this._get( + `/api/tables/${tableId}/actions`, + { + body: rowAction, + expectations, + ...config, + } + ) + } } diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index 9a7f2a85b1..d1935a5b51 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -1,3 +1,7 @@ export interface CreateRowActionRequest {} export interface RowAction {} + +export interface RowActionsResponse { + actions: RowAction[] +} From bf161d9d933d62307e5aaba48c70719577dc626c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 15:41:55 +0200 Subject: [PATCH 07/43] More types --- .../src/api/controllers/rowAction/crud.ts | 10 +++-- .../src/api/routes/tests/rowAction.spec.ts | 37 +++++++++++++------ .../src/tests/utilities/api/rowAction.ts | 2 - packages/types/src/api/web/app/rowAction.ts | 9 ++++- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 1e6b4f4671..83b4215f35 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -1,7 +1,6 @@ import { CreateRowActionRequest, Ctx, - RowAction, RowActionsResponse, } from "@budibase/types" import sdk from "../../../sdk" @@ -20,15 +19,18 @@ export async function find(ctx: Ctx) { // TODO - ctx.body = { actions: [] } + ctx.body = { + tableId: table._id!, + actions: [], + } } -export async function create(ctx: Ctx) { +export async function create(ctx: Ctx) { const table = await getTable(ctx) // TODO - ctx.status = 201 + ctx.status = 204 } export function update() { diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index edf6d64393..ac0bff4781 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -1,5 +1,5 @@ import _ from "lodash" -import { Table } from "@budibase/types" +import { CreateRowActionRequest, Table } from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" @@ -16,11 +16,17 @@ describe("/rowsActions", () => { afterAll(setup.afterAll) + function createRowActionRequest(): CreateRowActionRequest { + return { + name: generator.word(), + } + } + function unauthorisedTests() { it("returns unauthorised (401) for unauthenticated requests", async () => { await config.api.rowAction.save( table._id!, - {}, + createRowActionRequest(), { status: 401, body: { @@ -36,12 +42,20 @@ describe("/rowsActions", () => { builder: {}, }) await config.withUser(user, async () => { - await config.api.rowAction.save(generator.guid(), {}, { status: 403 }) + await config.api.rowAction.save( + generator.guid(), + createRowActionRequest(), + { status: 403 } + ) }) }) it("rejects (404) for a non-existing table", async () => { - await config.api.rowAction.save(generator.guid(), {}, { status: 404 }) + await config.api.rowAction.save( + generator.guid(), + createRowActionRequest(), + { status: 404 } + ) }) } @@ -49,11 +63,11 @@ describe("/rowsActions", () => { unauthorisedTests() it("accepts creating new row actions", async () => { - const res = await config.api.rowAction.save( - table._id!, - {}, - { status: 201 } - ) + const rowAction = createRowActionRequest() + + const res = await config.api.rowAction.save(table._id!, rowAction, { + status: 204, + }) expect(res).toEqual({}) }) @@ -63,9 +77,10 @@ describe("/rowsActions", () => { unauthorisedTests() it("returns empty for tables without row actions", async () => { - const res = await config.api.rowAction.find(table._id!, {}) + const tableId = table._id! + const res = await config.api.rowAction.find(tableId) - expect(res).toEqual({ actions: [] }) + expect(res).toEqual({ tableId, actions: [] }) }) }) }) diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index 7a85db21e9..a78633681e 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -21,14 +21,12 @@ export class RowActionAPI extends TestAPI { find = async ( tableId: string, - rowAction: CreateRowActionRequest, expectations?: Expectations, config?: { publicUser?: boolean } ) => { return await this._get( `/api/tables/${tableId}/actions`, { - body: rowAction, expectations, ...config, } diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index d1935a5b51..fceb606699 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -1,7 +1,12 @@ -export interface CreateRowActionRequest {} +export interface CreateRowActionRequest { + name: string +} -export interface RowAction {} +interface RowAction { + name: string +} export interface RowActionsResponse { + tableId: string actions: RowAction[] } From fe31f88cc8d4a01833c892921b05f474d51a1b8c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 15:48:16 +0200 Subject: [PATCH 08/43] Add validation --- packages/server/src/api/routes/rowAction.ts | 13 ++++++++++++- .../server/src/api/routes/tests/rowAction.spec.ts | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/rowAction.ts b/packages/server/src/api/routes/rowAction.ts index 7bc50377b8..18a87cd677 100644 --- a/packages/server/src/api/routes/rowAction.ts +++ b/packages/server/src/api/routes/rowAction.ts @@ -2,10 +2,19 @@ import Router from "@koa/router" import * as rowActionController from "../controllers/rowAction" import { authorizedResource } from "../../middleware/authorized" -import { permissions } from "@budibase/backend-core" +import { middleware, permissions } from "@budibase/backend-core" +import Joi from "joi" const { PermissionLevel, PermissionType } = permissions +export function rowActionValidator() { + return middleware.joiValidator.body( + Joi.object({ + name: Joi.string().required(), + }) + ) +} + const router: Router = new Router() // CRUD endpoints @@ -18,11 +27,13 @@ router .post( "/api/tables/:tableId/actions", authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionValidator(), rowActionController.create ) .put( "/api/tables/:tableId/actions/:actionId", authorizedResource(PermissionType.TABLE, PermissionLevel.READ, "tableId"), + rowActionValidator(), rowActionController.update ) .delete( diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index ac0bff4781..f372938b23 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -71,6 +71,19 @@ describe("/rowsActions", () => { expect(res).toEqual({}) }) + + it("rejects with bad request when creating with no name", async () => { + const rowAction: CreateRowActionRequest = { + name: undefined as any, + } + + await config.api.rowAction.save(table._id!, rowAction, { + status: 400, + body: { + message: 'Invalid body - "name" is required', + }, + }) + }) }) describe("find", () => { From 063eeeb6df0411762a5b6bd561b5d9f8b33570e7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 10 Jul 2024 15:49:13 +0200 Subject: [PATCH 09/43] Fix --- packages/server/src/api/routes/tests/rowAction.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index f372938b23..ea97e526d5 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -74,13 +74,13 @@ describe("/rowsActions", () => { it("rejects with bad request when creating with no name", async () => { const rowAction: CreateRowActionRequest = { - name: undefined as any, + name: "", } await config.api.rowAction.save(table._id!, rowAction, { status: 400, body: { - message: 'Invalid body - "name" is required', + message: 'Invalid body - "name" is not allowed to be empty', }, }) }) From 7dbfcc398e4c63824a738ea67a948b765652fc12 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 10:04:25 +0200 Subject: [PATCH 10/43] Implement create --- .../src/api/controllers/rowAction/crud.ts | 12 ++++++-- .../src/api/routes/tests/rowAction.spec.ts | 16 ++++++++-- packages/server/src/db/utils.ts | 8 +++++ packages/server/src/sdk/app/rowActions.ts | 30 +++++++++++++++++++ packages/server/src/sdk/index.ts | 2 ++ packages/types/src/api/web/app/rowAction.ts | 8 ++--- packages/types/src/documents/app/index.ts | 1 + .../types/src/documents/app/rowActions.ts | 8 +++++ 8 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 packages/server/src/sdk/app/rowActions.ts create mode 100644 packages/types/src/documents/app/rowActions.ts diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 83b4215f35..53a417b223 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -25,12 +25,18 @@ export async function find(ctx: Ctx) { } } -export async function create(ctx: Ctx) { +export async function create( + ctx: Ctx +) { const table = await getTable(ctx) - // TODO + const created = await sdk.rowActions.create(table._id!, ctx.request.body) - ctx.status = 204 + ctx.body = { + tableId: table._id!, + ...created, + } + ctx.status = 201 } export function update() { diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index ea97e526d5..bd7932d352 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -1,4 +1,6 @@ import _ from "lodash" +import tk from "timekeeper" + import { CreateRowActionRequest, Table } from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" @@ -9,6 +11,7 @@ describe("/rowsActions", () => { let table: Table beforeAll(async () => { + tk.freeze(new Date()) await config.init() table = await config.api.table.save(setup.structures.basicTable()) @@ -62,14 +65,21 @@ describe("/rowsActions", () => { describe("create", () => { unauthorisedTests() - it("accepts creating new row actions", async () => { + it("accepts creating new row actions for", async () => { const rowAction = createRowActionRequest() const res = await config.api.rowAction.save(table._id!, rowAction, { - status: 204, + status: 201, }) - expect(res).toEqual({}) + expect(res).toEqual({ + _id: `${table._id}_row_actions`, + _rev: expect.stringMatching(/^1-\w+/), + actions: [{ name: rowAction.name }], + tableId: table._id, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) }) it("rejects with bad request when creating with no name", async () => { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 3bd1749d77..32e72ffad7 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -348,3 +348,11 @@ export function isRelationshipColumn( ): column is RelationshipFieldMetadata { return column.type === FieldType.LINK } + +/** + * Generates a new row actions ID. + * @returns The new row actions ID which the row actions doc can be stored under. + */ +export function generateRowActionsID(tableId: string) { + return `${tableId}${SEPARATOR}row_actions` +} diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts new file mode 100644 index 0000000000..bfc4155289 --- /dev/null +++ b/packages/server/src/sdk/app/rowActions.ts @@ -0,0 +1,30 @@ +import { context } from "@budibase/backend-core" + +import { generateRowActionsID } from "../../db/utils" +import { TableRowActions } from "@budibase/types" + +export async function create(tableId: string, rowAction: { name: string }) { + const db = context.getAppDB() + const rowActionsId = generateRowActionsID(tableId) + let doc: TableRowActions + try { + doc = await db.get(rowActionsId) + } catch (e: any) { + if (e.status !== 404) { + throw e + } + + doc = { _id: rowActionsId, actions: [] } + } + + doc.actions.push(rowAction) + await db.put(doc) + + return await get(tableId) +} + +export async function get(tableId: string) { + const db = context.getAppDB() + const rowActionsId = generateRowActionsID(tableId) + return await db.get(rowActionsId) +} diff --git a/packages/server/src/sdk/index.ts b/packages/server/src/sdk/index.ts index c3057e3d4f..a871546b60 100644 --- a/packages/server/src/sdk/index.ts +++ b/packages/server/src/sdk/index.ts @@ -10,6 +10,7 @@ import { default as users } from "./users" import { default as plugins } from "./plugins" import * as views from "./app/views" import * as permissions from "./app/permissions" +import * as rowActions from "./app/rowActions" const sdk = { backups, @@ -24,6 +25,7 @@ const sdk = { views, permissions, links, + rowActions, } // default export for TS diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index fceb606699..fd42de20f8 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -2,11 +2,9 @@ export interface CreateRowActionRequest { name: string } -interface RowAction { - name: string -} - export interface RowActionsResponse { tableId: string - actions: RowAction[] + actions: { + name: string + }[] } diff --git a/packages/types/src/documents/app/index.ts b/packages/types/src/documents/app/index.ts index 3809fba6e5..f6726fd53c 100644 --- a/packages/types/src/documents/app/index.ts +++ b/packages/types/src/documents/app/index.ts @@ -16,3 +16,4 @@ export * from "./links" export * from "./component" export * from "./sqlite" export * from "./snippet" +export * from "./rowActions" diff --git a/packages/types/src/documents/app/rowActions.ts b/packages/types/src/documents/app/rowActions.ts new file mode 100644 index 0000000000..d6dea34f2e --- /dev/null +++ b/packages/types/src/documents/app/rowActions.ts @@ -0,0 +1,8 @@ +import { Document } from "../document" + +export interface TableRowActions extends Document { + _id: string + actions: { + name: string + }[] +} From 0831b7cf3fb6fc206fd4f0f45ec60018145d89bb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 10:13:28 +0200 Subject: [PATCH 11/43] Add extra tests --- .../src/api/routes/tests/rowAction.spec.ts | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index bd7932d352..e817259315 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -1,20 +1,23 @@ import _ from "lodash" import tk from "timekeeper" -import { CreateRowActionRequest, Table } from "@budibase/types" +import { CreateRowActionRequest } from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" describe("/rowsActions", () => { const config = setup.getConfig() - let table: Table + let tableId: string beforeAll(async () => { tk.freeze(new Date()) await config.init() + }) - table = await config.api.table.save(setup.structures.basicTable()) + beforeEach(async () => { + const table = await config.api.table.save(setup.structures.basicTable()) + tableId = table._id! }) afterAll(setup.afterAll) @@ -28,7 +31,7 @@ describe("/rowsActions", () => { function unauthorisedTests() { it("returns unauthorised (401) for unauthenticated requests", async () => { await config.api.rowAction.save( - table._id!, + tableId, createRowActionRequest(), { status: 401, @@ -65,18 +68,41 @@ describe("/rowsActions", () => { describe("create", () => { unauthorisedTests() - it("accepts creating new row actions for", async () => { + it("creates new row actions for tables without existing actions", async () => { const rowAction = createRowActionRequest() - const res = await config.api.rowAction.save(table._id!, rowAction, { + const res = await config.api.rowAction.save(tableId, rowAction, { status: 201, }) expect(res).toEqual({ - _id: `${table._id}_row_actions`, + _id: `${tableId}_row_actions`, _rev: expect.stringMatching(/^1-\w+/), actions: [{ name: rowAction.name }], - tableId: table._id, + tableId: tableId, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) + }) + + it("can create multiple row actions for the same tables", async () => { + const rowActions = generator.unique(() => createRowActionRequest(), 3) + + await config.api.rowAction.save(tableId, rowActions[0], { + status: 201, + }) + await config.api.rowAction.save(tableId, rowActions[1], { + status: 201, + }) + const res = await config.api.rowAction.save(tableId, rowActions[2], { + status: 201, + }) + + expect(res).toEqual({ + _id: `${tableId}_row_actions`, + _rev: expect.stringMatching(/^3-\w+/), + actions: rowActions, + tableId: tableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }) @@ -87,7 +113,7 @@ describe("/rowsActions", () => { name: "", } - await config.api.rowAction.save(table._id!, rowAction, { + await config.api.rowAction.save(tableId, rowAction, { status: 400, body: { message: 'Invalid body - "name" is not allowed to be empty', @@ -100,7 +126,6 @@ describe("/rowsActions", () => { unauthorisedTests() it("returns empty for tables without row actions", async () => { - const tableId = table._id! const res = await config.api.rowAction.find(tableId) expect(res).toEqual({ tableId, actions: [] }) From 645abea2cdb4e27f53829d7636bbb7f3117c0f9e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 10:19:11 +0200 Subject: [PATCH 12/43] Add extra tests --- .../src/api/routes/tests/rowAction.spec.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index e817259315..6281023991 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -85,7 +85,7 @@ describe("/rowsActions", () => { }) }) - it("can create multiple row actions for the same tables", async () => { + it("can create multiple row actions for the same table", async () => { const rowActions = generator.unique(() => createRowActionRequest(), 3) await config.api.rowAction.save(tableId, rowActions[0], { @@ -108,6 +108,41 @@ describe("/rowsActions", () => { }) }) + it("can create row actions for different tables", async () => { + const otherTable = await config.api.table.save( + setup.structures.basicTable() + ) + const otherTableId = otherTable._id! + + const rowAction1 = createRowActionRequest() + const rowAction2 = createRowActionRequest() + + const res1 = await config.api.rowAction.save(tableId, rowAction1, { + status: 201, + }) + const res2 = await config.api.rowAction.save(otherTableId, rowAction2, { + status: 201, + }) + + expect(res1).toEqual({ + _id: `${tableId}_row_actions`, + _rev: expect.stringMatching(/^1-\w+/), + actions: [rowAction1], + tableId: tableId, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) + + expect(res2).toEqual({ + _id: `${otherTableId}_row_actions`, + _rev: expect.stringMatching(/^1-\w+/), + actions: [rowAction2], + tableId: otherTableId, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) + }) + it("rejects with bad request when creating with no name", async () => { const rowAction: CreateRowActionRequest = { name: "", From fac9c35bce61017fdc081383b249449fd2e83e99 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 10:46:29 +0200 Subject: [PATCH 13/43] Simplify tests --- .../src/api/routes/tests/rowAction.spec.ts | 75 +++++++++++-------- .../src/tests/utilities/api/rowAction.ts | 19 +++-- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 6281023991..ebf0e76c09 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -4,6 +4,7 @@ import tk from "timekeeper" import { CreateRowActionRequest } from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" +import { Expectations } from "src/tests/utilities/api/base" describe("/rowsActions", () => { const config = setup.getConfig() @@ -22,6 +23,23 @@ describe("/rowsActions", () => { afterAll(setup.afterAll) + async function createRowAction( + tableId: string, + rowAction: CreateRowActionRequest, + expectations?: Expectations, + opts?: { publicUser?: boolean } + ) { + return await config.api.rowAction.save( + tableId, + rowAction, + { + ...expectations, + status: expectations?.status || 201, + }, + opts + ) + } + function createRowActionRequest(): CreateRowActionRequest { return { name: generator.word(), @@ -30,7 +48,7 @@ describe("/rowsActions", () => { function unauthorisedTests() { it("returns unauthorised (401) for unauthenticated requests", async () => { - await config.api.rowAction.save( + await createRowAction( tableId, createRowActionRequest(), { @@ -48,20 +66,16 @@ describe("/rowsActions", () => { builder: {}, }) await config.withUser(user, async () => { - await config.api.rowAction.save( - generator.guid(), - createRowActionRequest(), - { status: 403 } - ) + await createRowAction(generator.guid(), createRowActionRequest(), { + status: 403, + }) }) }) it("rejects (404) for a non-existing table", async () => { - await config.api.rowAction.save( - generator.guid(), - createRowActionRequest(), - { status: 404 } - ) + await createRowAction(generator.guid(), createRowActionRequest(), { + status: 404, + }) }) } @@ -71,9 +85,7 @@ describe("/rowsActions", () => { it("creates new row actions for tables without existing actions", async () => { const rowAction = createRowActionRequest() - const res = await config.api.rowAction.save(tableId, rowAction, { - status: 201, - }) + const res = await createRowAction(tableId, rowAction, { status: 201 }) expect(res).toEqual({ _id: `${tableId}_row_actions`, @@ -88,15 +100,9 @@ describe("/rowsActions", () => { it("can create multiple row actions for the same table", async () => { const rowActions = generator.unique(() => createRowActionRequest(), 3) - await config.api.rowAction.save(tableId, rowActions[0], { - status: 201, - }) - await config.api.rowAction.save(tableId, rowActions[1], { - status: 201, - }) - const res = await config.api.rowAction.save(tableId, rowActions[2], { - status: 201, - }) + await createRowAction(tableId, rowActions[0]) + await createRowAction(tableId, rowActions[1]) + const res = await createRowAction(tableId, rowActions[2]) expect(res).toEqual({ _id: `${tableId}_row_actions`, @@ -117,12 +123,8 @@ describe("/rowsActions", () => { const rowAction1 = createRowActionRequest() const rowAction2 = createRowActionRequest() - const res1 = await config.api.rowAction.save(tableId, rowAction1, { - status: 201, - }) - const res2 = await config.api.rowAction.save(otherTableId, rowAction2, { - status: 201, - }) + const res1 = await createRowAction(tableId, rowAction1) + const res2 = await createRowAction(otherTableId, rowAction2) expect(res1).toEqual({ _id: `${tableId}_row_actions`, @@ -148,7 +150,7 @@ describe("/rowsActions", () => { name: "", } - await config.api.rowAction.save(tableId, rowAction, { + await createRowAction(tableId, rowAction, { status: 400, body: { message: 'Invalid body - "name" is not allowed to be empty', @@ -165,5 +167,18 @@ describe("/rowsActions", () => { expect(res).toEqual({ tableId, actions: [] }) }) + + it("returns only the", async () => { + const rowActions = generator.unique(() => createRowActionRequest(), 5) + for (const rowAction of rowActions) { + await createRowAction(tableId, rowAction) + } + + const otherTable = await config.api.table.save( + setup.structures.basicTable() + ) + const otherTableId = otherTable._id! + await createRowAction(otherTableId, createRowActionRequest()) + }) }) }) diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index a78633681e..583475b666 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -1,8 +1,4 @@ -import { - CreateRowActionRequest, - RowAction, - RowActionsResponse, -} from "@budibase/types" +import { CreateRowActionRequest, RowActionsResponse } from "@budibase/types" import { Expectations, TestAPI } from "./base" export class RowActionAPI extends TestAPI { @@ -12,11 +8,14 @@ export class RowActionAPI extends TestAPI { expectations?: Expectations, config?: { publicUser?: boolean } ) => { - return await this._post(`/api/tables/${tableId}/actions`, { - body: rowAction, - expectations, - ...config, - }) + return await this._post( + `/api/tables/${tableId}/actions`, + { + body: rowAction, + expectations, + ...config, + } + ) } find = async ( From c565e35b5309350e41ce6bc9f6735c49ece38efc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 10:59:11 +0200 Subject: [PATCH 14/43] Implement find --- .../src/api/controllers/rowAction/crud.ts | 4 ++-- .../src/api/routes/tests/rowAction.spec.ts | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 53a417b223..e58a3030fc 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -17,11 +17,11 @@ async function getTable(ctx: Ctx) { export async function find(ctx: Ctx) { const table = await getTable(ctx) - // TODO + const actions = await sdk.rowActions.get(table._id!) ctx.body = { tableId: table._id!, - actions: [], + ...actions, } } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index ebf0e76c09..cec0410282 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -162,13 +162,7 @@ describe("/rowsActions", () => { describe("find", () => { unauthorisedTests() - it("returns empty for tables without row actions", async () => { - const res = await config.api.rowAction.find(tableId) - - expect(res).toEqual({ tableId, actions: [] }) - }) - - it("returns only the", async () => { + it("returns only the actions for the requested table", async () => { const rowActions = generator.unique(() => createRowActionRequest(), 5) for (const rowAction of rowActions) { await createRowAction(tableId, rowAction) @@ -179,6 +173,18 @@ describe("/rowsActions", () => { ) const otherTableId = otherTable._id! await createRowAction(otherTableId, createRowActionRequest()) + + const response = await config.api.rowAction.find(tableId) + expect(response).toEqual( + expect.objectContaining({ + tableId, + actions: rowActions, + }) + ) + }) + + it("returns 404 for tables without row actions", async () => { + await config.api.rowAction.find(tableId, { status: 404 }) }) }) }) From 2d8361d6fded6f5ea3200978fa08233247f92707 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 11:06:36 +0200 Subject: [PATCH 15/43] Always return when table exists --- packages/server/src/api/controllers/rowAction/crud.ts | 9 ++++++++- packages/server/src/api/routes/tests/rowAction.spec.ts | 10 ++++++++-- packages/server/src/sdk/app/rowActions.ts | 7 +++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index e58a3030fc..b54a4966bf 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -17,8 +17,15 @@ async function getTable(ctx: Ctx) { export async function find(ctx: Ctx) { const table = await getTable(ctx) - const actions = await sdk.rowActions.get(table._id!) + if (!(await sdk.rowActions.docExists(table._id!))) { + ctx.body = { + tableId: table._id!, + actions: [], + } + return + } + const actions = await sdk.rowActions.get(table._id!) ctx.body = { tableId: table._id!, ...actions, diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index cec0410282..b2f322c01f 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -183,8 +183,14 @@ describe("/rowsActions", () => { ) }) - it("returns 404 for tables without row actions", async () => { - await config.api.rowAction.find(tableId, { status: 404 }) + it("returns empty for tables without row actions", async () => { + const response = await config.api.rowAction.find(tableId) + expect(response).toEqual( + expect.objectContaining({ + tableId, + actions: [], + }) + ) }) }) }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index bfc4155289..6d1a98d052 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -28,3 +28,10 @@ export async function get(tableId: string) { const rowActionsId = generateRowActionsID(tableId) return await db.get(rowActionsId) } + +export async function docExists(tableId: string) { + const db = context.getAppDB() + const rowActionsId = generateRowActionsID(tableId) + const result = await db.exists(rowActionsId) + return result +} From 65d7656097935a6593408e9fae0cc3dce2a4c554 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 15:27:48 +0200 Subject: [PATCH 16/43] Unify newid --- packages/server/src/api/controllers/deploy/Deployment.ts | 5 ++--- packages/server/src/automations/utils.ts | 5 ++--- packages/server/src/db/inMemoryView.ts | 5 ++--- packages/server/src/db/newid.ts | 5 ----- packages/server/src/db/utils.ts | 5 +++-- packages/server/src/tests/utilities/TestConfiguration.ts | 4 +++- 6 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 packages/server/src/db/newid.ts diff --git a/packages/server/src/api/controllers/deploy/Deployment.ts b/packages/server/src/api/controllers/deploy/Deployment.ts index 611c82f28b..fe817730b6 100644 --- a/packages/server/src/api/controllers/deploy/Deployment.ts +++ b/packages/server/src/api/controllers/deploy/Deployment.ts @@ -1,5 +1,4 @@ -import newid from "../../../db/newid" -import { context } from "@budibase/backend-core" +import { context, utils } from "@budibase/backend-core" /** * This is used to pass around information about the deployment that is occurring @@ -12,7 +11,7 @@ export default class Deployment { appUrl?: string constructor(id = null) { - this._id = id || newid() + this._id = id || utils.newid() } setVerification(verification: any) { diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts index 4d7e169f52..784632b626 100644 --- a/packages/server/src/automations/utils.ts +++ b/packages/server/src/automations/utils.ts @@ -1,10 +1,9 @@ import { Thread, ThreadType } from "../threads" import { definitions } from "./triggerInfo" import { automationQueue } from "./bullboard" -import newid from "../db/newid" import { updateEntityMetadata } from "../utilities" import { MetadataTypes } from "../constants" -import { db as dbCore, context } from "@budibase/backend-core" +import { db as dbCore, context, utils } from "@budibase/backend-core" import { getAutomationMetadataParams } from "../db/utils" import { cloneDeep } from "lodash/fp" import { quotas } from "@budibase/pro" @@ -207,7 +206,7 @@ export async function enableCronTrigger(appId: any, automation: Automation) { ) } // make a job id rather than letting Bull decide, makes it easier to handle on way out - const jobId = `${appId}_cron_${newid()}` + const jobId = `${appId}_cron_${utils.newid()}` const job: any = await automationQueue.add( { automation, diff --git a/packages/server/src/db/inMemoryView.ts b/packages/server/src/db/inMemoryView.ts index 73e5c622eb..525c4b456e 100644 --- a/packages/server/src/db/inMemoryView.ts +++ b/packages/server/src/db/inMemoryView.ts @@ -1,9 +1,8 @@ -import newid from "./newid" import { Row, Document, DBView } from "@budibase/types" // bypass the main application db config // use in memory pouchdb directly -import { db as dbCore } from "@budibase/backend-core" +import { db as dbCore, utils } from "@budibase/backend-core" const Pouch = dbCore.getPouch({ inMemory: true }) @@ -16,7 +15,7 @@ export async function runView( // use a different ID each time for the DB, make sure they // are always unique for each query, don't want overlap // which could cause 409s - const db = new Pouch(newid()) + const db = new Pouch(utils.newid()) try { // write all the docs to the in memory Pouch (remove revs) await db.bulkDocs( diff --git a/packages/server/src/db/newid.ts b/packages/server/src/db/newid.ts deleted file mode 100644 index bc8f3bb04b..0000000000 --- a/packages/server/src/db/newid.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { v4 } from "uuid" - -export default function (): string { - return v4().replace(/-/g, "") -} diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 32e72ffad7..3ee787b50d 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -1,5 +1,4 @@ -import newid from "./newid" -import { context, db as dbCore } from "@budibase/backend-core" +import { context, db as dbCore, utils } from "@budibase/backend-core" import { DatabaseQueryOpts, Datasource, @@ -15,6 +14,8 @@ import { export { DocumentType, VirtualDocumentType } from "@budibase/types" +const newid = utils.newid + type Optional = string | null export const enum AppStatus { diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 828b389add..3d53149385 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -26,6 +26,7 @@ import { roles, sessions, tenancy, + utils, } from "@budibase/backend-core" import { app as appController, @@ -40,7 +41,6 @@ import { } from "./controllers" import { cleanup } from "../../utilities/fileSystem" -import newid from "../../db/newid" import { generateUserMetadataID } from "../../db/utils" import { startup } from "../../startup" import supertest from "supertest" @@ -74,6 +74,8 @@ import { cloneDeep } from "lodash" import jwt, { Secret } from "jsonwebtoken" import { Server } from "http" +const newid = utils.newid + mocks.licenses.init(pro) // use unlimited license by default From 3bcbb57baaa3d71a8f8e455e08771f9e0ce53479 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 15:32:25 +0200 Subject: [PATCH 17/43] Add id to each individual action --- .../src/api/routes/tests/rowAction.spec.ts | 31 ++++++++++++++++--- packages/server/src/sdk/app/rowActions.ts | 7 +++-- packages/types/src/api/web/app/rowAction.ts | 1 + .../types/src/documents/app/rowActions.ts | 1 + 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index b2f322c01f..e459c45185 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -90,7 +90,12 @@ describe("/rowsActions", () => { expect(res).toEqual({ _id: `${tableId}_row_actions`, _rev: expect.stringMatching(/^1-\w+/), - actions: [{ name: rowAction.name }], + actions: [ + { + id: expect.any(String), + name: rowAction.name, + }, + ], tableId: tableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -107,7 +112,10 @@ describe("/rowsActions", () => { expect(res).toEqual({ _id: `${tableId}_row_actions`, _rev: expect.stringMatching(/^3-\w+/), - actions: rowActions, + actions: rowActions.map(a => ({ + id: expect.any(String), + ...a, + })), tableId: tableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -129,7 +137,12 @@ describe("/rowsActions", () => { expect(res1).toEqual({ _id: `${tableId}_row_actions`, _rev: expect.stringMatching(/^1-\w+/), - actions: [rowAction1], + actions: [ + { + id: expect.any(String), + ...rowAction1, + }, + ], tableId: tableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -138,7 +151,12 @@ describe("/rowsActions", () => { expect(res2).toEqual({ _id: `${otherTableId}_row_actions`, _rev: expect.stringMatching(/^1-\w+/), - actions: [rowAction2], + actions: [ + { + id: expect.any(String), + ...rowAction2, + }, + ], tableId: otherTableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -178,7 +196,10 @@ describe("/rowsActions", () => { expect(response).toEqual( expect.objectContaining({ tableId, - actions: rowActions, + actions: rowActions.map(a => ({ + id: expect.any(String), + ...a, + })), }) ) }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 6d1a98d052..c283c183b8 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -1,4 +1,4 @@ -import { context } from "@budibase/backend-core" +import { context, utils } from "@budibase/backend-core" import { generateRowActionsID } from "../../db/utils" import { TableRowActions } from "@budibase/types" @@ -17,7 +17,10 @@ export async function create(tableId: string, rowAction: { name: string }) { doc = { _id: rowActionsId, actions: [] } } - doc.actions.push(rowAction) + doc.actions.push({ + id: utils.newid(), + ...rowAction, + }) await db.put(doc) return await get(tableId) diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index fd42de20f8..dc2731f9b6 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -5,6 +5,7 @@ export interface CreateRowActionRequest { export interface RowActionsResponse { tableId: string actions: { + id: string name: string }[] } diff --git a/packages/types/src/documents/app/rowActions.ts b/packages/types/src/documents/app/rowActions.ts index d6dea34f2e..5eaf5de5d6 100644 --- a/packages/types/src/documents/app/rowActions.ts +++ b/packages/types/src/documents/app/rowActions.ts @@ -3,6 +3,7 @@ import { Document } from "../document" export interface TableRowActions extends Document { _id: string actions: { + id: string name: string }[] } From de04a6f76debf3a10bd2cd7ae05a99346c8c1ab6 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 15:34:48 +0200 Subject: [PATCH 18/43] Change id --- packages/server/src/api/routes/tests/rowAction.spec.ts | 8 ++++---- packages/server/src/db/utils.ts | 2 +- packages/types/src/documents/document.ts | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index e459c45185..a241a23c6b 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -88,7 +88,7 @@ describe("/rowsActions", () => { const res = await createRowAction(tableId, rowAction, { status: 201 }) expect(res).toEqual({ - _id: `${tableId}_row_actions`, + _id: `ra_${tableId}`, _rev: expect.stringMatching(/^1-\w+/), actions: [ { @@ -110,7 +110,7 @@ describe("/rowsActions", () => { const res = await createRowAction(tableId, rowActions[2]) expect(res).toEqual({ - _id: `${tableId}_row_actions`, + _id: `ra_${tableId}`, _rev: expect.stringMatching(/^3-\w+/), actions: rowActions.map(a => ({ id: expect.any(String), @@ -135,7 +135,7 @@ describe("/rowsActions", () => { const res2 = await createRowAction(otherTableId, rowAction2) expect(res1).toEqual({ - _id: `${tableId}_row_actions`, + _id: `ra_${tableId}`, _rev: expect.stringMatching(/^1-\w+/), actions: [ { @@ -149,7 +149,7 @@ describe("/rowsActions", () => { }) expect(res2).toEqual({ - _id: `${otherTableId}_row_actions`, + _id: `ra_${otherTableId}`, _rev: expect.stringMatching(/^1-\w+/), actions: [ { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index 3ee787b50d..e3fe945863 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -355,5 +355,5 @@ export function isRelationshipColumn( * @returns The new row actions ID which the row actions doc can be stored under. */ export function generateRowActionsID(tableId: string) { - return `${tableId}${SEPARATOR}row_actions` + return `${DocumentType.ROW_ACTIONS}${SEPARATOR}${tableId}` } diff --git a/packages/types/src/documents/document.ts b/packages/types/src/documents/document.ts index 0de4337f4b..23aec05ee5 100644 --- a/packages/types/src/documents/document.ts +++ b/packages/types/src/documents/document.ts @@ -39,6 +39,7 @@ export enum DocumentType { AUDIT_LOG = "al", APP_MIGRATION_METADATA = "_design/migrations", SCIM_LOG = "scimlog", + ROW_ACTIONS = "ra", } // these are the core documents that make up the data, design From 17fc605e4f6b61c376fe7305f7d60a3b1cc4b346 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 16:57:32 +0200 Subject: [PATCH 19/43] Persist as object instead of array --- .../src/api/controllers/rowAction/crud.ts | 13 ++- .../src/api/routes/tests/rowAction.spec.ts | 110 +++++++----------- packages/server/src/sdk/app/rowActions.ts | 19 +-- .../src/tests/utilities/api/rowAction.ts | 8 +- packages/types/src/api/web/app/rowAction.ts | 16 ++- packages/types/src/documents/app/index.ts | 2 +- .../app/{rowActions.ts => rowAction.ts} | 10 +- packages/types/src/documents/document.ts | 1 + 8 files changed, 85 insertions(+), 94 deletions(-) rename packages/types/src/documents/app/{rowActions.ts => rowAction.ts} (62%) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index b54a4966bf..7f3123e120 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -1,6 +1,7 @@ import { CreateRowActionRequest, Ctx, + RowActionResponse, RowActionsResponse, } from "@budibase/types" import sdk from "../../../sdk" @@ -20,7 +21,7 @@ export async function find(ctx: Ctx) { if (!(await sdk.rowActions.docExists(table._id!))) { ctx.body = { tableId: table._id!, - actions: [], + actions: {}, } return } @@ -33,15 +34,19 @@ export async function find(ctx: Ctx) { } export async function create( - ctx: Ctx + ctx: Ctx ) { const table = await getTable(ctx) - const created = await sdk.rowActions.create(table._id!, ctx.request.body) + const { id, ...createdAction } = await sdk.rowActions.create( + table._id!, + ctx.request.body + ) ctx.body = { tableId: table._id!, - ...created, + actionId: id, + ...createdAction, } ctx.status = 201 } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index a241a23c6b..bee6964956 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -1,7 +1,7 @@ import _ from "lodash" import tk from "timekeeper" -import { CreateRowActionRequest } from "@budibase/types" +import { CreateRowActionRequest, RowActionResponse } from "@budibase/types" import * as setup from "./utilities" import { generator } from "@budibase/backend-core/tests" import { Expectations } from "src/tests/utilities/api/base" @@ -46,6 +46,12 @@ describe("/rowsActions", () => { } } + function createRowActionRequests(count: number): CreateRowActionRequest[] { + return generator + .unique(() => generator.word(), count) + .map(name => ({ name })) + } + function unauthorisedTests() { it("returns unauthorised (401) for unauthenticated requests", async () => { await createRowAction( @@ -84,85 +90,49 @@ describe("/rowsActions", () => { it("creates new row actions for tables without existing actions", async () => { const rowAction = createRowActionRequest() - - const res = await createRowAction(tableId, rowAction, { status: 201 }) + const res = await createRowAction(tableId, rowAction, { + status: 201, + }) expect(res).toEqual({ + tableId: tableId, + actionId: expect.stringMatching(/^row_action_\w+/), + ...rowAction, + }) + + expect(await config.api.rowAction.find(tableId)).toEqual({ _id: `ra_${tableId}`, _rev: expect.stringMatching(/^1-\w+/), - actions: [ - { - id: expect.any(String), - name: rowAction.name, - }, - ], tableId: tableId, + actions: { + [res.actionId]: rowAction, + }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }) }) it("can create multiple row actions for the same table", async () => { - const rowActions = generator.unique(() => createRowActionRequest(), 3) + const rowActions = createRowActionRequests(3) + const responses: RowActionResponse[] = [] + for (const action of rowActions) { + responses.push(await createRowAction(tableId, action)) + } - await createRowAction(tableId, rowActions[0]) - await createRowAction(tableId, rowActions[1]) - const res = await createRowAction(tableId, rowActions[2]) - - expect(res).toEqual({ + expect(await config.api.rowAction.find(tableId)).toEqual({ _id: `ra_${tableId}`, _rev: expect.stringMatching(/^3-\w+/), - actions: rowActions.map(a => ({ - id: expect.any(String), - ...a, - })), + actions: { + [responses[0].actionId]: rowActions[0], + [responses[1].actionId]: rowActions[1], + [responses[2].actionId]: rowActions[2], + }, tableId: tableId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }) }) - it("can create row actions for different tables", async () => { - const otherTable = await config.api.table.save( - setup.structures.basicTable() - ) - const otherTableId = otherTable._id! - - const rowAction1 = createRowActionRequest() - const rowAction2 = createRowActionRequest() - - const res1 = await createRowAction(tableId, rowAction1) - const res2 = await createRowAction(otherTableId, rowAction2) - - expect(res1).toEqual({ - _id: `ra_${tableId}`, - _rev: expect.stringMatching(/^1-\w+/), - actions: [ - { - id: expect.any(String), - ...rowAction1, - }, - ], - tableId: tableId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }) - - expect(res2).toEqual({ - _id: `ra_${otherTableId}`, - _rev: expect.stringMatching(/^1-\w+/), - actions: [ - { - id: expect.any(String), - ...rowAction2, - }, - ], - tableId: otherTableId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }) - }) - it("rejects with bad request when creating with no name", async () => { const rowAction: CreateRowActionRequest = { name: "", @@ -181,25 +151,25 @@ describe("/rowsActions", () => { unauthorisedTests() it("returns only the actions for the requested table", async () => { - const rowActions = generator.unique(() => createRowActionRequest(), 5) - for (const rowAction of rowActions) { - await createRowAction(tableId, rowAction) + const rowActions: RowActionResponse[] = [] + for (const action of createRowActionRequests(3)) { + rowActions.push(await createRowAction(tableId, action)) } const otherTable = await config.api.table.save( setup.structures.basicTable() ) - const otherTableId = otherTable._id! - await createRowAction(otherTableId, createRowActionRequest()) + await createRowAction(otherTable._id!, createRowActionRequest()) const response = await config.api.rowAction.find(tableId) expect(response).toEqual( expect.objectContaining({ tableId, - actions: rowActions.map(a => ({ - id: expect.any(String), - ...a, - })), + actions: { + [rowActions[0].actionId]: expect.any(Object), + [rowActions[1].actionId]: expect.any(Object), + [rowActions[2].actionId]: expect.any(Object), + }, }) ) }) @@ -209,7 +179,7 @@ describe("/rowsActions", () => { expect(response).toEqual( expect.objectContaining({ tableId, - actions: [], + actions: {}, }) ) }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index c283c183b8..fa60a075c9 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -1,7 +1,11 @@ import { context, utils } from "@budibase/backend-core" import { generateRowActionsID } from "../../db/utils" -import { TableRowActions } from "@budibase/types" +import { + SEPARATOR, + TableRowActions, + VirtualDocumentType, +} from "@budibase/types" export async function create(tableId: string, rowAction: { name: string }) { const db = context.getAppDB() @@ -14,16 +18,17 @@ export async function create(tableId: string, rowAction: { name: string }) { throw e } - doc = { _id: rowActionsId, actions: [] } + doc = { _id: rowActionsId, actions: {} } } - doc.actions.push({ - id: utils.newid(), - ...rowAction, - }) + const newId = `${VirtualDocumentType.ROW_ACTION}${SEPARATOR}${utils.newid()}` + doc.actions[newId] = rowAction await db.put(doc) - return await get(tableId) + return { + id: newId, + ...rowAction, + } } export async function get(tableId: string) { diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index 583475b666..a7c3932bc4 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -1,4 +1,8 @@ -import { CreateRowActionRequest, RowActionsResponse } from "@budibase/types" +import { + CreateRowActionRequest, + RowActionResponse, + RowActionsResponse, +} from "@budibase/types" import { Expectations, TestAPI } from "./base" export class RowActionAPI extends TestAPI { @@ -8,7 +12,7 @@ export class RowActionAPI extends TestAPI { expectations?: Expectations, config?: { publicUser?: boolean } ) => { - return await this._post( + return await this._post( `/api/tables/${tableId}/actions`, { body: rowAction, diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index dc2731f9b6..3219a5e4b7 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -1,11 +1,15 @@ -export interface CreateRowActionRequest { - name: string +export interface CreateRowActionRequest extends RowActionData {} + +export interface RowActionResponse extends RowActionData { + tableId: string + actionId: string } export interface RowActionsResponse { tableId: string - actions: { - id: string - name: string - }[] + actions: Record +} + +interface RowActionData { + name: string } diff --git a/packages/types/src/documents/app/index.ts b/packages/types/src/documents/app/index.ts index f6726fd53c..2b13676ba1 100644 --- a/packages/types/src/documents/app/index.ts +++ b/packages/types/src/documents/app/index.ts @@ -16,4 +16,4 @@ export * from "./links" export * from "./component" export * from "./sqlite" export * from "./snippet" -export * from "./rowActions" +export * from "./rowAction" diff --git a/packages/types/src/documents/app/rowActions.ts b/packages/types/src/documents/app/rowAction.ts similarity index 62% rename from packages/types/src/documents/app/rowActions.ts rename to packages/types/src/documents/app/rowAction.ts index 5eaf5de5d6..ea55d5dcd2 100644 --- a/packages/types/src/documents/app/rowActions.ts +++ b/packages/types/src/documents/app/rowAction.ts @@ -2,8 +2,10 @@ import { Document } from "../document" export interface TableRowActions extends Document { _id: string - actions: { - id: string - name: string - }[] + actions: Record< + string, + { + name: string + } + > } diff --git a/packages/types/src/documents/document.ts b/packages/types/src/documents/document.ts index 23aec05ee5..f5facfae9d 100644 --- a/packages/types/src/documents/document.ts +++ b/packages/types/src/documents/document.ts @@ -69,6 +69,7 @@ export enum InternalTable { // documents or enriched into existence as part of get requests export enum VirtualDocumentType { VIEW = "view", + ROW_ACTION = "row_action", } export interface Document { From d03a0ebb6814523fbf7218c6cec52434770564fb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 17:08:57 +0200 Subject: [PATCH 20/43] Implement update --- .../src/api/controllers/rowAction/crud.ts | 23 +++++++++-- .../src/api/routes/tests/rowAction.spec.ts | 41 +++++++++++++++++++ packages/server/src/sdk/app/rowActions.ts | 27 +++++++++++- .../src/tests/utilities/api/rowAction.ts | 17 ++++++++ packages/types/src/api/web/app/rowAction.ts | 5 +++ 5 files changed, 107 insertions(+), 6 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 7f3123e120..1b93228f59 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -3,6 +3,7 @@ import { Ctx, RowActionResponse, RowActionsResponse, + UpdateRowActionRequest, } from "@budibase/types" import sdk from "../../../sdk" @@ -38,21 +39,35 @@ export async function create( ) { const table = await getTable(ctx) - const { id, ...createdAction } = await sdk.rowActions.create( + const createdAction = await sdk.rowActions.create( table._id!, ctx.request.body ) ctx.body = { tableId: table._id!, - actionId: id, ...createdAction, } ctx.status = 201 } -export function update() { - throw new Error("Function not implemented.") +export async function update( + ctx: Ctx +) { + const table = await getTable(ctx) + const { actionId } = ctx.params + + const actions = await sdk.rowActions.update( + table._id!, + actionId, + ctx.request.body + ) + + ctx.body = { + tableId: table._id!, + + ...actions, + } } export function remove() { diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index bee6964956..e53d39b553 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -184,4 +184,45 @@ describe("/rowsActions", () => { ) }) }) + + describe("update", () => { + unauthorisedTests() + + it("can update existing actions", async () => { + for (const rowAction of createRowActionRequests(3)) { + await createRowAction(tableId, rowAction) + } + + const persisted = await config.api.rowAction.find(tableId) + + const [actionId, actionData] = _.sample( + Object.entries(persisted.actions) + )! + + const updatedName = generator.word() + + const res = await config.api.rowAction.update(tableId, actionId, { + ...actionData, + name: updatedName, + }) + + expect(res).toEqual({ + tableId, + actionId, + ...actionData, + name: updatedName, + }) + + expect(await config.api.rowAction.find(tableId)).toEqual( + expect.objectContaining({ + actions: expect.objectContaining({ + [actionId]: { + ...actionData, + name: updatedName, + }, + }), + }) + ) + }) + }) }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index fa60a075c9..1d9ed92441 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -1,4 +1,4 @@ -import { context, utils } from "@budibase/backend-core" +import { context, HTTPError, utils } from "@budibase/backend-core" import { generateRowActionsID } from "../../db/utils" import { @@ -26,7 +26,7 @@ export async function create(tableId: string, rowAction: { name: string }) { await db.put(doc) return { - id: newId, + actionId: newId, ...rowAction, } } @@ -43,3 +43,26 @@ export async function docExists(tableId: string) { const result = await db.exists(rowActionsId) return result } +export async function update( + tableId: string, + rowActionId: string, + rowAction: { name: string } +) { + const actionsDoc = await get(tableId) + + if (!actionsDoc.actions[rowActionId]) { + throw new HTTPError( + `Row action '${rowActionId}' not found in '${tableId}'`, + 400 + ) + } + actionsDoc.actions[rowActionId] = rowAction + + const db = context.getAppDB() + await db.put(actionsDoc) + + return { + actionId: rowActionId, + ...rowAction, + } +} diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index a7c3932bc4..1ff352faee 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -35,4 +35,21 @@ export class RowActionAPI extends TestAPI { } ) } + + update = async ( + tableId: string, + rowActionId: string, + rowAction: CreateRowActionRequest, + expectations?: Expectations, + config?: { publicUser?: boolean } + ) => { + return await this._put( + `/api/tables/${tableId}/actions/${rowActionId}`, + { + body: rowAction, + expectations, + ...config, + } + ) + } } diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index 3219a5e4b7..760ab697f8 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -13,3 +13,8 @@ export interface RowActionsResponse { interface RowActionData { name: string } + +export interface UpdateRowActionRequest { + id: string + name: string +} From 9ff3d8cf77dc00fe23726c8916c82d8360e69af2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 17:14:14 +0200 Subject: [PATCH 21/43] Add extra tests --- .../src/api/routes/tests/rowAction.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index e53d39b553..7a3bc26068 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -224,5 +224,31 @@ describe("/rowsActions", () => { }) ) }) + + it("throws Bad Request when trying to update by a non-existing id", async () => { + await createRowAction(tableId, createRowActionRequest()) + + await config.api.rowAction.update( + tableId, + generator.guid(), + createRowActionRequest(), + { status: 400 } + ) + }) + + it("throws Bad Request when trying to update by a via another table id", async () => { + const otherTable = await config.api.table.save( + setup.structures.basicTable() + ) + await createRowAction(otherTable._id!, createRowActionRequest()) + + const action = await createRowAction(tableId, createRowActionRequest()) + await config.api.rowAction.update( + otherTable._id!, + action.actionId, + createRowActionRequest(), + { status: 400 } + ) + }) }) }) From ba2d6fd73b7886977dcf07643c9dc20a7b4ffdf1 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 17:16:14 +0200 Subject: [PATCH 22/43] Renames --- .../src/api/controllers/rowAction/crud.ts | 1 - .../src/api/routes/tests/rowAction.spec.ts | 20 +++++++++---------- packages/server/src/sdk/app/rowActions.ts | 4 ++-- packages/types/src/api/web/app/rowAction.ts | 7 ++----- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index 1b93228f59..b4e5612a7f 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -65,7 +65,6 @@ export async function update( ctx.body = { tableId: table._id!, - ...actions, } } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 7a3bc26068..a1c2973b66 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -95,8 +95,8 @@ describe("/rowsActions", () => { }) expect(res).toEqual({ + id: expect.stringMatching(/^row_action_\w+/), tableId: tableId, - actionId: expect.stringMatching(/^row_action_\w+/), ...rowAction, }) @@ -105,7 +105,7 @@ describe("/rowsActions", () => { _rev: expect.stringMatching(/^1-\w+/), tableId: tableId, actions: { - [res.actionId]: rowAction, + [res.id]: rowAction, }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -123,9 +123,9 @@ describe("/rowsActions", () => { _id: `ra_${tableId}`, _rev: expect.stringMatching(/^3-\w+/), actions: { - [responses[0].actionId]: rowActions[0], - [responses[1].actionId]: rowActions[1], - [responses[2].actionId]: rowActions[2], + [responses[0].id]: rowActions[0], + [responses[1].id]: rowActions[1], + [responses[2].id]: rowActions[2], }, tableId: tableId, createdAt: new Date().toISOString(), @@ -166,9 +166,9 @@ describe("/rowsActions", () => { expect.objectContaining({ tableId, actions: { - [rowActions[0].actionId]: expect.any(Object), - [rowActions[1].actionId]: expect.any(Object), - [rowActions[2].actionId]: expect.any(Object), + [rowActions[0].id]: expect.any(Object), + [rowActions[1].id]: expect.any(Object), + [rowActions[2].id]: expect.any(Object), }, }) ) @@ -207,8 +207,8 @@ describe("/rowsActions", () => { }) expect(res).toEqual({ + id: actionId, tableId, - actionId, ...actionData, name: updatedName, }) @@ -245,7 +245,7 @@ describe("/rowsActions", () => { const action = await createRowAction(tableId, createRowActionRequest()) await config.api.rowAction.update( otherTable._id!, - action.actionId, + action.id, createRowActionRequest(), { status: 400 } ) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index 1d9ed92441..b6fce219c2 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -26,7 +26,7 @@ export async function create(tableId: string, rowAction: { name: string }) { await db.put(doc) return { - actionId: newId, + id: newId, ...rowAction, } } @@ -62,7 +62,7 @@ export async function update( await db.put(actionsDoc) return { - actionId: rowActionId, + id: rowActionId, ...rowAction, } } diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index 760ab697f8..72662fcb49 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -1,8 +1,8 @@ export interface CreateRowActionRequest extends RowActionData {} export interface RowActionResponse extends RowActionData { + id: string tableId: string - actionId: string } export interface RowActionsResponse { @@ -14,7 +14,4 @@ interface RowActionData { name: string } -export interface UpdateRowActionRequest { - id: string - name: string -} +export interface UpdateRowActionRequest extends RowActionData {} From 2035713b9c9a84a2907036f9de838471815aef1d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 11 Jul 2024 17:33:40 +0200 Subject: [PATCH 23/43] Implement delete --- .../src/api/controllers/rowAction/crud.ts | 8 +++- .../src/api/routes/tests/rowAction.spec.ts | 45 +++++++++++++++++++ packages/server/src/sdk/app/rowActions.ts | 17 +++++++ .../src/tests/utilities/api/rowAction.ts | 15 +++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index b4e5612a7f..c946515150 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -69,6 +69,10 @@ export async function update( } } -export function remove() { - throw new Error("Function not implemented.") +export async function remove(ctx: Ctx) { + const table = await getTable(ctx) + const { actionId } = ctx.params + + await sdk.rowActions.remove(table._id!, actionId) + ctx.status = 204 } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index a1c2973b66..2af3be00b5 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -251,4 +251,49 @@ describe("/rowsActions", () => { ) }) }) + + describe("delete", () => { + unauthorisedTests() + + it("can delete existing actions", async () => { + const actions: RowActionResponse[] = [] + for (const rowAction of createRowActionRequests(3)) { + actions.push(await createRowAction(tableId, rowAction)) + } + + const actionToDelete = _.sample(actions)! + + await config.api.rowAction.delete(tableId, actionToDelete.id, { + status: 204, + }) + + expect(await config.api.rowAction.find(tableId)).toEqual( + expect.objectContaining({ + actions: actions + .filter(a => a.id !== actionToDelete.id) + .reduce((acc, c) => ({ ...acc, [c.id]: expect.any(Object) }), {}), + }) + ) + }) + + it("throws Bad Request when trying to delete by a non-existing id", async () => { + await createRowAction(tableId, createRowActionRequest()) + + await config.api.rowAction.delete(tableId, generator.guid(), { + status: 400, + }) + }) + + it("throws Bad Request when trying to delete by a via another table id", async () => { + const otherTable = await config.api.table.save( + setup.structures.basicTable() + ) + await createRowAction(otherTable._id!, createRowActionRequest()) + + const action = await createRowAction(tableId, createRowActionRequest()) + await config.api.rowAction.delete(otherTable._id!, action.id, { + status: 400, + }) + }) + }) }) diff --git a/packages/server/src/sdk/app/rowActions.ts b/packages/server/src/sdk/app/rowActions.ts index b6fce219c2..4e96339b06 100644 --- a/packages/server/src/sdk/app/rowActions.ts +++ b/packages/server/src/sdk/app/rowActions.ts @@ -43,6 +43,7 @@ export async function docExists(tableId: string) { const result = await db.exists(rowActionsId) return result } + export async function update( tableId: string, rowActionId: string, @@ -66,3 +67,19 @@ export async function update( ...rowAction, } } + +export async function remove(tableId: string, rowActionId: string) { + const actionsDoc = await get(tableId) + + if (!actionsDoc.actions[rowActionId]) { + throw new HTTPError( + `Row action '${rowActionId}' not found in '${tableId}'`, + 400 + ) + } + + delete actionsDoc.actions[rowActionId] + + const db = context.getAppDB() + await db.put(actionsDoc) +} diff --git a/packages/server/src/tests/utilities/api/rowAction.ts b/packages/server/src/tests/utilities/api/rowAction.ts index 1ff352faee..b3a07e0a65 100644 --- a/packages/server/src/tests/utilities/api/rowAction.ts +++ b/packages/server/src/tests/utilities/api/rowAction.ts @@ -52,4 +52,19 @@ export class RowActionAPI extends TestAPI { } ) } + + delete = async ( + tableId: string, + rowActionId: string, + expectations?: Expectations, + config?: { publicUser?: boolean } + ) => { + return await this._delete( + `/api/tables/${tableId}/actions/${rowActionId}`, + { + expectations, + ...config, + } + ) + } } From b44397d0275bd0dbf6b0d80a1d2e03ebf970da14 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 Jul 2024 11:29:00 +0200 Subject: [PATCH 24/43] Dont return couch fields --- .../src/api/controllers/rowAction/crud.ts | 30 ++++++------ packages/server/src/api/routes/rowAction.ts | 2 + .../src/api/routes/tests/rowAction.spec.ts | 49 +++++++------------ packages/types/src/api/web/app/rowAction.ts | 13 ++--- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/packages/server/src/api/controllers/rowAction/crud.ts b/packages/server/src/api/controllers/rowAction/crud.ts index c946515150..640bc35378 100644 --- a/packages/server/src/api/controllers/rowAction/crud.ts +++ b/packages/server/src/api/controllers/rowAction/crud.ts @@ -21,17 +21,22 @@ export async function find(ctx: Ctx) { if (!(await sdk.rowActions.docExists(table._id!))) { ctx.body = { - tableId: table._id!, actions: {}, } return } - const actions = await sdk.rowActions.get(table._id!) - ctx.body = { - tableId: table._id!, - ...actions, + const { actions } = await sdk.rowActions.get(table._id!) + const result: RowActionsResponse = { + actions: Object.entries(actions).reduce>( + (acc, [key, action]) => ({ + ...acc, + [key]: { id: key, tableId: table._id!, ...action }, + }), + {} + ), } + ctx.body = result } export async function create( @@ -39,10 +44,9 @@ export async function create( ) { const table = await getTable(ctx) - const createdAction = await sdk.rowActions.create( - table._id!, - ctx.request.body - ) + const createdAction = await sdk.rowActions.create(table._id!, { + name: ctx.request.body.name, + }) ctx.body = { tableId: table._id!, @@ -57,11 +61,9 @@ export async function update( const table = await getTable(ctx) const { actionId } = ctx.params - const actions = await sdk.rowActions.update( - table._id!, - actionId, - ctx.request.body - ) + const actions = await sdk.rowActions.update(table._id!, actionId, { + name: ctx.request.body.name, + }) ctx.body = { tableId: table._id!, diff --git a/packages/server/src/api/routes/rowAction.ts b/packages/server/src/api/routes/rowAction.ts index 18a87cd677..3ec00dff4d 100644 --- a/packages/server/src/api/routes/rowAction.ts +++ b/packages/server/src/api/routes/rowAction.ts @@ -11,6 +11,8 @@ export function rowActionValidator() { return middleware.joiValidator.body( Joi.object({ name: Joi.string().required(), + id: Joi.optional(), + tableId: Joi.optional(), }) ) } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 2af3be00b5..976dc950e4 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -101,14 +101,13 @@ describe("/rowsActions", () => { }) expect(await config.api.rowAction.find(tableId)).toEqual({ - _id: `ra_${tableId}`, - _rev: expect.stringMatching(/^1-\w+/), - tableId: tableId, actions: { - [res.id]: rowAction, + [res.id]: { + ...rowAction, + id: res.id, + tableId: tableId, + }, }, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }) }) @@ -120,16 +119,11 @@ describe("/rowsActions", () => { } expect(await config.api.rowAction.find(tableId)).toEqual({ - _id: `ra_${tableId}`, - _rev: expect.stringMatching(/^3-\w+/), actions: { - [responses[0].id]: rowActions[0], - [responses[1].id]: rowActions[1], - [responses[2].id]: rowActions[2], + [responses[0].id]: { ...rowActions[0], id: responses[0].id, tableId }, + [responses[1].id]: { ...rowActions[1], id: responses[1].id, tableId }, + [responses[2].id]: { ...rowActions[2], id: responses[2].id, tableId }, }, - tableId: tableId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }) }) @@ -162,26 +156,20 @@ describe("/rowsActions", () => { await createRowAction(otherTable._id!, createRowActionRequest()) const response = await config.api.rowAction.find(tableId) - expect(response).toEqual( - expect.objectContaining({ - tableId, - actions: { - [rowActions[0].id]: expect.any(Object), - [rowActions[1].id]: expect.any(Object), - [rowActions[2].id]: expect.any(Object), - }, - }) - ) + expect(response).toEqual({ + actions: { + [rowActions[0].id]: expect.any(Object), + [rowActions[1].id]: expect.any(Object), + [rowActions[2].id]: expect.any(Object), + }, + }) }) it("returns empty for tables without row actions", async () => { const response = await config.api.rowAction.find(tableId) - expect(response).toEqual( - expect.objectContaining({ - tableId, - actions: {}, - }) - ) + expect(response).toEqual({ + actions: {}, + }) }) }) @@ -209,7 +197,6 @@ describe("/rowsActions", () => { expect(res).toEqual({ id: actionId, tableId, - ...actionData, name: updatedName, }) diff --git a/packages/types/src/api/web/app/rowAction.ts b/packages/types/src/api/web/app/rowAction.ts index 72662fcb49..ba95ba6b95 100644 --- a/packages/types/src/api/web/app/rowAction.ts +++ b/packages/types/src/api/web/app/rowAction.ts @@ -1,4 +1,8 @@ +interface RowActionData { + name: string +} export interface CreateRowActionRequest extends RowActionData {} +export interface UpdateRowActionRequest extends RowActionData {} export interface RowActionResponse extends RowActionData { id: string @@ -6,12 +10,5 @@ export interface RowActionResponse extends RowActionData { } export interface RowActionsResponse { - tableId: string - actions: Record + actions: Record } - -interface RowActionData { - name: string -} - -export interface UpdateRowActionRequest extends RowActionData {} From 50c8449f4ba5157587f604ef8b8d5955d15ba7e8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 Jul 2024 12:17:05 +0200 Subject: [PATCH 25/43] Add extra test --- .../src/middleware/joi-validator.ts | 11 +++++--- packages/server/src/api/routes/rowAction.ts | 5 ++-- .../src/api/routes/tests/rowAction.spec.ts | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/backend-core/src/middleware/joi-validator.ts b/packages/backend-core/src/middleware/joi-validator.ts index 5047cdbbc1..7575e7e778 100644 --- a/packages/backend-core/src/middleware/joi-validator.ts +++ b/packages/backend-core/src/middleware/joi-validator.ts @@ -4,8 +4,9 @@ import { Ctx } from "@budibase/types" function validate( schema: Joi.ObjectSchema | Joi.ArraySchema, property: string, - opts: { errorPrefix: string } = { errorPrefix: `Invalid ${property}` } + opts?: { errorPrefix?: string; allowUnknown?: boolean } ) { + const errorPrefix = opts?.errorPrefix || `Invalid ${property}` // Return a Koa middleware function return (ctx: Ctx, next: any) => { if (!schema) { @@ -28,10 +29,12 @@ function validate( }) } - const { error } = schema.validate(params) + const { error } = schema.validate(params, { + allowUnknown: opts?.allowUnknown, + }) if (error) { let message = error.message - if (opts.errorPrefix) { + if (errorPrefix) { message = `Invalid ${property} - ${message}` } ctx.throw(400, message) @@ -42,7 +45,7 @@ function validate( export function body( schema: Joi.ObjectSchema | Joi.ArraySchema, - opts?: { errorPrefix: string } + opts?: { errorPrefix?: string; allowUnknown?: boolean } ) { return validate(schema, "body", opts) } diff --git a/packages/server/src/api/routes/rowAction.ts b/packages/server/src/api/routes/rowAction.ts index 3ec00dff4d..f4f20822d1 100644 --- a/packages/server/src/api/routes/rowAction.ts +++ b/packages/server/src/api/routes/rowAction.ts @@ -11,9 +11,8 @@ export function rowActionValidator() { return middleware.joiValidator.body( Joi.object({ name: Joi.string().required(), - id: Joi.optional(), - tableId: Joi.optional(), - }) + }), + { allowUnknown: true } ) } diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 976dc950e4..68dcc484c4 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -139,6 +139,34 @@ describe("/rowsActions", () => { }, }) }) + + it("ignores not valid row action data", async () => { + const rowAction = createRowActionRequest() + const dirtyRowAction = { + ...rowAction, + id: generator.guid(), + valueToIgnore: generator.word(), + } + const res = await createRowAction(tableId, dirtyRowAction, { + status: 201, + }) + + expect(res).toEqual({ + id: expect.any(String), + tableId, + ...rowAction, + }) + + expect(await config.api.rowAction.find(tableId)).toEqual({ + actions: { + [res.id]: { + ...rowAction, + id: res.id, + tableId: tableId, + }, + }, + }) + }) }) describe("find", () => { From f06d0a5cd6e5fa3e8ecd334e4ef7b17ffe843b8f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 Jul 2024 12:17:32 +0200 Subject: [PATCH 26/43] Clean --- packages/server/src/api/routes/tests/rowAction.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/rowAction.spec.ts b/packages/server/src/api/routes/tests/rowAction.spec.ts index 68dcc484c4..6fa0b0b86f 100644 --- a/packages/server/src/api/routes/tests/rowAction.spec.ts +++ b/packages/server/src/api/routes/tests/rowAction.spec.ts @@ -160,9 +160,9 @@ describe("/rowsActions", () => { expect(await config.api.rowAction.find(tableId)).toEqual({ actions: { [res.id]: { - ...rowAction, id: res.id, tableId: tableId, + ...rowAction, }, }, }) From 1c69cfaeda18e81640fcf50ae2c6657e324e4b18 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Fri, 12 Jul 2024 12:32:36 +0200 Subject: [PATCH 27/43] Fix initialisation --- packages/backend-core/src/middleware/joi-validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/middleware/joi-validator.ts b/packages/backend-core/src/middleware/joi-validator.ts index 7575e7e778..a85c0e7108 100644 --- a/packages/backend-core/src/middleware/joi-validator.ts +++ b/packages/backend-core/src/middleware/joi-validator.ts @@ -6,7 +6,7 @@ function validate( property: string, opts?: { errorPrefix?: string; allowUnknown?: boolean } ) { - const errorPrefix = opts?.errorPrefix || `Invalid ${property}` + const errorPrefix = opts?.errorPrefix ?? `Invalid ${property}` // Return a Koa middleware function return (ctx: Ctx, next: any) => { if (!schema) { From 19ff925dec5f11cbdfc616bf084aa4fd1de209d7 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 12 Jul 2024 18:09:29 +0100 Subject: [PATCH 28/43] Adding test cases for less than/greater than or equal to, there was no test cases confirming these definitely work. Also aligning the test cases with how the frontend performs these tests today. --- .../src/api/routes/tests/search.spec.ts | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index e2335cb71c..ae35c4c5eb 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -4,7 +4,12 @@ import { getDatasource, knexClient, } from "../../../integrations/tests/utils" -import { db as dbCore, utils } from "@budibase/backend-core" +import { + db as dbCore, + MAX_VALID_DATE, + MIN_VALID_DATE, + utils, +} from "@budibase/backend-core" import * as setup from "./utilities" import { @@ -1098,21 +1103,37 @@ describe.each([ }).toFindNothing() }) - // We never implemented half-open ranges in Lucene. - !isLucene && - it("can search using just a low value", async () => { - await expectQuery({ - range: { age: { low: 5 } }, - }).toContainExactly([{ age: 10 }]) - }) + it("greater than equal to", async () => { + await expectQuery({ + range: { + age: { low: 10, high: Number.MAX_SAFE_INTEGER }, + }, + }).toContainExactly([{ age: 10 }]) + }) - // We never implemented half-open ranges in Lucene. - !isLucene && - it("can search using just a high value", async () => { - await expectQuery({ - range: { age: { high: 5 } }, - }).toContainExactly([{ age: 1 }]) - }) + it("greater than", async () => { + await expectQuery({ + range: { + age: { low: 5, high: Number.MAX_SAFE_INTEGER }, + }, + }).toContainExactly([{ age: 10 }]) + }) + + it("less than equal to", async () => { + await expectQuery({ + range: { + age: { high: 1, low: Number.MIN_SAFE_INTEGER }, + }, + }).toContainExactly([{ age: 1 }]) + }) + + it("less than", async () => { + await expectQuery({ + range: { + age: { high: 5, low: Number.MIN_SAFE_INTEGER }, + }, + }).toContainExactly([{ age: 1 }]) + }) }) describe("sort", () => { @@ -1232,21 +1253,29 @@ describe.each([ }).toFindNothing() }) - // We never implemented half-open ranges in Lucene. - !isLucene && - it("can search using just a low value", async () => { - await expectQuery({ - range: { dob: { low: JAN_5TH } }, - }).toContainExactly([{ dob: JAN_10TH }]) - }) + it("greater than equal to", async () => { + await expectQuery({ + range: { dob: { low: JAN_10TH, high: MAX_VALID_DATE.toISOString() } }, + }).toContainExactly([{ dob: JAN_10TH }]) + }) - // We never implemented half-open ranges in Lucene. - !isLucene && - it("can search using just a high value", async () => { - await expectQuery({ - range: { dob: { high: JAN_5TH } }, - }).toContainExactly([{ dob: JAN_1ST }]) - }) + it("greater than", async () => { + await expectQuery({ + range: { dob: { low: JAN_5TH, high: MAX_VALID_DATE.toISOString() } }, + }).toContainExactly([{ dob: JAN_10TH }]) + }) + + it("less than equal to", async () => { + await expectQuery({ + range: { dob: { high: JAN_1ST, low: MIN_VALID_DATE.toISOString() } }, + }).toContainExactly([{ dob: JAN_1ST }]) + }) + + it("less than", async () => { + await expectQuery({ + range: { dob: { high: JAN_5TH, low: MIN_VALID_DATE.toISOString() } }, + }).toContainExactly([{ dob: JAN_1ST }]) + }) }) describe("sort", () => { From 0a50ab284f2c76d38ea05ded64a0efc81e19bf55 Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 15 Jul 2024 11:12:26 +0100 Subject: [PATCH 29/43] Title text alignment was not being saved to the app metadata. --- .../[componentId]/_components/Navigation/index.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte index a0e0f1d93a..7c8b03caca 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Navigation/index.svelte @@ -124,7 +124,7 @@ nav.syncAppNavigation({ textAlign: align })} + onChange={align => update("textAlign", align)} value={$nav.textAlign} props={{ options: alignmentOptions, From ab679ac85dcaf7164b1178ca986369bff4123a8d Mon Sep 17 00:00:00 2001 From: Conor Webb <126772285+ConorWebb96@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:01:08 +0100 Subject: [PATCH 30/43] Add the option to use bindable inputs in your confirmations (#14138) Co-authored-by: deanhannigan --- .../actions/DeleteRow.svelte | 32 ++++++++--- .../actions/DuplicateRow.svelte | 34 +++++++---- .../actions/ExecuteQuery.svelte | 56 +++++++++++-------- .../ButtonActionEditor/actions/SaveRow.svelte | 33 ++++++++--- 4 files changed, 106 insertions(+), 49 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte index fd3521d597..410e4bb56e 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/DeleteRow.svelte @@ -1,5 +1,5 @@