diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte
index 53450c2200..f97f91df6b 100644
--- a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte
+++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte
@@ -21,7 +21,7 @@
return componentName || "element"
}
- const screenPlaceholder = {
+ const screenPlaceholder = {
name: "Screen Placeholder",
route: "*",
props: {
@@ -60,9 +60,8 @@
},
}
-
$: hasComponent = !!$store.currentPreviewItem
-
+
$: {
styles = ""
// Apply the CSS from the currently selected page and its screens
@@ -88,11 +87,10 @@
libraries: $store.libraries,
page: $store.pages[$store.currentPageName],
screens: [
- $store.currentFrontEndType === "page"
- ? screenPlaceholder
- : $store.currentPreviewItem,
+ $store.currentFrontEndType === "page"
+ ? screenPlaceholder
+ : $store.currentPreviewItem,
],
- appRootPath: "",
}
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
@@ -102,20 +100,26 @@
: ""
const refreshContent = () => {
- iframe.contentWindow.postMessage(JSON.stringify({
- styles,
- stylesheetLinks,
- selectedComponentType,
- selectedComponentId,
- frontendDefinition,
- }))
+ iframe.contentWindow.postMessage(
+ JSON.stringify({
+ styles,
+ stylesheetLinks,
+ selectedComponentType,
+ selectedComponentId,
+ frontendDefinition,
+ appId: $store.appId,
+ })
+ )
}
- $: if(iframe) iframe.contentWindow.addEventListener("bb-ready", refreshContent, { once: true })
+ $: if (iframe)
+ iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
+ once: true,
+ })
- $: if(iframe && frontendDefinition) {
- refreshContent()
- }
+ $: if (iframe && frontendDefinition) {
+ refreshContent()
+ }
diff --git a/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte.orig b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte.orig
new file mode 100644
index 0000000000..81c3624274
--- /dev/null
+++ b/packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte.orig
@@ -0,0 +1,161 @@
+
+
+
+ {#if hasComponent && $store.currentPreviewItem}
+
+ {/if}
+
+
+
diff --git a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js
index 3d305f1c1d..b1bda4ab4f 100644
--- a/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js
+++ b/packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js
@@ -44,6 +44,7 @@ export default `
document.head.appendChild(styles)
styles.appendChild(document.createTextNode(data.styles))
+ document.cookie = "budibase:appid=" + data.appId
window["##BUDIBASE_FRONTEND_DEFINITION##"] = data.frontendDefinition;
if (clientModule) {
clientModule.loadBudibase({ window, localStorage })
diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
index bfe5507256..bcebb4d2d4 100644
--- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
+++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
@@ -13,7 +13,6 @@
import CodeEditor from "./CodeEditor.svelte"
import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor"
-
import panelStructure from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte"
import DesignView from "./DesignView.svelte"
@@ -37,26 +36,11 @@
//use for getting controls for each component property
c => c._component === componentInstance._component
) || {}
-
+
let panelDefinition = {}
- $: {
- if(componentPropDefinition.properties) {
- if(selectedCategory.value === "design") {
- panelDefinition = componentPropDefinition.properties["design"]
- }else{
- let panelDef = componentPropDefinition.properties["settings"]
- if($store.currentFrontEndType === "page" && $store.currentView !== "component") {
- panelDefinition = [...page,...panelDef]
- }else if($store.currentFrontEndType === "screen" && $store.currentView !== "component") {
- panelDefinition = [...screen, ...panelDef]
- }else {
- panelDefinition = panelDef
- }
- }
- }
- }
-
+ $: panelDefinition = componentPropDefinition.properties &&
+ componentPropDefinition.properties[selectedCategory.value]
const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp
@@ -102,7 +86,9 @@
{componentInstance}
{componentDefinition}
{panelDefinition}
- onChange={onPropChanged} />
+ onChange={onPropChanged}
+ onScreenPropChange={store.setPageOrScreenProp}
+ screenOrPageInstance={$store.currentView !== "component" && $store.currentPreviewItem} />
{:else if selectedCategory.value === 'events'}
{/if}
diff --git a/packages/builder/src/components/userInterface/PagesList.svelte b/packages/builder/src/components/userInterface/PagesList.svelte
index 4a87c4ac8a..5a78ef7121 100644
--- a/packages/builder/src/components/userInterface/PagesList.svelte
+++ b/packages/builder/src/components/userInterface/PagesList.svelte
@@ -9,11 +9,11 @@
const pages = [
{
- title: "Main",
+ title: "Private",
id: "main",
},
{
- title: "Login",
+ title: "Public",
id: "unauthenticated",
},
]
diff --git a/packages/builder/src/components/userInterface/SettingsView.svelte b/packages/builder/src/components/userInterface/SettingsView.svelte
index 99e704d1d9..17e4fe5e71 100644
--- a/packages/builder/src/components/userInterface/SettingsView.svelte
+++ b/packages/builder/src/components/userInterface/SettingsView.svelte
@@ -2,20 +2,60 @@
import PropertyControl from "./PropertyControl.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte"
import Colorpicker from "../common/Colorpicker.svelte"
+ import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js"
+ import Input from "../common/Input.svelte"
export let panelDefinition = []
export let componentDefinition = {}
export let componentInstance = {}
export let onChange = () => {}
+ export let onScreenPropChange = () => {}
+ export let screenOrPageInstance
const propExistsOnComponentDef = prop => prop in componentDefinition.props
function handleChange(key, data) {
data.target ? onChange(key, data.target.value) : onChange(key, data)
}
+
+ function handleScreenPropChange (name, value) {
+ onScreenPropChange(name,value)
+ if(!isPage && name === "name") {
+ // screen name is changed... change URL
+ $goto(`./:page/${value}`)
+ }
+ }
+
+ const screenDefinition = [
+ { key: "name", label: "Name", control: Input },
+ { key: "description", label: "Description", control: Input },
+ { key: "route", label: "Route", control: Input },
+ ]
+
+ const pageDefinition = [
+ { key: "title", label: "Title", control: Input },
+ { key: "favicon", label: "Favicon", control: Input },
+ ]
+
+ $: isPage = screenOrPageInstance && screenOrPageInstance.favicon
+ $: screenOrPageDefinition = isPage ? pageDefinition : screenDefinition
+
+{#if screenOrPageInstance}
+ {#each screenOrPageDefinition as def}
+
+ {/each}
+
+{/if}
+
{#if panelDefinition && panelDefinition.length > 0}
{#each panelDefinition as definition}
{#if propExistsOnComponentDef(definition.key)}
diff --git a/packages/builder/src/components/userInterface/propertyCategories.js b/packages/builder/src/components/userInterface/propertyCategories.js
index 38ef0558e4..c0485304ce 100644
--- a/packages/builder/src/components/userInterface/propertyCategories.js
+++ b/packages/builder/src/components/userInterface/propertyCategories.js
@@ -12,8 +12,9 @@ export const layout = [
label: "Display",
key: "display",
control: OptionSelect,
- initialValue: "Flex",
+ initialValue: "",
options: [
+ { label: "", value: "" },
{ label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" },
],
@@ -39,6 +40,7 @@ export const layout = [
control: OptionSelect,
initialValue: "Flex Start",
options: [
+ { label: "", value: "" },
{ label: "Flex Start", value: "flex-start" },
{ label: "Flex End", value: "flex-end" },
{ label: "Center", value: "center" },
@@ -317,19 +319,31 @@ export const border = [
{
label: "Radius",
key: "border-radius",
- control: Input,
- width: "48px",
- placeholder: "px",
- textAlign: "center",
+ control: OptionSelect,
+ defaultValue: "None",
+ options: [
+ { label: "None", value: "0" },
+ { label: "small", value: "0.125rem" },
+ { label: "Medium", value: "0.25rem;" },
+ { label: "Large", value: "0.375rem" },
+ { label: "Extra large", value: "0.5rem" },
+ { label: "Full", value: "5678px" },
+ ],
},
{
label: "Width",
key: "border-width",
- control: Input,
- width: "48px",
- placeholder: "px",
- textAlign: "center",
- }, //custom
+ control: OptionSelect,
+ defaultValue: "None",
+ options: [
+ { label: "None", value: "0" },
+ { label: "Extra small", value: "0.5px" },
+ { label: "Small", value: "1px" },
+ { label: "Medium", value: "2px" },
+ { label: "Large", value: "4px" },
+ { label: "Extra large", value: "8px" },
+ ],
+ },
{
label: "Color",
key: "border-color",
@@ -339,6 +353,7 @@ export const border = [
label: "Style",
key: "border-style",
control: OptionSelect,
+ defaultValue: "None",
options: [
"none",
"hidden",
@@ -365,17 +380,50 @@ export const effects = [
},
{
label: "Rotate",
- key: "transform",
- control: Input,
- width: "48px",
- textAlign: "center",
- placeholder: "deg",
+ key: "transform-rotate",
+ control: OptionSelect,
+ defaultValue: "0",
+ options: [
+ "0",
+ "45deg",
+ "90deg",
+ "90deg",
+ "135deg",
+ "180deg",
+ "225deg",
+ "270deg",
+ "315dev",
+ ],
}, //needs special control
{
label: "Shadow",
key: "box-shadow",
- control: InputGroup,
- meta: [{ placeholder: "X" }, { placeholder: "Y" }, { placeholder: "B" }],
+ control: OptionSelect,
+ defaultValue: "None",
+ options: [
+ { label: "None", value: "none" },
+ { label: "Extra small", value: "0 1px 2px 0 rgba(0, 0, 0, 0.05)" },
+ {
+ label: "Small",
+ value:
+ "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)",
+ },
+ {
+ label: "Medium",
+ value:
+ "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
+ },
+ {
+ label: "Large",
+ value:
+ "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
+ },
+ {
+ label: "Extra large",
+ value:
+ "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
+ },
+ ],
},
]
diff --git a/packages/builder/src/components/userInterface/temporaryPanelStructure.js b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
index afd3a06694..7315e09736 100644
--- a/packages/builder/src/components/userInterface/temporaryPanelStructure.js
+++ b/packages/builder/src/components/userInterface/temporaryPanelStructure.js
@@ -11,6 +11,18 @@ export default {
name: "Basic",
isCategory: true,
children: [
+ {
+ _component: "@budibase/standard-components/embed",
+ icon: "ri-code-line",
+ name: "Embed",
+ description: "Embed content from 3rd party sources",
+ properties: {
+ design: {
+ ...all,
+ },
+ settings: [{ label: "Embed", key: "embed", control: Input }],
+ },
+ },
{
_component: "@budibase/standard-components/container",
name: "Container",
diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js
index f019897494..373ac1809e 100644
--- a/packages/client/src/api/index.js
+++ b/packages/client/src/api/index.js
@@ -1,9 +1,9 @@
import { authenticate } from "./authenticate"
import { triggerWorkflow } from "./workflow"
-export const createApi = ({ rootPath = "", setState, getState }) => {
+export const createApi = ({ setState, getState }) => {
const apiCall = method => async ({ url, body }) => {
- const response = await fetch(`${rootPath}${url}`, {
+ const response = await fetch(url, {
method: method,
headers: {
"Content-Type": "application/json",
@@ -45,7 +45,6 @@ export const createApi = ({ rootPath = "", setState, getState }) => {
const isSuccess = obj => !obj || !obj[ERROR_MEMBER]
const apiOpts = {
- rootPath,
setState,
getState,
isSuccess,
diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js
index a80c1009c7..a8874960e0 100644
--- a/packages/client/src/createApp.js
+++ b/packages/client/src/createApp.js
@@ -2,6 +2,7 @@ import { attachChildren } from "./render/attachChildren"
import { createTreeNode } from "./render/prepareRenderComponent"
import { screenRouter } from "./render/screenRouter"
import { createStateManager } from "./state/stateManager"
+import { getAppId } from "./render/getAppId"
export const createApp = ({
componentLibraries,
@@ -15,11 +16,9 @@ export const createApp = ({
const onScreenSlotRendered = screenSlotNode => {
const onScreenSelected = (screen, url) => {
const stateManager = createStateManager({
- frontendDefinition,
componentLibraries,
onScreenSlotRendered: () => {},
routeTo,
- appRootPath: frontendDefinition.appRootPath,
})
const getAttachChildrenParams = attachChildrenParams(stateManager)
screenSlotNode.props._children = [screen.props]
@@ -36,12 +35,8 @@ export const createApp = ({
routeTo = screenRouter({
screens: frontendDefinition.screens,
onScreenSelected,
- appRootPath: frontendDefinition.appRootPath,
})
- const fallbackPath = window.location.pathname.replace(
- frontendDefinition.appRootPath,
- ""
- )
+ const fallbackPath = window.location.pathname.replace(getAppId(), "")
routeTo(currentUrl || fallbackPath)
}
@@ -59,10 +54,8 @@ export const createApp = ({
let rootTreeNode
const pageStateManager = createStateManager({
- frontendDefinition,
componentLibraries,
onScreenSlotRendered,
- appRootPath: frontendDefinition.appRootPath,
// seems weird, but the routeTo variable may not be available at this point
routeTo: url => routeTo(url),
})
diff --git a/packages/client/src/index.js b/packages/client/src/index.js
index 95f722d445..a54ad79420 100644
--- a/packages/client/src/index.js
+++ b/packages/client/src/index.js
@@ -1,5 +1,6 @@
import { createApp } from "./createApp"
import { builtins, builtinLibName } from "./render/builtinComponents"
+import { getAppId } from "./render/getAppId"
/**
* create a web application from static budibase definition files.
@@ -8,7 +9,7 @@ import { builtins, builtinLibName } from "./render/builtinComponents"
export const loadBudibase = async opts => {
const _window = (opts && opts.window) || window
// const _localStorage = (opts && opts.localStorage) || localStorage
-
+ const appId = getAppId()
const frontendDefinition = _window["##BUDIBASE_FRONTEND_DEFINITION##"]
const user = {}
@@ -20,9 +21,7 @@ export const loadBudibase = async opts => {
for (let library of libraries) {
// fetch the JavaScript for the component libraries from the server
componentLibraryModules[library] = await import(
- `/${frontendDefinition.appId}/componentlibrary?library=${encodeURI(
- library
- )}`
+ `/componentlibrary?library=${encodeURI(library)}`
)
}
@@ -42,7 +41,7 @@ export const loadBudibase = async opts => {
})
const route = _window.location
- ? _window.location.pathname.replace(frontendDefinition.appRootPath, "")
+ ? _window.location.pathname.replace(`${appId}/`, "").replace(appId, "")
: ""
initialisePage(frontendDefinition.page, _window.document.body, route)
diff --git a/packages/client/src/render/getAppId.js b/packages/client/src/render/getAppId.js
new file mode 100644
index 0000000000..09a1f1b54f
--- /dev/null
+++ b/packages/client/src/render/getAppId.js
@@ -0,0 +1,5 @@
+export const getAppId = () =>
+ document.cookie
+ .split(";")
+ .find(c => c.trim().startsWith("budibase:appid"))
+ .split("=")[1]
diff --git a/packages/client/src/render/screenRouter.js b/packages/client/src/render/screenRouter.js
index 64319102f8..8bff4950ca 100644
--- a/packages/client/src/render/screenRouter.js
+++ b/packages/client/src/render/screenRouter.js
@@ -1,11 +1,19 @@
import regexparam from "regexparam"
import { routerStore } from "../state/store"
+import { getAppId } from "./getAppId"
-export const screenRouter = ({ screens, onScreenSelected, appRootPath }) => {
+export const screenRouter = ({ screens, onScreenSelected }) => {
const makeRootedPath = url => {
- if (appRootPath) {
- if (url) return `${appRootPath}${url.startsWith("/") ? "" : "/"}${url}`
- return appRootPath
+ if (
+ window.location.hostname === "localhost" ||
+ window.location.hostname === "127.0.0.1"
+ ) {
+ const appId = getAppId()
+ if (url) {
+ if (url.startsWith(appId)) return url
+ return `/${appId}${url.startsWith("/") ? "" : "/"}${url}`
+ }
+ return appId
}
return url
}
diff --git a/packages/client/src/state/bbComponentApi.js b/packages/client/src/state/bbComponentApi.js
index 7366a90f5c..4452b4839e 100644
--- a/packages/client/src/state/bbComponentApi.js
+++ b/packages/client/src/state/bbComponentApi.js
@@ -6,21 +6,9 @@ export const trimSlash = str => str.replace(/^\/+|\/+$/g, "")
export const bbFactory = ({
store,
- frontendDefinition,
componentLibraries,
onScreenSlotRendered,
}) => {
- const relativeUrl = url => {
- if (!frontendDefinition.appRootPath) return url
- if (
- url.startsWith("http:") ||
- url.startsWith("https:") ||
- url.startsWith("./")
- )
- return url
-
- return frontendDefinition.appRootPath + "/" + trimSlash(url)
- }
const apiCall = method => (url, body) =>
fetch(url, {
@@ -30,6 +18,7 @@ export const bbFactory = ({
"x-user-agent": "Budibase Builder",
},
body: body && JSON.stringify(body),
+ credentials: "same-origin",
})
const api = {
@@ -63,7 +52,6 @@ export const bbFactory = ({
getContext: getContext(treeNode),
setContext: setContext(treeNode),
store: store,
- relativeUrl,
api,
parent,
}
diff --git a/packages/client/src/state/eventHandlers.js b/packages/client/src/state/eventHandlers.js
index b013956dd4..cab825c24b 100644
--- a/packages/client/src/state/eventHandlers.js
+++ b/packages/client/src/state/eventHandlers.js
@@ -6,14 +6,13 @@ import { createApi } from "../api"
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
-export const eventHandlers = (rootPath, routeTo) => {
+export const eventHandlers = routeTo => {
const handler = (parameters, execute) => ({
execute,
parameters,
})
const api = createApi({
- rootPath,
setState,
getState: (path, fallback) => getState(path, fallback),
})
diff --git a/packages/client/src/state/stateManager.js b/packages/client/src/state/stateManager.js
index 8e777ca4b9..5c5f198956 100644
--- a/packages/client/src/state/stateManager.js
+++ b/packages/client/src/state/stateManager.js
@@ -21,13 +21,11 @@ const isMetaProp = propName =>
propName === "_styles"
export const createStateManager = ({
- appRootPath,
- frontendDefinition,
componentLibraries,
onScreenSlotRendered,
routeTo,
}) => {
- let handlerTypes = eventHandlers(appRootPath, routeTo)
+ let handlerTypes = eventHandlers(routeTo)
let currentState
const getCurrentState = () => currentState
@@ -35,7 +33,6 @@ export const createStateManager = ({
const bb = bbFactory({
store: appStore,
getCurrentState,
- frontendDefinition,
componentLibraries,
onScreenSlotRendered,
})
diff --git a/packages/client/tests/testAppDef.js b/packages/client/tests/testAppDef.js
index c02c18a3b2..e1e41c60c5 100644
--- a/packages/client/tests/testAppDef.js
+++ b/packages/client/tests/testAppDef.js
@@ -1,10 +1,9 @@
import { JSDOM } from "jsdom"
import { loadBudibase } from "../src/index"
-export const load = async (page, screens, url, appRootPath) => {
+export const load = async (page, screens, url) => {
screens = screens || []
url = url || "/"
- appRootPath = appRootPath || ""
const dom = new JSDOM("", {
url: `http://test${url}`,
})
@@ -13,7 +12,7 @@ export const load = async (page, screens, url, appRootPath) => {
autoAssignIds(s.props)
}
setAppDef(dom.window, page, screens)
- addWindowGlobals(dom.window, page, screens, appRootPath, {
+ addWindowGlobals(dom.window, page, screens, {
hierarchy: {},
actions: [],
triggers: [],
@@ -27,11 +26,10 @@ export const load = async (page, screens, url, appRootPath) => {
return { dom, app }
}
-const addWindowGlobals = (window, page, screens, appRootPath) => {
+const addWindowGlobals = (window, page, screens) => {
window["##BUDIBASE_FRONTEND_DEFINITION##"] = {
page,
screens,
- appRootPath,
}
}
@@ -88,7 +86,6 @@ const setAppDef = (window, page, screens) => {
componentLibraries: [],
page,
screens,
- appRootPath: "",
}
}
diff --git a/packages/materialdesign-components/src/Test/createApp.js b/packages/materialdesign-components/src/Test/createApp.js
index 96106635b7..0bd3098688 100644
--- a/packages/materialdesign-components/src/Test/createApp.js
+++ b/packages/materialdesign-components/src/Test/createApp.js
@@ -14,12 +14,6 @@ export default async () => {
componentLibraries["@budibase/standard-components"] = standardcomponents
const appDef = { hierarchy: {}, actions: {} }
const user = { name: "yeo", permissions: [] }
- const { initialisePage } = createApp(
- componentLibraries,
- { appRootPath: "" },
- appDef,
- user,
- {}
- )
+ const { initialisePage } = createApp(componentLibraries, {}, appDef, user, {})
return initialisePage
}
diff --git a/packages/server/package.json b/packages/server/package.json
index 0888ef2ae4..49a5626f6f 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -25,7 +25,7 @@
"scripts": {
"test": "jest routes --runInBand",
"test:integration": "jest workflow --runInBand",
- "test:watch": "jest -w",
+ "test:watch": "jest --watch",
"initialise": "node ../cli/bin/budi init -b local -q",
"budi": "node ../cli/bin/budi",
"dev:builder": "nodemon ../cli/bin/budi run",
diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js
index 5ec3aea617..6ca6d7edac 100644
--- a/packages/server/src/api/controllers/auth.js
+++ b/packages/server/src/api/controllers/auth.js
@@ -4,25 +4,27 @@ const ClientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt")
exports.authenticate = async ctx => {
+ if (!ctx.appId) ctx.throw(400, "No appId")
+
const { username, password } = ctx.request.body
if (!username) ctx.throw(400, "Username Required.")
if (!password) ctx.throw(400, "Password Required")
const masterDb = new CouchDB("clientAppLookup")
- const { clientId } = await masterDb.get(ctx.params.appId)
+
+ const { clientId } = await masterDb.get(ctx.appId)
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
// find the instance that the user is associated with
const db = new CouchDB(ClientDb.name(clientId))
- const appId = ctx.params.appId
- const app = await db.get(appId)
+ const app = await db.get(ctx.appId)
const instanceId = app.userInstanceMap[username]
if (!instanceId)
- ctx.throw(500, "User is not associated with an instance of app", appId)
+ ctx.throw(500, "User is not associated with an instance of app", ctx.appId)
// Check the user exists in the instance DB by username
const instanceDb = new CouchDB(instanceId)
@@ -41,7 +43,7 @@ exports.authenticate = async ctx => {
const payload = {
userId: dbUser._id,
accessLevelId: dbUser.accessLevelId,
- instanceId: instanceId,
+ instanceId,
}
const token = jwt.sign(payload, ctx.config.jwtSecret, {
diff --git a/packages/server/src/api/controllers/model.js b/packages/server/src/api/controllers/model.js
index 9d06c7a413..650342b33c 100644
--- a/packages/server/src/api/controllers/model.js
+++ b/packages/server/src/api/controllers/model.js
@@ -27,6 +27,23 @@ exports.create = async function(ctx) {
const result = await db.post(newModel)
newModel._rev = result.rev
+ const { schema } = ctx.request.body
+ for (let key in schema) {
+ // model has a linked record
+ if (schema[key].type === "link") {
+ // create the link field in the other model
+ const linkedModel = await db.get(schema[key].modelId)
+ linkedModel.schema[newModel.name] = {
+ type: "link",
+ modelId: newModel._id,
+ constraints: {
+ type: "array",
+ },
+ }
+ await db.put(linkedModel)
+ }
+ }
+
const designDoc = await db.get("_design/database")
designDoc.views = {
...designDoc.views,
@@ -50,7 +67,10 @@ exports.update = async function() {}
exports.destroy = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
- await db.remove(ctx.params.modelId, ctx.params.revId)
+ const modelToDelete = await db.get(ctx.params.modelId)
+
+ await db.remove(modelToDelete)
+
const modelViewId = `all_${ctx.params.modelId}`
// Delete all records for that model
@@ -59,6 +79,16 @@ exports.destroy = async function(ctx) {
records.rows.map(record => ({ id: record.id, _deleted: true }))
)
+ // Delete linked record fields in dependent models
+ for (let key in modelToDelete.schema) {
+ const { type, modelId } = modelToDelete.schema[key]
+ if (type === "link") {
+ const linkedModel = await db.get(modelId)
+ delete linkedModel.schema[modelToDelete.name]
+ await db.put(linkedModel)
+ }
+ }
+
// delete the "all" view
const designDoc = await db.get("_design/database")
delete designDoc.views[modelViewId]
diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js
index 5fab413f04..cf8eb27606 100644
--- a/packages/server/src/api/controllers/record.js
+++ b/packages/server/src/api/controllers/record.js
@@ -61,7 +61,7 @@ exports.fetchView = async function(ctx) {
ctx.body = response.rows.map(row => row.doc)
}
-exports.fetchModel = async function(ctx) {
+exports.fetchModelRecords = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const response = await db.query(`database/all_${ctx.params.modelId}`, {
include_docs: true,
@@ -69,6 +69,15 @@ exports.fetchModel = async function(ctx) {
ctx.body = response.rows.map(row => row.doc)
}
+exports.search = async function(ctx) {
+ const db = new CouchDB(ctx.params.instanceId)
+ const response = await db.allDocs({
+ include_docs: true,
+ ...ctx.request.body,
+ })
+ ctx.body = response.rows.map(row => row.doc)
+}
+
exports.find = async function(ctx) {
const db = new CouchDB(ctx.params.instanceId)
const record = await db.get(ctx.params.recordId)
diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js
index 557a9efe7a..d2d2596c0a 100644
--- a/packages/server/src/api/controllers/static.js
+++ b/packages/server/src/api/controllers/static.js
@@ -20,6 +20,27 @@ exports.serveApp = async function(ctx) {
"public",
ctx.isAuthenticated ? "main" : "unauthenticated"
)
+ // only set the appId cookie for /appId .. we COULD check for valid appIds
+ // but would like to avoid that DB hit
+ if (looksLikeAppId(ctx.params.appId)) {
+ ctx.cookies.set("budibase:appid", ctx.params.appId, {
+ path: "/",
+ httpOnly: false,
+ expires: new Date(2099, 1, 1),
+ })
+ }
+
+ await send(ctx, ctx.file || "index.html", { root: ctx.devPath || appPath })
+}
+
+exports.serveAppAsset = async function(ctx) {
+ // default to homedir
+ const appPath = resolve(
+ budibaseAppsDir(),
+ ctx.appId,
+ "public",
+ ctx.isAuthenticated ? "main" : "unauthenticated"
+ )
await send(ctx, ctx.file, { root: ctx.devPath || appPath })
}
@@ -28,7 +49,7 @@ exports.serveComponentLibrary = async function(ctx) {
// default to homedir
let componentLibraryPath = resolve(
budibaseAppsDir(),
- ctx.params.appId,
+ ctx.appId,
"node_modules",
decodeURI(ctx.query.library),
"dist"
@@ -44,3 +65,10 @@ exports.serveComponentLibrary = async function(ctx) {
await send(ctx, "/index.js", { root: componentLibraryPath })
}
+
+const looksLikeAppId = appId => {
+ const allowedChars = "0123456789abcdef".split("")
+ return (
+ appId.length === 32 && !appId.split("").some(c => allowedChars.includes(c))
+ )
+}
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index 990714fdc7..8c69407736 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -10,6 +10,7 @@ const {
instanceRoutes,
clientRoutes,
applicationRoutes,
+ recordRoutes,
modelRoutes,
viewRoutes,
staticRoutes,
@@ -59,6 +60,11 @@ router.use(async (ctx, next) => {
}
})
+router.use(async (ctx, next) => {
+ ctx.appId = ctx.cookies.get("budibase:appid")
+ await next()
+})
+
router.use(authRoutes.routes())
router.use(authRoutes.allowedMethods())
@@ -69,6 +75,9 @@ router.use(viewRoutes.allowedMethods())
router.use(modelRoutes.routes())
router.use(modelRoutes.allowedMethods())
+router.use(recordRoutes.routes())
+router.use(recordRoutes.allowedMethods())
+
router.use(userRoutes.routes())
router.use(userRoutes.allowedMethods())
diff --git a/packages/server/src/api/routes/auth.js b/packages/server/src/api/routes/auth.js
index fa95a3a5e6..b4b68e8929 100644
--- a/packages/server/src/api/routes/auth.js
+++ b/packages/server/src/api/routes/auth.js
@@ -3,6 +3,6 @@ const controller = require("../controllers/auth")
const router = Router()
-router.post("/:appId/api/authenticate", controller.authenticate)
+router.post("/api/authenticate", controller.authenticate)
module.exports = router
diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js
index c515d5f437..b50fee788a 100644
--- a/packages/server/src/api/routes/index.js
+++ b/packages/server/src/api/routes/index.js
@@ -5,6 +5,7 @@ const instanceRoutes = require("./instance")
const clientRoutes = require("./client")
const applicationRoutes = require("./application")
const modelRoutes = require("./model")
+const recordRoutes = require("./record")
const viewRoutes = require("./view")
const staticRoutes = require("./static")
const componentRoutes = require("./component")
@@ -18,6 +19,7 @@ module.exports = {
instanceRoutes,
clientRoutes,
applicationRoutes,
+ recordRoutes,
modelRoutes,
viewRoutes,
staticRoutes,
diff --git a/packages/server/src/api/routes/model.js b/packages/server/src/api/routes/model.js
index 388f4618bd..f1ec46dbe5 100644
--- a/packages/server/src/api/routes/model.js
+++ b/packages/server/src/api/routes/model.js
@@ -1,46 +1,10 @@
const Router = require("@koa/router")
const modelController = require("../controllers/model")
-const recordController = require("../controllers/record")
const authorized = require("../../middleware/authorized")
-const {
- READ_MODEL,
- WRITE_MODEL,
- BUILDER,
-} = require("../../utilities/accessLevels")
+const { BUILDER } = require("../../utilities/accessLevels")
const router = Router()
-// records
-
-router
- .get(
- "/api/:instanceId/:modelId/records",
- authorized(READ_MODEL, ctx => ctx.params.modelId),
- recordController.fetchModel
- )
- .get(
- "/api/:instanceId/:modelId/records/:recordId",
- authorized(READ_MODEL, ctx => ctx.params.modelId),
- recordController.find
- )
- .post(
- "/api/:instanceId/:modelId/records",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.save
- )
- .post(
- "/api/:instanceId/:modelId/records/validate",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.validate
- )
- .delete(
- "/api/:instanceId/:modelId/records/:recordId/:revId",
- authorized(WRITE_MODEL, ctx => ctx.params.modelId),
- recordController.destroy
- )
-
-// models
-
router
.get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch)
.get("/api/:instanceId/models/:id", authorized(BUILDER), modelController.find)
diff --git a/packages/server/src/api/routes/record.js b/packages/server/src/api/routes/record.js
new file mode 100644
index 0000000000..d555d3d8c8
--- /dev/null
+++ b/packages/server/src/api/routes/record.js
@@ -0,0 +1,36 @@
+const Router = require("@koa/router")
+const recordController = require("../controllers/record")
+const authorized = require("../../middleware/authorized")
+const { READ_MODEL, WRITE_MODEL } = require("../../utilities/accessLevels")
+
+const router = Router()
+
+router
+ .get(
+ "/api/:instanceId/:modelId/records",
+ authorized(READ_MODEL, ctx => ctx.params.modelId),
+ recordController.fetchModelRecords
+ )
+ .get(
+ "/api/:instanceId/:modelId/records/:recordId",
+ authorized(READ_MODEL, ctx => ctx.params.modelId),
+ recordController.find
+ )
+ .post("/api/:instanceId/records/search", recordController.search)
+ .post(
+ "/api/:instanceId/:modelId/records",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.save
+ )
+ .post(
+ "/api/:instanceId/:modelId/records/validate",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.validate
+ )
+ .delete(
+ "/api/:instanceId/:modelId/records/:recordId/:revId",
+ authorized(WRITE_MODEL, ctx => ctx.params.modelId),
+ recordController.destroy
+ )
+
+module.exports = router
diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js
index 5909d31d09..6fff1996fa 100644
--- a/packages/server/src/api/routes/static.js
+++ b/packages/server/src/api/routes/static.js
@@ -21,7 +21,8 @@ if (env.NODE_ENV !== "production") {
}
router
- .get("/:appId/componentlibrary", controller.serveComponentLibrary)
- .get("/:appId/:file*", controller.serveApp)
+ .get("/componentlibrary", controller.serveComponentLibrary)
+ .get("/assets/:file*", controller.serveAppAsset)
+ .get("/:appId/:path*", controller.serveApp)
module.exports = router
diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js
index 6029e080cc..495b841b10 100644
--- a/packages/server/src/api/routes/tests/couchTestUtils.js
+++ b/packages/server/src/api/routes/tests/couchTestUtils.js
@@ -253,3 +253,7 @@ exports.insertDocument = async (databaseId, document) => {
exports.destroyDocument = async (databaseId, documentId) => {
return await new CouchDB(databaseId).destroy(documentId)
}
+
+exports.getDocument = async (databaseId, documentId) => {
+ return await new CouchDB(databaseId).get(documentId)
+}
diff --git a/packages/server/src/api/routes/tests/model.spec.js b/packages/server/src/api/routes/tests/model.spec.js
index 65a44b677a..7134245fb3 100644
--- a/packages/server/src/api/routes/tests/model.spec.js
+++ b/packages/server/src/api/routes/tests/model.spec.js
@@ -3,9 +3,10 @@ const {
createModel,
supertest,
createClientDatabase,
- createApplication ,
+ createApplication,
defaultHeaders,
- builderEndpointShouldBlockNormalUsers
+ builderEndpointShouldBlockNormalUsers,
+ getDocument
} = require("./couchTestUtils")
describe("/models", () => {
@@ -97,7 +98,6 @@ describe("/models", () => {
instanceId: instance._id,
})
})
-
});
describe("destroy", () => {
@@ -108,7 +108,11 @@ describe("/models", () => {
testModel = await createModel(request, instance._id, testModel)
});
- it("returns a success response when a model is deleted.", done => {
+ afterEach(() => {
+ delete testModel._rev
+ })
+
+ it("returns a success response when a model is deleted.", async done => {
request
.delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
.set(defaultHeaders)
@@ -120,6 +124,41 @@ describe("/models", () => {
});
})
+ it("deletes linked references to the model after deletion", async done => {
+ const linkedModel = await createModel(request, instance._id, {
+ name: "LinkedModel",
+ type: "model",
+ key: "name",
+ schema: {
+ name: {
+ type: "text",
+ constraints: {
+ type: "string",
+ },
+ },
+ TestModel: {
+ type: "link",
+ modelId: testModel._id,
+ constraints: {
+ type: "array"
+ }
+ }
+ },
+ })
+
+ request
+ .delete(`/api/${instance._id}/models/${testModel._id}/${testModel._rev}`)
+ .set(defaultHeaders)
+ .expect('Content-Type', /json/)
+ .expect(200)
+ .end(async (_, res) => {
+ expect(res.res.statusMessage).toEqual(`Model ${testModel._id} deleted.`);
+ const dependentModel = await getDocument(instance._id, linkedModel._id)
+ expect(dependentModel.schema.TestModel).not.toBeDefined();
+ done();
+ });
+ })
+
it("should apply authorization to endpoint", async () => {
await builderEndpointShouldBlockNormalUsers({
request,
diff --git a/packages/server/src/api/routes/tests/record.spec.js b/packages/server/src/api/routes/tests/record.spec.js
index 2c8c542715..22ac67ecdc 100644
--- a/packages/server/src/api/routes/tests/record.spec.js
+++ b/packages/server/src/api/routes/tests/record.spec.js
@@ -110,6 +110,30 @@ describe("/records", () => {
expect(res.body.find(r => r.name === record.name)).toBeDefined()
})
+ it("lists records when queried by their ID", async () => {
+ const newRecord = {
+ modelId: model._id,
+ name: "Second Contact",
+ status: "new"
+ }
+ const record = await createRecord()
+ const secondRecord = await createRecord(newRecord)
+
+ const recordIds = [record.body._id, secondRecord.body._id]
+
+ const res = await request
+ .post(`/api/${instance._id}/records/search`)
+ .set(defaultHeaders)
+ .send({
+ keys: recordIds
+ })
+ .expect('Content-Type', /json/)
+ .expect(200)
+
+ expect(res.body.length).toBe(2)
+ expect(res.body.map(response => response._id)).toEqual(expect.arrayContaining(recordIds))
+ })
+
it("load should return 404 when record does not exist", async () => {
await createRecord()
await request
diff --git a/packages/server/src/utilities/builder/buildPage.js b/packages/server/src/utilities/builder/buildPage.js
index 3d44cb8072..ea0c9cce25 100644
--- a/packages/server/src/utilities/builder/buildPage.js
+++ b/packages/server/src/utilities/builder/buildPage.js
@@ -45,18 +45,16 @@ const copyClientLib = async (appPath, pageName) => {
const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const appPublicPath = publicPath(appPath, pageName)
- const appRootPath = rootPath(config, appId)
const stylesheetUrl = s =>
s.startsWith("http") ? s : `/${rootPath(config, appId)}/${s}`
const templateObj = {
title: pkg.page.title || "Budibase App",
- favicon: `${appRootPath}/${pkg.page.favicon || "/_shared/favicon.png"}`,
+ favicon: `${pkg.page.favicon || "/_shared/favicon.png"}`,
stylesheets: (pkg.page.stylesheets || []).map(stylesheetUrl),
screenStyles: pkg.screens.filter(s => s._css).map(s => s._css),
pageStyle: pkg.page._css,
- appRootPath,
}
const indexHtmlTemplate = await readFile(
@@ -74,7 +72,6 @@ const buildIndexHtml = async (config, appId, pageName, appPath, pkg) => {
const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
const appPath = appPackageFolder(config, appId)
const appPublicPath = publicPath(appPath, pageName)
- const appRootPath = rootPath(config, appId)
const filename = join(appPublicPath, "clientFrontendDefinition.js")
@@ -89,7 +86,6 @@ const buildFrontendAppDefinition = async (config, appId, pageName, pkg) => {
}
const clientUiDefinition = JSON.stringify({
- appRootPath: appRootPath,
page: pkg.page,
screens: pkg.screens,
libraries: [
diff --git a/packages/server/src/utilities/builder/index.template.html b/packages/server/src/utilities/builder/index.template.html
index 2f1f7ed87c..a21219de9b 100644
--- a/packages/server/src/utilities/builder/index.template.html
+++ b/packages/server/src/utilities/builder/index.template.html
@@ -24,15 +24,15 @@
{{ /each }}
{{ each(options.screenStyles) }}
-
+
{{ /each }}
{{ if(options.pageStyle) }}
-
+
{{ /if }}
-
-
+
+
diff --git a/packages/standard-components/components.json b/packages/standard-components/components.json
index a562650b85..c4c0246672 100644
--- a/packages/standard-components/components.json
+++ b/packages/standard-components/components.json
@@ -6,6 +6,13 @@
"component": "button"
}
},
+ "embed": {
+ "name": "Embed",
+ "description": "Embed stuff",
+ "props": {
+ "embed": "string"
+ }
+ },
"Navigation": {
"name": "Navigation",
"description": "A basic header navigation component",
diff --git a/packages/standard-components/public/clientAppDefinition.js b/packages/standard-components/public/clientAppDefinition.js
index be67dcf1a8..f77f59ec03 100644
--- a/packages/standard-components/public/clientAppDefinition.js
+++ b/packages/standard-components/public/clientAppDefinition.js
@@ -98,6 +98,5 @@ window["##BUDIBASE_APPDEFINITION##"] = {
nodeId: 0,
},
componentLibraries: ["budibase-standard-components"],
- appRootPath: "/testApp2",
props: {},
}
diff --git a/packages/standard-components/src/Embed.svelte b/packages/standard-components/src/Embed.svelte
new file mode 100644
index 0000000000..2640864681
--- /dev/null
+++ b/packages/standard-components/src/Embed.svelte
@@ -0,0 +1,5 @@
+
+
+{@html embed}
diff --git a/packages/standard-components/src/Login.svelte b/packages/standard-components/src/Login.svelte
index b52cc154ea..0675c65f46 100644
--- a/packages/standard-components/src/Login.svelte
+++ b/packages/standard-components/src/Login.svelte
@@ -13,30 +13,17 @@
let password = ""
let loading = false
let error = false
- let _logo = ""
let _buttonClass = ""
let _inputClass = ""
$: {
- _logo = _bb.relativeUrl(logo)
_buttonClass = buttonClass || "default-button"
_inputClass = inputClass || "default-input"
}
const login = async () => {
loading = true
- const response = await fetch(_bb.relativeUrl("/api/authenticate"), {
- body: JSON.stringify({
- username,
- password,
- }),
- headers: {
- "Content-Type": "application/json",
- "x-user-agent": "Budibase Builder",
- },
- method: "POST",
- })
-
+ const response = await _bb.api.post("/api/authenticate", { username, password })
if (response.status === 200) {
const json = await response.json()
localStorage.setItem("budibase:token", json.token)
@@ -51,9 +38,9 @@
- {#if _logo}
+ {#if logo}
-
+
{/if}
diff --git a/packages/standard-components/src/Test/createApp.js b/packages/standard-components/src/Test/createApp.js
index 65ad4792c4..48f65c5789 100644
--- a/packages/standard-components/src/Test/createApp.js
+++ b/packages/standard-components/src/Test/createApp.js
@@ -15,7 +15,7 @@ export default async () => {
const { initialisePage } = createApp(
window.document,
componentLibraries,
- { appRootPath: "" },
+ {},
appDef,
user,
{},
diff --git a/packages/standard-components/src/index.js b/packages/standard-components/src/index.js
index 2284ab1fcb..24342066e8 100644
--- a/packages/standard-components/src/index.js
+++ b/packages/standard-components/src/index.js
@@ -21,3 +21,4 @@ export { default as datalist } from "./DataList.svelte"
export { default as list } from "./List.svelte"
export { default as datasearch } from "./DataSearch.svelte"
export { default as datamap } from "./DataMap.svelte"
+export { default as embed } from "./Embed.svelte"