Merge branch 'master' into feature/add-grid-to-standard-components
This commit is contained in:
commit
28caf55ac4
|
@ -90,6 +90,8 @@ const contextToBindables = (models, walkResult) => context => {
|
||||||
runtimeBinding: `${contextParentPath}data.${key}`,
|
runtimeBinding: `${contextParentPath}data.${key}`,
|
||||||
// how the binding exressions looks to the user of the builder
|
// how the binding exressions looks to the user of the builder
|
||||||
readableBinding: `${context.instance._instanceName}.${model.name}.${key}`,
|
readableBinding: `${context.instance._instanceName}.${model.name}.${key}`,
|
||||||
|
// model / view info
|
||||||
|
model: context.model,
|
||||||
})
|
})
|
||||||
|
|
||||||
// see ModelViewSelect.svelte for the format of context.model
|
// see ModelViewSelect.svelte for the format of context.model
|
||||||
|
|
|
@ -36,7 +36,8 @@ export const saveCurrentPreviewItem = s =>
|
||||||
: saveScreenApi(s.currentPreviewItem, s)
|
: saveScreenApi(s.currentPreviewItem, s)
|
||||||
|
|
||||||
export const savePage = async s => {
|
export const savePage = async s => {
|
||||||
const page = s.pages[s.currentPageName]
|
const pageName = s.currentPageName || "main"
|
||||||
|
const page = s.pages[pageName]
|
||||||
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
|
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
|
||||||
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
||||||
uiFunctions: s.currentPageFunctions,
|
uiFunctions: s.currentPageFunctions,
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldOptions(field) {
|
function fieldOptions(field) {
|
||||||
return viewModel.schema[field].type === "string"
|
return viewModel.schema[field].type === "options"
|
||||||
? viewModel.schema[field].constraints.inclusion
|
? viewModel.schema[field].constraints.inclusion
|
||||||
: [true, false]
|
: [true, false]
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
.map(template => template.create())
|
.map(template => template.create())
|
||||||
|
|
||||||
for (let screen of screens) {
|
for (let screen of screens) {
|
||||||
console.log(JSON.stringify(screen))
|
|
||||||
try {
|
try {
|
||||||
await store.createScreen(screen)
|
await store.createScreen(screen)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input } from "@budibase/bbui"
|
import { Input, Label } from "@budibase/bbui"
|
||||||
import api from "builderStore/api"
|
import api from "builderStore/api"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
import analytics from "analytics"
|
import analytics from "analytics"
|
||||||
|
|
||||||
let keys = { budibase: "" }
|
let keys = { budibase: "" }
|
||||||
|
@ -38,6 +39,10 @@
|
||||||
edit
|
edit
|
||||||
value={keys.budibase}
|
value={keys.budibase}
|
||||||
label="Budibase API Key" />
|
label="Budibase API Key" />
|
||||||
|
<div>
|
||||||
|
<Label extraSmall grey>Instance ID (Webhooks)</Label>
|
||||||
|
<span>{$backendUiStore.selectedDatabase._id}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -45,4 +50,9 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: var(--spacing-xl);
|
grid-gap: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { Input, Button, Spacer, Select, ModalContent } from "@budibase/bbui"
|
import { Input, Button, Spacer, Select, ModalContent } from "@budibase/bbui"
|
||||||
import getTemplates from "builderStore/store/screenTemplates"
|
import getTemplates from "builderStore/store/screenTemplates"
|
||||||
import { some } from "lodash/fp"
|
import { some } from "lodash/fp"
|
||||||
|
import analytics from "analytics"
|
||||||
|
|
||||||
const CONTAINER = "@budibase/standard-components/container"
|
const CONTAINER = "@budibase/standard-components/container"
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
|
|
||||||
const templateChanged = newTemplateIndex => {
|
const templateChanged = newTemplateIndex => {
|
||||||
if (newTemplateIndex === undefined) return
|
if (newTemplateIndex === undefined) return
|
||||||
|
const template = templates[newTemplateIndex]
|
||||||
draftScreen = templates[newTemplateIndex].create()
|
draftScreen = templates[newTemplateIndex].create()
|
||||||
if (draftScreen.props._instanceName) {
|
if (draftScreen.props._instanceName) {
|
||||||
name = draftScreen.props._instanceName
|
name = draftScreen.props._instanceName
|
||||||
|
@ -63,6 +64,13 @@
|
||||||
|
|
||||||
store.createScreen(draftScreen)
|
store.createScreen(draftScreen)
|
||||||
|
|
||||||
|
if (templateIndex !== undefined) {
|
||||||
|
const template = templates[templateIndex]
|
||||||
|
analytics.captureEvent("Screen Created", {
|
||||||
|
template: template.id || template.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
finished()
|
finished()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,75 @@
|
||||||
<script>
|
<script>
|
||||||
import { DataList } from "@budibase/bbui"
|
import { DataList } from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { store } from "builderStore"
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
|
||||||
|
$: urls = getUrls()
|
||||||
|
|
||||||
const handleBlur = () => dispatch("change", value)
|
const handleBlur = () => dispatch("change", value)
|
||||||
|
|
||||||
|
// this will get urls of all screens, but only
|
||||||
|
// choose detail screens that are usable in the current context
|
||||||
|
// and substitute the :id param for the actual {{ ._id }} binding
|
||||||
|
const getUrls = () => {
|
||||||
|
const urls = [
|
||||||
|
...$store.screens
|
||||||
|
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||||
|
.map(screen => ({
|
||||||
|
name: screen.props._instanceName,
|
||||||
|
url: screen.route,
|
||||||
|
sort: screen.props._component,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
|
||||||
|
const bindableProperties = fetchBindableProperties({
|
||||||
|
componentInstanceId: $store.currentComponentInfo._id,
|
||||||
|
components: $store.components,
|
||||||
|
screen: $store.currentPreviewItem,
|
||||||
|
models: $backendUiStore.models,
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailScreens = $store.screens.filter(screen =>
|
||||||
|
screen.props._component.endsWith("/rowdetail")
|
||||||
|
)
|
||||||
|
|
||||||
|
for (let detailScreen of detailScreens) {
|
||||||
|
const idBinding = bindableProperties.find(p => {
|
||||||
|
if (
|
||||||
|
p.type === "context" &&
|
||||||
|
p.runtimeBinding.endsWith("._id") &&
|
||||||
|
p.model
|
||||||
|
) {
|
||||||
|
const modelId =
|
||||||
|
typeof p.model === "string" ? p.model : p.model.modelId
|
||||||
|
return modelId === detailScreen.props.model
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (idBinding) {
|
||||||
|
urls.push({
|
||||||
|
name: detailScreen.props._instanceName,
|
||||||
|
url: detailScreen.route.replace(
|
||||||
|
":id",
|
||||||
|
`{{ ${idBinding.runtimeBinding} }}`
|
||||||
|
),
|
||||||
|
sort: detailScreen.props._component,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DataList editable secondary on:blur={handleBlur} on:change bind:value>
|
<DataList editable secondary on:blur={handleBlur} on:change bind:value>
|
||||||
<option value="" />
|
<option value="" />
|
||||||
{#each $store.allScreens as screen}
|
{#each urls as url}
|
||||||
<option value={screen.route}>{screen.props._instanceName}</option>
|
<option value={url.url}>{url.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</DataList>
|
</DataList>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { isString, isUndefined } from "lodash/fp"
|
import { isString, isUndefined, cloneDeep } from "lodash/fp"
|
||||||
import { TYPE_MAP } from "./types"
|
import { TYPE_MAP } from "./types"
|
||||||
import { assign } from "lodash"
|
import { assign } from "lodash"
|
||||||
import { uuid } from "builderStore/uuid"
|
import { uuid } from "builderStore/uuid"
|
||||||
|
@ -83,13 +83,13 @@ const parsePropDef = propDef => {
|
||||||
if (isString(propDef)) {
|
if (isString(propDef)) {
|
||||||
if (!TYPE_MAP[propDef]) return error(`Type ${propDef} is not recognised.`)
|
if (!TYPE_MAP[propDef]) return error(`Type ${propDef} is not recognised.`)
|
||||||
|
|
||||||
return TYPE_MAP[propDef].default
|
return cloneDeep(TYPE_MAP[propDef].default)
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = TYPE_MAP[propDef.type]
|
const type = TYPE_MAP[propDef.type]
|
||||||
if (!type) return error(`Type ${propDef.type} is not recognised.`)
|
if (!type) return error(`Type ${propDef.type} is not recognised.`)
|
||||||
|
|
||||||
return propDef.default
|
return cloneDeep(propDef.default)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const arrayElementComponentName = (parentComponentName, arrayPropName) =>
|
export const arrayElementComponentName = (parentComponentName, arrayPropName) =>
|
||||||
|
|
|
@ -10,7 +10,6 @@ export const TYPE_MAP = {
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
default: [],
|
default: [],
|
||||||
options: [],
|
|
||||||
},
|
},
|
||||||
event: {
|
event: {
|
||||||
default: [],
|
default: [],
|
||||||
|
|
|
@ -361,19 +361,18 @@ export const typography = [
|
||||||
label: "Font",
|
label: "Font",
|
||||||
key: "font-family",
|
key: "font-family",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
defaultValue: "initial",
|
defaultValue: "Arial",
|
||||||
options: [
|
options: [
|
||||||
"initial",
|
|
||||||
"Arial",
|
"Arial",
|
||||||
"Arial Black",
|
"Arial Black",
|
||||||
"Cursive",
|
"Cursive",
|
||||||
"Courier",
|
"Courier",
|
||||||
"Comic Sans MS",
|
"Comic Sans MS",
|
||||||
"Helvetica",
|
"Helvetica",
|
||||||
|
"Helvetica Neue",
|
||||||
"Impact",
|
"Impact",
|
||||||
"Inter",
|
"Inter",
|
||||||
"Lucida Sans Unicode",
|
"Lucida Sans Unicode",
|
||||||
"Open Sans",
|
|
||||||
"Roboto",
|
"Roboto",
|
||||||
"Roboto Mono",
|
"Roboto Mono",
|
||||||
"Times New Roman",
|
"Times New Roman",
|
||||||
|
@ -467,9 +466,9 @@ export const background = [
|
||||||
label: "Gradient",
|
label: "Gradient",
|
||||||
key: "background-image",
|
key: "background-image",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
defaultValue: "None",
|
defaultValue: "",
|
||||||
options: [
|
options: [
|
||||||
{ label: "None", value: "None" },
|
{ label: "Select option", value: "" },
|
||||||
{
|
{
|
||||||
label: "Warm Flame",
|
label: "Warm Flame",
|
||||||
value: "linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);",
|
value: "linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);",
|
||||||
|
@ -518,9 +517,9 @@ export const background = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Image",
|
label: "Image",
|
||||||
key: "background-image",
|
key: "background",
|
||||||
control: Input,
|
control: Input,
|
||||||
placeholder: "Src",
|
placeholder: "url",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -665,7 +664,7 @@ export const transitions = [
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
placeholder: "sec",
|
placeholder: "sec",
|
||||||
options: ["0.2ms", "0.4ms", "0.8ms", "1s", "2s", "4s"],
|
options: ["0.4s", "0.6s", "0.8s", "1s", "2s", "4s"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Ease",
|
label: "Ease",
|
||||||
|
|
|
@ -386,7 +386,7 @@ export default {
|
||||||
{
|
{
|
||||||
label: "destinationUrl",
|
label: "destinationUrl",
|
||||||
key: "destinationUrl",
|
key: "destinationUrl",
|
||||||
control: Input,
|
control: ScreenSelect,
|
||||||
placeholder: "/table/_id",
|
placeholder: "/table/_id",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -435,7 +435,7 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Link Url",
|
label: "Link Url",
|
||||||
key: "linkUrl",
|
key: "linkUrl",
|
||||||
control: Input,
|
control: ScreenSelect,
|
||||||
placeholder: "Link URL",
|
placeholder: "Link URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -510,7 +510,7 @@ export default {
|
||||||
{
|
{
|
||||||
label: "Link Url",
|
label: "Link Url",
|
||||||
key: "linkUrl",
|
key: "linkUrl",
|
||||||
control: Input,
|
control: ScreenSelect,
|
||||||
placeholder: "Link URL",
|
placeholder: "Link URL",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const FIELDS = {
|
||||||
type: "options",
|
type: "options",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
presence: { allowEmpty: true },
|
presence: false,
|
||||||
inclusion: [],
|
inclusion: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -67,7 +67,7 @@ export const FIELDS = {
|
||||||
type: "link",
|
type: "link",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: { allowEmpty: true },
|
presence: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ exports.save = async function(ctx) {
|
||||||
views: {},
|
views: {},
|
||||||
...rest,
|
...rest,
|
||||||
}
|
}
|
||||||
|
let renameDocs = []
|
||||||
|
|
||||||
// if the model obj had an _id then it will have been retrieved
|
// if the model obj had an _id then it will have been retrieved
|
||||||
const oldModel = ctx.preExisting
|
const oldModel = ctx.preExisting
|
||||||
|
@ -49,14 +50,11 @@ exports.save = async function(ctx) {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
renameDocs = records.rows.map(({ doc }) => {
|
||||||
const docs = records.rows.map(({ doc }) => {
|
|
||||||
doc[_rename.updated] = doc[_rename.old]
|
doc[_rename.updated] = doc[_rename.old]
|
||||||
delete doc[_rename.old]
|
delete doc[_rename.old]
|
||||||
return doc
|
return doc
|
||||||
})
|
})
|
||||||
|
|
||||||
await db.bulkDocs(docs)
|
|
||||||
delete modelToSave._rename
|
delete modelToSave._rename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,9 +67,6 @@ exports.save = async function(ctx) {
|
||||||
modelView.schema = modelToSave.schema
|
modelView.schema = modelToSave.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await db.post(modelToSave)
|
|
||||||
modelToSave._rev = result.rev
|
|
||||||
|
|
||||||
// update linked records
|
// update linked records
|
||||||
await linkRecords.updateLinks({
|
await linkRecords.updateLinks({
|
||||||
instanceId,
|
instanceId,
|
||||||
|
@ -82,6 +77,14 @@ exports.save = async function(ctx) {
|
||||||
oldModel: oldModel,
|
oldModel: oldModel,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// don't perform any updates until relationships have been
|
||||||
|
// checked by the updateLinks function
|
||||||
|
if (renameDocs.length !== 0) {
|
||||||
|
await db.bulkDocs(renameDocs)
|
||||||
|
}
|
||||||
|
const result = await db.post(modelToSave)
|
||||||
|
modelToSave._rev = result.rev
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitModel(`model:save`, instanceId, modelToSave)
|
ctx.eventEmitter.emitModel(`model:save`, instanceId, modelToSave)
|
||||||
|
|
||||||
|
@ -105,11 +108,8 @@ exports.save = async function(ctx) {
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroy = async function(ctx) {
|
||||||
const instanceId = ctx.user.instanceId
|
const instanceId = ctx.user.instanceId
|
||||||
const db = new CouchDB(instanceId)
|
const db = new CouchDB(instanceId)
|
||||||
|
|
||||||
const modelToDelete = await db.get(ctx.params.modelId)
|
const modelToDelete = await db.get(ctx.params.modelId)
|
||||||
|
|
||||||
await db.remove(modelToDelete)
|
|
||||||
|
|
||||||
// Delete all records for that model
|
// Delete all records for that model
|
||||||
const records = await db.allDocs(
|
const records = await db.allDocs(
|
||||||
getRecordParams(ctx.params.modelId, null, {
|
getRecordParams(ctx.params.modelId, null, {
|
||||||
|
@ -117,7 +117,7 @@ exports.destroy = async function(ctx) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
await db.bulkDocs(
|
await db.bulkDocs(
|
||||||
records.rows.map(record => ({ _id: record.id, _deleted: true }))
|
records.rows.map(record => ({ ...record.doc, _deleted: true }))
|
||||||
)
|
)
|
||||||
|
|
||||||
// update linked records
|
// update linked records
|
||||||
|
@ -127,6 +127,9 @@ exports.destroy = async function(ctx) {
|
||||||
model: modelToDelete,
|
model: modelToDelete,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// don't remove the table itself until very end
|
||||||
|
await db.remove(modelToDelete)
|
||||||
|
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitModel(`model:delete`, instanceId, modelToDelete)
|
ctx.eventEmitter.emitModel(`model:delete`, instanceId, modelToDelete)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
|
|
@ -136,7 +136,7 @@ exports.performLocalFileProcessing = async function(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.serveApp = async function(ctx) {
|
exports.serveApp = async function(ctx) {
|
||||||
const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
|
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
||||||
|
|
||||||
// default to homedir
|
// default to homedir
|
||||||
const appPath = resolve(
|
const appPath = resolve(
|
||||||
|
@ -154,7 +154,7 @@ exports.serveApp = async function(ctx) {
|
||||||
// only set the appId cookie for /appId .. we COULD check for valid appIds
|
// only set the appId cookie for /appId .. we COULD check for valid appIds
|
||||||
// but would like to avoid that DB hit
|
// but would like to avoid that DB hit
|
||||||
const looksLikeAppId = /^(app_)?[0-9a-f]{32}$/.test(appId)
|
const looksLikeAppId = /^(app_)?[0-9a-f]{32}$/.test(appId)
|
||||||
if (looksLikeAppId && !ctx.isAuthenticated) {
|
if (looksLikeAppId && !ctx.auth.authenticated) {
|
||||||
const anonUser = {
|
const anonUser = {
|
||||||
userId: "ANON",
|
userId: "ANON",
|
||||||
accessLevelId: ANON_LEVEL_ID,
|
accessLevelId: ANON_LEVEL_ID,
|
||||||
|
@ -200,7 +200,7 @@ exports.serveAttachment = async function(ctx) {
|
||||||
|
|
||||||
exports.serveAppAsset = async function(ctx) {
|
exports.serveAppAsset = async function(ctx) {
|
||||||
// default to homedir
|
// default to homedir
|
||||||
const mainOrAuth = ctx.isAuthenticated ? "main" : "unauthenticated"
|
const mainOrAuth = ctx.auth.authenticated ? "main" : "unauthenticated"
|
||||||
|
|
||||||
const appPath = resolve(
|
const appPath = resolve(
|
||||||
budibaseAppsDir(),
|
budibaseAppsDir(),
|
||||||
|
|
|
@ -24,6 +24,7 @@ app.use(
|
||||||
)
|
)
|
||||||
|
|
||||||
app.context.eventEmitter = eventEmitter
|
app.context.eventEmitter = eventEmitter
|
||||||
|
app.context.auth = {}
|
||||||
|
|
||||||
// api routes
|
// api routes
|
||||||
app.use(api.routes())
|
app.use(api.routes())
|
||||||
|
|
|
@ -161,7 +161,7 @@ class LinkController {
|
||||||
})
|
})
|
||||||
// now add the docs to be deleted to the bulk operation
|
// now add the docs to be deleted to the bulk operation
|
||||||
operations.push(...toDeleteDocs)
|
operations.push(...toDeleteDocs)
|
||||||
// replace this field with a simple entry to denote there are links
|
// remove the field from this row, link doc will be added to record on way out
|
||||||
delete record[fieldName]
|
delete record[fieldName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,8 +234,16 @@ class LinkController {
|
||||||
for (let fieldName of Object.keys(schema)) {
|
for (let fieldName of Object.keys(schema)) {
|
||||||
const field = schema[fieldName]
|
const field = schema[fieldName]
|
||||||
if (field.type === "link") {
|
if (field.type === "link") {
|
||||||
|
// handle this in a separate try catch, want
|
||||||
|
// the put to bubble up as an error, if can't update
|
||||||
|
// table for some reason
|
||||||
|
let linkedModel
|
||||||
|
try {
|
||||||
|
linkedModel = await this._db.get(field.modelId)
|
||||||
|
} catch (err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// create the link field in the other model
|
// create the link field in the other model
|
||||||
const linkedModel = await this._db.get(field.modelId)
|
|
||||||
linkedModel.schema[field.fieldName] = {
|
linkedModel.schema[field.fieldName] = {
|
||||||
name: field.fieldName,
|
name: field.fieldName,
|
||||||
type: "link",
|
type: "link",
|
||||||
|
|
|
@ -42,6 +42,7 @@ exports.updateLinks = async function({
|
||||||
model,
|
model,
|
||||||
oldModel,
|
oldModel,
|
||||||
}) {
|
}) {
|
||||||
|
const baseReturnObj = record == null ? model : record
|
||||||
if (instanceId == null) {
|
if (instanceId == null) {
|
||||||
throw "Cannot operate without an instance ID."
|
throw "Cannot operate without an instance ID."
|
||||||
}
|
}
|
||||||
|
@ -50,12 +51,16 @@ exports.updateLinks = async function({
|
||||||
arguments[0].modelId = model._id
|
arguments[0].modelId = model._id
|
||||||
}
|
}
|
||||||
let linkController = new LinkController(arguments[0])
|
let linkController = new LinkController(arguments[0])
|
||||||
|
try {
|
||||||
if (
|
if (
|
||||||
!(await linkController.doesModelHaveLinkedFields()) &&
|
!(await linkController.doesModelHaveLinkedFields()) &&
|
||||||
(oldModel == null ||
|
(oldModel == null ||
|
||||||
!(await linkController.doesModelHaveLinkedFields(oldModel)))
|
!(await linkController.doesModelHaveLinkedFields(oldModel)))
|
||||||
) {
|
) {
|
||||||
return record
|
return baseReturnObj
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return baseReturnObj
|
||||||
}
|
}
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case EventType.RECORD_SAVE:
|
case EventType.RECORD_SAVE:
|
||||||
|
|
|
@ -57,22 +57,27 @@ exports.generateModelID = () => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the DB allDocs/query params for retrieving a record.
|
* Gets the DB allDocs/query params for retrieving a record.
|
||||||
* @param {string} modelId The model in which the records have been stored.
|
* @param {string|null} modelId The model in which the records have been stored.
|
||||||
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
|
* @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
|
||||||
* left null to get all the records in the model.
|
* left null to get all the records in the model.
|
||||||
* @param {object} otherProps Any other properties to add to the request.
|
* @param {object} otherProps Any other properties to add to the request.
|
||||||
* @returns {object} Parameters which can then be used with an allDocs request.
|
* @returns {object} Parameters which can then be used with an allDocs request.
|
||||||
*/
|
*/
|
||||||
exports.getRecordParams = (modelId, recordId = null, otherProps = {}) => {
|
exports.getRecordParams = (
|
||||||
|
modelId = null,
|
||||||
|
recordId = null,
|
||||||
|
otherProps = {}
|
||||||
|
) => {
|
||||||
if (modelId == null) {
|
if (modelId == null) {
|
||||||
throw "Cannot build params for records without a model ID"
|
return getDocParams(DocumentTypes.RECORD, null, otherProps)
|
||||||
}
|
} else {
|
||||||
const endOfKey =
|
const endOfKey =
|
||||||
recordId == null
|
recordId == null
|
||||||
? `${modelId}${SEPARATOR}`
|
? `${modelId}${SEPARATOR}`
|
||||||
: `${modelId}${SEPARATOR}${recordId}`
|
: `${modelId}${SEPARATOR}${recordId}`
|
||||||
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
|
return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a new record ID for the specified model.
|
* Gets a new record ID for the specified model.
|
||||||
|
@ -124,7 +129,14 @@ exports.generateAutomationID = () => {
|
||||||
* @returns {string} The new link doc ID which the automation doc can be stored under.
|
* @returns {string} The new link doc ID which the automation doc can be stored under.
|
||||||
*/
|
*/
|
||||||
exports.generateLinkID = (modelId1, modelId2, recordId1, recordId2) => {
|
exports.generateLinkID = (modelId1, modelId2, recordId1, recordId2) => {
|
||||||
return `${DocumentTypes.AUTOMATION}${SEPARATOR}${modelId1}${SEPARATOR}${modelId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}`
|
return `${DocumentTypes.LINK}${SEPARATOR}${modelId1}${SEPARATOR}${modelId2}${SEPARATOR}${recordId1}${SEPARATOR}${recordId2}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets parameters for retrieving link docs, this is a utility function for the getDocParams function.
|
||||||
|
*/
|
||||||
|
exports.getLinkParams = (otherProps = {}) => {
|
||||||
|
return getDocParams(DocumentTypes.LINK, null, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,8 +20,10 @@ module.exports = async (ctx, next) => {
|
||||||
if (builderToken) {
|
if (builderToken) {
|
||||||
try {
|
try {
|
||||||
const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret)
|
const jwtPayload = jwt.verify(builderToken, ctx.config.jwtSecret)
|
||||||
ctx.apiKey = jwtPayload.apiKey
|
ctx.auth = {
|
||||||
ctx.isAuthenticated = jwtPayload.accessLevelId === BUILDER_LEVEL_ID
|
apiKey: jwtPayload.apiKey,
|
||||||
|
authenticated: jwtPayload.accessLevelId === BUILDER_LEVEL_ID,
|
||||||
|
}
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
...jwtPayload,
|
...jwtPayload,
|
||||||
accessLevel: await getAccessLevel(
|
accessLevel: await getAccessLevel(
|
||||||
|
@ -38,14 +40,13 @@ module.exports = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appToken) {
|
if (!appToken) {
|
||||||
ctx.isAuthenticated = false
|
ctx.auth.authenticated = false
|
||||||
await next()
|
await next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
|
const jwtPayload = jwt.verify(appToken, ctx.config.jwtSecret)
|
||||||
ctx.apiKey = jwtPayload.apiKey
|
|
||||||
ctx.user = {
|
ctx.user = {
|
||||||
...jwtPayload,
|
...jwtPayload,
|
||||||
accessLevel: await getAccessLevel(
|
accessLevel: await getAccessLevel(
|
||||||
|
@ -53,7 +54,10 @@ module.exports = async (ctx, next) => {
|
||||||
jwtPayload.accessLevelId
|
jwtPayload.accessLevelId
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
ctx.isAuthenticated = ctx.user.accessLevelId !== ANON_LEVEL_ID
|
ctx.auth = {
|
||||||
|
authenticated: ctx.user.accessLevelId !== ANON_LEVEL_ID,
|
||||||
|
apiKey: jwtPayload.apiKey,
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,36 @@ const {
|
||||||
BUILDER_LEVEL_ID,
|
BUILDER_LEVEL_ID,
|
||||||
BUILDER,
|
BUILDER,
|
||||||
} = require("../utilities/accessLevels")
|
} = require("../utilities/accessLevels")
|
||||||
|
const environment = require("../environment")
|
||||||
|
const { apiKeyTable } = require("../db/dynamoClient")
|
||||||
|
|
||||||
module.exports = (permName, getItemId) => async (ctx, next) => {
|
module.exports = (permName, getItemId) => async (ctx, next) => {
|
||||||
if (!ctx.isAuthenticated) {
|
if (
|
||||||
|
environment.CLOUD &&
|
||||||
|
ctx.headers["x-api-key"] &&
|
||||||
|
ctx.headers["x-instanceid"]
|
||||||
|
) {
|
||||||
|
// api key header passed by external webhook
|
||||||
|
const apiKeyInfo = await apiKeyTable.get({
|
||||||
|
primary: ctx.headers["x-api-key"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (apiKeyInfo) {
|
||||||
|
ctx.auth = {
|
||||||
|
authenticated: true,
|
||||||
|
external: true,
|
||||||
|
apiKey: ctx.headers["x-api-key"],
|
||||||
|
}
|
||||||
|
ctx.user = {
|
||||||
|
instanceId: ctx.headers["x-instanceid"],
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.throw(403, "API key invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.auth.authenticated) {
|
||||||
ctx.throw(403, "Session not authenticated")
|
ctx.throw(403, "Session not authenticated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ module.exports = async (ctx, next) => {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await usageQuota.update(ctx.apiKey, property, usage)
|
await usageQuota.update(ctx.auth.apiKey, property, usage)
|
||||||
return next()
|
return next()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(403, err)
|
ctx.throw(403, err)
|
||||||
|
|
Loading…
Reference in New Issue