Merge branch 'feature/query-variables' of github.com:Budibase/budibase into rest-pagination
This commit is contained in:
commit
abc02d812a
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="bold">{value}</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
export let value
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<code>{value}</code>
|
|
@ -61,6 +61,10 @@ export { default as ColorPicker } from "./ColorPicker/ColorPicker.svelte"
|
||||||
export { default as InlineAlert } from "./InlineAlert/InlineAlert.svelte"
|
export { default as InlineAlert } from "./InlineAlert/InlineAlert.svelte"
|
||||||
export { default as Banner } from "./Banner/Banner.svelte"
|
export { default as Banner } from "./Banner/Banner.svelte"
|
||||||
|
|
||||||
|
// Renderers
|
||||||
|
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||||
|
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
||||||
|
|
||||||
// Typography
|
// Typography
|
||||||
export { default as Body } from "./Typography/Body.svelte"
|
export { default as Body } from "./Typography/Body.svelte"
|
||||||
export { default as Heading } from "./Typography/Heading.svelte"
|
export { default as Heading } from "./Typography/Heading.svelte"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.27-alpha.1",
|
"@budibase/bbui": "^1.0.27-alpha.2",
|
||||||
"@budibase/client": "^1.0.27-alpha.1",
|
"@budibase/client": "^1.0.27-alpha.2",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^1.0.27-alpha.1",
|
"@budibase/string-templates": "^1.0.27-alpha.2",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
|
|
||||||
const BASE_ROLE = { _id: "", inherits: "BASIC", permissionId: "Read/Write" }
|
const BASE_ROLE = { _id: "", inherits: "BASIC", permissionId: "write" }
|
||||||
|
|
||||||
let basePermissions = []
|
let basePermissions = []
|
||||||
let selectedRole = BASE_ROLE
|
let selectedRole = BASE_ROLE
|
||||||
|
|
|
@ -175,7 +175,8 @@
|
||||||
onConfirm={datasources.removeSchemaError}
|
onConfirm={datasources.removeSchemaError}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<Table
|
{#if plusTables && Object.values(plusTables).length > 0}
|
||||||
|
<Table
|
||||||
on:click={({ detail }) => onClickTable(detail)}
|
on:click={({ detail }) => onClickTable(detail)}
|
||||||
schema={tableSchema}
|
schema={tableSchema}
|
||||||
data={Object.values(plusTables)}
|
data={Object.values(plusTables)}
|
||||||
|
@ -183,7 +184,10 @@
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
customRenderers={[{ column: "primary", component: ArrayRenderer }]}
|
customRenderers={[{ column: "primary", component: ArrayRenderer }]}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<Body size="S"><i>No tables found.</i></Body>
|
||||||
|
{/if}
|
||||||
{#if plusTables?.length !== 0}
|
{#if plusTables?.length !== 0}
|
||||||
<Divider size="S" />
|
<Divider size="S" />
|
||||||
<div class="query-header">
|
<div class="query-header">
|
||||||
|
@ -196,14 +200,18 @@
|
||||||
Tell budibase how your tables are related to get even more smart features.
|
Tell budibase how your tables are related to get even more smart features.
|
||||||
</Body>
|
</Body>
|
||||||
{/if}
|
{/if}
|
||||||
<Table
|
{#if relationshipInfo && relationshipInfo.length > 0}
|
||||||
|
<Table
|
||||||
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
on:click={({ detail }) => openRelationshipModal(detail.from, detail.to)}
|
||||||
schema={relationshipSchema}
|
schema={relationshipSchema}
|
||||||
data={relationshipInfo}
|
data={relationshipInfo}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
/>
|
/>
|
||||||
|
{:else}
|
||||||
|
<Body size="S"><i>No relationships configured.</i></Body>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.query-header {
|
.query-header {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Body, Table } from "@budibase/bbui"
|
import { Body, Table, BoldRenderer, CodeRenderer } from "@budibase/bbui"
|
||||||
import { queries as queriesStore } from "stores/backend"
|
import { queries as queriesStore } from "stores/backend"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@
|
||||||
|
|
||||||
const dynamicVariableSchema = {
|
const dynamicVariableSchema = {
|
||||||
name: "",
|
name: "",
|
||||||
value: "",
|
|
||||||
query: "",
|
query: "",
|
||||||
|
value: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = dynamicVariable => {
|
const onClick = dynamicVariable => {
|
||||||
|
@ -36,12 +36,19 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Table
|
{#if dynamicVariables && dynamicVariables.length > 0}
|
||||||
|
<Table
|
||||||
on:click={({ detail }) => onClick(detail)}
|
on:click={({ detail }) => onClick(detail)}
|
||||||
schema={dynamicVariableSchema}
|
schema={dynamicVariableSchema}
|
||||||
data={dynamicVariables}
|
data={dynamicVariables}
|
||||||
allowEditColumns={false}
|
allowEditColumns={false}
|
||||||
allowEditRows={false}
|
allowEditRows={false}
|
||||||
allowSelectRows={false}
|
allowSelectRows={false}
|
||||||
/>
|
customRenderers={[
|
||||||
<Body size="S" />
|
{ column: "name", component: BoldRenderer },
|
||||||
|
{ column: "value", component: CodeRenderer },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Body size="S"><i>No dynamic variables specified.</i></Body>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -120,62 +120,6 @@ export function flipHeaderState(headersActivity) {
|
||||||
return enabled
|
return enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert dynamic variables list to simple key/val object
|
|
||||||
export function getDynamicVariables(datasource, queryId) {
|
|
||||||
const variablesList = datasource?.config?.dynamicVariables
|
|
||||||
if (variablesList && variablesList.length > 0) {
|
|
||||||
const filtered = queryId
|
|
||||||
? variablesList.filter(variable => variable.queryId === queryId)
|
|
||||||
: variablesList
|
|
||||||
return filtered.reduce(
|
|
||||||
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert dynamic variables object back to a list, enrich with query id
|
|
||||||
export function rebuildVariables(datasource, queryId, variables) {
|
|
||||||
let newVariables = []
|
|
||||||
if (variables) {
|
|
||||||
newVariables = Object.entries(variables).map(entry => {
|
|
||||||
return {
|
|
||||||
name: entry[0],
|
|
||||||
value: entry[1],
|
|
||||||
queryId,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
let existing = datasource?.config?.dynamicVariables || []
|
|
||||||
// filter out any by same name
|
|
||||||
existing = existing.filter(
|
|
||||||
variable =>
|
|
||||||
!newVariables.find(
|
|
||||||
newVar => newVar.name.toLowerCase() === variable.name.toLowerCase()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return [...existing, ...newVariables]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldShowVariables(dynamicVariables, variablesReadOnly) {
|
|
||||||
return !!(
|
|
||||||
dynamicVariables &&
|
|
||||||
// show when editable or when read only and not empty
|
|
||||||
(!variablesReadOnly || Object.keys(dynamicVariables).length > 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildAuthConfigs(datasource) {
|
|
||||||
if (datasource?.config?.authConfigs) {
|
|
||||||
return datasource.config.authConfigs.map(c => ({
|
|
||||||
label: c.name,
|
|
||||||
value: c._id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
breakQueryString,
|
breakQueryString,
|
||||||
buildQueryString,
|
buildQueryString,
|
||||||
|
@ -184,8 +128,4 @@ export default {
|
||||||
keyValueToQueryParameters,
|
keyValueToQueryParameters,
|
||||||
queryParametersToKeyValue,
|
queryParametersToKeyValue,
|
||||||
schemaToFields,
|
schemaToFields,
|
||||||
getDynamicVariables,
|
|
||||||
rebuildVariables,
|
|
||||||
shouldShowVariables,
|
|
||||||
buildAuthConfigs,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,33 @@
|
||||||
export let query
|
export let query
|
||||||
export let bodyType
|
export let bodyType
|
||||||
|
|
||||||
|
let text = ""
|
||||||
|
let json = ""
|
||||||
|
|
||||||
$: checkRequestBody(bodyType)
|
$: checkRequestBody(bodyType)
|
||||||
|
$: updateRequestBody(bodyType, text, json)
|
||||||
|
|
||||||
function checkRequestBody(type) {
|
function checkRequestBody(type) {
|
||||||
if (!bodyType || !query) {
|
if (!bodyType || !query) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentType = typeof query?.fields.requestBody
|
const currentType = typeof query?.fields.requestBody
|
||||||
if (objectTypes.includes(type) && currentType !== "object") {
|
const isObject = objectTypes.includes(type)
|
||||||
query.fields.requestBody = {}
|
const isText = textTypes.includes(type)
|
||||||
} else if (textTypes.includes(type) && currentType !== "string") {
|
if (isText && currentType === "string") {
|
||||||
query.fields.requestBody = ""
|
text = query.fields.requestBody
|
||||||
|
} else if (isObject && currentType === "object") {
|
||||||
|
json = query.fields.requestBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestBody(type, text, json) {
|
||||||
|
if (type === RawRestBodyTypes.NONE) {
|
||||||
|
query.fields.requestBody = null
|
||||||
|
} else if (objectTypes.includes(type)) {
|
||||||
|
query.fields.requestBody = json
|
||||||
|
} else {
|
||||||
|
query.fields.requestBody = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,16 +65,12 @@
|
||||||
<Body size="S" weight="800">THE REQUEST DOES NOT HAVE A BODY</Body>
|
<Body size="S" weight="800">THE REQUEST DOES NOT HAVE A BODY</Body>
|
||||||
</div>
|
</div>
|
||||||
{:else if objectTypes.includes(bodyType)}
|
{:else if objectTypes.includes(bodyType)}
|
||||||
<KeyValueBuilder
|
<KeyValueBuilder bind:object={json} name="param" headings />
|
||||||
bind:object={query.fields.requestBody}
|
|
||||||
name="param"
|
|
||||||
headings
|
|
||||||
/>
|
|
||||||
{:else if textTypes.includes(bodyType)}
|
{:else if textTypes.includes(bodyType)}
|
||||||
<CodeMirrorEditor
|
<CodeMirrorEditor
|
||||||
height={200}
|
height={200}
|
||||||
mode={editorMode(bodyType)}
|
mode={editorMode(bodyType)}
|
||||||
value={query.fields.requestBody}
|
value={text}
|
||||||
resize="vertical"
|
resize="vertical"
|
||||||
on:change={e => (query.fields.requestBody = e.detail)}
|
on:change={e => (query.fields.requestBody = e.detail)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
|
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
|
||||||
import Placeholder from "assets/bb-spaceship.svg"
|
import Placeholder from "assets/bb-spaceship.svg"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { RawRestBodyTypes } from "constants/backend"
|
||||||
|
|
||||||
let query, datasource
|
let query, datasource
|
||||||
let breakQs = {},
|
let breakQs = {},
|
||||||
|
@ -54,13 +55,10 @@
|
||||||
$: checkQueryName(url)
|
$: checkQueryName(url)
|
||||||
$: responseSuccess = response?.info?.code >= 200 && response?.info?.code < 400
|
$: responseSuccess = response?.info?.code >= 200 && response?.info?.code < 400
|
||||||
$: isGet = query?.queryVerb === "read"
|
$: isGet = query?.queryVerb === "read"
|
||||||
$: authConfigs = restUtils.buildAuthConfigs(datasource)
|
$: authConfigs = buildAuthConfigs(datasource)
|
||||||
$: schemaReadOnly = !responseSuccess
|
$: schemaReadOnly = !responseSuccess
|
||||||
$: variablesReadOnly = !responseSuccess
|
$: variablesReadOnly = !responseSuccess
|
||||||
$: showVariablesTab = restUtils.shouldShowVariables(
|
$: showVariablesTab = shouldShowVariables(dynamicVariables, variablesReadOnly)
|
||||||
dynamicVariables,
|
|
||||||
variablesReadOnly
|
|
||||||
)
|
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
return cloneDeep(
|
||||||
|
@ -117,11 +115,7 @@
|
||||||
notifications.success(`Request saved successfully.`)
|
notifications.success(`Request saved successfully.`)
|
||||||
|
|
||||||
if (dynamicVariables) {
|
if (dynamicVariables) {
|
||||||
datasource.config.dynamicVariables = restUtils.rebuildVariables(
|
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
||||||
datasource,
|
|
||||||
saveId,
|
|
||||||
dynamicVariables
|
|
||||||
)
|
|
||||||
datasource = await datasources.save(datasource)
|
datasource = await datasources.save(datasource)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -160,6 +154,16 @@
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildAuthConfigs = datasource => {
|
||||||
|
if (datasource?.config?.authConfigs) {
|
||||||
|
return datasource.config.authConfigs.map(c => ({
|
||||||
|
label: c.name,
|
||||||
|
value: c._id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const schemaMenuItems = [
|
const schemaMenuItems = [
|
||||||
{
|
{
|
||||||
text: "Create dynamic variable",
|
text: "Create dynamic variable",
|
||||||
|
@ -179,6 +183,49 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// convert dynamic variables list to simple key/val object
|
||||||
|
const getDynamicVariables = (datasource, queryId) => {
|
||||||
|
const variablesList = datasource?.config?.dynamicVariables
|
||||||
|
if (variablesList && variablesList.length > 0) {
|
||||||
|
const filtered = queryId
|
||||||
|
? variablesList.filter(variable => variable.queryId === queryId)
|
||||||
|
: variablesList
|
||||||
|
return filtered.reduce(
|
||||||
|
(acc, next) => ({ ...acc, [next.name]: next.value }),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert dynamic variables object back to a list, enrich with query id
|
||||||
|
const rebuildVariables = queryId => {
|
||||||
|
let variables = []
|
||||||
|
if (dynamicVariables) {
|
||||||
|
variables = Object.entries(dynamicVariables).map(entry => {
|
||||||
|
return {
|
||||||
|
name: entry[0],
|
||||||
|
value: entry[1],
|
||||||
|
queryId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let existing = datasource?.config?.dynamicVariables || []
|
||||||
|
// remove existing query variables (for changes and deletions)
|
||||||
|
existing = existing.filter(variable => variable.queryId !== queryId)
|
||||||
|
// re-add the new query variables
|
||||||
|
return [...existing, ...variables]
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldShowVariables = (dynamicVariables, variablesReadOnly) => {
|
||||||
|
return !!(
|
||||||
|
dynamicVariables &&
|
||||||
|
// show when editable or when read only and not empty
|
||||||
|
(!variablesReadOnly || Object.keys(dynamicVariables).length > 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
query = getSelectedQuery()
|
query = getSelectedQuery()
|
||||||
// clear any unsaved changes to the datasource
|
// clear any unsaved changes to the datasource
|
||||||
|
@ -214,12 +261,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (query && !query.fields.bodyType) {
|
if (query && !query.fields.bodyType) {
|
||||||
query.fields.bodyType = "none"
|
if (query.fields.requestBody) {
|
||||||
|
query.fields.bodyType = RawRestBodyTypes.JSON
|
||||||
|
} else {
|
||||||
|
query.fields.bodyType = RawRestBodyTypes.NONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (query && !query.fields.pagination) {
|
if (query && !query.fields.pagination) {
|
||||||
query.fields.pagination = {}
|
query.fields.pagination = {}
|
||||||
}
|
}
|
||||||
dynamicVariables = restUtils.getDynamicVariables(datasource, query._id)
|
dynamicVariables = getDynamicVariables(datasource, query._id)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.27-alpha.1",
|
"@budibase/bbui": "^1.0.27-alpha.2",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^1.0.27-alpha.1",
|
"@budibase/string-templates": "^1.0.27-alpha.2",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -57,6 +57,12 @@ module FetchMock {
|
||||||
],
|
],
|
||||||
bookmark: "test",
|
bookmark: "test",
|
||||||
})
|
})
|
||||||
|
} else if (url.includes("google.com")) {
|
||||||
|
return json({
|
||||||
|
url,
|
||||||
|
opts,
|
||||||
|
value: "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"en-GB\"></html>",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return fetch(url, opts)
|
return fetch(url, opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/auth": "^1.0.27-alpha.1",
|
"@budibase/auth": "^1.0.27-alpha.2",
|
||||||
"@budibase/client": "^1.0.27-alpha.1",
|
"@budibase/client": "^1.0.27-alpha.2",
|
||||||
"@budibase/string-templates": "^1.0.27-alpha.1",
|
"@budibase/string-templates": "^1.0.27-alpha.2",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -17,8 +17,12 @@ const parseBody = (curl: any) => {
|
||||||
if (curl.data) {
|
if (curl.data) {
|
||||||
const keys = Object.keys(curl.data)
|
const keys = Object.keys(curl.data)
|
||||||
if (keys.length) {
|
if (keys.length) {
|
||||||
const key = keys[0]
|
let key = keys[0]
|
||||||
try {
|
try {
|
||||||
|
// filter out the dollar syntax used by curl for shell support
|
||||||
|
if (key.startsWith("$")) {
|
||||||
|
key = key.substring(1)
|
||||||
|
}
|
||||||
return JSON.parse(key)
|
return JSON.parse(key)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|
|
@ -144,8 +144,8 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
datasource,
|
datasource,
|
||||||
queryVerb: query.queryVerb,
|
queryVerb: query.queryVerb,
|
||||||
fields: query.fields,
|
fields: query.fields,
|
||||||
parameters: ctx.request.body.parameter,
|
|
||||||
pagination: ctx.request.body.pagination,
|
pagination: ctx.request.body.pagination,
|
||||||
|
parameters: ctx.request.body.parameters,
|
||||||
transformer: query.transformer,
|
transformer: query.transformer,
|
||||||
queryId: ctx.params.queryId,
|
queryId: ctx.params.queryId,
|
||||||
})
|
})
|
||||||
|
|
|
@ -191,7 +191,8 @@ class QueryBuilder {
|
||||||
}
|
}
|
||||||
if (this.query.equal) {
|
if (this.query.equal) {
|
||||||
build(this.query.equal, (key, value) => {
|
build(this.query.equal, (key, value) => {
|
||||||
if (!value) {
|
// 0 evaluates to false, which means we would return all rows if we don't check it
|
||||||
|
if (!value && value !== 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return `${key}:${builder.preprocess(value, allPreProcessingOpts)}`
|
return `${key}:${builder.preprocess(value, allPreProcessingOpts)}`
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Mock out postgres for this
|
// Mock out postgres for this
|
||||||
jest.mock("pg")
|
jest.mock("pg")
|
||||||
|
jest.mock("node-fetch")
|
||||||
|
|
||||||
// Mock isProdAppID to we can later mock the implementation and pretend we are
|
// Mock isProdAppID to we can later mock the implementation and pretend we are
|
||||||
// using prod app IDs
|
// using prod app IDs
|
||||||
|
@ -226,4 +227,76 @@ describe("/queries", () => {
|
||||||
.expect(400)
|
.expect(400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("test variables", () => {
|
||||||
|
async function restDatasource(cfg) {
|
||||||
|
return await config.createDatasource({
|
||||||
|
datasource: {
|
||||||
|
...basicDatasource().datasource,
|
||||||
|
source: "REST",
|
||||||
|
config: cfg || {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should work with static variables", async () => {
|
||||||
|
const datasource = await restDatasource({
|
||||||
|
staticVariables: {
|
||||||
|
variable: "google",
|
||||||
|
variable2: "1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/queries/preview`)
|
||||||
|
.send({
|
||||||
|
datasourceId: datasource._id,
|
||||||
|
parameters: {},
|
||||||
|
fields: {
|
||||||
|
path: "www.{{ variable }}.com",
|
||||||
|
queryString: "test={{ variable2 }}",
|
||||||
|
},
|
||||||
|
queryVerb: "read",
|
||||||
|
})
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
// these responses come from the mock
|
||||||
|
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
||||||
|
expect(res.body.rows[0].url).toEqual("http://www.google.com?test=1")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should work with dynamic variables", async () => {
|
||||||
|
const datasource = await restDatasource()
|
||||||
|
const basedOnQuery = await config.createQuery({
|
||||||
|
...basicQuery(datasource._id),
|
||||||
|
fields: {
|
||||||
|
path: "www.google.com",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await config.updateDatasource({
|
||||||
|
...datasource,
|
||||||
|
config: {
|
||||||
|
dynamicVariables: [
|
||||||
|
{ queryId: basedOnQuery._id, name: "variable3", value: "{{ data.0.[value] }}" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/queries/preview`)
|
||||||
|
.send({
|
||||||
|
datasourceId: datasource._id,
|
||||||
|
parameters: {},
|
||||||
|
fields: {
|
||||||
|
path: "www.google.com",
|
||||||
|
queryString: "test={{ variable3 }}",
|
||||||
|
},
|
||||||
|
queryVerb: "read",
|
||||||
|
})
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(200)
|
||||||
|
expect(res.body.schemaFields).toEqual(["url", "opts", "value"])
|
||||||
|
expect(res.body.rows[0].url).toContain("doctype html")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -242,6 +242,16 @@ export interface RestConfig {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
authConfigs: AuthConfig[]
|
authConfigs: AuthConfig[]
|
||||||
|
staticVariables: {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
dynamicVariables: [
|
||||||
|
{
|
||||||
|
name: string
|
||||||
|
queryId: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginationConfig {
|
export interface PaginationConfig {
|
||||||
|
|
|
@ -53,7 +53,10 @@ module RestModule {
|
||||||
const { performance } = require("perf_hooks")
|
const { performance } = require("perf_hooks")
|
||||||
const FormData = require("form-data")
|
const FormData = require("form-data")
|
||||||
const { URLSearchParams } = require("url")
|
const { URLSearchParams } = require("url")
|
||||||
const { parseStringPromise: xmlParser, Builder: XmlBuilder } = require("xml2js")
|
const {
|
||||||
|
parseStringPromise: xmlParser,
|
||||||
|
Builder: XmlBuilder,
|
||||||
|
} = require("xml2js")
|
||||||
|
|
||||||
const SCHEMA: Integration = {
|
const SCHEMA: Integration = {
|
||||||
docs: "https://github.com/node-fetch/node-fetch",
|
docs: "https://github.com/node-fetch/node-fetch",
|
||||||
|
@ -238,7 +241,7 @@ module RestModule {
|
||||||
break
|
break
|
||||||
case BodyTypes.XML:
|
case BodyTypes.XML:
|
||||||
if (object != null) {
|
if (object != null) {
|
||||||
string = (new XmlBuilder()).buildObject(object)
|
string = new XmlBuilder().buildObject(object)
|
||||||
}
|
}
|
||||||
input.body = string
|
input.body = string
|
||||||
input.headers["Content-Type"] = "application/xml"
|
input.headers["Content-Type"] = "application/xml"
|
||||||
|
|
|
@ -316,6 +316,16 @@ class TestConfiguration {
|
||||||
return this.datasource
|
return this.datasource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateDatasource(datasource) {
|
||||||
|
const response = await this._req(
|
||||||
|
datasource,
|
||||||
|
{ datasourceId: datasource._id },
|
||||||
|
controllers.datasource.update
|
||||||
|
)
|
||||||
|
this.datasource = response.datasource
|
||||||
|
return this.datasource
|
||||||
|
}
|
||||||
|
|
||||||
async createQuery(config = null) {
|
async createQuery(config = null) {
|
||||||
if (!this.datasource && !config) {
|
if (!this.datasource && !config) {
|
||||||
throw "No data source created for query."
|
throw "No data source created for query."
|
||||||
|
|
|
@ -5,6 +5,9 @@ const { integrations } = require("../integrations")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
const { processStringSync } = require("@budibase/string-templates")
|
||||||
const CouchDB = require("../db")
|
const CouchDB = require("../db")
|
||||||
|
|
||||||
|
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
|
||||||
|
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
|
||||||
|
|
||||||
class QueryRunner {
|
class QueryRunner {
|
||||||
constructor(input, flags = { noRecursiveQuery: false }) {
|
constructor(input, flags = { noRecursiveQuery: false }) {
|
||||||
this.appId = input.appId
|
this.appId = input.appId
|
||||||
|
@ -171,7 +174,12 @@ class QueryRunner {
|
||||||
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
enrichedQuery[key] = this.enrichQueryFields(fields[key], parameters)
|
||||||
} else if (typeof fields[key] === "string") {
|
} else if (typeof fields[key] === "string") {
|
||||||
// enrich string value as normal
|
// enrich string value as normal
|
||||||
enrichedQuery[key] = processStringSync(fields[key], parameters, {
|
let value = fields[key]
|
||||||
|
// add triple brace to avoid escaping e.g. '=' in cookie header
|
||||||
|
if (IS_HANDLEBARS.test(value) && !IS_TRIPLE_BRACE.test(value)) {
|
||||||
|
value = `{${value}}`
|
||||||
|
}
|
||||||
|
enrichedQuery[key] = processStringSync(value, parameters, {
|
||||||
noHelpers: true,
|
noHelpers: true,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,7 +3,6 @@ const CouchDB = require("../db")
|
||||||
const { init } = require("@budibase/auth")
|
const { init } = require("@budibase/auth")
|
||||||
const redis = require("@budibase/auth/redis")
|
const redis = require("@budibase/auth/redis")
|
||||||
const { SEPARATOR } = require("@budibase/auth/db")
|
const { SEPARATOR } = require("@budibase/auth/db")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
|
||||||
|
|
||||||
const VARIABLE_TTL_SECONDS = 3600
|
const VARIABLE_TTL_SECONDS = 3600
|
||||||
let client
|
let client
|
||||||
|
|
|
@ -983,10 +983,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/auth@^1.0.27-alpha.1":
|
"@budibase/auth@^1.0.27-alpha.2":
|
||||||
version "1.0.33"
|
version "1.0.34"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-1.0.33.tgz#bb0c527ebca852c001c7e163a9cdfce49984adab"
|
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-1.0.34.tgz#f1daad174d494c5baae29ebfb2d0cf7a26d487bb"
|
||||||
integrity sha512-CJcBspSB6B4UwXenGfzCiBh4wWPq9ZrRZm+Dqo774Yw7dfa/hHisfFiRAuz0vHxZl6AStLUsTBNynrzt7bs7Mg==
|
integrity sha512-RN5xZVqk4D4GIFoTrm6kv6vxIcAyDgoopsGMYj8dQRCYWb6pvWzCKyDw4o1THXiK8BBD8ctFUE/FhtLXRj8F4w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@techpass/passport-openidconnect" "^0.3.0"
|
"@techpass/passport-openidconnect" "^0.3.0"
|
||||||
aws-sdk "^2.901.0"
|
aws-sdk "^2.901.0"
|
||||||
|
@ -1056,10 +1056,10 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/bbui@^1.0.33":
|
"@budibase/bbui@^1.0.34":
|
||||||
version "1.0.33"
|
version "1.0.34"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.33.tgz#e815c4883dbc9d28d31900160fc74aafc81d02c1"
|
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.34.tgz#3eba93984cd0075aecbf4ca8451b301e623c7afa"
|
||||||
integrity sha512-2W3Ub8J8brSuhwtXhznszTa54cTyUdDNMFDCnRrHseaDV05XrSykdndXYyWi4XC+FveV82AdvSICxgd6alRGzg==
|
integrity sha512-9mVzIWx4dQ8LIy4ncJaJBVwuTx2LEMqZl1XRr/NAI3rsTv+HIULe+zCUJSCYM2MGoR3qKSU7geIzIuwnRa8k4Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
|
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
|
||||||
"@spectrum-css/actionbutton" "^1.0.1"
|
"@spectrum-css/actionbutton" "^1.0.1"
|
||||||
|
@ -1106,14 +1106,14 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/client@^1.0.27-alpha.1":
|
"@budibase/client@^1.0.27-alpha.2":
|
||||||
version "1.0.33"
|
version "1.0.34"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.33.tgz#715aaaf15f13d9e40960fffea44002e817d8a569"
|
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.34.tgz#2bdac2030eabf780659481fa751cd7fecf1b609f"
|
||||||
integrity sha512-4+xM/ZTI247JbJc1noQS58VFh8AqfuQK1ZUSZIowuol1TtDu5dLGN7BysUFUe0mJZoDB/L+cB1I//43XqaSWDw==
|
integrity sha512-WGEVjwmKYv+B+J6crbia62BNFCzgKHEggsNAlCN3+IB+w9jxS2oiOJYPpYQnsSM2l4j4/Zbu9W30OhenSiB8kQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/bbui" "^1.0.33"
|
"@budibase/bbui" "^1.0.34"
|
||||||
"@budibase/standard-components" "^0.9.139"
|
"@budibase/standard-components" "^0.9.139"
|
||||||
"@budibase/string-templates" "^1.0.33"
|
"@budibase/string-templates" "^1.0.34"
|
||||||
regexparam "^1.3.0"
|
regexparam "^1.3.0"
|
||||||
shortid "^2.2.15"
|
shortid "^2.2.15"
|
||||||
svelte-spa-router "^3.0.5"
|
svelte-spa-router "^3.0.5"
|
||||||
|
@ -1163,10 +1163,10 @@
|
||||||
svelte-apexcharts "^1.0.2"
|
svelte-apexcharts "^1.0.2"
|
||||||
svelte-flatpickr "^3.1.0"
|
svelte-flatpickr "^3.1.0"
|
||||||
|
|
||||||
"@budibase/string-templates@^1.0.27-alpha.1", "@budibase/string-templates@^1.0.33":
|
"@budibase/string-templates@^1.0.27-alpha.2", "@budibase/string-templates@^1.0.34":
|
||||||
version "1.0.33"
|
version "1.0.34"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.33.tgz#7c39f0821d86ca1b26e8d5e7eca4256f24b09de0"
|
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.34.tgz#7e5f80a26db5d04063c0e78cdf37af88cff772c6"
|
||||||
integrity sha512-uvDJvVtAaBcMKnVylWlzGk+CzPXH+TIKojGAfgjCqfcBsc9GlyCCBLtANe8osi9Bdr1SsKOW9Fi5u9FTToNMJw==
|
integrity sha512-HWqXxQBXqVwBgCh3ptczVFxWOLHVUhuTShXCafRDgKAVY3d+LJzKGwiD40/oR/nUqeNtBbHgYWu8Z4gMQQYZhA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/handlebars-helpers" "^0.11.7"
|
"@budibase/handlebars-helpers" "^0.11.7"
|
||||||
dayjs "^1.10.4"
|
dayjs "^1.10.4"
|
||||||
|
@ -3304,9 +3304,9 @@ aws-sdk@^2.767.0:
|
||||||
xml2js "0.4.19"
|
xml2js "0.4.19"
|
||||||
|
|
||||||
aws-sdk@^2.901.0:
|
aws-sdk@^2.901.0:
|
||||||
version "2.1049.0"
|
version "2.1046.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1049.0.tgz#8146dcdf3a1ab603e50ff961169ee8abc537d48e"
|
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1046.0.tgz#9147b0fa1c86acbebd1a061e951ab5012f4499d7"
|
||||||
integrity sha512-+wls9iNlotMeoZepwgR0yPzXsjXzr2ijoi5ERmsPWfMTFMHkm6INndBtSkm6fpu/NZnl+7EaPPES2yhaqnhoJg==
|
integrity sha512-ocwHclMXdIA+NWocUyvp9Ild3/zy2vr5mHp3mTyodf0WU5lzBE8PocCVLSWhMAXLxyia83xv2y5f5AzAcetbqA==
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer "4.9.2"
|
buffer "4.9.2"
|
||||||
events "1.1.1"
|
events "1.1.1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.27-alpha.1",
|
"version": "1.0.27-alpha.2",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^1.0.27-alpha.1",
|
"@budibase/auth": "^1.0.27-alpha.2",
|
||||||
"@budibase/string-templates": "^1.0.27-alpha.1",
|
"@budibase/string-templates": "^1.0.27-alpha.2",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "^6.0.0",
|
"@sentry/node": "^6.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
Loading…
Reference in New Issue