diff --git a/lerna.json b/lerna.json index e9d7d7208b..50e7e77b52 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.46-alpha.4", + "version": "1.0.46-alpha.5", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 4068a4fc6e..31133af249 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.46-alpha.4", + "version": "1.0.46-alpha.5", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/constants.js b/packages/backend-core/src/constants.js index 8e6b01608e..28b9ced49b 100644 --- a/packages/backend-core/src/constants.js +++ b/packages/backend-core/src/constants.js @@ -8,7 +8,6 @@ exports.Cookies = { Auth: "budibase:auth", Init: "budibase:init", OIDC_CONFIG: "budibase:oidc:config", - RETURN_URL: "budibase:returnurl", } exports.Headers = { diff --git a/packages/backend-core/src/utils.js b/packages/backend-core/src/utils.js index 85dd32946f..8c00f2a8b8 100644 --- a/packages/backend-core/src/utils.js +++ b/packages/backend-core/src/utils.js @@ -96,12 +96,7 @@ exports.getCookie = (ctx, name) => { * @param {string|object} value The value of cookie which will be set. * @param {object} opts options like whether to sign. */ -exports.setCookie = ( - ctx, - value, - name = "builder", - opts = { sign: true, requestDomain: false } -) => { +exports.setCookie = (ctx, value, name = "builder", opts = { sign: true }) => { if (value && opts && opts.sign) { value = jwt.sign(value, options.secretOrKey) } @@ -113,7 +108,7 @@ exports.setCookie = ( overwrite: true, } - if (environment.COOKIE_DOMAIN && !opts.requestDomain) { + if (environment.COOKIE_DOMAIN) { config.domain = environment.COOKIE_DOMAIN } diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock index f28f2f932f..fc70e3d6a1 100644 --- a/packages/backend-core/yarn.lock +++ b/packages/backend-core/yarn.lock @@ -3410,9 +3410,9 @@ node-fetch@2.6.0: integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== node-fetch@^2.6.1: - version "2.6.6" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" diff --git a/packages/bbui/package.json b/packages/bbui/package.json index e31d0e8bc6..14b5afbfca 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.46-alpha.4", + "version": "1.0.46-alpha.5", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 82c582e186..d7b20b0864 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "1.0.46-alpha.4", + "version": "1.0.46-alpha.5", "license": "GPL-3.0", "private": true, "scripts": { @@ -65,11 +65,11 @@ } }, "dependencies": { - "@budibase/bbui": "^1.0.46-alpha.4", - "@budibase/client": "^1.0.46-alpha.4", - "@budibase/frontend-core": "^1.0.46-alpha.4", + "@budibase/bbui": "^1.0.46-alpha.5", + "@budibase/client": "^1.0.46-alpha.5", + "@budibase/frontend-core": "^1.0.46-alpha.5", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^1.0.46-alpha.4", + "@budibase/string-templates": "^1.0.46-alpha.5", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 23704556ad..5181e756c6 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -1,6 +1,5 @@ import { getFrontendStore } from "./store/frontend" import { getAutomationStore } from "./store/automation" -import { getHostingStore } from "./store/hosting" import { getThemeStore } from "./store/theme" import { derived, writable } from "svelte/store" import { FrontendTypes, LAYOUT_NAMES } from "../constants" @@ -9,7 +8,6 @@ import { findComponent } from "./componentUtils" export const store = getFrontendStore() export const automationStore = getAutomationStore() export const themeStore = getThemeStore() -export const hostingStore = getHostingStore() export const currentAsset = derived(store, $store => { const type = $store.currentFrontEndType diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 905cdd3976..62bcc30f8f 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -2,7 +2,6 @@ import { get, writable } from "svelte/store" import { cloneDeep } from "lodash/fp" import { allScreens, - hostingStore, currentAsset, mainLayout, selectedComponent, @@ -99,7 +98,6 @@ export const getFrontendStore = () => { // Initialise backend stores database.set(application.instance) - await hostingStore.actions.fetch() await datasources.init() await integrations.init() await queries.init() diff --git a/packages/builder/src/builderStore/store/hosting.js b/packages/builder/src/builderStore/store/hosting.js deleted file mode 100644 index 980130b147..0000000000 --- a/packages/builder/src/builderStore/store/hosting.js +++ /dev/null @@ -1,53 +0,0 @@ -import { writable } from "svelte/store" -import { API } from "api" -import { notifications } from "@budibase/bbui" - -const INITIAL_HOSTING_UI_STATE = { - appUrl: "", - deployedApps: {}, - deployedAppNames: [], - deployedAppUrls: [], -} - -export const getHostingStore = () => { - const store = writable({ ...INITIAL_HOSTING_UI_STATE }) - store.actions = { - fetch: async () => { - try { - const urls = await API.getHostingURLs() - store.update(state => { - state.appUrl = urls.app - return state - }) - } catch (error) { - store.update(state => { - state.appUrl = "" - return state - }) - notifications.error("Error fetching hosting URLs") - } - }, - fetchDeployedApps: async () => { - try { - const deployments = await API.getDeployedApps() - store.update(state => { - state.deployedApps = deployments - state.deployedAppNames = Object.values(deployments).map( - app => app.name - ) - state.deployedAppUrls = Object.values(deployments).map(app => app.url) - return state - }) - } catch (error) { - store.update(state => { - state.deployedApps = {} - state.deployedAppNames = [] - state.deployedAppUrls = [] - return state - }) - notifications.error("Failed detching deployed apps") - } - }, - } - return store -} diff --git a/packages/builder/src/components/deploy/DeploymentHistory.svelte b/packages/builder/src/components/deploy/DeploymentHistory.svelte index 8917161539..e933142348 100644 --- a/packages/builder/src/components/deploy/DeploymentHistory.svelte +++ b/packages/builder/src/components/deploy/DeploymentHistory.svelte @@ -6,7 +6,7 @@ import { API } from "api" import { notifications } from "@budibase/bbui" import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte" - import { store, hostingStore } from "builderStore" + import { store } from "builderStore" const DeploymentStatus = { SUCCESS: "SUCCESS", @@ -37,7 +37,7 @@ let poll let deployments = [] let urlComponent = $store.url || `/${appId}` - let deploymentUrl = `${$hostingStore.appUrl}${urlComponent}` + let deploymentUrl = `${urlComponent}` const formatDate = (date, format) => Intl.DateTimeFormat("en-GB", DATE_OPTIONS[format]).format(date) diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index c81a62d42c..91c4807dc8 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -1,100 +1,46 @@ {#if template?.fromFile} { $values.file = e.detail?.[0] - $touched.file = true + $validation.touched.file = true }} /> {/if} ($touched.name = true)} + error={$validation.touched.name && $validation.errors.name} + on:blur={() => ($validation.touched.name = true)} label="Name" placeholder={$auth.user.firstName - ? `${$auth.user.firstName}'s app` + ? `${$auth.user.firstName}s app` : "My app"} /> + ($validation.touched.url = true)} + label="URL" + placeholder={$values.name + ? "/" + encodeURIComponent($values.name).toLowerCase() + : "/"} + /> diff --git a/packages/builder/src/components/start/UpdateAppModal.svelte b/packages/builder/src/components/start/UpdateAppModal.svelte index 2caf9af7ef..1ce699b834 100644 --- a/packages/builder/src/components/start/UpdateAppModal.svelte +++ b/packages/builder/src/components/start/UpdateAppModal.svelte @@ -1,119 +1,75 @@ - - - Update the name of your app. - ($touched.name = true)} - on:change={() => (dirty = true)} - label="Name" - /> - - + + Update the name of your app. + ($validation.touched.name = true)} + label="Name" + /> + ($validation.touched.url = true)} + label="URL" + placeholder={$values.name + ? "/" + encodeURIComponent($values.name).toLowerCase() + : "/"} + /> + diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js index 04f12672e8..6b7aafdfa9 100644 --- a/packages/builder/src/constants/index.js +++ b/packages/builder/src/constants/index.js @@ -36,4 +36,7 @@ export const LAYOUT_NAMES = { export const BUDIBASE_INTERNAL_DB = "bb_internal" +// one or more word characters and whitespace export const APP_NAME_REGEX = /^[\w\s]+$/ +// zero or more non-whitespace characters +export const APP_URL_REGEX = /^\S*$/ diff --git a/packages/builder/src/helpers/validation/validation.js b/packages/builder/src/helpers/validation/validation.js index 8d80d720a1..db5dfe4430 100644 --- a/packages/builder/src/helpers/validation/validation.js +++ b/packages/builder/src/helpers/validation/validation.js @@ -1,5 +1,7 @@ import { writable, derived } from "svelte/store" +// DEPRECATED - Use the yup based validators for future validation + export function createValidationStore(initialValue, ...validators) { let touched = false diff --git a/packages/builder/src/helpers/validation/validators.js b/packages/builder/src/helpers/validation/validators.js index 036487fd50..f842f11313 100644 --- a/packages/builder/src/helpers/validation/validators.js +++ b/packages/builder/src/helpers/validation/validators.js @@ -1,3 +1,5 @@ +// TODO: Convert to yup based validators + export function emailValidator(value) { return ( (value && diff --git a/packages/builder/src/helpers/validation/yup/app.js b/packages/builder/src/helpers/validation/yup/app.js new file mode 100644 index 0000000000..de0f86446c --- /dev/null +++ b/packages/builder/src/helpers/validation/yup/app.js @@ -0,0 +1,83 @@ +import { string, mixed } from "yup" +import { APP_NAME_REGEX, APP_URL_REGEX } from "constants" + +export const name = (validation, { apps, currentApp } = { apps: [] }) => { + validation.addValidator( + "name", + string() + .trim() + .required("Your application must have a name") + .matches( + APP_NAME_REGEX, + "App name must be letters, numbers and spaces only" + ) + .test( + "non-existing-app-name", + "Another app with the same name already exists", + value => { + if (!value) { + // exit early, above validator will fail + return true + } + if (currentApp) { + // filter out the current app if present + apps = apps.filter(app => app.appId !== currentApp.appId) + } + return !apps + .map(app => app.name) + .some(appName => appName.toLowerCase() === value.toLowerCase()) + } + ) + ) +} + +export const url = (validation, { apps, currentApp } = { apps: [] }) => { + validation.addValidator( + "url", + string() + .nullable() + .matches(APP_URL_REGEX, "App URL must not contain spaces") + .test( + "non-existing-app-url", + "Another app with the same URL already exists", + value => { + // url is nullable + if (!value) { + return true + } + if (currentApp) { + // filter out the current app if present + apps = apps.filter(app => app.appId !== currentApp.appId) + } + return !apps + .map(app => app.url) + .some(appUrl => appUrl?.toLowerCase() === value.toLowerCase()) + } + ) + .test("valid-url", "Not a valid URL", value => { + // url is nullable + if (!value) { + return true + } + // make it clear that this is a url path and cannot be a full url + return ( + value.startsWith("/") && + !value.includes("http") && + !value.includes("www") && + !value.includes(".") && + value.length > 1 // just '/' is not valid + ) + }) + ) +} + +export const file = (validation, { template } = {}) => { + const templateToUse = + template && Object.keys(template).length === 0 ? null : template + validation.addValidator( + "file", + templateToUse?.fromFile + ? mixed().required("Please choose a file to import") + : null + ) +} diff --git a/packages/builder/src/helpers/validation/yup/index.js b/packages/builder/src/helpers/validation/yup/index.js new file mode 100644 index 0000000000..6783ad7e58 --- /dev/null +++ b/packages/builder/src/helpers/validation/yup/index.js @@ -0,0 +1,66 @@ +import { capitalise } from "helpers" +import { object } from "yup" +import { writable, get } from "svelte/store" +import { notifications } from "@budibase/bbui" + +export const createValidationStore = () => { + const DEFAULT = { + errors: {}, + touched: {}, + valid: false, + } + + const validator = {} + const validation = writable(DEFAULT) + + const addValidator = (propertyName, propertyValidator) => { + if (!propertyValidator || !propertyName) { + return + } + validator[propertyName] = propertyValidator + } + + const check = async values => { + const obj = object().shape(validator) + // clear the previous errors + const properties = Object.keys(validator) + properties.forEach(property => (get(validation).errors[property] = null)) + + let validationError = false + try { + await obj.validate(values, { abortEarly: false }) + } catch (error) { + if (!error.inner) { + notifications.error("Unexpected validation error", error) + validationError = true + } else { + error.inner.forEach(err => { + validation.update(store => { + store.errors[err.path] = capitalise(err.message) + return store + }) + }) + } + } + + let valid + if (properties.length && !validationError) { + valid = await obj.isValid(values) + } else { + // don't say valid until validators have been loaded + valid = false + } + + validation.update(store => { + store.valid = valid + return store + }) + } + + return { + subscribe: validation.subscribe, + set: validation.set, + check, + addValidator, + } +} diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index b4655d8f04..39cc780ac7 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -13,7 +13,7 @@ notifications, } from "@budibase/bbui" import { onMount } from "svelte" - import { apps, organisation, auth, admin } from "stores/portal" + import { apps, organisation, auth } from "stores/portal" import { goto } from "@roxi/routify" import { AppStatus } from "constants" import { gradient } from "actions" @@ -39,7 +39,6 @@ const publishedAppsOnly = app => app.status === AppStatus.DEPLOYED $: publishedApps = $apps.filter(publishedAppsOnly) - $: isCloud = $admin.cloud $: userApps = $auth.user?.builder?.global ? publishedApps : publishedApps.filter(app => @@ -47,7 +46,11 @@ ) function getUrl(app) { - return !isCloud ? `/app/${encodeURIComponent(app.name)}` : `/${app.prodId}` + if (app.url) { + return `/app${app.url}` + } else { + return `/${app.prodId}` + } } const logout = async () => { diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index e71dc325fb..ec463d5497 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -49,7 +49,6 @@ $: filteredApps = enrichedApps.filter(app => app?.name?.toLowerCase().includes(searchTerm.toLowerCase()) ) - $: isCloud = $admin.cloud const enrichApps = (apps, user, sortBy) => { const enrichedApps = apps.map(app => ({ @@ -80,7 +79,7 @@ } const initiateAppCreation = () => { - template = {} + template = null creationModal.show() creatingApp = true } @@ -148,12 +147,10 @@ } const viewApp = app => { - if (!isCloud && app.deployed) { - // special case to use the short form name if self hosted - window.open(`/app/${encodeURIComponent(app.name)}`) + if (app.url) { + window.open(`/app${app.url}`) } else { - const id = app.deployed ? app.prodId : app.devId - window.open(`/${id}`, "_blank") + window.open(`/${app.prodId}`) } } @@ -420,6 +417,11 @@ > + + + + + {selectedApp?.name}? -