Merge branch 'spectrum-bbui' of github.com:Budibase/budibase into spectrum-bbui-forms

This commit is contained in:
Andrew Kingston 2021-04-16 08:08:52 +01:00
commit b823f2bed1
21 changed files with 384 additions and 2698 deletions

View File

@ -51,9 +51,13 @@
"@spectrum-css/modal": "^3.0.1", "@spectrum-css/modal": "^3.0.1",
"@spectrum-css/picker": "^1.0.1", "@spectrum-css/picker": "^1.0.1",
"@spectrum-css/popover": "^3.0.1", "@spectrum-css/popover": "^3.0.1",
"@spectrum-css/progressbar": "^1.0.2",
"@spectrum-css/progresscircle": "^1.0.2",
"@spectrum-css/table": "^3.0.1", "@spectrum-css/table": "^3.0.1",
"@spectrum-css/textfield": "^3.0.1", "@spectrum-css/textfield": "^3.0.1",
"@spectrum-css/tabs": "^3.0.1",
"@spectrum-css/toast": "^3.0.1", "@spectrum-css/toast": "^3.0.1",
"@spectrum-css/typography": "^3.0.1",
"@spectrum-css/underlay": "^2.0.9", "@spectrum-css/underlay": "^2.0.9",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",

View File

@ -0,0 +1,14 @@
<script>
import "@spectrum-css/divider/dist/index-vars.css"
export let l = false
export let m = false
export let s = false
export let vertical = false
</script>
<hr
class:spectrum-Divider--sizeL={l}
class:spectrum-Divider--sizeM={m}
class:spectrum-Divider--sizeS={s}
class="spectrum-Divider spectrum-Divider--{vertical ? 'vertical' : 'horizontal'} spectrum-Dialog-divider">

View File

@ -1,8 +1,7 @@
<script> <script>
import "@spectrum-css/dialog/dist/index-vars.css"
import "@spectrum-css/modal/dist/index-vars.css" import "@spectrum-css/modal/dist/index-vars.css"
import "@spectrum-css/underlay/dist/index-vars.css" import "@spectrum-css/underlay/dist/index-vars.css"
import { createEventDispatcher, setContext } from "svelte" import { createEventDispatcher, setContext, tick } from "svelte"
import { fade, fly } from "svelte/transition" import { fade, fly } from "svelte/transition"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import Context from "../context" import Context from "../context"
@ -31,6 +30,14 @@
} }
} }
async function focusFirstInput(node) {
const inputs = node.querySelectorAll("input")
if (inputs) {
await tick()
inputs[0].focus()
}
}
setContext(Context.Modal, { show, hide }) setContext(Context.Modal, { show, hide })
</script> </script>
@ -45,6 +52,7 @@
<div class="modal-wrapper" on:click|self={hide}> <div class="modal-wrapper" on:click|self={hide}>
<div class="modal-inner-wrapper" on:click|self={hide}> <div class="modal-inner-wrapper" on:click|self={hide}>
<div <div
use:focusFirstInput
class="spectrum-Modal is-open" class="spectrum-Modal is-open"
transition:fly={{ y: 30, duration: 200 }}> transition:fly={{ y: 30, duration: 200 }}>
<slot /> <slot />

View File

@ -1,8 +1,8 @@
<script> <script>
import "@spectrum-css/divider/dist/index-vars.css"
import { getContext } from "svelte" import { getContext } from "svelte"
import Button from "../Button/Button.svelte" import Button from "../Button/Button.svelte"
import Heading from "../Typography/Heading.svelte"
import Divider from "../Divider/Divider.svelte"
import Icon from "../Icons/Icon.svelte" import Icon from "../Icons/Icon.svelte"
import Context from "../context" import Context from "../context"
@ -35,10 +35,8 @@
tabindex="-1" tabindex="-1"
aria-modal="true"> aria-modal="true">
<div class="spectrum-Dialog-grid"> <div class="spectrum-Dialog-grid">
<h1 class="spectrum-Dialog-heading">{title}</h1> <Heading m h2>{title}</Heading>
<hr <Divider m />
class="spectrum-Divider spectrum-Divider--sizeS spectrum-Divider--horizontal spectrum-Dialog-divider" />
<!-- TODO: Remove content-grid class once Layout components are in bbui --> <!-- TODO: Remove content-grid class once Layout components are in bbui -->
<section class="spectrum-Dialog-content content-grid"> <section class="spectrum-Dialog-content content-grid">
<slot /> <slot />

