Merge pull request #1269 from Budibase/tests/upping-coverage
Tests/upping coverage
This commit is contained in:
commit
f71b92e54f
|
@ -16,7 +16,7 @@ async function activate() {
|
|||
// this was an issue as NODE_ENV = 'cypress' on the server,
|
||||
// but 'production' on the client
|
||||
const response = await api.get("/api/analytics")
|
||||
analyticsEnabled = (await response.json()) === true
|
||||
analyticsEnabled = (await response.json()).enabled === true
|
||||
}
|
||||
if (!analyticsEnabled) return
|
||||
if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN })
|
||||
|
|
|
@ -1,190 +1,186 @@
|
|||
<script>
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let bindingDrawer
|
||||
export let bindableProperties = []
|
||||
|
||||
let originalValue = value
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let search = ""
|
||||
let validity = true
|
||||
|
||||
$: categories = Object.entries(groupBy("category", bindableProperties))
|
||||
$: value && checkValid()
|
||||
$: dispatch("update", value)
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
|
||||
function checkValid() {
|
||||
validity = isValid(value)
|
||||
}
|
||||
|
||||
function addToText(binding) {
|
||||
const position = getCaretPosition()
|
||||
const toAdd = `{{ ${binding.path} }}`
|
||||
if (position.start) {
|
||||
value =
|
||||
value.substring(0, position.start) +
|
||||
toAdd +
|
||||
value.substring(position.end, value.length)
|
||||
} else {
|
||||
value += toAdd
|
||||
}
|
||||
}
|
||||
export function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
bindingDrawer.close()
|
||||
import groupBy from "lodash/fp/groupBy"
|
||||
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { isValid } from "@budibase/string-templates"
|
||||
import { handlebarsCompletions } from "constants/completions"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let bindingDrawer
|
||||
export let bindableProperties = []
|
||||
|
||||
let originalValue = value
|
||||
let helpers = handlebarsCompletions()
|
||||
let getCaretPosition
|
||||
let search = ""
|
||||
let validity = true
|
||||
|
||||
$: categories = Object.entries(groupBy("category", bindableProperties))
|
||||
$: value && checkValid()
|
||||
$: dispatch("update", value)
|
||||
$: searchRgx = new RegExp(search, "ig")
|
||||
|
||||
function checkValid() {
|
||||
validity = isValid(value)
|
||||
}
|
||||
|
||||
function addToText(binding) {
|
||||
const position = getCaretPosition()
|
||||
const toAdd = `{{ ${binding.path} }}`
|
||||
if (position.start) {
|
||||
value =
|
||||
value.substring(0, position.start) +
|
||||
toAdd +
|
||||
value.substring(position.end, value.length)
|
||||
} else {
|
||||
value += toAdd
|
||||
}
|
||||
}
|
||||
export function cancel() {
|
||||
dispatch("update", originalValue)
|
||||
bindingDrawer.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="list">
|
||||
<Heading small>Available bindings</Heading>
|
||||
<Spacer medium />
|
||||
<Input extraThin placeholder="Search" bind:value={search} />
|
||||
<Spacer medium />
|
||||
{#each categories as [categoryName, bindings]}
|
||||
<Heading extraSmall>{categoryName}</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each bindableProperties.filter(binding =>
|
||||
binding.label.match(searchRgx)
|
||||
) as binding}
|
||||
<div class="binding" on:click={() => addToText(binding)}>
|
||||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{binding.description || ''}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
<Heading extraSmall>Helpers</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
|
||||
<div class="binding" on:click={() => addToText(helper)}>
|
||||
<span class="binding__label">{helper.label}</span>
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{@html helper.description || ''}
|
||||
</div>
|
||||
<pre>{helper.example || ''}</pre>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="text">
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to the textbox." />
|
||||
{#if !validity}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
||||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="list">
|
||||
<Heading small>Available bindings</Heading>
|
||||
<Spacer medium />
|
||||
<Input extraThin placeholder="Search" bind:value={search} />
|
||||
<Spacer medium />
|
||||
{#each categories as [categoryName, bindings]}
|
||||
<Heading extraSmall>{categoryName}</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each bindableProperties.filter(binding =>
|
||||
binding.label.match(searchRgx)
|
||||
) as binding}
|
||||
<div class="binding" on:click={() => addToText(binding)}>
|
||||
<span class="binding__label">{binding.label}</span>
|
||||
<span class="binding__type">{binding.type}</span>
|
||||
<br />
|
||||
<div class="binding__description">{binding.description || ''}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
<Heading extraSmall>Helpers</Heading>
|
||||
<Spacer extraSmall />
|
||||
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
|
||||
<div class="binding" on:click={() => addToText(helper)}>
|
||||
<span class="binding__label">{helper.label}</span>
|
||||
<br />
|
||||
<div class="binding__description">
|
||||
{@html helper.description || ''}
|
||||
</div>
|
||||
<pre>{helper.example || ''}</pre>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="text">
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
thin
|
||||
bind:value
|
||||
placeholder="Add text, or click the objects on the left to add them to the textbox." />
|
||||
{#if !validity}
|
||||
<p class="syntax-error">
|
||||
Current Handlebars syntax is invalid, please check the guide
|
||||
<a href="https://handlebarsjs.com/guide/">here</a>
|
||||
for more details.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.container {
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
}
|
||||
|
||||
.list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-l);
|
||||
overflow: auto;
|
||||
}
|
||||
.list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-l);
|
||||
overflow: auto;
|
||||
}
|
||||
.container {
|
||||
height: 40vh;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
}
|
||||
|
||||
.list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-l);
|
||||
overflow: auto;
|
||||
}
|
||||
.list {
|
||||
border-right: var(--border-light);
|
||||
padding: var(--spacing-l);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: var(--spacing-l);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.text :global(textarea) {
|
||||
min-height: 100px;
|
||||
}
|
||||
.text :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.binding {
|
||||
font-size: 12px;
|
||||
padding: var(--spacing-s);
|
||||
border-radius: var(--border-radius-m);
|
||||
}
|
||||
.binding:hover {
|
||||
background-color: var(--grey-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.binding__label {
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.binding__description {
|
||||
color: var(--grey-8);
|
||||
margin-top: 2px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.binding__type {
|
||||
font-family: monospace;
|
||||
background-color: var(--grey-2);
|
||||
border-radius: var(--border-radius-m);
|
||||
padding: 2px;
|
||||
margin-left: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.editor {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
.editor :global(textarea) {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.syntax-error a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
.list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: var(--spacing-l);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.text :global(textarea) {
|
||||
min-height: 100px;
|
||||
}
|
||||
.text :global(p) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.binding {
|
||||
font-size: 12px;
|
||||
padding: var(--spacing-s);
|
||||
border-radius: var(--border-radius-m);
|
||||
}
|
||||
.binding:hover {
|
||||
background-color: var(--grey-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
.binding__label {
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.binding__description {
|
||||
color: var(--grey-8);
|
||||
margin-top: 2px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.binding__type {
|
||||
font-family: monospace;
|
||||
background-color: var(--grey-2);
|
||||
border-radius: var(--border-radius-m);
|
||||
padding: 2px;
|
||||
margin-left: 2px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.editor {
|
||||
padding-left: var(--spacing-l);
|
||||
}
|
||||
.editor :global(textarea) {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: var(--spacing-l);
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
.syntax-error {
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.syntax-error a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { automationStore } from "builderStore"
|
||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from './AutomationBindingPanel.svelte'
|
||||
import AutomationBindingPanel from "./AutomationBindingPanel.svelte"
|
||||
|
||||
export let block
|
||||
export let webhookModal
|
||||
|
@ -70,7 +70,7 @@
|
|||
type={'email'}
|
||||
extraThin
|
||||
value={block.inputs[key]}
|
||||
on:change={e => block.inputs[key] = e.detail}
|
||||
on:change={e => (block.inputs[key] = e.detail)}
|
||||
{bindings} />
|
||||
{:else if value.customType === 'table'}
|
||||
<TableSelector bind:value={block.inputs[key]} />
|
||||
|
@ -86,7 +86,7 @@
|
|||
type={value.customType}
|
||||
extraThin
|
||||
value={block.inputs[key]}
|
||||
on:change={e => block.inputs[key] = e.detail}
|
||||
on:change={e => (block.inputs[key] = e.detail)}
|
||||
{bindings} />
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { backendUiStore } from "builderStore"
|
||||
import { Select } from "@budibase/bbui"
|
||||
import DrawerBindableInput from "../../common/DrawerBindableInput.svelte"
|
||||
import AutomationBindingPanel from './AutomationBindingPanel.svelte'
|
||||
import AutomationBindingPanel from "./AutomationBindingPanel.svelte"
|
||||
|
||||
export let value
|
||||
export let bindings
|
||||
|
@ -44,7 +44,7 @@
|
|||
panel={AutomationBindingPanel}
|
||||
extraThin
|
||||
value={value[field]}
|
||||
on:change={e => value[field] = e.detail}
|
||||
on:change={e => (value[field] = e.detail)}
|
||||
label={field}
|
||||
type="string"
|
||||
{bindings} />
|
||||
|
|
|
@ -25,10 +25,14 @@
|
|||
]
|
||||
|
||||
const transitions = [
|
||||
'none', 'fade', 'blur', 'fly', 'scale' // slide is hidden because it does not seem to result in any effect
|
||||
"none",
|
||||
"fade",
|
||||
"blur",
|
||||
"fly",
|
||||
"scale", // slide is hidden because it does not seem to result in any effect
|
||||
]
|
||||
|
||||
const capitalize = ([first,...rest]) => first.toUpperCase() + rest.join('');
|
||||
const capitalize = ([first, ...rest]) => first.toUpperCase() + rest.join("")
|
||||
|
||||
$: groups = componentDefinition?.styleable ? Object.keys(allStyles) : []
|
||||
</script>
|
||||
|
@ -72,13 +76,19 @@
|
|||
</div>
|
||||
</div>
|
||||
{#if componentDefinition?.transitionable}
|
||||
<div class="transitions">
|
||||
<Select value={componentInstance._transition} on:change={event => onUpdateTransition(event.target.value)} name="transition" label="Transition" secondary thin>
|
||||
{#each transitions as transition}
|
||||
<option value={transition}>{capitalize(transition)}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<div class="transitions">
|
||||
<Select
|
||||
value={componentInstance._transition}
|
||||
on:change={event => onUpdateTransition(event.target.value)}
|
||||
name="transition"
|
||||
label="Transition"
|
||||
secondary
|
||||
thin>
|
||||
{#each transitions as transition}
|
||||
<option value={transition}>{capitalize(transition)}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import {flip} from "svelte/animate";
|
||||
import {dndzone} from "svelte-dnd-action";
|
||||
import { flip } from "svelte/animate"
|
||||
import { dndzone } from "svelte-dnd-action"
|
||||
import { Button, DropdownMenu, Spacer } from "@budibase/bbui"
|
||||
import actionTypes from "./actions"
|
||||
|
||||
const flipDurationMs = 150;
|
||||
const flipDurationMs = 150
|
||||
|
||||
const EVENT_TYPE_KEY = "##eventHandlerType"
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
// dndzone needs an id on the array items, so this adds some temporary ones.
|
||||
if (actions) {
|
||||
actions = actions.map((action, i) => {
|
||||
return {...action, id: i}
|
||||
return { ...action, id: i }
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
const newAction = {
|
||||
parameters: {},
|
||||
[EVENT_TYPE_KEY]: actionType.name,
|
||||
id: actions ? actions.length + 1 : 0
|
||||
id: actions ? actions.length + 1 : 0,
|
||||
}
|
||||
if (!actions) {
|
||||
actions = []
|
||||
|
@ -56,11 +56,11 @@
|
|||
}
|
||||
|
||||
function handleDndConsider(e) {
|
||||
actions = e.detail.items;
|
||||
}
|
||||
function handleDndFinalize(e) {
|
||||
actions = e.detail.items;
|
||||
}
|
||||
actions = e.detail.items
|
||||
}
|
||||
function handleDndFinalize(e) {
|
||||
actions = e.detail.items
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="actions-container">
|
||||
|
@ -87,9 +87,15 @@
|
|||
</div>
|
||||
|
||||
{#if actions && actions.length > 0}
|
||||
<div class="action-dnd-container" use:dndzone={{items: actions, flipDurationMs, dropTargetStyle: { outline: 'none'}}} on:consider={handleDndConsider} on:finalize={handleDndFinalize}>
|
||||
<div
|
||||
class="action-dnd-container"
|
||||
use:dndzone={{ items: actions, flipDurationMs, dropTargetStyle: { outline: 'none' } }}
|
||||
on:consider={handleDndConsider}
|
||||
on:finalize={handleDndFinalize}>
|
||||
{#each actions as action, index (action.id)}
|
||||
<div class="action-container" animate:flip={{duration: flipDurationMs}}>
|
||||
<div
|
||||
class="action-container"
|
||||
animate:flip={{ duration: flipDurationMs }}>
|
||||
<div
|
||||
class="action-header"
|
||||
class:selected={action === selectedAction}
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
|
||||
{#if componentDefinition?.component?.endsWith('/fieldgroup')}
|
||||
<Button secondary wide on:click={() => confirmResetFieldsDialog?.show()}>
|
||||
Update Form Fields
|
||||
Update Form Fields
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
.topnavitemright:hover i {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
id,
|
||||
children: children.length,
|
||||
styles: { ...styles, id },
|
||||
transition
|
||||
transition,
|
||||
})
|
||||
|
||||
// Gets the component constructor for the specified component
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
const fetch = jest.requireActual("node-fetch")
|
||||
|
||||
module.exports = async (url, opts) => {
|
||||
// mocked data based on url
|
||||
if (url.includes("api/apps")) {
|
||||
return {
|
||||
json: async () => {
|
||||
return {
|
||||
app1: {
|
||||
url: "/app1",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return fetch(url, opts)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
const pg = {}
|
||||
|
||||
// constructor
|
||||
function Client() {}
|
||||
|
||||
Client.prototype.query = async function() {
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
a: "string",
|
||||
b: 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
Client.prototype.connect = async function() {}
|
||||
|
||||
pg.Client = Client
|
||||
|
||||
module.exports = pg
|
|
@ -33,7 +33,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "jest --testPathIgnorePatterns=routes && npm run test:integration",
|
||||
"test:integration": "jest routes --runInBand --coverage",
|
||||
"test:integration": "jest --runInBand --coverage",
|
||||
"test:watch": "jest --watch",
|
||||
"run:docker": "node src/index",
|
||||
"dev:builder": "cross-env PORT=4001 nodemon src/index.js",
|
||||
|
@ -53,7 +53,11 @@
|
|||
"src/**/*.js",
|
||||
"!**/node_modules/**",
|
||||
"!src/db/views/*.js",
|
||||
"!src/api/routes/tests"
|
||||
"!src/api/routes/tests/**/*.js",
|
||||
"!src/api/controllers/deploy/**/*.js",
|
||||
"!src/api/controllers/static/templates/**/*",
|
||||
"!src/api/controllers/static/selfhost/**/*",
|
||||
"!src/*.js"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"lcov",
|
||||
|
@ -122,6 +126,7 @@
|
|||
"zlib": "1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@budibase/standard-components": "^0.8.5",
|
||||
"@jest/test-sequencer": "^24.8.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "10.1.3",
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const env = require("../../environment")
|
||||
|
||||
exports.isEnabled = async function(ctx) {
|
||||
ctx.body = JSON.stringify(env.ENABLE_ANALYTICS === "true")
|
||||
ctx.body = {
|
||||
enabled: env.ENABLE_ANALYTICS === "true",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,13 @@ const { join } = require("../../utilities/centralPath")
|
|||
const readline = require("readline")
|
||||
const { budibaseAppsDir } = require("../../utilities/budibaseDir")
|
||||
const env = require("../../environment")
|
||||
const selfhost = require("../../selfhost")
|
||||
const ENV_FILE_PATH = "/.env"
|
||||
|
||||
exports.fetch = async function(ctx) {
|
||||
ctx.status = 200
|
||||
if (env.SELF_HOSTED) {
|
||||
ctx.body = {
|
||||
selfhost: await selfhost.getSelfHostAPIKey(),
|
||||
}
|
||||
} else {
|
||||
ctx.body = {
|
||||
budibase: env.BUDIBASE_API_KEY,
|
||||
userId: env.USERID_API_KEY,
|
||||
}
|
||||
ctx.body = {
|
||||
budibase: env.BUDIBASE_API_KEY,
|
||||
userId: env.USERID_API_KEY,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,9 +104,10 @@ async function createInstance(template) {
|
|||
await createRoutingView(appId)
|
||||
|
||||
// replicate the template data to the instance DB
|
||||
// this is currently very hard to test, downloading and importing template files
|
||||
/* istanbul ignore next */
|
||||
if (template) {
|
||||
let dbDumpReadStream
|
||||
|
||||
if (template.fileImportPath) {
|
||||
dbDumpReadStream = fs.createReadStream(template.fileImportPath)
|
||||
} else {
|
||||
|
@ -181,8 +182,9 @@ exports.create = async function(ctx) {
|
|||
const instanceDb = new CouchDB(appId)
|
||||
await instanceDb.put(newApplication)
|
||||
|
||||
const newAppFolder = await createEmptyAppPackage(ctx, newApplication)
|
||||
/* istanbul ignore next */
|
||||
if (env.NODE_ENV !== "jest") {
|
||||
const newAppFolder = await createEmptyAppPackage(ctx, newApplication)
|
||||
await downloadExtractComponentLibraries(newAppFolder)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ exports.authenticate = async ctx => {
|
|||
version: app.version,
|
||||
}
|
||||
// if in cloud add the user api key, unless self hosted
|
||||
/* istanbul ignore next */
|
||||
if (env.CLOUD && !env.SELF_HOSTED) {
|
||||
const { apiKey } = await getAPIKey(ctx.user.appId)
|
||||
payload.apiKey = apiKey
|
||||
|
@ -70,6 +71,7 @@ exports.authenticate = async ctx => {
|
|||
|
||||
exports.fetchSelf = async ctx => {
|
||||
const { userId, appId } = ctx.user
|
||||
/* istanbul ignore next */
|
||||
if (!userId || !appId) {
|
||||
ctx.body = {}
|
||||
return
|
||||
|
|
|
@ -98,6 +98,11 @@ exports.create = async function(ctx) {
|
|||
let automation = ctx.request.body
|
||||
automation.appId = ctx.user.appId
|
||||
|
||||
// call through to update if already exists
|
||||
if (automation._id && automation._rev) {
|
||||
return exports.update(ctx)
|
||||
}
|
||||
|
||||
automation._id = generateAutomationID()
|
||||
|
||||
automation.type = "automation"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const CouchDB = require("../../db")
|
||||
const bcrypt = require("../../utilities/bcrypt")
|
||||
const {
|
||||
generateDatasourceID,
|
||||
getDatasourceParams,
|
||||
|
@ -26,35 +25,12 @@ exports.save = async function(ctx) {
|
|||
...ctx.request.body,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await db.post(datasource)
|
||||
datasource._rev = response.rev
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = "Datasource saved successfully."
|
||||
ctx.body = datasource
|
||||
} catch (err) {
|
||||
ctx.throw(err.status, err)
|
||||
}
|
||||
}
|
||||
|
||||
exports.update = async function(ctx) {
|
||||
const db = new CouchDB(ctx.user.appId)
|
||||
const user = ctx.request.body
|
||||
const dbUser = await db.get(ctx.request.body._id)
|
||||
if (user.password) {
|
||||
user.password = await bcrypt.hash(user.password)
|
||||
} else {
|
||||
delete user.password
|
||||
}
|
||||
const newData = { ...dbUser, ...user }
|
||||
|
||||
const response = await db.put(newData)
|
||||
user._rev = response.rev
|
||||
const response = await db.post(datasource)
|
||||
datasource._rev = response.rev
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = `User ${ctx.request.body.email} updated successfully.`
|
||||
ctx.body = response
|
||||
ctx.message = "Datasource saved successfully."
|
||||
ctx.body = datasource
|
||||
}
|
||||
|
||||
exports.destroy = async function(ctx) {
|
||||
|
@ -73,6 +49,5 @@ exports.destroy = async function(ctx) {
|
|||
|
||||
exports.find = async function(ctx) {
|
||||
const database = new CouchDB(ctx.user.appId)
|
||||
const datasource = await database.get(ctx.params.datasourceId)
|
||||
ctx.body = datasource
|
||||
ctx.body = await database.get(ctx.params.datasourceId)
|
||||
}
|
||||
|
|
|
@ -38,6 +38,6 @@ exports.destroy = async function(ctx) {
|
|||
}
|
||||
|
||||
await db.remove(layoutId, layoutRev)
|
||||
ctx.message = "Layout deleted successfully"
|
||||
ctx.body = { message: "Layout deleted successfully" }
|
||||
ctx.status = 200
|
||||
}
|
||||
|
|
|
@ -110,7 +110,6 @@ exports.preview = async function(ctx) {
|
|||
|
||||
if (!Integration) {
|
||||
ctx.throw(400, "Integration type does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
const { fields, parameters, queryVerb } = ctx.request.body
|
||||
|
@ -140,7 +139,6 @@ exports.execute = async function(ctx) {
|
|||
|
||||
if (!Integration) {
|
||||
ctx.throw(400, "Integration type does not exist.")
|
||||
return
|
||||
}
|
||||
|
||||
const enrichedQuery = await enrichQueryFields(
|
||||
|
|
|
@ -224,6 +224,7 @@ exports.fetchView = async function(ctx) {
|
|||
try {
|
||||
table = await db.get(viewInfo.meta.tableId)
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
table = {
|
||||
schema: {},
|
||||
}
|
||||
|
@ -255,16 +256,24 @@ exports.fetchView = async function(ctx) {
|
|||
|
||||
exports.search = async function(ctx) {
|
||||
const appId = ctx.user.appId
|
||||
|
||||
const db = new CouchDB(appId)
|
||||
|
||||
const {
|
||||
query,
|
||||
pagination: { pageSize = 10, page },
|
||||
} = ctx.request.body
|
||||
|
||||
query.tableId = ctx.params.tableId
|
||||
// make all strings a starts with operation rather than pure equality
|
||||
for (const [key, queryVal] of Object.entries(query)) {
|
||||
if (typeof queryVal === "string") {
|
||||
query[key] = {
|
||||
$gt: queryVal,
|
||||
$lt: `${queryVal}\uffff`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pure equality for table
|
||||
query.tableId = ctx.params.tableId
|
||||
const response = await db.find({
|
||||
selector: query,
|
||||
limit: pageSize,
|
||||
|
@ -324,7 +333,6 @@ exports.destroy = async function(ctx) {
|
|||
const row = await db.get(ctx.params.rowId)
|
||||
if (row.tableId !== ctx.params.tableId) {
|
||||
ctx.throw(400, "Supplied tableId doesn't match the row's tableId")
|
||||
return
|
||||
}
|
||||
await linkRows.updateLinks({
|
||||
appId,
|
||||
|
@ -376,15 +384,6 @@ exports.fetchEnrichedRow = async function(ctx) {
|
|||
const db = new CouchDB(appId)
|
||||
const tableId = ctx.params.tableId
|
||||
const rowId = ctx.params.rowId
|
||||
if (appId == null || tableId == null || rowId == null) {
|
||||
ctx.status = 400
|
||||
ctx.body = {
|
||||
status: 400,
|
||||
error:
|
||||
"Cannot handle request, URI params have not been successfully prepared.",
|
||||
}
|
||||
return
|
||||
}
|
||||
// need table to work out where links go in row
|
||||
let [table, row] = await Promise.all([
|
||||
db.get(tableId),
|
||||
|
|
|
@ -41,6 +41,8 @@ exports.save = async ctx => {
|
|||
exports.destroy = async ctx => {
|
||||
const db = new CouchDB(ctx.user.appId)
|
||||
await db.remove(ctx.params.screenId, ctx.params.screenRev)
|
||||
ctx.message = "Screen deleted successfully"
|
||||
ctx.body = {
|
||||
message: "Screen deleted successfully",
|
||||
}
|
||||
ctx.status = 200
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ exports.fetch = async function(ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
// can't currently test this, have to ignore from coverage
|
||||
/* istanbul ignore next */
|
||||
exports.downloadTemplate = async function(ctx) {
|
||||
const { type, name } = ctx.params
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ exports.create = async function(ctx) {
|
|||
const response = await db.post(user)
|
||||
ctx.status = 200
|
||||
ctx.message = "User created successfully."
|
||||
ctx.userId = response._id
|
||||
ctx.userId = response.id
|
||||
ctx.body = {
|
||||
_rev: response.rev,
|
||||
email,
|
||||
|
@ -70,6 +70,9 @@ exports.update = async function(ctx) {
|
|||
const db = new CouchDB(ctx.user.appId)
|
||||
const user = ctx.request.body
|
||||
let dbUser
|
||||
if (user.email && !user._id) {
|
||||
user._id = generateUserID(user.email)
|
||||
}
|
||||
// get user incase password removed
|
||||
if (user._id) {
|
||||
dbUser = await db.get(user._id)
|
||||
|
@ -87,14 +90,15 @@ exports.update = async function(ctx) {
|
|||
user._rev = response.rev
|
||||
|
||||
ctx.status = 200
|
||||
ctx.message = `User ${ctx.request.body.email} updated successfully.`
|
||||
ctx.body = response
|
||||
}
|
||||
|
||||
exports.destroy = async function(ctx) {
|
||||
const database = new CouchDB(ctx.user.appId)
|
||||
await database.destroy(generateUserID(ctx.params.email))
|
||||
ctx.message = `User ${ctx.params.email} deleted.`
|
||||
ctx.body = {
|
||||
message: `User ${ctx.params.email} deleted.`,
|
||||
}
|
||||
ctx.status = 200
|
||||
}
|
||||
|
||||
|
|
|
@ -43,12 +43,10 @@ exports.save = async ctx => {
|
|||
webhook._id = generateWebhookID()
|
||||
}
|
||||
const response = await db.put(webhook)
|
||||
webhook._rev = response.rev
|
||||
ctx.body = {
|
||||
message: "Webhook created successfully",
|
||||
webhook: {
|
||||
...webhook,
|
||||
...response,
|
||||
},
|
||||
webhook,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,5 +93,7 @@ exports.trigger = async ctx => {
|
|||
})
|
||||
}
|
||||
ctx.status = 200
|
||||
ctx.body = "Webhook trigger fired successfully"
|
||||
ctx.body = {
|
||||
message: "Webhook trigger fired successfully",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,13 +41,15 @@ router.use(async (ctx, next) => {
|
|||
try {
|
||||
await next()
|
||||
} catch (err) {
|
||||
ctx.log.error(err)
|
||||
ctx.status = err.status || err.statusCode || 500
|
||||
ctx.body = {
|
||||
message: err.message,
|
||||
status: ctx.status,
|
||||
}
|
||||
console.trace(err)
|
||||
if (env.NODE_ENV !== "jest") {
|
||||
ctx.log.error(err)
|
||||
console.trace(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ const router = Router()
|
|||
router
|
||||
.get("/api/datasources", authorized(BUILDER), datasourceController.fetch)
|
||||
.get(
|
||||
"/api/datasources/:id",
|
||||
"/api/datasources/:datasourceId",
|
||||
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
|
||||
datasourceController.find
|
||||
)
|
||||
|
|
|
@ -11,11 +11,7 @@ router
|
|||
.get("/api/hosting/urls", authorized(BUILDER), controller.fetchUrls)
|
||||
.get("/api/hosting", authorized(BUILDER), controller.fetch)
|
||||
.post("/api/hosting", authorized(BUILDER), controller.save)
|
||||
.get(
|
||||
"/api/hosting/apps",
|
||||
authorized(BUILDER),
|
||||
selfhost,
|
||||
controller.getDeployedApps
|
||||
)
|
||||
// this isn't risky, doesn't return anything about apps other than names and URLs
|
||||
.get("/api/hosting/apps", selfhost, controller.getDeployedApps)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -8,6 +8,7 @@ const usage = require("../../middleware/usageQuota")
|
|||
|
||||
const router = Router()
|
||||
|
||||
/* istanbul ignore next */
|
||||
router.param("file", async (file, ctx, next) => {
|
||||
ctx.file = file && file.includes(".") ? file : "index.html"
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
const setup = require("./utilities")
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
describe("/api/keys", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("should allow fetching", async () => {
|
||||
const res = await request
|
||||
.get(`/api/keys`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toBeDefined()
|
||||
})
|
||||
|
||||
it("should check authorization for builder", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/keys`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should allow updating a value", async () => {
|
||||
fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "TEST_API_KEY=thing")
|
||||
const res = await request
|
||||
.put(`/api/keys/TEST`)
|
||||
.send({
|
||||
value: "test"
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body["TEST"]).toEqual("test")
|
||||
expect(process.env.TEST_API_KEY).toEqual("test")
|
||||
})
|
||||
|
||||
it("should check authorization for builder", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "PUT",
|
||||
url: `/api/keys/TEST`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -58,4 +58,43 @@ describe("/applications", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("fetchAppDefinition", () => {
|
||||
it("should be able to get an apps definition", async () => {
|
||||
const res = await request
|
||||
.get(`/api/applications/${config.getAppId()}/definition`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
// should have empty packages
|
||||
expect(res.body.screens.length).toEqual(2)
|
||||
expect(res.body.layouts.length).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetchAppPackage", () => {
|
||||
it("should be able to fetch the app package", async () => {
|
||||
const res = await request
|
||||
.get(`/api/applications/${config.getAppId()}/appPackage`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.application).toBeDefined()
|
||||
expect(res.body.screens.length).toEqual(2)
|
||||
expect(res.body.layouts.length).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should be able to fetch the app package", async () => {
|
||||
const res = await request
|
||||
.put(`/api/applications/${config.getAppId()}`)
|
||||
.send({
|
||||
name: "TEST_APP"
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.rev).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("/authenticate", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("authenticate", () => {
|
||||
it("should be able to create a layout", async () => {
|
||||
await config.createUser("test@test.com", "p4ssw0rd")
|
||||
const res = await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
email: "test@test.com",
|
||||
password: "p4ssw0rd",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.token).toBeDefined()
|
||||
expect(res.body.email).toEqual("test@test.com")
|
||||
expect(res.body.password).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should error if no app specified", async () => {
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it("should error if no email specified", async () => {
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
password: "test",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it("should error if no password specified", async () => {
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
email: "test",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it("should error if invalid user specified", async () => {
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
email: "test",
|
||||
password: "test",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect(401)
|
||||
})
|
||||
|
||||
it("should throw same error if wrong password specified", async () => {
|
||||
await config.createUser("test@test.com", "password")
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
email: "test@test.com",
|
||||
password: "test",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect(401)
|
||||
})
|
||||
|
||||
it("should throw an error for inactive users", async () => {
|
||||
await config.createUser("test@test.com", "password")
|
||||
await config.makeUserInactive("test@test.com")
|
||||
await request
|
||||
.post(`/api/authenticate`)
|
||||
.send({
|
||||
email: "test@test.com",
|
||||
password: "password",
|
||||
})
|
||||
.set(config.publicHeaders())
|
||||
.expect(401)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch self", () => {
|
||||
it("should be able to delete the layout", async () => {
|
||||
await config.createUser("test@test.com", "p4ssw0rd")
|
||||
const headers = await config.login("test@test.com", "p4ssw0rd")
|
||||
const res = await request
|
||||
.get(`/api/self`)
|
||||
.set(headers)
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.email).toEqual("test@test.com")
|
||||
})
|
||||
})
|
||||
})
|
|
@ -73,7 +73,7 @@ describe("/automations", () => {
|
|||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(Object.keys(res.body.action).length).toEqual(Object.keys(ACTION_DEFINITIONS).length)
|
||||
expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(Object.keys(ACTION_DEFINITIONS).length)
|
||||
expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length)
|
||||
expect(Object.keys(res.body.logic).length).toEqual(Object.keys(LOGIC_DEFINITIONS).length)
|
||||
})
|
||||
|
@ -109,6 +109,35 @@ describe("/automations", () => {
|
|||
automation = res.body.automation
|
||||
})
|
||||
|
||||
it("should be able to create an automation with a webhook trigger", async () => {
|
||||
const autoConfig = basicAutomation()
|
||||
autoConfig.definition.trigger = TRIGGER_DEFINITIONS["WEBHOOK"]
|
||||
autoConfig.definition.trigger.id = "webhook_trigger_id"
|
||||
const res = await request
|
||||
.post(`/api/automations`)
|
||||
.set(config.defaultHeaders())
|
||||
.send(autoConfig)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
const originalAuto = res.body.automation
|
||||
expect(originalAuto._id).toBeDefined()
|
||||
expect(originalAuto._rev).toBeDefined()
|
||||
// try removing the webhook trigger
|
||||
const newConfig = originalAuto
|
||||
newConfig.definition.trigger = TRIGGER_DEFINITIONS["ROW_SAVED"]
|
||||
newConfig.definition.trigger.id = "row_saved_id"
|
||||
const newRes = await request
|
||||
.post(`/api/automations`)
|
||||
.set(config.defaultHeaders())
|
||||
.send(newConfig)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
const newAuto = newRes.body.automation
|
||||
expect(newAuto._id).toEqual(originalAuto._id)
|
||||
expect(newAuto._rev).toBeDefined()
|
||||
expect(newAuto._rev).not.toEqual(originalAuto._rev)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
|
@ -119,6 +148,19 @@ describe("/automations", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("find", () => {
|
||||
it("should be able to find the automation", async () => {
|
||||
const automation = await config.createAutomation()
|
||||
const res = await request
|
||||
.get(`/api/automations/${automation._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toEqual(automation._id)
|
||||
expect(res.body._rev).toEqual(automation._rev)
|
||||
})
|
||||
})
|
||||
|
||||
describe("trigger", () => {
|
||||
it("trigger the automation successfully", async () => {
|
||||
let table = await config.createTable()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("/backups", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("exportAppDump", () => {
|
||||
it("should be able to export app", async () => {
|
||||
const res = await request
|
||||
.get(`/api/backups/export?appId=${config.getAppId()}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(200)
|
||||
expect(res.text).toBeDefined()
|
||||
expect(res.text.includes(`"db_name":"${config.getAppId()}"`)).toEqual(true)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/backups/export?appId=${config.getAppId()}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,16 @@
|
|||
const setup = require("./utilities")
|
||||
|
||||
describe("test things in the Cloud/Self hosted", () => {
|
||||
describe("test self hosted static page", () => {
|
||||
it("should be able to load the static page", async () => {
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
await config.init()
|
||||
const res = await request.get(`/`).expect(200)
|
||||
expect(res.text.includes("<title>Budibase self hosting️</title>")).toEqual(true)
|
||||
setup.afterAll()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,49 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
const fs = require("fs")
|
||||
const { resolve, join } = require("path")
|
||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||
|
||||
describe("/component", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
function mock() {
|
||||
const manifestFile = "manifest.json"
|
||||
const appId = config.getAppId()
|
||||
const libraries = ["@budibase/standard-components"]
|
||||
for (let library of libraries) {
|
||||
let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package")
|
||||
fs.mkdirSync(appDirectory, { recursive: true })
|
||||
const file = require.resolve(library).split("dist/index.js")[0] + manifestFile
|
||||
fs.copyFileSync(file, join(appDirectory, manifestFile))
|
||||
}
|
||||
}
|
||||
|
||||
describe("fetch definitions", () => {
|
||||
it("should be able to fetch definitions", async () => {
|
||||
// have to "mock" the files required
|
||||
mock()
|
||||
const res = await request
|
||||
.get(`/${config.getAppId()}/components/definitions`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body["@budibase/standard-components/container"]).toBeDefined()
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/${config.getAppId()}/components/definitions`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,15 +1,17 @@
|
|||
let { basicDatasource } = require("./utilities/structures")
|
||||
let { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
let {basicDatasource} = require("./utilities/structures")
|
||||
let {checkBuilderEndpoint} = require("./utilities/TestFunctions")
|
||||
let setup = require("./utilities")
|
||||
|
||||
describe("/datasources", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let datasource
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
datasource = await config.createDatasource()
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
|
@ -21,22 +23,12 @@ describe("/datasources", () => {
|
|||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.res.statusMessage).toEqual("Datasource saved successfully.");
|
||||
expect(res.body.name).toEqual("Test");
|
||||
})
|
||||
});
|
||||
expect(res.res.statusMessage).toEqual("Datasource saved successfully.")
|
||||
expect(res.body.name).toEqual("Test")
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await config.createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("returns all the datasources from the server", async () => {
|
||||
const res = await request
|
||||
.get(`/api/datasources`)
|
||||
|
@ -44,36 +36,37 @@ describe("/datasources", () => {
|
|||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
const datasources = res.body
|
||||
expect(datasources).toEqual([
|
||||
{
|
||||
"_id": datasources[0]._id,
|
||||
"_rev": datasources[0]._rev,
|
||||
...basicDatasource()
|
||||
}
|
||||
]);
|
||||
const datasources = res.body
|
||||
expect(datasources).toEqual([
|
||||
{
|
||||
"_id": datasources[0]._id,
|
||||
"_rev": datasources[0]._rev,
|
||||
...basicDatasource()
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/datasources`,
|
||||
})
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/datasources`,
|
||||
})
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe("find", () => {
|
||||
it("should be able to find a datasource", async () => {
|
||||
const res = await request
|
||||
.get(`/api/datasources/${datasource._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(200)
|
||||
expect(res.body._rev).toBeDefined()
|
||||
expect(res.body._id).toEqual(datasource._id)
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await config.createDatasource()
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
});
|
||||
|
||||
it("deletes queries for the datasource after deletion and returns a success message", async () => {
|
||||
await config.createQuery()
|
||||
|
||||
|
@ -87,8 +80,8 @@ describe("/datasources", () => {
|
|||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(res.body).toEqual([])
|
||||
|
||||
expect(res.body).toEqual([])
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
|
@ -99,5 +92,5 @@ describe("/datasources", () => {
|
|||
})
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
// mock out node fetch for this
|
||||
jest.mock("node-fetch")
|
||||
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("/hosting", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let app
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await config.init()
|
||||
})
|
||||
|
||||
describe("fetchInfo", () => {
|
||||
it("should be able to fetch hosting information", async () => {
|
||||
const res = await request
|
||||
.get(`/api/hosting/info`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toEqual({ types: ["cloud", "self"]})
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/hosting/info`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetchUrls", () => {
|
||||
it("should be able to fetch current app URLs", async () => {
|
||||
const res = await request
|
||||
.get(`/api/hosting/urls`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.app).toEqual(`https://${config.getAppId()}.app.budi.live`)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/hosting/urls`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("should be able to fetch the current hosting information", async () => {
|
||||
const res = await request
|
||||
.get(`/api/hosting`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toBeDefined()
|
||||
expect(res.body.hostingUrl).toBeDefined()
|
||||
expect(res.body.type).toEqual("cloud")
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/hosting`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("save", () => {
|
||||
it("should be able to update the hosting information", async () => {
|
||||
const res = await request
|
||||
.post(`/api/hosting`)
|
||||
.send({
|
||||
type: "self",
|
||||
selfHostKey: "budibase",
|
||||
hostingUrl: "localhost:10000",
|
||||
useHttps: false,
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.ok).toEqual(true)
|
||||
// make sure URL updated
|
||||
const urlRes = await request
|
||||
.get(`/api/hosting/urls`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(urlRes.body.app).toEqual(`http://localhost:10000/app`)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "POST",
|
||||
url: `/api/hosting`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("getDeployedApps", () => {
|
||||
it("should get apps when in builder", async () => {
|
||||
const res = await request
|
||||
.get(`/api/hosting/apps`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.app1).toEqual({url: "/app1"})
|
||||
})
|
||||
|
||||
it("should get apps when in cloud", async () => {
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
const res = await request
|
||||
.get(`/api/hosting/apps`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.app1).toEqual({url: "/app1"})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("/integrations", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("should be able to get all integration definitions", async () => {
|
||||
const res = await request
|
||||
.get(`/api/integrations`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.POSTGRES).toBeDefined()
|
||||
expect(res.body.POSTGRES.friendlyName).toEqual("PostgreSQL")
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/integrations`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("find", () => {
|
||||
it("should be able to get postgres definition", async () => {
|
||||
const res = await request
|
||||
.get(`/api/integrations/POSTGRES`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.friendlyName).toEqual("PostgreSQL")
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/integrations/POSTGRES`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,55 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
const { basicLayout } = require("./utilities/structures")
|
||||
|
||||
describe("/layouts", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let layout
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
layout = await config.createLayout()
|
||||
})
|
||||
|
||||
describe("save", () => {
|
||||
it("should be able to create a layout", async () => {
|
||||
const res = await request
|
||||
.post(`/api/layouts`)
|
||||
.send(basicLayout())
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body._rev).toBeDefined()
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "POST",
|
||||
url: `/api/layouts`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("should be able to delete the layout", async () => {
|
||||
const res = await request
|
||||
.delete(`/api/layouts/${layout._id}/${layout._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.message).toBeDefined()
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "DELETE",
|
||||
url: `/api/layouts/${layout._id}/${layout._rev}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,38 @@
|
|||
const setup = require("./utilities")
|
||||
|
||||
describe("/analytics", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("isEnabled", () => {
|
||||
it("check if analytics enabled", async () => {
|
||||
const res = await request
|
||||
.get(`/api/analytics`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(typeof res.body.enabled).toEqual("boolean")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("/health", () => {
|
||||
it("should confirm healthy", async () => {
|
||||
let config = setup.getConfig()
|
||||
await config.getRequest().get("/health").expect(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe("/version", () => {
|
||||
it("should confirm version", async () => {
|
||||
const config = setup.getConfig()
|
||||
const res = await config.getRequest().get("/version").expect(200)
|
||||
expect(res.text.split(".").length).toEqual(3)
|
||||
})
|
||||
})
|
|
@ -107,4 +107,19 @@ describe("/permission", () => {
|
|||
expect(res.status).toEqual(403)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch builtins", () => {
|
||||
it("should be able to fetch builtin definitions", async () => {
|
||||
const res = await request
|
||||
.get(`/api/permission/builtin`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(Array.isArray(res.body)).toEqual(true)
|
||||
const publicPerm = res.body.find(perm => perm._id === "public")
|
||||
expect(publicPerm).toBeDefined()
|
||||
expect(publicPerm.permissions).toBeDefined()
|
||||
expect(publicPerm.name).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,17 +1,32 @@
|
|||
// mock out postgres for this
|
||||
jest.mock("pg")
|
||||
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { basicQuery } = require("./utilities/structures")
|
||||
const { basicQuery, basicDatasource } = require("./utilities/structures")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("/queries", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let datasource, query
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
datasource = await config.createDatasource()
|
||||
query = await config.createQuery()
|
||||
})
|
||||
|
||||
async function createInvalidIntegration() {
|
||||
const datasource = await config.createDatasource({
|
||||
...basicDatasource(),
|
||||
source: "INVALID_INTEGRATION",
|
||||
})
|
||||
const query = await config.createQuery()
|
||||
return { datasource, query }
|
||||
}
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a new query", async () => {
|
||||
const { _id } = await config.createDatasource()
|
||||
|
@ -35,18 +50,7 @@ describe("/queries", () => {
|
|||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await config.createDatasource()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
})
|
||||
|
||||
it("returns all the queries from the server", async () => {
|
||||
const query = await config.createQuery()
|
||||
const res = await request
|
||||
.get(`/api/queries`)
|
||||
.set(config.defaultHeaders())
|
||||
|
@ -73,20 +77,34 @@ describe("/queries", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
let datasource
|
||||
|
||||
beforeEach(async () => {
|
||||
datasource = await config.createDatasource()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete datasource._rev
|
||||
})
|
||||
|
||||
it("deletes a query and returns a success message", async () => {
|
||||
describe("find", () => {
|
||||
it("should find a query in builder", async () => {
|
||||
const query = await config.createQuery()
|
||||
const res = await request
|
||||
.get(`/api/queries/${query._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body._id).toEqual(query._id)
|
||||
})
|
||||
|
||||
it("should find a query in cloud", async () => {
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
const query = await config.createQuery()
|
||||
const res = await request
|
||||
.get(`/api/queries/${query._id}`)
|
||||
.set(await config.roleHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.fields).toBeUndefined()
|
||||
expect(res.body.parameters).toBeUndefined()
|
||||
expect(res.body.schema).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("deletes a query and returns a success message", async () => {
|
||||
await request
|
||||
.delete(`/api/queries/${query._id}/${query._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
|
@ -105,8 +123,74 @@ describe("/queries", () => {
|
|||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "DELETE",
|
||||
url: `/api/datasources/${datasource._id}/${datasource._rev}`,
|
||||
url: `/api/queries/${config._id}/${config._rev}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("preview", () => {
|
||||
it("should be able to preview the query", async () => {
|
||||
const res = await request
|
||||
.post(`/api/queries/preview`)
|
||||
.send({
|
||||
datasourceId: datasource._id,
|
||||
parameters: {},
|
||||
fields: {},
|
||||
queryVerb: "read",
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
// these responses come from the mock
|
||||
expect(res.body.schemaFields).toEqual(["a", "b"])
|
||||
expect(res.body.rows.length).toEqual(1)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "POST",
|
||||
url: `/api/queries/preview`,
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail with invalid integration type", async () => {
|
||||
const { datasource } = await createInvalidIntegration()
|
||||
await request
|
||||
.post(`/api/queries/preview`)
|
||||
.send({
|
||||
datasourceId: datasource._id,
|
||||
parameters: {},
|
||||
fields: {},
|
||||
queryVerb: "read",
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe("execute", () => {
|
||||
it("should be able to execute the query", async () => {
|
||||
const res = await request
|
||||
.post(`/api/queries/${query._id}`)
|
||||
.send({
|
||||
parameters: {},
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(1)
|
||||
})
|
||||
|
||||
it("should fail with invalid integration type", async () => {
|
||||
const { query } = await createInvalidIntegration()
|
||||
await request
|
||||
.post(`/api/queries/${query._id}`)
|
||||
.send({
|
||||
parameters: {},
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -34,14 +34,7 @@ describe("/roles", () => {
|
|||
|
||||
describe("fetch", () => {
|
||||
it("should list custom roles, plus 2 default roles", async () => {
|
||||
const createRes = await request
|
||||
.post(`/api/roles`)
|
||||
.send(basicRole())
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
||||
const customRole = createRes.body
|
||||
const customRole = await config.createRole()
|
||||
|
||||
const res = await request
|
||||
.get(`/api/roles`)
|
||||
|
@ -68,24 +61,31 @@ describe("/roles", () => {
|
|||
BUILTIN_PERMISSION_IDS.READ_ONLY
|
||||
)
|
||||
})
|
||||
|
||||
it("should be able to get the role with a permission added", async () => {
|
||||
const table = await config.createTable()
|
||||
await config.addPermission(BUILTIN_ROLE_IDS.POWER, table._id)
|
||||
const res = await request
|
||||
.get(`/api/roles`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toBeGreaterThan(0)
|
||||
const power = res.body.find(role => role._id === BUILTIN_ROLE_IDS.POWER)
|
||||
expect(power.permissions[table._id]).toEqual("read")
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("should delete custom roles", async () => {
|
||||
const createRes = await request
|
||||
.post(`/api/roles`)
|
||||
.send({ name: "user", permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY })
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
||||
const customRole = createRes.body
|
||||
|
||||
const customRole = await config.createRole({
|
||||
name: "user",
|
||||
permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY
|
||||
})
|
||||
await request
|
||||
.delete(`/api/roles/${customRole._id}/${customRole._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(200)
|
||||
|
||||
await request
|
||||
.get(`/api/roles/${customRole._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
const setup = require("./utilities")
|
||||
const { basicScreen } = require("./utilities/structures")
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles")
|
||||
|
||||
const route = "/test"
|
||||
|
||||
describe("/routing", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let screen, screen2
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
screen = basicScreen()
|
||||
screen.routing.route = route
|
||||
screen = await config.createScreen(screen)
|
||||
screen2 = basicScreen()
|
||||
screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER
|
||||
screen2.routing.route = route
|
||||
screen2 = await config.createScreen(screen2)
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("returns the correct routing for basic user", async () => {
|
||||
const res = await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.BASIC))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.routes).toBeDefined()
|
||||
expect(res.body.routes[route]).toEqual({
|
||||
subpaths: {
|
||||
[route]: {
|
||||
screenId: screen._id,
|
||||
roleId: screen.routing.roleId
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("returns the correct routing for power user", async () => {
|
||||
const res = await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.POWER))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.routes).toBeDefined()
|
||||
expect(res.body.routes[route]).toEqual({
|
||||
subpaths: {
|
||||
[route]: {
|
||||
screenId: screen2._id,
|
||||
roleId: screen2.routing.roleId
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch all", () => {
|
||||
it("should fetch all routes for builder", async () => {
|
||||
const res = await request
|
||||
.get(`/api/routing`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.routes).toBeDefined()
|
||||
expect(res.body.routes[route].subpaths[route]).toBeDefined()
|
||||
const subpath = res.body.routes[route].subpaths[route]
|
||||
expect(subpath.screens[screen2.routing.roleId]).toEqual(screen2._id)
|
||||
expect(subpath.screens[screen.routing.roleId]).toEqual(screen._id)
|
||||
})
|
||||
|
||||
it("make sure it is a builder only endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/routing`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -17,15 +17,15 @@ describe("/rows", () => {
|
|||
row = basicRow(table._id)
|
||||
})
|
||||
|
||||
const loadRow = async id =>
|
||||
const loadRow = async (id, status = 200) =>
|
||||
await request
|
||||
.get(`/api/${table._id}/rows/${id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect(status)
|
||||
|
||||
|
||||
describe("save, load, update, delete", () => {
|
||||
describe("save, load, update", () => {
|
||||
it("returns a success message when the row is created", async () => {
|
||||
const res = await request
|
||||
.post(`/api/${row.tableId}/rows`)
|
||||
|
@ -217,38 +217,152 @@ describe("/rows", () => {
|
|||
|
||||
expect(savedRow.body.description).toEqual(existing.description)
|
||||
expect(savedRow.body.name).toEqual("Updated Name")
|
||||
|
||||
})
|
||||
|
||||
it("should throw an error when given improper types", async () => {
|
||||
const existing = await config.createRow()
|
||||
await request
|
||||
.patch(`/api/${table._id}/rows/${existing._id}`)
|
||||
.send({
|
||||
_id: existing._id,
|
||||
_rev: existing._rev,
|
||||
tableId: table._id,
|
||||
name: 1,
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("should be able to delete a row", async () => {
|
||||
const createdRow = await config.createRow(row)
|
||||
const res = await request
|
||||
.delete(`/api/${table._id}/rows/${createdRow._id}/${createdRow._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.ok).toEqual(true)
|
||||
})
|
||||
|
||||
it("shouldn't allow deleting a row in a table which is different to the one the row was created on", async () => {
|
||||
const createdRow = await config.createRow(row)
|
||||
await request
|
||||
.delete(`/api/wrong_table/rows/${createdRow._id}/${createdRow._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validate", () => {
|
||||
it("should return no errors on valid row", async () => {
|
||||
const result = await request
|
||||
const res = await request
|
||||
.post(`/api/${table._id}/rows/validate`)
|
||||
.send({ name: "ivan" })
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(result.body.valid).toBe(true)
|
||||
expect(Object.keys(result.body.errors)).toEqual([])
|
||||
expect(res.body.valid).toBe(true)
|
||||
expect(Object.keys(res.body.errors)).toEqual([])
|
||||
})
|
||||
|
||||
it("should errors on invalid row", async () => {
|
||||
const result = await request
|
||||
const res = await request
|
||||
.post(`/api/${table._id}/rows/validate`)
|
||||
.send({ name: 1 })
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
|
||||
expect(result.body.valid).toBe(false)
|
||||
expect(Object.keys(result.body.errors)).toEqual(["name"])
|
||||
expect(res.body.valid).toBe(false)
|
||||
expect(Object.keys(res.body.errors)).toEqual(["name"])
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe("enrich row unit test", () => {
|
||||
describe("bulkDelete", () => {
|
||||
it("should be able to delete a bulk set of rows", async () => {
|
||||
const row1 = await config.createRow()
|
||||
const row2 = await config.createRow()
|
||||
const res = await request
|
||||
.post(`/api/${table._id}/rows`)
|
||||
.send({
|
||||
type: "delete",
|
||||
rows: [
|
||||
row1,
|
||||
row2,
|
||||
]
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(2)
|
||||
await loadRow(row1._id, 404)
|
||||
})
|
||||
})
|
||||
|
||||
describe("search", () => {
|
||||
it("should run a search on the table", async () => {
|
||||
const row = await config.createRow()
|
||||
// add another row that shouldn't be found
|
||||
await config.createRow({
|
||||
...basicRow(),
|
||||
name: "Other Contact",
|
||||
})
|
||||
const res = await request
|
||||
.post(`/api/${table._id}/rows/search`)
|
||||
.send({
|
||||
query: {
|
||||
name: "Test",
|
||||
},
|
||||
pagination: { pageSize: 25, page: 0 }
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(1)
|
||||
expect(res.body[0]._id).toEqual(row._id)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetchView", () => {
|
||||
it("should be able to fetch tables contents via 'view'", async () => {
|
||||
const row = await config.createRow()
|
||||
const res = await request
|
||||
.get(`/api/views/all_${table._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(1)
|
||||
expect(res.body[0]._id).toEqual(row._id)
|
||||
})
|
||||
|
||||
it("should throw an error if view doesn't exist", async () => {
|
||||
await request
|
||||
.get(`/api/views/derp`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(400)
|
||||
})
|
||||
|
||||
it("should be able to run on a view", async () => {
|
||||
const view = await config.createView()
|
||||
const row = await config.createRow()
|
||||
const res = await request
|
||||
.get(`/api/views/${view._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(1)
|
||||
expect(res.body[0]._id).toEqual(row._id)
|
||||
})
|
||||
})
|
||||
|
||||
describe("user testing", () => {
|
||||
|
||||
})
|
||||
|
||||
describe("fetchEnrichedRows", () => {
|
||||
it("should allow enriching some linked rows", async () => {
|
||||
const table = await config.createLinkedTable()
|
||||
const firstRow = await config.createRow({
|
||||
|
@ -262,30 +376,45 @@ describe("/rows", () => {
|
|||
link: [{_id: firstRow._id}],
|
||||
tableId: table._id,
|
||||
})
|
||||
const enriched = await outputProcessing(config.getAppId(), table, [secondRow])
|
||||
expect(enriched[0].link.length).toBe(1)
|
||||
expect(enriched[0].link[0]._id).toBe(firstRow._id)
|
||||
expect(enriched[0].link[0].primaryDisplay).toBe("Test Contact")
|
||||
|
||||
// test basic enrichment
|
||||
const resBasic = await request
|
||||
.get(`/api/${table._id}/rows/${secondRow._id}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(resBasic.body.link[0]._id).toBe(firstRow._id)
|
||||
expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact")
|
||||
|
||||
// test full enrichment
|
||||
const resEnriched = await request
|
||||
.get(`/api/${table._id}/${secondRow._id}/enrich`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
expect(resEnriched.body.link.length).toBe(1)
|
||||
expect(resEnriched.body.link[0]._id).toBe(firstRow._id)
|
||||
expect(resEnriched.body.link[0].name).toBe("Test Contact")
|
||||
expect(resEnriched.body.link[0].description).toBe("original description")
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow enriching attachment rows", async () => {
|
||||
const table = await config.createAttachmentTable()
|
||||
const row = await config.createRow({
|
||||
name: "test",
|
||||
description: "test",
|
||||
attachment: [{
|
||||
url: "/test/thing",
|
||||
}],
|
||||
tableId: table._id,
|
||||
describe("attachments", () => {
|
||||
it("should allow enriching attachment rows", async () => {
|
||||
const table = await config.createAttachmentTable()
|
||||
const row = await config.createRow({
|
||||
name: "test",
|
||||
description: "test",
|
||||
attachment: [{
|
||||
url: "/test/thing",
|
||||
}],
|
||||
tableId: table._id,
|
||||
})
|
||||
// the environment needs configured for this
|
||||
await setup.switchToCloudForFunction(async () => {
|
||||
const enriched = await outputProcessing(config.getAppId(), table, [row])
|
||||
expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`)
|
||||
})
|
||||
})
|
||||
// the environment needs configured for this
|
||||
env.CLOUD = 1
|
||||
env.SELF_HOSTED = 1
|
||||
const enriched = await outputProcessing(config.getAppId(), table, [row])
|
||||
expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`)
|
||||
// remove env config
|
||||
env.CLOUD = undefined
|
||||
env.SELF_HOSTED = undefined
|
||||
})
|
||||
})
|
|
@ -0,0 +1,77 @@
|
|||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const setup = require("./utilities")
|
||||
const { basicScreen } = require("./utilities/structures")
|
||||
|
||||
describe("/screens", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let screen
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
screen = await config.createScreen()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("should be able to create a layout", async () => {
|
||||
const res = await request
|
||||
.get(`/api/screens`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.length).toEqual(3)
|
||||
expect(res.body.some(s => s._id === screen._id)).toEqual(true)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/screens`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("save", () => {
|
||||
it("should be able to save a screen", async () => {
|
||||
const screenCfg = basicScreen()
|
||||
const res = await request
|
||||
.post(`/api/screens`)
|
||||
.send(screenCfg)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body._rev).toBeDefined()
|
||||
expect(res.body.name).toEqual(screenCfg.name)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "POST",
|
||||
url: `/api/screens`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("should be able to delete the screen", async () => {
|
||||
const res = await request
|
||||
.delete(`/api/screens/${screen._id}/${screen._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.message).toBeDefined()
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "DELETE",
|
||||
url: `/api/screens/${screen._id}/${screen._rev}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,49 @@
|
|||
const setup = require("./utilities")
|
||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||
const fs = require("fs")
|
||||
const { join } = require("path")
|
||||
|
||||
describe("/templates", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("should be able to fetch templates", async () => {
|
||||
const res = await request
|
||||
.get(`/api/templates`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
// this test is quite light right now, templates aren't heavily utilised yet
|
||||
expect(Array.isArray(res.body)).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("export", () => {
|
||||
it("should be able to export the basic app", async () => {
|
||||
const res = await request
|
||||
.post(`/api/templates`)
|
||||
.send({
|
||||
templateName: "test",
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.message).toEqual("Created template: test")
|
||||
const dir = join(
|
||||
budibaseAppsDir(),
|
||||
"templates",
|
||||
"app",
|
||||
"test",
|
||||
"db"
|
||||
)
|
||||
expect(fs.existsSync(dir)).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -42,15 +42,19 @@ describe("/users", () => {
|
|||
})
|
||||
|
||||
describe("create", () => {
|
||||
async function create(user, status = 200) {
|
||||
return request
|
||||
.post(`/api/users`)
|
||||
.set(config.defaultHeaders())
|
||||
.send(user)
|
||||
.expect(status)
|
||||
.expect("Content-Type", /json/)
|
||||
}
|
||||
|
||||
it("returns a success message when a user is successfully created", async () => {
|
||||
const body = basicUser(BUILTIN_ROLE_IDS.POWER)
|
||||
body.email = "bill@budibase.com"
|
||||
const res = await request
|
||||
.post(`/api/users`)
|
||||
.set(config.defaultHeaders())
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
const res = await create(body)
|
||||
|
||||
expect(res.res.statusMessage).toEqual("User created successfully.")
|
||||
expect(res.body._id).toBeUndefined()
|
||||
|
@ -68,5 +72,65 @@ describe("/users", () => {
|
|||
failRole: BUILTIN_ROLE_IDS.PUBLIC,
|
||||
})
|
||||
})
|
||||
|
||||
it("should error if no email provided", async () => {
|
||||
const user = basicUser(BUILTIN_ROLE_IDS.POWER)
|
||||
delete user.email
|
||||
await create(user, 400)
|
||||
})
|
||||
|
||||
it("should error if no role provided", async () => {
|
||||
const user = basicUser(null)
|
||||
await create(user, 400)
|
||||
})
|
||||
|
||||
it("should throw error if user exists already", async () => {
|
||||
await config.createUser("test@test.com")
|
||||
const user = basicUser(BUILTIN_ROLE_IDS.POWER)
|
||||
user.email = "test@test.com"
|
||||
await create(user, 400)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
it("should be able to update the user", async () => {
|
||||
const user = await config.createUser()
|
||||
user.roleId = BUILTIN_ROLE_IDS.BASIC
|
||||
const res = await request
|
||||
.put(`/api/users`)
|
||||
.set(config.defaultHeaders())
|
||||
.send(user)
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
expect(res.body.ok).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("destroy", () => {
|
||||
it("should be able to delete the user", async () => {
|
||||
const email = "test@test.com"
|
||||
await config.createUser(email)
|
||||
const res = await request
|
||||
.delete(`/api/users/${email}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
expect(res.body.message).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("find", () => {
|
||||
it("should be able to find the user", async () => {
|
||||
const email = "test@test.com"
|
||||
await config.createUser(email)
|
||||
const res = await request
|
||||
.get(`/api/users/${email}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
expect(res.body.email).toEqual(email)
|
||||
expect(res.body.roleId).toEqual(BUILTIN_ROLE_IDS.POWER)
|
||||
expect(res.body.tableId).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -8,9 +8,15 @@ const {
|
|||
basicAutomation,
|
||||
basicDatasource,
|
||||
basicQuery,
|
||||
basicScreen,
|
||||
basicLayout,
|
||||
basicWebhook,
|
||||
} = require("./structures")
|
||||
const controllers = require("./controllers")
|
||||
const supertest = require("supertest")
|
||||
const fs = require("fs")
|
||||
const { budibaseAppsDir } = require("../../../../utilities/budibaseDir")
|
||||
const { join } = require("path")
|
||||
|
||||
const EMAIL = "babs@babs.com"
|
||||
const PASSWORD = "babs_password"
|
||||
|
@ -22,6 +28,7 @@ class TestConfiguration {
|
|||
// we need the request for logging in, involves cookies, hard to fake
|
||||
this.request = supertest(this.server)
|
||||
this.appId = null
|
||||
this.allApps = []
|
||||
}
|
||||
|
||||
getRequest() {
|
||||
|
@ -55,6 +62,13 @@ class TestConfiguration {
|
|||
|
||||
end() {
|
||||
this.server.close()
|
||||
const appDir = budibaseAppsDir()
|
||||
const files = fs.readdirSync(appDir)
|
||||
for (let file of files) {
|
||||
if (this.allApps.some(app => file.includes(app._id))) {
|
||||
fs.rmdirSync(join(appDir, file), { recursive: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultHeaders() {
|
||||
|
@ -83,9 +97,19 @@ class TestConfiguration {
|
|||
return headers
|
||||
}
|
||||
|
||||
async roleHeaders(email = EMAIL, roleId = BUILTIN_ROLE_IDS.ADMIN) {
|
||||
try {
|
||||
await this.createUser(email, PASSWORD, roleId)
|
||||
} catch (err) {
|
||||
// allow errors here
|
||||
}
|
||||
return this.login(email, PASSWORD)
|
||||
}
|
||||
|
||||
async createApp(appName) {
|
||||
this.app = await this._req({ name: appName }, null, controllers.app.create)
|
||||
this.appId = this.app._id
|
||||
this.allApps.push(this.app)
|
||||
return this.app
|
||||
}
|
||||
|
||||
|
@ -208,6 +232,24 @@ class TestConfiguration {
|
|||
return this._req(config, null, controllers.query.save)
|
||||
}
|
||||
|
||||
async createScreen(config = null) {
|
||||
config = config || basicScreen()
|
||||
return this._req(config, null, controllers.screen.save)
|
||||
}
|
||||
|
||||
async createWebhook(config = null) {
|
||||
if (!this.automation) {
|
||||
throw "Must create an automation before creating webhook."
|
||||
}
|
||||
config = config || basicWebhook(this.automation._id)
|
||||
return (await this._req(config, null, controllers.webhook.save)).webhook
|
||||
}
|
||||
|
||||
async createLayout(config = null) {
|
||||
config = config || basicLayout()
|
||||
return await this._req(config, null, controllers.layout.save)
|
||||
}
|
||||
|
||||
async createUser(
|
||||
email = EMAIL,
|
||||
password = PASSWORD,
|
||||
|
@ -224,6 +266,24 @@ class TestConfiguration {
|
|||
)
|
||||
}
|
||||
|
||||
async makeUserInactive(email) {
|
||||
const user = await this._req(
|
||||
null,
|
||||
{
|
||||
email,
|
||||
},
|
||||
controllers.user.find
|
||||
)
|
||||
return this._req(
|
||||
{
|
||||
...user,
|
||||
status: "inactive",
|
||||
},
|
||||
null,
|
||||
controllers.user.update
|
||||
)
|
||||
}
|
||||
|
||||
async login(email, password) {
|
||||
if (!email || !password) {
|
||||
await this.createUser()
|
||||
|
@ -241,6 +301,7 @@ class TestConfiguration {
|
|||
return {
|
||||
Accept: "application/json",
|
||||
Cookie: result.headers["set-cookie"],
|
||||
"x-budibase-app-id": this.appId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,7 @@ module.exports = {
|
|||
automation: require("../../../controllers/automation"),
|
||||
datasource: require("../../../controllers/datasource"),
|
||||
query: require("../../../controllers/query"),
|
||||
screen: require("../../../controllers/screen"),
|
||||
webhook: require("../../../controllers/webhook"),
|
||||
layout: require("../../../controllers/layout"),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const TestConfig = require("./TestConfiguration")
|
||||
const env = require("../../../../environment")
|
||||
|
||||
exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
|
@ -13,6 +14,8 @@ exports.afterAll = () => {
|
|||
if (config) {
|
||||
config.end()
|
||||
}
|
||||
// clear app files
|
||||
|
||||
request = null
|
||||
config = null
|
||||
}
|
||||
|
@ -30,3 +33,21 @@ exports.getConfig = () => {
|
|||
}
|
||||
return config
|
||||
}
|
||||
|
||||
exports.switchToCloudForFunction = async func => {
|
||||
// self hosted stops any attempts to Dynamo
|
||||
env.CLOUD = true
|
||||
env.SELF_HOSTED = true
|
||||
let error
|
||||
try {
|
||||
await func()
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
env.CLOUD = false
|
||||
env.SELF_HOSTED = false
|
||||
// don't throw error until after reset
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles")
|
|||
const {
|
||||
BUILTIN_PERMISSION_IDS,
|
||||
} = require("../../../../utilities/security/permissions")
|
||||
const { createHomeScreen } = require("../../../../constants/screens")
|
||||
const { EMPTY_LAYOUT } = require("../../../../constants/layouts")
|
||||
const { cloneDeep } = require("lodash/fp")
|
||||
|
||||
exports.basicTable = () => {
|
||||
return {
|
||||
|
@ -85,3 +88,22 @@ exports.basicUser = role => {
|
|||
roleId: role,
|
||||
}
|
||||
}
|
||||
|
||||
exports.basicScreen = () => {
|
||||
return createHomeScreen()
|
||||
}
|
||||
|
||||
exports.basicLayout = () => {
|
||||
return cloneDeep(EMPTY_LAYOUT)
|
||||
}
|
||||
|
||||
exports.basicWebhook = automationId => {
|
||||
return {
|
||||
live: true,
|
||||
name: "webhook",
|
||||
action: {
|
||||
type: "automation",
|
||||
target: automationId,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
const setup = require("./utilities")
|
||||
const { checkBuilderEndpoint } = require("./utilities/TestFunctions")
|
||||
const { basicWebhook, basicAutomation } = require("./utilities/structures")
|
||||
|
||||
describe("/webhooks", () => {
|
||||
let request = setup.getRequest()
|
||||
let config = setup.getConfig()
|
||||
let webhook
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
const autoConfig = basicAutomation()
|
||||
autoConfig.definition.trigger = {
|
||||
schema: { outputs: { properties: {} } },
|
||||
inputs: {},
|
||||
}
|
||||
await config.createAutomation(autoConfig)
|
||||
webhook = await config.createWebhook()
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
it("should create a webhook successfully", async () => {
|
||||
const automation = await config.createAutomation()
|
||||
const res = await request
|
||||
.put(`/api/webhooks`)
|
||||
.send(basicWebhook(automation._id))
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.webhook).toBeDefined()
|
||||
expect(typeof res.body.webhook._id).toEqual("string")
|
||||
expect(typeof res.body.webhook._rev).toEqual("string")
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "PUT",
|
||||
url: `/api/webhooks`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("returns the correct routing for basic user", async () => {
|
||||
const res = await request
|
||||
.get(`/api/webhooks`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(Array.isArray(res.body)).toEqual(true)
|
||||
expect(res.body[0]._id).toEqual(webhook._id)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "GET",
|
||||
url: `/api/webhooks`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
it("should successfully delete", async () => {
|
||||
const res = await request
|
||||
.delete(`/api/webhooks/${webhook._id}/${webhook._rev}`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toBeDefined()
|
||||
expect(res.body.ok).toEqual(true)
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "DELETE",
|
||||
url: `/api/webhooks/${webhook._id}/${webhook._rev}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("build schema", () => {
|
||||
it("should allow building a schema", async () => {
|
||||
const res = await request
|
||||
.post(`/api/webhooks/schema/${config.getAppId()}/${webhook._id}`)
|
||||
.send({
|
||||
a: 1
|
||||
})
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body).toBeDefined()
|
||||
// fetch to see if the schema has been updated
|
||||
const fetch = await request
|
||||
.get(`/api/webhooks`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(fetch.body[0]).toBeDefined()
|
||||
expect(fetch.body[0].bodySchema).toEqual({
|
||||
properties: {
|
||||
a: { type: "integer" }
|
||||
},
|
||||
type: "object",
|
||||
})
|
||||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await checkBuilderEndpoint({
|
||||
config,
|
||||
method: "POST",
|
||||
url: `/api/webhooks/schema/${config.getAppId()}/${webhook._id}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("trigger", () => {
|
||||
it("should allow triggering from public", async () => {
|
||||
const res = await request
|
||||
.post(`/api/webhooks/trigger/${config.getAppId()}/${webhook._id}`)
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.message).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -21,7 +21,7 @@ router
|
|||
controller.find
|
||||
)
|
||||
.put(
|
||||
"/api/users/",
|
||||
"/api/users",
|
||||
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
|
||||
controller.update
|
||||
)
|
||||
|
|
|
@ -56,7 +56,11 @@ if (electron.app && electron.app.isPackaged) {
|
|||
const server = http.createServer(app.callback())
|
||||
destroyable(server)
|
||||
|
||||
server.on("close", () => console.log("Server Closed"))
|
||||
server.on("close", () => {
|
||||
if (env.NODE_ENV !== "jest") {
|
||||
console.log("Server Closed")
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = server.listen(env.PORT || 0, async () => {
|
||||
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = async (ctx, next) => {
|
|||
token = ctx.cookies.get(getCookieName())
|
||||
authType = AuthTypes.BUILDER
|
||||
}
|
||||
|
||||
if (!token && appId) {
|
||||
token = ctx.cookies.get(getCookieName(appId))
|
||||
authType = AuthTypes.APP
|
||||
|
@ -58,6 +59,7 @@ module.exports = async (ctx, next) => {
|
|||
role: await getRole(appId, jwtPayload.roleId),
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
if (authType === AuthTypes.BUILDER) {
|
||||
clearCookie(ctx)
|
||||
ctx.status = 200
|
||||
|
|
|
@ -13,7 +13,7 @@ const { AuthTypes } = require("../constants")
|
|||
|
||||
const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER]
|
||||
|
||||
const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|"))
|
||||
const LOCAL_PASS = new RegExp(["webhooks/trigger"].join("|"))
|
||||
|
||||
function hasResource(ctx) {
|
||||
return ctx.resourceId != null
|
||||
|
@ -24,6 +24,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) {
|
||||
// api key header passed by external webhook
|
||||
if (await isAPIKeyValid(ctx.headers["x-api-key"])) {
|
||||
|
@ -37,14 +38,14 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
ctx.throw(403, "API key invalid")
|
||||
return ctx.throw(403, "API key invalid")
|
||||
}
|
||||
|
||||
// don't expose builder endpoints in the cloud
|
||||
if (env.CLOUD && permType === PermissionTypes.BUILDER) return
|
||||
|
||||
if (!ctx.user) {
|
||||
ctx.throw(403, "No user info found")
|
||||
return ctx.throw(403, "No user info found")
|
||||
}
|
||||
|
||||
const role = ctx.user.role
|
||||
|
@ -52,7 +53,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
ctx.appId,
|
||||
role._id
|
||||
)
|
||||
const isAdmin = ADMIN_ROLES.indexOf(role._id) !== -1
|
||||
const isAdmin = ADMIN_ROLES.includes(role._id)
|
||||
const isAuthed = ctx.auth.authenticated
|
||||
|
||||
// this may need to change in the future, right now only admins
|
||||
|
@ -61,7 +62,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
if (isAdmin && isAuthed) {
|
||||
return next()
|
||||
} else if (permType === PermissionTypes.BUILDER) {
|
||||
ctx.throw(403, "Not Authorized")
|
||||
return ctx.throw(403, "Not Authorized")
|
||||
}
|
||||
|
||||
if (
|
||||
|
|
|
@ -36,6 +36,8 @@ class ResourceIdGetter {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports.ResourceIdGetter = ResourceIdGetter
|
||||
|
||||
module.exports.paramResource = main => {
|
||||
return new ResourceIdGetter("params").mainResource(main).build()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Authenticated middleware sets the correct APP auth type information when the user is not in the builder 1`] = `
|
||||
Object {
|
||||
"apiKey": "1234",
|
||||
"appId": "budibase:app:local",
|
||||
"role": Role {
|
||||
"_id": "ADMIN",
|
||||
"inherits": "POWER",
|
||||
"name": "Admin",
|
||||
"permissionId": "admin",
|
||||
},
|
||||
"roleId": "ADMIN",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Authenticated middleware sets the correct BUILDER auth type information when the x-budibase-type header is not 'client' 1`] = `
|
||||
Object {
|
||||
"apiKey": "1234",
|
||||
"appId": "budibase:builder:local",
|
||||
"role": Role {
|
||||
"_id": "BUILDER",
|
||||
"name": "Builder",
|
||||
"permissionId": "admin",
|
||||
},
|
||||
"roleId": "BUILDER",
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,125 @@
|
|||
const { AuthTypes } = require("../../constants")
|
||||
const authenticatedMiddleware = require("../authenticated")
|
||||
const jwt = require("jsonwebtoken")
|
||||
jest.mock("jsonwebtoken")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(middleware) {
|
||||
this.middleware = authenticatedMiddleware
|
||||
this.ctx = {
|
||||
config: {},
|
||||
auth: {},
|
||||
cookies: {
|
||||
set: jest.fn(),
|
||||
get: jest.fn()
|
||||
},
|
||||
headers: {},
|
||||
params: {},
|
||||
path: "",
|
||||
request: {
|
||||
headers: {}
|
||||
},
|
||||
throw: jest.fn()
|
||||
}
|
||||
this.next = jest.fn()
|
||||
}
|
||||
|
||||
setHeaders(headers) {
|
||||
this.ctx.headers = headers
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
|
||||
afterEach() {
|
||||
jest.resetAllMocks()
|
||||
}
|
||||
}
|
||||
|
||||
describe("Authenticated middleware", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
config.afterEach()
|
||||
})
|
||||
|
||||
it("calls next() when on the builder path", async () => {
|
||||
config.ctx.path = "/_builder"
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("sets a new cookie when the current cookie does not match the app id from context", async () => {
|
||||
const appId = "app_123"
|
||||
config.setHeaders({
|
||||
"x-budibase-app-id": appId
|
||||
})
|
||||
config.ctx.cookies.get.mockImplementation(() => "cookieAppId")
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.cookies.set).toHaveBeenCalledWith(
|
||||
"budibase:currentapp:local",
|
||||
appId,
|
||||
expect.any(Object)
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
it("sets the correct BUILDER auth type information when the x-budibase-type header is not 'client'", async () => {
|
||||
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
|
||||
jwt.verify.mockImplementationOnce(() => ({
|
||||
apiKey: "1234",
|
||||
roleId: "BUILDER"
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER)
|
||||
expect(config.ctx.user).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("sets the correct APP auth type information when the user is not in the builder", async () => {
|
||||
config.setHeaders({
|
||||
"x-budibase-type": "client"
|
||||
})
|
||||
config.ctx.cookies.get.mockImplementation(() => `budibase:app:local`)
|
||||
jwt.verify.mockImplementationOnce(() => ({
|
||||
apiKey: "1234",
|
||||
roleId: "ADMIN"
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP)
|
||||
expect(config.ctx.user).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => {
|
||||
config.executeMiddleware()
|
||||
expect(config.ctx.auth.authenticated).toBe(false)
|
||||
expect(config.ctx.user.role).toEqual({
|
||||
_id: "PUBLIC",
|
||||
name: "Public",
|
||||
permissionId: "public"
|
||||
})
|
||||
})
|
||||
|
||||
it("clears the cookie when there is an error authenticating in the builder", async () => {
|
||||
config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local")
|
||||
jwt.verify.mockImplementationOnce(() => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.cookies.set).toBeCalledWith("budibase:builder:local")
|
||||
})
|
||||
})
|
|
@ -0,0 +1,196 @@
|
|||
const authorizedMiddleware = require("../authorized")
|
||||
const env = require("../../environment")
|
||||
const apiKey = require("../../utilities/security/apikey")
|
||||
const { AuthTypes } = require("../../constants")
|
||||
const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions")
|
||||
const { Test } = require("supertest")
|
||||
jest.mock("../../environment")
|
||||
jest.mock("../../utilities/security/apikey")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(role) {
|
||||
this.middleware = authorizedMiddleware(role)
|
||||
this.next = jest.fn()
|
||||
this.throw = jest.fn()
|
||||
this.ctx = {
|
||||
headers: {},
|
||||
request: {
|
||||
url: ""
|
||||
},
|
||||
auth: {},
|
||||
next: this.next,
|
||||
throw: this.throw
|
||||
}
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
|
||||
setUser(user) {
|
||||
this.ctx.user = user
|
||||
}
|
||||
|
||||
setMiddlewareRequiredPermission(...perms) {
|
||||
this.middleware = authorizedMiddleware(...perms)
|
||||
}
|
||||
|
||||
setResourceId(id) {
|
||||
this.ctx.resourceId = id
|
||||
}
|
||||
|
||||
setAuthenticated(isAuthed) {
|
||||
this.ctx.auth = { authenticated: isAuthed }
|
||||
}
|
||||
|
||||
setRequestUrl(url) {
|
||||
this.ctx.request.url = url
|
||||
}
|
||||
|
||||
setCloudEnv(isCloud) {
|
||||
env.CLOUD = isCloud
|
||||
}
|
||||
|
||||
setRequestHeaders(headers) {
|
||||
this.ctx.headers = headers
|
||||
}
|
||||
|
||||
afterEach() {
|
||||
jest.clearAllMocks()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe("Authorization middleware", () => {
|
||||
const next = jest.fn()
|
||||
let config
|
||||
|
||||
afterEach(() => {
|
||||
config.afterEach()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("passes the middleware for local webhooks", async () => {
|
||||
config.setRequestUrl("https://something/webhooks/trigger")
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe("external web hook call", () => {
|
||||
let ctx = {}
|
||||
let middleware
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
config.setCloudEnv(true)
|
||||
config.setRequestHeaders({
|
||||
"x-api-key": "abc123",
|
||||
"x-instanceid": "instance123",
|
||||
})
|
||||
})
|
||||
|
||||
it("passes to next() if api key is valid", async () => {
|
||||
apiKey.isAPIKeyValid.mockResolvedValueOnce(true)
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
expect(config.ctx.auth).toEqual({
|
||||
authenticated: AuthTypes.EXTERNAL,
|
||||
apiKey: config.ctx.headers["x-api-key"],
|
||||
})
|
||||
expect(config.ctx.user).toEqual({
|
||||
appId: config.ctx.headers["x-instanceid"],
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if api key is invalid", async () => {
|
||||
apiKey.isAPIKeyValid.mockResolvedValueOnce(false)
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "API key invalid")
|
||||
})
|
||||
})
|
||||
|
||||
describe("non-webhook call", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
config.setCloudEnv(true)
|
||||
config.setAuthenticated(true)
|
||||
})
|
||||
|
||||
it("throws when no user data is present in context", async () => {
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "No user info found")
|
||||
})
|
||||
|
||||
it("passes on to next() middleware if user is an admin", async () => {
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: "ADMIN",
|
||||
}
|
||||
})
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws if the user has only builder permissions", async () => {
|
||||
config.setCloudEnv(false)
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
}
|
||||
})
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "Not Authorized")
|
||||
})
|
||||
|
||||
it("passes on to next() middleware if the user has resource permission", async () => {
|
||||
config.setResourceId(PermissionTypes.QUERY)
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
}
|
||||
})
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.QUERY)
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws if the user session is not authenticated after permission checks", async () => {
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
},
|
||||
})
|
||||
config.setAuthenticated(false)
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "Session not authenticated")
|
||||
})
|
||||
|
||||
it("throws if the user does not have base permissions to perform the operation", async () => {
|
||||
config.setUser({
|
||||
role: {
|
||||
_id: ""
|
||||
},
|
||||
})
|
||||
config.setMiddlewareRequiredPermission(PermissionTypes.ADMIN, PermissionLevels.BASIC)
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(403, "User does not have permission")
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,105 @@
|
|||
const {
|
||||
paramResource,
|
||||
paramSubResource,
|
||||
bodyResource,
|
||||
bodySubResource,
|
||||
ResourceIdGetter
|
||||
} = require("../resourceId")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(middleware) {
|
||||
this.middleware = middleware
|
||||
this.ctx = {
|
||||
request: {},
|
||||
}
|
||||
this.next = jest.fn()
|
||||
}
|
||||
|
||||
setParams(params) {
|
||||
this.ctx.params = params
|
||||
}
|
||||
|
||||
setBody(body) {
|
||||
this.ctx.body = body
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
}
|
||||
|
||||
describe("resourceId middleware", () => {
|
||||
it("calls next() when there is no request object to parse", () => {
|
||||
const config = new TestConfiguration(paramResource("main"))
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
expect(config.ctx.resourceId).toBeUndefined()
|
||||
})
|
||||
|
||||
it("generates a resourceId middleware for context query parameters", () => {
|
||||
const config = new TestConfiguration(paramResource("main"))
|
||||
config.setParams({
|
||||
main: "test"
|
||||
})
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.resourceId).toEqual("test")
|
||||
})
|
||||
|
||||
it("generates a resourceId middleware for context query sub parameters", () => {
|
||||
const config = new TestConfiguration(paramSubResource("main", "sub"))
|
||||
config.setParams({
|
||||
main: "main",
|
||||
sub: "test"
|
||||
})
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.resourceId).toEqual("main")
|
||||
expect(config.ctx.subResourceId).toEqual("test")
|
||||
})
|
||||
|
||||
it("generates a resourceId middleware for context request body", () => {
|
||||
const config = new TestConfiguration(bodyResource("main"))
|
||||
config.setBody({
|
||||
main: "test"
|
||||
})
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.resourceId).toEqual("test")
|
||||
})
|
||||
|
||||
it("generates a resourceId middleware for context request body sub fields", () => {
|
||||
const config = new TestConfiguration(bodySubResource("main", "sub"))
|
||||
config.setBody({
|
||||
main: "main",
|
||||
sub: "test"
|
||||
})
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.resourceId).toEqual("main")
|
||||
expect(config.ctx.subResourceId).toEqual("test")
|
||||
})
|
||||
|
||||
it("parses resourceIds correctly for custom middlewares", () => {
|
||||
const middleware = new ResourceIdGetter("body")
|
||||
.mainResource("custom")
|
||||
.subResource("customSub")
|
||||
.build()
|
||||
config = new TestConfiguration(middleware)
|
||||
config.setBody({
|
||||
custom: "test",
|
||||
customSub: "subtest"
|
||||
})
|
||||
|
||||
config.executeMiddleware()
|
||||
|
||||
expect(config.ctx.resourceId).toEqual("test")
|
||||
expect(config.ctx.subResourceId).toEqual("subtest")
|
||||
})
|
||||
})
|
|
@ -0,0 +1,75 @@
|
|||
const selfHostMiddleware = require("../selfhost");
|
||||
const env = require("../../environment")
|
||||
const hosting = require("../../utilities/builder/hosting");
|
||||
jest.mock("../../environment")
|
||||
jest.mock("../../utilities/builder/hosting")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor() {
|
||||
this.next = jest.fn()
|
||||
this.throw = jest.fn()
|
||||
this.middleware = selfHostMiddleware
|
||||
|
||||
this.ctx = {
|
||||
next: this.next,
|
||||
throw: this.throw
|
||||
}
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
|
||||
setCloudHosted() {
|
||||
env.CLOUD = 1
|
||||
env.SELF_HOSTED = 0
|
||||
}
|
||||
|
||||
setSelfHosted() {
|
||||
env.CLOUD = 0
|
||||
env.SELF_HOSTED = 1
|
||||
}
|
||||
|
||||
afterEach() {
|
||||
jest.clearAllMocks()
|
||||
}
|
||||
}
|
||||
|
||||
describe("Self host middleware", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
config.afterEach()
|
||||
})
|
||||
|
||||
it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => {
|
||||
env.CLOUD = 1
|
||||
env.SELF_HOSTED = 1
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws when hostingInfo type is cloud", async () => {
|
||||
config.setSelfHosted()
|
||||
|
||||
hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD }))
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.")
|
||||
expect(config.next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => {
|
||||
config.setSelfHosted()
|
||||
|
||||
hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF }))
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,129 @@
|
|||
const usageQuotaMiddleware = require("../usageQuota")
|
||||
const usageQuota = require("../../utilities/usageQuota")
|
||||
const CouchDB = require("../../db")
|
||||
const env = require("../../environment")
|
||||
|
||||
jest.mock("../../db");
|
||||
jest.mock("../../utilities/usageQuota")
|
||||
jest.mock("../../environment")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor() {
|
||||
this.throw = jest.fn()
|
||||
this.next = jest.fn()
|
||||
this.middleware = usageQuotaMiddleware
|
||||
this.ctx = {
|
||||
throw: this.throw,
|
||||
next: this.next,
|
||||
user: {
|
||||
appId: "test"
|
||||
},
|
||||
request: {
|
||||
body: {}
|
||||
},
|
||||
req: {
|
||||
method: "POST",
|
||||
url: "/rows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
|
||||
cloudHosted(bool) {
|
||||
if (bool) {
|
||||
env.CLOUD = 1
|
||||
this.ctx.auth = { apiKey: "test" }
|
||||
} else {
|
||||
env.CLOUD = 0
|
||||
}
|
||||
}
|
||||
|
||||
setMethod(method) {
|
||||
this.ctx.req.method = method
|
||||
}
|
||||
|
||||
setUrl(url) {
|
||||
this.ctx.req.url = url
|
||||
}
|
||||
|
||||
setBody(body) {
|
||||
this.ctx.request.body = body
|
||||
}
|
||||
|
||||
setFiles(files) {
|
||||
this.ctx.request.files = { file: files }
|
||||
}
|
||||
}
|
||||
|
||||
describe("usageQuota middleware", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("skips the middleware if there is no usage property or method", async () => {
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("passes through to next middleware if document already exists", async () => {
|
||||
config.setBody({
|
||||
_id: "test"
|
||||
})
|
||||
|
||||
CouchDB.mockImplementationOnce(() => ({
|
||||
get: async () => true
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
expect(config.ctx.preExisting).toBe(true)
|
||||
})
|
||||
|
||||
it("throws if request has _id, but the document no longer exists", async () => {
|
||||
config.setBody({
|
||||
_id: "123"
|
||||
})
|
||||
|
||||
CouchDB.mockImplementationOnce(() => ({
|
||||
get: async () => {
|
||||
throw new Error()
|
||||
}
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(404, `${config.ctx.request.body._id} does not exist`)
|
||||
})
|
||||
|
||||
it("calculates and persists the correct usage quota for the relevant action", async () => {
|
||||
config.setUrl("/rows")
|
||||
config.cloudHosted(true)
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(usageQuota.update).toHaveBeenCalledWith("test", "rows", 1)
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("calculates the correct file size from a file upload call and adds it to quota", async () => {
|
||||
config.setUrl("/upload")
|
||||
config.cloudHosted(true)
|
||||
config.setFiles([
|
||||
{
|
||||
size: 100
|
||||
},
|
||||
{
|
||||
size: 10000
|
||||
},
|
||||
])
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(usageQuota.update).toHaveBeenCalledWith("test", "storage", 10100)
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
})
|
|
@ -43,6 +43,7 @@ module.exports = async (ctx, next) => {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if running in builder or a self hosted cloud usage quotas should not be executed
|
||||
if (!env.CLOUD || env.SELF_HOSTED) {
|
||||
return next()
|
||||
|
|
|
@ -205,7 +205,8 @@ class AccessController {
|
|||
tryingRoleId == null ||
|
||||
tryingRoleId === "" ||
|
||||
tryingRoleId === userRoleId ||
|
||||
tryingRoleId === BUILTIN_IDS.BUILDER
|
||||
tryingRoleId === BUILTIN_IDS.BUILDER ||
|
||||
userRoleId === BUILTIN_IDS.BUILDER
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ exports.getLocalTemplates = function() {
|
|||
return templateObj
|
||||
}
|
||||
|
||||
// can't really test this, downloading is just not something we should do in a behavioural test
|
||||
/* istanbul ignore next */
|
||||
exports.downloadTemplate = async function(type, name) {
|
||||
const dirName = join(budibaseAppsDir(), "templates", type, name)
|
||||
if (env.LOCAL_TEMPLATES) {
|
||||
|
@ -67,8 +69,7 @@ exports.performDump = performDump
|
|||
exports.exportTemplateFromApp = async function({ templateName, appId }) {
|
||||
// Copy frontend files
|
||||
const templatesDir = join(
|
||||
os.homedir(),
|
||||
".budibase",
|
||||
budibaseAppsDir(),
|
||||
"templates",
|
||||
"app",
|
||||
templateName,
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f"
|
||||
integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==
|
||||
|
||||
"@adobe/spectrum-css-workflow-icons@^1.1.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.0.tgz#cda8bbe873ba9317160458858ae979e5393e5550"
|
||||
integrity sha512-STSQQHvoBM0kf1JrNL3KEt88RklIctaGyGOzwUTnhtTkT1jHLaF4FgxrPDCvr1AT8VOq1nGplKUCeyZ9vdUUmA==
|
||||
|
||||
"@azure/ms-rest-azure-env@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f"
|
||||
|
@ -249,12 +254,24 @@
|
|||
lodash "^4.17.19"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@budibase/client@^0.8.3":
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.3.tgz#944a745cc82845987cabd48e2ce3a7e58b387865"
|
||||
integrity sha512-gEOmHlqStsFTtotduRRz9bld2s/066pSwM3CWRuspsz5yycPLMhWKcA3CdfxVlNoR9y7I7IFC9+pfM5STDJAMQ==
|
||||
"@budibase/bbui@^1.58.13":
|
||||
version "1.58.13"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.13.tgz#59df9c73def2d81c75dcbd2266c52c19db88dbd7"
|
||||
integrity sha512-Zk6CKXdBfKsTVzA1Xs5++shdSSZLfphVpZuKVbjfzkgtuhyH7ruucexuSHEpFsxjW5rEKgKIBoRFzCK5vPvN0w==
|
||||
dependencies:
|
||||
"@budibase/string-templates" "^0.8.3"
|
||||
markdown-it "^12.0.2"
|
||||
quill "^1.3.7"
|
||||
sirv-cli "^0.4.6"
|
||||
svelte-flatpickr "^2.4.0"
|
||||
svelte-portal "^1.0.0"
|
||||
turndown "^7.0.0"
|
||||
|
||||
"@budibase/client@^0.8.5":
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.5.tgz#31a6bbf8e7ff2a5ab635e8987357c310dcedf555"
|
||||
integrity sha512-igiHyFpqbYm2EyCy0aUlBlaPibpFa5DtQow1kFBAjUW2cyZdEt84JV4Mei77NueGo7zHcr6/ByF6ycdyeBgXQw==
|
||||
dependencies:
|
||||
"@budibase/string-templates" "^0.8.5"
|
||||
deep-equal "^2.0.1"
|
||||
regexparam "^1.3.0"
|
||||
shortid "^2.2.15"
|
||||
|
@ -292,10 +309,42 @@
|
|||
to-gfm-code-block "^0.1.1"
|
||||
year "^0.2.1"
|
||||
|
||||
"@budibase/string-templates@^0.8.3":
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.3.tgz#f3b1f31ef914b926fb5285bc701e1200568dc92d"
|
||||
integrity sha512-X4Z9/1TS5PtO5sF1CDoyp8xSJhXFWIhOldTNBzPeCjAaD+c9Q8gOgcwECWugJh2d05RjiVI6gDbeirT8Q2QMig==
|
||||
"@budibase/standard-components@^0.8.5":
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.8.5.tgz#4b94653110e4f20a8cb252b6421b620fd5ac31bc"
|
||||
integrity sha512-wDEuxiu/DyPQYR2zQSt7TdPlAzdjjePitfKDzdIxm/WM7umXDSvLkA39nRzicEXikti34+waS7H96xGNuednVw==
|
||||
dependencies:
|
||||
"@adobe/spectrum-css-workflow-icons" "^1.1.0"
|
||||
"@budibase/bbui" "^1.58.13"
|
||||
"@budibase/svelte-ag-grid" "^1.0.4"
|
||||
"@spectrum-css/actionbutton" "^1.0.0-beta.1"
|
||||
"@spectrum-css/button" "^3.0.0-beta.6"
|
||||
"@spectrum-css/checkbox" "^3.0.0-beta.6"
|
||||
"@spectrum-css/fieldlabel" "^3.0.0-beta.7"
|
||||
"@spectrum-css/icon" "^3.0.0-beta.2"
|
||||
"@spectrum-css/inputgroup" "^3.0.0-beta.7"
|
||||
"@spectrum-css/menu" "^3.0.0-beta.5"
|
||||
"@spectrum-css/page" "^3.0.0-beta.0"
|
||||
"@spectrum-css/picker" "^1.0.0-beta.3"
|
||||
"@spectrum-css/popover" "^3.0.0-beta.6"
|
||||
"@spectrum-css/stepper" "^3.0.0-beta.7"
|
||||
"@spectrum-css/textfield" "^3.0.0-beta.6"
|
||||
"@spectrum-css/vars" "^3.0.0-beta.2"
|
||||
apexcharts "^3.22.1"
|
||||
flatpickr "^4.6.6"
|
||||
loadicons "^1.0.0"
|
||||
lodash.debounce "^4.0.8"
|
||||
markdown-it "^12.0.2"
|
||||
quill "^1.3.7"
|
||||
remixicon "^2.5.0"
|
||||
svelte-apexcharts "^1.0.2"
|
||||
svelte-flatpickr "^3.1.0"
|
||||
turndown "^7.0.0"
|
||||
|
||||
"@budibase/string-templates@^0.8.5":
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.5.tgz#ad30e318f7486d4256b1165099fe2bd8004ef472"
|
||||
integrity sha512-PcpiiDlYJFIVwtFGIRqZQtRl8wbO6yr0/+1Gca0TwR2WhyUyAs/ojO+jLIj97JWh/hE5zKaZW7d4cMOf+BDI/A==
|
||||
dependencies:
|
||||
"@budibase/handlebars-helpers" "^0.11.3"
|
||||
dayjs "^1.10.4"
|
||||
|
@ -303,6 +352,13 @@
|
|||
handlebars-utils "^1.0.6"
|
||||
lodash "^4.17.20"
|
||||
|
||||
"@budibase/svelte-ag-grid@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@budibase/svelte-ag-grid/-/svelte-ag-grid-1.0.4.tgz#41cceec4bde2c4aea8b9da8f610fe36055c7709f"
|
||||
integrity sha512-JZm6qujxnZpqw7Twbegr6se4sHhyWzN0Cibrk5bVBH32hBgzD6dd33fxwrjHKkWFxjys9wRT+cqYgYVlSt9E3w==
|
||||
dependencies:
|
||||
ag-grid-community "^24.0.0"
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
|
||||
|
@ -830,6 +886,11 @@
|
|||
path-to-regexp "^1.1.1"
|
||||
urijs "^1.19.0"
|
||||
|
||||
"@polka/url@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31"
|
||||
integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==
|
||||
|
||||
"@sendgrid/client@^7.1.1":
|
||||
version "7.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.4.2.tgz#204a9fbb5dc05a721a5d8cd8930f57f9f8e612b1"
|
||||
|
@ -942,6 +1003,73 @@
|
|||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
|
||||
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
|
||||
|
||||
"@spectrum-css/actionbutton@^1.0.0-beta.1":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.0.tgz#c2f0939f6d49de0a855f08a9466e3f27105a1747"
|
||||
integrity sha512-klE5CGJEJXkc4DMLF8W+VPlLZ6SFr4WXI5Tc9NarOtbAc7mqhs2gWA8HpsPT717FWdxRVVt3sSuAydgKC/T0UA==
|
||||
|
||||
"@spectrum-css/button@^3.0.0-beta.6":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0.tgz#eebdd7a05eac9a40f297f802aca3efeb95931e83"
|
||||
integrity sha512-CUGkuHOhqgfIRYTEceybcW1YsUN61F9BgDhqymhVd1yJFsuh1xkwnmv3IIodukgS+1e3L0JY6ifU86IWX/Dx5w==
|
||||
|
||||
"@spectrum-css/checkbox@^3.0.0-beta.6":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.0.tgz#6ed15f433bed31a63818d154960aca044ce62182"
|
||||
integrity sha512-FpxxftMzuWT8qq3XB4oBQgWglXuCCEGBfgX82EI9VtrJmw9j0Lm/nThMLX353p9awM4GfT3l2LNOneHbNetaRQ==
|
||||
|
||||
"@spectrum-css/fieldlabel@^3.0.0-beta.7":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0.tgz#01be5e5a7b024516574820962b9a656f7b2e20d4"
|
||||
integrity sha512-dEOvDEigL9E60kQ9fT6MLyRzPKrPXAKulqDYOYpZaK2bsKrbIvsKb7NcuQynPAOE26FiuqQsp2khv5VqF4KzrA==
|
||||
|
||||
"@spectrum-css/icon@^3.0.0-beta.2":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0.tgz#a822c901ca049f487420053dfeaaf71c0850c848"
|
||||
integrity sha512-0VVx34WECxe+acSZsB+zk8T+AG8YimlCfUothuqLzcUgY6MnBESHJKOEuKKihxnihEm6EJiMc2NYA7+09kPv/A==
|
||||
|
||||
"@spectrum-css/inputgroup@^3.0.0-beta.7":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.0.tgz#c2790c2c0a4c435ca3fff9ba04f64dd2b252f980"
|
||||
integrity sha512-dlF8LmMwTa5G6Rl4zUiNCmRv7p2v+88jINnSwZHucgKZL0/HJZBRxjF1neeSfRFrc8R6cemoVXDHRDtZFaVtXQ==
|
||||
|
||||
"@spectrum-css/menu@^3.0.0-beta.5":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.0.tgz#78153ea60a36c87e9d815ce51dc7f84d6b9b9abd"
|
||||
integrity sha512-E6L6s1/cwh6hn4yhUHegiJ+Su03Bpa7qP5a6nEccpYePZxPAAN2FjZBWdMOPlGtv1e70vudAsoejli9nVthC2w==
|
||||
|
||||
"@spectrum-css/page@^3.0.0-beta.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0.tgz#159e79fd376e2def1a7a25a8b8a8fcfa94bd79d1"
|
||||
integrity sha512-4rNpGq99cfNSq/IOQNCiXio5gF/EEfjcSmihHBJlh7/VOB9zE84kMNW1Gux4cGEmdP14U1Zo1ZwnPIVs5ZuPgg==
|
||||
dependencies:
|
||||
"@spectrum-css/vars" "^3.0.0"
|
||||
|
||||
"@spectrum-css/picker@^1.0.0-beta.3":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.0.tgz#5758a128da081becd425b8d9433b24541f12b4b3"
|
||||
integrity sha512-aSoin2SVYl5W2R3nFp+V/Er6rAJUnwygO4E3g/tfDuImq8p5U3FKZj4sggSqfuD2U1PIwNSwX0D1RdxuGXsnUQ==
|
||||
|
||||
"@spectrum-css/popover@^3.0.0-beta.6":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.0.tgz#ec1ab86a66cc59bd522d3de2b7febe41e2a9fe46"
|
||||
integrity sha512-Lr2FZSJbDbDMp3bOlLtvDjOw6AwzRu3g0BbQ7NGK1l5MB06AhnqJX+TPB2iEDTfPdNyaDc5SCp55lBHP3RzHuw==
|
||||
|
||||
"@spectrum-css/stepper@^3.0.0-beta.7":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.0.tgz#ab5af818c86f2bc5050d0caee8b0a1c75201bfaf"
|
||||
integrity sha512-Gwvb4YLEBy/YtnFQ4aySnlve+pBrgPIm5LSq5IkeyjAKy7ZalQm9IIEkrVERHO1b+vbRZ6DW/aj2zYgzKgGMrA==
|
||||
|
||||
"@spectrum-css/textfield@^3.0.0-beta.6":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0.tgz#2f2d341b8d2c6f74e074b7e8df4a28307561cbbf"
|
||||
integrity sha512-ooXiSc5TZuZCFr3wl1JB60nS9FBBkGgqsml7kAS/7bOwRTCUPH7cY80SoaabRL8Z9Clml+K1Pa7I/r+Wphb53g==
|
||||
|
||||
"@spectrum-css/vars@^3.0.0", "@spectrum-css/vars@^3.0.0-beta.2":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0.tgz#c3ef4c2f07bd4f0d2734e730233ca81cb18106e7"
|
||||
integrity sha512-fNXU6qmcCbSiUoWGe/m9A8/THRHbpzwZ+iN8o/27tWIzcQIyZBZgjmV/kIMdF1dHpu5CuWik7mGV1Ex8tlzATg==
|
||||
|
||||
"@szmarczak/http-timer@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
|
||||
|
@ -1226,6 +1354,11 @@ adal-node@^0.1.28:
|
|||
xmldom ">= 0.1.x"
|
||||
xpath.js "~1.1.0"
|
||||
|
||||
ag-grid-community@^24.0.0:
|
||||
version "24.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-24.1.0.tgz#1e3cab51211822e08d56f03a491b7c0deaa398e6"
|
||||
integrity sha512-pWnWphuDcejZ8ahf6C734EpCx3XQ6dHEZWMWTlCdHNT0mZBLJ4YKCGACX+ttAEtSX2MGM3G13JncvuratUlYag==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
|
||||
|
@ -1563,6 +1696,18 @@ anymatch@~3.1.1:
|
|||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
apexcharts@^3.19.2, apexcharts@^3.22.1:
|
||||
version "3.25.0"
|
||||
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.25.0.tgz#f3f0f9f344f997230f5c7f2918408aa072627496"
|
||||
integrity sha512-uM7OF+jLL4ba79noYcrMwMgJW8DI+Ff28CCQoGq23g25z8nGSQEoU+u12YWlECA9gBA5tbmdaQhMxjlK+M6B9Q==
|
||||
dependencies:
|
||||
svg.draggable.js "^2.2.2"
|
||||
svg.easing.js "^2.0.0"
|
||||
svg.filter.js "^2.0.2"
|
||||
svg.pathmorphing.js "^0.1.3"
|
||||
svg.resize.js "^1.4.3"
|
||||
svg.select.js "^3.0.1"
|
||||
|
||||
app-builder-bin@3.5.10:
|
||||
version "3.5.10"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.10.tgz#4a7f9999fccc0c435b6284ae1366bc76a17c4a7d"
|
||||
|
@ -1621,6 +1766,11 @@ argparse@^1.0.10, argparse@^1.0.7:
|
|||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
args@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761"
|
||||
|
@ -2356,6 +2506,11 @@ clone-response@1.0.2, clone-response@^1.0.2:
|
|||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
clone@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
|
||||
co-body@^5.1.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124"
|
||||
|
@ -2489,6 +2644,11 @@ configstore@^5.0.1:
|
|||
write-file-atomic "^3.0.0"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
console-clear@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7"
|
||||
integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==
|
||||
|
||||
content-disposition@^0.5.2, content-disposition@~0.5.2:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
|
@ -2752,6 +2912,18 @@ decompress@^4.2.1:
|
|||
pify "^2.3.0"
|
||||
strip-dirs "^2.0.0"
|
||||
|
||||
deep-equal@^1.0.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
|
||||
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
|
||||
dependencies:
|
||||
is-arguments "^1.0.4"
|
||||
is-date-object "^1.0.1"
|
||||
is-regex "^1.0.4"
|
||||
object-is "^1.0.1"
|
||||
object-keys "^1.1.1"
|
||||
regexp.prototype.flags "^1.2.0"
|
||||
|
||||
deep-equal@^2.0.1:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9"
|
||||
|
@ -2944,6 +3116,11 @@ domexception@^1.0.1:
|
|||
dependencies:
|
||||
webidl-conversions "^4.0.2"
|
||||
|
||||
domino@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
|
||||
integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
|
||||
|
||||
dot-prop@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
||||
|
@ -3165,6 +3342,11 @@ ent@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
|
||||
integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
|
||||
|
||||
entities@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
|
||||
|
@ -3450,6 +3632,11 @@ event-target-shim@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||
|
||||
eventemitter3@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
|
||||
integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
|
||||
|
||||
events@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
|
@ -3551,7 +3738,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
|
|||
assign-symbols "^1.0.0"
|
||||
is-extendable "^1.0.1"
|
||||
|
||||
extend@^3.0.0, extend@~3.0.2:
|
||||
extend@^3.0.0, extend@^3.0.2, extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
@ -3621,6 +3808,11 @@ fast-deep-equal@^3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-diff@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
|
||||
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
|
@ -3800,6 +3992,11 @@ flat-cache@^2.0.1:
|
|||
rimraf "2.6.3"
|
||||
write "1.0.3"
|
||||
|
||||
flatpickr@^4.5.2, flatpickr@^4.6.6:
|
||||
version "4.6.9"
|
||||
resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499"
|
||||
integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==
|
||||
|
||||
flatstr@^1.0.12:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931"
|
||||
|
@ -3988,6 +4185,11 @@ get-object@^0.2.0:
|
|||
is-number "^2.0.2"
|
||||
isobject "^0.2.0"
|
||||
|
||||
get-port@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
|
||||
integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=
|
||||
|
||||
get-stream@3.0.0, get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
|
@ -4859,7 +5061,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
|
|||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-regex@^1.1.1, is-regex@^1.1.2:
|
||||
is-regex@^1.0.4, is-regex@^1.1.1, is-regex@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
|
||||
integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
|
||||
|
@ -5693,7 +5895,7 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
|||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
kleur@^3.0.3:
|
||||
kleur@^3.0.0, kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
@ -6002,6 +6204,13 @@ lines-and-columns@^1.1.6:
|
|||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
linkify-it@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
|
||||
integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==
|
||||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
load-bmfont@^1.3.1, load-bmfont@^1.4.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9"
|
||||
|
@ -6026,6 +6235,16 @@ load-json-file@^4.0.0:
|
|||
pify "^3.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
loadicons@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/loadicons/-/loadicons-1.0.0.tgz#79fd9b08ef2933988c94068cbd246ef3f21cbd04"
|
||||
integrity sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw==
|
||||
|
||||
local-access@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798"
|
||||
integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
|
@ -6280,6 +6499,17 @@ map-visit@^1.0.0:
|
|||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
markdown-it@^12.0.2:
|
||||
version "12.0.4"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.4.tgz#eec8247d296327eac3ba9746bdeec9cfcc751e33"
|
||||
integrity sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
entities "~2.1.0"
|
||||
linkify-it "^3.0.1"
|
||||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
matcher@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
|
||||
|
@ -6287,6 +6517,11 @@ matcher@^3.0.0:
|
|||
dependencies:
|
||||
escape-string-regexp "^4.0.0"
|
||||
|
||||
mdurl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
|
@ -6396,6 +6631,11 @@ mime@^1.3.4, mime@^1.4.1:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.3.1:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
|
||||
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
|
||||
|
||||
mime@^2.4.6:
|
||||
version "2.4.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||
|
@ -6473,6 +6713,11 @@ mri@1.1.4:
|
|||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
|
||||
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
|
||||
|
||||
mri@^1.1.0:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
|
||||
integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
@ -6755,7 +7000,7 @@ object-inspect@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
|
||||
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
|
||||
|
||||
object-is@^1.1.4:
|
||||
object-is@^1.0.1, object-is@^1.1.4:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
||||
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
|
||||
|
@ -6973,6 +7218,11 @@ pako@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||
|
||||
parchment@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5"
|
||||
integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
@ -7646,6 +7896,27 @@ quick-format-unescaped@^4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701"
|
||||
integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==
|
||||
|
||||
quill-delta@^3.6.2:
|
||||
version "3.6.3"
|
||||
resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
|
||||
integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
|
||||
dependencies:
|
||||
deep-equal "^1.0.1"
|
||||
extend "^3.0.2"
|
||||
fast-diff "1.1.2"
|
||||
|
||||
quill@^1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
|
||||
integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
|
||||
dependencies:
|
||||
clone "^2.1.1"
|
||||
deep-equal "^1.0.1"
|
||||
eventemitter3 "^2.0.3"
|
||||
extend "^3.0.2"
|
||||
parchment "^1.1.4"
|
||||
quill-delta "^3.6.2"
|
||||
|
||||
raw-body@^2.2.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||
|
@ -7822,7 +8093,7 @@ regex-not@^1.0.0, regex-not@^1.0.2:
|
|||
extend-shallow "^3.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
regexp.prototype.flags@^1.3.0:
|
||||
regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
|
||||
integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==
|
||||
|
@ -7869,6 +8140,11 @@ remarkable@^1.6.2, remarkable@^1.7.1:
|
|||
argparse "^1.0.10"
|
||||
autolinker "~0.28.0"
|
||||
|
||||
remixicon@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41"
|
||||
integrity sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww==
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
|
@ -8055,6 +8331,13 @@ rxjs@^6.6.0:
|
|||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
sade@^1.4.0:
|
||||
version "1.7.4"
|
||||
resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
|
||||
integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==
|
||||
dependencies:
|
||||
mri "^1.1.0"
|
||||
|
||||
safe-buffer@*, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
|
@ -8287,6 +8570,27 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
|
||||
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
|
||||
|
||||
sirv-cli@^0.4.6:
|
||||
version "0.4.6"
|
||||
resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04"
|
||||
integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww==
|
||||
dependencies:
|
||||
console-clear "^1.1.0"
|
||||
get-port "^3.2.0"
|
||||
kleur "^3.0.0"
|
||||
local-access "^1.0.1"
|
||||
sade "^1.4.0"
|
||||
sirv "^0.4.6"
|
||||
tinydate "^1.0.0"
|
||||
|
||||
sirv@^0.4.6:
|
||||
version "0.4.6"
|
||||
resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22"
|
||||
integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==
|
||||
dependencies:
|
||||
"@polka/url" "^0.5.0"
|
||||
mime "^2.3.1"
|
||||
|
||||
sisteransi@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
|
||||
|
@ -8749,6 +9053,32 @@ supports-color@^7.1.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
svelte-apexcharts@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1"
|
||||
integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA==
|
||||
dependencies:
|
||||
apexcharts "^3.19.2"
|
||||
|
||||
svelte-flatpickr@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-2.4.0.tgz#190871fc3305956c8c8fd3601cd036b8ac71ef49"
|
||||
integrity sha512-UUC5Te+b0qi4POg7VDwfGh0m5W3Hf64OwkfOTj6FEe/dYZN4cBzpQ82EuuQl0CTbbBAsMkcjJcixV1d2V6EHCQ==
|
||||
dependencies:
|
||||
flatpickr "^4.5.2"
|
||||
|
||||
svelte-flatpickr@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.1.0.tgz#ad83588430dbd55196a1a258b8ba27e7f9c1ee37"
|
||||
integrity sha512-zKyV+ukeVuJ8CW0Ing3T19VSekc4bPkou/5Riutt1yATrLvSsanNqcgqi7Q5IePvIoOF9GJ5OtHvn1qK9Wx9BQ==
|
||||
dependencies:
|
||||
flatpickr "^4.5.2"
|
||||
|
||||
svelte-portal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3"
|
||||
integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q==
|
||||
|
||||
svelte-spa-router@^3.0.5:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.1.0.tgz#a929f0def7e12c41f32bc356f91685aeadcd75bf"
|
||||
|
@ -8761,6 +9091,61 @@ svelte@3.30.0:
|
|||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.30.0.tgz#cbde341e96bf34f4ac73c8f14f8a014e03bfb7d6"
|
||||
integrity sha512-z+hdIACb9TROGvJBQWcItMtlr4s0DBUgJss6qWrtFkOoIInkG+iAMo/FJZQFyDBQZc+dul2+TzYSi/tpTT5/Ag==
|
||||
|
||||
svg.draggable.js@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba"
|
||||
integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==
|
||||
dependencies:
|
||||
svg.js "^2.0.1"
|
||||
|
||||
svg.easing.js@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12"
|
||||
integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=
|
||||
dependencies:
|
||||
svg.js ">=2.3.x"
|
||||
|
||||
svg.filter.js@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203"
|
||||
integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=
|
||||
dependencies:
|
||||
svg.js "^2.2.5"
|
||||
|
||||
svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d"
|
||||
integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==
|
||||
|
||||
svg.pathmorphing.js@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65"
|
||||
integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==
|
||||
dependencies:
|
||||
svg.js "^2.4.0"
|
||||
|
||||
svg.resize.js@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332"
|
||||
integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==
|
||||
dependencies:
|
||||
svg.js "^2.6.5"
|
||||
svg.select.js "^2.1.2"
|
||||
|
||||
svg.select.js@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73"
|
||||
integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==
|
||||
dependencies:
|
||||
svg.js "^2.2.5"
|
||||
|
||||
svg.select.js@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917"
|
||||
integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==
|
||||
dependencies:
|
||||
svg.js "^2.6.5"
|
||||
|
||||
symbol-tree@^3.2.2:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
|
@ -8940,6 +9325,11 @@ tinycolor2@^1.4.1:
|
|||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
|
||||
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
|
||||
|
||||
tinydate@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb"
|
||||
integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
|
@ -9083,6 +9473,13 @@ tunnel@0.0.6, tunnel@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
turndown@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.0.0.tgz#19b2a6a2d1d700387a1e07665414e4af4fec5225"
|
||||
integrity sha512-G1FfxfR0mUNMeGjszLYl3kxtopC4O9DRRiMlMDDVHvU1jaBkGFg4qxIyjIk2aiKLHyDyZvZyu4qBO2guuYBy3Q==
|
||||
dependencies:
|
||||
domino "^2.1.6"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
@ -9142,6 +9539,11 @@ typeof-article@^0.1.1:
|
|||
dependencies:
|
||||
kind-of "^3.1.0"
|
||||
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.13.0"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.0.tgz#66ed69f7241f33f13531d3d51d5bcebf00df7f69"
|
||||
|
|
|
@ -8,55 +8,81 @@
|
|||
</script>
|
||||
|
||||
{#if type === 'div'}
|
||||
<div in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<div
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</div>
|
||||
{:else if type === 'header'}
|
||||
<header in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<header
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</header>
|
||||
{:else if type === 'main'}
|
||||
<main in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<main
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</main>
|
||||
{:else if type === 'footer'}
|
||||
<footer in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<footer
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</footer>
|
||||
{:else if type === 'aside'}
|
||||
<aside in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<aside
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</aside>
|
||||
{:else if type === 'summary'}
|
||||
<summary in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<summary
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</summary>
|
||||
{:else if type === 'details'}
|
||||
<details in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<details
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</details>
|
||||
{:else if type === 'article'}
|
||||
<article in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<article
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</article>
|
||||
{:else if type === 'nav'}
|
||||
<nav in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<nav
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</nav>
|
||||
{:else if type === 'mark'}
|
||||
<mark in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<mark
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</mark>
|
||||
{:else if type === 'figure'}
|
||||
<figure in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<figure
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</figure>
|
||||
{:else if type === 'figcaption'}
|
||||
<figcaption in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<figcaption
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</figcaption>
|
||||
{:else if type === 'paragraph'}
|
||||
<p in:transition={{type: $component.transition}} use:styleable={$component.styles}>
|
||||
<p
|
||||
in:transition={{ type: $component.transition }}
|
||||
use:styleable={$component.styles}>
|
||||
<slot />
|
||||
</p>
|
||||
{/if}
|
||||
|
|
|
@ -152,7 +152,9 @@
|
|||
{#if selectedRows.length > 0}
|
||||
<DeleteButton text small on:click={modal.show()}>
|
||||
<Icon name="addrow" />
|
||||
Delete {selectedRows.length} row(s)
|
||||
Delete
|
||||
{selectedRows.length}
|
||||
row(s)
|
||||
</DeleteButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue