From 6d24a30d91af98cb78611f893bb3442522b8af50 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 5 Jul 2023 18:28:04 +0100 Subject: [PATCH 001/821] Basic refactor work, the types required for the new API endpoints. --- packages/types/src/api/web/user.ts | 7 +++++++ packages/types/src/documents/global/user.ts | 2 ++ packages/worker/src/api/controllers/global/users.ts | 8 ++++++++ packages/worker/src/api/routes/global/users.ts | 3 +++ 4 files changed, 20 insertions(+) diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index 619362805a..4a27f781af 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -85,3 +85,10 @@ export interface AcceptUserInviteResponse { export interface SyncUserRequest { previousUser?: User } + +export interface AddAppBuilderRequest { + userId: string + appId: string +} + +export interface RemoveAppBuilderRequest {} diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index 9b4aadf404..cae3c0e6e2 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -43,9 +43,11 @@ export interface User extends Document { roles: UserRoles builder?: { global: boolean + apps?: string[] } admin?: { global: boolean + apps?: string[] } password?: string status?: UserStatus diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 320f7be01a..b3720d578f 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -8,6 +8,8 @@ import env from "../../../environment" import { AcceptUserInviteRequest, AcceptUserInviteResponse, + AddAppBuilderRequest, + RemoveAppBuilderRequest, BulkUserRequest, BulkUserResponse, CloudAccount, @@ -431,3 +433,9 @@ export const inviteAccept = async ( ctx.throw(400, "Unable to create new user, invitation invalid.") } } + +export const addAppBuilder = async (ctx: Ctx) => {} + +export const removeAppBuilder = async ( + ctx: Ctx +) => {} diff --git a/packages/worker/src/api/routes/global/users.ts b/packages/worker/src/api/routes/global/users.ts index 47e76c17be..557065e9a4 100644 --- a/packages/worker/src/api/routes/global/users.ts +++ b/packages/worker/src/api/routes/global/users.ts @@ -5,6 +5,7 @@ import Joi from "joi" import cloudRestricted from "../../../middleware/cloudRestricted" import { users } from "../validation" import * as selfController from "../../controllers/global/self" +import { addAppBuilder } from "../../controllers/global/users" const router: Router = new Router() @@ -131,5 +132,7 @@ router users.buildUserSaveValidation(), selfController.updateSelf ) + .post("/api/global/users/builder", controller.addAppBuilder) + .delete("/api/global/users/builder", controller.removeAppBuilder) export default router From 586bca16d06cc73abeca99a05e4007892b9d7af0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:44:48 +0100 Subject: [PATCH 002/821] Move OFFLINE_MODE to backend-core environment --- packages/backend-core/src/environment.ts | 1 + packages/server/src/environment.ts | 1 - packages/worker/src/api/controllers/system/environment.ts | 3 ++- packages/worker/src/environment.ts | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index eab8cd4c45..c0785ef419 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -156,6 +156,7 @@ const environment = { : false, VERSION: findVersion(), DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER, + OFFLINE_MODE: process.env.OFFLINE_MODE, _set(key: any, value: any) { process.env[key] = value // @ts-ignore diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 79ee5d977c..64b342d577 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -81,7 +81,6 @@ const environment = { SELF_HOSTED: process.env.SELF_HOSTED, HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT, FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", - OFFLINE_MODE: process.env.OFFLINE_MODE, // old CLIENT_ID: process.env.CLIENT_ID, _set(key: string, value: any) { diff --git a/packages/worker/src/api/controllers/system/environment.ts b/packages/worker/src/api/controllers/system/environment.ts index f3f917c2dd..729a00fa88 100644 --- a/packages/worker/src/api/controllers/system/environment.ts +++ b/packages/worker/src/api/controllers/system/environment.ts @@ -1,10 +1,11 @@ import { Ctx } from "@budibase/types" import env from "../../../environment" +import { env as coreEnv } from "@budibase/backend-core" export const fetch = async (ctx: Ctx) => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, - offlineMode: !!env.OFFLINE_MODE, + offlineMode: !!coreEnv.OFFLINE_MODE, cloud: !env.SELF_HOSTED, accountPortalUrl: env.ACCOUNT_PORTAL_URL, disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL, diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index 74d58831d9..678ffe7f14 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -61,7 +61,6 @@ const environment = { CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600, SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD, ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY, - OFFLINE_MODE: process.env.OFFLINE_MODE, /** * Mock the email service in use - links to ethereal hosted emails are logged instead. */ From efe53bb217ecfccc34d3da3311e4d5aeea19b029 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:46:25 +0100 Subject: [PATCH 003/821] Update license endpoints to provide consistent pattern for offline license and license key (create, read, delete) --- packages/types/src/api/web/global/index.ts | 1 + packages/types/src/api/web/global/license.ts | 19 +++++ .../src/api/controllers/global/license.ts | 69 ++++++++++++++----- .../worker/src/api/routes/global/license.ts | 21 +++++- 4 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 packages/types/src/api/web/global/license.ts diff --git a/packages/types/src/api/web/global/index.ts b/packages/types/src/api/web/global/index.ts index bf4b43f0ba..efcb6dc39c 100644 --- a/packages/types/src/api/web/global/index.ts +++ b/packages/types/src/api/web/global/index.ts @@ -3,3 +3,4 @@ export * from "./auditLogs" export * from "./events" export * from "./configs" export * from "./scim" +export * from "./license" diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts new file mode 100644 index 0000000000..34de2e4361 --- /dev/null +++ b/packages/types/src/api/web/global/license.ts @@ -0,0 +1,19 @@ +// LICENSE KEY + +export interface ActivateLicenseKeyRequest { + licenseKey: string +} + +export interface GetLicenseKeyResponse { + licenseKey: string, +} + +// OFFLINE LICENSE + +export interface ActivateOfflineLicenseRequest { + offlineLicense: string +} + +export interface GetOfflineLicenseResponse { + offlineLicense: string +} diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 2bd173010f..b4670e0dbb 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,33 +1,66 @@ import { licensing, quotas } from "@budibase/pro" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseRequest, + GetLicenseKeyResponse, + GetOfflineLicenseResponse, + UserCtx, +} from "@budibase/types" -export const activate = async (ctx: any) => { +// LICENSE KEY + +export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - if (!licenseKey) { - ctx.throw(400, "licenseKey is required") - } - await licensing.activateLicenseKey(licenseKey) ctx.status = 200 } +export async function getLicenseKey(ctx: UserCtx) { + const licenseKey = await licensing.getLicenseKey() + if (licenseKey) { + ctx.body = { licenseKey: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteLicenseKey(ctx: UserCtx) { + await licensing.deleteLicenseKey() + ctx.status = 204 +} + +// OFFLINE LICENSE + +export async function activateOfflineLicense(ctx: UserCtx) { + const { offlineLicense } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicense) + ctx.status = 200 +} + +export async function getOfflineLicense(ctx: UserCtx) { + const offlineLicense = await licensing.getOfflineLicense() + if (offlineLicense) { + ctx.body = { offlineLicense: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteOfflineLicense(ctx: UserCtx) { + await licensing.deleteOfflineLicense() + ctx.status = 204 +} + +// LICENSES + export const refresh = async (ctx: any) => { await licensing.cache.refresh() ctx.status = 200 } -export const getInfo = async (ctx: any) => { - const licenseInfo = await licensing.getLicenseInfo() - if (licenseInfo) { - licenseInfo.licenseKey = "*" - ctx.body = licenseInfo - } - ctx.status = 200 -} - -export const deleteInfo = async (ctx: any) => { - await licensing.deleteLicenseInfo() - ctx.status = 200 -} +// USAGE export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 0fb2a6e8bd..1d6185a402 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -1,13 +1,28 @@ import Router from "@koa/router" import * as controller from "../../controllers/global/license" +import { middleware } from "@budibase/backend-core" +import Joi from "joi" + +const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ + licenseKey: Joi.string().required(), + }).required()) + +const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ + offlineLicense: Joi.string().required(), + }).required()) const router: Router = new Router() router - .post("/api/global/license/activate", controller.activate) .post("/api/global/license/refresh", controller.refresh) - .get("/api/global/license/info", controller.getInfo) - .delete("/api/global/license/info", controller.deleteInfo) .get("/api/global/license/usage", controller.getQuotaUsage) + // LICENSE KEY + .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .get("/api/global/license/key", controller.getLicenseKey) + .delete("/api/global/license/key", controller.deleteLicenseKey) + // OFFLINE LICENSE + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) + .get("/api/global/license/offline", controller.getOfflineLicense) + .delete("/api/global/license/offline", controller.deleteOfflineLicense) export default router From 1ba3665ed4a0985ca2f0f4f96fac780bbc82623b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:47:12 +0100 Subject: [PATCH 004/821] Updates to upgrade page to change config based on offlineMode value --- .../builder/portal/account/upgrade.svelte | 215 +++++++++++++----- packages/builder/src/stores/portal/admin.js | 1 + packages/frontend-core/src/api/licensing.js | 57 +++-- 3 files changed, 201 insertions(+), 72 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index f0ee87bde5..a37625a5dc 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -10,6 +10,8 @@ Label, ButtonGroup, notifications, + CopyInput, + File } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -21,44 +23,122 @@ $: license = $auth.user.license $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + // LICENSE KEY $: activateDisabled = !licenseKey || licenseKeyDisabled - - let licenseInfo - let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal + // OFFLINE LICENSE + let installationIdentifier = undefined + let offlineLicense = undefined + const offlineLicenseExtensions = [ + ".txt", + ] + // Make sure page can't be visited directly in cloud $: { if ($admin.cloud) { $redirect("../../portal") } + + console.log({ offlineLicense }) } - const activate = async () => { + // LICENSE KEY + + const getLicenseKey = async () => { try { - await API.activateLicenseKey({ licenseKey }) - await auth.getSelf() - await setLicenseInfo() - notifications.success("Successfully activated") + licenseKey = await API.getLicenseKey() + if (licenseKey) { + licenseKey = "**********************************************" + licenseKeyType = "password" + licenseKeyDisabled = true + activateDisabled = true + } } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error retrieving license key") } } - const destroy = async () => { + const activateLicenseKey = async () => { + try { + await API.activateLicenseKey({ licenseKey }) + await auth.getSelf() + await getLicenseKey() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating license key") + } + } + + const deleteLicenseKey = async () => { try { await API.deleteLicenseKey({ licenseKey }) await auth.getSelf() - await setLicenseInfo() + await getLicenseKey() // reset the form licenseKey = "" licenseKeyDisabled = false - notifications.success("Successfully deleted") + notifications.success("Offline license removed") } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error deleting license key") + } + } + + // OFFLINE LICENSE + + const getOfflineLicense = async () => { + try { + const license = await API.getOfflineLicense() + if (license) { + offlineLicense = { + name: "license" + } + } else { + offlineLicense = undefined + } + } catch (e) { + console.error(e) + notifications.error("Error loading offline license") + } + } + + async function activateOfflineLicense(offlineLicense) { + try { + await API.activateOfflineLicense({ offlineLicense }) + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating offline license") + } + } + + async function deleteOfflineLicense() { + try { + await API.deleteOfflineLicense() + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully removed ofline license") + } catch (e) { + console.error(e) + notifications.error("Error upload offline license") + } + } + + async function onOfflineLicenseChange(event) { + if (event.detail) { + const reader = new FileReader() + reader.readAsText(event.detail) + reader.onload = () => activateOfflineLicense(reader.result) + } else { + await deleteOfflineLicense() } } @@ -73,29 +153,19 @@ } } - // deactivate the license key field if there is a license key set - $: { - if (licenseInfo?.licenseKey) { - licenseKey = "**********************************************" - licenseKeyType = "password" - licenseKeyDisabled = true - activateDisabled = true - } - } - - const setLicenseInfo = async () => { - licenseInfo = await API.getLicenseInfo() - } - onMount(async () => { - await setLicenseInfo() + if ($admin.offlineMode) { + await getOfflineLicense() + } else { + await getLicenseKey() + } }) {#if $auth.isAdmin} @@ -112,38 +182,73 @@ - - Activate - Enter your license key below to activate your plan - - -
-
- - + Installation identifier + Share this with support@budibase.com to obtain your offline license + + +
+
+ +
+
+
+ + + License + Upload your license to activate your plan + + +
+
-
- - - {#if licenseInfo?.licenseKey} - - {/if} - - + {#if licenseKey} + + {/if} + + + {/if} - Plan - + Plan + You are currently on the {license.plan.type} plan +
+ If you purchase or update your plan on the account + portal, click the refresh button to sync those changes +
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index b9467fd037..19abcfd841 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, + offlineMode: false } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index c27d79d740..5f7813cfca 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,32 +1,56 @@ export const buildLicensingEndpoints = API => ({ - /** - * Activates a self hosted license key - */ + + // LICENSE KEY + activateLicenseKey: async data => { return API.post({ - url: `/api/global/license/activate`, + url: `/api/global/license/key`, body: data, }) }, - - /** - * Delete a self hosted license key - */ deleteLicenseKey: async () => { return API.delete({ - url: `/api/global/license/info`, + url: `/api/global/license/key`, }) }, + getLicenseKey: async () => { + try { + return await API.get({ + url: "/api/global/license/key", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, - /** - * Get the license info - metadata about the license including the - * obfuscated license key. - */ - getLicenseInfo: async () => { - return API.get({ - url: "/api/global/license/info", + // OFFLINE LICENSE + + activateOfflineLicense: async ({ offlineLicense }) => { + return API.post({ + url: "/api/global/license/offline", + body: { + offlineLicense + }, }) }, + deleteOfflineLicense: async () => { + return API.delete({ + url: "/api/global/license/offline", + }) + }, + getOfflineLicense: async () => { + try { + return await API.get({ + url: "/api/global/license/offline", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, /** * Refreshes the license cache @@ -36,7 +60,6 @@ export const buildLicensingEndpoints = API => ({ url: "/api/global/license/refresh", }) }, - /** * Retrieve the usage information for the tenant */ From d02f474fd10e1cbc155836627f1a356d1b48dbba Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:26:46 +0100 Subject: [PATCH 005/821] Be more explicit about offline license vs offline license token --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 34de2e4361..0196b69c7f 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -11,9 +11,9 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE export interface ActivateOfflineLicenseRequest { - offlineLicense: string + offlineLicenseToken: string } export interface GetOfflineLicenseResponse { - offlineLicense: string + offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b4670e0dbb..b33efa1491 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -33,15 +33,15 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE export async function activateOfflineLicense(ctx: UserCtx) { - const { offlineLicense } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicense) + const { offlineLicenseToken } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicense = await licensing.getOfflineLicense() - if (offlineLicense) { - ctx.body = { offlineLicense: "*" } + const offlineLicenseToken = await licensing.getOfflineLicenseToken() + if (offlineLicenseToken) { + ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 } else { ctx.status = 404 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicense() + await licensing.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d6185a402..889d7d1ed4 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -8,8 +8,8 @@ const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ }).required()) const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ - offlineLicense: Joi.string().required(), - }).required()) + offlineLicenseToken: Joi.string().required(), +}).required()) const router: Router = new Router() From 1fde82bd81ed1e4aa9daf80dba98739b9cae4708 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:58:20 +0100 Subject: [PATCH 006/821] Aesthetic UI updates --- .../builder/portal/account/upgrade.svelte | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index a37625a5dc..48ff3514b7 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -24,14 +24,16 @@ $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` // LICENSE KEY + $: activateDisabled = !licenseKey || licenseKeyDisabled let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal - // OFFLINE LICENSE - let installationIdentifier = undefined + // OFFLINE + + let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -108,9 +110,9 @@ } } - async function activateOfflineLicense(offlineLicense) { + async function activateOfflineLicense(offlineLicenseToken) { try { - await API.activateOfflineLicense({ offlineLicense }) + await API.activateOfflineLicense({ offlineLicenseToken }) await auth.getSelf() await getOfflineLicense() notifications.success("Successfully activated") @@ -134,10 +136,16 @@ async function onOfflineLicenseChange(event) { if (event.detail) { + // prevent file preview jitter by assigning constant + // as soon as possible + offlineLicense = { + name: "license" + } const reader = new FileReader() reader.readAsText(event.detail) reader.onload = () => activateOfflineLicense(reader.result) } else { + offlineLicense = undefined await deleteOfflineLicense() } } @@ -178,6 +186,7 @@ {:else} To manage your plan visit your account +
 
{/if}
@@ -270,7 +279,7 @@ } .field { display: grid; - grid-template-columns: 100px 1fr; + grid-template-columns: 300px 1fr; grid-gap: var(--spacing-l); align-items: center; } From bdd431c7e815277c9bf555fd5f7f26b7b4da7ce1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 22:00:13 +0100 Subject: [PATCH 007/821] Update request body for offline license activation --- packages/frontend-core/src/api/licensing.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 5f7813cfca..cacf972971 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -27,11 +27,11 @@ export const buildLicensingEndpoints = API => ({ // OFFLINE LICENSE - activateOfflineLicense: async ({ offlineLicense }) => { + activateOfflineLicense: async ({ offlineLicenseToken }) => { return API.post({ url: "/api/global/license/offline", body: { - offlineLicense + offlineLicenseToken }, }) }, From 700d8131d14bb5f226106ec41ecc9ae2cd2750b9 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:34:10 +0100 Subject: [PATCH 008/821] db / licenseInfo.spec.ts --- packages/worker/src/api/routes/global/tests/license.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index be0673729e..d548ad83bb 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -21,9 +21,6 @@ describe("/api/global/license", () => { describe("POST /api/global/license/refresh", () => {}) - describe("GET /api/global/license/info", () => {}) - - describe("DELETE /api/global/license/info", () => {}) describe("GET /api/global/license/usage", () => {}) }) From 1fa5cd519cd6f089f40e6a43e11202f9adb09ac2 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:48:12 +0100 Subject: [PATCH 009/821] Move license keys to their own module --- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b33efa1491..9310eb3739 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -11,12 +11,12 @@ import { export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - await licensing.activateLicenseKey(licenseKey) + await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 } export async function getLicenseKey(ctx: UserCtx) { - const licenseKey = await licensing.getLicenseKey() + const licenseKey = await licensing.keys.getLicenseKey() if (licenseKey) { ctx.body = { licenseKey: "*" } ctx.status = 200 @@ -26,7 +26,7 @@ export async function getLicenseKey(ctx: UserCtx) { } export async function deleteLicenseKey(ctx: UserCtx) { - await licensing.deleteLicenseKey() + await licensing.keys.deleteLicenseKey() ctx.status = 204 } @@ -34,12 +34,12 @@ export async function deleteLicenseKey(ctx: UserCtx) { export async function activateOfflineLicense(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicenseToken = await licensing.getOfflineLicenseToken() + const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicenseToken() + await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } From 19d3e12177d576b8ffbc441f478bfa0b8c966267 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 16:55:11 +0100 Subject: [PATCH 010/821] Allow pro to be mocked in worker --- packages/worker/__mocks__/@budibase/pro.ts | 26 +++++++++++++++++++ .../worker/src/tests/TestConfiguration.ts | 11 ++++---- packages/worker/src/tests/mocks/index.ts | 4 +++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/worker/__mocks__/@budibase/pro.ts diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts new file mode 100644 index 0000000000..a9611ba705 --- /dev/null +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -0,0 +1,26 @@ +const actual = jest.requireActual("@budibase/pro") +const pro = { + ...actual, + licensing: { + keys: { + activateLicenseKey: jest.fn(), + getLicenseKey: jest.fn(), + deleteLicenseKey: jest.fn(), + }, + offline: { + activateOfflineLicense: jest.fn(), + getOfflineLicenseToken: jest.fn(), + deleteOfflineLicenseToken: jest.fn(), + }, + cache: { + ...actual.licensing.cache, + refresh: jest.fn(), + } + }, + quotas: { + ...actual.quotas, + getQuotaUsage: jest.fn() + }, +} + +export = pro diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index a79ac0e189..b41b76efda 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -1,8 +1,7 @@ import mocks from "./mocks" // init the licensing mock -import * as pro from "@budibase/pro" -mocks.licenses.init(pro) +mocks.licenses.init(mocks.pro) // use unlimited license by default mocks.licenses.useUnlimited() @@ -238,21 +237,21 @@ class TestConfiguration { const db = context.getGlobalDB() - const id = dbCore.generateDevInfoID(this.user._id) + const id = dbCore.generateDevInfoID(this.user!._id) // TODO: dry this.apiKey = encryption.encrypt( `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}` ) const devInfo = { _id: id, - userId: this.user._id, + userId: this.user!._id, apiKey: this.apiKey, } await db.put(devInfo) }) } - async getUser(email: string): Promise { + async getUser(email: string): Promise { return context.doInTenant(this.getTenantId(), () => { return users.getGlobalUserByEmail(email) }) @@ -264,7 +263,7 @@ class TestConfiguration { } const response = await this._req(user, null, controllers.users.save) const body = response as SaveUserResponse - return this.getUser(body.email) + return this.getUser(body.email) as Promise } // CONFIGS diff --git a/packages/worker/src/tests/mocks/index.ts b/packages/worker/src/tests/mocks/index.ts index 30bb4e1d09..cab019bb46 100644 --- a/packages/worker/src/tests/mocks/index.ts +++ b/packages/worker/src/tests/mocks/index.ts @@ -1,7 +1,11 @@ import * as email from "./email" import { mocks } from "@budibase/backend-core/tests" +import * as _pro from "@budibase/pro" +const pro = jest.mocked(_pro, true) + export default { email, + pro, ...mocks, } From e0b059fa2bf66bf8ed2a53a18d7a4a61f0eee75b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 011/821] api / license.spec.ts updates --- .../src/api/controllers/global/license.ts | 1 + .../api/routes/global/tests/license.spec.ts | 85 +++++++++++++++++-- packages/worker/src/tests/api/license.ts | 43 +++++++++- 3 files changed, 120 insertions(+), 9 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 9310eb3739..b8c8566018 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -64,4 +64,5 @@ export const refresh = async (ctx: any) => { export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() + ctx.status = 200 } diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index d548ad83bb..c3c423c833 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,4 +1,6 @@ -import { TestConfiguration } from "../../../../tests" +import { TestConfiguration, mocks, structures } from "../../../../tests" +const licensing = mocks.pro.licensing +const quotas = mocks.pro.quotas describe("/api/global/license", () => { const config = new TestConfiguration() @@ -12,15 +14,86 @@ describe("/api/global/license", () => { }) afterEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() }) - describe("POST /api/global/license/activate", () => { - it("activates license", () => {}) + describe("POST /api/global/license/refresh", () => { + it("returns 200", async () => { + const res = await config.api.license.refresh() + expect(res.status).toBe(200) + expect(licensing.cache.refresh).toBeCalledTimes(1) + }) }) - describe("POST /api/global/license/refresh", () => {}) + describe("GET /api/global/license/usage", () => { + it("returns 200", async () => { + const usage = structures.quotas.usage() + quotas.getQuotaUsage.mockResolvedValue(usage) + const res = await config.api.license.getUsage() + expect(res.status).toBe(200) + expect(res.body).toEqual(usage) + }) + }) + describe("POST /api/global/license/key", () => { + it("returns 200", async () => { + const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + expect(res.status).toBe(200) + expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") + }) + }) - describe("GET /api/global/license/usage", () => {}) + describe("GET /api/global/license/key", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(404) + }) + it("returns 200 + license key", async () => { + licensing.keys.getLicenseKey.mockResolvedValue("licenseKey") + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + licenseKey: "*" + }) + }) + }) + + describe("DELETE /api/global/license/key", () => { + it("returns 204", async () => { + const res = await config.api.license.deleteLicenseKey() + expect(licensing.keys.deleteLicenseKey).toBeCalledTimes(1) + expect(res.status).toBe(204) + }) + }) + + describe("POST /api/global/license/offline", () => { + it("activates offline license", async () => { + const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) + expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + expect(res.status).toBe(200) + }) + }) + + describe("GET /api/global/license/offline", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(404) + }) + it("returns 200", async () => { + licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + offlineLicenseToken: "*" + }) + }) + }) + + describe("DELETE /api/global/license/offline", () => { + it("deletes offline license", async () => { + const res = await config.api.license.deleteOfflineLicense() + expect(res.status).toBe(204) + expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 9d7745a80e..89f85b25e3 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,17 +1,54 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" +import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { super(config) } - activate = async (licenseKey: string) => { + refresh = async () => { return this.request - .post("/api/global/license/activate") - .send({ licenseKey: licenseKey }) + .post("/api/global/license/refresh") + .set(this.config.defaultHeaders()) + } + getUsage = async () => { + return this.request + .get("/api/global/license/usage") .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) } + activateLicenseKey = async (body: ActivateLicenseKeyRequest) => { + return this.request + .post("/api/global/license/key") + .send(body) + .set(this.config.defaultHeaders()) + } + getLicenseKey = async () => { + return this.request + .get("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + deleteLicenseKey = async () => { + return this.request + .delete("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + return this.request + .post("/api/global/license/offline") + .send(body) + .set(this.config.defaultHeaders()) + } + getOfflineLicense = async () => { + return this.request + .get("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } + deleteOfflineLicense = async () => { + return this.request + .delete("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } } From 7831579e98c388c278a6a2ff1b7114bbf9e9985c Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 012/821] api / license.spec.ts updates --- .../backend-core/src/events/identification.ts | 2 +- .../src/api/controllers/global/license.ts | 1 + .../api/routes/global/tests/license.spec.ts | 85 +++++++++++++++++-- packages/worker/src/tests/api/license.ts | 43 +++++++++- 4 files changed, 121 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 5eb11d1354..948d3b692b 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -264,7 +264,7 @@ const getEventTenantId = async (tenantId: string): Promise => { } } -const getUniqueTenantId = async (tenantId: string): Promise => { +export const getUniqueTenantId = async (tenantId: string): Promise => { // make sure this tenantId always matches the tenantId in context return context.doInTenant(tenantId, () => { return withCache(CacheKey.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 9310eb3739..b8c8566018 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -64,4 +64,5 @@ export const refresh = async (ctx: any) => { export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() + ctx.status = 200 } diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index d548ad83bb..c3c423c833 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,4 +1,6 @@ -import { TestConfiguration } from "../../../../tests" +import { TestConfiguration, mocks, structures } from "../../../../tests" +const licensing = mocks.pro.licensing +const quotas = mocks.pro.quotas describe("/api/global/license", () => { const config = new TestConfiguration() @@ -12,15 +14,86 @@ describe("/api/global/license", () => { }) afterEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() }) - describe("POST /api/global/license/activate", () => { - it("activates license", () => {}) + describe("POST /api/global/license/refresh", () => { + it("returns 200", async () => { + const res = await config.api.license.refresh() + expect(res.status).toBe(200) + expect(licensing.cache.refresh).toBeCalledTimes(1) + }) }) - describe("POST /api/global/license/refresh", () => {}) + describe("GET /api/global/license/usage", () => { + it("returns 200", async () => { + const usage = structures.quotas.usage() + quotas.getQuotaUsage.mockResolvedValue(usage) + const res = await config.api.license.getUsage() + expect(res.status).toBe(200) + expect(res.body).toEqual(usage) + }) + }) + describe("POST /api/global/license/key", () => { + it("returns 200", async () => { + const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + expect(res.status).toBe(200) + expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") + }) + }) - describe("GET /api/global/license/usage", () => {}) + describe("GET /api/global/license/key", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(404) + }) + it("returns 200 + license key", async () => { + licensing.keys.getLicenseKey.mockResolvedValue("licenseKey") + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + licenseKey: "*" + }) + }) + }) + + describe("DELETE /api/global/license/key", () => { + it("returns 204", async () => { + const res = await config.api.license.deleteLicenseKey() + expect(licensing.keys.deleteLicenseKey).toBeCalledTimes(1) + expect(res.status).toBe(204) + }) + }) + + describe("POST /api/global/license/offline", () => { + it("activates offline license", async () => { + const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) + expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + expect(res.status).toBe(200) + }) + }) + + describe("GET /api/global/license/offline", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(404) + }) + it("returns 200", async () => { + licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + offlineLicenseToken: "*" + }) + }) + }) + + describe("DELETE /api/global/license/offline", () => { + it("deletes offline license", async () => { + const res = await config.api.license.deleteOfflineLicense() + expect(res.status).toBe(204) + expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 9d7745a80e..89f85b25e3 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,17 +1,54 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" +import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { super(config) } - activate = async (licenseKey: string) => { + refresh = async () => { return this.request - .post("/api/global/license/activate") - .send({ licenseKey: licenseKey }) + .post("/api/global/license/refresh") + .set(this.config.defaultHeaders()) + } + getUsage = async () => { + return this.request + .get("/api/global/license/usage") .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) } + activateLicenseKey = async (body: ActivateLicenseKeyRequest) => { + return this.request + .post("/api/global/license/key") + .send(body) + .set(this.config.defaultHeaders()) + } + getLicenseKey = async () => { + return this.request + .get("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + deleteLicenseKey = async () => { + return this.request + .delete("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + return this.request + .post("/api/global/license/offline") + .send(body) + .set(this.config.defaultHeaders()) + } + getOfflineLicense = async () => { + return this.request + .get("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } + deleteOfflineLicense = async () => { + return this.request + .delete("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } } From 574c361f6b8785f0981383fd8bdb2f1f5404cdb9 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:07:10 +0100 Subject: [PATCH 013/821] /api/global/license/offline/identifier API --- packages/types/src/api/web/global/license.ts | 6 ++++++ packages/types/src/sdk/licensing/license.ts | 10 ++++++++++ packages/worker/__mocks__/@budibase/pro.ts | 1 + .../src/api/controllers/global/license.ts | 7 +++++++ .../worker/src/api/routes/global/license.ts | 1 + .../src/api/routes/global/tests/license.spec.ts | 17 ++++++++++++++--- packages/worker/src/tests/api/license.ts | 5 +++++ 7 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 0196b69c7f..4a36c4f1d8 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -17,3 +17,9 @@ export interface ActivateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string } + +// IDENTIFIER + +export interface GetOfflineIdentifierResponse { + identifierBase64: string +} diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index b287e67adf..ebc874bca0 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,5 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +export interface OfflineIdentifier { + installId: string, + tenantId: string +} + +// export interface OfflineLicense extends License { +// identifier?: OfflineIdentifier +// identifierBase64: string +// } + export interface License { features: Feature[] quotas: Quotas diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index a9611ba705..bd6250fede 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,6 +11,7 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), + getIdentifierBase64: jest.fn() }, cache: { ...actual.licensing.cache, diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b8c8566018..73b3c72d1e 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -3,6 +3,7 @@ import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest, GetLicenseKeyResponse, + GetOfflineIdentifierResponse, GetOfflineLicenseResponse, UserCtx, } from "@budibase/types" @@ -53,6 +54,12 @@ export async function deleteOfflineLicense(ctx: UserCtx) { ctx.status = 204 } +export async function getOfflineLicenseIdentifier(ctx: UserCtx) { + const identifierBase64 = await licensing.offline.getIdentifierBase64() + ctx.body = { identifierBase64 } + ctx.status = 200 +} + // LICENSES export const refresh = async (ctx: any) => { diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 889d7d1ed4..1d3d9c460b 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -24,5 +24,6 @@ router .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) .get("/api/global/license/offline", controller.getOfflineLicense) .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index c3c423c833..512d6c9c52 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -26,7 +26,7 @@ describe("/api/global/license", () => { }) describe("GET /api/global/license/usage", () => { - it("returns 200", async () => { + it("returns 200 + usage", async () => { const usage = structures.quotas.usage() quotas.getQuotaUsage.mockResolvedValue(usage) const res = await config.api.license.getUsage() @@ -79,7 +79,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(404) }) - it("returns 200", async () => { + it("returns 200 + offline license token", async () => { licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) @@ -90,10 +90,21 @@ describe("/api/global/license", () => { }) describe("DELETE /api/global/license/offline", () => { - it("deletes offline license", async () => { + it("returns 204", async () => { const res = await config.api.license.deleteOfflineLicense() expect(res.status).toBe(204) expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) }) }) + + describe("GET /api/global/license/offline/identifier", () => { + it("returns 200 + identifier base64", async () => { + licensing.offline.getIdentifierBase64.mockResolvedValue("base64") + const res = await config.api.license.getOfflineLicenseIdentifier() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + identifierBase64: "base64" + }) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 89f85b25e3..76d3e88df7 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -51,4 +51,9 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/offline") .set(this.config.defaultHeaders()) } + getOfflineLicenseIdentifier = async () => { + return this.request + .get("/api/global/license/offline/identifier") + .set(this.config.defaultHeaders()) + } } From 974b7d8514506b85434aa993d811402eefa08374 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:08:51 +0100 Subject: [PATCH 014/821] Integrate UI with identifier API --- .../builder/portal/account/upgrade.svelte | 18 +++++++++++++----- packages/frontend-core/src/api/licensing.js | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 48ff3514b7..8e2fd5c641 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -33,7 +33,7 @@ // OFFLINE - let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" + let offlineLicenseIdentifier = "" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -44,8 +44,6 @@ if ($admin.cloud) { $redirect("../../portal") } - - console.log({ offlineLicense }) } // LICENSE KEY @@ -110,6 +108,16 @@ } } + const getOfflineLicenseIdentifier = async () => { + try { + const res = await API.getOfflineLicenseIdentifier() + offlineLicenseIdentifier = res.identifierBase64 + } catch (e) { + console.error(e) + notifications.error("Error loading installation identifier") + } + } + async function activateOfflineLicense(offlineLicenseToken) { try { await API.activateOfflineLicense({ offlineLicenseToken }) @@ -163,7 +171,7 @@ onMount(async () => { if ($admin.offlineMode) { - await getOfflineLicense() + await Promise.all([getOfflineLicense(), getOfflineLicenseIdentifier()]) } else { await getLicenseKey() } @@ -199,7 +207,7 @@
- +
diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index cacf972971..9fe98d5e74 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -51,6 +51,11 @@ export const buildLicensingEndpoints = API => ({ } } }, + getOfflineLicenseIdentifier: async () => { + return await API.get({ + url: "/api/global/license/offline/identifier", + }) + }, /** * Refreshes the license cache From 6ae59d767bc0ff8e04800109bb6f8b2f4e654032 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:48:52 +0100 Subject: [PATCH 015/821] offline license sdk module --- packages/backend-core/src/errors/errors.ts | 12 ++++++++++++ packages/types/src/documents/account/account.ts | 1 + packages/types/src/sdk/licensing/feature.ts | 1 + packages/types/src/sdk/licensing/license.ts | 9 +++++---- packages/types/src/shared/typeUtils.ts | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/errors/errors.ts b/packages/backend-core/src/errors/errors.ts index 4e1f1abbb5..7d55d25e89 100644 --- a/packages/backend-core/src/errors/errors.ts +++ b/packages/backend-core/src/errors/errors.ts @@ -55,6 +55,18 @@ export class HTTPError extends BudibaseError { } } +export class NotFoundError extends HTTPError { + constructor(message: string) { + super(message, 404) + } +} + +export class BadRequestError extends HTTPError { + constructor(message: string) { + super(message, 400) + } +} + // LICENSING export class UsageLimitError extends HTTPError { diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts index dad8abed30..5321aa7e08 100644 --- a/packages/types/src/documents/account/account.ts +++ b/packages/types/src/documents/account/account.ts @@ -51,6 +51,7 @@ export interface Account extends CreateAccount { licenseRequestedAt?: number licenseOverrides?: LicenseOverrides quotaUsage?: QuotaUsage + offlineLicenseToken?: string } export interface PasswordAccount extends Account { diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts index 1cbdd55bcf..f286a1cc44 100644 --- a/packages/types/src/sdk/licensing/feature.ts +++ b/packages/types/src/sdk/licensing/feature.ts @@ -9,6 +9,7 @@ export enum Feature { BRANDING = "branding", SCIM = "scim", SYNC_AUTOMATIONS = "syncAutomations", + OFFLINE = "offline", } export type PlanFeatures = { [key in PlanType]: Feature[] | undefined } diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index ebc874bca0..e8ad98fe7a 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,14 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +import { ISO8601 } from "../../shared" export interface OfflineIdentifier { installId: string, tenantId: string } -// export interface OfflineLicense extends License { -// identifier?: OfflineIdentifier -// identifierBase64: string -// } +export interface OfflineLicense extends License { + identifier: OfflineIdentifier + expireAt: ISO8601 +} export interface License { features: Feature[] diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 71fadfc7aa..143865c60b 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -1,3 +1,5 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } + +export type ISO8601 = string \ No newline at end of file From d0d5a55047616adaac8f0219d34594af85b6e90b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:49:45 +0100 Subject: [PATCH 016/821] /api/internal/accounts/:accountId/license/offline --- packages/types/src/api/account/license.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index a867358559..e23f6cab97 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -1,5 +1,6 @@ import { LicenseOverrides, QuotaUsage } from "../../documents" -import { PlanType } from "../../sdk" +import { OfflineLicense, PlanType } from "../../sdk" +import { ISO8601 } from "../../shared" export interface GetLicenseRequest { // All fields should be optional to cater for @@ -26,3 +27,13 @@ export interface UpdateLicenseRequest { planType?: PlanType overrides?: LicenseOverrides } + +export interface CreateOfflineLicenseRequest { + installationIdentifierBase64: string + expireAt: ISO8601 +} + +export interface GetOfflineLicenseResponse { + offlineLicenseToken: string + license: OfflineLicense +} \ No newline at end of file From 7c4fe15781b61f1ba62172c44c9d4c71bc40b83a Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 16:12:19 +0100 Subject: [PATCH 017/821] Request / response renames --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 4a36c4f1d8..900a0db96d 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -10,11 +10,11 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE -export interface ActivateOfflineLicenseRequest { +export interface ActivateOfflineLicenseTokenRequest { offlineLicenseToken: string } -export interface GetOfflineLicenseResponse { +export interface GetOfflineLicenseTokenResponse { offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 73b3c72d1e..fa95eeee0d 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,10 +1,10 @@ import { licensing, quotas } from "@budibase/pro" import { ActivateLicenseKeyRequest, - ActivateOfflineLicenseRequest, + ActivateOfflineLicenseTokenRequest, GetLicenseKeyResponse, GetOfflineIdentifierResponse, - GetOfflineLicenseResponse, + GetOfflineLicenseTokenResponse, UserCtx, } from "@budibase/types" @@ -33,13 +33,13 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicense(ctx: UserCtx) { +export async function activateOfflineLicenseToken(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.offline.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicense(ctx: UserCtx) { +export async function getOfflineLicenseToken(ctx: UserCtx) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { +export async function deleteOfflineLicenseToken(ctx: UserCtx) { await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d3d9c460b..199dbb3846 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -21,9 +21,9 @@ router .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) - .get("/api/global/license/offline", controller.getOfflineLicense) - .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .get("/api/global/license/offline", controller.getOfflineLicenseToken) + .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router From 94fc6d8b0fca5b31e8628628f8ea076b7a48aba8 Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 11 Jul 2023 12:47:17 +0100 Subject: [PATCH 018/821] Merge commit --- .../ButtonActionEditor/ButtonActionDrawer.svelte | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte index aa8e1af950..cd7b40aaa0 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte @@ -175,6 +175,11 @@ } return allBindings } + + const toDisplay = eventKey => { + const type = actionTypes.find(action => action.name == eventKey) + return type?.displayName || type?.name + } @@ -200,7 +205,9 @@
    {#each category as actionType}
  • - {actionType.name} + + {actionType.displayName || actionType.name} +
  • {/each}
@@ -231,7 +238,7 @@ >
- {index + 1}. {action[EVENT_TYPE_KEY]} + {index + 1}. {toDisplay(action[EVENT_TYPE_KEY])}
Date: Thu, 13 Jul 2023 12:36:50 +0100 Subject: [PATCH 019/821] Account Portal API Testing --- packages/backend-core/src/constants/misc.ts | 2 + .../src/account-api/api/apis/AccountAPI.ts | 55 +++++++++++++++++++ .../account-api/config/TestConfiguration.ts | 34 ++++++++++++ qa-core/src/account-api/fixtures/accounts.ts | 20 +++++++ qa-core/src/account-api/fixtures/index.ts | 1 + .../account-api/tests/accounts/create.spec.ts | 20 +++++++ .../account-api/tests/accounts/delete.spec.ts | 25 +++++++++ .../account-api/tests/accounts/search.spec.ts | 22 ++++++++ .../tests/accounts/validate.spec.ts | 27 +++++++++ .../account-api/tests/accounts/verify.spec.ts | 24 ++++++++ 10 files changed, 230 insertions(+) create mode 100644 qa-core/src/account-api/config/TestConfiguration.ts create mode 100644 qa-core/src/account-api/fixtures/accounts.ts create mode 100644 qa-core/src/account-api/fixtures/index.ts create mode 100644 qa-core/src/account-api/tests/accounts/create.spec.ts create mode 100644 qa-core/src/account-api/tests/accounts/delete.spec.ts create mode 100644 qa-core/src/account-api/tests/accounts/search.spec.ts create mode 100644 qa-core/src/account-api/tests/accounts/validate.spec.ts create mode 100644 qa-core/src/account-api/tests/accounts/verify.spec.ts diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index ba2533cf4a..0c68798164 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -20,6 +20,8 @@ export enum Header { TYPE = "x-budibase-type", PREVIEW_ROLE = "x-budibase-role", TENANT_ID = "x-budibase-tenant-id", + VERIFICATION_CODE = "x-budibase-verification-code", + RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code", TOKEN = "x-budibase-token", CSRF_TOKEN = "x-csrf-token", CORRELATION_ID = "x-budibase-correlation-id", diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index fc6f6caecb..0e457c07e8 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -72,4 +72,59 @@ export default class AccountAPI { } return response } + + async verifyAccount( + verificationCode: string, + opts: APIRequestOpts = { doExpect: true } + ): Promise { + const [response, json] = await this.client.post( + `/api/accounts/verify`, + { + body: { verificationCode }, + } + ) + if (opts.doExpect) { + expect(response).toHaveStatusCode(200) + } + return response + } + + async verifyAccountSendEmail( + email: string, + opts: APIRequestOpts = { doExpect: true } + ): Promise { + const [response, json] = await this.client.post( + `/api/accounts/verify/send`, + { + body: { email }, + } + ) + if (opts.doExpect) { + expect(response).toHaveStatusCode(200) + } + return response + } + + async search( + searchType: string, + search: 'email' | 'tenantId', + opts: APIRequestOpts = { doExpect: true } + ): Promise { + let body: { email?: string; tenantId?: string } = {} + + if (search === 'email') { + body.email = searchType; + } else if (search === 'tenantId') { + body.tenantId = searchType; + } + + const [response, json] = await this.client.post( + `/api/accounts/search`, + {body: body} + ) + if (opts.doExpect) { + expect(response).toHaveStatusCode(200) + } + return response + } } diff --git a/qa-core/src/account-api/config/TestConfiguration.ts b/qa-core/src/account-api/config/TestConfiguration.ts new file mode 100644 index 0000000000..827cee42b7 --- /dev/null +++ b/qa-core/src/account-api/config/TestConfiguration.ts @@ -0,0 +1,34 @@ +import { AccountInternalAPI } from "../api" +import { BudibaseTestConfiguration } from "../../shared" + +export default class TestConfiguration extends BudibaseTestConfiguration { + // apis + api: AccountInternalAPI + + context: T + + constructor() { + super() + this.api = new AccountInternalAPI(this.state) + this.context = {} + } + + async beforeAll() { + await super.beforeAll() + await this.setApiKey() + } + + async afterAll() { + await super.afterAll() + } + + async setApiKey() { + const apiKeyResponse = await this.internalApi.self.getApiKey() + this.state.apiKey = apiKeyResponse.apiKey + } + + async setCookieAuth() { + const apiKeyResponse = await this.internalApi.self.getApiKey() + this.state.apiKey = apiKeyResponse.apiKey + } +} diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts new file mode 100644 index 0000000000..c6c6fa163f --- /dev/null +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -0,0 +1,20 @@ +import { generator } from "../../shared" +import { Hosting, CreateAccountRequest } from "@budibase/types" + +export const generateAccount = (): CreateAccountRequest => { + const uuid = generator.guid() + + const email = `${uuid}@budibase.com` + const tenant = `tenant${uuid.replace(/-/g, "")}` + + return { + email, + hosting: Hosting.CLOUD, + name: email, + password: uuid, + profession: "software_engineer", + size: "10+", + tenantId: tenant, + tenantName: tenant, + } +} diff --git a/qa-core/src/account-api/fixtures/index.ts b/qa-core/src/account-api/fixtures/index.ts new file mode 100644 index 0000000000..7d09ee1c2c --- /dev/null +++ b/qa-core/src/account-api/fixtures/index.ts @@ -0,0 +1 @@ +export * as accounts from "./accounts" \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/create.spec.ts b/qa-core/src/account-api/tests/accounts/create.spec.ts new file mode 100644 index 0000000000..e46a56e591 --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/create.spec.ts @@ -0,0 +1,20 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" + +describe("Account API - Create Account", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Creates a new account", async () => { + await config.api.accounts.create({ + ...fixtures.accounts.generateAccount() + }) + }) +}) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/delete.spec.ts b/qa-core/src/account-api/tests/accounts/delete.spec.ts new file mode 100644 index 0000000000..22fd39d17f --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/delete.spec.ts @@ -0,0 +1,25 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" + +describe("Account API - Delete Account", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + // it("Deletes an account", async () => { + // + // }) + + it("Deletes an account by ID", async () => { + const [response, account] = await config.api.accounts.create({ + ...fixtures.accounts.generateAccount() + }) + await config.api.accounts.delete(account.accountId) + }) +}) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/search.spec.ts b/qa-core/src/account-api/tests/accounts/search.spec.ts new file mode 100644 index 0000000000..6f4437d119 --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/search.spec.ts @@ -0,0 +1,22 @@ +import TestConfiguration from "../../config/TestConfiguration" +import { generator } from "../../../shared" + +describe("Account API - Search for Account", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Search account by email", async () => { + await config.api.accounts.search(generator.email(), "email") + }) + + it("Search account by tenantId", async () => { + await config.api.accounts.search(generator.word(), "tenantId") + }) +}) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/validate.spec.ts b/qa-core/src/account-api/tests/accounts/validate.spec.ts new file mode 100644 index 0000000000..56894bb03f --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/validate.spec.ts @@ -0,0 +1,27 @@ +import TestConfiguration from "../../config/TestConfiguration" +import { generator } from "../../../shared" +import * as fixtures from "../../fixtures"; + +describe("Account API - Validate Account", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + const tenant = generator.word({length: 6}) + const email = `${tenant}@budibase.com` + + + it("Validates an email", async () => { + await config.api.accounts.validateEmail(email) + }) + + it("Validates a tenant ID", async () => { + await config.api.accounts.validateTenantId(tenant) + }) +}) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/verify.spec.ts b/qa-core/src/account-api/tests/accounts/verify.spec.ts new file mode 100644 index 0000000000..d3f2ff1a18 --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/verify.spec.ts @@ -0,0 +1,24 @@ +import TestConfiguration from "../../config/TestConfiguration" +import { generator } from "../../../shared" +import * as fixtures from "../../fixtures"; + +describe("Account API - Verify Account", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + + it("Verify an account", async () => { + await config.api.accounts.verifyAccount() + }) + + it("Send account verification email ", async () => { + await config.api.accounts.verifyAccountSendEmail() + }) +}) \ No newline at end of file From 6f7ef18084389c8e525109b01e58cfeb742239a4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 12:48:44 +0100 Subject: [PATCH 020/821] Add account password login integration with global setup --- .../src/account-api/api/AccountInternalAPI.ts | 4 ++- qa-core/src/account-api/api/apis/AuthAPI.ts | 32 +++++++++++++++++++ qa-core/src/account-api/api/apis/index.ts | 1 + qa-core/src/jest/globalSetup.ts | 3 +- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 qa-core/src/account-api/api/apis/AuthAPI.ts diff --git a/qa-core/src/account-api/api/AccountInternalAPI.ts b/qa-core/src/account-api/api/AccountInternalAPI.ts index a5abdcbccd..3813ad2c9e 100644 --- a/qa-core/src/account-api/api/AccountInternalAPI.ts +++ b/qa-core/src/account-api/api/AccountInternalAPI.ts @@ -1,15 +1,17 @@ import AccountInternalAPIClient from "./AccountInternalAPIClient" -import { AccountAPI, LicenseAPI } from "./apis" +import { AccountAPI, LicenseAPI, AuthAPI } from "./apis" import { State } from "../../types" export default class AccountInternalAPI { client: AccountInternalAPIClient + auth: AuthAPI accounts: AccountAPI licenses: LicenseAPI constructor(state: State) { this.client = new AccountInternalAPIClient(state) + this.auth = new AuthAPI(this.client) this.accounts = new AccountAPI(this.client) this.licenses = new LicenseAPI(this.client) } diff --git a/qa-core/src/account-api/api/apis/AuthAPI.ts b/qa-core/src/account-api/api/apis/AuthAPI.ts new file mode 100644 index 0000000000..4d2dee6a0d --- /dev/null +++ b/qa-core/src/account-api/api/apis/AuthAPI.ts @@ -0,0 +1,32 @@ +import { Response } from "node-fetch" +import AccountInternalAPIClient from "../AccountInternalAPIClient" +import { APIRequestOpts } from "../../../types" + +export default class AuthAPI { + client: AccountInternalAPIClient + + constructor(client: AccountInternalAPIClient) { + this.client = client + } + + async login( + email: string, + password: string, + opts: APIRequestOpts = { doExpect: true } + ): Promise<[Response, string]> { + const [response, json] = await this.client.post( + `/api/auth/login`, + { + body: { + username: email, + password: password, + }, + } + ) + if (opts.doExpect) { + expect(response).toHaveStatusCode(200) + } + const cookie = response.headers.get("set-cookie") + return [response, cookie!] + } +} diff --git a/qa-core/src/account-api/api/apis/index.ts b/qa-core/src/account-api/api/apis/index.ts index b8a1dc7b4a..1137ac3e36 100644 --- a/qa-core/src/account-api/api/apis/index.ts +++ b/qa-core/src/account-api/api/apis/index.ts @@ -1,2 +1,3 @@ +export { default as AuthAPI } from "./AuthAPI" export { default as AccountAPI } from "./AccountAPI" export { default as LicenseAPI } from "./LicenseAPI" diff --git a/qa-core/src/jest/globalSetup.ts b/qa-core/src/jest/globalSetup.ts index 040a65ecef..f73ea1011e 100644 --- a/qa-core/src/jest/globalSetup.ts +++ b/qa-core/src/jest/globalSetup.ts @@ -68,8 +68,7 @@ async function loginAsAdmin() { } async function loginAsAccount(account: CreateAccountRequest) { - const [res, cookie] = await internalApi.auth.login( - account.tenantId, + const [res, cookie] = await accountsApi.auth.login( account.email, account.password, API_OPTS From 052a74f1d75fa0293df3a96cd818426fda1fbdf4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 21:53:05 +0100 Subject: [PATCH 021/821] offline license structure --- .../core/utilities/structures/licenses.ts | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 22e73f2871..35c9156ec6 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,7 @@ import { Billing, Customer, Feature, - License, + License, OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -11,6 +11,7 @@ import { Quotas, Subscription, } from "@budibase/types" +import { generator } from "./generator" export function price(): PurchasedPrice { return { @@ -127,14 +128,16 @@ export function subscription(): Subscription { } } +interface GenerateLicenseOpts { + quotas?: Quotas + plan?: PurchasedPlan + planType?: PlanType + features?: Feature[] + billing?: Billing +} + export const license = ( - opts: { - quotas?: Quotas - plan?: PurchasedPlan - planType?: PlanType - features?: Feature[] - billing?: Billing - } = {} + opts: GenerateLicenseOpts = {} ): License => { return { features: opts.features || [], @@ -143,3 +146,17 @@ export const license = ( billing: opts.billing || billing(), } } + +export function offlineLicense ( + opts: GenerateLicenseOpts = {} +): OfflineLicense { + const base = license(opts) + return { + ...base, + expireAt: new Date().toISOString(), + identifier: { + installId: generator.guid(), + tenantId: generator.guid() + } + } +} \ No newline at end of file From 1371a009a85dcaa9115b058564ceb9a287b70f85 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 22:06:52 +0100 Subject: [PATCH 022/821] use automocking in offline.spec.ts --- .../backend-core/tests/core/utilities/structures/accounts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 807153cd09..42e9d4b63a 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -13,7 +13,7 @@ import { } from "@budibase/types" import _ from "lodash" -export const account = (): Account => { +export const account = (partial: Partial = {}): Account => { return { accountId: uuid(), tenantId: generator.word(), @@ -29,6 +29,7 @@ export const account = (): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), + ...partial } } From a481eee39e0fe795cbc84c4dd000c548b3bffecd Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 14 Jul 2023 09:11:34 +0100 Subject: [PATCH 023/821] Binding selection fixes, delete controller refactor and some fixes --- .../builder/src/builderStore/dataBinding.js | 2 + .../actions/DeleteRow.svelte | 78 +++++++++++-------- .../controls/ButtonActionEditor/manifest.json | 1 + .../src/components/app/table/Table.svelte | 8 ++ packages/client/src/utils/buttonActions.js | 50 ++++++++++-- .../server/src/api/controllers/public/rows.ts | 2 +- .../server/src/api/controllers/row/index.ts | 17 ++++ 7 files changed, 117 insertions(+), 41 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index e9c8643bce..bbe116721a 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -491,6 +491,7 @@ const getSelectedRowsBindings = asset => { readableBinding: `${table._instanceName}.Selected rows`, category: "Selected rows", icon: "ViewRow", + display: { name: table._instanceName }, })) ) @@ -506,6 +507,7 @@ const getSelectedRowsBindings = asset => { )}.${makePropSafe("selectedRows")}`, readableBinding: `${block._instanceName}.Selected rows`, category: "Selected rows", + display: { name: block._instanceName }, })) ) } 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 109eb9a956..e4a5f171ff 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 @@
- - Please specify one or more rows to delete. +
+ + + {/if} +
diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json index 2ec7235c59..6ed545f541 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/manifest.json @@ -24,6 +24,7 @@ }, { "name": "Delete Row", + "displayName": "Delete Rows", "type": "data", "component": "DeleteRow" }, diff --git a/packages/client/src/components/app/table/Table.svelte b/packages/client/src/components/app/table/Table.svelte index 248151a7a2..0ed76317db 100644 --- a/packages/client/src/components/app/table/Table.svelte +++ b/packages/client/src/components/app/table/Table.svelte @@ -47,6 +47,14 @@ ) } + // If the data changes, double check that the selected elements are still present. + $: if (data) { + let rowIds = data.map(row => row._id) + if (rowIds.length) { + selectedRows = selectedRows.filter(row => rowIds.includes(row._id)) + } + } + const getFields = (schema, customColumns, showAutoColumns) => { // Check for an invalid column selection let invalid = false diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 68f312f0ad..97d0d827bd 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -101,13 +101,47 @@ const fetchRowHandler = async action => { } } -const deleteRowHandler = async action => { - const { tableId, revId, rowId, notificationOverride } = action.parameters - if (tableId && rowId) { +const deleteRowHandler = async (action, context) => { + const { tableId, rowId: rowConfig, notificationOverride } = action.parameters + + if (tableId && rowConfig) { try { - await API.deleteRow({ tableId, rowId, revId }) + let requestConfig + + let parsedRowConfig = [] + if (typeof rowConfig === "string") { + try { + parsedRowConfig = JSON.parse(rowConfig) + } catch (e) { + parsedRowConfig = rowConfig + .split(",") + .map(id => id.trim()) + .filter(id => id) + } + } else { + parsedRowConfig = rowConfig + } + + if ( + typeof parsedRowConfig === "object" && + parsedRowConfig.constructor === Object + ) { + requestConfig = [parsedRowConfig] + } else if (Array.isArray(parsedRowConfig)) { + requestConfig = parsedRowConfig + } + + if (!requestConfig.length) { + notificationStore.actions.warning("No valid rows were supplied") + return false + } + + const resp = await API.deleteRows({ tableId, rows: requestConfig }) + if (!notificationOverride) { - notificationStore.actions.success("Row deleted") + notificationStore.actions.success( + resp?.length == 1 ? "Row deleted" : `${resp.length} Rows deleted` + ) } // Refresh related datasources @@ -115,8 +149,10 @@ const deleteRowHandler = async action => { invalidateRelationships: true, }) } catch (error) { - // Abort next actions - return false + console.error(error) + notificationStore.actions.error( + "An error occurred while executing the query" + ) } } } diff --git a/packages/server/src/api/controllers/public/rows.ts b/packages/server/src/api/controllers/public/rows.ts index df856f1fe0..39cf85a2a3 100644 --- a/packages/server/src/api/controllers/public/rows.ts +++ b/packages/server/src/api/controllers/public/rows.ts @@ -5,7 +5,7 @@ import { convertBookmark } from "../../../utilities" // makes sure that the user doesn't need to pass in the type, tableId or _id params for // the call to be correct -function fixRow(row: Row, params: any) { +export function fixRow(row: Row, params: any) { if (!params || !row) { return row } diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 91270429a4..6a969affaf 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -5,6 +5,8 @@ import { isExternalTable } from "../../../integrations/utils" import { Ctx } from "@budibase/types" import * as utils from "./utils" import { gridSocket } from "../../../websockets" +import { addRev } from "../public/utils" +import { fixRow } from "../public/rows" function pickApi(tableId: any) { if (isExternalTable(tableId)) { @@ -88,7 +90,22 @@ export async function destroy(ctx: any) { const inputs = ctx.request.body const tableId = utils.getTableId(ctx) let response, row + if (inputs.rows) { + const targetRows = inputs.rows.map( + (row: { [key: string]: string | string }) => { + let processedRow = typeof row == "string" ? { _id: row } : row + return !processedRow._rev + ? addRev(fixRow(processedRow, ctx.params), tableId) + : fixRow(processedRow, ctx.params) + } + ) + + const rowDeletes = await Promise.all(targetRows) + if (rowDeletes) { + ctx.request.body.rows = rowDeletes + } + let { rows } = await quotas.addQuery( () => pickApi(tableId).bulkDestroy(ctx), { From 41dc86436cdf93490485b9836474b197068f22ac Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 09:21:23 +0100 Subject: [PATCH 024/821] Add structures for Installation type --- .../tests/core/utilities/structures/index.ts | 1 + .../tests/core/utilities/structures/installation.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 packages/backend-core/tests/core/utilities/structures/installation.ts diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index 2c094f43a7..c4404856e1 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,6 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" +export * as installation from "./installation" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/installation.ts new file mode 100644 index 0000000000..bd2ae4abfe --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/installation.ts @@ -0,0 +1,10 @@ +import { generator } from "@budibase/backend-core/tests" +import { Installation } from "@budibase/types" + +export function install(): Installation { + return { + _id: "install", + installId: generator.guid(), + version: generator.string() + } +} \ No newline at end of file From 7f3dfc8bbaab834ad655654d4d366b98070fdda0 Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 14 Jul 2023 11:42:22 +0100 Subject: [PATCH 025/821] Linting --- packages/client/src/utils/buttonActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 97d0d827bd..653ed6a98c 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -101,7 +101,7 @@ const fetchRowHandler = async action => { } } -const deleteRowHandler = async (action, context) => { +const deleteRowHandler = async action => { const { tableId, rowId: rowConfig, notificationOverride } = action.parameters if (tableId && rowConfig) { From 3326d061f19ebcf4b46e76ff3a92ec5e14ca58ea Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 11:44:05 +0100 Subject: [PATCH 026/821] core structure updates --- .../tests/core/utilities/structures/db.ts | 6 +++--- .../utilities/structures/documents/index.ts | 1 + .../structures/documents/platform/index.ts | 1 + .../{ => documents/platform}/installation.ts | 2 ++ .../tests/core/utilities/structures/index.ts | 2 +- .../core/utilities/structures/licenses.ts | 19 ++++++++++++++----- 6 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/index.ts create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts rename packages/backend-core/tests/core/utilities/structures/{ => documents/platform}/installation.ts (82%) diff --git a/packages/backend-core/tests/core/utilities/structures/db.ts b/packages/backend-core/tests/core/utilities/structures/db.ts index 31a52dce8b..87325573eb 100644 --- a/packages/backend-core/tests/core/utilities/structures/db.ts +++ b/packages/backend-core/tests/core/utilities/structures/db.ts @@ -1,4 +1,4 @@ -import { structures } from ".." +import { generator } from "./generator" import { newid } from "../../../../src/docIds/newid" export function id() { @@ -6,7 +6,7 @@ export function id() { } export function rev() { - return `${structures.generator.character({ + return `${generator.character({ numeric: true, - })}-${structures.uuid().replace(/-/, "")}` + })}-${generator.guid().replace(/-/, "")}` } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts new file mode 100644 index 0000000000..1c82c5b7d4 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -0,0 +1 @@ +export * from "./platform" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts new file mode 100644 index 0000000000..46b85f0435 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -0,0 +1 @@ +export * as installation from "./installation" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts similarity index 82% rename from packages/backend-core/tests/core/utilities/structures/installation.ts rename to packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index bd2ae4abfe..30d58fd349 100644 --- a/packages/backend-core/tests/core/utilities/structures/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,9 +1,11 @@ import { generator } from "@budibase/backend-core/tests" import { Installation } from "@budibase/types" +import * as db from "../../db" export function install(): Installation { return { _id: "install", + _rev: db.rev(), installId: generator.guid(), version: generator.string() } diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index c4404856e1..1a49e912fc 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,7 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" -export * as installation from "./installation" +export * as docs from "./documents" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 35c9156ec6..fae0c7d807 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,9 @@ import { Billing, Customer, Feature, - License, OfflineLicense, + License, + OfflineIdentifier, + OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -154,9 +156,16 @@ export function offlineLicense ( return { ...base, expireAt: new Date().toISOString(), - identifier: { - installId: generator.guid(), - tenantId: generator.guid() - } + identifier: offlineIdentifier() + } +} + +export function offlineIdentifier( + installId: string = generator.guid(), + tenantId: string = generator.guid(), +): OfflineIdentifier { + return { + installId, + tenantId } } \ No newline at end of file From 7870f313161c17717a22593f3a12c209306c90cf Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 14 Jul 2023 12:10:12 +0100 Subject: [PATCH 027/821] Check presence of next fn before attempting to execute. --- packages/client/src/utils/buttonActions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 68f312f0ad..0405dc7d55 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -478,7 +478,7 @@ export const enrichButtonActions = (actions, context) => { actions.slice(i + 1), newContext ) - resolve(await next()) + resolve(typeof next === "function" ? await next() : true) } else { resolve(false) } From 18a05faf6713acf16f84e4f2b9a736d73e8b5690 Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 14 Jul 2023 14:19:19 +0100 Subject: [PATCH 028/821] Ensure the code editors are in the correct modes --- .../SetupPanel/AutomationBlockSetup.svelte | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 823dcc432b..e26052d1fa 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -33,6 +33,7 @@ import { bindingsToCompletions, jsAutocomplete, + hbAutocomplete, EditorModes, } from "components/common/CodeEditor" import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte" @@ -70,7 +71,10 @@ $: queryLimit = tableId?.includes("datasource") ? "∞" : "1000" $: isTrigger = block?.type === "TRIGGER" $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW - + $: codeMode = + stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS + $: buildCompletions = + stepId === "EXECUTE_BASH" ? hbAutocomplete : jsAutocomplete /** * TODO - Remove after November 2023 * ******************************* @@ -497,17 +501,21 @@ inputData[key] = e.detail }} completions={[ - jsAutocomplete([ - ...bindingsToCompletions(bindings, EditorModes.JS), + buildCompletions([ + ...bindingsToCompletions(bindings, codeMode), ]), ]} - mode={EditorModes.JS} + mode={codeMode} height={500} />
-
Add available bindings by typing $
+
+ Add available bindings by typing {codeMode == EditorModes.JS ? "$" : "{{"} +
From fa94b8b9fc43f8a58e130daa9ca7946821b585ab Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Fri, 14 Jul 2023 14:24:01 +0100 Subject: [PATCH 029/821] Changes for Account API Testing --- .../worker/src/api/controllers/global/auth.ts | 2 ++ .../src/account-api/api/apis/AccountAPI.ts | 7 ++++++ qa-core/src/account-api/api/apis/AuthAPI.ts | 2 +- .../account-api/config/TestConfiguration.ts | 6 ++--- .../account-api/tests/accounts/create.spec.ts | 2 +- .../account-api/tests/accounts/delete.spec.ts | 23 ++++++++++++++++--- .../tests/accounts/validate.spec.ts | 2 ++ .../account-api/tests/accounts/verify.spec.ts | 14 +++++++++++ .../src/shared/BudibaseTestConfiguration.ts | 3 ++- 9 files changed, 51 insertions(+), 10 deletions(-) diff --git a/packages/worker/src/api/controllers/global/auth.ts b/packages/worker/src/api/controllers/global/auth.ts index 131601c6ad..a458a98aa7 100644 --- a/packages/worker/src/api/controllers/global/auth.ts +++ b/packages/worker/src/api/controllers/global/auth.ts @@ -53,6 +53,8 @@ async function passportCallback( } export const login = async (ctx: Ctx, next: any) => { + const tenantId = context.getTenantId() + console.log(tenantId) const email = ctx.request.body.username const user = await userSdk.getUserByEmail(email) diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index 0e457c07e8..7897a3c210 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -73,6 +73,13 @@ export default class AccountAPI { return response } + async deleteCurrentAccount() { + const [response, json] = await this.client.del( + `/api/accounts` + ) + return response + } + async verifyAccount( verificationCode: string, opts: APIRequestOpts = { doExpect: true } diff --git a/qa-core/src/account-api/api/apis/AuthAPI.ts b/qa-core/src/account-api/api/apis/AuthAPI.ts index 4d2dee6a0d..9c375d2b5d 100644 --- a/qa-core/src/account-api/api/apis/AuthAPI.ts +++ b/qa-core/src/account-api/api/apis/AuthAPI.ts @@ -18,7 +18,7 @@ export default class AuthAPI { `/api/auth/login`, { body: { - username: email, + email: email, password: password, }, } diff --git a/qa-core/src/account-api/config/TestConfiguration.ts b/qa-core/src/account-api/config/TestConfiguration.ts index 827cee42b7..ee5f3fbd45 100644 --- a/qa-core/src/account-api/config/TestConfiguration.ts +++ b/qa-core/src/account-api/config/TestConfiguration.ts @@ -27,8 +27,6 @@ export default class TestConfiguration extends BudibaseTestConfiguration { this.state.apiKey = apiKeyResponse.apiKey } - async setCookieAuth() { - const apiKeyResponse = await this.internalApi.self.getApiKey() - this.state.apiKey = apiKeyResponse.apiKey - } + + } diff --git a/qa-core/src/account-api/tests/accounts/create.spec.ts b/qa-core/src/account-api/tests/accounts/create.spec.ts index e46a56e591..2c26c5170b 100644 --- a/qa-core/src/account-api/tests/accounts/create.spec.ts +++ b/qa-core/src/account-api/tests/accounts/create.spec.ts @@ -14,7 +14,7 @@ describe("Account API - Create Account", () => { it("Creates a new account", async () => { await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() + ...fixtures.accounts.generateAccount() }) }) }) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/delete.spec.ts b/qa-core/src/account-api/tests/accounts/delete.spec.ts index 22fd39d17f..20df48dafb 100644 --- a/qa-core/src/account-api/tests/accounts/delete.spec.ts +++ b/qa-core/src/account-api/tests/accounts/delete.spec.ts @@ -12,9 +12,26 @@ describe("Account API - Delete Account", () => { await config.afterAll() }) - // it("Deletes an account", async () => { - // - // }) + it("Deletes an account", async () => { + // Create account + const createAccountRequest = fixtures.accounts.generateAccount() + + await config.api.accounts.create( + createAccountRequest + ) + + // Login - Get cookie + await config.login( + createAccountRequest.email, + createAccountRequest.password, + createAccountRequest.tenantId + ) + + // Delete account + const [ res ] = await config.api.accounts.deleteCurrentAccount() + + expect(res.status).toBe(204) + }) it("Deletes an account by ID", async () => { const [response, account] = await config.api.accounts.create({ diff --git a/qa-core/src/account-api/tests/accounts/validate.spec.ts b/qa-core/src/account-api/tests/accounts/validate.spec.ts index 56894bb03f..5794949eee 100644 --- a/qa-core/src/account-api/tests/accounts/validate.spec.ts +++ b/qa-core/src/account-api/tests/accounts/validate.spec.ts @@ -18,10 +18,12 @@ describe("Account API - Validate Account", () => { it("Validates an email", async () => { + await config.api.accounts.validateEmail(email) }) it("Validates a tenant ID", async () => { + await config.api.accounts.validateTenantId(tenant) }) }) \ No newline at end of file diff --git a/qa-core/src/account-api/tests/accounts/verify.spec.ts b/qa-core/src/account-api/tests/accounts/verify.spec.ts index d3f2ff1a18..222c4cb4ea 100644 --- a/qa-core/src/account-api/tests/accounts/verify.spec.ts +++ b/qa-core/src/account-api/tests/accounts/verify.spec.ts @@ -15,10 +15,24 @@ describe("Account API - Verify Account", () => { it("Verify an account", async () => { + // Create account + await config.api.accounts.create({ + ...fixtures.accounts.generateAccount() + }) + // Invite user + + // Verify account via code await config.api.accounts.verifyAccount() }) it("Send account verification email ", async () => { + // Create account + await config.api.accounts.create({ + ...fixtures.accounts.generateAccount() + }) + // Invite user + + // Verify account via email await config.api.accounts.verifyAccountSendEmail() }) }) \ No newline at end of file diff --git a/qa-core/src/shared/BudibaseTestConfiguration.ts b/qa-core/src/shared/BudibaseTestConfiguration.ts index 12a0f16138..d8fd2bee61 100644 --- a/qa-core/src/shared/BudibaseTestConfiguration.ts +++ b/qa-core/src/shared/BudibaseTestConfiguration.ts @@ -43,7 +43,8 @@ export default class BudibaseTestConfiguration { async login(email: string, password: string, tenantId?: string) { if (!tenantId && this.state.tenantId) { tenantId = this.state.tenantId - } else { + } + if (!tenantId) { throw new Error("Could not determine tenant id") } const [res, cookie] = await this.internalApi.auth.login( From f45a439b2686fcd70b85d9597bf0d432811bbafb Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 15:03:51 +0100 Subject: [PATCH 030/821] Add state helpers for isolating account deletion test --- .../account-api/tests/accounts/delete.spec.ts | 30 +++++++++---------- .../src/shared/BudibaseTestConfiguration.ts | 24 +++++++++++++++ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/qa-core/src/account-api/tests/accounts/delete.spec.ts b/qa-core/src/account-api/tests/accounts/delete.spec.ts index 20df48dafb..2d476d78c0 100644 --- a/qa-core/src/account-api/tests/accounts/delete.spec.ts +++ b/qa-core/src/account-api/tests/accounts/delete.spec.ts @@ -13,24 +13,22 @@ describe("Account API - Delete Account", () => { }) it("Deletes an account", async () => { - // Create account - const createAccountRequest = fixtures.accounts.generateAccount() + await config.doInNewState(async () => { + // Create account + const createAccountRequest = fixtures.accounts.generateAccount() + await config.api.accounts.create(createAccountRequest) - await config.api.accounts.create( - createAccountRequest - ) + // Login - Get cookie + await config.login( + createAccountRequest.email, + createAccountRequest.password, + createAccountRequest.tenantId + ) - // Login - Get cookie - await config.login( - createAccountRequest.email, - createAccountRequest.password, - createAccountRequest.tenantId - ) - - // Delete account - const [ res ] = await config.api.accounts.deleteCurrentAccount() - - expect(res.status).toBe(204) + // Delete account + const res = await config.api.accounts.deleteCurrentAccount() + expect(res.status).toBe(204) + }) }) it("Deletes an account by ID", async () => { diff --git a/qa-core/src/shared/BudibaseTestConfiguration.ts b/qa-core/src/shared/BudibaseTestConfiguration.ts index d8fd2bee61..c1aaeb0cae 100644 --- a/qa-core/src/shared/BudibaseTestConfiguration.ts +++ b/qa-core/src/shared/BudibaseTestConfiguration.ts @@ -40,6 +40,30 @@ export default class BudibaseTestConfiguration { // AUTH + async doInNewState(task: any) { + return this.doWithState(task, {}) + } + + async doWithState(task: any, state: State) { + const original = this.state + + // override the state + this.state.apiKey = state.apiKey + this.state.appId = state.appId + this.state.cookie = state.cookie + this.state.tableId = state.tableId + this.state.tenantId = state.tenantId + + await task() + + // restore the state + this.state.apiKey = original.apiKey + this.state.appId = original.appId + this.state.cookie = original.cookie + this.state.tableId = original.tableId + this.state.tenantId = original.tenantId + } + async login(email: string, password: string, tenantId?: string) { if (!tenantId && this.state.tenantId) { tenantId = this.state.tenantId From c464e5c91d471596bfae031b8f1330b22b1bfefd Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 15:22:44 +0100 Subject: [PATCH 031/821] Type /api/accounts/search request + response bodies --- packages/types/src/api/account/accounts.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/types/src/api/account/accounts.ts b/packages/types/src/api/account/accounts.ts index 9191babc86..e2accf7c18 100644 --- a/packages/types/src/api/account/accounts.ts +++ b/packages/types/src/api/account/accounts.ts @@ -1,3 +1,4 @@ +import { Account } from "../../documents" import { Hosting } from "../../sdk" export interface CreateAccountRequest { @@ -11,3 +12,11 @@ export interface CreateAccountRequest { name?: string password: string } + +export interface SearchAccountsRequest { + // one or the other - not both + email?: string + tenantId?: string +} + +export type SearchAccountsResponse = Account[] \ No newline at end of file From 278f6f8df3ec48cbcc6331a0338dea0aa60fde8f Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 15:23:22 +0100 Subject: [PATCH 032/821] Update account search tests / add email to state --- .../src/account-api/api/apis/AccountAPI.ts | 8 ++--- .../account-api/config/TestConfiguration.ts | 3 -- .../account-api/tests/accounts/search.spec.ts | 31 ++++++++++++++++--- qa-core/src/jest/globalSetup.ts | 2 ++ .../src/shared/BudibaseTestConfiguration.ts | 4 +++ qa-core/src/types/state.ts | 1 + 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index 7897a3c210..37aa3313ba 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -1,5 +1,5 @@ import { Response } from "node-fetch" -import { Account, CreateAccountRequest } from "@budibase/types" +import { Account, CreateAccountRequest, SearchAccountsRequest, SearchAccountsResponse } from "@budibase/types" import AccountInternalAPIClient from "../AccountInternalAPIClient" import { APIRequestOpts } from "../../../types" @@ -116,8 +116,8 @@ export default class AccountAPI { searchType: string, search: 'email' | 'tenantId', opts: APIRequestOpts = { doExpect: true } - ): Promise { - let body: { email?: string; tenantId?: string } = {} + ): Promise<[Response, SearchAccountsResponse]> { + let body: SearchAccountsRequest = {} if (search === 'email') { body.email = searchType; @@ -132,6 +132,6 @@ export default class AccountAPI { if (opts.doExpect) { expect(response).toHaveStatusCode(200) } - return response + return [response, json] } } diff --git a/qa-core/src/account-api/config/TestConfiguration.ts b/qa-core/src/account-api/config/TestConfiguration.ts index ee5f3fbd45..6322be97f0 100644 --- a/qa-core/src/account-api/config/TestConfiguration.ts +++ b/qa-core/src/account-api/config/TestConfiguration.ts @@ -26,7 +26,4 @@ export default class TestConfiguration extends BudibaseTestConfiguration { const apiKeyResponse = await this.internalApi.self.getApiKey() this.state.apiKey = apiKeyResponse.apiKey } - - - } diff --git a/qa-core/src/account-api/tests/accounts/search.spec.ts b/qa-core/src/account-api/tests/accounts/search.spec.ts index 6f4437d119..a39cf8e325 100644 --- a/qa-core/src/account-api/tests/accounts/search.spec.ts +++ b/qa-core/src/account-api/tests/accounts/search.spec.ts @@ -12,11 +12,32 @@ describe("Account API - Search for Account", () => { await config.afterAll() }) - it("Search account by email", async () => { - await config.api.accounts.search(generator.email(), "email") - }) - it("Search account by tenantId", async () => { - await config.api.accounts.search(generator.word(), "tenantId") + describe("POST /api/accounts/search", () => { + describe("by tenant", () => { + it("returns 200 + empty", async () => { + const tenantId = generator.string() + const [res, body] = await config.api.accounts.search(tenantId, "tenantId") + expect(res.status).toBe(200) + expect(body.length).toBe(0) + }) + + it("returns 200 + found", async () => { + const [res, body] = await config.api.accounts.search(config.state.tenantId!, "tenantId") + expect(res.status).toBe(200) + expect(body.length).toBe(1) + expect(body[0].tenantId).toBe(config.state.tenantId) + }) + }) + + describe("by email", () => { + it("returns 200 + empty", async () => { + await config.api.accounts.search(generator.word(), "email") + }) + + it("returns 200 + found", async () => { + await config.api.accounts.search(generator.word(), "email") + }) + }) }) }) \ No newline at end of file diff --git a/qa-core/src/jest/globalSetup.ts b/qa-core/src/jest/globalSetup.ts index f73ea1011e..e52f8cfe16 100644 --- a/qa-core/src/jest/globalSetup.ts +++ b/qa-core/src/jest/globalSetup.ts @@ -89,6 +89,8 @@ async function setup() { // @ts-ignore global.qa.tenantId = account.tenantId // @ts-ignore + global.qa.email = account.email + // @ts-ignore global.qa.accountId = newAccount.accountId await loginAsAccount(account) } else { diff --git a/qa-core/src/shared/BudibaseTestConfiguration.ts b/qa-core/src/shared/BudibaseTestConfiguration.ts index c1aaeb0cae..c6955fd87f 100644 --- a/qa-core/src/shared/BudibaseTestConfiguration.ts +++ b/qa-core/src/shared/BudibaseTestConfiguration.ts @@ -23,6 +23,8 @@ export default class BudibaseTestConfiguration { // @ts-ignore this.state.tenantId = global.qa.tenantId // @ts-ignore + this.state.email = global.qa.email + // @ts-ignore this.state.cookie = global.qa.authCookie } @@ -53,6 +55,7 @@ export default class BudibaseTestConfiguration { this.state.cookie = state.cookie this.state.tableId = state.tableId this.state.tenantId = state.tenantId + this.state.email = state.email await task() @@ -62,6 +65,7 @@ export default class BudibaseTestConfiguration { this.state.cookie = original.cookie this.state.tableId = original.tableId this.state.tenantId = original.tenantId + this.state.email = original.email } async login(email: string, password: string, tenantId?: string) { diff --git a/qa-core/src/types/state.ts b/qa-core/src/types/state.ts index 0da471e090..8fab3998ce 100644 --- a/qa-core/src/types/state.ts +++ b/qa-core/src/types/state.ts @@ -4,4 +4,5 @@ export interface State { cookie?: string tableId?: string tenantId?: string + email?: string } From 4a38d55ce8837d9efa1ea2c8df3d10933d4ec88b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 16:55:48 +0100 Subject: [PATCH 033/821] Lint --- .../core/utilities/structures/accounts.ts | 2 +- .../utilities/structures/documents/index.ts | 2 +- .../structures/documents/platform/index.ts | 2 +- .../documents/platform/installation.ts | 4 +-- .../core/utilities/structures/licenses.ts | 16 ++++------ .../builder/portal/account/upgrade.svelte | 22 ++++++++------ packages/builder/src/stores/portal/admin.js | 2 +- packages/frontend-core/src/api/licensing.js | 3 +- packages/pro | 2 +- packages/types/src/api/account/license.ts | 2 +- packages/types/src/api/web/global/license.ts | 2 +- packages/types/src/sdk/licensing/license.ts | 2 +- packages/types/src/shared/typeUtils.ts | 2 +- packages/worker/__mocks__/@budibase/pro.ts | 6 ++-- .../src/api/controllers/global/license.ts | 16 +++++++--- .../worker/src/api/routes/global/license.ts | 29 ++++++++++++++----- .../api/routes/global/tests/license.spec.ts | 22 +++++++++----- packages/worker/src/tests/api/license.ts | 7 +++-- 18 files changed, 88 insertions(+), 55 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 42e9d4b63a..8476399aa3 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -29,7 +29,7 @@ export const account = (partial: Partial = {}): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), - ...partial + ...partial, } } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts index 1c82c5b7d4..c3bfba3597 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -1 +1 @@ -export * from "./platform" \ No newline at end of file +export * from "./platform" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts index 46b85f0435..98a6314999 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -1 +1 @@ -export * as installation from "./installation" \ No newline at end of file +export * as installation from "./installation" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 30d58fd349..25c5e50e00 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -7,6 +7,6 @@ export function install(): Installation { _id: "install", _rev: db.rev(), installId: generator.guid(), - version: generator.string() + version: generator.string(), } -} \ No newline at end of file +} diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index fae0c7d807..5cce84edfd 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -138,9 +138,7 @@ interface GenerateLicenseOpts { billing?: Billing } -export const license = ( - opts: GenerateLicenseOpts = {} -): License => { +export const license = (opts: GenerateLicenseOpts = {}): License => { return { features: opts.features || [], quotas: opts.quotas || quotas(), @@ -149,23 +147,21 @@ export const license = ( } } -export function offlineLicense ( - opts: GenerateLicenseOpts = {} -): OfflineLicense { +export function offlineLicense(opts: GenerateLicenseOpts = {}): OfflineLicense { const base = license(opts) return { ...base, expireAt: new Date().toISOString(), - identifier: offlineIdentifier() + identifier: offlineIdentifier(), } } export function offlineIdentifier( installId: string = generator.guid(), - tenantId: string = generator.guid(), + tenantId: string = generator.guid() ): OfflineIdentifier { return { installId, - tenantId + tenantId, } -} \ No newline at end of file +} diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 8e2fd5c641..8f76286c8e 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -11,7 +11,7 @@ ButtonGroup, notifications, CopyInput, - File + File, } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -35,9 +35,7 @@ let offlineLicenseIdentifier = "" let offlineLicense = undefined - const offlineLicenseExtensions = [ - ".txt", - ] + const offlineLicenseExtensions = [".txt"] // Make sure page can't be visited directly in cloud $: { @@ -97,7 +95,7 @@ const license = await API.getOfflineLicense() if (license) { offlineLicense = { - name: "license" + name: "license", } } else { offlineLicense = undefined @@ -147,7 +145,7 @@ // prevent file preview jitter by assigning constant // as soon as possible offlineLicense = { - name: "license" + name: "license", } const reader = new FileReader() reader.readAsText(event.detail) @@ -202,7 +200,9 @@ {#if $admin.offlineMode} Installation identifier - Share this with support@budibase.com to obtain your offline license + Share this with support@budibase.com to obtain your offline license
@@ -263,8 +263,12 @@ You are currently on the {license.plan.type} plan
- If you purchase or update your plan on the account - portal, click the refresh button to sync those changes + If you purchase or update your plan on the account + portal, click the refresh button to sync those changes
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 19abcfd841..2106acac27 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,7 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, - offlineMode: false + offlineMode: false, } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 9fe98d5e74..987fc34cf5 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,5 +1,4 @@ export const buildLicensingEndpoints = API => ({ - // LICENSE KEY activateLicenseKey: async data => { @@ -31,7 +30,7 @@ export const buildLicensingEndpoints = API => ({ return API.post({ url: "/api/global/license/offline", body: { - offlineLicenseToken + offlineLicenseToken, }, }) }, diff --git a/packages/pro b/packages/pro index 544c7e067d..b5124e76b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 544c7e067de69832469cde673e59501480d6d98a +Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index e23f6cab97..edb1267ecf 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -36,4 +36,4 @@ export interface CreateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string license: OfflineLicense -} \ No newline at end of file +} diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 900a0db96d..21d8876412 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -5,7 +5,7 @@ export interface ActivateLicenseKeyRequest { } export interface GetLicenseKeyResponse { - licenseKey: string, + licenseKey: string } // OFFLINE LICENSE diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index e8ad98fe7a..105c3680a3 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -2,7 +2,7 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." import { ISO8601 } from "../../shared" export interface OfflineIdentifier { - installId: string, + installId: string tenantId: string } diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 143865c60b..fbe215fdb9 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -2,4 +2,4 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } -export type ISO8601 = string \ No newline at end of file +export type ISO8601 = string diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index bd6250fede..f1e79bd7c7 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,16 +11,16 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), - getIdentifierBase64: jest.fn() + getIdentifierBase64: jest.fn(), }, cache: { ...actual.licensing.cache, refresh: jest.fn(), - } + }, }, quotas: { ...actual.quotas, - getQuotaUsage: jest.fn() + getQuotaUsage: jest.fn(), }, } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index fa95eeee0d..111cb5cea3 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -10,7 +10,9 @@ import { // LICENSE KEY -export async function activateLicenseKey(ctx: UserCtx) { +export async function activateLicenseKey( + ctx: UserCtx +) { const { licenseKey } = ctx.request.body await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 @@ -33,13 +35,17 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicenseToken(ctx: UserCtx) { +export async function activateOfflineLicenseToken( + ctx: UserCtx +) { const { offlineLicenseToken } = ctx.request.body await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicenseToken(ctx: UserCtx) { +export async function getOfflineLicenseToken( + ctx: UserCtx +) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -54,7 +60,9 @@ export async function deleteOfflineLicenseToken(ctx: UserCtx) { ctx.status = 204 } -export async function getOfflineLicenseIdentifier(ctx: UserCtx) { +export async function getOfflineLicenseIdentifier( + ctx: UserCtx +) { const identifierBase64 = await licensing.offline.getIdentifierBase64() ctx.body = { identifierBase64 } ctx.status = 200 diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 199dbb3846..b0e474e4d5 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -3,13 +3,17 @@ import * as controller from "../../controllers/global/license" import { middleware } from "@budibase/backend-core" import Joi from "joi" -const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ +const activateLicenseKeyValidator = middleware.joiValidator.body( + Joi.object({ licenseKey: Joi.string().required(), - }).required()) + }).required() +) -const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ +const activateOfflineLicenseValidator = middleware.joiValidator.body( + Joi.object({ offlineLicenseToken: Joi.string().required(), -}).required()) + }).required() +) const router: Router = new Router() @@ -17,13 +21,24 @@ router .post("/api/global/license/refresh", controller.refresh) .get("/api/global/license/usage", controller.getQuotaUsage) // LICENSE KEY - .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .post( + "/api/global/license/key", + activateLicenseKeyValidator, + controller.activateLicenseKey + ) .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .post( + "/api/global/license/offline", + activateOfflineLicenseValidator, + controller.activateOfflineLicenseToken + ) .get("/api/global/license/offline", controller.getOfflineLicenseToken) .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) - .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) + .get( + "/api/global/license/offline/identifier", + controller.getOfflineLicenseIdentifier + ) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index 512d6c9c52..26e3b3dfb5 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -37,7 +37,9 @@ describe("/api/global/license", () => { describe("POST /api/global/license/key", () => { it("returns 200", async () => { - const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + const res = await config.api.license.activateLicenseKey({ + licenseKey: "licenseKey", + }) expect(res.status).toBe(200) expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") }) @@ -53,7 +55,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getLicenseKey() expect(res.status).toBe(200) expect(res.body).toEqual({ - licenseKey: "*" + licenseKey: "*", }) }) }) @@ -68,8 +70,12 @@ describe("/api/global/license", () => { describe("POST /api/global/license/offline", () => { it("activates offline license", async () => { - const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) - expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + const res = await config.api.license.activateOfflineLicense({ + offlineLicenseToken: "offlineLicenseToken", + }) + expect(licensing.offline.activateOfflineLicenseToken).toBeCalledWith( + "offlineLicenseToken" + ) expect(res.status).toBe(200) }) }) @@ -80,11 +86,13 @@ describe("/api/global/license", () => { expect(res.status).toBe(404) }) it("returns 200 + offline license token", async () => { - licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + licensing.offline.getOfflineLicenseToken.mockResolvedValue( + "offlineLicenseToken" + ) const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) expect(res.body).toEqual({ - offlineLicenseToken: "*" + offlineLicenseToken: "*", }) }) }) @@ -103,7 +111,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicenseIdentifier() expect(res.status).toBe(200) expect(res.body).toEqual({ - identifierBase64: "base64" + identifierBase64: "base64", }) }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 76d3e88df7..a6645226af 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,6 +1,9 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" -import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseTokenRequest, +} from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { @@ -35,7 +38,7 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/key") .set(this.config.defaultHeaders()) } - activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + activateOfflineLicense = async (body: ActivateOfflineLicenseTokenRequest) => { return this.request .post("/api/global/license/offline") .send(body) From 31422cd3ec316bbe2df585e5ba6337b72e8f9882 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 20:56:50 +0100 Subject: [PATCH 034/821] fix pro mock --- packages/worker/__mocks__/@budibase/pro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index f1e79bd7c7..59c7939111 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -8,7 +8,7 @@ const pro = { deleteLicenseKey: jest.fn(), }, offline: { - activateOfflineLicense: jest.fn(), + activateOfflineLicenseToken: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), getIdentifierBase64: jest.fn(), From ea52013503fe67d7fb6636857724a75ccfd2608c Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 15 Jul 2023 00:12:18 +0100 Subject: [PATCH 035/821] Offline licensing integration tests --- .../src/account-api/api/apis/LicenseAPI.ts | 42 ++++++++++- qa-core/src/account-api/fixtures/accounts.ts | 3 +- .../tests/licensing/offline.spec.ts | 72 +++++++++++++++++++ .../internal-api/api/BudibaseInternalAPI.ts | 3 + qa-core/src/internal-api/api/apis/BaseAPI.ts | 4 +- .../src/internal-api/api/apis/LicenseAPI.ts | 34 +++++++++ 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 qa-core/src/account-api/tests/licensing/offline.spec.ts create mode 100644 qa-core/src/internal-api/api/apis/LicenseAPI.ts diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index e0601fe127..a7c1d63c4b 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -1,5 +1,5 @@ import AccountInternalAPIClient from "../AccountInternalAPIClient" -import { Account, UpdateLicenseRequest } from "@budibase/types" +import { Account, CreateOfflineLicenseRequest, GetOfflineLicenseResponse, UpdateLicenseRequest } from "@budibase/types" import { Response } from "node-fetch" export default class LicenseAPI { @@ -28,4 +28,44 @@ export default class LicenseAPI { } return [response, json] } + + // TODO: Better approach for setting tenant id header + + async createOfflineLicense( + accountId: string, + tenantId: string, + body: CreateOfflineLicenseRequest, + opts: { status?: number } = {} + ): Promise { + const [response, json] = await this.client.post( + `/api/internal/accounts/${accountId}/license/offline`, + { + body, + internal: true, + headers: { + "x-budibase-tenant-id": tenantId + } + } + ) + expect(response.status).toBe(opts.status ? opts.status : 201) + return response + } + + async getOfflineLicense( + accountId: string, + tenantId: string, + opts: { status?: number } = {} + ): Promise<[Response, GetOfflineLicenseResponse]> { + const [response, json] = await this.client.get( + `/api/internal/accounts/${accountId}/license/offline`, + { + internal: true, + headers: { + "x-budibase-tenant-id": tenantId + } + } + ) + expect(response.status).toBe(opts.status ? opts.status : 200) + return [response, json] + } } diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts index c6c6fa163f..fdb499bdb6 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -1,7 +1,7 @@ import { generator } from "../../shared" import { Hosting, CreateAccountRequest } from "@budibase/types" -export const generateAccount = (): CreateAccountRequest => { +export const generateAccount = (partial: Partial): CreateAccountRequest => { const uuid = generator.guid() const email = `${uuid}@budibase.com` @@ -16,5 +16,6 @@ export const generateAccount = (): CreateAccountRequest => { size: "10+", tenantId: tenant, tenantName: tenant, + ...partial, } } diff --git a/qa-core/src/account-api/tests/licensing/offline.spec.ts b/qa-core/src/account-api/tests/licensing/offline.spec.ts new file mode 100644 index 0000000000..f9627ed7e1 --- /dev/null +++ b/qa-core/src/account-api/tests/licensing/offline.spec.ts @@ -0,0 +1,72 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixures from "../../fixtures" +import { Hosting, Feature } from "@budibase/types" + +describe("offline", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + // TODO: Currently requires a self host install + account portal + // How do we flag this as a self host specific test? + + it("creates, activates and deletes offline license", async () => { + // installation: Delete any token + await config.internalApi.license.deleteOfflineLicenseToken() + + // installation: Assert token not found + let [getTokenRes] = await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) + + // installation: Retrieve Identifier + const [getIdentifierRes, identifier] = await config.internalApi.license.getOfflineIdentifier() + + // account-portal: Create self-host account + const createAccountRequest = fixures.accounts.generateAccount({ hosting: Hosting.SELF }) + const [createAccountRes, account] = await config.accountsApi.accounts.create(createAccountRequest) + const accountId = account.accountId! + const tenantId = account.tenantId! + + // account-portal: Enable feature on license + await config.accountsApi.licenses.updateLicense(accountId, { + overrides: { + features: [Feature.OFFLINE] + } + }) + + // account-portal: Create offline token + const expireAt = new Date() + expireAt.setDate(new Date().getDate() + 1) + await config.accountsApi.licenses.createOfflineLicense( + accountId, + tenantId, + { + expireAt: expireAt.toISOString(), + installationIdentifierBase64: identifier.identifierBase64 + }) + + // account-portal: Retrieve offline token + const [getLicenseRes, offlineLicense] = await config.accountsApi.licenses.getOfflineLicense(accountId, tenantId) + + // installation: Activate offline token + await config.internalApi.license.activateOfflineLicenseToken({ + offlineLicenseToken: offlineLicense.offlineLicenseToken + }) + + // installation: Assert token found + await config.internalApi.license.getOfflineLicenseToken() + + // TODO: Assert on license for current user + + // installation: Remove the token + await config.internalApi.license.deleteOfflineLicenseToken() + + // installation: Assert token not found + await config.internalApi.license.getOfflineLicenseToken({ status: 404 }) + }) +}) \ No newline at end of file diff --git a/qa-core/src/internal-api/api/BudibaseInternalAPI.ts b/qa-core/src/internal-api/api/BudibaseInternalAPI.ts index 316775b1b9..9b55c22deb 100644 --- a/qa-core/src/internal-api/api/BudibaseInternalAPI.ts +++ b/qa-core/src/internal-api/api/BudibaseInternalAPI.ts @@ -11,6 +11,7 @@ import DatasourcesAPI from "./apis/DatasourcesAPI" import IntegrationsAPI from "./apis/IntegrationsAPI" import QueriesAPI from "./apis/QueriesAPI" import PermissionsAPI from "./apis/PermissionsAPI" +import LicenseAPI from "./apis/LicenseAPI" import BudibaseInternalAPIClient from "./BudibaseInternalAPIClient" import { State } from "../../types" @@ -30,6 +31,7 @@ export default class BudibaseInternalAPI { integrations: IntegrationsAPI queries: QueriesAPI permissions: PermissionsAPI + license: LicenseAPI constructor(state: State) { this.client = new BudibaseInternalAPIClient(state) @@ -47,5 +49,6 @@ export default class BudibaseInternalAPI { this.integrations = new IntegrationsAPI(this.client) this.queries = new QueriesAPI(this.client) this.permissions = new PermissionsAPI(this.client) + this.license = new LicenseAPI(this.client) } } diff --git a/qa-core/src/internal-api/api/apis/BaseAPI.ts b/qa-core/src/internal-api/api/apis/BaseAPI.ts index b7eae45087..c0a3b344d6 100644 --- a/qa-core/src/internal-api/api/apis/BaseAPI.ts +++ b/qa-core/src/internal-api/api/apis/BaseAPI.ts @@ -8,9 +8,9 @@ export default class BaseAPI { this.client = client } - async get(url: string): Promise<[Response, any]> { + async get(url: string, status?: number): Promise<[Response, any]> { const [response, json] = await this.client.get(url) - expect(response).toHaveStatusCode(200) + expect(response).toHaveStatusCode(status ? status : 200) return [response, json] } diff --git a/qa-core/src/internal-api/api/apis/LicenseAPI.ts b/qa-core/src/internal-api/api/apis/LicenseAPI.ts new file mode 100644 index 0000000000..772ead9166 --- /dev/null +++ b/qa-core/src/internal-api/api/apis/LicenseAPI.ts @@ -0,0 +1,34 @@ +import { Response } from "node-fetch" +import { + ActivateOfflineLicenseTokenRequest, + GetOfflineIdentifierResponse, + GetOfflineLicenseTokenResponse, +} from "@budibase/types" +import BudibaseInternalAPIClient from "../BudibaseInternalAPIClient" +import BaseAPI from "./BaseAPI" + +export default class LicenseAPI extends BaseAPI { + constructor(client: BudibaseInternalAPIClient) { + super(client) + } + + async getOfflineLicenseToken(opts: { status?: number } = {}): Promise<[Response, GetOfflineLicenseTokenResponse]> { + const [response, body] = await this.get(`/global/license/offline`, opts.status) + return [response, body] + } + + async deleteOfflineLicenseToken(): Promise<[Response]> { + const [response] = await this.del(`/global/license/offline`, 204) + return [response] + } + + async activateOfflineLicenseToken(body: ActivateOfflineLicenseTokenRequest): Promise<[Response]> { + const [response] = await this.post(`/global/license/offline`, body) + return [response] + } + + async getOfflineIdentifier(): Promise<[Response, GetOfflineIdentifierResponse]> { + const [response, body] = await this.get(`/global/license/offline/identifier`) + return [response, body] + } +} From 8ea6feb7201615cb9c6bea9f1c259b59b3870ed1 Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 17 Jul 2023 10:21:35 +0100 Subject: [PATCH 036/821] Added autocomplete flag --- .../SetupPanel/AutomationBlockSetup.svelte | 16 +++++----- .../common/CodeEditor/CodeEditor.svelte | 29 ++++++++++++------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index e26052d1fa..0a5b61869c 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -32,7 +32,6 @@ import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import { bindingsToCompletions, - jsAutocomplete, hbAutocomplete, EditorModes, } from "components/common/CodeEditor" @@ -73,8 +72,12 @@ $: isUpdateRow = stepId === ActionStepID.UPDATE_ROW $: codeMode = stepId === "EXECUTE_BASH" ? EditorModes.Handlebars : EditorModes.JS - $: buildCompletions = - stepId === "EXECUTE_BASH" ? hbAutocomplete : jsAutocomplete + + $: stepCompletions = + codeMode === EditorModes.Handlebars + ? [hbAutocomplete([...bindingsToCompletions(bindings, codeMode)])] + : [] + /** * TODO - Remove after November 2023 * ******************************* @@ -500,12 +503,9 @@ onChange({ detail: e.detail }, key) inputData[key] = e.detail }} - completions={[ - buildCompletions([ - ...bindingsToCompletions(bindings, codeMode), - ]), - ]} + completions={stepCompletions} mode={codeMode} + autocompleteEnabled={codeMode != EditorModes.JS} height={500} />
diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index dadca85fac..2ce93e42c8 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -48,6 +48,7 @@ export let mode = EditorModes.Handlebars export let value = "" export let placeholder = null + export let autocompleteEnabled = true // Export a function to expose caret position export const getCaretPosition = () => { @@ -131,12 +132,6 @@ syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }), highlightActiveLineGutter(), highlightSpecialChars(), - autocompletion({ - override: [...completions], - closeOnBlur: true, - icons: false, - optionClass: () => "autocomplete-option", - }), EditorView.lineWrapping, EditorView.updateListener.of(v => { const docStr = v.state.doc?.toString() @@ -159,11 +154,16 @@ const buildExtensions = base => { const complete = [...base] - if (mode.name == "javascript") { - complete.push(javascript()) - complete.push(highlightWhitespace()) - complete.push(lineNumbers()) - complete.push(foldGutter()) + + if (autocompleteEnabled) { + complete.push( + autocompletion({ + override: [...completions], + closeOnBlur: true, + icons: false, + optionClass: () => "autocomplete-option", + }) + ) complete.push( EditorView.inputHandler.of((view, from, to, insert) => { if (insert === "$") { @@ -193,6 +193,13 @@ ) } + if (mode.name == "javascript") { + complete.push(javascript()) + complete.push(highlightWhitespace()) + complete.push(lineNumbers()) + complete.push(foldGutter()) + } + if (placeholder) { complete.push(placeholderFn(placeholder)) } From f17043390226b46cfcf47492506c782847dc6095 Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 17 Jul 2023 11:15:57 +0100 Subject: [PATCH 037/821] Added the binding drawer back in for the javascript step --- .../SetupPanel/AutomationBlockSetup.svelte | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 0a5b61869c..cece075860 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -15,6 +15,7 @@ Icon, Checkbox, DatePicker, + Detail, } from "@budibase/bbui" import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte" import { automationStore, selectedAutomation } from "builderStore" @@ -55,6 +56,7 @@ let drawer let fillWidth = true let inputData + let codeBindingOpen = false $: filters = lookForFilters(schemaProperties) || [] $: tempFilters = filters @@ -496,6 +498,18 @@ /> {:else if value.customType === "code"} + {#if codeMode == EditorModes.JS} + (codeBindingOpen = !codeBindingOpen)} + quiet + icon={codeBindingOpen ? "ChevronDown" : "ChevronRight"} + > + Bindings + + {#if codeBindingOpen} +
{JSON.stringify(bindings, null, 2)}
+ {/if} + {/if} { @@ -509,14 +523,16 @@ height={500} />
- -
-
- Add available bindings by typing {codeMode == EditorModes.JS ? "$" : "{{"} + {#if codeMode == EditorModes.Handlebars} + +
+
+ Add available bindings by typing + }} + +
-
+ {/if}
{:else if value.customType === "loopOption"} From ce24339e22fba3c63419598584d0838b6be537c6 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 16:48:57 +0100 Subject: [PATCH 038/821] Update how blocks key child components for reference when ejecting, and ensure search fields in table blocks are keyed properly --- packages/client/src/components/Block.svelte | 21 +++++++++++-------- .../src/components/BlockComponent.svelte | 10 +++++---- .../components/app/blocks/TableBlock.svelte | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte index b72ff81b49..37af28b4dc 100644 --- a/packages/client/src/components/Block.svelte +++ b/packages/client/src/components/Block.svelte @@ -8,27 +8,28 @@ let structureLookupMap = {} - const registerBlockComponent = (id, order, parentId, instance) => { + const registerBlockComponent = (id, parentId, order, instance) => { // Ensure child map exists if (!structureLookupMap[parentId]) { structureLookupMap[parentId] = {} } // Add this instance in this order, overwriting any existing instance in // this order in case of repeaters - structureLookupMap[parentId][order] = instance + structureLookupMap[parentId][id] = { order, instance } } - const unregisterBlockComponent = (order, parentId) => { + const unregisterBlockComponent = (id, parentId) => { // Ensure child map exists if (!structureLookupMap[parentId]) { return } - delete structureLookupMap[parentId][order] + delete structureLookupMap[parentId][id] } const eject = () => { // Start the new structure with the root component - let definition = structureLookupMap[$component.id][0] + let definition = Object.values(structureLookupMap[$component.id])[0] + .instance // Copy styles from block to root component definition._styles = { @@ -49,10 +50,12 @@ const attachChildren = (rootComponent, map) => { // Transform map into children array let id = rootComponent._id - const children = Object.entries(map[id] || {}).map(([order, instance]) => ({ - order, - instance, - })) + const children = Object.values(map[id] || {}).map( + ({ order, instance }) => ({ + order, + instance, + }) + ) if (!children.length) { return } diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index 4f720e2931..24d9b4dee4 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -23,6 +23,8 @@ // Create a fake component instance so that we can use the core Component // to render this part of the block, taking advantage of binding enrichment $: id = `${block.id}-${context ?? rand}` + $: parentId = $component?.id + $: inBuilder = $builderStore.inBuilder $: instance = { _component: `@budibase/standard-components/${type}`, _id: id, @@ -38,14 +40,14 @@ // Register this block component if we're inside the builder so it can be // ejected later $: { - if ($builderStore.inBuilder) { - block.registerComponent(id, order ?? 0, $component?.id, instance) + if (inBuilder) { + block.registerComponent(id, parentId, order ?? 0, instance) } } onDestroy(() => { - if ($builderStore.inBuilder) { - block.unregisterComponent(order ?? 0, $component?.id) + if (inBuilder) { + block.unregisterComponent(id, parentId) } }) diff --git a/packages/client/src/components/app/blocks/TableBlock.svelte b/packages/client/src/components/app/blocks/TableBlock.svelte index e45b53880d..eb42d85f45 100644 --- a/packages/client/src/components/app/blocks/TableBlock.svelte +++ b/packages/client/src/components/app/blocks/TableBlock.svelte @@ -169,7 +169,7 @@ order={1} > {#if enrichedSearchColumns?.length} - {#each enrichedSearchColumns as column, idx} + {#each enrichedSearchColumns as column, idx (column.name)} Date: Mon, 17 Jul 2023 16:49:39 +0100 Subject: [PATCH 039/821] Ensure search fields in cards block are keyed properly --- packages/client/src/components/app/blocks/CardsBlock.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/app/blocks/CardsBlock.svelte b/packages/client/src/components/app/blocks/CardsBlock.svelte index bbe54867ee..3e48247f92 100644 --- a/packages/client/src/components/app/blocks/CardsBlock.svelte +++ b/packages/client/src/components/app/blocks/CardsBlock.svelte @@ -126,7 +126,7 @@ order={1} > {#if enrichedSearchColumns?.length} - {#each enrichedSearchColumns as column, idx} + {#each enrichedSearchColumns as column, idx (column.name)} Date: Mon, 17 Jul 2023 16:56:48 +0100 Subject: [PATCH 040/821] Increase safety when ejecting blocks --- packages/client/src/components/Block.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte index 37af28b4dc..65c2b38197 100644 --- a/packages/client/src/components/Block.svelte +++ b/packages/client/src/components/Block.svelte @@ -28,8 +28,11 @@ const eject = () => { // Start the new structure with the root component - let definition = Object.values(structureLookupMap[$component.id])[0] - .instance + const rootMap = structureLookupMap[$component.id] || {} + let definition = { ...Object.values(rootMap)[0]?.instance } + if (!definition) { + return + } // Copy styles from block to root component definition._styles = { From 4425c5992933c01f75e6b9c4d5f83b8c4abf19c9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 16:57:56 +0100 Subject: [PATCH 041/821] Remove pointless shallow clone --- packages/client/src/components/Block.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte index 65c2b38197..26be3fd4bf 100644 --- a/packages/client/src/components/Block.svelte +++ b/packages/client/src/components/Block.svelte @@ -29,7 +29,7 @@ const eject = () => { // Start the new structure with the root component const rootMap = structureLookupMap[$component.id] || {} - let definition = { ...Object.values(rootMap)[0]?.instance } + let definition = Object.values(rootMap)[0]?.instance if (!definition) { return } From 92e4422e893cea97f22819a7e1f97f0eae83cb76 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 16:59:40 +0100 Subject: [PATCH 042/821] Remove pointless map --- packages/client/src/components/Block.svelte | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/client/src/components/Block.svelte b/packages/client/src/components/Block.svelte index 26be3fd4bf..a739065015 100644 --- a/packages/client/src/components/Block.svelte +++ b/packages/client/src/components/Block.svelte @@ -53,12 +53,7 @@ const attachChildren = (rootComponent, map) => { // Transform map into children array let id = rootComponent._id - const children = Object.values(map[id] || {}).map( - ({ order, instance }) => ({ - order, - instance, - }) - ) + const children = Object.values(map[id] || {}) if (!children.length) { return } From 22b2edb2bff8f194a6203feebab1c27ea13432c0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 17 Jul 2023 20:55:26 +0100 Subject: [PATCH 043/821] OpenAPI 3.0 docs for offline license and license key endpoints --- packages/worker/openapi-3.0.yaml | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/worker/openapi-3.0.yaml diff --git a/packages/worker/openapi-3.0.yaml b/packages/worker/openapi-3.0.yaml new file mode 100644 index 0000000000..c68654fffb --- /dev/null +++ b/packages/worker/openapi-3.0.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.0 +info: + title: Worker API Specification + version: 1.0.0 +servers: + - url: "http://localhost:10000" + description: localhost + - url: "https://budibaseqa.app" + description: QA + - url: "https://preprod.qa.budibase.net" + description: Preprod + - url: "https://budibase.app" + description: Production + +tags: + - name: license + description: License operations + +paths: + /api/global/license/key: + post: + tags: + - license + summary: Activate license key + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateLicenseKeyRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get license key + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetLicenseKeyResponse' + delete: + tags: + - license + summary: Delete license key + responses: + '204': + description: No content + /api/global/license/offline: + post: + tags: + - license + summary: Activate offline license + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateOfflineLicenseTokenRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get offline license + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineLicenseTokenResponse' + delete: + tags: + - license + summary: Delete offline license + responses: + '204': + description: No content + /api/global/license/offline/identifier: + get: + tags: + - license + summary: Get offline identifier + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineIdentifierResponse' + +components: + schemas: + ActivateOfflineLicenseTokenRequest: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + GetOfflineLicenseTokenResponse: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + ActivateLicenseKeyRequest: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetLicenseKeyResponse: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetOfflineIdentifierResponse: + type: object + properties: + identifierBase64: + type: string + required: + - identifierBase64 \ No newline at end of file From d7bcdfe3afce8e65e03fd792bf51551a95662a29 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 17 Jul 2023 21:48:02 +0100 Subject: [PATCH 044/821] pin non-v versions --- .github/workflows/release-master.yml | 2 +- .github/workflows/release-selfhost.yml | 6 +++--- .github/workflows/release-singleimage.yml | 4 ++-- charts/budibase/Chart.yaml | 2 +- charts/budibase/values.yaml | 20 ++++++++++---------- charts/pvc.yaml | 11 +++++++++++ charts/rollout_test.sh | 5 +++++ 7 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 charts/pvc.yaml create mode 100755 charts/rollout_test.sh diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index 68625ad7af..c8810b7442 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -96,7 +96,7 @@ jobs: git fetch mkdir sync echo "Packaging chart to sync dir" - helm package charts/budibase --version 0.0.0-master --app-version v"$RELEASE_VERSION" --destination sync + helm package charts/budibase --version 0.0.0-master --app-version "$RELEASE_VERSION" --destination sync echo "Packaging successful" git checkout gh-pages echo "Indexing helm repo" diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index f4524e99dc..39ee812726 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -43,7 +43,7 @@ jobs: run: | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD - release_tag=v${{ env.RELEASE_VERSION }} + release_tag=${{ env.RELEASE_VERSION }} # Pull apps and worker images docker pull budibase/apps:$release_tag @@ -108,8 +108,8 @@ jobs: - name: Perform Github Release uses: softprops/action-gh-release@v1 with: - name: v${{ env.RELEASE_VERSION }} - tag_name: v${{ env.RELEASE_VERSION }} + name: ${{ env.RELEASE_VERSION }} + tag_name: ${{ env.RELEASE_VERSION }} generate_release_notes: true files: | packages/cli/build/cli-win.exe diff --git a/.github/workflows/release-singleimage.yml b/.github/workflows/release-singleimage.yml index 5408b48ef8..92f21bd649 100644 --- a/.github/workflows/release-singleimage.yml +++ b/.github/workflows/release-singleimage.yml @@ -71,7 +71,7 @@ jobs: context: . push: true platforms: linux/amd64,linux/arm64 - tags: budibase/budibase,budibase/budibase:v${{ env.RELEASE_VERSION }} + tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile - name: Tag and release Budibase Azure App Service docker image uses: docker/build-push-action@v2 @@ -80,5 +80,5 @@ jobs: push: true platforms: linux/amd64 build-args: TARGETBUILD=aas - tags: budibase/budibase-aas,budibase/budibase-aas:v${{ env.RELEASE_VERSION }} + tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }} file: ./hosting/single/Dockerfile diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 05b3f24dbd..9cefbb13a0 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -14,7 +14,7 @@ type: application # populates on packaging version: 0.0.0 # populates on packaging -appVersion: 0.0.0 +appVersion: v2.8.10 dependencies: - name: couchdb version: 3.3.4 diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 2d89e81b7f..f4f578191b 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -76,8 +76,8 @@ affinity: {} globals: appVersion: "" # Use as an override to .Chart.AppVersion budibaseEnv: PRODUCTION - tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" - enableAnalytics: "1" + tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" + enableAnalytics: "0" sentryDSN: "" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup @@ -95,9 +95,9 @@ globals: createSecrets: true # creates an internal API key, JWT secrets and redis password for you # if createSecrets is set to false, you can hard-code your secrets here - apiEncryptionKey: "" - internalApiKey: "" - jwtSecret: "" + apiEncryptionKey: "test" + internalApiKey: "test" + jwtSecret: "test" cdnUrl: "" # fallback values used during live rotation internalApiKeyFallback: "" @@ -209,14 +209,14 @@ services: # Override values in couchDB subchart couchdb: ## clusterSize is the initial size of the CouchDB cluster. - clusterSize: 3 + clusterSize: 1 allowAdminParty: false # Secret Management createAdminSecret: true - # adminUsername: budibase - # adminPassword: budibase + adminUsername: budibase + adminPassword: budibase # adminHash: -pbkdf2-this_is_not_necessarily_secure_either # cookieAuthSecret: admin @@ -239,11 +239,11 @@ couchdb: ## provisioning of Persistent Volumes; leaving it unset will invoke the default ## provisioner. persistentVolume: - enabled: false + enabled: true accessModes: - ReadWriteOnce size: 10Gi - storageClass: "" + storageClass: "local-path" ## The CouchDB image image: diff --git a/charts/pvc.yaml b/charts/pvc.yaml new file mode 100644 index 0000000000..956032c825 --- /dev/null +++ b/charts/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: local-path-pvc +spec: + accessModes: + - ReadWriteOnce + storageClassName: local-path + resources: + requests: + storage: 2Gi diff --git a/charts/rollout_test.sh b/charts/rollout_test.sh new file mode 100755 index 0000000000..e222fc5ca5 --- /dev/null +++ b/charts/rollout_test.sh @@ -0,0 +1,5 @@ +for i in {1..100000}; do +curl -s -XGET -o /dev/null -w "%{http_code}" http://localhost:10000/builder +sleep 0.1 + +done From 0e92bcece421ec65588e97bcfbc2472983d9d45c Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 18 Jul 2023 07:44:26 +0000 Subject: [PATCH 045/821] Bump version to 2.8.12 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 4fa6ce58d8..23bad4c3bd 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.11", + "version": "2.8.12", "npmClient": "yarn", "packages": [ "packages/*" From 7c7d909d811919c809736447df0f5837195605e3 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 18 Jul 2023 08:56:16 +0100 Subject: [PATCH 046/821] Update validation editor setting to be full width and have descriptive text --- .../ValidationEditor/ValidationEditor.svelte | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte index 6c62c9f5af..6db24e8d69 100644 --- a/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/ValidationEditor/ValidationEditor.svelte @@ -8,16 +8,29 @@ export let componentDefinition export let type + const dispatch = createEventDispatcher() let drawer - const dispatch = createEventDispatcher() + $: text = getText(value) + const save = () => { dispatch("change", value) drawer.hide() } + + const getText = rules => { + if (!rules?.length) { + return "No rules set" + } else { + return `${rules.length} rule${rules.length === 1 ? "" : "s"} set` + } + } -Configure validation +
+ {text} +
+ Configure validation rules for this field. @@ -31,3 +44,9 @@ {componentDefinition} /> + + From 8897705e6565c60748ca914a92177fd2785f305b Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Tue, 18 Jul 2023 08:07:54 +0000 Subject: [PATCH 047/821] Bump version to 2.8.13 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 23bad4c3bd..ebdf4935f0 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.12", + "version": "2.8.13", "npmClient": "yarn", "packages": [ "packages/*" From ae8ed1a1e55bdea60e0d5fbd6ab37527a3e50a49 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 18 Jul 2023 09:36:20 +0100 Subject: [PATCH 048/821] Add filtering to relationship picker --- .../design/settings/componentSettings.js | 2 + .../controls/FilterEditor/FilterEditor.svelte | 5 ++- .../controls/RelationshipFilterEditor.svelte | 35 ++++++++++++++++ packages/client/manifest.json | 15 ++++--- .../app/forms/RelationshipField.svelte | 40 ++++++++----------- 5 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 314391e77c..8b151564a1 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -23,6 +23,7 @@ import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte" import GridColumnEditor from "./controls/ColumnEditor/GridColumnEditor.svelte" import BarButtonList from "./controls/BarButtonList.svelte" import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" +import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte" const componentMap = { text: DrawerBindableInput, @@ -44,6 +45,7 @@ const componentMap = { schema: SchemaSelect, section: SectionSelect, filter: FilterEditor, + "filter/relationship": RelationshipFilterEditor, url: URLSelect, fieldConfiguration: FieldConfiguration, columns: ColumnEditor, diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte index 88c3842f54..828d189850 100644 --- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterEditor.svelte @@ -13,13 +13,14 @@ export let value = [] export let componentInstance export let bindings = [] + export let schema = null let drawer $: tempValue = value $: datasource = getDatasourceForProvider($currentAsset, componentInstance) - $: schema = getSchemaForDatasource($currentAsset, datasource)?.schema - $: schemaFields = Object.values(schema || {}) + $: dsSchema = getSchemaForDatasource($currentAsset, datasource)?.schema + $: schemaFields = Object.values(schema || dsSchema || {}) $: text = getText(value?.filter(filter => filter.field)) async function saveFilter() { diff --git a/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte b/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte new file mode 100644 index 0000000000..0010a22d15 --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte @@ -0,0 +1,35 @@ + + + diff --git a/packages/client/manifest.json b/packages/client/manifest.json index e336ad3817..2c8bab6448 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -3489,6 +3489,16 @@ } ] }, + { + "type": "validation/link", + "label": "Validation", + "key": "validation" + }, + { + "type": "filter/relationship", + "label": "Filtering", + "key": "filter" + }, { "type": "boolean", "label": "Autocomplete", @@ -3500,11 +3510,6 @@ "label": "Disabled", "key": "disabled", "defaultValue": false - }, - { - "type": "validation/link", - "label": "Validation", - "key": "validation" } ] }, diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 735b44b9ae..0c8b076a67 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -1,5 +1,6 @@
{#if options && Array.isArray(options)} {#each options as option} + {@const optionValue = getOptionValue(option)}
value.trim()) } From 39746e0bf0db3633723ee2df60551ec3b25469df Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 18 Jul 2023 16:57:48 +0100 Subject: [PATCH 058/821] Main body of work to handle the new approach of per app builders support. --- packages/backend-core/src/constants/db.ts | 2 - packages/backend-core/src/db/views.ts | 11 ---- .../backend-core/src/events/identification.ts | 5 +- .../src/middleware/builderOnly.ts | 12 ++-- .../src/middleware/builderOrAdmin.ts | 15 +++-- packages/backend-core/src/users.ts | 62 ++++++++++++++++++- .../server/src/api/controllers/application.ts | 4 +- packages/server/src/middleware/authorized.ts | 11 +++- packages/server/src/middleware/currentapp.ts | 14 ++--- .../functions/backfill/global/users.ts | 10 ++- packages/server/src/utilities/global.ts | 6 +- packages/types/src/documents/global/user.ts | 1 - packages/worker/src/sdk/users/events.ts | 20 +++--- packages/worker/src/sdk/users/users.ts | 4 +- 14 files changed, 115 insertions(+), 62 deletions(-) diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index be49b9f261..34dab6c088 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -14,8 +14,6 @@ export enum ViewName { USER_BY_APP = "by_app", USER_BY_EMAIL = "by_email2", BY_API_KEY = "by_api_key", - /** @deprecated - could be deleted */ - USER_BY_BUILDERS = "by_builders", LINK = "by_link", ROUTING = "screen_routes", AUTOMATION_LOGS = "automation_logs", diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts index fddb1ab34b..7f5ef29a0a 100644 --- a/packages/backend-core/src/db/views.ts +++ b/packages/backend-core/src/db/views.ts @@ -105,16 +105,6 @@ export const createApiKeyView = async () => { await createView(db, viewJs, ViewName.BY_API_KEY) } -export const createUserBuildersView = async () => { - const db = getGlobalDB() - const viewJs = `function(doc) { - if (doc.builder && doc.builder.global === true) { - emit(doc._id, doc._id) - } - }` - await createView(db, viewJs, ViewName.USER_BY_BUILDERS) -} - export interface QueryViewOptions { arrayResponse?: boolean } @@ -223,7 +213,6 @@ export const queryPlatformView = async ( const CreateFuncByName: any = { [ViewName.USER_BY_EMAIL]: createNewUserEmailView, [ViewName.BY_API_KEY]: createApiKeyView, - [ViewName.USER_BY_BUILDERS]: createUserBuildersView, [ViewName.USER_BY_APP]: createUserAppView, } diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 5eb11d1354..52fcb2431f 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -21,6 +21,7 @@ import { processors } from "./processors" import { newid } from "../utils" import * as installation from "../installation" import * as configs from "../configs" +import * as users from "../users" import { withCache, TTL, CacheKey } from "../cache/generic" /** @@ -164,8 +165,8 @@ const identifyUser = async ( const id = user._id as string const tenantId = await getEventTenantId(user.tenantId) const type = IdentityType.USER - let builder = user.builder?.global || false - let admin = user.admin?.global || false + let builder = users.hasBuilderPermissions(user) + let admin = users.hasAdminPermissions(user) let providerType if (isSSOUser(user)) { providerType = user.providerType diff --git a/packages/backend-core/src/middleware/builderOnly.ts b/packages/backend-core/src/middleware/builderOnly.ts index a00fd63a22..744321252e 100644 --- a/packages/backend-core/src/middleware/builderOnly.ts +++ b/packages/backend-core/src/middleware/builderOnly.ts @@ -1,10 +1,10 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" +import { isBuilder } from "../users" +import { getAppId } from "../context" -export default async (ctx: BBContext, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) - ) { +export default async (ctx: UserCtx, next: any) => { + const appId = getAppId() + if (!ctx.internal && !isBuilder(ctx.user, appId)) { ctx.throw(403, "Builder user only endpoint.") } return next() diff --git a/packages/backend-core/src/middleware/builderOrAdmin.ts b/packages/backend-core/src/middleware/builderOrAdmin.ts index 26bb3a1bda..2ba5bfe1e2 100644 --- a/packages/backend-core/src/middleware/builderOrAdmin.ts +++ b/packages/backend-core/src/middleware/builderOrAdmin.ts @@ -1,12 +1,11 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" +import { isBuilder, isAdmin } from "../users" +import { getAppId } from "../context" -export default async (ctx: BBContext, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) && - (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) - ) { - ctx.throw(403, "Builder user only endpoint.") +export default async (ctx: UserCtx, next: any) => { + const appId = getAppId() + if (!ctx.internal && !isBuilder(ctx.user, appId) && !isAdmin(ctx.user)) { + ctx.throw(403, "Admin/Builder user only endpoint.") } return next() } diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index b49058f546..2e6ede3cf3 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -12,7 +12,12 @@ import { UNICODE_MAX, ViewName, } from "./db" -import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types" +import { + BulkDocsResponse, + SearchUsersRequest, + User, + ContextUser, +} from "@budibase/types" import { getGlobalDB } from "./context" import * as context from "./context" @@ -178,7 +183,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => { * Performs a starts with search on the global email view. */ export const searchGlobalUsersByEmail = async ( - email: string, + email: string | unknown, opts: any, getOpts?: GetOpts ) => { @@ -248,3 +253,56 @@ export async function getUserCount() { }) return response.total_rows } + +// checks if a user is specifically a builder, given an app ID +export function isBuilder(user: User | ContextUser, appId?: string) { + if (user.builder?.global) { + return true + } else if (appId && user.builder?.apps?.includes(appId)) { + return true + } + return false +} + +// alias for hasAdminPermission, currently do the same thing +// in future whether someone has admin permissions and whether they are +// an admin for a specific resource could be separated +export function isAdmin(user: User | ContextUser) { + return hasAdminPermissions(user) +} + +// checks if a user is capable of building any app +export function hasBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.builder?.global || user.builder?.apps?.length !== 0 +} + +// checks if a user is capable of being an admin +export function hasAdminPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.admin?.global +} + +// used to remove the builder/admin permissions, for processing the +// user as an app user (they may have some specific role/group +export function removePortalUserPermissions(user: User | ContextUser) { + delete user.admin + delete user.builder + return user +} + +export function cleanseUserObject(user: User | ContextUser, base?: User) { + delete user.admin + delete user.builder + delete user.roles + if (base) { + user.admin = base.admin + user.builder = base.builder + user.roles = base.roles + } + return user +} diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 418ccf637e..6a0088d4dc 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -30,6 +30,7 @@ import { objectStore, roles, tenancy, + users, } from "@budibase/backend-core" import { USERS_TABLE_SCHEMA } from "../../constants" import { @@ -222,6 +223,7 @@ export async function fetchAppDefinition(ctx: UserCtx) { export async function fetchAppPackage(ctx: UserCtx) { const db = context.getAppDB() + const appId = context.getAppId() let application = await db.get(DocumentType.APP_METADATA) const layouts = await getLayouts() let screens = await getScreens() @@ -233,7 +235,7 @@ export async function fetchAppPackage(ctx: UserCtx) { ) // Only filter screens if the user is not a builder - if (!(ctx.user.builder && ctx.user.builder.global)) { + if (!users.isBuilder(ctx.user, appId)) { const userRoleId = getUserRoleId(ctx) const accessController = new roles.AccessController() screens = await accessController.checkScreensAccess(screens, userRoleId) diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 6268163fb2..81e42604bc 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -1,4 +1,10 @@ -import { roles, permissions, auth, context } from "@budibase/backend-core" +import { + roles, + permissions, + auth, + context, + users, +} from "@budibase/backend-core" import { Role } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" @@ -21,8 +27,9 @@ const checkAuthorized = async ( permType: any, permLevel: any ) => { + const appId = context.getAppId() // check if this is a builder api and the user is not a builder - const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global + const isBuilder = users.isBuilder(ctx.user, appId) const isBuilderApi = permType === permissions.PermissionType.BUILDER if (isBuilderApi && !isBuilder) { return ctx.throw(403, "Not Authorized") diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 6879a103bc..10ee13a67a 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -4,12 +4,14 @@ import { roles, tenancy, context, + users, } from "@budibase/backend-core" import { generateUserMetadataID, isDevAppID } from "../db/utils" import { getCachedSelf } from "../utilities/global" import env from "../environment" import { isWebhookEndpoint } from "./utils" -import { UserCtx } from "@budibase/types" +import { UserCtx, ContextUser } from "@budibase/types" +import { removePortalUserPermissions } from "@budibase/backend-core/src/users" export default async (ctx: UserCtx, next: any) => { // try to get the appID from the request @@ -42,8 +44,7 @@ export default async (ctx: UserCtx, next: any) => { roleId = globalUser.roleId || roleId // Allow builders to specify their role via a header - const isBuilder = - globalUser && globalUser.builder && globalUser.builder.global + const isBuilder = users.isBuilder(globalUser, appId) const isDevApp = appId && isDevAppID(appId) const roleHeader = ctx.request && @@ -56,8 +57,7 @@ export default async (ctx: UserCtx, next: any) => { roleId = roleHeader // Delete admin and builder flags so that the specified role is honoured - delete ctx.user.builder - delete ctx.user.admin + ctx.user = users.removePortalUserPermissions(ctx.user) as ContextUser } } catch (error) { // Swallow error and do nothing @@ -81,9 +81,7 @@ export default async (ctx: UserCtx, next: any) => { !tenancy.isUserInAppTenant(requestAppId, ctx.user) ) { // don't error, simply remove the users rights (they are a public user) - delete ctx.user.builder - delete ctx.user.admin - delete ctx.user.roles + ctx.user = users.cleanseUserObject(ctx.user) as ContextUser ctx.isAuthenticated = false roleId = roles.BUILTIN_ROLE_IDS.PUBLIC skipCookie = true diff --git a/packages/server/src/migrations/functions/backfill/global/users.ts b/packages/server/src/migrations/functions/backfill/global/users.ts index 05c5f8f56e..9f536a97a5 100644 --- a/packages/server/src/migrations/functions/backfill/global/users.ts +++ b/packages/server/src/migrations/functions/backfill/global/users.ts @@ -1,4 +1,8 @@ -import { events, db as dbUtils } from "@budibase/backend-core" +import { + events, + db as dbUtils, + users as usersCore, +} from "@budibase/backend-core" import { User, CloudAccount } from "@budibase/types" import { DEFAULT_TIMESTAMP } from ".." @@ -30,11 +34,11 @@ export const backfill = async ( await events.identification.identifyUser(user, account, timestamp) await events.user.created(user, timestamp) - if (user.admin?.global) { + if (usersCore.hasAdminPermissions(user)) { await events.user.permissionAdminAssigned(user, timestamp) } - if (user.builder?.global) { + if (usersCore.hasBuilderPermissions(user)) { await events.user.permissionBuilderAssigned(user, timestamp) } diff --git a/packages/server/src/utilities/global.ts b/packages/server/src/utilities/global.ts index c9869b4920..63e33dafee 100644 --- a/packages/server/src/utilities/global.ts +++ b/packages/server/src/utilities/global.ts @@ -5,6 +5,7 @@ import { cache, tenancy, context, + users, } from "@budibase/backend-core" import env from "../environment" import { groups } from "@budibase/pro" @@ -22,8 +23,7 @@ export function updateAppRole( } // if in an multi-tenancy environment make sure roles are never updated if (env.MULTI_TENANCY && appId && !tenancy.isUserInAppTenant(appId, user)) { - delete user.builder - delete user.admin + user = users.removePortalUserPermissions(user) user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC return user } @@ -32,7 +32,7 @@ export function updateAppRole( user.roleId = user.roles[dbCore.getProdAppID(appId)] } // if a role wasn't found then either set as admin (builder) or public (everyone else) - if (!user.roleId && user.builder && user.builder.global) { + if (!user.roleId && users.isBuilder(user, appId)) { user.roleId = roles.BUILTIN_ROLE_IDS.ADMIN } else if (!user.roleId && !user?.userGroups?.length) { user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index cae3c0e6e2..e9e3ccc662 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -47,7 +47,6 @@ export interface User extends Document { } admin?: { global: boolean - apps?: string[] } password?: string status?: UserStatus diff --git a/packages/worker/src/sdk/users/events.ts b/packages/worker/src/sdk/users/events.ts index 7d86182a3c..d8af13a82f 100644 --- a/packages/worker/src/sdk/users/events.ts +++ b/packages/worker/src/sdk/users/events.ts @@ -1,15 +1,18 @@ import env from "../../environment" -import { events, accounts, tenancy } from "@budibase/backend-core" +import { events, accounts, tenancy, users } from "@budibase/backend-core" import { User, UserRoles, CloudAccount } from "@budibase/types" +const hasBuilderPerm = users.hasBuilderPermissions +const hasAdminPerm = users.hasAdminPermissions + export const handleDeleteEvents = async (user: any) => { await events.user.deleted(user) - if (isBuilder(user)) { + if (hasBuilderPerm(user)) { await events.user.permissionBuilderRemoved(user) } - if (isAdmin(user)) { + if (hasAdminPerm(user)) { await events.user.permissionAdminRemoved(user) } } @@ -103,23 +106,20 @@ export const handleSaveEvents = async ( await handleAppRoleEvents(user, existingUser) } -const isBuilder = (user: any) => user.builder && user.builder.global -const isAdmin = (user: any) => user.admin && user.admin.global - export const isAddingBuilder = (user: any, existingUser: any) => { - return isAddingPermission(user, existingUser, isBuilder) + return isAddingPermission(user, existingUser, hasBuilderPerm) } export const isRemovingBuilder = (user: any, existingUser: any) => { - return isRemovingPermission(user, existingUser, isBuilder) + return isRemovingPermission(user, existingUser, hasBuilderPerm) } const isAddingAdmin = (user: any, existingUser: any) => { - return isAddingPermission(user, existingUser, isAdmin) + return isAddingPermission(user, existingUser, hasAdminPerm) } const isRemovingAdmin = (user: any, existingUser: any) => { - return isRemovingPermission(user, existingUser, isAdmin) + return isRemovingPermission(user, existingUser, hasAdminPerm) } const isOnboardingComplete = (user: any, existingUser: any) => { diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index c3cb8500cb..cfa68932d2 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -252,9 +252,7 @@ export const save = async ( let builtUser = await buildUser(user, opts, tenantId, dbUser) // don't allow a user to update its own roles/perms if (opts.currentUserId && opts.currentUserId === dbUser?._id) { - builtUser.builder = dbUser.builder - builtUser.admin = dbUser.admin - builtUser.roles = dbUser.roles + builtUser = usersCore.cleanseUserObject(builtUser, dbUser) as User } if (!dbUser && roles?.length) { From 91847504c8bd4e79f278f5d5287c5c6f5deda088 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 18 Jul 2023 18:10:15 +0100 Subject: [PATCH 059/821] Adding test cases for admin/builder checking middlewares. --- .../backend-core/src/middleware/adminOnly.ts | 4 +- .../src/middleware/tests/builder.spec.ts | 141 ++++++++++++++++++ packages/backend-core/src/users.ts | 2 +- .../tests/core/utilities/structures/users.ts | 19 +++ packages/server/src/middleware/currentapp.ts | 1 - packages/types/src/documents/global/user.ts | 11 +- 6 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 packages/backend-core/src/middleware/tests/builder.spec.ts diff --git a/packages/backend-core/src/middleware/adminOnly.ts b/packages/backend-core/src/middleware/adminOnly.ts index dbe1e3a501..dc2fe9064e 100644 --- a/packages/backend-core/src/middleware/adminOnly.ts +++ b/packages/backend-core/src/middleware/adminOnly.ts @@ -1,6 +1,6 @@ -import { BBContext } from "@budibase/types" +import { UserCtx } from "@budibase/types" -export default async (ctx: BBContext, next: any) => { +export default async (ctx: UserCtx, next: any) => { if ( !ctx.internal && (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) diff --git a/packages/backend-core/src/middleware/tests/builder.spec.ts b/packages/backend-core/src/middleware/tests/builder.spec.ts new file mode 100644 index 0000000000..68c72ffe8a --- /dev/null +++ b/packages/backend-core/src/middleware/tests/builder.spec.ts @@ -0,0 +1,141 @@ +import adminOnly from "../adminOnly" +import builderOnly from "../builderOnly" +import builderOrAdmin from "../builderOrAdmin" +import { structures } from "../../../tests" +import { ContextUser } from "@budibase/types" +import { doInAppContext } from "../../context" + +const appId = "app_aaa" +const basicUser = structures.users.user() +const adminUser = structures.users.adminUser() +const adminOnlyUser = structures.users.adminOnlyUser() +const builderUser = structures.users.builderUser() +const appBuilderUser = structures.users.appBuilderUser(appId) + +function buildUserCtx(user: ContextUser) { + return { + internal: false, + user, + throw: jest.fn(), + } as any +} + +function passed(throwFn: jest.Func, nextFn: jest.Func) { + expect(throwFn).not.toBeCalled() + expect(nextFn).toBeCalled() +} + +function threw(throwFn: jest.Func) { + // cant check next, the throw function doesn't actually throw - so it still continues + expect(throwFn).toBeCalled() +} + +describe("adminOnly middleware", () => { + it("should allow admin user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + adminOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + adminOnly(ctx, next) + threw(ctx.throw) + }) + + it("should not allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + adminOnly(ctx, next) + threw(ctx.throw) + }) +}) + +describe("builderOnly middleware", () => { + it("should allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + builderOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow app builder user", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext(appId, () => { + builderOnly(ctx, next) + }) + passed(ctx.throw, next) + }) + + it("should allow admin and builder user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + builderOnly(ctx, next) + passed(ctx.throw, next) + }) + + it("should not allow admin user", () => { + const ctx = buildUserCtx(adminOnlyUser), + next = jest.fn() + builderOnly(ctx, next) + threw(ctx.throw) + }) + + it("should not allow app builder user to different app", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext("app_bbb", () => { + builderOnly(ctx, next) + }) + threw(ctx.throw) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + builderOnly(ctx, next) + threw(ctx.throw) + }) +}) + +describe("builderOrAdmin middleware", () => { + it("should allow builder user", () => { + const ctx = buildUserCtx(builderUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow builder and admin user", () => { + const ctx = buildUserCtx(adminUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow admin user", () => { + const ctx = buildUserCtx(adminOnlyUser), + next = jest.fn() + builderOrAdmin(ctx, next) + passed(ctx.throw, next) + }) + + it("should allow app builder user", () => { + const ctx = buildUserCtx(appBuilderUser), + next = jest.fn() + doInAppContext(appId, () => { + builderOrAdmin(ctx, next) + }) + passed(ctx.throw, next) + }) + + it("should not allow basic user", () => { + const ctx = buildUserCtx(basicUser), + next = jest.fn() + builderOrAdmin(ctx, next) + threw(ctx.throw) + }) +}) diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 2e6ede3cf3..71cd599f87 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -258,7 +258,7 @@ export async function getUserCount() { export function isBuilder(user: User | ContextUser, appId?: string) { if (user.builder?.global) { return true - } else if (appId && user.builder?.apps?.includes(appId)) { + } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { return true } return false diff --git a/packages/backend-core/tests/core/utilities/structures/users.ts b/packages/backend-core/tests/core/utilities/structures/users.ts index 7a6b4f0d80..0a4f2e8b54 100644 --- a/packages/backend-core/tests/core/utilities/structures/users.ts +++ b/packages/backend-core/tests/core/utilities/structures/users.ts @@ -1,5 +1,6 @@ import { AdminUser, + AdminOnlyUser, BuilderUser, SSOAuthDetails, SSOUser, @@ -21,6 +22,15 @@ export const adminUser = (userProps?: any): AdminUser => { } } +export const adminOnlyUser = (userProps?: any): AdminOnlyUser => { + return { + ...user(userProps), + admin: { + global: true, + }, + } +} + export const builderUser = (userProps?: any): BuilderUser => { return { ...user(userProps), @@ -30,6 +40,15 @@ export const builderUser = (userProps?: any): BuilderUser => { } } +export const appBuilderUser = (appId: string, userProps?: any): BuilderUser => { + return { + ...user(userProps), + builder: { + apps: [appId], + }, + } +} + export function ssoUser( opts: { user?: any; details?: SSOAuthDetails } = {} ): SSOUser { diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 10ee13a67a..1f580d80e4 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -11,7 +11,6 @@ import { getCachedSelf } from "../utilities/global" import env from "../environment" import { isWebhookEndpoint } from "./utils" import { UserCtx, ContextUser } from "@budibase/types" -import { removePortalUserPermissions } from "@budibase/backend-core/src/users" export default async (ctx: UserCtx, next: any) => { // try to get the appID from the request diff --git a/packages/types/src/documents/global/user.ts b/packages/types/src/documents/global/user.ts index e9e3ccc662..2ce714801d 100644 --- a/packages/types/src/documents/global/user.ts +++ b/packages/types/src/documents/global/user.ts @@ -42,7 +42,7 @@ export interface User extends Document { forceResetPassword?: boolean roles: UserRoles builder?: { - global: boolean + global?: boolean apps?: string[] } admin?: { @@ -70,7 +70,8 @@ export interface UserRoles { export interface BuilderUser extends User { builder: { - global: boolean + global?: boolean + apps?: string[] } } @@ -83,6 +84,12 @@ export interface AdminUser extends User { } } +export interface AdminOnlyUser extends User { + admin: { + global: boolean + } +} + export function isUser(user: object): user is User { return !!(user as User).roles } From c5337c652a2ac6b81a07f0a1704b04c2775089fe Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 18 Jul 2023 21:13:17 +0100 Subject: [PATCH 060/821] Remove no longer needed `LOG_CONTEXT` setting on logger module, update qa-core to disable pino logger via env var, add configurable jest timeout via env var --- packages/backend-core/src/logging/index.ts | 3 --- .../backend-core/src/logging/pino/logger.ts | 17 +++++++---------- qa-core/scripts/createEnv.js | 2 ++ qa-core/src/jest/globalSetup.ts | 18 +++++++++--------- qa-core/src/jest/jestSetup.ts | 7 +++---- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/backend-core/src/logging/index.ts b/packages/backend-core/src/logging/index.ts index b87062c478..49d643375d 100644 --- a/packages/backend-core/src/logging/index.ts +++ b/packages/backend-core/src/logging/index.ts @@ -1,6 +1,3 @@ export * as correlation from "./correlation/correlation" export { logger } from "./pino/logger" export * from "./alerts" - -// turn off or on context logging i.e. tenantId, appId etc -export let LOG_CONTEXT = true diff --git a/packages/backend-core/src/logging/pino/logger.ts b/packages/backend-core/src/logging/pino/logger.ts index c96bc83e04..8a5de9372d 100644 --- a/packages/backend-core/src/logging/pino/logger.ts +++ b/packages/backend-core/src/logging/pino/logger.ts @@ -3,7 +3,6 @@ import pino, { LoggerOptions } from "pino" import * as context from "../../context" import * as correlation from "../correlation" import { IdentityType } from "@budibase/types" -import { LOG_CONTEXT } from "../index" // LOGGER @@ -83,15 +82,13 @@ if (!env.DISABLE_PINO_LOGGER) { let contextObject = {} - if (LOG_CONTEXT) { - contextObject = { - tenantId: getTenantId(), - appId: getAppId(), - automationId: getAutomationId(), - identityId: identity?._id, - identityType: identity?.type, - correlationId: correlation.getId(), - } + contextObject = { + tenantId: getTenantId(), + appId: getAppId(), + automationId: getAutomationId(), + identityId: identity?._id, + identityType: identity?.type, + correlationId: correlation.getId(), } const mergingObject: any = { diff --git a/qa-core/scripts/createEnv.js b/qa-core/scripts/createEnv.js index e5ab783735..ebb165f3a9 100644 --- a/qa-core/scripts/createEnv.js +++ b/qa-core/scripts/createEnv.js @@ -12,6 +12,8 @@ function init() { BB_ADMIN_USER_EMAIL: "admin", BB_ADMIN_USER_PASSWORD: "admin", LOG_LEVEL: "info", + JEST_TIMEOUT: "60000", + DISABLE_PINO_LOGGER: "1" } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/qa-core/src/jest/globalSetup.ts b/qa-core/src/jest/globalSetup.ts index e11d0e4423..68300cee26 100644 --- a/qa-core/src/jest/globalSetup.ts +++ b/qa-core/src/jest/globalSetup.ts @@ -1,5 +1,4 @@ -process.env.DISABLE_PINO_LOGGER = "1" -import { DEFAULT_TENANT_ID, logging } from "@budibase/backend-core" +import { DEFAULT_TENANT_ID } from "@budibase/backend-core" import { AccountInternalAPI } from "../account-api" import * as fixtures from "../internal-api/fixtures" import { BudibaseInternalAPI } from "../internal-api" @@ -7,10 +6,6 @@ import { Account, CreateAccountRequest, Feature } from "@budibase/types" import env from "../environment" import { APIRequestOpts } from "../types" -// turn off or on context logging i.e. tenantId, appId etc -// it's not applicable for the qa run -logging.LOG_CONTEXT = false - const accountsApi = new AccountInternalAPI({}) const internalApi = new BudibaseInternalAPI({}) @@ -24,7 +19,7 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> { await accountsApi.accounts.validateEmail(account.email, API_OPTS) await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS) const [res, newAccount] = await accountsApi.accounts.create( - account, {...API_OPTS, autoVerify: true}) + account, {...API_OPTS, autoVerify: true }) await updateLicense(newAccount.accountId) return [account, newAccount] } @@ -32,7 +27,7 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> { const UNLIMITED = { value: -1 } async function updateLicense(accountId: string) { - await accountsApi.licenses.updateLicense(accountId, { + const [response] = await accountsApi.licenses.updateLicense(accountId, { overrides: { // add all features features: Object.values(Feature), @@ -50,7 +45,12 @@ async function updateLicense(accountId: string) { }, }, }, - }) + }, { doExpect: false }) + if (response.status !== 200) { + throw new Error( + `Could not update license for accountId=${accountId}: ${response.status}` + ) + } } async function loginAsAdmin() { diff --git a/qa-core/src/jest/jestSetup.ts b/qa-core/src/jest/jestSetup.ts index 5c86d5fd24..928e547337 100644 --- a/qa-core/src/jest/jestSetup.ts +++ b/qa-core/src/jest/jestSetup.ts @@ -1,4 +1,3 @@ -import { logging } from "@budibase/backend-core" -logging.LOG_CONTEXT = false - -jest.setTimeout(60000) +const envTimeout = process.env.JEST_TIMEOUT +const timeout = envTimeout && parseInt(envTimeout) +jest.setTimeout(timeout || 60000) From 944b6e0baae7f01f5de5e79ff9413edc3dd25ba5 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 18 Jul 2023 21:14:07 +0100 Subject: [PATCH 061/821] Update account api clients to consistently handle status code handling and return types --- .../src/account-api/api/apis/AccountAPI.ts | 166 +++++++++--------- qa-core/src/account-api/api/apis/AuthAPI.ts | 33 ++-- qa-core/src/account-api/api/apis/BaseAPI.ts | 22 +++ .../src/account-api/api/apis/LicenseAPI.ts | 29 ++- 4 files changed, 140 insertions(+), 110 deletions(-) create mode 100644 qa-core/src/account-api/api/apis/BaseAPI.ts diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index 9450f6b2cd..8c6ccec6b8 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -2,127 +2,135 @@ import { Response } from "node-fetch" import { Account, CreateAccountRequest, SearchAccountsRequest, SearchAccountsResponse } from "@budibase/types" import AccountInternalAPIClient from "../AccountInternalAPIClient" import { APIRequestOpts } from "../../../types" +import { Header } from "@budibase/backend-core" +import BaseAPI from "./BaseAPI" -export default class AccountAPI { +export default class AccountAPI extends BaseAPI { client: AccountInternalAPIClient constructor(client: AccountInternalAPIClient) { + super() this.client = client } async validateEmail( email: string, - opts: APIRequestOpts = { doExpect: true } - ): Promise { - const [response, json] = await this.client.post( - `/api/accounts/validate/email`, - { - body: { email }, - } - ) - return response + opts: APIRequestOpts = { status: 200 } + ) { + return this.doRequest(() => { + return this.client.post( + `/api/accounts/validate/email`, + { + body: { email }, + } + ) + }, opts) } async validateTenantId( tenantId: string, - opts: APIRequestOpts = { doExpect: true } - ): Promise { - const [response, json] = await this.client.post( - `/api/accounts/validate/tenantId`, - { - body: { tenantId }, - } - ) - return response + opts: APIRequestOpts = { status: 200 } + ) { + return this.doRequest(() => { + return this.client.post( + `/api/accounts/validate/tenantId`, + { + body: { tenantId }, + } + ) + }, opts) } async create( body: CreateAccountRequest, - opts: APIRequestOpts & { autoVerify: boolean } = { doExpect: true, autoVerify: true } + opts: APIRequestOpts & { autoVerify: boolean } = { status: 201, autoVerify: false } ): Promise<[Response, Account]> { - const headers = { - "no-verify": opts.autoVerify ? "1" : "0" - } - const [response, json] = await this.client.post(`/api/accounts`, { - body, - headers, - }) - if (opts.doExpect) { - expect(response).toHaveStatusCode(201) - } - return [response, json] - } - - async delete(accountID: string, opts: APIRequestOpts = {status:204}) { - const [response, json] = await this.client.del( - `/api/accounts/${accountID}`, - { - internal: true, + return this.doRequest(() => { + const headers = { + "no-verify": opts.autoVerify ? "1" : "0" } - ) - // can't use expect here due to use in global teardown - if (response.status !== opts.status) { - throw new Error(`status: ${response.status} not equal to expected: ${opts.status}`) - } - return response + return this.client.post(`/api/accounts`, { + body, + headers, + }) + }, opts) } - async deleteCurrentAccount() { - const [response, json] = await this.client.del( + async delete( + accountID: string, + opts: APIRequestOpts = { status: 204 }) { + return this.doRequest(() => { + return this.client.del( + `/api/accounts/${accountID}`, + { + internal: true, + } + ) + }, opts) + } + + async deleteCurrentAccount( + opts: APIRequestOpts = { status: 204 } + ) { + return this.doRequest(() => { + return this.client.del( `/api/accounts` - ) - return response + ) + }, opts) } async verifyAccount( - verificationCode: string, - opts: APIRequestOpts = { doExpect: true } - ): Promise { - const [response, json] = await this.client.post( + verificationCode: string, + opts: APIRequestOpts = { status: 200 } + ){ + return this.doRequest(() => { + return this.client.post( `/api/accounts/verify`, { body: { verificationCode }, } - ) - if (opts.doExpect) { - expect(response).toHaveStatusCode(200) - } - return response + ) + }, opts) } - async verifyAccountSendEmail( + async sendVerificationEmail( email: string, - opts: APIRequestOpts = { doExpect: true } - ): Promise { - const [response, json] = await this.client.post( + opts: APIRequestOpts = { status: 200 } + ): Promise<[Response, string]> { + return this.doRequest(async () => { + const [response] = await this.client.post( `/api/accounts/verify/send`, { body: { email }, + headers: { + [Header.RETURN_VERIFICATION_CODE]: "1" + } } - ) - if (opts.doExpect) { - expect(response).toHaveStatusCode(200) - } - return response + ) + const code = response.headers.get(Header.VERIFICATION_CODE) + return [response, code] + }, opts) } async search( searchType: string, search: 'email' | 'tenantId', - opts: APIRequestOpts = { doExpect: true } + opts: APIRequestOpts = { status: 200 } ): Promise<[Response, SearchAccountsResponse]> { - let body: SearchAccountsRequest = {} - - if (search === 'email') { - body.email = searchType - } else if (search === 'tenantId') { - body.tenantId = searchType - } - - const [response, json] = await this.client.post( + return this.doRequest(() => { + let body: SearchAccountsRequest = {} + if (search === 'email') { + body.email = searchType + } else if (search === 'tenantId') { + body.tenantId = searchType + } + return this.client.post( `/api/accounts/search`, - {body: body} - ) - return [response, json] + { + body, + internal: true + } + ) + }, opts) } } diff --git a/qa-core/src/account-api/api/apis/AuthAPI.ts b/qa-core/src/account-api/api/apis/AuthAPI.ts index 5f2df6d250..c5d654ade0 100644 --- a/qa-core/src/account-api/api/apis/AuthAPI.ts +++ b/qa-core/src/account-api/api/apis/AuthAPI.ts @@ -1,32 +1,33 @@ import { Response } from "node-fetch" import AccountInternalAPIClient from "../AccountInternalAPIClient" import { APIRequestOpts } from "../../../types" +import BaseAPI from "./BaseAPI" -export default class AuthAPI { +export default class AuthAPI extends BaseAPI { client: AccountInternalAPIClient constructor(client: AccountInternalAPIClient) { + super() this.client = client } async login( email: string, password: string, - opts: APIRequestOpts = { doExpect: true } + opts: APIRequestOpts = { doExpect: true, status: 200 } ): Promise<[Response, string]> { - const [response, json] = await this.client.post( - `/api/auth/login`, - { - body: { - email: email, - password: password, - }, - } - ) - // if (opts.doExpect) { - // expect(response).toHaveStatusCode(200) - // } - const cookie = response.headers.get("set-cookie") - return [response, cookie!] + return this.doRequest(async () => { + const [res] = await this.client.post( + `/api/auth/login`, + { + body: { + email: email, + password: password, + }, + } + ) + const cookie = res.headers.get("set-cookie") + return [res, cookie] + }, opts) } } diff --git a/qa-core/src/account-api/api/apis/BaseAPI.ts b/qa-core/src/account-api/api/apis/BaseAPI.ts new file mode 100644 index 0000000000..db0d921dd6 --- /dev/null +++ b/qa-core/src/account-api/api/apis/BaseAPI.ts @@ -0,0 +1,22 @@ +import { Response } from "node-fetch" +import { APIRequestOpts } from "../../../types" + +// TODO: Add to LicenseAPI + +export default class BaseAPI { + + async doRequest( + request: () => Promise<[Response, any]>, + opts: APIRequestOpts): Promise<[Response, any]> { + const [response, body] = await request() + + // do expect on by default + if (opts.doExpect === undefined) { + opts.doExpect = true + } + if (opts.doExpect && opts.status) { + expect(response).toHaveStatusCode(opts.status) + } + return [response, body] + } +} \ No newline at end of file diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index e0601fe127..67e67edcb1 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -1,31 +1,30 @@ import AccountInternalAPIClient from "../AccountInternalAPIClient" import { Account, UpdateLicenseRequest } from "@budibase/types" import { Response } from "node-fetch" +import BaseAPI from "./BaseAPI" +import { APIRequestOpts } from "../../../types" -export default class LicenseAPI { +export default class LicenseAPI extends BaseAPI { client: AccountInternalAPIClient constructor(client: AccountInternalAPIClient) { + super() this.client = client } async updateLicense( accountId: string, - body: UpdateLicenseRequest + body: UpdateLicenseRequest, + opts: APIRequestOpts = { status: 200 } ): Promise<[Response, Account]> { - const [response, json] = await this.client.put( - `/api/accounts/${accountId}/license`, - { - body, - internal: true, - } - ) - - if (response.status !== 200) { - throw new Error( - `Could not update license for accountId=${accountId}: ${response.status}` + return this.doRequest(() => { + return this.client.put( + `/api/accounts/${accountId}/license`, + { + body, + internal: true, + } ) - } - return [response, json] + }, opts) } } From 4dc558d3e9935c83ab00e702d421235ede8d88a5 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 18 Jul 2023 21:15:13 +0100 Subject: [PATCH 062/821] Restructure account tests to test signup and deletion as a single flow --- .../api/AccountInternalAPIClient.ts | 6 +- qa-core/src/account-api/fixtures/accounts.ts | 1 + .../tests/accounts/accounts.internal.spec.ts | 29 +++++++ .../tests/accounts/accounts.spec.ts | 80 +++++++++++++++++++ .../account-api/tests/accounts/create.spec.ts | 23 ------ .../account-api/tests/accounts/delete.spec.ts | 52 ------------ .../account-api/tests/accounts/search.spec.ts | 70 ---------------- .../tests/accounts/validate.spec.ts | 46 ----------- .../account-api/tests/accounts/verify.spec.ts | 56 ------------- qa-core/src/jest/globalTeardown.ts | 7 +- .../src/shared/BudibaseTestConfiguration.ts | 18 ++++- 11 files changed, 133 insertions(+), 255 deletions(-) create mode 100644 qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts create mode 100644 qa-core/src/account-api/tests/accounts/accounts.spec.ts delete mode 100644 qa-core/src/account-api/tests/accounts/create.spec.ts delete mode 100644 qa-core/src/account-api/tests/accounts/delete.spec.ts delete mode 100644 qa-core/src/account-api/tests/accounts/search.spec.ts delete mode 100644 qa-core/src/account-api/tests/accounts/validate.spec.ts delete mode 100644 qa-core/src/account-api/tests/accounts/verify.spec.ts diff --git a/qa-core/src/account-api/api/AccountInternalAPIClient.ts b/qa-core/src/account-api/api/AccountInternalAPIClient.ts index 85807fd87a..b1e32cac1f 100644 --- a/qa-core/src/account-api/api/AccountInternalAPIClient.ts +++ b/qa-core/src/account-api/api/AccountInternalAPIClient.ts @@ -1,6 +1,8 @@ +import { Response } from "node-fetch" import env from "../../environment" import fetch, { HeadersInit } from "node-fetch" import { State } from "../../types" +import { Header } from "@budibase/backend-core" type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" @@ -28,7 +30,7 @@ export default class AccountInternalAPIClient { apiCall = (method: APIMethod) => - async (url = "", options: ApiOptions = {}) => { + async (url = "", options: ApiOptions = {}): Promise<[Response, any]> => { const requestOptions = { method, body: JSON.stringify(options.body), @@ -46,7 +48,7 @@ export default class AccountInternalAPIClient { if (options.internal) { requestOptions.headers = { ...requestOptions.headers, - ...{ "x-budibase-api-key": env.ACCOUNT_PORTAL_API_KEY }, + ...{ [Header.API_KEY] : env.ACCOUNT_PORTAL_API_KEY }, } } diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts index c6c6fa163f..6c52cb52b0 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -1,6 +1,7 @@ import { generator } from "../../shared" import { Hosting, CreateAccountRequest } from "@budibase/types" +// TODO: Refactor me to central location export const generateAccount = (): CreateAccountRequest => { const uuid = generator.guid() diff --git a/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts new file mode 100644 index 0000000000..47b0ffb12a --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts @@ -0,0 +1,29 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" +import { generator } from "../../../shared" + +describe("Account Internal Operations", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("performs account deletion", async () => { + // Deleting by unknown id doesn't work + const accountId = generator.string() + await config.api.accounts.delete(accountId, { status: 404 }) + + // Create new account + const [_, account] = await config.api.accounts.create({ + ...fixtures.accounts.generateAccount(), + }) + + // New account can be deleted + await config.api.accounts.delete(account.accountId) + }) +}) diff --git a/qa-core/src/account-api/tests/accounts/accounts.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.spec.ts new file mode 100644 index 0000000000..e61e2089c2 --- /dev/null +++ b/qa-core/src/account-api/tests/accounts/accounts.spec.ts @@ -0,0 +1,80 @@ +import TestConfiguration from "../../config/TestConfiguration" +import * as fixtures from "../../fixtures" +import { generator } from "../../../shared" + +describe("accounts", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.beforeAll() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("performs signup and deletion flow", async () => { + await config.doInNewState(async () => { + const createAccountRequest = fixtures.accounts.generateAccount() + const email = createAccountRequest.email + const tenantId = createAccountRequest.tenantId + + // Validation - email and tenant id allowed + await config.api.accounts.validateEmail(email) + await config.api.accounts.validateTenantId(tenantId) + + // Create unverified account + await config.api.accounts.create(createAccountRequest) + + // Validation - email and tenant id no longer valid + await config.api.accounts.validateEmail(email, { status: 400 }) + await config.api.accounts.validateTenantId(tenantId, { status: 400 }) + + // Attempt to log in using unverified account + await config.loginAsAccount(createAccountRequest, { status: 400 }) + + // Re-send verification email to get access to code + const [_, code] = await config.accountsApi.accounts.sendVerificationEmail(email) + + // Send the verification request + await config.accountsApi.accounts.verifyAccount(code!) + + // Can now login to the account + await config.loginAsAccount(createAccountRequest) + + // Delete account + await config.api.accounts.deleteCurrentAccount() + + // Can't login + await config.loginAsAccount(createAccountRequest, { status: 403 }) + }) + }) + + describe("searching accounts", () => { + it("searches by tenant id", async () => { + const tenantId = generator.string() + + // empty result + const [emptyRes, emptyBody] = await config.api.accounts.search(tenantId, "tenantId") + expect(emptyBody.length).toBe(0) + + // hit result + const [hitRes, hitBody] = await config.api.accounts.search(config.state.tenantId!, "tenantId") + expect(hitBody.length).toBe(1) + expect(hitBody[0].tenantId).toBe(config.state.tenantId) + }) + + it("searches by email", async () => { + const email = generator.email() + + // empty result + const [emptyRes, emptyBody] = await config.api.accounts.search(email, "email") + expect(emptyBody.length).toBe(0) + + // hit result + const [hitRes, hitBody] = await config.api.accounts.search(config.state.email!, "email") + expect(hitBody.length).toBe(1) + expect(hitBody[0].email).toBe(config.state.email) + }) + }) +}) diff --git a/qa-core/src/account-api/tests/accounts/create.spec.ts b/qa-core/src/account-api/tests/accounts/create.spec.ts deleted file mode 100644 index 11a243a8ba..0000000000 --- a/qa-core/src/account-api/tests/accounts/create.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import TestConfiguration from "../../config/TestConfiguration" -import * as fixtures from "../../fixtures" - -describe("Account API - Create Account", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.beforeAll() - }) - - afterAll(async () => { - await config.afterAll() - }) - - describe("POST /api/accounts/", () => { - it("Returns 201", async () => { - const [res, account] = await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() - }) - expect(res.status).toBe(201) - }) - }) -}) diff --git a/qa-core/src/account-api/tests/accounts/delete.spec.ts b/qa-core/src/account-api/tests/accounts/delete.spec.ts deleted file mode 100644 index c3abeed12c..0000000000 --- a/qa-core/src/account-api/tests/accounts/delete.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import TestConfiguration from "../../config/TestConfiguration" -import * as fixtures from "../../fixtures" -import { generator } from "../../../shared" - -describe("Account API - Delete Account", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.beforeAll() - }) - - afterAll(async () => { - await config.afterAll() - }) - - describe("DEL /api/accounts", () => { - it("Returns 204", async () => { - await config.doInNewState(async () => { - // Create account - const createAccountRequest = fixtures.accounts.generateAccount() - await config.api.accounts.create(createAccountRequest) - - // Login - Get cookie - await config.login( - createAccountRequest.email, - createAccountRequest.password, - createAccountRequest.tenantId - ) - - // Delete account - const res = await config.api.accounts.deleteCurrentAccount() - expect(res.status).toBe(204) - }) - }) - }) - - describe("DEL /api/accounts/{accountId}", () => { - it("Returns 204", async () => { - const [response, account] = await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() - }) - // Delete account by ID - const res = await config.api.accounts.delete(account.accountId) - expect(res.status).toBe(204) - }) - - it("returns 404 - Account not found", async () => { - const accountId = generator.string() - await config.api.accounts.delete(accountId, {status:404}) - }) - }) -}) diff --git a/qa-core/src/account-api/tests/accounts/search.spec.ts b/qa-core/src/account-api/tests/accounts/search.spec.ts deleted file mode 100644 index d928c23996..0000000000 --- a/qa-core/src/account-api/tests/accounts/search.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import TestConfiguration from "../../config/TestConfiguration" -import { generator } from "../../../shared" - -describe("Account API - Search for Account", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.beforeAll() - }) - - afterAll(async () => { - await config.afterAll() - }) - - describe("POST /api/accounts/search", () => { - describe("by tenant", () => { - it("returns 200 + empty", async () => { - const tenantId = generator.string() - const [res, body] = - await config.api.accounts.search(tenantId, "tenantId") - expect(res.status).toBe(200) - expect(body.length).toBe(0) - }) - - it("returns 200 + found", async () => { - const [res, body] = - await config.api.accounts.search(config.state.tenantId!, "tenantId") - expect(res.status).toBe(200) - expect(body.length).toBe(1) - expect(body[0].tenantId).toBe(config.state.tenantId) - }) - - it("returns 400 + error: Invalid body - tenantId is not allowed to be empty", async () => { - const [res, body] = - await config.api.accounts.search("", "tenantId") - expect(body).toEqual({ - message: "Invalid body - \"tenantId\" is not allowed to be empty", - status: 400 - }) - }) - }) - - describe("by email", () => { - it("returns 200 + empty", async () => { - const email = generator.email() - const [res, body] = - await config.api.accounts.search(email, "email") - expect(res.status).toBe(200) - expect(body.length).toBe(0) - }) - - it("returns 200 + found", async () => { - const [res, body] = - await config.api.accounts.search(config.state.email!, "email") - expect(res.status).toBe(200) - expect(body.length).toBe(1) - expect(body[0].email).toBe(config.state.email) - }) - - it("returns 400 + error: Invalid body - email is not allowed to be empty", async () => { - const [res, body] = - await config.api.accounts.search("", "email") - expect(body).toEqual({ - message: "Invalid body - \"email\" is not allowed to be empty", - status: 400 - }) - }) - }) - }) -}) diff --git a/qa-core/src/account-api/tests/accounts/validate.spec.ts b/qa-core/src/account-api/tests/accounts/validate.spec.ts deleted file mode 100644 index 16a32ecbe0..0000000000 --- a/qa-core/src/account-api/tests/accounts/validate.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import TestConfiguration from "../../config/TestConfiguration" -import * as fixtures from "../../fixtures" -import { generator } from "../../../shared" - -describe("Account API - Validate Account", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.beforeAll() - }) - - afterAll(async () => { - await config.afterAll() - }) - - describe("POST /api/accounts/validate/email", () => { - it("Returns 200", async () => { - const email = generator.email() - const res = await config.api.accounts.validateEmail(email) - expect(res.status).toBe(200) - }) - - it("returns 400", async () => { - const [response, account] = await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() - }) - const res = await config.api.accounts.validateEmail(account.email) - expect(res.status).toBe(400) - }) - }) - - describe("POST /api/accounts/validate/tenantId", () => { - it("Returns 200", async () => { - const res = await config.api.accounts.validateTenantId("randomtenant") - expect(res.status).toBe(200) - }) - - it("Returns 400", async () => { - const [response, account] = await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() - }) - const res = await config.api.accounts.validateTenantId(account.tenantId) - expect(res.status).toBe(400) - }) - }) -}) diff --git a/qa-core/src/account-api/tests/accounts/verify.spec.ts b/qa-core/src/account-api/tests/accounts/verify.spec.ts deleted file mode 100644 index 6c14b4a4d9..0000000000 --- a/qa-core/src/account-api/tests/accounts/verify.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import TestConfiguration from "../../config/TestConfiguration" -import * as fixtures from "../../fixtures" - -describe("Account API - Verify Account", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.beforeAll() - }) - - afterAll(async () => { - await config.afterAll() - }) - - describe("POST /api/accounts/verify", () => { - it("returns 200", async () => { - // Create unverified account - const createAccountRequest = fixtures.accounts.generateAccount() - const [res, acc] = await config.api.accounts.create( - createAccountRequest, - { doExpect: true, autoVerify: false }) - - // Attempt to log in using unverified account - const [loginResponse, cookie] = await config.accountsApi.auth.login( - createAccountRequest.email, - createAccountRequest.password, - ) - - // await config.login( - // createAccountRequest.email, - // createAccountRequest.password, - // createAccountRequest.tenantId, - // ) - - // Expect response - cannot login via unverified account - - - // Verify account via code - // await config.api.accounts.verifyAccount() - - // Expect response - login successful - }) - }) - - describe("POST /api/accounts/verify/send", () => { - it("Send account verification email ", async () => { - // Create account - await config.api.accounts.create({ - ...fixtures.accounts.generateAccount() - }) - - // Verify account via email - //await config.api.accounts.verifyAccountSendEmail() - }) - }) -}) diff --git a/qa-core/src/jest/globalTeardown.ts b/qa-core/src/jest/globalTeardown.ts index 52696a72f8..f1b8c1d541 100644 --- a/qa-core/src/jest/globalTeardown.ts +++ b/qa-core/src/jest/globalTeardown.ts @@ -10,8 +10,11 @@ const API_OPTS: APIRequestOpts = { doExpect: false } async function deleteAccount() { // @ts-ignore const accountID = global.qa.accountId - // can't run 'expect' blocks in teardown - await accountsApi.accounts.delete(accountID) + + const [response] = await accountsApi.accounts.delete(accountID, { doExpect: false }) + if (response.status !== 204) { + throw new Error(`status: ${response.status} not equal to expected: 201`) + } } async function teardown() { diff --git a/qa-core/src/shared/BudibaseTestConfiguration.ts b/qa-core/src/shared/BudibaseTestConfiguration.ts index c6955fd87f..9841b8d163 100644 --- a/qa-core/src/shared/BudibaseTestConfiguration.ts +++ b/qa-core/src/shared/BudibaseTestConfiguration.ts @@ -1,7 +1,8 @@ import { BudibaseInternalAPI } from "../internal-api" import { AccountInternalAPI } from "../account-api" -import { CreateAppRequest, State } from "../types" +import { APIRequestOpts, CreateAppRequest, State } from "../types" import * as fixtures from "../internal-api/fixtures" +import { CreateAccountRequest } from "@budibase/types" export default class BudibaseTestConfiguration { // apis @@ -42,12 +43,12 @@ export default class BudibaseTestConfiguration { // AUTH - async doInNewState(task: any) { + async doInNewState(task: () => Promise) { return this.doWithState(task, {}) } - async doWithState(task: any, state: State) { - const original = this.state + async doWithState(task: () => Promise, state: State) { + const original = { ...this.state } // override the state this.state.apiKey = state.apiKey @@ -68,6 +69,15 @@ export default class BudibaseTestConfiguration { this.state.email = original.email } + async loginAsAccount(account: CreateAccountRequest, opts: APIRequestOpts = {}) { + const [_, cookie] = await this.accountsApi.auth.login( + account.email, + account.password, + opts + ) + this.state.cookie = cookie + } + async login(email: string, password: string, tenantId?: string) { if (!tenantId && this.state.tenantId) { tenantId = this.state.tenantId From e7d304d4cfa2169b398d3652372aaecbc12c1eef Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 18 Jul 2023 21:15:44 +0100 Subject: [PATCH 063/821] Add swc transformer to make tests start faster --- qa-core/jest.config.ts | 3 ++ qa-core/package.json | 2 + qa-core/yarn.lock | 104 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/qa-core/jest.config.ts b/qa-core/jest.config.ts index de63f9074b..eb942eca97 100644 --- a/qa-core/jest.config.ts +++ b/qa-core/jest.config.ts @@ -5,6 +5,9 @@ const config: Config.InitialOptions = { setupFiles: ["./src/jest/jestSetup.ts"], setupFilesAfterEnv: ["./src/jest/jest.extends.ts"], testEnvironment: "node", + transform: { + "^.+\\.ts?$": "@swc/jest", + }, globalSetup: "./src/jest/globalSetup.ts", globalTeardown: "./src/jest/globalTeardown.ts", moduleNameMapper: { diff --git a/qa-core/package.json b/qa-core/package.json index 13f2e80332..555b277742 100644 --- a/qa-core/package.json +++ b/qa-core/package.json @@ -30,6 +30,8 @@ "jest": "29.0.0", "prettier": "2.7.1", "start-server-and-test": "1.14.0", + "@swc/core": "^1.3.25", + "@swc/jest": "^0.2.24", "timekeeper": "2.2.0", "ts-jest": "29.0.0", "ts-node": "10.8.1", diff --git a/qa-core/yarn.lock b/qa-core/yarn.lock index a2cfae27a9..a423e45b21 100644 --- a/qa-core/yarn.lock +++ b/qa-core/yarn.lock @@ -455,6 +455,13 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/create-cache-key-function@^27.4.2": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31" + integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== + dependencies: + "@jest/types" "^27.5.1" + "@jest/environment@^29.5.0": version "29.5.0" resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz" @@ -589,6 +596,17 @@ slash "^3.0.0" write-file-atomic "^4.0.2" +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@jest/types@^29.0.0", "@jest/types@^29.5.0": version "29.5.0" resolved "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz" @@ -738,6 +756,80 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@swc/core-darwin-arm64@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.70.tgz#056ac6899e22cb7f7be21388d4d938ca5123a72b" + integrity sha512-31+mcl0dgdRHvZRjhLOK9V6B+qJ7nxDZYINr9pBlqGWxknz37Vld5KK19Kpr79r0dXUZvaaelLjCnJk9dA2PcQ== + +"@swc/core-darwin-x64@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.70.tgz#3945814de6fadbee5b46cb2a3422353acb420c5c" + integrity sha512-GMFJ65E18zQC80t0os+TZvI+8lbRuitncWVge/RXmXbVLPRcdykP4EJ87cqzcG5Ah0z18/E0T+ixD6jHRisrYQ== + +"@swc/core-linux-arm-gnueabihf@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.70.tgz#7960e54ede1af75a7ef99ee53febf37fea6269a8" + integrity sha512-wjhCwS8LCiAq2VedF1b4Bryyw68xZnfMED4pLRazAl8BaUlDFANfRBORNunxlfHQj4V3x39IaiLgCZRHMdzXBg== + +"@swc/core-linux-arm64-gnu@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.70.tgz#df9654e5040bbeb1619739756a7f50100e38ace8" + integrity sha512-9D/Rx67cAOnMiexvCqARxvhj7coRajTp5HlJHuf+rfwMqI2hLhpO9/pBMQxBUAWxODO/ksQ/OF+GJRjmtWw/2A== + +"@swc/core-linux-arm64-musl@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.70.tgz#2c2aab5a136c7eb409ddc9cdc4f947a68fd74493" + integrity sha512-gkjxBio7XD+1GlQVVyPP/qeFkLu83VhRHXaUrkNYpr5UZG9zZurBERT9nkS6Y+ouYh+Q9xmw57aIyd2KvD2zqQ== + +"@swc/core-linux-x64-gnu@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.70.tgz#774351532b154ed36a5c6d14b647e7a8ab510028" + integrity sha512-/nCly+V4xfMVwfEUoLLAukxUSot/RcSzsf6GdsGTjFcrp5sZIntAjokYRytm3VT1c2TK321AfBorsi9R5w8Y7Q== + +"@swc/core-linux-x64-musl@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.70.tgz#c0b1b4ad5f4ef187eaa093589a4933ecb6836546" + integrity sha512-HoOsPJbt361KGKaivAK0qIiYARkhzlxeAfvF5NlnKxkIMOZpQ46Lwj3tR0VWohKbrhS+cYKFlVuDi5XnDkx0XA== + +"@swc/core-win32-arm64-msvc@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.70.tgz#8640267ce3959db0e7e682103677a5e0500b5ea7" + integrity sha512-hm4IBK/IaRil+aj1cWU6f0GyAdHpw/Jr5nyFYLM2c/tt7w2t5hgb8NjzM2iM84lOClrig1fG6edj2vCF1dFzNQ== + +"@swc/core-win32-ia32-msvc@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.70.tgz#f95d5656622f5a963bc0125da9fda84cf40faa8d" + integrity sha512-5cgKUKIT/9Fp5fCA+zIjYCQ4dSvjFYOeWGZR3QiTXGkC4bGa1Ji9SEPyeIAX0iruUnKjYaZB9RvHK2tNn7RLrQ== + +"@swc/core-win32-x64-msvc@1.3.70": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.70.tgz#5b3acddb96fdf60df089b837061915cb4be94eaa" + integrity sha512-LE8lW46+TQBzVkn2mHBlk8DIElPIZ2dO5P8AbJiARNBAnlqQWu67l9gWM89UiZ2l33J2cI37pHzON3tKnT8f9g== + +"@swc/core@^1.3.25": + version "1.3.70" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.70.tgz#f5ddc6fe6add7a99f5b94d2214ad0d8527d11479" + integrity sha512-LWVWlEDLlOD25PvA2NEz41UzdwXnlDyBiZbe69s3zM0DfCPwZXLUm79uSqH9ItsOjTrXSL5/1+XUL6C/BZwChA== + optionalDependencies: + "@swc/core-darwin-arm64" "1.3.70" + "@swc/core-darwin-x64" "1.3.70" + "@swc/core-linux-arm-gnueabihf" "1.3.70" + "@swc/core-linux-arm64-gnu" "1.3.70" + "@swc/core-linux-arm64-musl" "1.3.70" + "@swc/core-linux-x64-gnu" "1.3.70" + "@swc/core-linux-x64-musl" "1.3.70" + "@swc/core-win32-arm64-msvc" "1.3.70" + "@swc/core-win32-ia32-msvc" "1.3.70" + "@swc/core-win32-x64-msvc" "1.3.70" + +"@swc/jest@^0.2.24": + version "0.2.26" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.26.tgz#6ef2d6d31869e3aaddc132603bc21f2e4c57cc5d" + integrity sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A== + dependencies: + "@jest/create-cache-key-function" "^27.4.2" + jsonc-parser "^3.2.0" + "@techpass/passport-openidconnect@0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@techpass/passport-openidconnect/-/passport-openidconnect-0.3.2.tgz" @@ -885,6 +977,13 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== +"@types/yargs@^16.0.0": + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== + dependencies: + "@types/yargs-parser" "*" + "@types/yargs@^17.0.8": version "17.0.22" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz" @@ -2866,6 +2965,11 @@ json5@^2.2.1, json5@^2.2.2: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + jsonwebtoken@9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz" From d521bffc61d71f51fab8d68398f2902722ae2837 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 19 Jul 2023 12:22:29 +0100 Subject: [PATCH 064/821] tidy up --- charts/pvc.yaml | 11 ----------- charts/rollout_test.sh | 5 ----- 2 files changed, 16 deletions(-) delete mode 100644 charts/pvc.yaml delete mode 100755 charts/rollout_test.sh diff --git a/charts/pvc.yaml b/charts/pvc.yaml deleted file mode 100644 index 956032c825..0000000000 --- a/charts/pvc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: local-path-pvc -spec: - accessModes: - - ReadWriteOnce - storageClassName: local-path - resources: - requests: - storage: 2Gi diff --git a/charts/rollout_test.sh b/charts/rollout_test.sh deleted file mode 100755 index e222fc5ca5..0000000000 --- a/charts/rollout_test.sh +++ /dev/null @@ -1,5 +0,0 @@ -for i in {1..100000}; do -curl -s -XGET -o /dev/null -w "%{http_code}" http://localhost:10000/builder -sleep 0.1 - -done From 9076f79ad401c0d7415b2bd720fb25d61d79d981 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 19 Jul 2023 12:33:23 +0100 Subject: [PATCH 065/821] reducing couchDB k8s to one node, reverting values to default --- charts/budibase/Chart.yaml | 2 +- charts/budibase/values.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 9cefbb13a0..05b3f24dbd 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -14,7 +14,7 @@ type: application # populates on packaging version: 0.0.0 # populates on packaging -appVersion: v2.8.10 +appVersion: 0.0.0 dependencies: - name: couchdb version: 3.3.4 diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index f4f578191b..74e4c52654 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -76,8 +76,8 @@ affinity: {} globals: appVersion: "" # Use as an override to .Chart.AppVersion budibaseEnv: PRODUCTION - tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" - enableAnalytics: "0" + tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" + enableAnalytics: "1" sentryDSN: "" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup @@ -95,9 +95,9 @@ globals: createSecrets: true # creates an internal API key, JWT secrets and redis password for you # if createSecrets is set to false, you can hard-code your secrets here - apiEncryptionKey: "test" - internalApiKey: "test" - jwtSecret: "test" + apiEncryptionKey: "" + internalApiKey: "" + jwtSecret: "" cdnUrl: "" # fallback values used during live rotation internalApiKeyFallback: "" @@ -215,8 +215,8 @@ couchdb: # Secret Management createAdminSecret: true - adminUsername: budibase - adminPassword: budibase + # adminUsername: budibase + # adminPassword: budibase # adminHash: -pbkdf2-this_is_not_necessarily_secure_either # cookieAuthSecret: admin @@ -239,11 +239,11 @@ couchdb: ## provisioning of Persistent Volumes; leaving it unset will invoke the default ## provisioner. persistentVolume: - enabled: true + enabled: false accessModes: - ReadWriteOnce size: 10Gi - storageClass: "local-path" + storageClass: "" ## The CouchDB image image: From 7afd77065f19fde802ad83fb1b543d57891d138d Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 19 Jul 2023 14:57:48 +0100 Subject: [PATCH 066/821] updating versionCommit tag --- scripts/versionCommit.sh | 2 +- yarn.lock | 93 +++------------------------------------- 2 files changed, 7 insertions(+), 88 deletions(-) diff --git a/scripts/versionCommit.sh b/scripts/versionCommit.sh index 71c931d736..86db0136e7 100755 --- a/scripts/versionCommit.sh +++ b/scripts/versionCommit.sh @@ -13,6 +13,6 @@ node ./bumpVersion.js $1 NEW_VERSION=$(node -p "require('../lerna.json').version") git add ../lerna.json git commit -m "Bump version to $NEW_VERSION" -git tag v$NEW_VERSION +git tag $NEW_VERSION git push git push --tags \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bb016d1936..3acf765870 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1866,10 +1866,10 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/handlebars-helpers@^0.11.8": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.8.tgz#6953d29673a8c5c407e096c0a84890465c7ce841" - integrity sha512-ggWJUt0GqsHFAEup5tlWlcrmYML57nKhpNGGLzVsqXVYN8eVmf3xluYmmMe7fDYhQH0leSprrdEXmsdFQF3HAQ== +"@budibase/handlebars-helpers@^0.11.9": + version "0.11.9" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.9.tgz#46ec9c571cd667b73e94918f82d6414acc52a281" + integrity sha512-S1AGkrljacSddTDw+SuPTU+HH+GZr83aodj5fZ2Ia3ZBSmKK6/WKq3ZXlX7L14s6N7HYzEY2MXdC7ZDd3bpfjQ== dependencies: array-sort "^1.0.0" define-property "^2.0.2" @@ -1880,16 +1880,13 @@ handlebars "^4.7.7" handlebars-utils "^1.0.6" has-value "^2.0.2" - helper-md "^0.2.2" html-tag "^2.0.0" - is-even "^1.0.0" is-glob "^4.0.1" kind-of "^6.0.3" micromatch "^3.1.5" relative "^3.0.2" striptags "^3.1.1" to-gfm-code-block "^0.1.1" - year "^0.2.1" "@budibase/nano@10.1.2": version "10.1.2" @@ -7038,7 +7035,7 @@ arg@^5.0.2: resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== -argparse@^1.0.10, argparse@^1.0.7: +argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -7278,13 +7275,6 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== -autolinker@~0.28.0: - version "0.28.1" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" - integrity sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ== - dependencies: - gulp-header "^1.7.1" - available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -8862,7 +8852,7 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" -concat-with-sourcemaps@*, concat-with-sourcemaps@^1.1.0: +concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== @@ -10646,11 +10636,6 @@ enquirer@^2.3.5, enquirer@^2.3.6, enquirer@~2.3.6: dependencies: ansi-colors "^4.1.1" -ent@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA== - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -13185,15 +13170,6 @@ gtoken@^5.0.4: google-p12-pem "^3.1.3" jws "^4.0.0" -gulp-header@^1.7.1: - version "1.8.12" - resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" - integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== - dependencies: - concat-with-sourcemaps "*" - lodash.template "^4.4.0" - through2 "^2.0.0" - handlebars-utils@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" @@ -13376,16 +13352,6 @@ help-me@^4.0.1: glob "^8.0.0" readable-stream "^3.6.0" -helper-md@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" - integrity sha512-49TaQzK+Ic7ZVTq4i1UZxRUJEmAilTk8hz7q4I0WNUaTclLR8ArJV5B3A1fe1xF2HtsDTr2gYKLaVTof/Lt84Q== - dependencies: - ent "^2.2.0" - extend-shallow "^2.0.1" - fs-exists-sync "^0.1.0" - remarkable "^1.6.2" - hexoid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" @@ -14157,13 +14123,6 @@ is-docker@^2.0.0, is-docker@^2.1.1: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-even@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" - integrity sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg== - dependencies: - is-odd "^0.1.2" - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -14341,13 +14300,6 @@ is-object@~0.1.2: resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7" integrity sha512-GkfZZlIZtpkFrqyAXPQSRBMsaHAw+CgoKe2HXAkjd/sfoI9+hS8PT4wg2rJxdQyUKr7N2vHJbg7/jQtE5l5vBQ== -is-odd@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" - integrity sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw== - dependencies: - is-number "^3.0.0" - is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -17066,11 +17018,6 @@ lodash-es@^4.17.11: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -17201,21 +17148,6 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== -lodash.template@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -21769,14 +21701,6 @@ relative@^3.0.2: dependencies: isobject "^2.0.0" -remarkable@^1.6.2: - version "1.7.4" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" - integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== - dependencies: - argparse "^1.0.10" - autolinker "~0.28.0" - remixicon@2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41" @@ -26127,11 +26051,6 @@ yauzl@^2.10.0, yauzl@^2.4.2: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -year@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" - integrity sha512-9GnJUZ0QM4OgXuOzsKNzTJ5EOkums1Xc+3YQXp+Q+UxFjf7zLucp9dQ8QMIft0Szs1E1hUiXFim1OYfEKFq97w== - ylru@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.3.2.tgz#0de48017473275a4cbdfc83a1eaf67c01af8a785" From b01c800d08c768bb97cd2c0336e00286399e1e31 Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Wed, 19 Jul 2023 15:18:02 +0100 Subject: [PATCH 067/821] Updating comments --- .../tests/accounts/accounts.internal.spec.ts | 2 +- .../tests/accounts/accounts.spec.ts | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts index 47b0ffb12a..fc5eb57de7 100644 --- a/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts +++ b/qa-core/src/account-api/tests/accounts/accounts.internal.spec.ts @@ -13,7 +13,7 @@ describe("Account Internal Operations", () => { await config.afterAll() }) - it("performs account deletion", async () => { + it("performs account deletion by ID", async () => { // Deleting by unknown id doesn't work const accountId = generator.string() await config.api.accounts.delete(accountId, { status: 404 }) diff --git a/qa-core/src/account-api/tests/accounts/accounts.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.spec.ts index e61e2089c2..e1c0dd2c20 100644 --- a/qa-core/src/account-api/tests/accounts/accounts.spec.ts +++ b/qa-core/src/account-api/tests/accounts/accounts.spec.ts @@ -2,7 +2,7 @@ import TestConfiguration from "../../config/TestConfiguration" import * as fixtures from "../../fixtures" import { generator } from "../../../shared" -describe("accounts", () => { +describe("Accounts", () => { const config = new TestConfiguration() beforeAll(async () => { @@ -15,18 +15,19 @@ describe("accounts", () => { it("performs signup and deletion flow", async () => { await config.doInNewState(async () => { + // Create account const createAccountRequest = fixtures.accounts.generateAccount() const email = createAccountRequest.email const tenantId = createAccountRequest.tenantId - // Validation - email and tenant id allowed + // Validation - email and tenant ID allowed await config.api.accounts.validateEmail(email) await config.api.accounts.validateTenantId(tenantId) // Create unverified account await config.api.accounts.create(createAccountRequest) - // Validation - email and tenant id no longer valid + // Validation - email and tenant ID no longer valid await config.api.accounts.validateEmail(email, { status: 400 }) await config.api.accounts.validateTenantId(tenantId, { status: 400 }) @@ -39,26 +40,26 @@ describe("accounts", () => { // Send the verification request await config.accountsApi.accounts.verifyAccount(code!) - // Can now login to the account + // Can now log in to the account await config.loginAsAccount(createAccountRequest) // Delete account await config.api.accounts.deleteCurrentAccount() - // Can't login + // Can't log in await config.loginAsAccount(createAccountRequest, { status: 403 }) }) }) - describe("searching accounts", () => { - it("searches by tenant id", async () => { + describe("Searching accounts", () => { + it("search by tenant ID", async () => { const tenantId = generator.string() - // empty result - const [emptyRes, emptyBody] = await config.api.accounts.search(tenantId, "tenantId") + // Empty result + const [_, emptyBody] = await config.api.accounts.search(tenantId, "tenantId") expect(emptyBody.length).toBe(0) - // hit result + // Hit result const [hitRes, hitBody] = await config.api.accounts.search(config.state.tenantId!, "tenantId") expect(hitBody.length).toBe(1) expect(hitBody[0].tenantId).toBe(config.state.tenantId) @@ -67,11 +68,11 @@ describe("accounts", () => { it("searches by email", async () => { const email = generator.email() - // empty result - const [emptyRes, emptyBody] = await config.api.accounts.search(email, "email") + // Empty result + const [_, emptyBody] = await config.api.accounts.search(email, "email") expect(emptyBody.length).toBe(0) - // hit result + // Hit result const [hitRes, hitBody] = await config.api.accounts.search(config.state.email!, "email") expect(hitBody.length).toBe(1) expect(hitBody[0].email).toBe(config.state.email) From ecf75a96857ce1cb454f749bbc65df7b1828880c Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Wed, 19 Jul 2023 15:42:12 +0100 Subject: [PATCH 068/821] lint --- packages/types/src/api/account/accounts.ts | 2 +- qa-core/scripts/createEnv.js | 2 +- .../api/AccountInternalAPIClient.ts | 2 +- .../src/account-api/api/apis/AccountAPI.ts | 109 ++++++++---------- qa-core/src/account-api/api/apis/AuthAPI.ts | 15 +-- qa-core/src/account-api/api/apis/BaseAPI.ts | 6 +- .../src/account-api/api/apis/LicenseAPI.ts | 11 +- .../account-api/config/TestConfiguration.ts | 38 +++--- qa-core/src/account-api/fixtures/accounts.ts | 26 ++--- qa-core/src/account-api/fixtures/index.ts | 2 +- .../tests/accounts/accounts.spec.ts | 19 ++- qa-core/src/jest/globalSetup.ts | 42 ++++--- qa-core/src/jest/globalTeardown.ts | 4 +- .../src/shared/BudibaseTestConfiguration.ts | 5 +- 14 files changed, 140 insertions(+), 143 deletions(-) diff --git a/packages/types/src/api/account/accounts.ts b/packages/types/src/api/account/accounts.ts index e2accf7c18..bb3419c5d1 100644 --- a/packages/types/src/api/account/accounts.ts +++ b/packages/types/src/api/account/accounts.ts @@ -19,4 +19,4 @@ export interface SearchAccountsRequest { tenantId?: string } -export type SearchAccountsResponse = Account[] \ No newline at end of file +export type SearchAccountsResponse = Account[] diff --git a/qa-core/scripts/createEnv.js b/qa-core/scripts/createEnv.js index ebb165f3a9..64b9664049 100644 --- a/qa-core/scripts/createEnv.js +++ b/qa-core/scripts/createEnv.js @@ -13,7 +13,7 @@ function init() { BB_ADMIN_USER_PASSWORD: "admin", LOG_LEVEL: "info", JEST_TIMEOUT: "60000", - DISABLE_PINO_LOGGER: "1" + DISABLE_PINO_LOGGER: "1", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/qa-core/src/account-api/api/AccountInternalAPIClient.ts b/qa-core/src/account-api/api/AccountInternalAPIClient.ts index b1e32cac1f..1b73f51320 100644 --- a/qa-core/src/account-api/api/AccountInternalAPIClient.ts +++ b/qa-core/src/account-api/api/AccountInternalAPIClient.ts @@ -48,7 +48,7 @@ export default class AccountInternalAPIClient { if (options.internal) { requestOptions.headers = { ...requestOptions.headers, - ...{ [Header.API_KEY] : env.ACCOUNT_PORTAL_API_KEY }, + ...{ [Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY }, } } diff --git a/qa-core/src/account-api/api/apis/AccountAPI.ts b/qa-core/src/account-api/api/apis/AccountAPI.ts index 8c6ccec6b8..33e64da7ad 100644 --- a/qa-core/src/account-api/api/apis/AccountAPI.ts +++ b/qa-core/src/account-api/api/apis/AccountAPI.ts @@ -1,5 +1,10 @@ import { Response } from "node-fetch" -import { Account, CreateAccountRequest, SearchAccountsRequest, SearchAccountsResponse } from "@budibase/types" +import { + Account, + CreateAccountRequest, + SearchAccountsRequest, + SearchAccountsResponse, +} from "@budibase/types" import AccountInternalAPIClient from "../AccountInternalAPIClient" import { APIRequestOpts } from "../../../types" import { Header } from "@budibase/backend-core" @@ -13,17 +18,11 @@ export default class AccountAPI extends BaseAPI { this.client = client } - async validateEmail( - email: string, - opts: APIRequestOpts = { status: 200 } - ) { + async validateEmail(email: string, opts: APIRequestOpts = { status: 200 }) { return this.doRequest(() => { - return this.client.post( - `/api/accounts/validate/email`, - { - body: { email }, - } - ) + return this.client.post(`/api/accounts/validate/email`, { + body: { email }, + }) }, opts) } @@ -32,22 +31,22 @@ export default class AccountAPI extends BaseAPI { opts: APIRequestOpts = { status: 200 } ) { return this.doRequest(() => { - return this.client.post( - `/api/accounts/validate/tenantId`, - { - body: { tenantId }, - } - ) + return this.client.post(`/api/accounts/validate/tenantId`, { + body: { tenantId }, + }) }, opts) } async create( body: CreateAccountRequest, - opts: APIRequestOpts & { autoVerify: boolean } = { status: 201, autoVerify: false } + opts: APIRequestOpts & { autoVerify: boolean } = { + status: 201, + autoVerify: false, + } ): Promise<[Response, Account]> { return this.doRequest(() => { const headers = { - "no-verify": opts.autoVerify ? "1" : "0" + "no-verify": opts.autoVerify ? "1" : "0", } return this.client.post(`/api/accounts`, { body, @@ -56,81 +55,63 @@ export default class AccountAPI extends BaseAPI { }, opts) } - async delete( - accountID: string, - opts: APIRequestOpts = { status: 204 }) { + async delete(accountID: string, opts: APIRequestOpts = { status: 204 }) { return this.doRequest(() => { - return this.client.del( - `/api/accounts/${accountID}`, - { - internal: true, - } - ) + return this.client.del(`/api/accounts/${accountID}`, { + internal: true, + }) }, opts) } - async deleteCurrentAccount( - opts: APIRequestOpts = { status: 204 } - ) { + async deleteCurrentAccount(opts: APIRequestOpts = { status: 204 }) { return this.doRequest(() => { - return this.client.del( - `/api/accounts` - ) + return this.client.del(`/api/accounts`) }, opts) } async verifyAccount( verificationCode: string, opts: APIRequestOpts = { status: 200 } - ){ + ) { return this.doRequest(() => { - return this.client.post( - `/api/accounts/verify`, - { - body: { verificationCode }, - } - ) + return this.client.post(`/api/accounts/verify`, { + body: { verificationCode }, + }) }, opts) } async sendVerificationEmail( - email: string, - opts: APIRequestOpts = { status: 200 } + email: string, + opts: APIRequestOpts = { status: 200 } ): Promise<[Response, string]> { return this.doRequest(async () => { - const [response] = await this.client.post( - `/api/accounts/verify/send`, - { - body: { email }, - headers: { - [Header.RETURN_VERIFICATION_CODE]: "1" - } - } - ) + const [response] = await this.client.post(`/api/accounts/verify/send`, { + body: { email }, + headers: { + [Header.RETURN_VERIFICATION_CODE]: "1", + }, + }) const code = response.headers.get(Header.VERIFICATION_CODE) return [response, code] }, opts) } async search( - searchType: string, - search: 'email' | 'tenantId', - opts: APIRequestOpts = { status: 200 } + searchType: string, + search: "email" | "tenantId", + opts: APIRequestOpts = { status: 200 } ): Promise<[Response, SearchAccountsResponse]> { return this.doRequest(() => { let body: SearchAccountsRequest = {} - if (search === 'email') { + if (search === "email") { body.email = searchType - } else if (search === 'tenantId') { + } else if (search === "tenantId") { body.tenantId = searchType } - return this.client.post( - `/api/accounts/search`, - { - body, - internal: true - } - ) + return this.client.post(`/api/accounts/search`, { + body, + internal: true, + }) }, opts) } } diff --git a/qa-core/src/account-api/api/apis/AuthAPI.ts b/qa-core/src/account-api/api/apis/AuthAPI.ts index c5d654ade0..50345c891b 100644 --- a/qa-core/src/account-api/api/apis/AuthAPI.ts +++ b/qa-core/src/account-api/api/apis/AuthAPI.ts @@ -17,15 +17,12 @@ export default class AuthAPI extends BaseAPI { opts: APIRequestOpts = { doExpect: true, status: 200 } ): Promise<[Response, string]> { return this.doRequest(async () => { - const [res] = await this.client.post( - `/api/auth/login`, - { - body: { - email: email, - password: password, - }, - } - ) + const [res] = await this.client.post(`/api/auth/login`, { + body: { + email: email, + password: password, + }, + }) const cookie = res.headers.get("set-cookie") return [res, cookie] }, opts) diff --git a/qa-core/src/account-api/api/apis/BaseAPI.ts b/qa-core/src/account-api/api/apis/BaseAPI.ts index db0d921dd6..7b74a2d3f5 100644 --- a/qa-core/src/account-api/api/apis/BaseAPI.ts +++ b/qa-core/src/account-api/api/apis/BaseAPI.ts @@ -4,10 +4,10 @@ import { APIRequestOpts } from "../../../types" // TODO: Add to LicenseAPI export default class BaseAPI { - async doRequest( request: () => Promise<[Response, any]>, - opts: APIRequestOpts): Promise<[Response, any]> { + opts: APIRequestOpts + ): Promise<[Response, any]> { const [response, body] = await request() // do expect on by default @@ -19,4 +19,4 @@ export default class BaseAPI { } return [response, body] } -} \ No newline at end of file +} diff --git a/qa-core/src/account-api/api/apis/LicenseAPI.ts b/qa-core/src/account-api/api/apis/LicenseAPI.ts index 67e67edcb1..f0398ade95 100644 --- a/qa-core/src/account-api/api/apis/LicenseAPI.ts +++ b/qa-core/src/account-api/api/apis/LicenseAPI.ts @@ -18,13 +18,10 @@ export default class LicenseAPI extends BaseAPI { opts: APIRequestOpts = { status: 200 } ): Promise<[Response, Account]> { return this.doRequest(() => { - return this.client.put( - `/api/accounts/${accountId}/license`, - { - body, - internal: true, - } - ) + return this.client.put(`/api/accounts/${accountId}/license`, { + body, + internal: true, + }) }, opts) } } diff --git a/qa-core/src/account-api/config/TestConfiguration.ts b/qa-core/src/account-api/config/TestConfiguration.ts index 6322be97f0..66adb85ca2 100644 --- a/qa-core/src/account-api/config/TestConfiguration.ts +++ b/qa-core/src/account-api/config/TestConfiguration.ts @@ -2,28 +2,28 @@ import { AccountInternalAPI } from "../api" import { BudibaseTestConfiguration } from "../../shared" export default class TestConfiguration extends BudibaseTestConfiguration { - // apis - api: AccountInternalAPI + // apis + api: AccountInternalAPI - context: T + context: T - constructor() { - super() - this.api = new AccountInternalAPI(this.state) - this.context = {} - } + constructor() { + super() + this.api = new AccountInternalAPI(this.state) + this.context = {} + } - async beforeAll() { - await super.beforeAll() - await this.setApiKey() - } + async beforeAll() { + await super.beforeAll() + await this.setApiKey() + } - async afterAll() { - await super.afterAll() - } + async afterAll() { + await super.afterAll() + } - async setApiKey() { - const apiKeyResponse = await this.internalApi.self.getApiKey() - this.state.apiKey = apiKeyResponse.apiKey - } + async setApiKey() { + const apiKeyResponse = await this.internalApi.self.getApiKey() + this.state.apiKey = apiKeyResponse.apiKey + } } diff --git a/qa-core/src/account-api/fixtures/accounts.ts b/qa-core/src/account-api/fixtures/accounts.ts index 6c52cb52b0..62c84f3647 100644 --- a/qa-core/src/account-api/fixtures/accounts.ts +++ b/qa-core/src/account-api/fixtures/accounts.ts @@ -3,19 +3,19 @@ import { Hosting, CreateAccountRequest } from "@budibase/types" // TODO: Refactor me to central location export const generateAccount = (): CreateAccountRequest => { - const uuid = generator.guid() + const uuid = generator.guid() - const email = `${uuid}@budibase.com` - const tenant = `tenant${uuid.replace(/-/g, "")}` + const email = `${uuid}@budibase.com` + const tenant = `tenant${uuid.replace(/-/g, "")}` - return { - email, - hosting: Hosting.CLOUD, - name: email, - password: uuid, - profession: "software_engineer", - size: "10+", - tenantId: tenant, - tenantName: tenant, - } + return { + email, + hosting: Hosting.CLOUD, + name: email, + password: uuid, + profession: "software_engineer", + size: "10+", + tenantId: tenant, + tenantName: tenant, + } } diff --git a/qa-core/src/account-api/fixtures/index.ts b/qa-core/src/account-api/fixtures/index.ts index 7d09ee1c2c..952f4a8cc8 100644 --- a/qa-core/src/account-api/fixtures/index.ts +++ b/qa-core/src/account-api/fixtures/index.ts @@ -1 +1 @@ -export * as accounts from "./accounts" \ No newline at end of file +export * as accounts from "./accounts" diff --git a/qa-core/src/account-api/tests/accounts/accounts.spec.ts b/qa-core/src/account-api/tests/accounts/accounts.spec.ts index e1c0dd2c20..e3a4d4eacf 100644 --- a/qa-core/src/account-api/tests/accounts/accounts.spec.ts +++ b/qa-core/src/account-api/tests/accounts/accounts.spec.ts @@ -35,7 +35,9 @@ describe("Accounts", () => { await config.loginAsAccount(createAccountRequest, { status: 400 }) // Re-send verification email to get access to code - const [_, code] = await config.accountsApi.accounts.sendVerificationEmail(email) + const [_, code] = await config.accountsApi.accounts.sendVerificationEmail( + email + ) // Send the verification request await config.accountsApi.accounts.verifyAccount(code!) @@ -56,11 +58,17 @@ describe("Accounts", () => { const tenantId = generator.string() // Empty result - const [_, emptyBody] = await config.api.accounts.search(tenantId, "tenantId") + const [_, emptyBody] = await config.api.accounts.search( + tenantId, + "tenantId" + ) expect(emptyBody.length).toBe(0) // Hit result - const [hitRes, hitBody] = await config.api.accounts.search(config.state.tenantId!, "tenantId") + const [hitRes, hitBody] = await config.api.accounts.search( + config.state.tenantId!, + "tenantId" + ) expect(hitBody.length).toBe(1) expect(hitBody[0].tenantId).toBe(config.state.tenantId) }) @@ -73,7 +81,10 @@ describe("Accounts", () => { expect(emptyBody.length).toBe(0) // Hit result - const [hitRes, hitBody] = await config.api.accounts.search(config.state.email!, "email") + const [hitRes, hitBody] = await config.api.accounts.search( + config.state.email!, + "email" + ) expect(hitBody.length).toBe(1) expect(hitBody[0].email).toBe(config.state.email) }) diff --git a/qa-core/src/jest/globalSetup.ts b/qa-core/src/jest/globalSetup.ts index 68300cee26..103126d74c 100644 --- a/qa-core/src/jest/globalSetup.ts +++ b/qa-core/src/jest/globalSetup.ts @@ -9,7 +9,7 @@ import { APIRequestOpts } from "../types" const accountsApi = new AccountInternalAPI({}) const internalApi = new BudibaseInternalAPI({}) -const API_OPTS: APIRequestOpts = { doExpect: false} +const API_OPTS: APIRequestOpts = { doExpect: false } // @ts-ignore global.qa = {} @@ -18,8 +18,10 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> { const account = fixtures.accounts.generateAccount() await accountsApi.accounts.validateEmail(account.email, API_OPTS) await accountsApi.accounts.validateTenantId(account.tenantId, API_OPTS) - const [res, newAccount] = await accountsApi.accounts.create( - account, {...API_OPTS, autoVerify: true }) + const [res, newAccount] = await accountsApi.accounts.create(account, { + ...API_OPTS, + autoVerify: true, + }) await updateLicense(newAccount.accountId) return [account, newAccount] } @@ -27,25 +29,29 @@ async function createAccount(): Promise<[CreateAccountRequest, Account]> { const UNLIMITED = { value: -1 } async function updateLicense(accountId: string) { - const [response] = await accountsApi.licenses.updateLicense(accountId, { - overrides: { - // add all features - features: Object.values(Feature), - quotas: { - usage: { - monthly: { - automations: UNLIMITED, - }, - static: { - rows: UNLIMITED, - users: UNLIMITED, - userGroups: UNLIMITED, - plugins: UNLIMITED, + const [response] = await accountsApi.licenses.updateLicense( + accountId, + { + overrides: { + // add all features + features: Object.values(Feature), + quotas: { + usage: { + monthly: { + automations: UNLIMITED, + }, + static: { + rows: UNLIMITED, + users: UNLIMITED, + userGroups: UNLIMITED, + plugins: UNLIMITED, + }, }, }, }, }, - }, { doExpect: false }) + { doExpect: false } + ) if (response.status !== 200) { throw new Error( `Could not update license for accountId=${accountId}: ${response.status}` diff --git a/qa-core/src/jest/globalTeardown.ts b/qa-core/src/jest/globalTeardown.ts index f1b8c1d541..ff3fea7b7c 100644 --- a/qa-core/src/jest/globalTeardown.ts +++ b/qa-core/src/jest/globalTeardown.ts @@ -11,7 +11,9 @@ async function deleteAccount() { // @ts-ignore const accountID = global.qa.accountId - const [response] = await accountsApi.accounts.delete(accountID, { doExpect: false }) + const [response] = await accountsApi.accounts.delete(accountID, { + doExpect: false, + }) if (response.status !== 204) { throw new Error(`status: ${response.status} not equal to expected: 201`) } diff --git a/qa-core/src/shared/BudibaseTestConfiguration.ts b/qa-core/src/shared/BudibaseTestConfiguration.ts index 9841b8d163..18b7c89ec8 100644 --- a/qa-core/src/shared/BudibaseTestConfiguration.ts +++ b/qa-core/src/shared/BudibaseTestConfiguration.ts @@ -69,7 +69,10 @@ export default class BudibaseTestConfiguration { this.state.email = original.email } - async loginAsAccount(account: CreateAccountRequest, opts: APIRequestOpts = {}) { + async loginAsAccount( + account: CreateAccountRequest, + opts: APIRequestOpts = {} + ) { const [_, cookie] = await this.accountsApi.auth.login( account.email, account.password, From 85dea47a31d12b859ef4a864e17213337c2c5f47 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 19 Jul 2023 16:19:34 +0100 Subject: [PATCH 069/821] Moving user admin/builder functions to shared-core for frontend to use. --- packages/backend-core/package.json | 1 + packages/backend-core/src/constants/db.ts | 44 ++----------------- packages/backend-core/src/users.ts | 40 +++-------------- packages/server/src/middleware/currentapp.ts | 4 +- packages/shared-core/src/index.ts | 1 + .../src/sdk/documents/applications.ts | 29 ++++++++++++ .../shared-core/src/sdk/documents/index.ts | 2 + .../shared-core/src/sdk/documents/users.ts | 35 +++++++++++++++ packages/shared-core/src/sdk/index.ts | 1 + packages/types/src/documents/document.ts | 41 +++++++++++++++++ packages/worker/src/sdk/users/users.ts | 2 +- 11 files changed, 123 insertions(+), 77 deletions(-) create mode 100644 packages/shared-core/src/sdk/documents/applications.ts create mode 100644 packages/shared-core/src/sdk/documents/index.ts create mode 100644 packages/shared-core/src/sdk/documents/users.ts create mode 100644 packages/shared-core/src/sdk/index.ts diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 7f3c064c92..93f2529987 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -22,6 +22,7 @@ "dependencies": { "@budibase/nano": "10.1.2", "@budibase/pouchdb-replication-stream": "1.2.10", + "@budibase/shared-core": "0.0.0", "@budibase/types": "0.0.0", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/backend-core/src/constants/db.ts b/packages/backend-core/src/constants/db.ts index 34dab6c088..83f8298f54 100644 --- a/packages/backend-core/src/constants/db.ts +++ b/packages/backend-core/src/constants/db.ts @@ -1,5 +1,5 @@ -export const SEPARATOR = "_" -export const UNICODE_MAX = "\ufff0" +import { prefixed, DocumentType } from "@budibase/types" +export { SEPARATOR, UNICODE_MAX, DocumentType } from "@budibase/types" /** * Can be used to create a few different forms of querying a view. @@ -34,42 +34,6 @@ export enum InternalTable { USER_METADATA = "ta_users", } -export enum DocumentType { - USER = "us", - GROUP = "gr", - WORKSPACE = "workspace", - CONFIG = "config", - TEMPLATE = "template", - APP = "app", - DEV = "dev", - APP_DEV = "app_dev", - APP_METADATA = "app_metadata", - ROLE = "role", - MIGRATIONS = "migrations", - DEV_INFO = "devinfo", - AUTOMATION_LOG = "log_au", - ACCOUNT_METADATA = "acc_metadata", - PLUGIN = "plg", - DATASOURCE = "datasource", - DATASOURCE_PLUS = "datasource_plus", - APP_BACKUP = "backup", - TABLE = "ta", - ROW = "ro", - AUTOMATION = "au", - LINK = "li", - WEBHOOK = "wh", - INSTANCE = "inst", - LAYOUT = "layout", - SCREEN = "screen", - QUERY = "query", - DEPLOYMENTS = "deployments", - METADATA = "metadata", - MEM_VIEW = "view", - USER_FLAG = "flag", - AUTOMATION_METADATA = "meta_au", - AUDIT_LOG = "al", -} - export const StaticDatabases = { GLOBAL: { name: "global-db", @@ -93,7 +57,7 @@ export const StaticDatabases = { }, } -export const APP_PREFIX = DocumentType.APP + SEPARATOR -export const APP_DEV = DocumentType.APP_DEV + SEPARATOR +export const APP_PREFIX = prefixed(DocumentType.APP) +export const APP_DEV = prefixed(DocumentType.APP_DEV) export const APP_DEV_PREFIX = APP_DEV export const BUDIBASE_DATASOURCE_TYPE = "budibase" diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 71cd599f87..7224d827e8 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -18,6 +18,7 @@ import { User, ContextUser, } from "@budibase/types" +import { sdk } from "@budibase/shared-core" import { getGlobalDB } from "./context" import * as context from "./context" @@ -38,6 +39,12 @@ function removeUserPassword(users: User | User[]) { return users } +// extract from shared-core to make easily accessible from backend-core +export const isBuilder = sdk.users.isBuilder +export const isAdmin = sdk.users.isAdmin +export const hasAdminPermissions = sdk.users.hasAdminPermissions +export const hasBuilderPermissions = sdk.users.hasBuilderPermissions + export const bulkGetGlobalUsersById = async ( userIds: string[], opts?: GetOpts @@ -254,39 +261,6 @@ export async function getUserCount() { return response.total_rows } -// checks if a user is specifically a builder, given an app ID -export function isBuilder(user: User | ContextUser, appId?: string) { - if (user.builder?.global) { - return true - } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { - return true - } - return false -} - -// alias for hasAdminPermission, currently do the same thing -// in future whether someone has admin permissions and whether they are -// an admin for a specific resource could be separated -export function isAdmin(user: User | ContextUser) { - return hasAdminPermissions(user) -} - -// checks if a user is capable of building any app -export function hasBuilderPermissions(user?: User | ContextUser) { - if (!user) { - return false - } - return user.builder?.global || user.builder?.apps?.length !== 0 -} - -// checks if a user is capable of being an admin -export function hasAdminPermissions(user?: User | ContextUser) { - if (!user) { - return false - } - return user.admin?.global -} - // used to remove the builder/admin permissions, for processing the // user as an app user (they may have some specific role/group export function removePortalUserPermissions(user: User | ContextUser) { diff --git a/packages/server/src/middleware/currentapp.ts b/packages/server/src/middleware/currentapp.ts index 1f580d80e4..800d43e69c 100644 --- a/packages/server/src/middleware/currentapp.ts +++ b/packages/server/src/middleware/currentapp.ts @@ -24,7 +24,7 @@ export default async (ctx: UserCtx, next: any) => { if ( isDevAppID(requestAppId) && !isWebhookEndpoint(ctx) && - (!ctx.user || !ctx.user.builder || !ctx.user.builder.global) + !users.isBuilder(ctx.user, requestAppId) ) { return ctx.redirect("/") } @@ -70,7 +70,6 @@ export default async (ctx: UserCtx, next: any) => { } return context.doInAppContext(appId, async () => { - let skipCookie = false // if the user not in the right tenant then make sure they have no permissions // need to judge this only based on the request app ID, if ( @@ -83,7 +82,6 @@ export default async (ctx: UserCtx, next: any) => { ctx.user = users.cleanseUserObject(ctx.user) as ContextUser ctx.isAuthenticated = false roleId = roles.BUILTIN_ROLE_IDS.PUBLIC - skipCookie = true } ctx.appId = appId diff --git a/packages/shared-core/src/index.ts b/packages/shared-core/src/index.ts index 21f2f2c639..4a47eda898 100644 --- a/packages/shared-core/src/index.ts +++ b/packages/shared-core/src/index.ts @@ -2,3 +2,4 @@ export * from "./constants" export * as dataFilters from "./filters" export * as helpers from "./helpers" export * as utils from "./utils" +export * as sdk from "./sdk" diff --git a/packages/shared-core/src/sdk/documents/applications.ts b/packages/shared-core/src/sdk/documents/applications.ts new file mode 100644 index 0000000000..05129f6e75 --- /dev/null +++ b/packages/shared-core/src/sdk/documents/applications.ts @@ -0,0 +1,29 @@ +import { DocumentType, prefixed } from "@budibase/types" + +const APP_PREFIX = prefixed(DocumentType.APP) +const APP_DEV_PREFIX = prefixed(DocumentType.APP_DEV) + +export function getDevAppID(appId: string) { + if (!appId || appId.startsWith(APP_DEV_PREFIX)) { + return appId + } + // split to take off the app_ element, then join it together incase any other app_ exist + const split = appId.split(APP_PREFIX) + split.shift() + const rest = split.join(APP_PREFIX) + return `${APP_DEV_PREFIX}${rest}` +} + +/** + * Convert a development app ID to a deployed app ID. + */ +export function getProdAppID(appId: string) { + if (!appId || !appId.startsWith(APP_DEV_PREFIX)) { + return appId + } + // split to take off the app_dev element, then join it together incase any other app_ exist + const split = appId.split(APP_DEV_PREFIX) + split.shift() + const rest = split.join(APP_DEV_PREFIX) + return `${APP_PREFIX}${rest}` +} diff --git a/packages/shared-core/src/sdk/documents/index.ts b/packages/shared-core/src/sdk/documents/index.ts new file mode 100644 index 0000000000..d20631eef4 --- /dev/null +++ b/packages/shared-core/src/sdk/documents/index.ts @@ -0,0 +1,2 @@ +export * as applications from "./applications" +export * as users from "./users" diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts new file mode 100644 index 0000000000..df3ef0025f --- /dev/null +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -0,0 +1,35 @@ +import { ContextUser, User } from "@budibase/types" +import { getProdAppID } from "./applications" + +// checks if a user is specifically a builder, given an app ID +export function isBuilder(user: User | ContextUser, appId?: string) { + if (user.builder?.global) { + return true + } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { + return true + } + return false +} + +// alias for hasAdminPermission, currently do the same thing +// in future whether someone has admin permissions and whether they are +// an admin for a specific resource could be separated +export function isAdmin(user: User | ContextUser) { + return hasAdminPermissions(user) +} + +// checks if a user is capable of building any app +export function hasBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.builder?.global || user.builder?.apps?.length !== 0 +} + +// checks if a user is capable of being an admin +export function hasAdminPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return user.admin?.global +} diff --git a/packages/shared-core/src/sdk/index.ts b/packages/shared-core/src/sdk/index.ts new file mode 100644 index 0000000000..87868e2723 --- /dev/null +++ b/packages/shared-core/src/sdk/index.ts @@ -0,0 +1 @@ +export * from "./documents" diff --git a/packages/types/src/documents/document.ts b/packages/types/src/documents/document.ts index ac05214b82..6ba318269b 100644 --- a/packages/types/src/documents/document.ts +++ b/packages/types/src/documents/document.ts @@ -1,3 +1,44 @@ +export const SEPARATOR = "_" +export const UNICODE_MAX = "\ufff0" + +export const prefixed = (type: DocumentType) => `${type}${SEPARATOR}` + +export enum DocumentType { + USER = "us", + GROUP = "gr", + WORKSPACE = "workspace", + CONFIG = "config", + TEMPLATE = "template", + APP = "app", + DEV = "dev", + APP_DEV = "app_dev", + APP_METADATA = "app_metadata", + ROLE = "role", + MIGRATIONS = "migrations", + DEV_INFO = "devinfo", + AUTOMATION_LOG = "log_au", + ACCOUNT_METADATA = "acc_metadata", + PLUGIN = "plg", + DATASOURCE = "datasource", + DATASOURCE_PLUS = "datasource_plus", + APP_BACKUP = "backup", + TABLE = "ta", + ROW = "ro", + AUTOMATION = "au", + LINK = "li", + WEBHOOK = "wh", + INSTANCE = "inst", + LAYOUT = "layout", + SCREEN = "screen", + QUERY = "query", + DEPLOYMENTS = "deployments", + METADATA = "metadata", + MEM_VIEW = "view", + USER_FLAG = "flag", + AUTOMATION_METADATA = "meta_au", + AUDIT_LOG = "al", +} + export interface Document { _id?: string _rev?: string diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index cfa68932d2..e0aa042995 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -176,7 +176,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => { export async function isPreventPasswordActions(user: User, account?: Account) { // when in maintenance mode we allow sso users with the admin role // to perform any password action - this prevents lockout - if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && user.admin?.global) { + if (coreEnv.ENABLE_SSO_MAINTENANCE_MODE && usersCore.isAdmin(user)) { return false } From a99124608ff01759cccfec2e377a4a3d813de3d6 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 19 Jul 2023 15:22:43 +0000 Subject: [PATCH 070/821] Bump version to 2.8.15 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index bca2548eb1..551b1f60f2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.14", + "version": "2.8.15", "npmClient": "yarn", "packages": [ "packages/*" From 0d7a933db699d5c2f0f1c08952e7e59badca3145 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 19 Jul 2023 15:48:12 +0000 Subject: [PATCH 071/821] Bump version to 2.8.16 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 551b1f60f2..83b8f35697 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.15", + "version": "2.8.16", "npmClient": "yarn", "packages": [ "packages/*" From 6631c2644d3c1840bdb838faab567d4698353dc8 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Wed, 19 Jul 2023 17:18:37 +0100 Subject: [PATCH 072/821] Fixing merge issue --- packages/backend-core/src/logging/index.ts | 3 --- packages/backend-core/src/logging/pino/logger.ts | 2 -- qa-core/src/jest/jestSetup.ts | 8 +++----- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/logging/index.ts b/packages/backend-core/src/logging/index.ts index f7e1c4fa41..0824fa681b 100644 --- a/packages/backend-core/src/logging/index.ts +++ b/packages/backend-core/src/logging/index.ts @@ -2,6 +2,3 @@ export * as correlation from "./correlation/correlation" export { logger } from "./pino/logger" export * from "./alerts" export * as system from "./system" - -// turn off or on context logging i.e. tenantId, appId etc -export let LOG_CONTEXT = true diff --git a/packages/backend-core/src/logging/pino/logger.ts b/packages/backend-core/src/logging/pino/logger.ts index 73b91d2fde..f2024db72b 100644 --- a/packages/backend-core/src/logging/pino/logger.ts +++ b/packages/backend-core/src/logging/pino/logger.ts @@ -2,11 +2,9 @@ import pino, { LoggerOptions } from "pino" import pinoPretty from "pino-pretty" import { IdentityType } from "@budibase/types" - import env from "../../environment" import * as context from "../../context" import * as correlation from "../correlation" -import { LOG_CONTEXT } from "../index" import { localFileDestination } from "../system" diff --git a/qa-core/src/jest/jestSetup.ts b/qa-core/src/jest/jestSetup.ts index 6c60845c87..f89d71dfcb 100644 --- a/qa-core/src/jest/jestSetup.ts +++ b/qa-core/src/jest/jestSetup.ts @@ -1,5 +1,3 @@ -import { logging } from "@budibase/backend-core" -logging.LOG_CONTEXT = false - -jest.retryTimes(2) -jest.setTimeout(60000) +const envTimeout = process.env.JEST_TIMEOUT +const timeout = envTimeout && parseInt(envTimeout) +jest.setTimeout(timeout || 60000) \ No newline at end of file From f9b54d6de493e3abcef75b474a4afcb5494d255d Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Wed, 19 Jul 2023 17:30:16 +0100 Subject: [PATCH 073/821] Update test commands to use dedicated environments: ci / prod / qa --- .github/workflows/budibase_ci.yml | 2 +- qa-core/package.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index b644a6bdb9..86e7df66da 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -159,7 +159,7 @@ jobs: run: | cd qa-core yarn setup - yarn test:ci + yarn serve:test:ci env: BB_ADMIN_USER_EMAIL: admin BB_ADMIN_USER_PASSWORD: admin diff --git a/qa-core/package.json b/qa-core/package.json index 555b277742..1fbd0c0254 100644 --- a/qa-core/package.json +++ b/qa-core/package.json @@ -15,8 +15,10 @@ "test:watch": "yarn run test --watch", "test:debug": "DEBUG=1 yarn run test", "test:notify": "node scripts/testResultsWebhook", - "test:smoke": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts", - "test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke", + "test:cloud:prod": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts", + "test:cloud:qa": "yarn run test", + "test:ci": "yarn run test --testPathIgnorePatterns=/.+\\.integration\\.spec\\.ts /account-api/", + "serve:test:ci": "start-server-and-test dev:built http://localhost:4001/health test:smoke", "serve": "start-server-and-test dev:built http://localhost:4001/health", "dev:built": "cd ../ && yarn dev:built" }, From ec5203f775f8820a172a846b981faf38f316b681 Mon Sep 17 00:00:00 2001 From: Mitch-Budibase Date: Wed, 19 Jul 2023 17:31:33 +0100 Subject: [PATCH 074/821] PR changes --- packages/worker/src/api/controllers/global/auth.ts | 2 -- qa-core/src/account-api/api/apis/BaseAPI.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/worker/src/api/controllers/global/auth.ts b/packages/worker/src/api/controllers/global/auth.ts index a458a98aa7..131601c6ad 100644 --- a/packages/worker/src/api/controllers/global/auth.ts +++ b/packages/worker/src/api/controllers/global/auth.ts @@ -53,8 +53,6 @@ async function passportCallback( } export const login = async (ctx: Ctx, next: any) => { - const tenantId = context.getTenantId() - console.log(tenantId) const email = ctx.request.body.username const user = await userSdk.getUserByEmail(email) diff --git a/qa-core/src/account-api/api/apis/BaseAPI.ts b/qa-core/src/account-api/api/apis/BaseAPI.ts index 7b74a2d3f5..ed5d261e9e 100644 --- a/qa-core/src/account-api/api/apis/BaseAPI.ts +++ b/qa-core/src/account-api/api/apis/BaseAPI.ts @@ -1,8 +1,6 @@ import { Response } from "node-fetch" import { APIRequestOpts } from "../../../types" -// TODO: Add to LicenseAPI - export default class BaseAPI { async doRequest( request: () => Promise<[Response, any]>, From e469abb6793cecd1170f7c8c129dc3955539e1f5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 19 Jul 2023 18:05:02 +0100 Subject: [PATCH 075/821] reworking frontend to use shared core functions to check if is admin or builder (needs further expansion). --- .../backend-core/src/middleware/adminOnly.ts | 6 +-- .../_components/BuilderSidePanel.svelte | 12 ++--- .../src/pages/builder/apps/index.svelte | 46 +++++++++---------- .../builder/src/pages/builder/index.svelte | 3 +- .../src/pages/builder/portal/_layout.svelte | 3 +- .../portal/users/users/[userId].svelte | 7 +-- .../_components/AppsTableRenderer.svelte | 3 +- .../users/_components/UpdateRolesModal.svelte | 3 +- packages/builder/src/stores/portal/auth.js | 9 ++-- packages/builder/src/stores/portal/users.js | 9 +++- .../shared-core/src/sdk/documents/users.ts | 4 ++ 11 files changed, 58 insertions(+), 47 deletions(-) diff --git a/packages/backend-core/src/middleware/adminOnly.ts b/packages/backend-core/src/middleware/adminOnly.ts index dc2fe9064e..6b2ee87c01 100644 --- a/packages/backend-core/src/middleware/adminOnly.ts +++ b/packages/backend-core/src/middleware/adminOnly.ts @@ -1,10 +1,8 @@ import { UserCtx } from "@budibase/types" +import { isAdmin } from "../users" export default async (ctx: UserCtx, next: any) => { - if ( - !ctx.internal && - (!ctx.user || !ctx.user.admin || !ctx.user.admin.global) - ) { + if (!ctx.internal && !isAdmin(ctx.user)) { ctx.throw(403, "Admin user only endpoint.") } return next() diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index 1dd4453537..db56602463 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -12,12 +12,12 @@ } from "@budibase/bbui" import { store } from "builderStore" import { groups, licensing, apps, users, auth, admin } from "stores/portal" - import { fetchData } from "@budibase/frontend-core" + import { fetchData, Constants, Utils } from "@budibase/frontend-core" + import { sdk } from "@budibase/shared-core" import { API } from "api" import GroupIcon from "../../../portal/users/groups/_components/GroupIcon.svelte" import RoleSelect from "components/common/RoleSelect.svelte" import UpgradeModal from "components/common/users/UpgradeModal.svelte" - import { Constants, Utils } from "@budibase/frontend-core" import { emailValidator } from "helpers/validation" import { roles } from "stores/backend" import { fly } from "svelte/transition" @@ -108,7 +108,7 @@ await usersFetch.refresh() filteredUsers = $usersFetch.rows.map(user => { - const isBuilderOrAdmin = user.admin?.global || user.builder?.global + const isBuilderOrAdmin = sdk.users.isBuilderOrAdmin(user, prodAppId) let role = undefined if (isBuilderOrAdmin) { role = Constants.Roles.ADMIN @@ -258,7 +258,7 @@ } // Must exclude users who have explicit privileges const userByEmail = filteredUsers.reduce((acc, user) => { - if (user.role || user.admin?.global || user.builder?.global) { + if (user.role || sdk.users.isBuilderOrAdmin(user, prodAppId)) { acc.push(user.email) } return acc @@ -389,9 +389,9 @@ } const userTitle = user => { - if (user.admin?.global) { + if (sdk.users.isAdmin(user)) { return "Admin" - } else if (user.builder?.global) { + } else if (sdk.users.isBuilder(user, prodAppId)) { return "Developer" } else { return "App user" diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index ab75c50747..f6c5df17c9 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -22,7 +22,7 @@ import Spaceman from "assets/bb-space-man.svg" import Logo from "assets/bb-emblem.svg" import { UserAvatar } from "@budibase/frontend-core" - import { helpers } from "@budibase/shared-core" + import { helpers, sdk } from "@budibase/shared-core" let loaded = false let userInfoModal @@ -43,32 +43,30 @@ $: userGroups = $groups.filter(group => group.users.find(user => user._id === $auth.user?._id) ) - let userApps = [] $: publishedApps = $apps.filter(publishedAppsOnly) + $: userApps = getUserApps($auth.user) - $: { - if (!Object.keys($auth.user?.roles).length && $auth.user?.userGroups) { - userApps = - $auth.user?.builder?.global || $auth.user?.admin?.global - ? publishedApps - : publishedApps.filter(app => { - return userGroups.find(group => { - return groups.actions - .getGroupAppIds(group) - .map(role => apps.extractAppId(role)) - .includes(app.appId) - }) - }) - } else { - userApps = - $auth.user?.builder?.global || $auth.user?.admin?.global - ? publishedApps - : publishedApps.filter(app => - Object.keys($auth.user?.roles) - .map(x => apps.extractAppId(x)) - .includes(app.appId) - ) + function getUserApps(user) { + if (sdk.users.isAdmin(user)) { + return publishedApps } + return publishedApps.filter(app => { + if (sdk.users.isBuilder(user, app.appId)) { + return true + } + if (!Object.keys(user?.roles).length && user?.userGroups) { + return userGroups.find(group => { + return groups.actions + .getGroupAppIds(group) + .map(role => apps.extractAppId(role)) + .includes(app.appId) + }) + } else { + return Object.keys($auth.user?.roles) + .map(x => apps.extractAppId(x)) + .includes(app.appId) + } + }) } function getUrl(app) { diff --git a/packages/builder/src/pages/builder/index.svelte b/packages/builder/src/pages/builder/index.svelte index fcaa7fc55b..c6d9d3c1c3 100644 --- a/packages/builder/src/pages/builder/index.svelte +++ b/packages/builder/src/pages/builder/index.svelte @@ -1,11 +1,12 @@ {#if tooltip && showTooltip}
- +
{/if}
@@ -80,15 +80,9 @@ position: absolute; pointer-events: none; left: 50%; - top: calc(100% + 4px); - width: 100vw; - max-width: 150px; + bottom: calc(100% + 4px); transform: translateX(-50%); text-align: center; - } - - .spectrum-Icon--sizeXS { - width: 10px; - height: 10px; + z-index: 1; } diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 6ffd7f8bb3..f319f09b16 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -33,6 +33,7 @@ import { getBindings } from "components/backend/DataTable/formula" import { getContext } from "svelte" import JSONSchemaModal from "./JSONSchemaModal.svelte" + import { ValidColumnNameRegex } from "@budibase/shared-core" const AUTO_TYPE = "auto" const FORMULA_TYPE = FIELDS.FORMULA.type @@ -379,7 +380,7 @@ const newError = {} if (!external && fieldInfo.name?.startsWith("_")) { newError.name = `Column name cannot start with an underscore.` - } else if (fieldInfo.name && !fieldInfo.name.match(/^[_a-zA-Z0-9\s]*$/g)) { + } else if (fieldInfo.name && !fieldInfo.name.match(ValidColumnNameRegex)) { newError.name = `Illegal character; must be alpha-numeric.` } else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) { newError.name = `${PROHIBITED_COLUMN_NAMES.join( diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index f34a3e9c98..1191f92b31 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -1,5 +1,5 @@
@@ -127,10 +129,8 @@ on:change={handleFile} />
{/each} From 2645e4cdad6a00069657e3a3b6535ade908ca0cc Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 14:56:01 +0100 Subject: [PATCH 098/821] Build fixes --- .../utilities/structures/documents/platform/installation.ts | 2 +- packages/pro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 25c5e50e00..711c6cf14f 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,4 +1,4 @@ -import { generator } from "@budibase/backend-core/tests" +import { generator } from "../../generator" import { Installation } from "@budibase/types" import * as db from "../../db" diff --git a/packages/pro b/packages/pro index b5124e76b9..5775dda6b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 +Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa From ca6582eb9617aadf628cff3671dbf079d780cbbb Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:06:43 +0100 Subject: [PATCH 099/821] Update openapi.json --- packages/server/specs/openapi.json | 9 ++++++--- packages/server/specs/openapi.yaml | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index bcff78e861..d97b09568c 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -841,7 +841,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1045,7 +1046,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1260,7 +1262,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index a9daed6c76..86807c9981 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -768,6 +768,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -931,6 +932,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -1101,6 +1103,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: From 6e974861160f4bcf0bcf472e1ea40565541fc9f3 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 20 Jul 2023 14:12:28 +0000 Subject: [PATCH 100/821] Bump version to 2.8.18 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 4dd30fd5f7..d97372e035 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.17", + "version": "2.8.18", "npmClient": "yarn", "packages": [ "packages/*" From 8fb796bdb3f866deb764cb575df63899f4f42dfe Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:31:19 +0100 Subject: [PATCH 101/821] Remove 'nightly' from test discord notification --- qa-core/scripts/testResultsWebhook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa-core/scripts/testResultsWebhook.js b/qa-core/scripts/testResultsWebhook.js index 40cc42082d..5fbdd3a32e 100644 --- a/qa-core/scripts/testResultsWebhook.js +++ b/qa-core/scripts/testResultsWebhook.js @@ -42,7 +42,7 @@ async function discordResultsNotification(report) { Accept: "application/json", }, body: JSON.stringify({ - content: `**Nightly Tests Status**: ${OUTCOME}`, + content: `**Tests Status**: ${OUTCOME}`, embeds: [ { title: `Budi QA Bot - ${env}`, From 054fb24ea935ce59c0872f7033f37fe2a4df75d5 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 20 Jul 2023 16:15:59 +0100 Subject: [PATCH 102/821] demote some app metadata related bb-alert messages --- packages/backend-core/src/cache/appMetadata.ts | 11 ++++------- packages/backend-core/src/db/utils.ts | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/cache/appMetadata.ts b/packages/backend-core/src/cache/appMetadata.ts index 5b66c356d3..f1f3133757 100644 --- a/packages/backend-core/src/cache/appMetadata.ts +++ b/packages/backend-core/src/cache/appMetadata.ts @@ -2,8 +2,8 @@ import { getAppClient } from "../redis/init" import { doWithDB, DocumentType } from "../db" import { Database, App } from "@budibase/types" -const AppState = { - INVALID: "invalid", +export enum AppState { + INVALID = "invalid" } const EXPIRY_SECONDS = 3600 @@ -61,11 +61,8 @@ export async function getAppMetadata(appId: string) { } await client.store(appId, metadata, expiry) } - // we've stored in the cache an object to tell us that it is currently invalid - if (isInvalid(metadata)) { - throw { status: 404, message: "No app metadata found" } - } - return metadata as App + + return metadata as App & { state: AppState } } /** diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 6034296996..137e210898 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -2,7 +2,7 @@ import env from "../environment" import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants" import { getTenantId, getGlobalDBName } from "../context" import { doWithDB, directCouchAllDbs } from "./db" -import { getAppMetadata } from "../cache/appMetadata" +import { AppState, getAppMetadata } from "../cache/appMetadata" import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions" import { App, Database } from "@budibase/types" import { getStartEndKeyURL } from "../docIds" @@ -101,7 +101,7 @@ export async function getAllApps({ const response = await Promise.allSettled(appPromises) const apps = response .filter( - (result: any) => result.status === "fulfilled" && result.value != null + (result: any) => result.status === "fulfilled" && result.value?.state !== AppState.INVALID ) .map(({ value }: any) => value) if (!all) { @@ -126,7 +126,7 @@ export async function getAppsByIDs(appIds: string[]) { ) // have to list the apps which exist, some may have been deleted return settled - .filter(promise => promise.status === "fulfilled") + .filter(promise => promise.status === "fulfilled" && promise.value.state !== AppState.INVALID) .map(promise => (promise as PromiseFulfilledResult).value) } From 6b6fba9393bd83f849ad89a1cbc919915f26e626 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 20 Jul 2023 16:38:24 +0100 Subject: [PATCH 103/821] optional chain --- packages/backend-core/src/db/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 137e210898..08764154eb 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -126,7 +126,7 @@ export async function getAppsByIDs(appIds: string[]) { ) // have to list the apps which exist, some may have been deleted return settled - .filter(promise => promise.status === "fulfilled" && promise.value.state !== AppState.INVALID) + .filter(promise => promise.status === "fulfilled" && promise.value?.state !== AppState.INVALID) .map(promise => (promise as PromiseFulfilledResult).value) } From 5cc97ebcd5db81d7377381914801acf03b6e4e5e Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 16:50:57 +0100 Subject: [PATCH 104/821] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 5775dda6b9..1b8fd8ed44 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa +Subproject commit 1b8fd8ed445c4c25210f8faf07ff404c41fbc805 From b5340c20d8a23f0f3f7a6ce738152747aaa2439d Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 20 Jul 2023 16:56:31 +0100 Subject: [PATCH 105/821] lint --- packages/backend-core/src/cache/appMetadata.ts | 2 +- packages/backend-core/src/db/utils.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/cache/appMetadata.ts b/packages/backend-core/src/cache/appMetadata.ts index f1f3133757..5ecc6f10b1 100644 --- a/packages/backend-core/src/cache/appMetadata.ts +++ b/packages/backend-core/src/cache/appMetadata.ts @@ -3,7 +3,7 @@ import { doWithDB, DocumentType } from "../db" import { Database, App } from "@budibase/types" export enum AppState { - INVALID = "invalid" + INVALID = "invalid", } const EXPIRY_SECONDS = 3600 diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 08764154eb..b6e793c065 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -101,7 +101,9 @@ export async function getAllApps({ const response = await Promise.allSettled(appPromises) const apps = response .filter( - (result: any) => result.status === "fulfilled" && result.value?.state !== AppState.INVALID + (result: any) => + result.status === "fulfilled" && + result.value?.state !== AppState.INVALID ) .map(({ value }: any) => value) if (!all) { @@ -126,7 +128,11 @@ export async function getAppsByIDs(appIds: string[]) { ) // have to list the apps which exist, some may have been deleted return settled - .filter(promise => promise.status === "fulfilled" && promise.value?.state !== AppState.INVALID) + .filter( + promise => + promise.status === "fulfilled" && + promise.value?.state !== AppState.INVALID + ) .map(promise => (promise as PromiseFulfilledResult).value) } From 3abe5d4cb21dda1a9cba0f1764499ad96d87f817 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 20 Jul 2023 18:34:12 +0100 Subject: [PATCH 106/821] Frontend work to support logging in as an app builder - also making sure when a new app is created that the user is assigned app access to it. --- packages/backend-core/src/users.ts | 25 +++++++++ packages/builder/src/stores/portal/menu.js | 44 ++++++++------- .../server/src/api/controllers/application.ts | 37 ++++--------- packages/server/src/db/utils.ts | 8 +-- packages/server/src/middleware/authorized.ts | 11 ++-- .../src/sdk/app/applications/applications.ts | 54 +++++++++++++++++++ .../server/src/sdk/app/applications/index.ts | 2 + .../shared-core/src/sdk/documents/users.ts | 16 +++++- 8 files changed, 143 insertions(+), 54 deletions(-) create mode 100644 packages/server/src/sdk/app/applications/applications.ts diff --git a/packages/backend-core/src/users.ts b/packages/backend-core/src/users.ts index 7224d827e8..05abe70fe3 100644 --- a/packages/backend-core/src/users.ts +++ b/packages/backend-core/src/users.ts @@ -2,6 +2,7 @@ import { directCouchFind, DocumentType, generateAppUserID, + getGlobalIDFromUserMetadataID, getGlobalUserParams, getProdAppID, getUsersByAppParams, @@ -21,6 +22,7 @@ import { import { sdk } from "@budibase/shared-core" import { getGlobalDB } from "./context" import * as context from "./context" +import { user as userCache } from "./cache" type GetOpts = { cleanup?: boolean } @@ -42,8 +44,10 @@ function removeUserPassword(users: User | User[]) { // extract from shared-core to make easily accessible from backend-core export const isBuilder = sdk.users.isBuilder export const isAdmin = sdk.users.isAdmin +export const isAdminOrBuilder = sdk.users.isAdminOrBuilder export const hasAdminPermissions = sdk.users.hasAdminPermissions export const hasBuilderPermissions = sdk.users.hasBuilderPermissions +export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions export const bulkGetGlobalUsersById = async ( userIds: string[], @@ -77,6 +81,27 @@ export const bulkUpdateGlobalUsers = async (users: User[]) => { return (await db.bulkDocs(users)) as BulkDocsResponse } +export const grantAppBuilderAccess = async (userId: string, appId: string) => { + const prodAppId = getProdAppID(appId) + const db = getGlobalDB() + const user = (await db.get(userId)) as User + if (!user.builder) { + user.builder = {} + } + if (!user.builder.apps) { + user.builder.apps = [] + } + if (!user.builder.apps.includes(prodAppId)) { + user.builder.apps.push(prodAppId) + } + try { + await db.put(user) + await userCache.invalidateUser(userId) + } catch (err: any) { + throw new Error(`Unable to grant user access: ${err.message}`) + } +} + export async function getById(id: string, opts?: GetOpts): Promise { const db = context.getGlobalDB() let user = await db.get(id) diff --git a/packages/builder/src/stores/portal/menu.js b/packages/builder/src/stores/portal/menu.js index c66c98d00f..2c17ce0b36 100644 --- a/packages/builder/src/stores/portal/menu.js +++ b/packages/builder/src/stores/portal/menu.js @@ -2,8 +2,12 @@ import { derived } from "svelte/store" import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" import { admin } from "./admin" import { auth } from "./auth" +import { sdk } from "@budibase/shared-core" export const menu = derived([admin, auth], ([$admin, $auth]) => { + const user = $auth?.user + const isAdmin = sdk.users.isAdmin(user) + const cloud = $admin?.cloud // Determine user sub pages let userSubPages = [ { @@ -24,19 +28,24 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { title: "Apps", href: "/builder/portal/apps", }, - { + ] + if ( + sdk.users.hasBuilderPermissions(user) && + !sdk.users.hasAppBuilderPermissions(user) + ) { + menu.push({ title: "Users", href: "/builder/portal/users", subPages: userSubPages, - }, - { - title: "Plugins", - href: "/builder/portal/plugins", - }, - ] + }) + } + menu.push({ + title: "Plugins", + href: "/builder/portal/plugins", + }) // Add settings page for admins - if ($auth.isAdmin) { + if (isAdmin) { let settingsSubPages = [ { title: "Auth", @@ -59,7 +68,7 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { href: "/builder/portal/settings/environment", }, ] - if (!$admin.cloud) { + if (!cloud) { settingsSubPages.push({ title: "Version", href: "/builder/portal/settings/version", @@ -84,38 +93,35 @@ export const menu = derived([admin, auth], ([$admin, $auth]) => { href: "/builder/portal/account/usage", }, ] - if ($auth.isAdmin) { + if (isAdmin) { accountSubPages.push({ title: "Audit Logs", href: "/builder/portal/account/auditLogs", }) - if (!$admin.cloud) { + if (!cloud) { accountSubPages.push({ title: "System Logs", href: "/builder/portal/account/systemLogs", }) } } - if ($admin.cloud && $auth?.user?.accountPortalAccess) { + if (cloud && user?.accountPortalAccess) { accountSubPages.push({ title: "Upgrade", - href: $admin.accountPortalUrl + "/portal/upgrade", + href: $admin?.accountPortalUrl + "/portal/upgrade", }) - } else if (!$admin.cloud && $auth.isAdmin) { + } else if (!cloud && isAdmin) { accountSubPages.push({ title: "Upgrade", href: "/builder/portal/account/upgrade", }) } // add license check here - if ( - $auth?.user?.accountPortalAccess && - $auth.user.account.stripeCustomerId - ) { + if (user?.accountPortalAccess && user.account.stripeCustomerId) { accountSubPages.push({ title: "Billing", - href: $admin.accountPortalUrl + "/portal/billing", + href: $admin?.accountPortalUrl + "/portal/billing", }) } menu.push({ diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 6a0088d4dc..9538790827 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -50,12 +50,13 @@ import { MigrationType, PlanType, Screen, - SocketSession, UserCtx, + ContextUser, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import sdk from "../../sdk" import { builderSocket } from "../../websockets" +import { grantAppBuilderAccess } from "@budibase/backend-core/src/users" // utility function, need to do away with this async function getLayouts() { @@ -178,32 +179,10 @@ export const addSampleData = async (ctx: UserCtx) => { } export async function fetch(ctx: UserCtx) { - const dev = ctx.query && ctx.query.status === AppStatus.DEV - const all = ctx.query && ctx.query.status === AppStatus.ALL - const apps = (await dbCore.getAllApps({ dev, all })) as App[] - - const appIds = apps - .filter((app: any) => app.status === "development") - .map((app: any) => app.appId) - - // get the locks for all the dev apps - if (dev || all) { - const locks = await getLocksById(appIds) - for (let app of apps) { - const lock = locks[app.appId] - if (lock) { - app.lockedBy = lock - } else { - // make sure its definitely not present - delete app.lockedBy - } - } - } - - // Enrich apps with all builder user sessions - const enrichedApps = await sdk.users.sessions.enrichApps(apps) - - ctx.body = await checkAppMetadata(enrichedApps) + ctx.body = await sdk.applications.fetch( + ctx.query.status as AppStatus, + ctx.user + ) } export async function fetchAppDefinition(ctx: UserCtx) { @@ -395,6 +374,10 @@ async function appPostCreate(ctx: UserCtx, app: App) { tenantId, appId: app.appId, }) + // they are an app builder, creating a new app, make sure they can access it + if (users.hasAppBuilderPermissions(ctx.user)) { + await users.grantAppBuilderAccess(ctx.user._id!, app.appId) + } await creationEvents(ctx.request, app) // app import & template creation if (ctx.request.body.useTemplate === "true") { diff --git a/packages/server/src/db/utils.ts b/packages/server/src/db/utils.ts index e08392c3a1..eb5cbc27ef 100644 --- a/packages/server/src/db/utils.ts +++ b/packages/server/src/db/utils.ts @@ -3,10 +3,10 @@ import { db as dbCore } from "@budibase/backend-core" type Optional = string | null -export const AppStatus = { - DEV: "development", - ALL: "all", - DEPLOYED: "published", +export enum AppStatus { + DEV = "development", + ALL = "all", + DEPLOYED = "published", } export const BudibaseInternalDB = { diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 81e42604bc..dba5d47cb9 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -5,7 +5,7 @@ import { context, users, } from "@budibase/backend-core" -import { Role } from "@budibase/types" +import { Role, UserCtx } from "@budibase/types" import builderMiddleware from "./builder" import { isWebhookEndpoint } from "./utils" @@ -22,14 +22,19 @@ const csrf = auth.buildCsrfMiddleware() * - Otherwise the user must have the required role. */ const checkAuthorized = async ( - ctx: any, + ctx: UserCtx, resourceRoles: any, permType: any, permLevel: any ) => { const appId = context.getAppId() // check if this is a builder api and the user is not a builder - const isBuilder = users.isBuilder(ctx.user, appId) + let isBuilder + if (!appId) { + isBuilder = users.hasBuilderPermissions(ctx.user) + } else { + isBuilder = users.isBuilder(ctx.user, appId) + } const isBuilderApi = permType === permissions.PermissionType.BUILDER if (isBuilderApi && !isBuilder) { return ctx.throw(403, "Not Authorized") diff --git a/packages/server/src/sdk/app/applications/applications.ts b/packages/server/src/sdk/app/applications/applications.ts new file mode 100644 index 0000000000..73cacb8983 --- /dev/null +++ b/packages/server/src/sdk/app/applications/applications.ts @@ -0,0 +1,54 @@ +import { AppStatus } from "../../../db/utils" +import { App, ContextUser } from "@budibase/types" +import { getLocksById } from "../../../utilities/redis" +import { enrichApps } from "../../users/sessions" +import { checkAppMetadata } from "../../../automations/logging" +import { db as dbCore, users } from "@budibase/backend-core" + +export function filterAppList(user: ContextUser, apps: App[]) { + let appList: string[] = [] + if (users.hasAppBuilderPermissions(user)) { + appList = user.builder?.apps! + } else if (!users.isAdminOrBuilder(user)) { + appList = Object.keys(user.roles || {}) + } else { + return apps + } + const finalApps: App[] = [] + for (let app of apps) { + if (appList.includes(dbCore.getProdAppID(app.appId))) { + finalApps.push(app) + } + } + return finalApps +} + +export async function fetch(status: AppStatus, user: ContextUser) { + const dev = status === AppStatus.DEV + const all = status === AppStatus.ALL + let apps = (await dbCore.getAllApps({ dev, all })) as App[] + apps = filterAppList(user, apps) + + const appIds = apps + .filter((app: any) => app.status === "development") + .map((app: any) => app.appId) + + // get the locks for all the dev apps + if (dev || all) { + const locks = await getLocksById(appIds) + for (let app of apps) { + const lock = locks[app.appId] + if (lock) { + app.lockedBy = lock + } else { + // make sure its definitely not present + delete app.lockedBy + } + } + } + + // Enrich apps with all builder user sessions + const enrichedApps = await enrichApps(apps) + + return await checkAppMetadata(enrichedApps) +} diff --git a/packages/server/src/sdk/app/applications/index.ts b/packages/server/src/sdk/app/applications/index.ts index d917225e52..963d065ce2 100644 --- a/packages/server/src/sdk/app/applications/index.ts +++ b/packages/server/src/sdk/app/applications/index.ts @@ -1,7 +1,9 @@ import * as sync from "./sync" import * as utils from "./utils" +import * as applications from "./applications" export default { ...sync, ...utils, + ...applications, } diff --git a/packages/shared-core/src/sdk/documents/users.ts b/packages/shared-core/src/sdk/documents/users.ts index 931f651a0e..1a9314f731 100644 --- a/packages/shared-core/src/sdk/documents/users.ts +++ b/packages/shared-core/src/sdk/documents/users.ts @@ -3,6 +3,9 @@ import { getProdAppID } from "./applications" // checks if a user is specifically a builder, given an app ID export function isBuilder(user: User | ContextUser, appId?: string) { + if (!user) { + return false + } if (user.builder?.global) { return true } else if (appId && user.builder?.apps?.includes(getProdAppID(appId))) { @@ -15,6 +18,9 @@ export function isBuilder(user: User | ContextUser, appId?: string) { // in future whether someone has admin permissions and whether they are // an admin for a specific resource could be separated export function isAdmin(user: User | ContextUser) { + if (!user) { + return false + } return hasAdminPermissions(user) } @@ -22,12 +28,20 @@ export function isAdminOrBuilder(user: User | ContextUser, appId?: string) { return isBuilder(user, appId) || isAdmin(user) } +// check if they are a builder within an app (not necessarily a global builder) +export function hasAppBuilderPermissions(user?: User | ContextUser) { + if (!user) { + return false + } + return !user.builder?.global && user.builder?.apps?.length !== 0 +} + // checks if a user is capable of building any app export function hasBuilderPermissions(user?: User | ContextUser) { if (!user) { return false } - return user.builder?.global || user.builder?.apps?.length !== 0 + return user.builder?.global || hasAppBuilderPermissions(user) } // checks if a user is capable of being an admin From 6aeb31c355bf359b9411e8414d2d7e2fd53a9414 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:44:48 +0100 Subject: [PATCH 107/821] Move OFFLINE_MODE to backend-core environment --- packages/backend-core/src/environment.ts | 1 + packages/server/src/environment.ts | 1 - packages/worker/src/api/controllers/system/environment.ts | 3 ++- packages/worker/src/environment.ts | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index eab8cd4c45..c0785ef419 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -156,6 +156,7 @@ const environment = { : false, VERSION: findVersion(), DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER, + OFFLINE_MODE: process.env.OFFLINE_MODE, _set(key: any, value: any) { process.env[key] = value // @ts-ignore diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 79ee5d977c..64b342d577 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -81,7 +81,6 @@ const environment = { SELF_HOSTED: process.env.SELF_HOSTED, HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT, FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", - OFFLINE_MODE: process.env.OFFLINE_MODE, // old CLIENT_ID: process.env.CLIENT_ID, _set(key: string, value: any) { diff --git a/packages/worker/src/api/controllers/system/environment.ts b/packages/worker/src/api/controllers/system/environment.ts index f3f917c2dd..729a00fa88 100644 --- a/packages/worker/src/api/controllers/system/environment.ts +++ b/packages/worker/src/api/controllers/system/environment.ts @@ -1,10 +1,11 @@ import { Ctx } from "@budibase/types" import env from "../../../environment" +import { env as coreEnv } from "@budibase/backend-core" export const fetch = async (ctx: Ctx) => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, - offlineMode: !!env.OFFLINE_MODE, + offlineMode: !!coreEnv.OFFLINE_MODE, cloud: !env.SELF_HOSTED, accountPortalUrl: env.ACCOUNT_PORTAL_URL, disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL, diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index 74d58831d9..678ffe7f14 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -61,7 +61,6 @@ const environment = { CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600, SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD, ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY, - OFFLINE_MODE: process.env.OFFLINE_MODE, /** * Mock the email service in use - links to ethereal hosted emails are logged instead. */ From 0e80766125b5e476d06748a49ec4d647961b0803 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:46:25 +0100 Subject: [PATCH 108/821] Update license endpoints to provide consistent pattern for offline license and license key (create, read, delete) --- packages/types/src/api/web/global/index.ts | 1 + packages/types/src/api/web/global/license.ts | 19 +++++ .../src/api/controllers/global/license.ts | 69 ++++++++++++++----- .../worker/src/api/routes/global/license.ts | 21 +++++- 4 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 packages/types/src/api/web/global/license.ts diff --git a/packages/types/src/api/web/global/index.ts b/packages/types/src/api/web/global/index.ts index bf4b43f0ba..efcb6dc39c 100644 --- a/packages/types/src/api/web/global/index.ts +++ b/packages/types/src/api/web/global/index.ts @@ -3,3 +3,4 @@ export * from "./auditLogs" export * from "./events" export * from "./configs" export * from "./scim" +export * from "./license" diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts new file mode 100644 index 0000000000..34de2e4361 --- /dev/null +++ b/packages/types/src/api/web/global/license.ts @@ -0,0 +1,19 @@ +// LICENSE KEY + +export interface ActivateLicenseKeyRequest { + licenseKey: string +} + +export interface GetLicenseKeyResponse { + licenseKey: string, +} + +// OFFLINE LICENSE + +export interface ActivateOfflineLicenseRequest { + offlineLicense: string +} + +export interface GetOfflineLicenseResponse { + offlineLicense: string +} diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 2bd173010f..b4670e0dbb 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,33 +1,66 @@ import { licensing, quotas } from "@budibase/pro" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseRequest, + GetLicenseKeyResponse, + GetOfflineLicenseResponse, + UserCtx, +} from "@budibase/types" -export const activate = async (ctx: any) => { +// LICENSE KEY + +export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - if (!licenseKey) { - ctx.throw(400, "licenseKey is required") - } - await licensing.activateLicenseKey(licenseKey) ctx.status = 200 } +export async function getLicenseKey(ctx: UserCtx) { + const licenseKey = await licensing.getLicenseKey() + if (licenseKey) { + ctx.body = { licenseKey: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteLicenseKey(ctx: UserCtx) { + await licensing.deleteLicenseKey() + ctx.status = 204 +} + +// OFFLINE LICENSE + +export async function activateOfflineLicense(ctx: UserCtx) { + const { offlineLicense } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicense) + ctx.status = 200 +} + +export async function getOfflineLicense(ctx: UserCtx) { + const offlineLicense = await licensing.getOfflineLicense() + if (offlineLicense) { + ctx.body = { offlineLicense: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteOfflineLicense(ctx: UserCtx) { + await licensing.deleteOfflineLicense() + ctx.status = 204 +} + +// LICENSES + export const refresh = async (ctx: any) => { await licensing.cache.refresh() ctx.status = 200 } -export const getInfo = async (ctx: any) => { - const licenseInfo = await licensing.getLicenseInfo() - if (licenseInfo) { - licenseInfo.licenseKey = "*" - ctx.body = licenseInfo - } - ctx.status = 200 -} - -export const deleteInfo = async (ctx: any) => { - await licensing.deleteLicenseInfo() - ctx.status = 200 -} +// USAGE export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 0fb2a6e8bd..1d6185a402 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -1,13 +1,28 @@ import Router from "@koa/router" import * as controller from "../../controllers/global/license" +import { middleware } from "@budibase/backend-core" +import Joi from "joi" + +const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ + licenseKey: Joi.string().required(), + }).required()) + +const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ + offlineLicense: Joi.string().required(), + }).required()) const router: Router = new Router() router - .post("/api/global/license/activate", controller.activate) .post("/api/global/license/refresh", controller.refresh) - .get("/api/global/license/info", controller.getInfo) - .delete("/api/global/license/info", controller.deleteInfo) .get("/api/global/license/usage", controller.getQuotaUsage) + // LICENSE KEY + .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .get("/api/global/license/key", controller.getLicenseKey) + .delete("/api/global/license/key", controller.deleteLicenseKey) + // OFFLINE LICENSE + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) + .get("/api/global/license/offline", controller.getOfflineLicense) + .delete("/api/global/license/offline", controller.deleteOfflineLicense) export default router From d3f9348403b64f8b2971bcfdb1c02bcbb24755f5 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:47:12 +0100 Subject: [PATCH 109/821] Updates to upgrade page to change config based on offlineMode value --- .../builder/portal/account/upgrade.svelte | 215 +++++++++++++----- packages/builder/src/stores/portal/admin.js | 1 + packages/frontend-core/src/api/licensing.js | 57 +++-- 3 files changed, 201 insertions(+), 72 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index f0ee87bde5..a37625a5dc 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -10,6 +10,8 @@ Label, ButtonGroup, notifications, + CopyInput, + File } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -21,44 +23,122 @@ $: license = $auth.user.license $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + // LICENSE KEY $: activateDisabled = !licenseKey || licenseKeyDisabled - - let licenseInfo - let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal + // OFFLINE LICENSE + let installationIdentifier = undefined + let offlineLicense = undefined + const offlineLicenseExtensions = [ + ".txt", + ] + // Make sure page can't be visited directly in cloud $: { if ($admin.cloud) { $redirect("../../portal") } + + console.log({ offlineLicense }) } - const activate = async () => { + // LICENSE KEY + + const getLicenseKey = async () => { try { - await API.activateLicenseKey({ licenseKey }) - await auth.getSelf() - await setLicenseInfo() - notifications.success("Successfully activated") + licenseKey = await API.getLicenseKey() + if (licenseKey) { + licenseKey = "**********************************************" + licenseKeyType = "password" + licenseKeyDisabled = true + activateDisabled = true + } } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error retrieving license key") } } - const destroy = async () => { + const activateLicenseKey = async () => { + try { + await API.activateLicenseKey({ licenseKey }) + await auth.getSelf() + await getLicenseKey() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating license key") + } + } + + const deleteLicenseKey = async () => { try { await API.deleteLicenseKey({ licenseKey }) await auth.getSelf() - await setLicenseInfo() + await getLicenseKey() // reset the form licenseKey = "" licenseKeyDisabled = false - notifications.success("Successfully deleted") + notifications.success("Offline license removed") } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error deleting license key") + } + } + + // OFFLINE LICENSE + + const getOfflineLicense = async () => { + try { + const license = await API.getOfflineLicense() + if (license) { + offlineLicense = { + name: "license" + } + } else { + offlineLicense = undefined + } + } catch (e) { + console.error(e) + notifications.error("Error loading offline license") + } + } + + async function activateOfflineLicense(offlineLicense) { + try { + await API.activateOfflineLicense({ offlineLicense }) + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating offline license") + } + } + + async function deleteOfflineLicense() { + try { + await API.deleteOfflineLicense() + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully removed ofline license") + } catch (e) { + console.error(e) + notifications.error("Error upload offline license") + } + } + + async function onOfflineLicenseChange(event) { + if (event.detail) { + const reader = new FileReader() + reader.readAsText(event.detail) + reader.onload = () => activateOfflineLicense(reader.result) + } else { + await deleteOfflineLicense() } } @@ -73,29 +153,19 @@ } } - // deactivate the license key field if there is a license key set - $: { - if (licenseInfo?.licenseKey) { - licenseKey = "**********************************************" - licenseKeyType = "password" - licenseKeyDisabled = true - activateDisabled = true - } - } - - const setLicenseInfo = async () => { - licenseInfo = await API.getLicenseInfo() - } - onMount(async () => { - await setLicenseInfo() + if ($admin.offlineMode) { + await getOfflineLicense() + } else { + await getLicenseKey() + } }) {#if $auth.isAdmin} @@ -112,38 +182,73 @@ - - Activate - Enter your license key below to activate your plan - - -
-
- - + Installation identifier + Share this with support@budibase.com to obtain your offline license + + +
+
+ +
+
+
+ + + License + Upload your license to activate your plan + + +
+
-
- - - {#if licenseInfo?.licenseKey} - - {/if} - - + {#if licenseKey} + + {/if} + + + {/if} - Plan - + Plan + You are currently on the {license.plan.type} plan +
+ If you purchase or update your plan on the account + portal, click the refresh button to sync those changes +
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index b9467fd037..19abcfd841 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, + offlineMode: false } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index c27d79d740..5f7813cfca 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,32 +1,56 @@ export const buildLicensingEndpoints = API => ({ - /** - * Activates a self hosted license key - */ + + // LICENSE KEY + activateLicenseKey: async data => { return API.post({ - url: `/api/global/license/activate`, + url: `/api/global/license/key`, body: data, }) }, - - /** - * Delete a self hosted license key - */ deleteLicenseKey: async () => { return API.delete({ - url: `/api/global/license/info`, + url: `/api/global/license/key`, }) }, + getLicenseKey: async () => { + try { + return await API.get({ + url: "/api/global/license/key", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, - /** - * Get the license info - metadata about the license including the - * obfuscated license key. - */ - getLicenseInfo: async () => { - return API.get({ - url: "/api/global/license/info", + // OFFLINE LICENSE + + activateOfflineLicense: async ({ offlineLicense }) => { + return API.post({ + url: "/api/global/license/offline", + body: { + offlineLicense + }, }) }, + deleteOfflineLicense: async () => { + return API.delete({ + url: "/api/global/license/offline", + }) + }, + getOfflineLicense: async () => { + try { + return await API.get({ + url: "/api/global/license/offline", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, /** * Refreshes the license cache @@ -36,7 +60,6 @@ export const buildLicensingEndpoints = API => ({ url: "/api/global/license/refresh", }) }, - /** * Retrieve the usage information for the tenant */ From 8e2a551a1560a26e897f3a96d028baf3e1d1efc1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:26:46 +0100 Subject: [PATCH 110/821] Be more explicit about offline license vs offline license token --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 34de2e4361..0196b69c7f 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -11,9 +11,9 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE export interface ActivateOfflineLicenseRequest { - offlineLicense: string + offlineLicenseToken: string } export interface GetOfflineLicenseResponse { - offlineLicense: string + offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b4670e0dbb..b33efa1491 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -33,15 +33,15 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE export async function activateOfflineLicense(ctx: UserCtx) { - const { offlineLicense } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicense) + const { offlineLicenseToken } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicense = await licensing.getOfflineLicense() - if (offlineLicense) { - ctx.body = { offlineLicense: "*" } + const offlineLicenseToken = await licensing.getOfflineLicenseToken() + if (offlineLicenseToken) { + ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 } else { ctx.status = 404 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicense() + await licensing.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d6185a402..889d7d1ed4 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -8,8 +8,8 @@ const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ }).required()) const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ - offlineLicense: Joi.string().required(), - }).required()) + offlineLicenseToken: Joi.string().required(), +}).required()) const router: Router = new Router() From ec5381c9d58859c0ac56ef9798281127d29d0510 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:58:20 +0100 Subject: [PATCH 111/821] Aesthetic UI updates --- .../builder/portal/account/upgrade.svelte | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index a37625a5dc..48ff3514b7 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -24,14 +24,16 @@ $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` // LICENSE KEY + $: activateDisabled = !licenseKey || licenseKeyDisabled let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal - // OFFLINE LICENSE - let installationIdentifier = undefined + // OFFLINE + + let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -108,9 +110,9 @@ } } - async function activateOfflineLicense(offlineLicense) { + async function activateOfflineLicense(offlineLicenseToken) { try { - await API.activateOfflineLicense({ offlineLicense }) + await API.activateOfflineLicense({ offlineLicenseToken }) await auth.getSelf() await getOfflineLicense() notifications.success("Successfully activated") @@ -134,10 +136,16 @@ async function onOfflineLicenseChange(event) { if (event.detail) { + // prevent file preview jitter by assigning constant + // as soon as possible + offlineLicense = { + name: "license" + } const reader = new FileReader() reader.readAsText(event.detail) reader.onload = () => activateOfflineLicense(reader.result) } else { + offlineLicense = undefined await deleteOfflineLicense() } } @@ -178,6 +186,7 @@ {:else} To manage your plan visit your account +
 
{/if}
@@ -270,7 +279,7 @@ } .field { display: grid; - grid-template-columns: 100px 1fr; + grid-template-columns: 300px 1fr; grid-gap: var(--spacing-l); align-items: center; } From 7bc14235aabff2e0bd172a9ed7cfa0cd769294e0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 22:00:13 +0100 Subject: [PATCH 112/821] Update request body for offline license activation --- packages/frontend-core/src/api/licensing.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 5f7813cfca..cacf972971 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -27,11 +27,11 @@ export const buildLicensingEndpoints = API => ({ // OFFLINE LICENSE - activateOfflineLicense: async ({ offlineLicense }) => { + activateOfflineLicense: async ({ offlineLicenseToken }) => { return API.post({ url: "/api/global/license/offline", body: { - offlineLicense + offlineLicenseToken }, }) }, From 7c18a7a443b83dba4dc31b50fb6d57cd317b236f Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:34:10 +0100 Subject: [PATCH 113/821] db / licenseInfo.spec.ts --- packages/worker/src/api/routes/global/tests/license.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index be0673729e..d548ad83bb 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -21,9 +21,6 @@ describe("/api/global/license", () => { describe("POST /api/global/license/refresh", () => {}) - describe("GET /api/global/license/info", () => {}) - - describe("DELETE /api/global/license/info", () => {}) describe("GET /api/global/license/usage", () => {}) }) From 6c3d01375b91b75de2beea8c36343d551a88816d Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:48:12 +0100 Subject: [PATCH 114/821] Move license keys to their own module --- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b33efa1491..9310eb3739 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -11,12 +11,12 @@ import { export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - await licensing.activateLicenseKey(licenseKey) + await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 } export async function getLicenseKey(ctx: UserCtx) { - const licenseKey = await licensing.getLicenseKey() + const licenseKey = await licensing.keys.getLicenseKey() if (licenseKey) { ctx.body = { licenseKey: "*" } ctx.status = 200 @@ -26,7 +26,7 @@ export async function getLicenseKey(ctx: UserCtx) { } export async function deleteLicenseKey(ctx: UserCtx) { - await licensing.deleteLicenseKey() + await licensing.keys.deleteLicenseKey() ctx.status = 204 } @@ -34,12 +34,12 @@ export async function deleteLicenseKey(ctx: UserCtx) { export async function activateOfflineLicense(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicenseToken = await licensing.getOfflineLicenseToken() + const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicenseToken() + await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } From 20c87b44b1479deaec0fe817acac6e4cea224818 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 16:55:11 +0100 Subject: [PATCH 115/821] Allow pro to be mocked in worker --- packages/worker/__mocks__/@budibase/pro.ts | 26 +++++++++++++++++++ .../worker/src/tests/TestConfiguration.ts | 11 ++++---- packages/worker/src/tests/mocks/index.ts | 4 +++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/worker/__mocks__/@budibase/pro.ts diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts new file mode 100644 index 0000000000..a9611ba705 --- /dev/null +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -0,0 +1,26 @@ +const actual = jest.requireActual("@budibase/pro") +const pro = { + ...actual, + licensing: { + keys: { + activateLicenseKey: jest.fn(), + getLicenseKey: jest.fn(), + deleteLicenseKey: jest.fn(), + }, + offline: { + activateOfflineLicense: jest.fn(), + getOfflineLicenseToken: jest.fn(), + deleteOfflineLicenseToken: jest.fn(), + }, + cache: { + ...actual.licensing.cache, + refresh: jest.fn(), + } + }, + quotas: { + ...actual.quotas, + getQuotaUsage: jest.fn() + }, +} + +export = pro diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index a79ac0e189..b41b76efda 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -1,8 +1,7 @@ import mocks from "./mocks" // init the licensing mock -import * as pro from "@budibase/pro" -mocks.licenses.init(pro) +mocks.licenses.init(mocks.pro) // use unlimited license by default mocks.licenses.useUnlimited() @@ -238,21 +237,21 @@ class TestConfiguration { const db = context.getGlobalDB() - const id = dbCore.generateDevInfoID(this.user._id) + const id = dbCore.generateDevInfoID(this.user!._id) // TODO: dry this.apiKey = encryption.encrypt( `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}` ) const devInfo = { _id: id, - userId: this.user._id, + userId: this.user!._id, apiKey: this.apiKey, } await db.put(devInfo) }) } - async getUser(email: string): Promise { + async getUser(email: string): Promise { return context.doInTenant(this.getTenantId(), () => { return users.getGlobalUserByEmail(email) }) @@ -264,7 +263,7 @@ class TestConfiguration { } const response = await this._req(user, null, controllers.users.save) const body = response as SaveUserResponse - return this.getUser(body.email) + return this.getUser(body.email) as Promise } // CONFIGS diff --git a/packages/worker/src/tests/mocks/index.ts b/packages/worker/src/tests/mocks/index.ts index 30bb4e1d09..cab019bb46 100644 --- a/packages/worker/src/tests/mocks/index.ts +++ b/packages/worker/src/tests/mocks/index.ts @@ -1,7 +1,11 @@ import * as email from "./email" import { mocks } from "@budibase/backend-core/tests" +import * as _pro from "@budibase/pro" +const pro = jest.mocked(_pro, true) + export default { email, + pro, ...mocks, } From c0568b9153ca16b29be00de6d4e76519d92edbd6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 116/821] api / license.spec.ts updates --- .../src/api/controllers/global/license.ts | 1 + .../api/routes/global/tests/license.spec.ts | 85 +++++++++++++++++-- packages/worker/src/tests/api/license.ts | 43 +++++++++- 3 files changed, 120 insertions(+), 9 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 9310eb3739..b8c8566018 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -64,4 +64,5 @@ export const refresh = async (ctx: any) => { export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() + ctx.status = 200 } diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index d548ad83bb..c3c423c833 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,4 +1,6 @@ -import { TestConfiguration } from "../../../../tests" +import { TestConfiguration, mocks, structures } from "../../../../tests" +const licensing = mocks.pro.licensing +const quotas = mocks.pro.quotas describe("/api/global/license", () => { const config = new TestConfiguration() @@ -12,15 +14,86 @@ describe("/api/global/license", () => { }) afterEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() }) - describe("POST /api/global/license/activate", () => { - it("activates license", () => {}) + describe("POST /api/global/license/refresh", () => { + it("returns 200", async () => { + const res = await config.api.license.refresh() + expect(res.status).toBe(200) + expect(licensing.cache.refresh).toBeCalledTimes(1) + }) }) - describe("POST /api/global/license/refresh", () => {}) + describe("GET /api/global/license/usage", () => { + it("returns 200", async () => { + const usage = structures.quotas.usage() + quotas.getQuotaUsage.mockResolvedValue(usage) + const res = await config.api.license.getUsage() + expect(res.status).toBe(200) + expect(res.body).toEqual(usage) + }) + }) + describe("POST /api/global/license/key", () => { + it("returns 200", async () => { + const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + expect(res.status).toBe(200) + expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") + }) + }) - describe("GET /api/global/license/usage", () => {}) + describe("GET /api/global/license/key", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(404) + }) + it("returns 200 + license key", async () => { + licensing.keys.getLicenseKey.mockResolvedValue("licenseKey") + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + licenseKey: "*" + }) + }) + }) + + describe("DELETE /api/global/license/key", () => { + it("returns 204", async () => { + const res = await config.api.license.deleteLicenseKey() + expect(licensing.keys.deleteLicenseKey).toBeCalledTimes(1) + expect(res.status).toBe(204) + }) + }) + + describe("POST /api/global/license/offline", () => { + it("activates offline license", async () => { + const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) + expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + expect(res.status).toBe(200) + }) + }) + + describe("GET /api/global/license/offline", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(404) + }) + it("returns 200", async () => { + licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + offlineLicenseToken: "*" + }) + }) + }) + + describe("DELETE /api/global/license/offline", () => { + it("deletes offline license", async () => { + const res = await config.api.license.deleteOfflineLicense() + expect(res.status).toBe(204) + expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 9d7745a80e..89f85b25e3 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,17 +1,54 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" +import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { super(config) } - activate = async (licenseKey: string) => { + refresh = async () => { return this.request - .post("/api/global/license/activate") - .send({ licenseKey: licenseKey }) + .post("/api/global/license/refresh") + .set(this.config.defaultHeaders()) + } + getUsage = async () => { + return this.request + .get("/api/global/license/usage") .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) } + activateLicenseKey = async (body: ActivateLicenseKeyRequest) => { + return this.request + .post("/api/global/license/key") + .send(body) + .set(this.config.defaultHeaders()) + } + getLicenseKey = async () => { + return this.request + .get("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + deleteLicenseKey = async () => { + return this.request + .delete("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + return this.request + .post("/api/global/license/offline") + .send(body) + .set(this.config.defaultHeaders()) + } + getOfflineLicense = async () => { + return this.request + .get("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } + deleteOfflineLicense = async () => { + return this.request + .delete("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } } From 9e1e869949d103c75056b77adbca923756f786ed Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 117/821] api / license.spec.ts updates --- packages/backend-core/src/events/identification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 5eb11d1354..948d3b692b 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -264,7 +264,7 @@ const getEventTenantId = async (tenantId: string): Promise => { } } -const getUniqueTenantId = async (tenantId: string): Promise => { +export const getUniqueTenantId = async (tenantId: string): Promise => { // make sure this tenantId always matches the tenantId in context return context.doInTenant(tenantId, () => { return withCache(CacheKey.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { From 90e869dc045abcf69852693a60a96b370f91e51b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:07:10 +0100 Subject: [PATCH 118/821] /api/global/license/offline/identifier API --- packages/types/src/api/web/global/license.ts | 6 ++++++ packages/types/src/sdk/licensing/license.ts | 10 ++++++++++ packages/worker/__mocks__/@budibase/pro.ts | 1 + .../src/api/controllers/global/license.ts | 7 +++++++ .../worker/src/api/routes/global/license.ts | 1 + .../src/api/routes/global/tests/license.spec.ts | 17 ++++++++++++++--- packages/worker/src/tests/api/license.ts | 5 +++++ 7 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 0196b69c7f..4a36c4f1d8 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -17,3 +17,9 @@ export interface ActivateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string } + +// IDENTIFIER + +export interface GetOfflineIdentifierResponse { + identifierBase64: string +} diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index b287e67adf..ebc874bca0 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,5 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +export interface OfflineIdentifier { + installId: string, + tenantId: string +} + +// export interface OfflineLicense extends License { +// identifier?: OfflineIdentifier +// identifierBase64: string +// } + export interface License { features: Feature[] quotas: Quotas diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index a9611ba705..bd6250fede 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,6 +11,7 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), + getIdentifierBase64: jest.fn() }, cache: { ...actual.licensing.cache, diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b8c8566018..73b3c72d1e 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -3,6 +3,7 @@ import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest, GetLicenseKeyResponse, + GetOfflineIdentifierResponse, GetOfflineLicenseResponse, UserCtx, } from "@budibase/types" @@ -53,6 +54,12 @@ export async function deleteOfflineLicense(ctx: UserCtx) { ctx.status = 204 } +export async function getOfflineLicenseIdentifier(ctx: UserCtx) { + const identifierBase64 = await licensing.offline.getIdentifierBase64() + ctx.body = { identifierBase64 } + ctx.status = 200 +} + // LICENSES export const refresh = async (ctx: any) => { diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 889d7d1ed4..1d3d9c460b 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -24,5 +24,6 @@ router .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) .get("/api/global/license/offline", controller.getOfflineLicense) .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index c3c423c833..512d6c9c52 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -26,7 +26,7 @@ describe("/api/global/license", () => { }) describe("GET /api/global/license/usage", () => { - it("returns 200", async () => { + it("returns 200 + usage", async () => { const usage = structures.quotas.usage() quotas.getQuotaUsage.mockResolvedValue(usage) const res = await config.api.license.getUsage() @@ -79,7 +79,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(404) }) - it("returns 200", async () => { + it("returns 200 + offline license token", async () => { licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) @@ -90,10 +90,21 @@ describe("/api/global/license", () => { }) describe("DELETE /api/global/license/offline", () => { - it("deletes offline license", async () => { + it("returns 204", async () => { const res = await config.api.license.deleteOfflineLicense() expect(res.status).toBe(204) expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) }) }) + + describe("GET /api/global/license/offline/identifier", () => { + it("returns 200 + identifier base64", async () => { + licensing.offline.getIdentifierBase64.mockResolvedValue("base64") + const res = await config.api.license.getOfflineLicenseIdentifier() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + identifierBase64: "base64" + }) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 89f85b25e3..76d3e88df7 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -51,4 +51,9 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/offline") .set(this.config.defaultHeaders()) } + getOfflineLicenseIdentifier = async () => { + return this.request + .get("/api/global/license/offline/identifier") + .set(this.config.defaultHeaders()) + } } From c69fa2a6408d6529525be77f9f1a5942f76b0ab3 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:08:51 +0100 Subject: [PATCH 120/821] Integrate UI with identifier API --- .../builder/portal/account/upgrade.svelte | 18 +++++++++++++----- packages/frontend-core/src/api/licensing.js | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 48ff3514b7..8e2fd5c641 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -33,7 +33,7 @@ // OFFLINE - let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" + let offlineLicenseIdentifier = "" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -44,8 +44,6 @@ if ($admin.cloud) { $redirect("../../portal") } - - console.log({ offlineLicense }) } // LICENSE KEY @@ -110,6 +108,16 @@ } } + const getOfflineLicenseIdentifier = async () => { + try { + const res = await API.getOfflineLicenseIdentifier() + offlineLicenseIdentifier = res.identifierBase64 + } catch (e) { + console.error(e) + notifications.error("Error loading installation identifier") + } + } + async function activateOfflineLicense(offlineLicenseToken) { try { await API.activateOfflineLicense({ offlineLicenseToken }) @@ -163,7 +171,7 @@ onMount(async () => { if ($admin.offlineMode) { - await getOfflineLicense() + await Promise.all([getOfflineLicense(), getOfflineLicenseIdentifier()]) } else { await getLicenseKey() } @@ -199,7 +207,7 @@
- +
diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index cacf972971..9fe98d5e74 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -51,6 +51,11 @@ export const buildLicensingEndpoints = API => ({ } } }, + getOfflineLicenseIdentifier: async () => { + return await API.get({ + url: "/api/global/license/offline/identifier", + }) + }, /** * Refreshes the license cache From 91d3cdff3fe8466e9ad6d2e911971f3006513573 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:48:52 +0100 Subject: [PATCH 121/821] offline license sdk module --- packages/backend-core/src/errors/errors.ts | 12 ++++++++++++ packages/types/src/documents/account/account.ts | 1 + packages/types/src/sdk/licensing/feature.ts | 1 + packages/types/src/sdk/licensing/license.ts | 9 +++++---- packages/types/src/shared/typeUtils.ts | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/errors/errors.ts b/packages/backend-core/src/errors/errors.ts index 4e1f1abbb5..7d55d25e89 100644 --- a/packages/backend-core/src/errors/errors.ts +++ b/packages/backend-core/src/errors/errors.ts @@ -55,6 +55,18 @@ export class HTTPError extends BudibaseError { } } +export class NotFoundError extends HTTPError { + constructor(message: string) { + super(message, 404) + } +} + +export class BadRequestError extends HTTPError { + constructor(message: string) { + super(message, 400) + } +} + // LICENSING export class UsageLimitError extends HTTPError { diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts index dad8abed30..5321aa7e08 100644 --- a/packages/types/src/documents/account/account.ts +++ b/packages/types/src/documents/account/account.ts @@ -51,6 +51,7 @@ export interface Account extends CreateAccount { licenseRequestedAt?: number licenseOverrides?: LicenseOverrides quotaUsage?: QuotaUsage + offlineLicenseToken?: string } export interface PasswordAccount extends Account { diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts index 1cbdd55bcf..f286a1cc44 100644 --- a/packages/types/src/sdk/licensing/feature.ts +++ b/packages/types/src/sdk/licensing/feature.ts @@ -9,6 +9,7 @@ export enum Feature { BRANDING = "branding", SCIM = "scim", SYNC_AUTOMATIONS = "syncAutomations", + OFFLINE = "offline", } export type PlanFeatures = { [key in PlanType]: Feature[] | undefined } diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index ebc874bca0..e8ad98fe7a 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,14 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +import { ISO8601 } from "../../shared" export interface OfflineIdentifier { installId: string, tenantId: string } -// export interface OfflineLicense extends License { -// identifier?: OfflineIdentifier -// identifierBase64: string -// } +export interface OfflineLicense extends License { + identifier: OfflineIdentifier + expireAt: ISO8601 +} export interface License { features: Feature[] diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 71fadfc7aa..143865c60b 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -1,3 +1,5 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } + +export type ISO8601 = string \ No newline at end of file From b8273540e0ab44f94dc3de4a1ac3f2c4db49d9d6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:49:45 +0100 Subject: [PATCH 122/821] /api/internal/accounts/:accountId/license/offline --- packages/types/src/api/account/license.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index a867358559..e23f6cab97 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -1,5 +1,6 @@ import { LicenseOverrides, QuotaUsage } from "../../documents" -import { PlanType } from "../../sdk" +import { OfflineLicense, PlanType } from "../../sdk" +import { ISO8601 } from "../../shared" export interface GetLicenseRequest { // All fields should be optional to cater for @@ -26,3 +27,13 @@ export interface UpdateLicenseRequest { planType?: PlanType overrides?: LicenseOverrides } + +export interface CreateOfflineLicenseRequest { + installationIdentifierBase64: string + expireAt: ISO8601 +} + +export interface GetOfflineLicenseResponse { + offlineLicenseToken: string + license: OfflineLicense +} \ No newline at end of file From 34d9f1c4f8640803bec031aaa112f8e8f17221b6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 16:12:19 +0100 Subject: [PATCH 123/821] Request / response renames --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 4a36c4f1d8..900a0db96d 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -10,11 +10,11 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE -export interface ActivateOfflineLicenseRequest { +export interface ActivateOfflineLicenseTokenRequest { offlineLicenseToken: string } -export interface GetOfflineLicenseResponse { +export interface GetOfflineLicenseTokenResponse { offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 73b3c72d1e..fa95eeee0d 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,10 +1,10 @@ import { licensing, quotas } from "@budibase/pro" import { ActivateLicenseKeyRequest, - ActivateOfflineLicenseRequest, + ActivateOfflineLicenseTokenRequest, GetLicenseKeyResponse, GetOfflineIdentifierResponse, - GetOfflineLicenseResponse, + GetOfflineLicenseTokenResponse, UserCtx, } from "@budibase/types" @@ -33,13 +33,13 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicense(ctx: UserCtx) { +export async function activateOfflineLicenseToken(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.offline.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicense(ctx: UserCtx) { +export async function getOfflineLicenseToken(ctx: UserCtx) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { +export async function deleteOfflineLicenseToken(ctx: UserCtx) { await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d3d9c460b..199dbb3846 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -21,9 +21,9 @@ router .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) - .get("/api/global/license/offline", controller.getOfflineLicense) - .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .get("/api/global/license/offline", controller.getOfflineLicenseToken) + .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router From a74469b8ae3c1c7edb71c01b00a3eda113e808d4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 21:53:05 +0100 Subject: [PATCH 124/821] offline license structure --- .../core/utilities/structures/licenses.ts | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 22e73f2871..35c9156ec6 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,7 @@ import { Billing, Customer, Feature, - License, + License, OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -11,6 +11,7 @@ import { Quotas, Subscription, } from "@budibase/types" +import { generator } from "./generator" export function price(): PurchasedPrice { return { @@ -127,14 +128,16 @@ export function subscription(): Subscription { } } +interface GenerateLicenseOpts { + quotas?: Quotas + plan?: PurchasedPlan + planType?: PlanType + features?: Feature[] + billing?: Billing +} + export const license = ( - opts: { - quotas?: Quotas - plan?: PurchasedPlan - planType?: PlanType - features?: Feature[] - billing?: Billing - } = {} + opts: GenerateLicenseOpts = {} ): License => { return { features: opts.features || [], @@ -143,3 +146,17 @@ export const license = ( billing: opts.billing || billing(), } } + +export function offlineLicense ( + opts: GenerateLicenseOpts = {} +): OfflineLicense { + const base = license(opts) + return { + ...base, + expireAt: new Date().toISOString(), + identifier: { + installId: generator.guid(), + tenantId: generator.guid() + } + } +} \ No newline at end of file From 166156d3b5d51568d07dd8783eb9cb58d6915898 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 22:06:52 +0100 Subject: [PATCH 125/821] use automocking in offline.spec.ts --- .../backend-core/tests/core/utilities/structures/accounts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 807153cd09..42e9d4b63a 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -13,7 +13,7 @@ import { } from "@budibase/types" import _ from "lodash" -export const account = (): Account => { +export const account = (partial: Partial = {}): Account => { return { accountId: uuid(), tenantId: generator.word(), @@ -29,6 +29,7 @@ export const account = (): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), + ...partial } } From 8cdfc8dd8319a07b767afd16301b0980d486e57d Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 09:21:23 +0100 Subject: [PATCH 126/821] Add structures for Installation type --- .../tests/core/utilities/structures/index.ts | 1 + .../tests/core/utilities/structures/installation.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 packages/backend-core/tests/core/utilities/structures/installation.ts diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index 2c094f43a7..c4404856e1 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,6 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" +export * as installation from "./installation" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/installation.ts new file mode 100644 index 0000000000..bd2ae4abfe --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/installation.ts @@ -0,0 +1,10 @@ +import { generator } from "@budibase/backend-core/tests" +import { Installation } from "@budibase/types" + +export function install(): Installation { + return { + _id: "install", + installId: generator.guid(), + version: generator.string() + } +} \ No newline at end of file From 1964148581c446aee45adf5eb75a22ec990f9918 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 11:44:05 +0100 Subject: [PATCH 127/821] core structure updates --- .../tests/core/utilities/structures/db.ts | 6 +++--- .../utilities/structures/documents/index.ts | 1 + .../structures/documents/platform/index.ts | 1 + .../{ => documents/platform}/installation.ts | 2 ++ .../tests/core/utilities/structures/index.ts | 2 +- .../core/utilities/structures/licenses.ts | 19 ++++++++++++++----- 6 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/index.ts create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts rename packages/backend-core/tests/core/utilities/structures/{ => documents/platform}/installation.ts (82%) diff --git a/packages/backend-core/tests/core/utilities/structures/db.ts b/packages/backend-core/tests/core/utilities/structures/db.ts index 31a52dce8b..87325573eb 100644 --- a/packages/backend-core/tests/core/utilities/structures/db.ts +++ b/packages/backend-core/tests/core/utilities/structures/db.ts @@ -1,4 +1,4 @@ -import { structures } from ".." +import { generator } from "./generator" import { newid } from "../../../../src/docIds/newid" export function id() { @@ -6,7 +6,7 @@ export function id() { } export function rev() { - return `${structures.generator.character({ + return `${generator.character({ numeric: true, - })}-${structures.uuid().replace(/-/, "")}` + })}-${generator.guid().replace(/-/, "")}` } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts new file mode 100644 index 0000000000..1c82c5b7d4 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -0,0 +1 @@ +export * from "./platform" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts new file mode 100644 index 0000000000..46b85f0435 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -0,0 +1 @@ +export * as installation from "./installation" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts similarity index 82% rename from packages/backend-core/tests/core/utilities/structures/installation.ts rename to packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index bd2ae4abfe..30d58fd349 100644 --- a/packages/backend-core/tests/core/utilities/structures/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,9 +1,11 @@ import { generator } from "@budibase/backend-core/tests" import { Installation } from "@budibase/types" +import * as db from "../../db" export function install(): Installation { return { _id: "install", + _rev: db.rev(), installId: generator.guid(), version: generator.string() } diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index c4404856e1..1a49e912fc 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,7 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" -export * as installation from "./installation" +export * as docs from "./documents" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 35c9156ec6..fae0c7d807 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,9 @@ import { Billing, Customer, Feature, - License, OfflineLicense, + License, + OfflineIdentifier, + OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -154,9 +156,16 @@ export function offlineLicense ( return { ...base, expireAt: new Date().toISOString(), - identifier: { - installId: generator.guid(), - tenantId: generator.guid() - } + identifier: offlineIdentifier() + } +} + +export function offlineIdentifier( + installId: string = generator.guid(), + tenantId: string = generator.guid(), +): OfflineIdentifier { + return { + installId, + tenantId } } \ No newline at end of file From c8fc67d230dcf4e7c322ba8730665d66f78209b7 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 16:55:48 +0100 Subject: [PATCH 128/821] Lint --- .../core/utilities/structures/accounts.ts | 2 +- .../utilities/structures/documents/index.ts | 2 +- .../structures/documents/platform/index.ts | 2 +- .../documents/platform/installation.ts | 4 +-- .../core/utilities/structures/licenses.ts | 16 ++++------ .../builder/portal/account/upgrade.svelte | 22 ++++++++------ packages/builder/src/stores/portal/admin.js | 2 +- packages/frontend-core/src/api/licensing.js | 3 +- packages/pro | 2 +- packages/types/src/api/account/license.ts | 2 +- packages/types/src/api/web/global/license.ts | 2 +- packages/types/src/sdk/licensing/license.ts | 2 +- packages/types/src/shared/typeUtils.ts | 2 +- packages/worker/__mocks__/@budibase/pro.ts | 6 ++-- .../src/api/controllers/global/license.ts | 16 +++++++--- .../worker/src/api/routes/global/license.ts | 29 ++++++++++++++----- .../api/routes/global/tests/license.spec.ts | 22 +++++++++----- packages/worker/src/tests/api/license.ts | 7 +++-- 18 files changed, 88 insertions(+), 55 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 42e9d4b63a..8476399aa3 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -29,7 +29,7 @@ export const account = (partial: Partial = {}): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), - ...partial + ...partial, } } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts index 1c82c5b7d4..c3bfba3597 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -1 +1 @@ -export * from "./platform" \ No newline at end of file +export * from "./platform" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts index 46b85f0435..98a6314999 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -1 +1 @@ -export * as installation from "./installation" \ No newline at end of file +export * as installation from "./installation" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 30d58fd349..25c5e50e00 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -7,6 +7,6 @@ export function install(): Installation { _id: "install", _rev: db.rev(), installId: generator.guid(), - version: generator.string() + version: generator.string(), } -} \ No newline at end of file +} diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index fae0c7d807..5cce84edfd 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -138,9 +138,7 @@ interface GenerateLicenseOpts { billing?: Billing } -export const license = ( - opts: GenerateLicenseOpts = {} -): License => { +export const license = (opts: GenerateLicenseOpts = {}): License => { return { features: opts.features || [], quotas: opts.quotas || quotas(), @@ -149,23 +147,21 @@ export const license = ( } } -export function offlineLicense ( - opts: GenerateLicenseOpts = {} -): OfflineLicense { +export function offlineLicense(opts: GenerateLicenseOpts = {}): OfflineLicense { const base = license(opts) return { ...base, expireAt: new Date().toISOString(), - identifier: offlineIdentifier() + identifier: offlineIdentifier(), } } export function offlineIdentifier( installId: string = generator.guid(), - tenantId: string = generator.guid(), + tenantId: string = generator.guid() ): OfflineIdentifier { return { installId, - tenantId + tenantId, } -} \ No newline at end of file +} diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 8e2fd5c641..8f76286c8e 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -11,7 +11,7 @@ ButtonGroup, notifications, CopyInput, - File + File, } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -35,9 +35,7 @@ let offlineLicenseIdentifier = "" let offlineLicense = undefined - const offlineLicenseExtensions = [ - ".txt", - ] + const offlineLicenseExtensions = [".txt"] // Make sure page can't be visited directly in cloud $: { @@ -97,7 +95,7 @@ const license = await API.getOfflineLicense() if (license) { offlineLicense = { - name: "license" + name: "license", } } else { offlineLicense = undefined @@ -147,7 +145,7 @@ // prevent file preview jitter by assigning constant // as soon as possible offlineLicense = { - name: "license" + name: "license", } const reader = new FileReader() reader.readAsText(event.detail) @@ -202,7 +200,9 @@ {#if $admin.offlineMode} Installation identifier - Share this with support@budibase.com to obtain your offline license + Share this with support@budibase.com to obtain your offline license
@@ -263,8 +263,12 @@ You are currently on the {license.plan.type} plan
- If you purchase or update your plan on the account - portal, click the refresh button to sync those changes + If you purchase or update your plan on the account + portal, click the refresh button to sync those changes
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 19abcfd841..2106acac27 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,7 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, - offlineMode: false + offlineMode: false, } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 9fe98d5e74..987fc34cf5 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,5 +1,4 @@ export const buildLicensingEndpoints = API => ({ - // LICENSE KEY activateLicenseKey: async data => { @@ -31,7 +30,7 @@ export const buildLicensingEndpoints = API => ({ return API.post({ url: "/api/global/license/offline", body: { - offlineLicenseToken + offlineLicenseToken, }, }) }, diff --git a/packages/pro b/packages/pro index 1a5207d91f..b5124e76b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1a5207d91fb9e0835562c708dd9c421973026543 +Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index e23f6cab97..edb1267ecf 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -36,4 +36,4 @@ export interface CreateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string license: OfflineLicense -} \ No newline at end of file +} diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 900a0db96d..21d8876412 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -5,7 +5,7 @@ export interface ActivateLicenseKeyRequest { } export interface GetLicenseKeyResponse { - licenseKey: string, + licenseKey: string } // OFFLINE LICENSE diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index e8ad98fe7a..105c3680a3 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -2,7 +2,7 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." import { ISO8601 } from "../../shared" export interface OfflineIdentifier { - installId: string, + installId: string tenantId: string } diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 143865c60b..fbe215fdb9 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -2,4 +2,4 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } -export type ISO8601 = string \ No newline at end of file +export type ISO8601 = string diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index bd6250fede..f1e79bd7c7 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,16 +11,16 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), - getIdentifierBase64: jest.fn() + getIdentifierBase64: jest.fn(), }, cache: { ...actual.licensing.cache, refresh: jest.fn(), - } + }, }, quotas: { ...actual.quotas, - getQuotaUsage: jest.fn() + getQuotaUsage: jest.fn(), }, } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index fa95eeee0d..111cb5cea3 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -10,7 +10,9 @@ import { // LICENSE KEY -export async function activateLicenseKey(ctx: UserCtx) { +export async function activateLicenseKey( + ctx: UserCtx +) { const { licenseKey } = ctx.request.body await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 @@ -33,13 +35,17 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicenseToken(ctx: UserCtx) { +export async function activateOfflineLicenseToken( + ctx: UserCtx +) { const { offlineLicenseToken } = ctx.request.body await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicenseToken(ctx: UserCtx) { +export async function getOfflineLicenseToken( + ctx: UserCtx +) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -54,7 +60,9 @@ export async function deleteOfflineLicenseToken(ctx: UserCtx) { ctx.status = 204 } -export async function getOfflineLicenseIdentifier(ctx: UserCtx) { +export async function getOfflineLicenseIdentifier( + ctx: UserCtx +) { const identifierBase64 = await licensing.offline.getIdentifierBase64() ctx.body = { identifierBase64 } ctx.status = 200 diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 199dbb3846..b0e474e4d5 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -3,13 +3,17 @@ import * as controller from "../../controllers/global/license" import { middleware } from "@budibase/backend-core" import Joi from "joi" -const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ +const activateLicenseKeyValidator = middleware.joiValidator.body( + Joi.object({ licenseKey: Joi.string().required(), - }).required()) + }).required() +) -const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ +const activateOfflineLicenseValidator = middleware.joiValidator.body( + Joi.object({ offlineLicenseToken: Joi.string().required(), -}).required()) + }).required() +) const router: Router = new Router() @@ -17,13 +21,24 @@ router .post("/api/global/license/refresh", controller.refresh) .get("/api/global/license/usage", controller.getQuotaUsage) // LICENSE KEY - .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .post( + "/api/global/license/key", + activateLicenseKeyValidator, + controller.activateLicenseKey + ) .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .post( + "/api/global/license/offline", + activateOfflineLicenseValidator, + controller.activateOfflineLicenseToken + ) .get("/api/global/license/offline", controller.getOfflineLicenseToken) .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) - .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) + .get( + "/api/global/license/offline/identifier", + controller.getOfflineLicenseIdentifier + ) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index 512d6c9c52..26e3b3dfb5 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -37,7 +37,9 @@ describe("/api/global/license", () => { describe("POST /api/global/license/key", () => { it("returns 200", async () => { - const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + const res = await config.api.license.activateLicenseKey({ + licenseKey: "licenseKey", + }) expect(res.status).toBe(200) expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") }) @@ -53,7 +55,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getLicenseKey() expect(res.status).toBe(200) expect(res.body).toEqual({ - licenseKey: "*" + licenseKey: "*", }) }) }) @@ -68,8 +70,12 @@ describe("/api/global/license", () => { describe("POST /api/global/license/offline", () => { it("activates offline license", async () => { - const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) - expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + const res = await config.api.license.activateOfflineLicense({ + offlineLicenseToken: "offlineLicenseToken", + }) + expect(licensing.offline.activateOfflineLicenseToken).toBeCalledWith( + "offlineLicenseToken" + ) expect(res.status).toBe(200) }) }) @@ -80,11 +86,13 @@ describe("/api/global/license", () => { expect(res.status).toBe(404) }) it("returns 200 + offline license token", async () => { - licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + licensing.offline.getOfflineLicenseToken.mockResolvedValue( + "offlineLicenseToken" + ) const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) expect(res.body).toEqual({ - offlineLicenseToken: "*" + offlineLicenseToken: "*", }) }) }) @@ -103,7 +111,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicenseIdentifier() expect(res.status).toBe(200) expect(res.body).toEqual({ - identifierBase64: "base64" + identifierBase64: "base64", }) }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 76d3e88df7..a6645226af 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,6 +1,9 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" -import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseTokenRequest, +} from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { @@ -35,7 +38,7 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/key") .set(this.config.defaultHeaders()) } - activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + activateOfflineLicense = async (body: ActivateOfflineLicenseTokenRequest) => { return this.request .post("/api/global/license/offline") .send(body) From 55efb6e618b827cd8fa46fd800fa368d168e66f6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 20:56:50 +0100 Subject: [PATCH 129/821] fix pro mock --- packages/worker/__mocks__/@budibase/pro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index f1e79bd7c7..59c7939111 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -8,7 +8,7 @@ const pro = { deleteLicenseKey: jest.fn(), }, offline: { - activateOfflineLicense: jest.fn(), + activateOfflineLicenseToken: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), getIdentifierBase64: jest.fn(), From 797f281f6d6ce6d1a4303d089d9783b0010785ef Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 17 Jul 2023 20:55:26 +0100 Subject: [PATCH 130/821] OpenAPI 3.0 docs for offline license and license key endpoints --- packages/worker/openapi-3.0.yaml | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/worker/openapi-3.0.yaml diff --git a/packages/worker/openapi-3.0.yaml b/packages/worker/openapi-3.0.yaml new file mode 100644 index 0000000000..c68654fffb --- /dev/null +++ b/packages/worker/openapi-3.0.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.0 +info: + title: Worker API Specification + version: 1.0.0 +servers: + - url: "http://localhost:10000" + description: localhost + - url: "https://budibaseqa.app" + description: QA + - url: "https://preprod.qa.budibase.net" + description: Preprod + - url: "https://budibase.app" + description: Production + +tags: + - name: license + description: License operations + +paths: + /api/global/license/key: + post: + tags: + - license + summary: Activate license key + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateLicenseKeyRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get license key + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetLicenseKeyResponse' + delete: + tags: + - license + summary: Delete license key + responses: + '204': + description: No content + /api/global/license/offline: + post: + tags: + - license + summary: Activate offline license + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateOfflineLicenseTokenRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get offline license + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineLicenseTokenResponse' + delete: + tags: + - license + summary: Delete offline license + responses: + '204': + description: No content + /api/global/license/offline/identifier: + get: + tags: + - license + summary: Get offline identifier + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineIdentifierResponse' + +components: + schemas: + ActivateOfflineLicenseTokenRequest: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + GetOfflineLicenseTokenResponse: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + ActivateLicenseKeyRequest: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetLicenseKeyResponse: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetOfflineIdentifierResponse: + type: object + properties: + identifierBase64: + type: string + required: + - identifierBase64 \ No newline at end of file From a6073e0ecd834cb17b802486ee7b7b4e926c6a12 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 14:56:01 +0100 Subject: [PATCH 132/821] Build fixes --- .../utilities/structures/documents/platform/installation.ts | 2 +- packages/pro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 25c5e50e00..711c6cf14f 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,4 +1,4 @@ -import { generator } from "@budibase/backend-core/tests" +import { generator } from "../../generator" import { Installation } from "@budibase/types" import * as db from "../../db" diff --git a/packages/pro b/packages/pro index b5124e76b9..5775dda6b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 +Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa From c3412f0cfbcb2795083926db6f92100c3daf7b4e Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:06:43 +0100 Subject: [PATCH 133/821] Update openapi.json --- packages/server/specs/openapi.json | 9 ++++++--- packages/server/specs/openapi.yaml | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index bcff78e861..d97b09568c 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -841,7 +841,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1045,7 +1046,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1260,7 +1262,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index a9daed6c76..86807c9981 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -768,6 +768,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -931,6 +932,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -1101,6 +1103,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: From 4e88779b5ed5d5e3fa91171e8d9c576080e10707 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:31:19 +0100 Subject: [PATCH 134/821] Remove 'nightly' from test discord notification --- qa-core/scripts/testResultsWebhook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa-core/scripts/testResultsWebhook.js b/qa-core/scripts/testResultsWebhook.js index 40cc42082d..5fbdd3a32e 100644 --- a/qa-core/scripts/testResultsWebhook.js +++ b/qa-core/scripts/testResultsWebhook.js @@ -42,7 +42,7 @@ async function discordResultsNotification(report) { Accept: "application/json", }, body: JSON.stringify({ - content: `**Nightly Tests Status**: ${OUTCOME}`, + content: `**Tests Status**: ${OUTCOME}`, embeds: [ { title: `Budi QA Bot - ${env}`, From 3fb0380eec3d06c2136789204f51a3b3476518b1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 16:50:57 +0100 Subject: [PATCH 135/821] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 5775dda6b9..1b8fd8ed44 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa +Subproject commit 1b8fd8ed445c4c25210f8faf07ff404c41fbc805 From 552f96fb81c78c6f3be9882bf4b5fcdb783753d2 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 21:37:24 +0100 Subject: [PATCH 136/821] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 1b8fd8ed44..88b3c19262 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1b8fd8ed445c4c25210f8faf07ff404c41fbc805 +Subproject commit 88b3c192624d19f37d5743afc36b92f1c1ebb1db From 8f0043157d885a2f6861c7c767b7b55a1853825a Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 21 Jul 2023 08:43:53 +0100 Subject: [PATCH 137/821] Styling fix for license key input --- .../src/pages/builder/portal/account/upgrade.svelte | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 8f76286c8e..5c1e8bee0e 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -205,10 +205,8 @@ >
-
-
+
-
@@ -291,8 +289,11 @@ } .field { display: grid; - grid-template-columns: 300px 1fr; + grid-template-columns: 100px 1fr; grid-gap: var(--spacing-l); align-items: center; } + .identifier-input { + width: 300px; + } From 73ae3d44d45b65375a266b452c04a87c87f95250 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 21 Jul 2023 07:50:42 +0000 Subject: [PATCH 138/821] Bump version to 2.8.19 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index d97372e035..a74cf9934d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.18", + "version": "2.8.19", "npmClient": "yarn", "packages": [ "packages/*" From b5a656e350be385623b41c3e8e883b37849f0117 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 21 Jul 2023 09:06:55 +0100 Subject: [PATCH 139/821] Update datasource config button hover styles to fix issues in light theme --- .../_components/EditDatasourceConfigButton.svelte | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte index 154957a561..9e50ab8da3 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/_components/EditDatasourceConfigButton.svelte @@ -74,11 +74,12 @@ border: 1px solid var(--spectrum-global-color-gray-300); border-radius: 5px; width: 100%; - background-color: var(--spectrum-global-color-gray-50); + background: var(--spectrum-global-color-gray-50); color: white; overflow: hidden; padding: 12px 16px; box-sizing: border-box; + transition: background 130ms ease-out; } .left { flex: 1; @@ -94,7 +95,7 @@ } .button:hover { cursor: pointer; - filter: brightness(1.2); + background: var(--spectrum-global-color-gray-100); } .connected { display: flex; From 567c7fd485724823542b7c3f436c00ded5dcef42 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 21 Jul 2023 09:43:25 +0100 Subject: [PATCH 140/821] code review --- packages/backend-core/src/cache/appMetadata.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/cache/appMetadata.ts b/packages/backend-core/src/cache/appMetadata.ts index 5ecc6f10b1..69f734162b 100644 --- a/packages/backend-core/src/cache/appMetadata.ts +++ b/packages/backend-core/src/cache/appMetadata.ts @@ -24,6 +24,10 @@ function isInvalid(metadata?: { state: string }) { return !metadata || metadata.state === AppState.INVALID } +interface DeletedAppMetadata { + state: AppState +} + /** * Get the requested app metadata by id. * Use redis cache to first read the app metadata. @@ -31,7 +35,9 @@ function isInvalid(metadata?: { state: string }) { * @param {string} appId the id of the app to get metadata from. * @returns {object} the app metadata. */ -export async function getAppMetadata(appId: string) { +export async function getAppMetadata( + appId: string +): Promise { const client = await getAppClient() // try cache let metadata = await client.get(appId) @@ -62,7 +68,7 @@ export async function getAppMetadata(appId: string) { await client.store(appId, metadata, expiry) } - return metadata as App & { state: AppState } + return metadata } /** From 108d18df75753d9569637fa4441a3ae6f009eec1 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 21 Jul 2023 09:57:37 +0100 Subject: [PATCH 141/821] fix type check for deleted app type --- packages/backend-core/src/cache/appMetadata.ts | 13 ++++++------- packages/backend-core/src/db/utils.ts | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/backend-core/src/cache/appMetadata.ts b/packages/backend-core/src/cache/appMetadata.ts index 69f734162b..0c320ec776 100644 --- a/packages/backend-core/src/cache/appMetadata.ts +++ b/packages/backend-core/src/cache/appMetadata.ts @@ -5,6 +5,11 @@ import { Database, App } from "@budibase/types" export enum AppState { INVALID = "invalid", } + +export interface DeletedApp { + state: AppState +} + const EXPIRY_SECONDS = 3600 /** @@ -24,10 +29,6 @@ function isInvalid(metadata?: { state: string }) { return !metadata || metadata.state === AppState.INVALID } -interface DeletedAppMetadata { - state: AppState -} - /** * Get the requested app metadata by id. * Use redis cache to first read the app metadata. @@ -35,9 +36,7 @@ interface DeletedAppMetadata { * @param {string} appId the id of the app to get metadata from. * @returns {object} the app metadata. */ -export async function getAppMetadata( - appId: string -): Promise { +export async function getAppMetadata(appId: string): Promise { const client = await getAppClient() // try cache let metadata = await client.get(appId) diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index b6e793c065..4ebf8392b5 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -2,7 +2,7 @@ import env from "../environment" import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants" import { getTenantId, getGlobalDBName } from "../context" import { doWithDB, directCouchAllDbs } from "./db" -import { AppState, getAppMetadata } from "../cache/appMetadata" +import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata" import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions" import { App, Database } from "@budibase/types" import { getStartEndKeyURL } from "../docIds" @@ -131,7 +131,7 @@ export async function getAppsByIDs(appIds: string[]) { .filter( promise => promise.status === "fulfilled" && - promise.value?.state !== AppState.INVALID + (promise.value as DeletedApp).state !== AppState.INVALID ) .map(promise => (promise as PromiseFulfilledResult).value) } From af8be511dfac26a4e0935c57d85889729d0cfeac Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 21 Jul 2023 10:39:58 +0100 Subject: [PATCH 142/821] Add back in XS icons --- packages/bbui/src/Icon/Icon.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index a2cf8a1b3a..11dc9963d5 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -85,4 +85,9 @@ text-align: center; z-index: 1; } + + .spectrum-Icon--sizeXS { + width: var(--spectrum-global-dimension-size-150); + height: var(--spectrum-global-dimension-size-150); + } From c5a0711dd891726c9cc9ce999128315d1cc98fa5 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 21 Jul 2023 10:12:39 +0000 Subject: [PATCH 143/821] Bump version to 2.8.20 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index a74cf9934d..db1b6dc3f5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.8.19", + "version": "2.8.20", "npmClient": "yarn", "packages": [ "packages/*" From a56f0c91dd60fde7f3b8ac8fa7e0ef6ff8f12b3e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Jul 2023 12:15:33 +0100 Subject: [PATCH 144/821] Quick improvement to table types, before now the field schema was quite difficult to parse/work out what components of the schema were used for what, this at least separates them into particularly grouped bits of metadata, so it is obvious which parts are used for which. In future we should really flip this a bit, so that FieldSchema is the base implementation, and then each of the types has its own schema extending that base - but that would be a more serious refactor (need to cast to the correct type when using based on the 'type' property. --- packages/types/src/documents/app/table.ts | 97 ------------------ .../src/documents/app/table/constants.ts | 9 ++ .../types/src/documents/app/table/index.ts | 3 + .../types/src/documents/app/table/schema.ts | 98 +++++++++++++++++++ .../types/src/documents/app/table/table.ts | 30 ++++++ 5 files changed, 140 insertions(+), 97 deletions(-) delete mode 100644 packages/types/src/documents/app/table.ts create mode 100644 packages/types/src/documents/app/table/constants.ts create mode 100644 packages/types/src/documents/app/table/index.ts create mode 100644 packages/types/src/documents/app/table/schema.ts create mode 100644 packages/types/src/documents/app/table/table.ts diff --git a/packages/types/src/documents/app/table.ts b/packages/types/src/documents/app/table.ts deleted file mode 100644 index 18b415da5f..0000000000 --- a/packages/types/src/documents/app/table.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Document } from "../document" -import { View } from "./view" -import { RenameColumn } from "../../sdk" -import { FieldType } from "./row" - -export enum RelationshipTypes { - ONE_TO_MANY = "one-to-many", - MANY_TO_ONE = "many-to-one", - MANY_TO_MANY = "many-to-many", -} - -export enum AutoReason { - FOREIGN_KEY = "foreign_key", -} - -export interface FieldSchema { - type: FieldType - externalType?: string - fieldName?: string - name: string - sortable?: boolean - tableId?: string - relationshipType?: RelationshipTypes - through?: string - foreignKey?: string - icon?: string - autocolumn?: boolean - autoReason?: AutoReason - subtype?: string - throughFrom?: string - throughTo?: string - formula?: string - formulaType?: string - main?: boolean - ignoreTimezones?: boolean - timeOnly?: boolean - lastID?: number - useRichText?: boolean | null - order?: number - width?: number - meta?: { - toTable: string - toKey: string - } - constraints?: { - type?: string - email?: boolean - inclusion?: string[] - length?: { - minimum?: string | number | null - maximum?: string | number | null - } - numericality?: { - greaterThanOrEqualTo: string | null - lessThanOrEqualTo: string | null - } - presence?: - | boolean - | { - allowEmpty?: boolean - } - datetime?: { - latest: string - earliest: string - } - } -} - -export interface TableSchema { - [key: string]: FieldSchema -} - -export interface Table extends Document { - type?: string - views?: { [key: string]: View } - name: string - primary?: string[] - schema: TableSchema - primaryDisplay?: string - sourceId?: string - relatedFormula?: string[] - constrained?: string[] - sql?: boolean - indexes?: { [key: string]: any } - rows?: { [key: string]: any } - created?: boolean - rowHeight?: number -} - -export interface ExternalTable extends Table { - sourceId: string -} - -export interface TableRequest extends Table { - _rename?: RenameColumn - created?: boolean -} diff --git a/packages/types/src/documents/app/table/constants.ts b/packages/types/src/documents/app/table/constants.ts new file mode 100644 index 0000000000..12a347c715 --- /dev/null +++ b/packages/types/src/documents/app/table/constants.ts @@ -0,0 +1,9 @@ +export enum RelationshipTypes { + ONE_TO_MANY = "one-to-many", + MANY_TO_ONE = "many-to-one", + MANY_TO_MANY = "many-to-many", +} + +export enum AutoReason { + FOREIGN_KEY = "foreign_key", +} diff --git a/packages/types/src/documents/app/table/index.ts b/packages/types/src/documents/app/table/index.ts new file mode 100644 index 0000000000..c22788d269 --- /dev/null +++ b/packages/types/src/documents/app/table/index.ts @@ -0,0 +1,3 @@ +export * from "./table" +export * from "./schema" +export * from "./constants" diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts new file mode 100644 index 0000000000..188a2fddd3 --- /dev/null +++ b/packages/types/src/documents/app/table/schema.ts @@ -0,0 +1,98 @@ +// all added by grid/table when defining the +// column size, position and whether it can be viewed +import { FieldType } from "../row" +import { AutoReason, RelationshipTypes } from "./constants" + +export interface UIFieldMetadata { + order?: number + width?: number + visible?: boolean + icon?: string +} + +export interface RelationshipFieldMetadata { + main?: boolean + fieldName?: string + tableId?: string + // below is used for SQL relationships, needed to define the foreign keys + // or the tables used for many-to-many relationships (through) + relationshipType?: RelationshipTypes + through?: string + foreignKey?: string + throughFrom?: string + throughTo?: string +} + +export interface AutoColumnFieldMetadata { + autocolumn?: boolean + subtype?: string + lastID?: number + // if the column was turned to an auto-column for SQL, explains why (primary, foreign etc) + autoReason?: AutoReason +} + +export interface NumberFieldMetadata { + // used specifically when Budibase generates external tables, this denotes if a number field + // is a foreign key used for a many-to-many relationship + meta?: { + toTable: string + toKey: string + } +} + +export interface DateFieldMetadata { + ignoreTimezones?: boolean + timeOnly?: boolean +} + +export interface StringFieldMetadata { + useRichText?: boolean | null +} + +export interface FormulaFieldMetadata { + formula?: string + formulaType?: string +} + +export interface FieldConstraints { + type?: string + email?: boolean + inclusion?: string[] + length?: { + minimum?: string | number | null + maximum?: string | number | null + } + numericality?: { + greaterThanOrEqualTo: string | null + lessThanOrEqualTo: string | null + } + presence?: + | boolean + | { + allowEmpty?: boolean + } + datetime?: { + latest: string + earliest: string + } +} + +export interface FieldSchema + extends UIFieldMetadata, + DateFieldMetadata, + RelationshipFieldMetadata, + AutoColumnFieldMetadata, + StringFieldMetadata, + FormulaFieldMetadata, + NumberFieldMetadata { + type: FieldType + name: string + sortable?: boolean + // only used by external databases, to denote the real type + externalType?: string + constraints?: FieldConstraints +} + +export interface TableSchema { + [key: string]: FieldSchema +} diff --git a/packages/types/src/documents/app/table/table.ts b/packages/types/src/documents/app/table/table.ts new file mode 100644 index 0000000000..f4dc790267 --- /dev/null +++ b/packages/types/src/documents/app/table/table.ts @@ -0,0 +1,30 @@ +import { Document } from "../../document" +import { View } from "../view" +import { RenameColumn } from "../../../sdk" +import { TableSchema } from "./schema" + +export interface Table extends Document { + type?: string + views?: { [key: string]: View } + name: string + primary?: string[] + schema: TableSchema + primaryDisplay?: string + sourceId?: string + relatedFormula?: string[] + constrained?: string[] + sql?: boolean + indexes?: { [key: string]: any } + rows?: { [key: string]: any } + created?: boolean + rowHeight?: number +} + +export interface ExternalTable extends Table { + sourceId: string +} + +export interface TableRequest extends Table { + _rename?: RenameColumn + created?: boolean +} From fb725f91b37051367aa6740e6a04255608cfed8f Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 21 Jul 2023 12:27:05 +0100 Subject: [PATCH 145/821] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 4d9840700e..93291fc6b8 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 4d9840700e7684581c39965b7cb6a2b2428c477c +Subproject commit 93291fc6b8227be8b1decafe2765c1b742fc4f21 From d77a4062c7f4c7bc2674ab2a8821cd500e83a553 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Fri, 21 Jul 2023 12:32:23 +0100 Subject: [PATCH 146/821] Update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index ac35929be1..2fb4c36fa8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -231,18 +231,33 @@ An overview of the CI pipelines can be found [here](../.github/workflows/README. ### Pro -@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you require to update code inside the pro package it can be cloned to the same root level as budibase, e.g. +@budibase/pro is the closed source package that supports licensed features in budibase. By default the package will be pulled from NPM and will not normally need to be touched in local development. If you need to make an update to pro and have access to the repo, then you can update your submodule within the mono-repo by running `git submodule update --init` - from here you can use normal submodule flow to develop a change within pro. + +Once you have updated to use the pro submodule, it will be linked into all of your local dependencies by NX as with all other monorepo packages. If you have been using the NPM version of `@budibase/pro` then you may need to run a `git reset --hard` to fix all of the pro versions back to `0.0.0` to be monorepo aware. + +From here - to develop a change in pro, you can follow the below flow: ``` -. -|_ budibase -|_ budibase-pro +# enter the pro submodule +cd packages/pro +# get the base branch you are working from (same as monorepo) +git fetch +git checkout +# create a branch, named the same as the branch in your monorepo +git checkout -b +... make changes +# commit the changes you've made, with a message for pro +git commit +# within the monorepo, add the pro reference to your branch, commit it with a message like "Update pro ref" +cd ../.. +git add packages/pro +git commit ``` +From here, you will have created a branch in the pro repository and commited the reference to your branch on the monorepo. When you eventually PR this work back into the mainline branch, you will need to first merge your pro PR to the pro mainline, then go into your PR in the monorepo and update the reference again to the new mainline. + Note that only budibase maintainers will be able to access the pro repo. -By default, NX will make sure that dependencies are replaced with local source aware version. This is achieved using the `yarn link` command. To see specifically how dependencies are linked see [scripts/link-dependencies.sh](../scripts/link-dependencies.sh). The same link script is used to link dependencies to account-portal in local dev. - ### Troubleshooting Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above to create a fresh Budibase installation. From 7673673db26d77640ef33e54f6df99d1143901e4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Jul 2023 12:57:47 +0100 Subject: [PATCH 147/821] Updating RelationshipTypes -> RelationshipType. --- .../DataTable/modals/CreateEditColumn.svelte | 12 +++---- .../Datasources/CreateEditRelationship.svelte | 30 ++++++++-------- .../backend/Datasources/relationshipErrors.js | 4 +-- .../builder/src/constants/backend/index.js | 2 +- packages/server/specs/resources/table.ts | 8 ++--- .../api/controllers/row/ExternalRequest.ts | 4 +-- .../src/api/controllers/table/external.ts | 20 +++++------ packages/server/src/constants/index.ts | 2 +- .../db/defaultData/datasource_bb_default.ts | 6 ++-- .../src/db/linkedRows/LinkController.ts | 20 +++++------ .../src/db/tests/linkController.spec.js | 30 ++++++++-------- .../src/integration-test/postgres.spec.ts | 36 +++++++++---------- .../server/src/integrations/base/sqlTable.ts | 6 ++-- .../server/src/sdk/app/tables/validation.ts | 6 ++-- .../src/documents/app/table/constants.ts | 2 +- .../types/src/documents/app/table/schema.ts | 4 +-- 16 files changed, 93 insertions(+), 99 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 208739a540..dfb028d38d 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -18,7 +18,7 @@ import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { FIELDS, - RelationshipTypes, + RelationshipType, ALLOWABLE_STRING_OPTIONS, ALLOWABLE_NUMBER_OPTIONS, ALLOWABLE_STRING_TYPES, @@ -183,7 +183,7 @@ dispatch("updatecolumns") if ( saveColumn.type === LINK_TYPE && - saveColumn.relationshipType === RelationshipTypes.MANY_TO_MANY + saveColumn.relationshipType === RelationshipType.MANY_TO_MANY ) { // Fetching the new tables tables.fetch() @@ -237,7 +237,7 @@ // Default relationships many to many if (editableColumn.type === LINK_TYPE) { - editableColumn.relationshipType = RelationshipTypes.MANY_TO_MANY + editableColumn.relationshipType = RelationshipType.MANY_TO_MANY } if (editableColumn.type === FORMULA_TYPE) { editableColumn.formulaType = "dynamic" @@ -285,17 +285,17 @@ { name: `Many ${thisName} rows → many ${linkName} rows`, alt: `Many ${table.name} rows → many ${linkTable.name} rows`, - value: RelationshipTypes.MANY_TO_MANY, + value: RelationshipType.MANY_TO_MANY, }, { name: `One ${linkName} row → many ${thisName} rows`, alt: `One ${linkTable.name} rows → many ${table.name} rows`, - value: RelationshipTypes.ONE_TO_MANY, + value: RelationshipType.ONE_TO_MANY, }, { name: `One ${thisName} row → many ${linkName} rows`, alt: `One ${table.name} rows → many ${linkTable.name} rows`, - value: RelationshipTypes.MANY_TO_ONE, + value: RelationshipType.MANY_TO_ONE, }, ] } diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index 53fcf56e7f..36c6a32801 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -1,5 +1,5 @@ @@ -183,8 +187,14 @@ value={query.name} on:input={e => { let newValue = e.target.value || "" - query.name = newValue.trim() + if (newValue.match(ValidQueryNameRegex)) { + query.name = newValue.trim() + nameError = null + } else { + nameError = "Invalid query name" + } }} + error={nameError} />
{#if queryConfig} @@ -250,9 +260,9 @@ size="L" />
- Add a JavaScript function to transform the query result. + + Add a JavaScript function to transform the query result. +
Results - +