View File

@ -0,0 +1,38 @@
<script>
import "@spectrum-css/progressbar/dist/index-vars.css"
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let value = false
export let easing = cubicOut
export let duration = 1000;
export let width = false;
export let sideLabel = false
export let overBackground = false
export let s = false;
export let m = false;
export let l = false;
export let xl = false;
const progress = tweened(0, {
duration: duration,
easing: easing
});
$: if (value) $progress = value
</script>
<div class:spectrum-ProgressBar--indeterminate={!value} class:spectrum-ProgressBar--sideLabel={sideLabel} class:spectrum-ProgressBar--sizeS={s} class:spectrum-ProgressBar--sizeM={m} class:spectrum-ProgressBar--sizeL={l} class:spectrum-ProgressBar--sizeXL={xl} class="spectrum-ProgressBar" value={$progress} role="progressbar" aria-valuenow={$progress} aria-valuemin="0" aria-valuemax="100" style={width ? `width: ${width}px;` : ''}>
{#if $$slots}
<div class:spectrum-FieldLabel--sizeS={s} class:spectrum-FieldLabel--sizeM={m} class:spectrum-FieldLabel--sizeL={l} class:spectrum-FieldLabel--sizeXL={xl} class="spectrum-FieldLabel spectrum-ProgressBar-label"><slot /></div>
{/if}
{#if value}
<div class:spectrum-FieldLabel--sizeS={s} class:spectrum-FieldLabel--sizeM={m} class:spectrum-FieldLabel--sizeL={l} class:spectrum-FieldLabel--sizeXL={xl} class="spectrum-FieldLabel spectrum-ProgressBar-percentage">{Math.round($progress)}%</div>
{/if}
<div class="spectrum-ProgressBar-track">
<div class="spectrum-ProgressBar-fill" style={value ? `width: ${$progress}%` : ''}></div>
</div>
<div class="spectrum-ProgressBar-label" hidden=""></div>
</div>

View File

@ -0,0 +1,29 @@
<script>
// WIP! Does not yet work.
import "@spectrum-css/progresscircle/dist/index-vars.css"
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
export let value = false
export let small;
export let large;
export let overBackground;
</script>
<div class:spectrum-ProgressBar--indeterminate={!value} class:spectrum-ProgressCircle--small={small} class:spectrum-ProgressCircle--large={large} class="spectrum-ProgressCircle">
<div class="spectrum-ProgressCircle-track"></div>
<div class="spectrum-ProgressCircle-fills">
<div class="spectrum-ProgressCircle-fillMask1">
<div class="spectrum-ProgressCircle-fillSubMask1">
<div class="spectrum-ProgressCircle-fill"></div>
</div>
</div>
<div class="spectrum-ProgressCircle-fillMask2">
<div class="spectrum-ProgressCircle-fillSubMask2">
<div class="spectrum-ProgressCircle-fill"></div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<script>
import { getContext, onMount } from 'svelte'
import Portal from "svelte-portal"
export let title
export let icon = '';
const selected = getContext('tab')
let tab;
let tabInfo
const setTabInfo = () => {
tabInfo = tab.getBoundingClientRect()
if ($selected.title === title) {
$selected.info = tabInfo
}
}
onMount(() => {
setTabInfo()
})
</script>
<div bind:this={tab} on:click={() => $selected = {...$selected, title, info: tab.getBoundingClientRect()} } class:is-selected={$selected.title === title} class="spectrum-Tabs-item" tabindex="0">
{#if icon}
<svg class="spectrum-Icon spectrum-Icon--sizeM" focusable="false" aria-hidden="true" aria-label="Folder">
<use xlink:href="#spectrum-icon-18-{icon}" />
</svg>
{/if}
<span class="spectrum-Tabs-itemLabel">{title}</span>
</div>
{#if $selected.title === title}
<Portal target=".spectrum-Tabs-content-{$selected.id}">
<slot />
</Portal>
{/if}

View File

@ -0,0 +1,71 @@
<script>
import "@spectrum-css/tabs/dist/index-vars.css"
import { writable } from 'svelte/store'
import { onMount, setContext, createEventDispatcher } from 'svelte'
export let selected;
export let vertical = false
let _id = id()
const tab = writable({title: selected, id: _id})
setContext('tab', tab)
let container;
const dispatch = createEventDispatcher()
$: selected = $tab.title
$: selected = dispatch('select', selected)
let top, left, width, height;
$: calculateIndicatorLength($tab)
$: calculateIndicatorOffset($tab)
function calculateIndicatorLength() {
if (!vertical) {
width = $tab.info?.width + 24 + 'px'
height = $tab.info?.height
} else {
height = $tab.info?.height + 4 + 'px'
width = $tab.info?.width
}
}
function calculateIndicatorOffset() {
if (!vertical) {
left = $tab.info?.left - container?.getBoundingClientRect().left - 12 + 'px'
top = $tab.info?.top
} else {
top = $tab.info?.top - container?.getBoundingClientRect().top + 'px'
left = $tab.info?.left
}
}
onMount(() => {
calculateIndicatorLength()
calculateIndicatorOffset()
})
function id() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
)
}
</script>
<div bind:this={container} class="selected-border spectrum-Tabs spectrum-Tabs--{vertical ? 'vertical' : 'horizontal'}">
<slot />
<div class="spectrum-Tabs-selectionIndicator indicator-transition" style="width: {width}; height: {height}; left: {left}; top: {top};"></div>
</div>
<div class="spectrum-Tabs-content spectrum-Tabs-content-{_id}" />
<style>
.spectrum-Tabs-content {
margin-top: var(--spectrum-global-dimension-static-size-150);
}
.indicator-transition {
transition: all 200ms
}
</style>

View File

@ -0,0 +1,29 @@
<script>
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xxxl = false;
export let xxl = false;
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
export let xxs = false;
export let serif = false;
</script>
<p class="spectrum-Body"
class:spectrum-Body--serif={serif}
class:spectrum-Body--sizeXXXL={xxxl}
class:spectrum-Body--sizeXXL={xxl}
class:spectrum-Body--sizeXL={xl}
class:spectrum-Body--sizeL={l}
class:spectrum-Body--sizeM={m}
class:spectrum-Body--sizeS={s}
class:spectrum-Body--sizeXS={xs}
class:spectrum-Body--sizeXXS={xxs}>
<slot />
</p>

View File

@ -0,0 +1,20 @@
<script>
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
</script>
<code class="spectrum-Code"
class:spectrum-Code--sizeXL={xl}
class:spectrum-Code--sizeL={l}
class:spectrum-Code--sizeM={m}
class:spectrum-Code--sizeS={s}
class:spectrum-Code--sizeXS={xs}>
<slot />
</code>

View File

@ -0,0 +1,21 @@
<script>
import "@spectrum-css/typography/dist/index-vars.css"
// Sizes
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let serif = false;
</script>
<p class="spectrum-Detail"
class:spectrum-Detail--serif={serif}
class:spectrum-Detail--sizeXL={xl}
class:spectrum-Detail--sizeL={l}
class:spectrum-Detail--sizeM={m}
class:spectrum-Detail--sizeS={s}>
<slot />
</p>

View File

@ -0,0 +1,78 @@
<script>
import "@spectrum-css/typography/dist/index-vars.css"
// Level
export let h1 = false;
export let h2 = false;
export let h3 = false;
export let h4 = false;
// Sizes
export let xxxl = false;
export let xxl = false;
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
export let xxs = false;
export let serif = false;
</script>
{#if h1}
<h1 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h1>
{:else if h2}
<h2 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h2>
{:else if h3}
<h3 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h3>
{:else if h4}
<h4 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h4>
{:else}
SPECIFY HEADING SIZE!
{/if}

View File

@ -21,8 +21,8 @@ export { default as DetailSummary } from "./List/Items/DetailSummary.svelte"
export { default as Switcher } from "./Switcher/Switcher.svelte" export { default as Switcher } from "./Switcher/Switcher.svelte"
export { default as DropdownMenu } from "./DropdownMenu/DropdownMenu.svelte" export { default as DropdownMenu } from "./DropdownMenu/DropdownMenu.svelte"
export { default as Popover } from "./Popover/Popover.svelte" export { default as Popover } from "./Popover/Popover.svelte"
export { default as Body } from "./Styleguide/Body.svelte" export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
export { default as Heading } from "./Styleguide/Heading.svelte" export { default as ProgressCircle } from "./ProgressCircle/ProgressCircle.svelte"
export { default as Label } from "./Styleguide/Label.svelte" export { default as Label } from "./Styleguide/Label.svelte"
export { default as Link } from "./Link/Link.svelte" export { default as Link } from "./Link/Link.svelte"
export { default as Close } from "./Button/Close.svelte" export { default as Close } from "./Button/Close.svelte"
@ -39,6 +39,16 @@ export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Slider } from "./Form/Slider.svelte" export { default as Slider } from "./Form/Slider.svelte"
export { default as Context } from "./context" export { default as Context } from "./context"
export { default as Table } from "./Table/Table.svelte" export { default as Table } from "./Table/Table.svelte"
export { default as Tabs } from "./Tabs/Tabs.svelte"
export { default as Tab } from "./Tabs/Tab.svelte"
export { default as Divider } from "./Divider/Divider.svelte"
// Typography
export { default as Body } from "./Typography/Body.svelte"
export { default as Heading } from "./Typography/Heading.svelte"
export { default as Detail } from "./Typography/Detail.svelte"
export { default as Code } from "./Typography/Code.svelte"
// Core form components to be used elsewhere (standard components) // Core form components to be used elsewhere (standard components)
export * from "./Form/Core" export * from "./Form/Core"

File diff suppressed because it is too large Load Diff

View File

@ -46,12 +46,12 @@
<div class="container"> <div class="container">
<div class="list"> <div class="list">
<Heading small>Available bindings</Heading> <Heading s h3>Available bindings</Heading>
<Spacer medium /> <Spacer medium />
<Input extraThin placeholder="Search" bind:value={search} /> <Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium /> <Spacer medium />
{#each categories as [categoryName, bindings]} {#each categories as [categoryName, bindings]}
<Heading extraSmall>{categoryName}</Heading> <Heading xs h4>{categoryName}</Heading>
<Spacer extraSmall /> <Spacer extraSmall />
{#each bindableProperties.filter(binding => {#each bindableProperties.filter(binding =>
binding.label.match(searchRgx) binding.label.match(searchRgx)
@ -64,7 +64,7 @@
</div> </div>
{/each} {/each}
{/each} {/each}
<Heading extraSmall>Helpers</Heading> <Heading xs h3>Helpers</Heading>
<Spacer extraSmall /> <Spacer extraSmall />
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper} {#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
<div class="binding" on:click={() => addToText(helper)}> <div class="binding" on:click={() => addToText(helper)}>

View File

@ -63,7 +63,7 @@
<Input extraThin placeholder="Search" bind:value={search} /> <Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium /> <Spacer medium />
{#if context} {#if context}
<Heading extraSmall>Columns</Heading> <Heading xs h3>Columns</Heading>
<Spacer small /> <Spacer small />
<ul> <ul>
{#each context.filter(context => {#each context.filter(context =>
@ -77,7 +77,7 @@
{/if} {/if}
<Spacer small /> <Spacer small />
{#if instance} {#if instance}
<Heading extraSmall>Components</Heading> <Heading xs h3>Components</Heading>
<Spacer small /> <Spacer small />
<ul> <ul>
{#each instance.filter(instance => {#each instance.filter(instance =>
@ -90,7 +90,7 @@
</ul> </ul>
{/if} {/if}
<Spacer small /> <Spacer small />
<Heading extraSmall>Helpers</Heading> <Heading xs h3>Helpers</Heading>
<Spacer small /> <Spacer small />
<ul> <ul>
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper} {#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}

View File

@ -137,7 +137,7 @@
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}> <DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
<div class="dropdown"> <div class="dropdown">
<div class="title"> <div class="title">
<Heading extraSmall>Tables</Heading> <Heading xs h3>Tables</Heading>
</div> </div>
<ul> <ul>
{#each tables as table} {#each tables as table}
@ -150,7 +150,7 @@
</ul> </ul>
<hr /> <hr />
<div class="title"> <div class="title">
<Heading extraSmall>Views</Heading> <Heading xs h3>Views</Heading>
</div> </div>
<ul> <ul>
{#each views as view} {#each views as view}

View File

@ -26,7 +26,7 @@
</script> </script>
<div class="apps-card"> <div class="apps-card">
<Heading small black>{name}</Heading> <Heading s h3>{name}</Heading>
<Spacer medium /> <Spacer medium />
<div class="card-footer" data-cy={`app-${name}`}> <div class="card-footer" data-cy={`app-${name}`}>
<ActionButton text medium blue on:click={() => $goto(`/builder/${_id}`)}> <ActionButton text medium blue on:click={() => $goto(`/builder/${_id}`)}>

View File

@ -1,6 +1,6 @@
<script> <script>
import AppCard from "./AppCard.svelte" import AppCard from "./AppCard.svelte"
import { Heading } from "@budibase/bbui" import { Heading, Divider } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
@ -19,23 +19,18 @@
</script> </script>
<div class="root"> <div class="root">
<Heading lh medium black>Your Apps</Heading> <Heading m h2>Your Apps</Heading>
<Divider s />
{#await promise} {#await promise}
<div class="spinner-container"> <div class="spinner-container">
<Spinner size="30" /> <Spinner size="30" />
</div> </div>
{:then apps} {:then apps}
<div class="inner">
<div>
<div>
<div class="apps"> <div class="apps">
{#each apps as app} {#each apps as app}
<AppCard {...app} /> <AppCard {...app} />
{/each} {/each}
</div> </div>
</div>
</div>
</div>
{:catch err} {:catch err}
<h1 style="color:red">{err}</h1> <h1 style="color:red">{err}</h1>
{/await} {/await}
@ -43,6 +38,7 @@
<style> <style>
.apps { .apps {
margin-top: var(--spectrum-global-dimension-static-size-150);
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-gap: var(--layout-m); grid-gap: var(--layout-m);

View File

@ -3,7 +3,7 @@
import AppList from "components/start/AppList.svelte" import AppList from "components/start/AppList.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
import CreateAppModal from "components/start/CreateAppModal.svelte" import CreateAppModal from "components/start/CreateAppModal.svelte"
import { Button, Heading, Modal, Spacer, Menu, MenuSection, MenuItem } from "@budibase/bbui" import { Button, Heading, Modal, Spacer, Divider } from "@budibase/bbui"
import TemplateList from "components/start/TemplateList.svelte" import TemplateList from "components/start/TemplateList.svelte"
import analytics from "analytics" import analytics from "analytics"
import Banner from "/assets/orange-landscape.png" import Banner from "/assets/orange-landscape.png"
@ -57,7 +57,7 @@
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<Heading medium black>Welcome to the Budibase Beta</Heading> <Heading m h1>Welcome to the Budibase Beta</Heading>
<div class="button-group"> <div class="button-group">
<Button secondary on:click={initiateAppImport}>Import Web App</Button> <Button secondary on:click={initiateAppImport}>Import Web App</Button>
<Spacer medium /> <Spacer medium />

View File

@ -18,58 +18,5 @@ function hasResource(ctx) {
} }
module.exports = (permType, permLevel = null) => async (ctx, next) => { module.exports = (permType, permLevel = null) => async (ctx, next) => {
if (env.isProd() && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) {
// api key header passed by external webhook
if (await isAPIKeyValid(ctx.headers["x-api-key"])) {
ctx.auth = {
authenticated: AuthTypes.EXTERNAL,
apiKey: ctx.headers["x-api-key"],
}
ctx.user = {
appId: ctx.headers["x-instanceid"],
}
return next()
}
return ctx.throw(403, "API key invalid")
}
if (!ctx.user) {
return ctx.throw(403, "No user info found")
}
const role = ctx.user.role
const isAdmin = ADMIN_ROLES.includes(role._id)
const isAuthed = ctx.auth.authenticated
const { basePermissions, permissions } = await getUserPermissions(
ctx.appId,
role._id
)
// this may need to change in the future, right now only admins
// can have access to builder features, this is hard coded into
// our rules
if (isAdmin && isAuthed) {
return next()
} else if (permType === PermissionTypes.BUILDER) {
return ctx.throw(403, "Not Authorized")
}
if (
hasResource(ctx) &&
doesHaveResourcePermission(permissions, permLevel, ctx)
) {
return next()
}
if (!isAuthed) {
ctx.throw(403, "Session not authenticated")
}
if (!doesHaveBasePermission(permType, permLevel, basePermissions)) {
ctx.throw(403, "User does not have permission")
}
return next() return next()
} }