Merge branch 'master' into table-settings-updates
This commit is contained in:
commit
ce3d18972f
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.4.9",
|
"version": "3.4.11",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -67,6 +67,15 @@ describe("utils", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("gets appId from query params", async () => {
|
||||||
|
const ctx = structures.koa.newContext()
|
||||||
|
const expected = db.generateAppID()
|
||||||
|
ctx.query = { appId: expected }
|
||||||
|
|
||||||
|
const actual = await utils.getAppIdFromCtx(ctx)
|
||||||
|
expect(actual).toBe(expected)
|
||||||
|
})
|
||||||
|
|
||||||
it("doesn't get appId from url when previewing", async () => {
|
it("doesn't get appId from url when previewing", async () => {
|
||||||
const ctx = structures.koa.newContext()
|
const ctx = structures.koa.newContext()
|
||||||
const appId = db.generateAppID()
|
const appId = db.generateAppID()
|
||||||
|
|
|
@ -101,6 +101,11 @@ export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
appId = confirmAppId(pathId)
|
appId = confirmAppId(pathId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// look in queryParams
|
||||||
|
if (!appId && ctx.query?.appId) {
|
||||||
|
appId = confirmAppId(ctx.query?.appId as string)
|
||||||
|
}
|
||||||
|
|
||||||
// lookup using custom url - prod apps only
|
// lookup using custom url - prod apps only
|
||||||
// filter out the builder preview path which collides with the prod app path
|
// filter out the builder preview path which collides with the prod app path
|
||||||
// to ensure we don't load all apps excessively
|
// to ensure we don't load all apps excessively
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { writable, get } from "svelte/store"
|
import { writable, get } from "svelte/store"
|
||||||
import { findComponentParent, findComponentPath } from "@/helpers/components"
|
import { findComponentParent, findComponentPath } from "@/helpers/components"
|
||||||
import { selectedScreen, componentStore } from "@/stores/builder"
|
import { selectedScreen, componentStore } from "@/stores/builder"
|
||||||
|
import { DropPosition } from "@budibase/types"
|
||||||
export const DropPosition = {
|
export { DropPosition } from "@budibase/types"
|
||||||
ABOVE: "above",
|
|
||||||
BELOW: "below",
|
|
||||||
INSIDE: "inside",
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
source: null,
|
source: null,
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { layoutStore } from "./layouts.js"
|
import { layoutStore } from "./layouts"
|
||||||
import { appStore } from "./app.js"
|
import { appStore } from "./app"
|
||||||
import { componentStore, selectedComponent } from "./components"
|
import { componentStore, selectedComponent } from "./components"
|
||||||
import { navigationStore } from "./navigation.js"
|
import { navigationStore } from "./navigation"
|
||||||
import { themeStore } from "./theme.js"
|
import { themeStore } from "./theme"
|
||||||
import { screenStore, selectedScreen, sortedScreens } from "./screens"
|
import { screenStore, selectedScreen, sortedScreens } from "./screens"
|
||||||
import { builderStore } from "./builder.js"
|
import { builderStore } from "./builder"
|
||||||
import { hoverStore } from "./hover.js"
|
import { hoverStore } from "./hover"
|
||||||
import { previewStore } from "./preview.js"
|
import { previewStore } from "./preview"
|
||||||
import {
|
import {
|
||||||
automationStore,
|
automationStore,
|
||||||
selectedAutomation,
|
selectedAutomation,
|
||||||
automationHistoryStore,
|
automationHistoryStore,
|
||||||
} from "./automations.js"
|
} from "./automations"
|
||||||
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users"
|
||||||
import { deploymentStore } from "./deployments.js"
|
import { deploymentStore } from "./deployments"
|
||||||
import { contextMenuStore } from "./contextMenu.js"
|
import { contextMenuStore } from "./contextMenu"
|
||||||
import { snippets } from "./snippets"
|
import { snippets } from "./snippets"
|
||||||
import {
|
import {
|
||||||
screenComponentsList,
|
screenComponentsList,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onDestroy, onMount, setContext } from "svelte"
|
import { getContext, onDestroy, onMount, setContext } from "svelte"
|
||||||
import { builderStore } from "@/stores/builder.js"
|
import { builderStore } from "@/stores/builder"
|
||||||
import { blockStore } from "@/stores/blocks"
|
import { blockStore } from "@/stores/blocks"
|
||||||
|
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onDestroy } from "svelte"
|
import { getContext, onDestroy } from "svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { builderStore } from "../stores/builder.js"
|
import { builderStore } from "../stores/builder"
|
||||||
import Component from "@/components/Component.svelte"
|
import Component from "@/components/Component.svelte"
|
||||||
|
|
||||||
export let type
|
export let type
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import { CoreDropzone, ProgressCircle, Helpers } from "@budibase/bbui"
|
import { CoreDropzone, ProgressCircle, Helpers } from "@budibase/bbui"
|
||||||
import { getContext, onMount, onDestroy } from "svelte"
|
import { getContext, onMount, onDestroy } from "svelte"
|
||||||
import { builderStore } from "@/stores/builder.js"
|
import { builderStore } from "@/stores/builder"
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
|
|
||||||
export let datasourceId
|
export let datasourceId
|
||||||
|
|
|
@ -19,7 +19,6 @@ import type { ActionTypes } from "@/constants"
|
||||||
import { Readable } from "svelte/store"
|
import { Readable } from "svelte/store"
|
||||||
import {
|
import {
|
||||||
Screen,
|
Screen,
|
||||||
Layout,
|
|
||||||
Theme,
|
Theme,
|
||||||
AppCustomTheme,
|
AppCustomTheme,
|
||||||
PreviewDevice,
|
PreviewDevice,
|
||||||
|
@ -48,7 +47,6 @@ declare global {
|
||||||
// Data from builder
|
// Data from builder
|
||||||
"##BUDIBASE_APP_ID##"?: string
|
"##BUDIBASE_APP_ID##"?: string
|
||||||
"##BUDIBASE_IN_BUILDER##"?: true
|
"##BUDIBASE_IN_BUILDER##"?: true
|
||||||
"##BUDIBASE_PREVIEW_LAYOUT##"?: Layout
|
|
||||||
"##BUDIBASE_PREVIEW_SCREEN##"?: Screen
|
"##BUDIBASE_PREVIEW_SCREEN##"?: Screen
|
||||||
"##BUDIBASE_SELECTED_COMPONENT_ID##"?: string
|
"##BUDIBASE_SELECTED_COMPONENT_ID##"?: string
|
||||||
"##BUDIBASE_PREVIEW_ID##"?: number
|
"##BUDIBASE_PREVIEW_ID##"?: number
|
||||||
|
@ -59,13 +57,8 @@ declare global {
|
||||||
"##BUDIBASE_PREVIEW_NAVIGATION##"?: AppNavigation
|
"##BUDIBASE_PREVIEW_NAVIGATION##"?: AppNavigation
|
||||||
"##BUDIBASE_HIDDEN_COMPONENT_IDS##"?: string[]
|
"##BUDIBASE_HIDDEN_COMPONENT_IDS##"?: string[]
|
||||||
"##BUDIBASE_USED_PLUGINS##"?: Plugin[]
|
"##BUDIBASE_USED_PLUGINS##"?: Plugin[]
|
||||||
"##BUDIBASE_LOCATION##"?: {
|
|
||||||
protocol: string
|
|
||||||
hostname: string
|
|
||||||
port: string
|
|
||||||
}
|
|
||||||
"##BUDIBASE_SNIPPETS##"?: Snippet[]
|
"##BUDIBASE_SNIPPETS##"?: Snippet[]
|
||||||
"##BUDIBASE_COMPONENT_ERRORS##"?: Record<string, UIComponentError>[]
|
"##BUDIBASE_COMPONENT_ERRORS##"?: Record<string, UIComponentError[]>
|
||||||
"##BUDIBASE_CUSTOM_COMPONENTS##"?: CustomComponent[]
|
"##BUDIBASE_CUSTOM_COMPONENTS##"?: CustomComponent[]
|
||||||
|
|
||||||
// Other flags
|
// Other flags
|
||||||
|
@ -115,7 +108,6 @@ const loadBudibase = async () => {
|
||||||
builderStore.set({
|
builderStore.set({
|
||||||
...get(builderStore),
|
...get(builderStore),
|
||||||
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
inBuilder: !!window["##BUDIBASE_IN_BUILDER##"],
|
||||||
layout: window["##BUDIBASE_PREVIEW_LAYOUT##"],
|
|
||||||
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
screen: window["##BUDIBASE_PREVIEW_SCREEN##"],
|
||||||
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
|
selectedComponentId: window["##BUDIBASE_SELECTED_COMPONENT_ID##"],
|
||||||
previewId: window["##BUDIBASE_PREVIEW_ID##"],
|
previewId: window["##BUDIBASE_PREVIEW_ID##"],
|
||||||
|
@ -125,7 +117,6 @@ const loadBudibase = async () => {
|
||||||
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
navigation: window["##BUDIBASE_PREVIEW_NAVIGATION##"],
|
||||||
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
hiddenComponentIds: window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"],
|
||||||
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
usedPlugins: window["##BUDIBASE_USED_PLUGINS##"],
|
||||||
location: window["##BUDIBASE_LOCATION##"],
|
|
||||||
snippets: window["##BUDIBASE_SNIPPETS##"],
|
snippets: window["##BUDIBASE_SNIPPETS##"],
|
||||||
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
|
componentErrors: window["##BUDIBASE_COMPONENT_ERRORS##"],
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,9 +2,39 @@ import { writable, get } from "svelte/store"
|
||||||
import { API } from "@/api"
|
import { API } from "@/api"
|
||||||
import { devToolsStore } from "./devTools.js"
|
import { devToolsStore } from "./devTools.js"
|
||||||
import { eventStore } from "./events.js"
|
import { eventStore } from "./events.js"
|
||||||
|
import {
|
||||||
|
ComponentDefinition,
|
||||||
|
DropPosition,
|
||||||
|
PingSource,
|
||||||
|
PreviewDevice,
|
||||||
|
Screen,
|
||||||
|
Theme,
|
||||||
|
AppCustomTheme,
|
||||||
|
AppNavigation,
|
||||||
|
Plugin,
|
||||||
|
Snippet,
|
||||||
|
UIComponentError,
|
||||||
|
} from "@budibase/types"
|
||||||
|
|
||||||
|
interface BuilderStore {
|
||||||
|
inBuilder: boolean
|
||||||
|
screen?: Screen | null
|
||||||
|
selectedComponentId?: string | null
|
||||||
|
editMode: boolean
|
||||||
|
previewId?: number | null
|
||||||
|
theme?: Theme | null
|
||||||
|
customTheme?: AppCustomTheme | null
|
||||||
|
previewDevice?: PreviewDevice
|
||||||
|
navigation?: AppNavigation | null
|
||||||
|
hiddenComponentIds?: string[]
|
||||||
|
usedPlugins?: Plugin[] | null
|
||||||
|
metadata: { componentId: string; step: number } | null
|
||||||
|
snippets?: Snippet[] | null
|
||||||
|
componentErrors?: Record<string, UIComponentError[]>
|
||||||
|
}
|
||||||
|
|
||||||
const createBuilderStore = () => {
|
const createBuilderStore = () => {
|
||||||
const initialState = {
|
const initialState: BuilderStore = {
|
||||||
inBuilder: false,
|
inBuilder: false,
|
||||||
screen: null,
|
screen: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
|
@ -16,17 +46,13 @@ const createBuilderStore = () => {
|
||||||
navigation: null,
|
navigation: null,
|
||||||
hiddenComponentIds: [],
|
hiddenComponentIds: [],
|
||||||
usedPlugins: null,
|
usedPlugins: null,
|
||||||
eventResolvers: {},
|
|
||||||
metadata: null,
|
metadata: null,
|
||||||
snippets: null,
|
snippets: null,
|
||||||
componentErrors: {},
|
componentErrors: {},
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
|
||||||
layout: null,
|
|
||||||
}
|
}
|
||||||
const store = writable(initialState)
|
const store = writable(initialState)
|
||||||
const actions = {
|
const actions = {
|
||||||
selectComponent: id => {
|
selectComponent: (id: string) => {
|
||||||
if (id === get(store).selectedComponentId) {
|
if (id === get(store).selectedComponentId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -38,46 +64,59 @@ const createBuilderStore = () => {
|
||||||
devToolsStore.actions.setAllowSelection(false)
|
devToolsStore.actions.setAllowSelection(false)
|
||||||
eventStore.actions.dispatchEvent("select-component", { id })
|
eventStore.actions.dispatchEvent("select-component", { id })
|
||||||
},
|
},
|
||||||
updateProp: (prop, value) => {
|
updateProp: (prop: string, value: any) => {
|
||||||
eventStore.actions.dispatchEvent("update-prop", { prop, value })
|
eventStore.actions.dispatchEvent("update-prop", { prop, value })
|
||||||
},
|
},
|
||||||
updateStyles: async (styles, id) => {
|
updateStyles: async (styles: Record<string, any>, id: string) => {
|
||||||
await eventStore.actions.dispatchEvent("update-styles", {
|
await eventStore.actions.dispatchEvent("update-styles", {
|
||||||
styles,
|
styles,
|
||||||
id,
|
id,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
keyDown: (key, ctrlKey) => {
|
keyDown: (key: string, ctrlKey: boolean) => {
|
||||||
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
|
eventStore.actions.dispatchEvent("key-down", { key, ctrlKey })
|
||||||
},
|
},
|
||||||
duplicateComponent: (id, mode = "below", selectComponent = true) => {
|
duplicateComponent: (
|
||||||
|
id: string,
|
||||||
|
mode = DropPosition.BELOW,
|
||||||
|
selectComponent = true
|
||||||
|
) => {
|
||||||
eventStore.actions.dispatchEvent("duplicate-component", {
|
eventStore.actions.dispatchEvent("duplicate-component", {
|
||||||
id,
|
id,
|
||||||
mode,
|
mode,
|
||||||
selectComponent,
|
selectComponent,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteComponent: id => {
|
deleteComponent: (id: string) => {
|
||||||
eventStore.actions.dispatchEvent("delete-component", { id })
|
eventStore.actions.dispatchEvent("delete-component", { id })
|
||||||
},
|
},
|
||||||
notifyLoaded: () => {
|
notifyLoaded: () => {
|
||||||
eventStore.actions.dispatchEvent("preview-loaded")
|
eventStore.actions.dispatchEvent("preview-loaded")
|
||||||
},
|
},
|
||||||
analyticsPing: async ({ embedded }) => {
|
analyticsPing: async ({ embedded }: { embedded: boolean }) => {
|
||||||
try {
|
try {
|
||||||
await API.analyticsPing({ source: "app", embedded })
|
await API.analyticsPing({ source: PingSource.APP, embedded })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
moveComponent: async (componentId, destinationComponentId, mode) => {
|
moveComponent: async (
|
||||||
|
componentId: string,
|
||||||
|
destinationComponentId: string,
|
||||||
|
mode: DropPosition
|
||||||
|
) => {
|
||||||
await eventStore.actions.dispatchEvent("move-component", {
|
await eventStore.actions.dispatchEvent("move-component", {
|
||||||
componentId,
|
componentId,
|
||||||
destinationComponentId,
|
destinationComponentId,
|
||||||
mode,
|
mode,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
dropNewComponent: (component, parent, index, props) => {
|
dropNewComponent: (
|
||||||
|
component: string,
|
||||||
|
parent: string,
|
||||||
|
index: number,
|
||||||
|
props: Record<string, any>
|
||||||
|
) => {
|
||||||
eventStore.actions.dispatchEvent("drop-new-component", {
|
eventStore.actions.dispatchEvent("drop-new-component", {
|
||||||
component,
|
component,
|
||||||
parent,
|
parent,
|
||||||
|
@ -85,7 +124,7 @@ const createBuilderStore = () => {
|
||||||
props,
|
props,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setEditMode: enabled => {
|
setEditMode: (enabled: boolean) => {
|
||||||
if (enabled === get(store).editMode) {
|
if (enabled === get(store).editMode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -94,18 +133,18 @@ const createBuilderStore = () => {
|
||||||
requestAddComponent: () => {
|
requestAddComponent: () => {
|
||||||
eventStore.actions.dispatchEvent("request-add-component")
|
eventStore.actions.dispatchEvent("request-add-component")
|
||||||
},
|
},
|
||||||
highlightSetting: setting => {
|
highlightSetting: (setting: string) => {
|
||||||
eventStore.actions.dispatchEvent("highlight-setting", { setting })
|
eventStore.actions.dispatchEvent("highlight-setting", { setting })
|
||||||
},
|
},
|
||||||
ejectBlock: (id, definition) => {
|
ejectBlock: (id: string, definition: ComponentDefinition) => {
|
||||||
eventStore.actions.dispatchEvent("eject-block", { id, definition })
|
eventStore.actions.dispatchEvent("eject-block", { id, definition })
|
||||||
},
|
},
|
||||||
updateUsedPlugin: (name, hash) => {
|
updateUsedPlugin: (name: string, hash: string) => {
|
||||||
// Check if we used this plugin
|
// Check if we used this plugin
|
||||||
const used = get(store)?.usedPlugins?.find(x => x.name === name)
|
const used = get(store)?.usedPlugins?.find(x => x.name === name)
|
||||||
if (used) {
|
if (used) {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.usedPlugins = state.usedPlugins.filter(x => x.name !== name)
|
state.usedPlugins = state.usedPlugins!.filter(x => x.name !== name)
|
||||||
state.usedPlugins.push({
|
state.usedPlugins.push({
|
||||||
...used,
|
...used,
|
||||||
hash,
|
hash,
|
||||||
|
@ -117,13 +156,13 @@ const createBuilderStore = () => {
|
||||||
// Notify the builder so we can reload component definitions
|
// Notify the builder so we can reload component definitions
|
||||||
eventStore.actions.dispatchEvent("reload-plugin")
|
eventStore.actions.dispatchEvent("reload-plugin")
|
||||||
},
|
},
|
||||||
addParentComponent: (componentId, parentType) => {
|
addParentComponent: (componentId: string, parentType: string) => {
|
||||||
eventStore.actions.dispatchEvent("add-parent-component", {
|
eventStore.actions.dispatchEvent("add-parent-component", {
|
||||||
componentId,
|
componentId,
|
||||||
parentType,
|
parentType,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setMetadata: metadata => {
|
setMetadata: (metadata: { componentId: string; step: number }) => {
|
||||||
store.update(state => ({
|
store.update(state => ({
|
||||||
...state,
|
...state,
|
||||||
metadata,
|
metadata,
|
||||||
|
@ -132,7 +171,7 @@ const createBuilderStore = () => {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...store,
|
...store,
|
||||||
set: state => store.set({ ...initialState, ...state }),
|
set: (state: BuilderStore) => store.set({ ...initialState, ...state }),
|
||||||
actions,
|
actions,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
import { appStore } from "../app.js"
|
import { appStore } from "../app.js"
|
||||||
import { builderStore } from "../builder.js"
|
import { builderStore } from "../builder"
|
||||||
|
|
||||||
export const devToolsEnabled = derived(
|
export const devToolsEnabled = derived(
|
||||||
[appStore, builderStore],
|
[appStore, builderStore],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { appStore } from "../app.js"
|
import { appStore } from "../app.js"
|
||||||
import { builderStore } from "../builder.js"
|
import { builderStore } from "../builder"
|
||||||
import { derivedMemo } from "@budibase/frontend-core"
|
import { derivedMemo } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export const snippets = derivedMemo(
|
export const snippets = derivedMemo(
|
||||||
|
|
|
@ -36,11 +36,6 @@ const createScreenStore = () => {
|
||||||
activeScreen = Helpers.cloneDeep($builderStore.screen)
|
activeScreen = Helpers.cloneDeep($builderStore.screen)
|
||||||
screens = [activeScreen]
|
screens = [activeScreen]
|
||||||
|
|
||||||
// Legacy - allow the builder to specify a layout
|
|
||||||
if ($builderStore.layout) {
|
|
||||||
activeLayout = $builderStore.layout
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach meta
|
// Attach meta
|
||||||
const errors = $builderStore.componentErrors || {}
|
const errors = $builderStore.componentErrors || {}
|
||||||
const attachComponentMeta = component => {
|
const attachComponentMeta = component => {
|
||||||
|
|
|
@ -80,7 +80,6 @@
|
||||||
// Set some flags so the app knows we're in the builder
|
// Set some flags so the app knows we're in the builder
|
||||||
window["##BUDIBASE_IN_BUILDER##"] = true
|
window["##BUDIBASE_IN_BUILDER##"] = true
|
||||||
window["##BUDIBASE_APP_ID##"] = appId
|
window["##BUDIBASE_APP_ID##"] = appId
|
||||||
window["##BUDIBASE_PREVIEW_LAYOUT##"] = layout
|
|
||||||
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
window["##BUDIBASE_PREVIEW_SCREEN##"] = screen
|
||||||
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
window["##BUDIBASE_SELECTED_COMPONENT_ID##"] = selectedComponentId
|
||||||
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
window["##BUDIBASE_PREVIEW_ID##"] = Math.random()
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
|
window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
|
||||||
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
|
window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
|
||||||
window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
|
window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
|
||||||
window["##BUDIBASE_LOCATION##"] = location
|
|
||||||
window["##BUDIBASE_SNIPPETS##"] = snippets
|
window["##BUDIBASE_SNIPPETS##"] = snippets
|
||||||
window['##BUDIBASE_COMPONENT_ERRORS##'] = componentErrors
|
window['##BUDIBASE_COMPONENT_ERRORS##'] = componentErrors
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,14 @@ import Router from "@koa/router"
|
||||||
import * as controller from "../controllers/backup"
|
import * as controller from "../controllers/backup"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
import { permissions } from "@budibase/backend-core"
|
import { permissions } from "@budibase/backend-core"
|
||||||
|
import ensureTenantAppOwnership from "../../middleware/ensureTenantAppOwnership"
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/api/backups/export",
|
"/api/backups/export",
|
||||||
authorized(permissions.BUILDER),
|
authorized(permissions.BUILDER),
|
||||||
|
ensureTenantAppOwnership,
|
||||||
controller.exportAppDump
|
controller.exportAppDump
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { tenancy, utils, context } from "@budibase/backend-core"
|
||||||
|
import { UserCtx } from "@budibase/types"
|
||||||
|
|
||||||
|
async function ensureTenantAppOwnership(ctx: UserCtx, next: any) {
|
||||||
|
const appId = await utils.getAppIdFromCtx(ctx)
|
||||||
|
if (!appId) {
|
||||||
|
ctx.throw(400, "appId must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
const appTenantId = context.getTenantIDFromAppID(appId)
|
||||||
|
const tenantId = tenancy.getTenantId()
|
||||||
|
|
||||||
|
if (appTenantId !== tenantId) {
|
||||||
|
ctx.throw(403, "Unauthorized")
|
||||||
|
}
|
||||||
|
await next()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ensureTenantAppOwnership
|
|
@ -0,0 +1,75 @@
|
||||||
|
import ensureTenantAppOwnership from "../ensureTenantAppOwnership"
|
||||||
|
import { tenancy, utils } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
jest.mock("@budibase/backend-core", () => ({
|
||||||
|
...jest.requireActual("@budibase/backend-core"),
|
||||||
|
tenancy: {
|
||||||
|
getTenantId: jest.fn(),
|
||||||
|
},
|
||||||
|
utils: {
|
||||||
|
getAppIdFromCtx: jest.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
class TestConfiguration {
|
||||||
|
constructor(appId = "tenant_1") {
|
||||||
|
this.next = jest.fn()
|
||||||
|
this.throw = jest.fn()
|
||||||
|
this.middleware = ensureTenantAppOwnership
|
||||||
|
|
||||||
|
this.ctx = {
|
||||||
|
next: this.next,
|
||||||
|
throw: this.throw,
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.getAppIdFromCtx.mockResolvedValue(appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeMiddleware() {
|
||||||
|
return this.middleware(this.ctx, this.next)
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach() {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Ensure Tenant Ownership Middleware", () => {
|
||||||
|
let config
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
config = new TestConfiguration()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
config.afterEach()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls next() when appId matches tenant ID", async () => {
|
||||||
|
tenancy.getTenantId.mockReturnValue("tenant_1")
|
||||||
|
|
||||||
|
await config.executeMiddleware()
|
||||||
|
|
||||||
|
expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx)
|
||||||
|
expect(config.next).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws when tenant appId does not match tenant ID", async () => {
|
||||||
|
const appId = "app_dev_tenant3_fce449c4d75b4e4a9c7a6980d82a3e22"
|
||||||
|
utils.getAppIdFromCtx.mockResolvedValue(appId)
|
||||||
|
tenancy.getTenantId.mockReturnValue("tenant_2")
|
||||||
|
|
||||||
|
await config.executeMiddleware()
|
||||||
|
|
||||||
|
expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx)
|
||||||
|
expect(config.throw).toHaveBeenCalledWith(403, "Unauthorized")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws 400 when appId is missing", async () => {
|
||||||
|
utils.getAppIdFromCtx.mockResolvedValue(null)
|
||||||
|
|
||||||
|
await config.executeMiddleware()
|
||||||
|
|
||||||
|
expect(config.throw).toHaveBeenCalledWith(400, "appId must be provided")
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,2 +1,8 @@
|
||||||
// type purely to capture structures that the type is unknown, but maybe known later
|
// type purely to capture structures that the type is unknown, but maybe known later
|
||||||
export type UIObject = Record<string, any>
|
export type UIObject = Record<string, any>
|
||||||
|
|
||||||
|
export const enum DropPosition {
|
||||||
|
ABOVE = "above",
|
||||||
|
BELOW = "below",
|
||||||
|
INSIDE = "inside",
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue