removal of appRoot - appId comes in cookie
This commit is contained in:
parent
108fa4ca13
commit
19d132c6c2
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
|
@ -5,7 +5,6 @@ import { writable, get } from "svelte/store"
|
|||
import api from "../api"
|
||||
import { DEFAULT_PAGES_OBJECT } from "../../constants"
|
||||
import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents"
|
||||
import { rename } from "components/userInterface/pagesParsing/renameScreen"
|
||||
import {
|
||||
createProps,
|
||||
makePropsSafe,
|
||||
|
@ -24,6 +23,7 @@ import {
|
|||
saveCurrentPreviewItem as _saveCurrentPreviewItem,
|
||||
saveScreenApi as _saveScreenApi,
|
||||
regenerateCssForCurrentScreen,
|
||||
renameCurrentScreen,
|
||||
} from "../storeUtils"
|
||||
|
||||
export const getStore = () => {
|
||||
|
@ -52,7 +52,6 @@ export const getStore = () => {
|
|||
store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store)
|
||||
|
||||
store.saveScreen = saveScreen(store)
|
||||
store.renameScreen = renameScreen(store)
|
||||
store.deleteScreen = deleteScreen(store)
|
||||
store.setCurrentScreen = setCurrentScreen(store)
|
||||
store.setCurrentPage = setCurrentPage(store)
|
||||
|
@ -63,6 +62,7 @@ export const getStore = () => {
|
|||
store.addChildComponent = addChildComponent(store)
|
||||
store.selectComponent = selectComponent(store)
|
||||
store.setComponentProp = setComponentProp(store)
|
||||
store.setPageOrScreenProp = setPageOrScreenProp(store)
|
||||
store.setComponentStyle = setComponentStyle(store)
|
||||
store.setComponentCode = setComponentCode(store)
|
||||
store.setScreenType = setScreenType(store)
|
||||
|
@ -207,46 +207,6 @@ const deleteScreen = store => name => {
|
|||
})
|
||||
}
|
||||
|
||||
const renameScreen = store => (oldname, newname) => {
|
||||
store.update(s => {
|
||||
const { screens, pages, error, changedScreens } = rename(
|
||||
s.pages,
|
||||
s.screens,
|
||||
oldname,
|
||||
newname
|
||||
)
|
||||
|
||||
if (error) {
|
||||
// should really do something with this
|
||||
return s
|
||||
}
|
||||
|
||||
s.screens = screens
|
||||
s.pages = pages
|
||||
if (s.currentPreviewItem.name === oldname)
|
||||
s.currentPreviewItem.name = newname
|
||||
|
||||
const saveAllChanged = async () => {
|
||||
for (let screenName of changedScreens) {
|
||||
const changedScreen = getExactComponent(screens, screenName)
|
||||
await api.post(`/_builder/api/${s.appId}/screen`, changedScreen)
|
||||
}
|
||||
}
|
||||
|
||||
api
|
||||
.patch(`/_builder/api/${s.appId}/screen`, {
|
||||
oldname,
|
||||
newname,
|
||||
})
|
||||
.then(() => saveAllChanged())
|
||||
.then(() => {
|
||||
_savePage(s)
|
||||
})
|
||||
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
const savePage = store => async page => {
|
||||
store.update(state => {
|
||||
if (state.currentFrontEndType !== "page" || !state.currentPageName) {
|
||||
|
@ -400,6 +360,18 @@ const setComponentProp = store => (name, value) => {
|
|||
})
|
||||
}
|
||||
|
||||
const setPageOrScreenProp = store => (name, value) => {
|
||||
store.update(state => {
|
||||
if (name === "name" && state.currentFrontEndType === "screen") {
|
||||
state = renameCurrentScreen(value, state)
|
||||
} else {
|
||||
state.currentPreviewItem[name] = value
|
||||
_saveCurrentPreviewItem(state)
|
||||
}
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
||||
const setComponentStyle = store => (type, name, value) => {
|
||||
store.update(state => {
|
||||
if (!state.currentComponentInfo._styles) {
|
||||
|
|
|
@ -45,6 +45,19 @@ export const saveScreenApi = (screen, s) => {
|
|||
.then(() => savePage(s))
|
||||
}
|
||||
|
||||
export const renameCurrentScreen = (newname, state) => {
|
||||
const oldname = state.currentPreviewItem.name
|
||||
state.currentPreviewItem.name = newname
|
||||
api.patch(
|
||||
`/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`,
|
||||
{
|
||||
oldname,
|
||||
newname,
|
||||
}
|
||||
)
|
||||
return state
|
||||
}
|
||||
|
||||
export const walkProps = (props, action, cancelToken = null) => {
|
||||
cancelToken = cancelToken || { cancelled: false }
|
||||
action(props, () => {
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
onChange(_value)
|
||||
}
|
||||
|
||||
$: displayValues = value && suffix
|
||||
? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
|
||||
: value || []
|
||||
$: displayValues =
|
||||
value && suffix
|
||||
? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
|
||||
: value || []
|
||||
</script>
|
||||
|
||||
<div class="input-container">
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="component-container">
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
<script>
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import { map, join } from "lodash/fp"
|
||||
import iframeTemplate from "./iframeTemplate"
|
||||
import { pipe } from "components/common/core"
|
||||
|
||||
let iframe
|
||||
let styles = ""
|
||||
|
||||
function transform_component(comp) {
|
||||
const props = comp.props || comp
|
||||
if (props && props._children && props._children.length) {
|
||||
props._children = props._children.map(transform_component)
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
const getComponentTypeName = component => {
|
||||
let [componentName] = component._component.match(/[a-z]*$/)
|
||||
return componentName || "element"
|
||||
}
|
||||
|
||||
const screenPlaceholder = {
|
||||
name: "Screen Placeholder",
|
||||
route: "*",
|
||||
props: {
|
||||
_component: "@budibase/standard-components/container",
|
||||
type: "div",
|
||||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
||||
_id: "__screenslot__text",
|
||||
_code: "",
|
||||
className: "",
|
||||
onLoad: [],
|
||||
type: "div",
|
||||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/text",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_id: "__screenslot__text_2",
|
||||
_code: "",
|
||||
text: "content",
|
||||
font: "",
|
||||
color: "",
|
||||
textAlign: "inline",
|
||||
verticalAlign: "inline",
|
||||
formattingTag: "none",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
$: hasComponent = !!$store.currentPreviewItem
|
||||
|
||||
$: {
|
||||
styles = ""
|
||||
// Apply the CSS from the currently selected page and its screens
|
||||
const currentPage = $store.pages[$store.currentPageName]
|
||||
styles += currentPage._css
|
||||
for (let screen of currentPage._screens) {
|
||||
styles += screen._css
|
||||
}
|
||||
styles = styles
|
||||
}
|
||||
|
||||
$: stylesheetLinks = pipe($store.pages.stylesheets, [
|
||||
map(s => `<link rel="stylesheet" href="${s}"/>`),
|
||||
join("\n"),
|
||||
])
|
||||
|
||||
$: screensExist =
|
||||
$store.currentPreviewItem._screens &&
|
||||
$store.currentPreviewItem._screens.length > 0
|
||||
|
||||
$: frontendDefinition = {
|
||||
appId: $store.appId,
|
||||
libraries: $store.libraries,
|
||||
page: $store.pages[$store.currentPageName],
|
||||
screens: [
|
||||
$store.currentFrontEndType === "page"
|
||||
? screenPlaceholder
|
||||
: $store.currentPreviewItem,
|
||||
],
|
||||
}
|
||||
|
||||
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
|
||||
|
||||
$: selectedComponentId = $store.currentComponentInfo
|
||||
? $store.currentComponentInfo._id
|
||||
: ""
|
||||
|
||||
const refreshContent = () => {
|
||||
<<<<<<< HEAD
|
||||
iframe.contentWindow.postMessage(JSON.stringify({
|
||||
styles,
|
||||
stylesheetLinks,
|
||||
selectedComponentType,
|
||||
selectedComponentId,
|
||||
frontendDefinition,
|
||||
appId: $store.appId
|
||||
}))
|
||||
=======
|
||||
iframe.contentWindow.postMessage(
|
||||
JSON.stringify({
|
||||
styles,
|
||||
stylesheetLinks,
|
||||
selectedComponentType,
|
||||
selectedComponentId,
|
||||
frontendDefinition,
|
||||
})
|
||||
)
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
$: if (iframe)
|
||||
iframe.contentWindow.addEventListener("bb-ready", refreshContent, {
|
||||
once: true,
|
||||
})
|
||||
|
||||
$: if (iframe && frontendDefinition) {
|
||||
refreshContent()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="component-container">
|
||||
{#if hasComponent && $store.currentPreviewItem}
|
||||
<iframe
|
||||
style="height: 100%; width: 100%"
|
||||
title="componentPreview"
|
||||
bind:this={iframe}
|
||||
srcdoc={iframeTemplate} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.component-container {
|
||||
grid-row-start: middle;
|
||||
grid-column-start: middle;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.component-container iframe {
|
||||
border: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -44,6 +44,7 @@ export default `<html>
|
|||
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 })
|
||||
|
|
|
@ -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'}
|
||||
<EventsEditor component={componentInstance} />
|
||||
{/if}
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
const pages = [
|
||||
{
|
||||
title: "Main",
|
||||
title: "Private",
|
||||
id: "main",
|
||||
},
|
||||
{
|
||||
title: "Login",
|
||||
title: "Public",
|
||||
id: "unauthenticated",
|
||||
},
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
</script>
|
||||
|
||||
{#if screenOrPageInstance}
|
||||
{#each screenOrPageDefinition as def}
|
||||
<PropertyControl
|
||||
control={def.control}
|
||||
label={def.label}
|
||||
key={def.key}
|
||||
value={screenOrPageInstance[def.key]}
|
||||
onChange={handleScreenPropChange}
|
||||
props={{ ...excludeProps(def, ['control', 'label']) }} />
|
||||
{/each}
|
||||
<hr/>
|
||||
{/if}
|
||||
|
||||
{#if panelDefinition && panelDefinition.length > 0}
|
||||
{#each panelDefinition as definition}
|
||||
{#if propExistsOnComponentDef(definition.key)}
|
||||
|
|
|
@ -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)",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export const getAppId = () =>
|
||||
document.cookie
|
||||
.split(";")
|
||||
.find(c => c.trim().startsWith("budibase:appid"))
|
||||
.split("=")[1]
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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("<!DOCTYPE html><html><body></body><html>", {
|
||||
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: "",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -24,15 +24,15 @@
|
|||
{{ /each }}
|
||||
|
||||
{{ each(options.screenStyles) }}
|
||||
<link rel='stylesheet' href='{{ appRootPath }}{{ @this }}'>
|
||||
<link rel='stylesheet' href='/assets{{ @this }}'>
|
||||
{{ /each }}
|
||||
|
||||
{{ if(options.pageStyle) }}
|
||||
<link rel='stylesheet' href='{{ appRootPath }}{{ pageStyle }}'>
|
||||
<link rel='stylesheet' href='/assets{{ pageStyle }}'>
|
||||
{{ /if }}
|
||||
|
||||
<script src='{{ appRootPath }}/clientFrontendDefinition.js'></script>
|
||||
<script src='{{ appRootPath }}/budibase-client.js'></script>
|
||||
<script src='/assets/clientFrontendDefinition.js'></script>
|
||||
<script src='/assets/budibase-client.js'></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
"component": "button"
|
||||
}
|
||||
},
|
||||
"embed": {
|
||||
"name": "Embed",
|
||||
"description": "Embed stuff",
|
||||
"props": {
|
||||
"embed": "string"
|
||||
}
|
||||
},
|
||||
"Navigation": {
|
||||
"name": "Navigation",
|
||||
"description": "A basic header navigation component",
|
||||
|
|
|
@ -98,6 +98,5 @@ window["##BUDIBASE_APPDEFINITION##"] = {
|
|||
nodeId: 0,
|
||||
},
|
||||
componentLibraries: ["budibase-standard-components"],
|
||||
appRootPath: "/testApp2",
|
||||
props: {},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
export let embed
|
||||
</script>
|
||||
|
||||
{@html embed}
|
|
@ -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 @@
|
|||
|
||||
<div class="root">
|
||||
<div class="content">
|
||||
{#if _logo}
|
||||
{#if logo}
|
||||
<div class="logo-container">
|
||||
<img src={_logo} alt="logo" />
|
||||
<img src={logo} alt="logo" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ export default async () => {
|
|||
const { initialisePage } = createApp(
|
||||
window.document,
|
||||
componentLibraries,
|
||||
{ appRootPath: "" },
|
||||
{},
|
||||
appDef,
|
||||
user,
|
||||
{},
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue