From 6aeb31c355bf359b9411e8414d2d7e2fd53a9414 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:44:48 +0100 Subject: [PATCH 01/33] Move OFFLINE_MODE to backend-core environment --- packages/backend-core/src/environment.ts | 1 + packages/server/src/environment.ts | 1 - packages/worker/src/api/controllers/system/environment.ts | 3 ++- packages/worker/src/environment.ts | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index eab8cd4c45..c0785ef419 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -156,6 +156,7 @@ const environment = { : false, VERSION: findVersion(), DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER, + OFFLINE_MODE: process.env.OFFLINE_MODE, _set(key: any, value: any) { process.env[key] = value // @ts-ignore diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index 79ee5d977c..64b342d577 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -81,7 +81,6 @@ const environment = { SELF_HOSTED: process.env.SELF_HOSTED, HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT, FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", - OFFLINE_MODE: process.env.OFFLINE_MODE, // old CLIENT_ID: process.env.CLIENT_ID, _set(key: string, value: any) { diff --git a/packages/worker/src/api/controllers/system/environment.ts b/packages/worker/src/api/controllers/system/environment.ts index f3f917c2dd..729a00fa88 100644 --- a/packages/worker/src/api/controllers/system/environment.ts +++ b/packages/worker/src/api/controllers/system/environment.ts @@ -1,10 +1,11 @@ import { Ctx } from "@budibase/types" import env from "../../../environment" +import { env as coreEnv } from "@budibase/backend-core" export const fetch = async (ctx: Ctx) => { ctx.body = { multiTenancy: !!env.MULTI_TENANCY, - offlineMode: !!env.OFFLINE_MODE, + offlineMode: !!coreEnv.OFFLINE_MODE, cloud: !env.SELF_HOSTED, accountPortalUrl: env.ACCOUNT_PORTAL_URL, disableAccountPortal: env.DISABLE_ACCOUNT_PORTAL, diff --git a/packages/worker/src/environment.ts b/packages/worker/src/environment.ts index 74d58831d9..678ffe7f14 100644 --- a/packages/worker/src/environment.ts +++ b/packages/worker/src/environment.ts @@ -61,7 +61,6 @@ const environment = { CHECKLIST_CACHE_TTL: parseIntSafe(process.env.CHECKLIST_CACHE_TTL) || 3600, SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD, ENCRYPTED_TEST_PUBLIC_API_KEY: process.env.ENCRYPTED_TEST_PUBLIC_API_KEY, - OFFLINE_MODE: process.env.OFFLINE_MODE, /** * Mock the email service in use - links to ethereal hosted emails are logged instead. */ From 0e80766125b5e476d06748a49ec4d647961b0803 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:46:25 +0100 Subject: [PATCH 02/33] Update license endpoints to provide consistent pattern for offline license and license key (create, read, delete) --- packages/types/src/api/web/global/index.ts | 1 + packages/types/src/api/web/global/license.ts | 19 +++++ .../src/api/controllers/global/license.ts | 69 ++++++++++++++----- .../worker/src/api/routes/global/license.ts | 21 +++++- 4 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 packages/types/src/api/web/global/license.ts diff --git a/packages/types/src/api/web/global/index.ts b/packages/types/src/api/web/global/index.ts index bf4b43f0ba..efcb6dc39c 100644 --- a/packages/types/src/api/web/global/index.ts +++ b/packages/types/src/api/web/global/index.ts @@ -3,3 +3,4 @@ export * from "./auditLogs" export * from "./events" export * from "./configs" export * from "./scim" +export * from "./license" diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts new file mode 100644 index 0000000000..34de2e4361 --- /dev/null +++ b/packages/types/src/api/web/global/license.ts @@ -0,0 +1,19 @@ +// LICENSE KEY + +export interface ActivateLicenseKeyRequest { + licenseKey: string +} + +export interface GetLicenseKeyResponse { + licenseKey: string, +} + +// OFFLINE LICENSE + +export interface ActivateOfflineLicenseRequest { + offlineLicense: string +} + +export interface GetOfflineLicenseResponse { + offlineLicense: string +} diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 2bd173010f..b4670e0dbb 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,33 +1,66 @@ import { licensing, quotas } from "@budibase/pro" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseRequest, + GetLicenseKeyResponse, + GetOfflineLicenseResponse, + UserCtx, +} from "@budibase/types" -export const activate = async (ctx: any) => { +// LICENSE KEY + +export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - if (!licenseKey) { - ctx.throw(400, "licenseKey is required") - } - await licensing.activateLicenseKey(licenseKey) ctx.status = 200 } +export async function getLicenseKey(ctx: UserCtx) { + const licenseKey = await licensing.getLicenseKey() + if (licenseKey) { + ctx.body = { licenseKey: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteLicenseKey(ctx: UserCtx) { + await licensing.deleteLicenseKey() + ctx.status = 204 +} + +// OFFLINE LICENSE + +export async function activateOfflineLicense(ctx: UserCtx) { + const { offlineLicense } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicense) + ctx.status = 200 +} + +export async function getOfflineLicense(ctx: UserCtx) { + const offlineLicense = await licensing.getOfflineLicense() + if (offlineLicense) { + ctx.body = { offlineLicense: "*" } + ctx.status = 200 + } else { + ctx.status = 404 + } +} + +export async function deleteOfflineLicense(ctx: UserCtx) { + await licensing.deleteOfflineLicense() + ctx.status = 204 +} + +// LICENSES + export const refresh = async (ctx: any) => { await licensing.cache.refresh() ctx.status = 200 } -export const getInfo = async (ctx: any) => { - const licenseInfo = await licensing.getLicenseInfo() - if (licenseInfo) { - licenseInfo.licenseKey = "*" - ctx.body = licenseInfo - } - ctx.status = 200 -} - -export const deleteInfo = async (ctx: any) => { - await licensing.deleteLicenseInfo() - ctx.status = 200 -} +// USAGE export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 0fb2a6e8bd..1d6185a402 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -1,13 +1,28 @@ import Router from "@koa/router" import * as controller from "../../controllers/global/license" +import { middleware } from "@budibase/backend-core" +import Joi from "joi" + +const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ + licenseKey: Joi.string().required(), + }).required()) + +const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ + offlineLicense: Joi.string().required(), + }).required()) const router: Router = new Router() router - .post("/api/global/license/activate", controller.activate) .post("/api/global/license/refresh", controller.refresh) - .get("/api/global/license/info", controller.getInfo) - .delete("/api/global/license/info", controller.deleteInfo) .get("/api/global/license/usage", controller.getQuotaUsage) + // LICENSE KEY + .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .get("/api/global/license/key", controller.getLicenseKey) + .delete("/api/global/license/key", controller.deleteLicenseKey) + // OFFLINE LICENSE + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) + .get("/api/global/license/offline", controller.getOfflineLicense) + .delete("/api/global/license/offline", controller.deleteOfflineLicense) export default router From d3f9348403b64f8b2971bcfdb1c02bcbb24755f5 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 20:47:12 +0100 Subject: [PATCH 03/33] Updates to upgrade page to change config based on offlineMode value --- .../builder/portal/account/upgrade.svelte | 215 +++++++++++++----- packages/builder/src/stores/portal/admin.js | 1 + packages/frontend-core/src/api/licensing.js | 57 +++-- 3 files changed, 201 insertions(+), 72 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index f0ee87bde5..a37625a5dc 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -10,6 +10,8 @@ Label, ButtonGroup, notifications, + CopyInput, + File } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -21,44 +23,122 @@ $: license = $auth.user.license $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` + // LICENSE KEY $: activateDisabled = !licenseKey || licenseKeyDisabled - - let licenseInfo - let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal + // OFFLINE LICENSE + let installationIdentifier = undefined + let offlineLicense = undefined + const offlineLicenseExtensions = [ + ".txt", + ] + // Make sure page can't be visited directly in cloud $: { if ($admin.cloud) { $redirect("../../portal") } + + console.log({ offlineLicense }) } - const activate = async () => { + // LICENSE KEY + + const getLicenseKey = async () => { try { - await API.activateLicenseKey({ licenseKey }) - await auth.getSelf() - await setLicenseInfo() - notifications.success("Successfully activated") + licenseKey = await API.getLicenseKey() + if (licenseKey) { + licenseKey = "**********************************************" + licenseKeyType = "password" + licenseKeyDisabled = true + activateDisabled = true + } } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error retrieving license key") } } - const destroy = async () => { + const activateLicenseKey = async () => { + try { + await API.activateLicenseKey({ licenseKey }) + await auth.getSelf() + await getLicenseKey() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating license key") + } + } + + const deleteLicenseKey = async () => { try { await API.deleteLicenseKey({ licenseKey }) await auth.getSelf() - await setLicenseInfo() + await getLicenseKey() // reset the form licenseKey = "" licenseKeyDisabled = false - notifications.success("Successfully deleted") + notifications.success("Offline license removed") } catch (e) { - notifications.error(e.message) + console.error(e) + notifications.error("Error deleting license key") + } + } + + // OFFLINE LICENSE + + const getOfflineLicense = async () => { + try { + const license = await API.getOfflineLicense() + if (license) { + offlineLicense = { + name: "license" + } + } else { + offlineLicense = undefined + } + } catch (e) { + console.error(e) + notifications.error("Error loading offline license") + } + } + + async function activateOfflineLicense(offlineLicense) { + try { + await API.activateOfflineLicense({ offlineLicense }) + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully activated") + } catch (e) { + console.error(e) + notifications.error("Error activating offline license") + } + } + + async function deleteOfflineLicense() { + try { + await API.deleteOfflineLicense() + await auth.getSelf() + await getOfflineLicense() + notifications.success("Successfully removed ofline license") + } catch (e) { + console.error(e) + notifications.error("Error upload offline license") + } + } + + async function onOfflineLicenseChange(event) { + if (event.detail) { + const reader = new FileReader() + reader.readAsText(event.detail) + reader.onload = () => activateOfflineLicense(reader.result) + } else { + await deleteOfflineLicense() } } @@ -73,29 +153,19 @@ } } - // deactivate the license key field if there is a license key set - $: { - if (licenseInfo?.licenseKey) { - licenseKey = "**********************************************" - licenseKeyType = "password" - licenseKeyDisabled = true - activateDisabled = true - } - } - - const setLicenseInfo = async () => { - licenseInfo = await API.getLicenseInfo() - } - onMount(async () => { - await setLicenseInfo() + if ($admin.offlineMode) { + await getOfflineLicense() + } else { + await getLicenseKey() + } }) {#if $auth.isAdmin} @@ -112,38 +182,73 @@ - - Activate - Enter your license key below to activate your plan - - -
-
- - + Installation identifier + Share this with support@budibase.com to obtain your offline license + + +
+
+ +
+
+
+ + + License + Upload your license to activate your plan + + +
+
-
- - - {#if licenseInfo?.licenseKey} - - {/if} - - + {#if licenseKey} + + {/if} + + + {/if} - Plan - + Plan + You are currently on the {license.plan.type} plan +
+ If you purchase or update your plan on the account + portal, click the refresh button to sync those changes +
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index b9467fd037..19abcfd841 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,6 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, + offlineMode: false } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index c27d79d740..5f7813cfca 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,32 +1,56 @@ export const buildLicensingEndpoints = API => ({ - /** - * Activates a self hosted license key - */ + + // LICENSE KEY + activateLicenseKey: async data => { return API.post({ - url: `/api/global/license/activate`, + url: `/api/global/license/key`, body: data, }) }, - - /** - * Delete a self hosted license key - */ deleteLicenseKey: async () => { return API.delete({ - url: `/api/global/license/info`, + url: `/api/global/license/key`, }) }, + getLicenseKey: async () => { + try { + return await API.get({ + url: "/api/global/license/key", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, - /** - * Get the license info - metadata about the license including the - * obfuscated license key. - */ - getLicenseInfo: async () => { - return API.get({ - url: "/api/global/license/info", + // OFFLINE LICENSE + + activateOfflineLicense: async ({ offlineLicense }) => { + return API.post({ + url: "/api/global/license/offline", + body: { + offlineLicense + }, }) }, + deleteOfflineLicense: async () => { + return API.delete({ + url: "/api/global/license/offline", + }) + }, + getOfflineLicense: async () => { + try { + return await API.get({ + url: "/api/global/license/offline", + }) + } catch (e) { + if (e.status !== 404) { + throw e + } + } + }, /** * Refreshes the license cache @@ -36,7 +60,6 @@ export const buildLicensingEndpoints = API => ({ url: "/api/global/license/refresh", }) }, - /** * Retrieve the usage information for the tenant */ From 8e2a551a1560a26e897f3a96d028baf3e1d1efc1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:26:46 +0100 Subject: [PATCH 04/33] Be more explicit about offline license vs offline license token --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 34de2e4361..0196b69c7f 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -11,9 +11,9 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE export interface ActivateOfflineLicenseRequest { - offlineLicense: string + offlineLicenseToken: string } export interface GetOfflineLicenseResponse { - offlineLicense: string + offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b4670e0dbb..b33efa1491 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -33,15 +33,15 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE export async function activateOfflineLicense(ctx: UserCtx) { - const { offlineLicense } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicense) + const { offlineLicenseToken } = ctx.request.body + await licensing.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicense = await licensing.getOfflineLicense() - if (offlineLicense) { - ctx.body = { offlineLicense: "*" } + const offlineLicenseToken = await licensing.getOfflineLicenseToken() + if (offlineLicenseToken) { + ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 } else { ctx.status = 404 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicense() + await licensing.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d6185a402..889d7d1ed4 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -8,8 +8,8 @@ const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ }).required()) const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ - offlineLicense: Joi.string().required(), - }).required()) + offlineLicenseToken: Joi.string().required(), +}).required()) const router: Router = new Router() From ec5381c9d58859c0ac56ef9798281127d29d0510 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 21:58:20 +0100 Subject: [PATCH 05/33] Aesthetic UI updates --- .../builder/portal/account/upgrade.svelte | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index a37625a5dc..48ff3514b7 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -24,14 +24,16 @@ $: upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` // LICENSE KEY + $: activateDisabled = !licenseKey || licenseKeyDisabled let licenseKeyDisabled = false let licenseKeyType = "text" let licenseKey = "" let deleteLicenseKeyModal - // OFFLINE LICENSE - let installationIdentifier = undefined + // OFFLINE + + let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -108,9 +110,9 @@ } } - async function activateOfflineLicense(offlineLicense) { + async function activateOfflineLicense(offlineLicenseToken) { try { - await API.activateOfflineLicense({ offlineLicense }) + await API.activateOfflineLicense({ offlineLicenseToken }) await auth.getSelf() await getOfflineLicense() notifications.success("Successfully activated") @@ -134,10 +136,16 @@ async function onOfflineLicenseChange(event) { if (event.detail) { + // prevent file preview jitter by assigning constant + // as soon as possible + offlineLicense = { + name: "license" + } const reader = new FileReader() reader.readAsText(event.detail) reader.onload = () => activateOfflineLicense(reader.result) } else { + offlineLicense = undefined await deleteOfflineLicense() } } @@ -178,6 +186,7 @@ {:else} To manage your plan visit your account +
 
{/if}
@@ -270,7 +279,7 @@ } .field { display: grid; - grid-template-columns: 100px 1fr; + grid-template-columns: 300px 1fr; grid-gap: var(--spacing-l); align-items: center; } From 7bc14235aabff2e0bd172a9ed7cfa0cd769294e0 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 6 Jul 2023 22:00:13 +0100 Subject: [PATCH 06/33] Update request body for offline license activation --- packages/frontend-core/src/api/licensing.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 5f7813cfca..cacf972971 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -27,11 +27,11 @@ export const buildLicensingEndpoints = API => ({ // OFFLINE LICENSE - activateOfflineLicense: async ({ offlineLicense }) => { + activateOfflineLicense: async ({ offlineLicenseToken }) => { return API.post({ url: "/api/global/license/offline", body: { - offlineLicense + offlineLicenseToken }, }) }, From 7c18a7a443b83dba4dc31b50fb6d57cd317b236f Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:34:10 +0100 Subject: [PATCH 07/33] db / licenseInfo.spec.ts --- packages/worker/src/api/routes/global/tests/license.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index be0673729e..d548ad83bb 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -21,9 +21,6 @@ describe("/api/global/license", () => { describe("POST /api/global/license/refresh", () => {}) - describe("GET /api/global/license/info", () => {}) - - describe("DELETE /api/global/license/info", () => {}) describe("GET /api/global/license/usage", () => {}) }) From 6c3d01375b91b75de2beea8c36343d551a88816d Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 11:48:12 +0100 Subject: [PATCH 08/33] Move license keys to their own module --- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b33efa1491..9310eb3739 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -11,12 +11,12 @@ import { export async function activateLicenseKey(ctx: UserCtx) { const { licenseKey } = ctx.request.body - await licensing.activateLicenseKey(licenseKey) + await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 } export async function getLicenseKey(ctx: UserCtx) { - const licenseKey = await licensing.getLicenseKey() + const licenseKey = await licensing.keys.getLicenseKey() if (licenseKey) { ctx.body = { licenseKey: "*" } ctx.status = 200 @@ -26,7 +26,7 @@ export async function getLicenseKey(ctx: UserCtx) { } export async function deleteLicenseKey(ctx: UserCtx) { - await licensing.deleteLicenseKey() + await licensing.keys.deleteLicenseKey() ctx.status = 204 } @@ -34,12 +34,12 @@ export async function deleteLicenseKey(ctx: UserCtx) { export async function activateOfflineLicense(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicense(offlineLicenseToken) ctx.status = 200 } export async function getOfflineLicense(ctx: UserCtx) { - const offlineLicenseToken = await licensing.getOfflineLicenseToken() + const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } ctx.status = 200 @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { - await licensing.deleteOfflineLicenseToken() + await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } From 20c87b44b1479deaec0fe817acac6e4cea224818 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 16:55:11 +0100 Subject: [PATCH 09/33] Allow pro to be mocked in worker --- packages/worker/__mocks__/@budibase/pro.ts | 26 +++++++++++++++++++ .../worker/src/tests/TestConfiguration.ts | 11 ++++---- packages/worker/src/tests/mocks/index.ts | 4 +++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/worker/__mocks__/@budibase/pro.ts diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts new file mode 100644 index 0000000000..a9611ba705 --- /dev/null +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -0,0 +1,26 @@ +const actual = jest.requireActual("@budibase/pro") +const pro = { + ...actual, + licensing: { + keys: { + activateLicenseKey: jest.fn(), + getLicenseKey: jest.fn(), + deleteLicenseKey: jest.fn(), + }, + offline: { + activateOfflineLicense: jest.fn(), + getOfflineLicenseToken: jest.fn(), + deleteOfflineLicenseToken: jest.fn(), + }, + cache: { + ...actual.licensing.cache, + refresh: jest.fn(), + } + }, + quotas: { + ...actual.quotas, + getQuotaUsage: jest.fn() + }, +} + +export = pro diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index a79ac0e189..b41b76efda 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -1,8 +1,7 @@ import mocks from "./mocks" // init the licensing mock -import * as pro from "@budibase/pro" -mocks.licenses.init(pro) +mocks.licenses.init(mocks.pro) // use unlimited license by default mocks.licenses.useUnlimited() @@ -238,21 +237,21 @@ class TestConfiguration { const db = context.getGlobalDB() - const id = dbCore.generateDevInfoID(this.user._id) + const id = dbCore.generateDevInfoID(this.user!._id) // TODO: dry this.apiKey = encryption.encrypt( `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}` ) const devInfo = { _id: id, - userId: this.user._id, + userId: this.user!._id, apiKey: this.apiKey, } await db.put(devInfo) }) } - async getUser(email: string): Promise { + async getUser(email: string): Promise { return context.doInTenant(this.getTenantId(), () => { return users.getGlobalUserByEmail(email) }) @@ -264,7 +263,7 @@ class TestConfiguration { } const response = await this._req(user, null, controllers.users.save) const body = response as SaveUserResponse - return this.getUser(body.email) + return this.getUser(body.email) as Promise } // CONFIGS diff --git a/packages/worker/src/tests/mocks/index.ts b/packages/worker/src/tests/mocks/index.ts index 30bb4e1d09..cab019bb46 100644 --- a/packages/worker/src/tests/mocks/index.ts +++ b/packages/worker/src/tests/mocks/index.ts @@ -1,7 +1,11 @@ import * as email from "./email" import { mocks } from "@budibase/backend-core/tests" +import * as _pro from "@budibase/pro" +const pro = jest.mocked(_pro, true) + export default { email, + pro, ...mocks, } From c0568b9153ca16b29be00de6d4e76519d92edbd6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 10/33] api / license.spec.ts updates --- .../src/api/controllers/global/license.ts | 1 + .../api/routes/global/tests/license.spec.ts | 85 +++++++++++++++++-- packages/worker/src/tests/api/license.ts | 43 +++++++++- 3 files changed, 120 insertions(+), 9 deletions(-) diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 9310eb3739..b8c8566018 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -64,4 +64,5 @@ export const refresh = async (ctx: any) => { export const getQuotaUsage = async (ctx: any) => { ctx.body = await quotas.getQuotaUsage() + ctx.status = 200 } diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index d548ad83bb..c3c423c833 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,4 +1,6 @@ -import { TestConfiguration } from "../../../../tests" +import { TestConfiguration, mocks, structures } from "../../../../tests" +const licensing = mocks.pro.licensing +const quotas = mocks.pro.quotas describe("/api/global/license", () => { const config = new TestConfiguration() @@ -12,15 +14,86 @@ describe("/api/global/license", () => { }) afterEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() }) - describe("POST /api/global/license/activate", () => { - it("activates license", () => {}) + describe("POST /api/global/license/refresh", () => { + it("returns 200", async () => { + const res = await config.api.license.refresh() + expect(res.status).toBe(200) + expect(licensing.cache.refresh).toBeCalledTimes(1) + }) }) - describe("POST /api/global/license/refresh", () => {}) + describe("GET /api/global/license/usage", () => { + it("returns 200", async () => { + const usage = structures.quotas.usage() + quotas.getQuotaUsage.mockResolvedValue(usage) + const res = await config.api.license.getUsage() + expect(res.status).toBe(200) + expect(res.body).toEqual(usage) + }) + }) + describe("POST /api/global/license/key", () => { + it("returns 200", async () => { + const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + expect(res.status).toBe(200) + expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") + }) + }) - describe("GET /api/global/license/usage", () => {}) + describe("GET /api/global/license/key", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(404) + }) + it("returns 200 + license key", async () => { + licensing.keys.getLicenseKey.mockResolvedValue("licenseKey") + const res = await config.api.license.getLicenseKey() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + licenseKey: "*" + }) + }) + }) + + describe("DELETE /api/global/license/key", () => { + it("returns 204", async () => { + const res = await config.api.license.deleteLicenseKey() + expect(licensing.keys.deleteLicenseKey).toBeCalledTimes(1) + expect(res.status).toBe(204) + }) + }) + + describe("POST /api/global/license/offline", () => { + it("activates offline license", async () => { + const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) + expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + expect(res.status).toBe(200) + }) + }) + + describe("GET /api/global/license/offline", () => { + it("returns 404 when not found", async () => { + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(404) + }) + it("returns 200", async () => { + licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + const res = await config.api.license.getOfflineLicense() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + offlineLicenseToken: "*" + }) + }) + }) + + describe("DELETE /api/global/license/offline", () => { + it("deletes offline license", async () => { + const res = await config.api.license.deleteOfflineLicense() + expect(res.status).toBe(204) + expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 9d7745a80e..89f85b25e3 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,17 +1,54 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" +import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { super(config) } - activate = async (licenseKey: string) => { + refresh = async () => { return this.request - .post("/api/global/license/activate") - .send({ licenseKey: licenseKey }) + .post("/api/global/license/refresh") + .set(this.config.defaultHeaders()) + } + getUsage = async () => { + return this.request + .get("/api/global/license/usage") .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) } + activateLicenseKey = async (body: ActivateLicenseKeyRequest) => { + return this.request + .post("/api/global/license/key") + .send(body) + .set(this.config.defaultHeaders()) + } + getLicenseKey = async () => { + return this.request + .get("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + deleteLicenseKey = async () => { + return this.request + .delete("/api/global/license/key") + .set(this.config.defaultHeaders()) + } + activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + return this.request + .post("/api/global/license/offline") + .send(body) + .set(this.config.defaultHeaders()) + } + getOfflineLicense = async () => { + return this.request + .get("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } + deleteOfflineLicense = async () => { + return this.request + .delete("/api/global/license/offline") + .set(this.config.defaultHeaders()) + } } From 9e1e869949d103c75056b77adbca923756f786ed Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 7 Jul 2023 20:07:15 +0100 Subject: [PATCH 11/33] api / license.spec.ts updates --- packages/backend-core/src/events/identification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/events/identification.ts b/packages/backend-core/src/events/identification.ts index 5eb11d1354..948d3b692b 100644 --- a/packages/backend-core/src/events/identification.ts +++ b/packages/backend-core/src/events/identification.ts @@ -264,7 +264,7 @@ const getEventTenantId = async (tenantId: string): Promise => { } } -const getUniqueTenantId = async (tenantId: string): Promise => { +export const getUniqueTenantId = async (tenantId: string): Promise => { // make sure this tenantId always matches the tenantId in context return context.doInTenant(tenantId, () => { return withCache(CacheKey.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => { From 90e869dc045abcf69852693a60a96b370f91e51b Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:07:10 +0100 Subject: [PATCH 12/33] /api/global/license/offline/identifier API --- packages/types/src/api/web/global/license.ts | 6 ++++++ packages/types/src/sdk/licensing/license.ts | 10 ++++++++++ packages/worker/__mocks__/@budibase/pro.ts | 1 + .../src/api/controllers/global/license.ts | 7 +++++++ .../worker/src/api/routes/global/license.ts | 1 + .../src/api/routes/global/tests/license.spec.ts | 17 ++++++++++++++--- packages/worker/src/tests/api/license.ts | 5 +++++ 7 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 0196b69c7f..4a36c4f1d8 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -17,3 +17,9 @@ export interface ActivateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string } + +// IDENTIFIER + +export interface GetOfflineIdentifierResponse { + identifierBase64: string +} diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index b287e67adf..ebc874bca0 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,5 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +export interface OfflineIdentifier { + installId: string, + tenantId: string +} + +// export interface OfflineLicense extends License { +// identifier?: OfflineIdentifier +// identifierBase64: string +// } + export interface License { features: Feature[] quotas: Quotas diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index a9611ba705..bd6250fede 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,6 +11,7 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), + getIdentifierBase64: jest.fn() }, cache: { ...actual.licensing.cache, diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index b8c8566018..73b3c72d1e 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -3,6 +3,7 @@ import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest, GetLicenseKeyResponse, + GetOfflineIdentifierResponse, GetOfflineLicenseResponse, UserCtx, } from "@budibase/types" @@ -53,6 +54,12 @@ export async function deleteOfflineLicense(ctx: UserCtx) { ctx.status = 204 } +export async function getOfflineLicenseIdentifier(ctx: UserCtx) { + const identifierBase64 = await licensing.offline.getIdentifierBase64() + ctx.body = { identifierBase64 } + ctx.status = 200 +} + // LICENSES export const refresh = async (ctx: any) => { diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 889d7d1ed4..1d3d9c460b 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -24,5 +24,6 @@ router .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) .get("/api/global/license/offline", controller.getOfflineLicense) .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index c3c423c833..512d6c9c52 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -26,7 +26,7 @@ describe("/api/global/license", () => { }) describe("GET /api/global/license/usage", () => { - it("returns 200", async () => { + it("returns 200 + usage", async () => { const usage = structures.quotas.usage() quotas.getQuotaUsage.mockResolvedValue(usage) const res = await config.api.license.getUsage() @@ -79,7 +79,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(404) }) - it("returns 200", async () => { + it("returns 200 + offline license token", async () => { licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) @@ -90,10 +90,21 @@ describe("/api/global/license", () => { }) describe("DELETE /api/global/license/offline", () => { - it("deletes offline license", async () => { + it("returns 204", async () => { const res = await config.api.license.deleteOfflineLicense() expect(res.status).toBe(204) expect(licensing.offline.deleteOfflineLicenseToken).toBeCalledTimes(1) }) }) + + describe("GET /api/global/license/offline/identifier", () => { + it("returns 200 + identifier base64", async () => { + licensing.offline.getIdentifierBase64.mockResolvedValue("base64") + const res = await config.api.license.getOfflineLicenseIdentifier() + expect(res.status).toBe(200) + expect(res.body).toEqual({ + identifierBase64: "base64" + }) + }) + }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 89f85b25e3..76d3e88df7 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -51,4 +51,9 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/offline") .set(this.config.defaultHeaders()) } + getOfflineLicenseIdentifier = async () => { + return this.request + .get("/api/global/license/offline/identifier") + .set(this.config.defaultHeaders()) + } } From c69fa2a6408d6529525be77f9f1a5942f76b0ab3 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Sat, 8 Jul 2023 13:08:51 +0100 Subject: [PATCH 14/33] Integrate UI with identifier API --- .../builder/portal/account/upgrade.svelte | 18 +++++++++++++----- packages/frontend-core/src/api/licensing.js | 5 +++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 48ff3514b7..8e2fd5c641 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -33,7 +33,7 @@ // OFFLINE - let installationIdentifier = "aW5zdGFsbGF0aW9uSWQ9M2MwYmYyZjMtOGJlZi00YTBkLTllN2UtZTU4NmUxMDg2ZjVhLGluc3RhbGxhdGlvblRlbmFudElkPWU5ZWUwNDI0LTE4N2UtNDNhMS1hMDY1LTNiODhmZmE4YzJhZg==\n" + let offlineLicenseIdentifier = "" let offlineLicense = undefined const offlineLicenseExtensions = [ ".txt", @@ -44,8 +44,6 @@ if ($admin.cloud) { $redirect("../../portal") } - - console.log({ offlineLicense }) } // LICENSE KEY @@ -110,6 +108,16 @@ } } + const getOfflineLicenseIdentifier = async () => { + try { + const res = await API.getOfflineLicenseIdentifier() + offlineLicenseIdentifier = res.identifierBase64 + } catch (e) { + console.error(e) + notifications.error("Error loading installation identifier") + } + } + async function activateOfflineLicense(offlineLicenseToken) { try { await API.activateOfflineLicense({ offlineLicenseToken }) @@ -163,7 +171,7 @@ onMount(async () => { if ($admin.offlineMode) { - await getOfflineLicense() + await Promise.all([getOfflineLicense(), getOfflineLicenseIdentifier()]) } else { await getLicenseKey() } @@ -199,7 +207,7 @@
- +
diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index cacf972971..9fe98d5e74 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -51,6 +51,11 @@ export const buildLicensingEndpoints = API => ({ } } }, + getOfflineLicenseIdentifier: async () => { + return await API.get({ + url: "/api/global/license/offline/identifier", + }) + }, /** * Refreshes the license cache From 91d3cdff3fe8466e9ad6d2e911971f3006513573 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:48:52 +0100 Subject: [PATCH 15/33] offline license sdk module --- packages/backend-core/src/errors/errors.ts | 12 ++++++++++++ packages/types/src/documents/account/account.ts | 1 + packages/types/src/sdk/licensing/feature.ts | 1 + packages/types/src/sdk/licensing/license.ts | 9 +++++---- packages/types/src/shared/typeUtils.ts | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/errors/errors.ts b/packages/backend-core/src/errors/errors.ts index 4e1f1abbb5..7d55d25e89 100644 --- a/packages/backend-core/src/errors/errors.ts +++ b/packages/backend-core/src/errors/errors.ts @@ -55,6 +55,18 @@ export class HTTPError extends BudibaseError { } } +export class NotFoundError extends HTTPError { + constructor(message: string) { + super(message, 404) + } +} + +export class BadRequestError extends HTTPError { + constructor(message: string) { + super(message, 400) + } +} + // LICENSING export class UsageLimitError extends HTTPError { diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts index dad8abed30..5321aa7e08 100644 --- a/packages/types/src/documents/account/account.ts +++ b/packages/types/src/documents/account/account.ts @@ -51,6 +51,7 @@ export interface Account extends CreateAccount { licenseRequestedAt?: number licenseOverrides?: LicenseOverrides quotaUsage?: QuotaUsage + offlineLicenseToken?: string } export interface PasswordAccount extends Account { diff --git a/packages/types/src/sdk/licensing/feature.ts b/packages/types/src/sdk/licensing/feature.ts index 1cbdd55bcf..f286a1cc44 100644 --- a/packages/types/src/sdk/licensing/feature.ts +++ b/packages/types/src/sdk/licensing/feature.ts @@ -9,6 +9,7 @@ export enum Feature { BRANDING = "branding", SCIM = "scim", SYNC_AUTOMATIONS = "syncAutomations", + OFFLINE = "offline", } export type PlanFeatures = { [key in PlanType]: Feature[] | undefined } diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index ebc874bca0..e8ad98fe7a 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -1,14 +1,15 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." +import { ISO8601 } from "../../shared" export interface OfflineIdentifier { installId: string, tenantId: string } -// export interface OfflineLicense extends License { -// identifier?: OfflineIdentifier -// identifierBase64: string -// } +export interface OfflineLicense extends License { + identifier: OfflineIdentifier + expireAt: ISO8601 +} export interface License { features: Feature[] diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 71fadfc7aa..143865c60b 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -1,3 +1,5 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } + +export type ISO8601 = string \ No newline at end of file From b8273540e0ab44f94dc3de4a1ac3f2c4db49d9d6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 12:49:45 +0100 Subject: [PATCH 16/33] /api/internal/accounts/:accountId/license/offline --- packages/types/src/api/account/license.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index a867358559..e23f6cab97 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -1,5 +1,6 @@ import { LicenseOverrides, QuotaUsage } from "../../documents" -import { PlanType } from "../../sdk" +import { OfflineLicense, PlanType } from "../../sdk" +import { ISO8601 } from "../../shared" export interface GetLicenseRequest { // All fields should be optional to cater for @@ -26,3 +27,13 @@ export interface UpdateLicenseRequest { planType?: PlanType overrides?: LicenseOverrides } + +export interface CreateOfflineLicenseRequest { + installationIdentifierBase64: string + expireAt: ISO8601 +} + +export interface GetOfflineLicenseResponse { + offlineLicenseToken: string + license: OfflineLicense +} \ No newline at end of file From 34d9f1c4f8640803bec031aaa112f8e8f17221b6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 10 Jul 2023 16:12:19 +0100 Subject: [PATCH 17/33] Request / response renames --- packages/types/src/api/web/global/license.ts | 4 ++-- .../worker/src/api/controllers/global/license.ts | 12 ++++++------ packages/worker/src/api/routes/global/license.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 4a36c4f1d8..900a0db96d 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -10,11 +10,11 @@ export interface GetLicenseKeyResponse { // OFFLINE LICENSE -export interface ActivateOfflineLicenseRequest { +export interface ActivateOfflineLicenseTokenRequest { offlineLicenseToken: string } -export interface GetOfflineLicenseResponse { +export interface GetOfflineLicenseTokenResponse { offlineLicenseToken: string } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index 73b3c72d1e..fa95eeee0d 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -1,10 +1,10 @@ import { licensing, quotas } from "@budibase/pro" import { ActivateLicenseKeyRequest, - ActivateOfflineLicenseRequest, + ActivateOfflineLicenseTokenRequest, GetLicenseKeyResponse, GetOfflineIdentifierResponse, - GetOfflineLicenseResponse, + GetOfflineLicenseTokenResponse, UserCtx, } from "@budibase/types" @@ -33,13 +33,13 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicense(ctx: UserCtx) { +export async function activateOfflineLicenseToken(ctx: UserCtx) { const { offlineLicenseToken } = ctx.request.body - await licensing.offline.activateOfflineLicense(offlineLicenseToken) + await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicense(ctx: UserCtx) { +export async function getOfflineLicenseToken(ctx: UserCtx) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -49,7 +49,7 @@ export async function getOfflineLicense(ctx: UserCtx) { +export async function deleteOfflineLicenseToken(ctx: UserCtx) { await licensing.offline.deleteOfflineLicenseToken() ctx.status = 204 } diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 1d3d9c460b..199dbb3846 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -21,9 +21,9 @@ router .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicense) - .get("/api/global/license/offline", controller.getOfflineLicense) - .delete("/api/global/license/offline", controller.deleteOfflineLicense) + .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .get("/api/global/license/offline", controller.getOfflineLicenseToken) + .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) export default router From a74469b8ae3c1c7edb71c01b00a3eda113e808d4 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 21:53:05 +0100 Subject: [PATCH 18/33] offline license structure --- .../core/utilities/structures/licenses.ts | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 22e73f2871..35c9156ec6 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,7 @@ import { Billing, Customer, Feature, - License, + License, OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -11,6 +11,7 @@ import { Quotas, Subscription, } from "@budibase/types" +import { generator } from "./generator" export function price(): PurchasedPrice { return { @@ -127,14 +128,16 @@ export function subscription(): Subscription { } } +interface GenerateLicenseOpts { + quotas?: Quotas + plan?: PurchasedPlan + planType?: PlanType + features?: Feature[] + billing?: Billing +} + export const license = ( - opts: { - quotas?: Quotas - plan?: PurchasedPlan - planType?: PlanType - features?: Feature[] - billing?: Billing - } = {} + opts: GenerateLicenseOpts = {} ): License => { return { features: opts.features || [], @@ -143,3 +146,17 @@ export const license = ( billing: opts.billing || billing(), } } + +export function offlineLicense ( + opts: GenerateLicenseOpts = {} +): OfflineLicense { + const base = license(opts) + return { + ...base, + expireAt: new Date().toISOString(), + identifier: { + installId: generator.guid(), + tenantId: generator.guid() + } + } +} \ No newline at end of file From 166156d3b5d51568d07dd8783eb9cb58d6915898 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 13 Jul 2023 22:06:52 +0100 Subject: [PATCH 19/33] use automocking in offline.spec.ts --- .../backend-core/tests/core/utilities/structures/accounts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 807153cd09..42e9d4b63a 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -13,7 +13,7 @@ import { } from "@budibase/types" import _ from "lodash" -export const account = (): Account => { +export const account = (partial: Partial = {}): Account => { return { accountId: uuid(), tenantId: generator.word(), @@ -29,6 +29,7 @@ export const account = (): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), + ...partial } } From 8cdfc8dd8319a07b767afd16301b0980d486e57d Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 09:21:23 +0100 Subject: [PATCH 20/33] Add structures for Installation type --- .../tests/core/utilities/structures/index.ts | 1 + .../tests/core/utilities/structures/installation.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 packages/backend-core/tests/core/utilities/structures/installation.ts diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index 2c094f43a7..c4404856e1 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,6 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" +export * as installation from "./installation" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/installation.ts new file mode 100644 index 0000000000..bd2ae4abfe --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/installation.ts @@ -0,0 +1,10 @@ +import { generator } from "@budibase/backend-core/tests" +import { Installation } from "@budibase/types" + +export function install(): Installation { + return { + _id: "install", + installId: generator.guid(), + version: generator.string() + } +} \ No newline at end of file From 1964148581c446aee45adf5eb75a22ec990f9918 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 11:44:05 +0100 Subject: [PATCH 21/33] core structure updates --- .../tests/core/utilities/structures/db.ts | 6 +++--- .../utilities/structures/documents/index.ts | 1 + .../structures/documents/platform/index.ts | 1 + .../{ => documents/platform}/installation.ts | 2 ++ .../tests/core/utilities/structures/index.ts | 2 +- .../core/utilities/structures/licenses.ts | 19 ++++++++++++++----- 6 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/index.ts create mode 100644 packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts rename packages/backend-core/tests/core/utilities/structures/{ => documents/platform}/installation.ts (82%) diff --git a/packages/backend-core/tests/core/utilities/structures/db.ts b/packages/backend-core/tests/core/utilities/structures/db.ts index 31a52dce8b..87325573eb 100644 --- a/packages/backend-core/tests/core/utilities/structures/db.ts +++ b/packages/backend-core/tests/core/utilities/structures/db.ts @@ -1,4 +1,4 @@ -import { structures } from ".." +import { generator } from "./generator" import { newid } from "../../../../src/docIds/newid" export function id() { @@ -6,7 +6,7 @@ export function id() { } export function rev() { - return `${structures.generator.character({ + return `${generator.character({ numeric: true, - })}-${structures.uuid().replace(/-/, "")}` + })}-${generator.guid().replace(/-/, "")}` } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts new file mode 100644 index 0000000000..1c82c5b7d4 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -0,0 +1 @@ +export * from "./platform" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts new file mode 100644 index 0000000000..46b85f0435 --- /dev/null +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -0,0 +1 @@ +export * as installation from "./installation" \ No newline at end of file diff --git a/packages/backend-core/tests/core/utilities/structures/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts similarity index 82% rename from packages/backend-core/tests/core/utilities/structures/installation.ts rename to packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index bd2ae4abfe..30d58fd349 100644 --- a/packages/backend-core/tests/core/utilities/structures/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,9 +1,11 @@ import { generator } from "@budibase/backend-core/tests" import { Installation } from "@budibase/types" +import * as db from "../../db" export function install(): Installation { return { _id: "install", + _rev: db.rev(), installId: generator.guid(), version: generator.string() } diff --git a/packages/backend-core/tests/core/utilities/structures/index.ts b/packages/backend-core/tests/core/utilities/structures/index.ts index c4404856e1..1a49e912fc 100644 --- a/packages/backend-core/tests/core/utilities/structures/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/index.ts @@ -2,7 +2,7 @@ export * from "./common" export * as accounts from "./accounts" export * as apps from "./apps" export * as db from "./db" -export * as installation from "./installation" +export * as docs from "./documents" export * as koa from "./koa" export * as licenses from "./licenses" export * as plugins from "./plugins" diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index 35c9156ec6..fae0c7d807 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -2,7 +2,9 @@ import { Billing, Customer, Feature, - License, OfflineLicense, + License, + OfflineIdentifier, + OfflineLicense, PlanModel, PlanType, PriceDuration, @@ -154,9 +156,16 @@ export function offlineLicense ( return { ...base, expireAt: new Date().toISOString(), - identifier: { - installId: generator.guid(), - tenantId: generator.guid() - } + identifier: offlineIdentifier() + } +} + +export function offlineIdentifier( + installId: string = generator.guid(), + tenantId: string = generator.guid(), +): OfflineIdentifier { + return { + installId, + tenantId } } \ No newline at end of file From c8fc67d230dcf4e7c322ba8730665d66f78209b7 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 16:55:48 +0100 Subject: [PATCH 22/33] Lint --- .../core/utilities/structures/accounts.ts | 2 +- .../utilities/structures/documents/index.ts | 2 +- .../structures/documents/platform/index.ts | 2 +- .../documents/platform/installation.ts | 4 +-- .../core/utilities/structures/licenses.ts | 16 ++++------ .../builder/portal/account/upgrade.svelte | 22 ++++++++------ packages/builder/src/stores/portal/admin.js | 2 +- packages/frontend-core/src/api/licensing.js | 3 +- packages/pro | 2 +- packages/types/src/api/account/license.ts | 2 +- packages/types/src/api/web/global/license.ts | 2 +- packages/types/src/sdk/licensing/license.ts | 2 +- packages/types/src/shared/typeUtils.ts | 2 +- packages/worker/__mocks__/@budibase/pro.ts | 6 ++-- .../src/api/controllers/global/license.ts | 16 +++++++--- .../worker/src/api/routes/global/license.ts | 29 ++++++++++++++----- .../api/routes/global/tests/license.spec.ts | 22 +++++++++----- packages/worker/src/tests/api/license.ts | 7 +++-- 18 files changed, 88 insertions(+), 55 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/accounts.ts b/packages/backend-core/tests/core/utilities/structures/accounts.ts index 42e9d4b63a..8476399aa3 100644 --- a/packages/backend-core/tests/core/utilities/structures/accounts.ts +++ b/packages/backend-core/tests/core/utilities/structures/accounts.ts @@ -29,7 +29,7 @@ export const account = (partial: Partial = {}): Account => { size: "10+", profession: "Software Engineer", quotaUsage: quotas.usage(), - ...partial + ...partial, } } diff --git a/packages/backend-core/tests/core/utilities/structures/documents/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/index.ts index 1c82c5b7d4..c3bfba3597 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/index.ts @@ -1 +1 @@ -export * from "./platform" \ No newline at end of file +export * from "./platform" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts index 46b85f0435..98a6314999 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/index.ts @@ -1 +1 @@ -export * as installation from "./installation" \ No newline at end of file +export * as installation from "./installation" diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 30d58fd349..25c5e50e00 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -7,6 +7,6 @@ export function install(): Installation { _id: "install", _rev: db.rev(), installId: generator.guid(), - version: generator.string() + version: generator.string(), } -} \ No newline at end of file +} diff --git a/packages/backend-core/tests/core/utilities/structures/licenses.ts b/packages/backend-core/tests/core/utilities/structures/licenses.ts index fae0c7d807..5cce84edfd 100644 --- a/packages/backend-core/tests/core/utilities/structures/licenses.ts +++ b/packages/backend-core/tests/core/utilities/structures/licenses.ts @@ -138,9 +138,7 @@ interface GenerateLicenseOpts { billing?: Billing } -export const license = ( - opts: GenerateLicenseOpts = {} -): License => { +export const license = (opts: GenerateLicenseOpts = {}): License => { return { features: opts.features || [], quotas: opts.quotas || quotas(), @@ -149,23 +147,21 @@ export const license = ( } } -export function offlineLicense ( - opts: GenerateLicenseOpts = {} -): OfflineLicense { +export function offlineLicense(opts: GenerateLicenseOpts = {}): OfflineLicense { const base = license(opts) return { ...base, expireAt: new Date().toISOString(), - identifier: offlineIdentifier() + identifier: offlineIdentifier(), } } export function offlineIdentifier( installId: string = generator.guid(), - tenantId: string = generator.guid(), + tenantId: string = generator.guid() ): OfflineIdentifier { return { installId, - tenantId + tenantId, } -} \ No newline at end of file +} diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 8e2fd5c641..8f76286c8e 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -11,7 +11,7 @@ ButtonGroup, notifications, CopyInput, - File + File, } from "@budibase/bbui" import { auth, admin } from "stores/portal" import { redirect } from "@roxi/routify" @@ -35,9 +35,7 @@ let offlineLicenseIdentifier = "" let offlineLicense = undefined - const offlineLicenseExtensions = [ - ".txt", - ] + const offlineLicenseExtensions = [".txt"] // Make sure page can't be visited directly in cloud $: { @@ -97,7 +95,7 @@ const license = await API.getOfflineLicense() if (license) { offlineLicense = { - name: "license" + name: "license", } } else { offlineLicense = undefined @@ -147,7 +145,7 @@ // prevent file preview jitter by assigning constant // as soon as possible offlineLicense = { - name: "license" + name: "license", } const reader = new FileReader() reader.readAsText(event.detail) @@ -202,7 +200,9 @@ {#if $admin.offlineMode} Installation identifier - Share this with support@budibase.com to obtain your offline license + Share this with support@budibase.com to obtain your offline license
@@ -263,8 +263,12 @@ You are currently on the {license.plan.type} plan
- If you purchase or update your plan on the account - portal, click the refresh button to sync those changes + If you purchase or update your plan on the account + portal, click the refresh button to sync those changes
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { diff --git a/packages/builder/src/stores/portal/admin.js b/packages/builder/src/stores/portal/admin.js index 19abcfd841..2106acac27 100644 --- a/packages/builder/src/stores/portal/admin.js +++ b/packages/builder/src/stores/portal/admin.js @@ -17,7 +17,7 @@ export const DEFAULT_CONFIG = { adminUser: { checked: false }, sso: { checked: false }, }, - offlineMode: false + offlineMode: false, } export function createAdminStore() { diff --git a/packages/frontend-core/src/api/licensing.js b/packages/frontend-core/src/api/licensing.js index 9fe98d5e74..987fc34cf5 100644 --- a/packages/frontend-core/src/api/licensing.js +++ b/packages/frontend-core/src/api/licensing.js @@ -1,5 +1,4 @@ export const buildLicensingEndpoints = API => ({ - // LICENSE KEY activateLicenseKey: async data => { @@ -31,7 +30,7 @@ export const buildLicensingEndpoints = API => ({ return API.post({ url: "/api/global/license/offline", body: { - offlineLicenseToken + offlineLicenseToken, }, }) }, diff --git a/packages/pro b/packages/pro index 1a5207d91f..b5124e76b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1a5207d91fb9e0835562c708dd9c421973026543 +Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 diff --git a/packages/types/src/api/account/license.ts b/packages/types/src/api/account/license.ts index e23f6cab97..edb1267ecf 100644 --- a/packages/types/src/api/account/license.ts +++ b/packages/types/src/api/account/license.ts @@ -36,4 +36,4 @@ export interface CreateOfflineLicenseRequest { export interface GetOfflineLicenseResponse { offlineLicenseToken: string license: OfflineLicense -} \ No newline at end of file +} diff --git a/packages/types/src/api/web/global/license.ts b/packages/types/src/api/web/global/license.ts index 900a0db96d..21d8876412 100644 --- a/packages/types/src/api/web/global/license.ts +++ b/packages/types/src/api/web/global/license.ts @@ -5,7 +5,7 @@ export interface ActivateLicenseKeyRequest { } export interface GetLicenseKeyResponse { - licenseKey: string, + licenseKey: string } // OFFLINE LICENSE diff --git a/packages/types/src/sdk/licensing/license.ts b/packages/types/src/sdk/licensing/license.ts index e8ad98fe7a..105c3680a3 100644 --- a/packages/types/src/sdk/licensing/license.ts +++ b/packages/types/src/sdk/licensing/license.ts @@ -2,7 +2,7 @@ import { PurchasedPlan, Quotas, Feature, Billing } from "." import { ISO8601 } from "../../shared" export interface OfflineIdentifier { - installId: string, + installId: string tenantId: string } diff --git a/packages/types/src/shared/typeUtils.ts b/packages/types/src/shared/typeUtils.ts index 143865c60b..fbe215fdb9 100644 --- a/packages/types/src/shared/typeUtils.ts +++ b/packages/types/src/shared/typeUtils.ts @@ -2,4 +2,4 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P] } -export type ISO8601 = string \ No newline at end of file +export type ISO8601 = string diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index bd6250fede..f1e79bd7c7 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -11,16 +11,16 @@ const pro = { activateOfflineLicense: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), - getIdentifierBase64: jest.fn() + getIdentifierBase64: jest.fn(), }, cache: { ...actual.licensing.cache, refresh: jest.fn(), - } + }, }, quotas: { ...actual.quotas, - getQuotaUsage: jest.fn() + getQuotaUsage: jest.fn(), }, } diff --git a/packages/worker/src/api/controllers/global/license.ts b/packages/worker/src/api/controllers/global/license.ts index fa95eeee0d..111cb5cea3 100644 --- a/packages/worker/src/api/controllers/global/license.ts +++ b/packages/worker/src/api/controllers/global/license.ts @@ -10,7 +10,9 @@ import { // LICENSE KEY -export async function activateLicenseKey(ctx: UserCtx) { +export async function activateLicenseKey( + ctx: UserCtx +) { const { licenseKey } = ctx.request.body await licensing.keys.activateLicenseKey(licenseKey) ctx.status = 200 @@ -33,13 +35,17 @@ export async function deleteLicenseKey(ctx: UserCtx) { // OFFLINE LICENSE -export async function activateOfflineLicenseToken(ctx: UserCtx) { +export async function activateOfflineLicenseToken( + ctx: UserCtx +) { const { offlineLicenseToken } = ctx.request.body await licensing.offline.activateOfflineLicenseToken(offlineLicenseToken) ctx.status = 200 } -export async function getOfflineLicenseToken(ctx: UserCtx) { +export async function getOfflineLicenseToken( + ctx: UserCtx +) { const offlineLicenseToken = await licensing.offline.getOfflineLicenseToken() if (offlineLicenseToken) { ctx.body = { offlineLicenseToken: "*" } @@ -54,7 +60,9 @@ export async function deleteOfflineLicenseToken(ctx: UserCtx) { ctx.status = 204 } -export async function getOfflineLicenseIdentifier(ctx: UserCtx) { +export async function getOfflineLicenseIdentifier( + ctx: UserCtx +) { const identifierBase64 = await licensing.offline.getIdentifierBase64() ctx.body = { identifierBase64 } ctx.status = 200 diff --git a/packages/worker/src/api/routes/global/license.ts b/packages/worker/src/api/routes/global/license.ts index 199dbb3846..b0e474e4d5 100644 --- a/packages/worker/src/api/routes/global/license.ts +++ b/packages/worker/src/api/routes/global/license.ts @@ -3,13 +3,17 @@ import * as controller from "../../controllers/global/license" import { middleware } from "@budibase/backend-core" import Joi from "joi" -const activateLicenseKeyValidator = middleware.joiValidator.body(Joi.object({ +const activateLicenseKeyValidator = middleware.joiValidator.body( + Joi.object({ licenseKey: Joi.string().required(), - }).required()) + }).required() +) -const activateOfflineLicenseValidator = middleware.joiValidator.body(Joi.object({ +const activateOfflineLicenseValidator = middleware.joiValidator.body( + Joi.object({ offlineLicenseToken: Joi.string().required(), -}).required()) + }).required() +) const router: Router = new Router() @@ -17,13 +21,24 @@ router .post("/api/global/license/refresh", controller.refresh) .get("/api/global/license/usage", controller.getQuotaUsage) // LICENSE KEY - .post("/api/global/license/key", activateLicenseKeyValidator, controller.activateLicenseKey) + .post( + "/api/global/license/key", + activateLicenseKeyValidator, + controller.activateLicenseKey + ) .get("/api/global/license/key", controller.getLicenseKey) .delete("/api/global/license/key", controller.deleteLicenseKey) // OFFLINE LICENSE - .post("/api/global/license/offline", activateOfflineLicenseValidator, controller.activateOfflineLicenseToken) + .post( + "/api/global/license/offline", + activateOfflineLicenseValidator, + controller.activateOfflineLicenseToken + ) .get("/api/global/license/offline", controller.getOfflineLicenseToken) .delete("/api/global/license/offline", controller.deleteOfflineLicenseToken) - .get("/api/global/license/offline/identifier", controller.getOfflineLicenseIdentifier) + .get( + "/api/global/license/offline/identifier", + controller.getOfflineLicenseIdentifier + ) export default router diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index 512d6c9c52..26e3b3dfb5 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -37,7 +37,9 @@ describe("/api/global/license", () => { describe("POST /api/global/license/key", () => { it("returns 200", async () => { - const res = await config.api.license.activateLicenseKey({ licenseKey: "licenseKey" }) + const res = await config.api.license.activateLicenseKey({ + licenseKey: "licenseKey", + }) expect(res.status).toBe(200) expect(licensing.keys.activateLicenseKey).toBeCalledWith("licenseKey") }) @@ -53,7 +55,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getLicenseKey() expect(res.status).toBe(200) expect(res.body).toEqual({ - licenseKey: "*" + licenseKey: "*", }) }) }) @@ -68,8 +70,12 @@ describe("/api/global/license", () => { describe("POST /api/global/license/offline", () => { it("activates offline license", async () => { - const res = await config.api.license.activateOfflineLicense({ offlineLicenseToken: "offlineLicenseToken"}) - expect(licensing.offline.activateOfflineLicense).toBeCalledWith("offlineLicenseToken") + const res = await config.api.license.activateOfflineLicense({ + offlineLicenseToken: "offlineLicenseToken", + }) + expect(licensing.offline.activateOfflineLicenseToken).toBeCalledWith( + "offlineLicenseToken" + ) expect(res.status).toBe(200) }) }) @@ -80,11 +86,13 @@ describe("/api/global/license", () => { expect(res.status).toBe(404) }) it("returns 200 + offline license token", async () => { - licensing.offline.getOfflineLicenseToken.mockResolvedValue("offlineLicenseToken") + licensing.offline.getOfflineLicenseToken.mockResolvedValue( + "offlineLicenseToken" + ) const res = await config.api.license.getOfflineLicense() expect(res.status).toBe(200) expect(res.body).toEqual({ - offlineLicenseToken: "*" + offlineLicenseToken: "*", }) }) }) @@ -103,7 +111,7 @@ describe("/api/global/license", () => { const res = await config.api.license.getOfflineLicenseIdentifier() expect(res.status).toBe(200) expect(res.body).toEqual({ - identifierBase64: "base64" + identifierBase64: "base64", }) }) }) diff --git a/packages/worker/src/tests/api/license.ts b/packages/worker/src/tests/api/license.ts index 76d3e88df7..a6645226af 100644 --- a/packages/worker/src/tests/api/license.ts +++ b/packages/worker/src/tests/api/license.ts @@ -1,6 +1,9 @@ import TestConfiguration from "../TestConfiguration" import { TestAPI } from "./base" -import { ActivateLicenseKeyRequest, ActivateOfflineLicenseRequest } from "@budibase/types" +import { + ActivateLicenseKeyRequest, + ActivateOfflineLicenseTokenRequest, +} from "@budibase/types" export class LicenseAPI extends TestAPI { constructor(config: TestConfiguration) { @@ -35,7 +38,7 @@ export class LicenseAPI extends TestAPI { .delete("/api/global/license/key") .set(this.config.defaultHeaders()) } - activateOfflineLicense = async (body: ActivateOfflineLicenseRequest) => { + activateOfflineLicense = async (body: ActivateOfflineLicenseTokenRequest) => { return this.request .post("/api/global/license/offline") .send(body) From 55efb6e618b827cd8fa46fd800fa368d168e66f6 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 14 Jul 2023 20:56:50 +0100 Subject: [PATCH 23/33] fix pro mock --- packages/worker/__mocks__/@budibase/pro.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/worker/__mocks__/@budibase/pro.ts b/packages/worker/__mocks__/@budibase/pro.ts index f1e79bd7c7..59c7939111 100644 --- a/packages/worker/__mocks__/@budibase/pro.ts +++ b/packages/worker/__mocks__/@budibase/pro.ts @@ -8,7 +8,7 @@ const pro = { deleteLicenseKey: jest.fn(), }, offline: { - activateOfflineLicense: jest.fn(), + activateOfflineLicenseToken: jest.fn(), getOfflineLicenseToken: jest.fn(), deleteOfflineLicenseToken: jest.fn(), getIdentifierBase64: jest.fn(), From 797f281f6d6ce6d1a4303d089d9783b0010785ef Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 17 Jul 2023 20:55:26 +0100 Subject: [PATCH 24/33] OpenAPI 3.0 docs for offline license and license key endpoints --- packages/worker/openapi-3.0.yaml | 133 +++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/worker/openapi-3.0.yaml diff --git a/packages/worker/openapi-3.0.yaml b/packages/worker/openapi-3.0.yaml new file mode 100644 index 0000000000..c68654fffb --- /dev/null +++ b/packages/worker/openapi-3.0.yaml @@ -0,0 +1,133 @@ +openapi: 3.0.0 +info: + title: Worker API Specification + version: 1.0.0 +servers: + - url: "http://localhost:10000" + description: localhost + - url: "https://budibaseqa.app" + description: QA + - url: "https://preprod.qa.budibase.net" + description: Preprod + - url: "https://budibase.app" + description: Production + +tags: + - name: license + description: License operations + +paths: + /api/global/license/key: + post: + tags: + - license + summary: Activate license key + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateLicenseKeyRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get license key + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetLicenseKeyResponse' + delete: + tags: + - license + summary: Delete license key + responses: + '204': + description: No content + /api/global/license/offline: + post: + tags: + - license + summary: Activate offline license + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateOfflineLicenseTokenRequest' + responses: + '200': + description: Success + get: + tags: + - license + summary: Get offline license + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineLicenseTokenResponse' + delete: + tags: + - license + summary: Delete offline license + responses: + '204': + description: No content + /api/global/license/offline/identifier: + get: + tags: + - license + summary: Get offline identifier + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/GetOfflineIdentifierResponse' + +components: + schemas: + ActivateOfflineLicenseTokenRequest: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + GetOfflineLicenseTokenResponse: + type: object + properties: + offlineLicenseToken: + type: string + required: + - offlineLicenseToken + ActivateLicenseKeyRequest: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetLicenseKeyResponse: + type: object + properties: + licenseKey: + type: string + required: + - licenseKey + GetOfflineIdentifierResponse: + type: object + properties: + identifierBase64: + type: string + required: + - identifierBase64 \ No newline at end of file From a6073e0ecd834cb17b802486ee7b7b4e926c6a12 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 14:56:01 +0100 Subject: [PATCH 26/33] Build fixes --- .../utilities/structures/documents/platform/installation.ts | 2 +- packages/pro | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts index 25c5e50e00..711c6cf14f 100644 --- a/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts +++ b/packages/backend-core/tests/core/utilities/structures/documents/platform/installation.ts @@ -1,4 +1,4 @@ -import { generator } from "@budibase/backend-core/tests" +import { generator } from "../../generator" import { Installation } from "@budibase/types" import * as db from "../../db" diff --git a/packages/pro b/packages/pro index b5124e76b9..5775dda6b9 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit b5124e76b9fa8020641e8d019ac1713c6245d6e6 +Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa From c3412f0cfbcb2795083926db6f92100c3daf7b4e Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:06:43 +0100 Subject: [PATCH 27/33] Update openapi.json --- packages/server/specs/openapi.json | 9 ++++++--- packages/server/specs/openapi.yaml | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/server/specs/openapi.json b/packages/server/specs/openapi.json index bcff78e861..d97b09568c 100644 --- a/packages/server/specs/openapi.json +++ b/packages/server/specs/openapi.json @@ -841,7 +841,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1045,7 +1046,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, @@ -1260,7 +1262,8 @@ "auto", "json", "internal", - "barcodeqr" + "barcodeqr", + "bigint" ], "description": "Defines the type of the column, most explain themselves, a link column is a relationship." }, diff --git a/packages/server/specs/openapi.yaml b/packages/server/specs/openapi.yaml index a9daed6c76..86807c9981 100644 --- a/packages/server/specs/openapi.yaml +++ b/packages/server/specs/openapi.yaml @@ -768,6 +768,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -931,6 +932,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: @@ -1101,6 +1103,7 @@ components: - json - internal - barcodeqr + - bigint description: Defines the type of the column, most explain themselves, a link column is a relationship. constraints: From 4e88779b5ed5d5e3fa91171e8d9c576080e10707 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 15:31:19 +0100 Subject: [PATCH 28/33] Remove 'nightly' from test discord notification --- qa-core/scripts/testResultsWebhook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa-core/scripts/testResultsWebhook.js b/qa-core/scripts/testResultsWebhook.js index 40cc42082d..5fbdd3a32e 100644 --- a/qa-core/scripts/testResultsWebhook.js +++ b/qa-core/scripts/testResultsWebhook.js @@ -42,7 +42,7 @@ async function discordResultsNotification(report) { Accept: "application/json", }, body: JSON.stringify({ - content: `**Nightly Tests Status**: ${OUTCOME}`, + content: `**Tests Status**: ${OUTCOME}`, embeds: [ { title: `Budi QA Bot - ${env}`, From 3fb0380eec3d06c2136789204f51a3b3476518b1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 16:50:57 +0100 Subject: [PATCH 29/33] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 5775dda6b9..1b8fd8ed44 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 5775dda6b9fe8badd5253a20ef4c78b0bd687bfa +Subproject commit 1b8fd8ed445c4c25210f8faf07ff404c41fbc805 From 552f96fb81c78c6f3be9882bf4b5fcdb783753d2 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Thu, 20 Jul 2023 21:37:24 +0100 Subject: [PATCH 30/33] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 1b8fd8ed44..88b3c19262 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 1b8fd8ed445c4c25210f8faf07ff404c41fbc805 +Subproject commit 88b3c192624d19f37d5743afc36b92f1c1ebb1db From 8f0043157d885a2f6861c7c767b7b55a1853825a Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Fri, 21 Jul 2023 08:43:53 +0100 Subject: [PATCH 31/33] Styling fix for license key input --- .../src/pages/builder/portal/account/upgrade.svelte | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 8f76286c8e..5c1e8bee0e 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -205,10 +205,8 @@ >
-
-
+
-
@@ -291,8 +289,11 @@ } .field { display: grid; - grid-template-columns: 300px 1fr; + grid-template-columns: 100px 1fr; grid-gap: var(--spacing-l); align-items: center; } + .identifier-input { + width: 300px; + } From bfb7586b49f08288ae4d1886c4da756552c0bc5f Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 25 Jul 2023 11:19:08 +0100 Subject: [PATCH 32/33] Lint --- .../builder/src/pages/builder/portal/account/upgrade.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/portal/account/upgrade.svelte b/packages/builder/src/pages/builder/portal/account/upgrade.svelte index 5c1e8bee0e..b9ce143728 100644 --- a/packages/builder/src/pages/builder/portal/account/upgrade.svelte +++ b/packages/builder/src/pages/builder/portal/account/upgrade.svelte @@ -206,7 +206,7 @@
- +
From 0c095fdbefa08952448616b35d962efbe52e8941 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Tue, 25 Jul 2023 11:22:00 +0100 Subject: [PATCH 33/33] Update pro ref --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 88b3c19262..44fa63cc0f 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 88b3c192624d19f37d5743afc36b92f1c1ebb1db +Subproject commit 44fa63cc0f536648ac57ba61b62c60dd2812dbc6