From 586bca16d06cc73abeca99a05e4007892b9d7af0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:44:48 +0100 Subject: [PATCH 01/68] 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 02/68] 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 03/68] 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 04/68] 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 05/68] 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 06/68] 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 07/68] 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 08/68] 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 09/68] 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 10/68] 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 11/68] 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 12/68] /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 13/68] 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 14/68] 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 15/68] /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 16/68] 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 052a74f1d75fa0293df3a96cd818426fda1fbdf4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 21:53:05 +0100 Subject: [PATCH 17/68] 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 18/68] 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 41dc86436cdf93490485b9836474b197068f22ac Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 09:21:23 +0100 Subject: [PATCH 19/68] 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 3326d061f19ebcf4b46e76ff3a92ec5e14ca58ea Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 11:44:05 +0100 Subject: [PATCH 20/68] 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 4a38d55ce8837d9efa1ea2c8df3d10933d4ec88b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 16:55:48 +0100 Subject: [PATCH 21/68] 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 22/68] 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 22b2edb2bff8f194a6203feebab1c27ea13432c0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 17 Jul 2023 20:55:26 +0100 Subject: [PATCH 23/68] 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 faa00fa64df836178bf2b8beb0bb6211480c2387 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 12:05:04 +0100 Subject: [PATCH 24/68] Fix release and prerelease tag triggers --- .github/workflows/release-develop.yml | 2 +- .github/workflows/release-master.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index a7bf041eb5..e8d88fa136 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -6,7 +6,7 @@ concurrency: on: push: tags: - - v*-alpha.* + - ".*-alpha.*" workflow_dispatch: env: diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index c8810b7442..3c33dfcd86 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -6,9 +6,9 @@ concurrency: on: push: tags: - - "v[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+" # Exclude all pre-releases - - "!v*[0-9]+.[0-9]+.[0-9]+-*" + - "!*[0-9]+.[0-9]+.[0-9]+-*" env: # Posthog token used by ui at build time From ba5002f7421bc4ad1f83bbbd266f1a1c274ff3ff Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 12:18:00 +0100 Subject: [PATCH 25/68] Adjust tag regex for prerelease --- .github/workflows/release-develop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml index e8d88fa136..61cb283e28 100644 --- a/.github/workflows/release-develop.yml +++ b/.github/workflows/release-develop.yml @@ -6,7 +6,7 @@ concurrency: on: push: tags: - - ".*-alpha.*" + - "*-alpha.*" workflow_dispatch: env: From 5d0918a6cb1ceaf468b59da90e2b3ed884182f3a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 20 Jul 2023 12:21:09 +0100 Subject: [PATCH 26/68] Prevent invalid characters in column names when importing tables --- packages/bbui/src/Icon/Icon.svelte | 12 ++--- .../DataTable/modals/CreateEditColumn.svelte | 3 +- .../TableNavigator/TableDataImport.svelte | 46 ++++++++++--------- packages/server/src/utilities/schema.ts | 8 ++++ packages/shared-core/src/constants.ts | 1 + 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index 452a8c74a1..a2cf8a1b3a 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -47,7 +47,7 @@ {#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 31/68] 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 32/68] 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 33/68] 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 34/68] 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 35/68] 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 36/68] 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 37/68] 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 38/68] 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 73ae3d44d45b65375a266b452c04a87c87f95250 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 21 Jul 2023 07:50:42 +0000 Subject: [PATCH 39/68] 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 567c7fd485724823542b7c3f436c00ded5dcef42 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 21 Jul 2023 09:43:25 +0100 Subject: [PATCH 40/68] 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 41/68] 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 42/68] 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 43/68] 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 44/68] 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 45/68] 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 7673673db26d77640ef33e54f6df99d1143901e4 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Jul 2023 12:57:47 +0100 Subject: [PATCH 46/68] 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 @@