From 1a94d2dca9efc52741556e59cab75da14faa12ba Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 May 2024 14:21:58 +0100 Subject: [PATCH 1/7] wip --- .../tests/data/agency-client-portal.tar.gz | Bin 0 -> 37078 bytes .../src/api/routes/tests/templates.spec.js | 24 ------ .../src/api/routes/tests/templates.spec.ts | 80 ++++++++++++++++++ .../server/src/tests/utilities/api/index.ts | 3 + .../src/tests/utilities/api/template.ts | 8 ++ 5 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 packages/server/src/api/routes/tests/data/agency-client-portal.tar.gz delete mode 100644 packages/server/src/api/routes/tests/templates.spec.js create mode 100644 packages/server/src/api/routes/tests/templates.spec.ts create mode 100644 packages/server/src/tests/utilities/api/template.ts diff --git a/packages/server/src/api/routes/tests/data/agency-client-portal.tar.gz b/packages/server/src/api/routes/tests/data/agency-client-portal.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e839dc73eef9102b1c8847a633b3b394b91a363f GIT binary patch literal 37078 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 Me=>=giU0}%0PkSt=l}o! literal 0 HcmV?d00001 diff --git a/packages/server/src/api/routes/tests/templates.spec.js b/packages/server/src/api/routes/tests/templates.spec.js deleted file mode 100644 index 1406a75c59..0000000000 --- a/packages/server/src/api/routes/tests/templates.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -const setup = require("./utilities") - -describe("/templates", () => { - let request = setup.getRequest() - let config = setup.getConfig() - - afterAll(setup.afterAll) - - beforeAll(async () => { - await config.init() - }) - - describe("fetch", () => { - it("should be able to fetch templates", async () => { - const res = await request - .get(`/api/templates`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - // this test is quite light right now, templates aren't heavily utilised yet - expect(Array.isArray(res.body)).toEqual(true) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/templates.spec.ts b/packages/server/src/api/routes/tests/templates.spec.ts new file mode 100644 index 0000000000..8a5cfb3537 --- /dev/null +++ b/packages/server/src/api/routes/tests/templates.spec.ts @@ -0,0 +1,80 @@ +import * as setup from "./utilities" +import nock from "nock" + +interface App { + background: string + icon: string + category: string + description: string + name: string + url: string + type: string + key: string + image: string +} + +interface Manifest { + templates: { + app: { [key: string]: App } + } +} + +function setManifest(manifest: Manifest) { + nock("https://prod-budi-templates.s3-eu-west-1.amazonaws.com") + .get("/manifest.json") + .reply(200, manifest) +} + +function mockApp(key: string, tarPath: string) { + nock("https://prod-budi-templates.s3-eu-west-1.amazonaws.com") + .get(`/app/${key}.tar.gz`) + .replyWithFile(200, tarPath) +} + +function mockAgencyClientPortal() { + setManifest({ + templates: { + app: { + "Agency Client Portal": { + background: "#20a3a8", + icon: "Project", + category: "Portals", + description: + "Manage clients, streamline communications, and securely share files.", + name: "Agency Client Portal", + url: "https://budibase.com/portals/templates/agency-client-portal-template/", + type: "app", + key: "app/agency-client-portal", + image: + "https://prod-budi-templates.s3.eu-west-1.amazonaws.com/images/agency-client-portal.png", + }, + }, + }, + }) + + mockApp( + "agency-client-portal", + "packages/server/src/api/routes/tests/data/agency-client-portal.tar.gz" + ) +} + +describe("/templates", () => { + let config = setup.getConfig() + + afterAll(setup.afterAll) + beforeAll(async () => { + await config.init() + }) + beforeEach(() => { + nock.cleanAll() + mockAgencyClientPortal() + }) + + describe("fetch", () => { + it("should be able to fetch templates", async () => { + const res = await config.api.templates.fetch() + expect(res).toHaveLength(1) + expect(res[0].name).toBe("Agency Client Portal") + }) + }) +}) diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index d66acd86fd..554fa36588 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -12,6 +12,7 @@ import { AttachmentAPI } from "./attachment" import { UserAPI } from "./user" import { QueryAPI } from "./query" import { RoleAPI } from "./role" +import { TemplateAPI } from "./template" export default class API { table: TableAPI @@ -27,6 +28,7 @@ export default class API { user: UserAPI query: QueryAPI roles: RoleAPI + templates: TemplateAPI constructor(config: TestConfiguration) { this.table = new TableAPI(config) @@ -42,5 +44,6 @@ export default class API { this.user = new UserAPI(config) this.query = new QueryAPI(config) this.roles = new RoleAPI(config) + this.templates = new TemplateAPI(config) } } diff --git a/packages/server/src/tests/utilities/api/template.ts b/packages/server/src/tests/utilities/api/template.ts new file mode 100644 index 0000000000..6dc2a7a4da --- /dev/null +++ b/packages/server/src/tests/utilities/api/template.ts @@ -0,0 +1,8 @@ +import { Template } from "@budibase/types" +import { Expectations, TestAPI } from "./base" + +export class TemplateAPI extends TestAPI { + fetch = async (expectations?: Expectations): Promise => { + return await this._get("/api/templates", { expectations }) + } +} From fb4cecc93fa9087e60d345deb7f5fa3daa6ad0fc Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 29 May 2024 17:07:29 +0100 Subject: [PATCH 2/7] Update template tests to make sure importing templates works for SQS. --- .../src/api/routes/tests/templates.spec.ts | 55 +++++++++++++++++-- .../src/tests/utilities/TestConfiguration.ts | 10 ++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/server/src/api/routes/tests/templates.spec.ts b/packages/server/src/api/routes/tests/templates.spec.ts index 8a5cfb3537..680ddb39d7 100644 --- a/packages/server/src/api/routes/tests/templates.spec.ts +++ b/packages/server/src/api/routes/tests/templates.spec.ts @@ -1,5 +1,7 @@ import * as setup from "./utilities" +import path from "path" import nock from "nock" +import { generator } from "@budibase/backend-core/tests" interface App { background: string @@ -27,7 +29,7 @@ function setManifest(manifest: Manifest) { function mockApp(key: string, tarPath: string) { nock("https://prod-budi-templates.s3-eu-west-1.amazonaws.com") - .get(`/app/${key}.tar.gz`) + .get(`/templates/app/${key}.tar.gz`) .replyWithFile(200, tarPath) } @@ -54,7 +56,7 @@ function mockAgencyClientPortal() { mockApp( "agency-client-portal", - "packages/server/src/api/routes/tests/data/agency-client-portal.tar.gz" + path.resolve(__dirname, "data", "agency-client-portal.tar.gz") ) } @@ -72,9 +74,52 @@ describe("/templates", () => { describe("fetch", () => { it("should be able to fetch templates", async () => { - const res = await config.api.templates.fetch() - expect(res).toHaveLength(1) - expect(res[0].name).toBe("Agency Client Portal") + const templates = await config.api.templates.fetch() + expect(templates).toHaveLength(1) + expect(templates[0].name).toBe("Agency Client Portal") }) }) + + describe("create app from template", () => { + it.each(["sqs", "lucene"])( + `should be able to create an app from a template (%s)`, + async source => { + const env = { + SQS_SEARCH_ENABLE: source === "sqs" ? "true" : "false", + } + + await config.withEnv(env, async () => { + const name = generator.guid().replaceAll("-", "") + const url = `/${name}` + + const app = await config.api.application.create({ + name, + url, + useTemplate: "true", + templateName: "Agency Client Portal", + templateKey: "app/agency-client-portal", + }) + expect(app.name).toBe(name) + expect(app.url).toBe(url) + + await config.withApp(app, async () => { + const tables = await config.api.table.fetch() + expect(tables).toHaveLength(2) + + tables.sort((a, b) => a.name.localeCompare(b.name)) + const [agencyProjects, users] = tables + expect(agencyProjects.name).toBe("Agency Projects") + expect(users.name).toBe("Users") + + const { rows } = await config.api.row.search(agencyProjects._id!, { + tableId: agencyProjects._id!, + query: {}, + }) + + expect(rows).toHaveLength(3) + }) + }) + } + ) + }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 58dbecd197..325d911f07 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -314,6 +314,16 @@ export default class TestConfiguration { } } + async withApp(app: App | string, f: () => Promise) { + const oldAppId = this.appId + this.appId = typeof app === "string" ? app : app.appId + try { + return await f() + } finally { + this.appId = oldAppId + } + } + // UTILS _req | void, Res>( From dee797656a10f6be8678c196d793b417b841ed4c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 30 May 2024 14:31:45 +0100 Subject: [PATCH 3/7] Update account-portal submodule to latest master. --- packages/account-portal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account-portal b/packages/account-portal index c167c331ff..a03225549e 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit c167c331ff9b8161fc18e2ecbaaf1ea5815ba964 +Subproject commit a03225549e3ce61f43d0da878da162e08941b939 From 6f02185abe5947dd3e62e2e17b9489c77db31242 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 30 May 2024 14:33:10 +0100 Subject: [PATCH 4/7] Put pro back in line with master. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 1879d8686b..5189b83bea 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1879d8686b1d9392707595a02cdd4981923e7f99 +Subproject commit 5189b83bea1868574ff7f4c51fe5db38a11badb8 From 25a4e1d99948f19ca1e7f9d274492dca78c25a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 19:53:00 +0000 Subject: [PATCH 5/7] Bump mysql2 from 3.9.7 to 3.9.8 in /packages/server Bumps [mysql2](https://github.com/sidorares/node-mysql2) from 3.9.7 to 3.9.8. - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v3.9.7...v3.9.8) --- updated-dependencies: - dependency-name: mysql2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index e816ad3f18..bd5a82cb29 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -97,7 +97,7 @@ "memorystream": "0.3.1", "mongodb": "^6.3.0", "mssql": "10.0.1", - "mysql2": "3.9.7", + "mysql2": "3.9.8", "node-fetch": "2.6.7", "object-sizeof": "2.6.1", "openai": "^3.2.1", From 1777ac4b046903f2905b93a724eb39ead0e179aa Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 31 May 2024 14:59:15 +0100 Subject: [PATCH 6/7] Fix mariadb healthcheck. --- packages/server/src/integrations/tests/utils/mariadb.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/integrations/tests/utils/mariadb.ts b/packages/server/src/integrations/tests/utils/mariadb.ts index fcd79b8e56..c4dd4cf43b 100644 --- a/packages/server/src/integrations/tests/utils/mariadb.ts +++ b/packages/server/src/integrations/tests/utils/mariadb.ts @@ -18,7 +18,7 @@ class MariaDBWaitStrategy extends AbstractWaitStrategy { await logs.waitUntilReady(container, boundPorts, startTime) const command = Wait.forSuccessfulCommand( - `mysqladmin ping -h localhost -P 3306 -u root -ppassword` + `/usr/local/bin/healthcheck.sh --innodb_initialized` ) await command.waitUntilReady(container) } From cbb3c9aa934fb6adee76154606540c7cdd9b7448 Mon Sep 17 00:00:00 2001 From: melohagan <101575380+melohagan@users.noreply.github.com> Date: Fri, 31 May 2024 15:34:08 +0100 Subject: [PATCH 7/7] Allow a user invite to be revoked (#13805) * Add free_trial to deploy camunda script * Allow user invites to be deleted * Refactor to pass invite codes * lint * update account-portal * yarn lock * users terminology instead of rows and invites --- .../builder/portal/users/users/index.svelte | 33 ++++++++++--- packages/builder/src/stores/portal/users.js | 5 ++ packages/frontend-core/src/api/user.js | 10 ++++ packages/types/src/api/web/user.ts | 5 ++ .../src/api/controllers/global/users.ts | 16 +++++++ .../worker/src/api/routes/global/users.ts | 5 ++ yarn.lock | 47 +++++++++++++++++++ 7 files changed, 115 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/users/users/index.svelte b/packages/builder/src/pages/builder/portal/users/users/index.svelte index c2fbce5747..58da310104 100644 --- a/packages/builder/src/pages/builder/portal/users/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/index.svelte @@ -60,6 +60,7 @@ userLimitReachedModal let searchEmail = undefined let selectedRows = [] + let selectedInvites = [] let bulkSaveResponse let customRenderers = [ { column: "email", component: EmailTableRenderer }, @@ -123,7 +124,7 @@ return {} } let pendingSchema = JSON.parse(JSON.stringify(tblSchema)) - pendingSchema.email.displayName = "Pending Invites" + pendingSchema.email.displayName = "Pending Users" return pendingSchema } @@ -132,6 +133,7 @@ const { admin, builder, userGroups, apps } = invite.info return { + _id: invite.code, email: invite.email, builder, admin, @@ -260,9 +262,26 @@ return } - await users.bulkDelete(ids) - notifications.success(`Successfully deleted ${selectedRows.length} rows`) + if (ids.length > 0) { + await users.bulkDelete(ids) + } + + if (selectedInvites.length > 0) { + await users.removeInvites( + selectedInvites.map(invite => ({ + code: invite._id, + })) + ) + pendingInvites = await users.getInvites() + } + + notifications.success( + `Successfully deleted ${ + selectedRows.length + selectedInvites.length + } users` + ) selectedRows = [] + selectedInvites = [] await fetch.refresh() } catch (error) { notifications.error("Error deleting users") @@ -328,15 +347,15 @@ {/if}
- - {#if selectedRows.length > 0} + {#if selectedRows.length > 0 || selectedInvites.length > 0} {/if} +
({ }) }, + /** + * Removes multiple user invites from Redis cache + */ + removeUserInvites: async inviteCodes => { + return await API.post({ + url: "/api/global/users/multi/invite/delete", + body: inviteCodes, + }) + }, + /** * Accepts an invite to join the platform and creates a user. * @param inviteCode the invite code sent in the email diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index f59bda133b..471ca86616 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -45,7 +45,12 @@ export interface InviteUserRequest { userInfo: any } +export interface DeleteInviteUserRequest { + code: string +} + export type InviteUsersRequest = InviteUserRequest[] +export type DeleteInviteUsersRequest = DeleteInviteUserRequest[] export interface InviteUsersResponse { successful: { email: string }[] diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 46bf13284e..cd69281f56 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -10,6 +10,8 @@ import { CreateAdminUserRequest, CreateAdminUserResponse, Ctx, + DeleteInviteUserRequest, + DeleteInviteUsersRequest, InviteUserRequest, InviteUsersRequest, InviteUsersResponse, @@ -335,6 +337,20 @@ export const inviteMultiple = async (ctx: Ctx) => { ctx.body = await userSdk.invite(ctx.request.body) } +export const removeMultipleInvites = async ( + ctx: Ctx +) => { + const inviteCodesToRemove = ctx.request.body.map( + (invite: DeleteInviteUserRequest) => invite.code + ) + for (const code of inviteCodesToRemove) { + await cache.invite.deleteCode(code) + } + ctx.body = { + message: "User invites successfully removed.", + } +} + export const checkInvite = async (ctx: any) => { const { code } = ctx.params let invite diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index b40c491830..0372c187f8 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -108,6 +108,11 @@ router buildInviteMultipleValidation(), controller.inviteMultiple ) + .post( + "/api/global/users/multi/invite/delete", + auth.builderOrAdmin, + controller.removeMultipleInvites + ) // non-global endpoints .get("/api/global/users/invite/:code", controller.checkInvite) diff --git a/yarn.lock b/yarn.lock index 677b7cb441..9daf499918 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11904,6 +11904,17 @@ glob@^10.0.0, glob@^10.2.2: minipass "^7.0.4" path-scurry "^1.10.2" +glob@^10.3.7: + version "10.4.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.1.tgz#0cfb01ab6a6b438177bfe6a58e2576f6efe909c2" + integrity sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + path-scurry "^1.11.1" + glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -13472,6 +13483,15 @@ jackspeak@^2.3.6: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.1.2.tgz#eada67ea949c6b71de50f1b09c92a961897b90ab" + integrity sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -15751,6 +15771,13 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -15845,6 +15872,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== +minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -17378,6 +17410,14 @@ path-scurry@^1.10.2, path-scurry@^1.6.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@1.x: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -19318,6 +19358,13 @@ rimraf@^4.4.1: dependencies: glob "^9.2.0" +rimraf@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.7.tgz#27bddf202e7d89cb2e0381656380d1734a854a74" + integrity sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg== + dependencies: + glob "^10.3.7" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"