mirror of https://github.com/Budibase/budibase.git
70 changed files with 1951 additions and 692 deletions
|
After Width: | Height: | Size: 292 B |
|
After Width: | Height: | Size: 271 B |
@ -0,0 +1,68 @@ |
|||||
|
<script> |
||||
|
import Button from "components/common/Button.svelte" |
||||
|
export let name, |
||||
|
description = `A minimalist CRM which removes the noise and allows you to focus |
||||
|
on your business.`, |
||||
|
_id |
||||
|
</script> |
||||
|
|
||||
|
<div class="apps-card"> |
||||
|
<h3 class="app-title">{name}</h3> |
||||
|
<p class="app-desc">{description}</p> |
||||
|
<div class="card-footer"> |
||||
|
<div class="modified-date">Last Edited - 25th May 2020</div> |
||||
|
<a href={`/_builder/${_id}`} class="app-button">Open Web App</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.apps-card { |
||||
|
background-color: var(--white); |
||||
|
padding: 20px; |
||||
|
max-width: 400px; |
||||
|
max-height: 150px; |
||||
|
border-radius: 5px; |
||||
|
border: 1px solid var(--grey-medium); |
||||
|
} |
||||
|
|
||||
|
.app-button:hover { |
||||
|
background-color: var(--grey-light); |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
.app-title { |
||||
|
font-size: 18px; |
||||
|
font-weight: 700; |
||||
|
color: var(--ink); |
||||
|
text-transform: capitalize; |
||||
|
} |
||||
|
|
||||
|
.app-desc { |
||||
|
color: var(--ink-light); |
||||
|
} |
||||
|
|
||||
|
.card-footer { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: baseline; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.modified-date { |
||||
|
font-size: 14px; |
||||
|
color: var(--ink-light); |
||||
|
} |
||||
|
|
||||
|
.app-button { |
||||
|
background-color: var(--white); |
||||
|
color: var(--ink); |
||||
|
padding: 12px 20px; |
||||
|
border-radius: 5px; |
||||
|
border: 1px var(--grey) solid; |
||||
|
font-size: 14px; |
||||
|
font-weight: 400; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.2s; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,197 @@ |
|||||
|
<script> |
||||
|
import Spinner from "components/common/Spinner.svelte" |
||||
|
import { Input, TextArea, Button } from "@budibase/bbui" |
||||
|
import { goto } from "@sveltech/routify" |
||||
|
import { AppsIcon, InfoIcon, CloseIcon } from "components/common/Icons/" |
||||
|
import { getContext } from "svelte" |
||||
|
import { fade } from "svelte/transition" |
||||
|
|
||||
|
const { open, close } = getContext("simple-modal") |
||||
|
|
||||
|
let name = "" |
||||
|
let description = "" |
||||
|
let loading = false |
||||
|
let error = {} |
||||
|
|
||||
|
const createNewApp = async () => { |
||||
|
if ((name.length > 100 || name.length < 1) && description.length < 1) { |
||||
|
error = { |
||||
|
name: true, |
||||
|
description: true, |
||||
|
} |
||||
|
} else if (description.length < 1) { |
||||
|
error = { |
||||
|
name: false, |
||||
|
description: true, |
||||
|
} |
||||
|
} else if (name.length > 100 || name.length < 1) { |
||||
|
error = { |
||||
|
name: true, |
||||
|
} |
||||
|
} else { |
||||
|
error = {} |
||||
|
const data = { name, description } |
||||
|
loading = true |
||||
|
try { |
||||
|
const response = await fetch("/api/applications", { |
||||
|
method: "POST", // *GET, POST, PUT, DELETE, etc. |
||||
|
credentials: "same-origin", // include, *same-origin, omit |
||||
|
headers: { |
||||
|
"Content-Type": "application/json", |
||||
|
// 'Content-Type': 'application/x-www-form-urlencoded', |
||||
|
}, |
||||
|
body: JSON.stringify(data), // body data type must match "Content-Type" header |
||||
|
}) |
||||
|
|
||||
|
const res = await response.json() |
||||
|
|
||||
|
$goto(`./${res._id}`) |
||||
|
} catch (error) { |
||||
|
console.error(error) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let value |
||||
|
let onChange = () => {} |
||||
|
|
||||
|
function _onCancel() { |
||||
|
close() |
||||
|
} |
||||
|
|
||||
|
async function _onOkay() { |
||||
|
await createNewApp() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="container"> |
||||
|
<div class="body"> |
||||
|
<div class="heading"> |
||||
|
<span class="icon"> |
||||
|
<AppsIcon /> |
||||
|
</span> |
||||
|
<h3>Create new web app</h3> |
||||
|
</div> |
||||
|
<Input |
||||
|
name="name" |
||||
|
label="Name" |
||||
|
placeholder="Enter application name" |
||||
|
on:change={e => (name = e.target.value)} |
||||
|
on:input={e => (name = e.target.value)} /> |
||||
|
{#if error.name} |
||||
|
<span class="error">You need to enter a name for your application.</span> |
||||
|
{/if} |
||||
|
<TextArea |
||||
|
bind:value={description} |
||||
|
name="description" |
||||
|
label="Description" |
||||
|
placeholder="Describe your application" /> |
||||
|
{#if error.description} |
||||
|
<span class="error"> |
||||
|
Please enter a short description of your application |
||||
|
</span> |
||||
|
{/if} |
||||
|
</div> |
||||
|
<div class="footer"> |
||||
|
<a href="./#" class="info"> |
||||
|
<InfoIcon /> |
||||
|
How to get started |
||||
|
</a> |
||||
|
<Button outline thin on:click={_onCancel}>Cancel</Button> |
||||
|
<Button primary thin on:click={_onOkay}>Save</Button> |
||||
|
</div> |
||||
|
<div class="close-button" on:click={_onCancel}> |
||||
|
<CloseIcon /> |
||||
|
</div> |
||||
|
{#if loading} |
||||
|
<div in:fade class="spinner-container"> |
||||
|
<Spinner /> |
||||
|
<span class="spinner-text">Creating your app...</span> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.container { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.close-button { |
||||
|
cursor: pointer; |
||||
|
position: absolute; |
||||
|
top: 20px; |
||||
|
right: 20px; |
||||
|
} |
||||
|
.close-button :global(svg) { |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
} |
||||
|
.heading { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
h3 { |
||||
|
margin: 0; |
||||
|
font-size: 24px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
.icon { |
||||
|
display: grid; |
||||
|
border-radius: 3px; |
||||
|
align-content: center; |
||||
|
justify-content: center; |
||||
|
margin-right: 12px; |
||||
|
height: 20px; |
||||
|
width: 20px; |
||||
|
padding: 10px; |
||||
|
background-color: var(--blue-light); |
||||
|
} |
||||
|
.info { |
||||
|
color: var(--primary100); |
||||
|
text-decoration-color: var(--primary100); |
||||
|
} |
||||
|
.info :global(svg) { |
||||
|
fill: var(--primary100); |
||||
|
margin-right: 8px; |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
} |
||||
|
.body { |
||||
|
padding: 40px 40px 80px 40px; |
||||
|
display: grid; |
||||
|
grid-gap: 20px; |
||||
|
} |
||||
|
.footer { |
||||
|
display: grid; |
||||
|
grid-gap: 20px; |
||||
|
align-items: center; |
||||
|
grid-template-columns: 1fr auto auto; |
||||
|
padding: 30px 40px; |
||||
|
border-bottom-left-radius: 5px; |
||||
|
border-bottom-right-radius: 50px; |
||||
|
background-color: var(--grey-light); |
||||
|
} |
||||
|
.spinner-container { |
||||
|
background: white; |
||||
|
position: absolute; |
||||
|
border-radius: 5px; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
display: grid; |
||||
|
justify-items: center; |
||||
|
align-content: center; |
||||
|
grid-gap: 50px; |
||||
|
} |
||||
|
.spinner-text { |
||||
|
font-size: 2em; |
||||
|
} |
||||
|
.error { |
||||
|
color: var(--deletion100); |
||||
|
font-weight: bold; |
||||
|
font-size: 0.8em; |
||||
|
} |
||||
|
</style> |
||||
@ -1,66 +1,56 @@ |
|||||
<script> |
<script> |
||||
|
import { fly } from "svelte/transition" |
||||
export let item |
export let item |
||||
</script> |
</script> |
||||
|
|
||||
<div class="item-item" on:click> |
<div class="item-item" transition:fly={{ y: 100, duration: 1000 }} on:click> |
||||
<div class="item-icon"> |
<div class="item-icon"> |
||||
<i class={item.icon} /> |
<i class={item.icon} /> |
||||
</div> |
</div> |
||||
<div class="item-text"> |
<div class="item-text"> |
||||
<div class="item-name">{item.name}</div> |
<div class="item-name">{item.name}</div> |
||||
<div class="item-description"> |
|
||||
<p>{item.description}</p> |
|
||||
</div> |
|
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
|
|
||||
<style> |
<style> |
||||
.item-item { |
.item-item { |
||||
display: flex; |
display: flex; |
||||
flex-direction: row; |
flex-direction: column; |
||||
padding: 10px 0px 8px 10px; |
|
||||
align-items: center; |
|
||||
cursor: pointer; |
cursor: pointer; |
||||
|
margin-bottom: 8px; |
||||
|
padding: 8px 0px 16px 0px; |
||||
|
width: 120px; |
||||
|
height: 80px; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin-right: 8px; |
||||
|
background-color: var(--grey-light); |
||||
|
border-radius: 3px; |
||||
} |
} |
||||
|
|
||||
.item-item:hover { |
.item-item:hover { |
||||
background: #fbfbfb; |
background: var(--grey); |
||||
border-radius: 5px; |
border-radius: 3px; |
||||
|
transition: all 0.2s; |
||||
} |
} |
||||
|
|
||||
.item-icon { |
.item-icon { |
||||
flex: 0 0 40px; |
border-radius: 3px; |
||||
background: #f1f4fc; |
|
||||
height: 40px; |
|
||||
border-radius: 5px; |
|
||||
display: flex; |
display: flex; |
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
} |
} |
||||
|
|
||||
.item-text { |
.item-text { |
||||
display: flex; |
display: flex; |
||||
padding-left: 16px; |
|
||||
padding-top: 8px; |
|
||||
flex-direction: column; |
flex-direction: column; |
||||
} |
} |
||||
|
|
||||
.item-name { |
.item-name { |
||||
font-size: 14px; |
font-size: 14px; |
||||
font-weight: 500; |
font-weight: 400; |
||||
} |
|
||||
|
|
||||
.item-description { |
|
||||
font-size: 12px; |
|
||||
color: #808192; |
|
||||
} |
|
||||
|
|
||||
p { |
|
||||
line-height: 15px; |
|
||||
} |
} |
||||
|
|
||||
i { |
i { |
||||
font-size: 24px; |
font-size: 24px; |
||||
color: #808192; |
color: var(--ink-light); |
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,217 @@ |
|||||
|
<script> |
||||
|
import Modal from "svelte-simple-modal" |
||||
|
import { |
||||
|
SettingsIcon, |
||||
|
AppsIcon, |
||||
|
UpdatesIcon, |
||||
|
HostingIcon, |
||||
|
DocumentationIcon, |
||||
|
TutorialsIcon, |
||||
|
CommunityIcon, |
||||
|
ContributionIcon, |
||||
|
BugIcon, |
||||
|
EmailIcon, |
||||
|
TwitterIcon, |
||||
|
} from "components/common/Icons/" |
||||
|
</script> |
||||
|
|
||||
|
<Modal> |
||||
|
<div class="root"> |
||||
|
<div class="ui-nav"> |
||||
|
<div class="home-logo"> |
||||
|
<img src="/_builder/assets/bb-logo.svg" alt="Budibase icon" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="nav-section"> |
||||
|
<div class="nav-section-title">Build</div> |
||||
|
<div class="nav-item-home"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<AppsIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Apps</div> |
||||
|
</div> |
||||
|
<div class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<SettingsIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Settings</div> |
||||
|
</div> |
||||
|
<a href="https://budibase.con/login" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<UpdatesIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Updates</div> |
||||
|
</a> |
||||
|
<a href="https://budibase.con/login" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<HostingIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Hosting</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div class="nav-section"> |
||||
|
<div class="nav-section-title">Learn</div> |
||||
|
<a href="https://docs.budibase.com/" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<DocumentationIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Documentation</div> |
||||
|
</a> |
||||
|
<a |
||||
|
href="https://docs.budibase.com/tutorial/quick-start" |
||||
|
target="_blank" |
||||
|
class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<TutorialsIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Tutorials</div> |
||||
|
</a> |
||||
|
<a href="https://forum.budibase.com/" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<CommunityIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Community</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div class="nav-section"> |
||||
|
<div class="nav-section-title">Contact</div> |
||||
|
<a |
||||
|
href="https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md" |
||||
|
target="_blank" |
||||
|
class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<ContributionIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Contribute to our product</div> |
||||
|
</a> |
||||
|
<a |
||||
|
href="https://github.com/Budibase/budibase/issues" |
||||
|
target="_blank" |
||||
|
class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<BugIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Report bug</div> |
||||
|
</a> |
||||
|
<a href="mailto:support@budibase.com" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<EmailIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Email</div> |
||||
|
</a> |
||||
|
<a href="https://twitter.com/budibase" target="_blank" class="nav-item"> |
||||
|
<span class="nav-item-icon"> |
||||
|
<TwitterIcon /> |
||||
|
</span> |
||||
|
<div class="nav-item-title">Twitter</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="main"> |
||||
|
<slot /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Modal> |
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
display: grid; |
||||
|
grid-template-columns: 275px 1fr; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
background: var(--grey-light); |
||||
|
} |
||||
|
|
||||
|
@media only screen and (min-width: 1800px) { |
||||
|
.root { |
||||
|
display: grid; |
||||
|
grid-template-columns: 300px 1fr; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
background: var(--grey-light); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.main { |
||||
|
grid-column: 2; |
||||
|
} |
||||
|
|
||||
|
.ui-nav { |
||||
|
grid-column: 1; |
||||
|
background-color: var(--white); |
||||
|
padding: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
border-right: 1px solid var(--grey-medium); |
||||
|
} |
||||
|
|
||||
|
.home-logo { |
||||
|
cursor: pointer; |
||||
|
height: 40px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.home-logo img { |
||||
|
height: 40px; |
||||
|
} |
||||
|
|
||||
|
.nav-section { |
||||
|
margin: 20px 0px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.nav-section-title { |
||||
|
font-size: 20px; |
||||
|
color: var(--ink); |
||||
|
font-weight: 700; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.nav-item { |
||||
|
cursor: pointer; |
||||
|
margin: 0px 0px 4px 0px; |
||||
|
padding: 0px 0px 0px 12px; |
||||
|
height: 40px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.nav-item-home { |
||||
|
cursor: pointer; |
||||
|
margin: 0px 0px 4px 0px; |
||||
|
padding: 0px 0px 0px 12px; |
||||
|
height: 40px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
box-sizing: border-box; |
||||
|
background-color: var(--blue-light); |
||||
|
} |
||||
|
|
||||
|
.nav-item:hover { |
||||
|
background-color: var(--grey-light); |
||||
|
border-radius: 3px; |
||||
|
} |
||||
|
|
||||
|
.nav-item::selection { |
||||
|
background-color: var(--blue-light); |
||||
|
border-radius: 3px; |
||||
|
} |
||||
|
|
||||
|
.nav-item-title { |
||||
|
font-size: 14px; |
||||
|
color: var(--ink); |
||||
|
font-weight: 500; |
||||
|
margin-left: 12px; |
||||
|
} |
||||
|
|
||||
|
.nav-item-icon { |
||||
|
color: var(--ink-light); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,108 @@ |
|||||
|
const CouchDB = require("../../db") |
||||
|
const newid = require("../../db/newid") |
||||
|
const { |
||||
|
generateAdminPermissions, |
||||
|
generatePowerUserPermissions, |
||||
|
POWERUSER_LEVEL_ID, |
||||
|
ADMIN_LEVEL_ID, |
||||
|
} = require("../../utilities/accessLevels") |
||||
|
|
||||
|
exports.fetch = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
const body = await db.query("database/by_type", { |
||||
|
include_docs: true, |
||||
|
key: ["accesslevel"], |
||||
|
}) |
||||
|
const customAccessLevels = body.rows.map(row => row.doc) |
||||
|
|
||||
|
const staticAccessLevels = [ |
||||
|
{ |
||||
|
_id: ADMIN_LEVEL_ID, |
||||
|
name: "Admin", |
||||
|
permissions: await generateAdminPermissions(ctx.params.instanceId), |
||||
|
}, |
||||
|
{ |
||||
|
_id: POWERUSER_LEVEL_ID, |
||||
|
name: "Power User", |
||||
|
permissions: await generatePowerUserPermissions(ctx.params.instanceId), |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
ctx.body = [...staticAccessLevels, ...customAccessLevels] |
||||
|
} |
||||
|
|
||||
|
exports.find = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
ctx.body = await db.get(ctx.params.levelId) |
||||
|
} |
||||
|
|
||||
|
exports.update = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
const level = await db.get(ctx.params.levelId) |
||||
|
level.name = ctx.body.name |
||||
|
level.permissions = ctx.request.body.permissions |
||||
|
const result = await db.put(level) |
||||
|
level._rev = result.rev |
||||
|
ctx.body = level |
||||
|
ctx.message = `Level ${level.name} updated successfully.` |
||||
|
} |
||||
|
|
||||
|
exports.patch = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
const level = await db.get(ctx.params.levelId) |
||||
|
const { removedPermissions, addedPermissions, _rev } = ctx.request.body |
||||
|
|
||||
|
if (!_rev) throw new Error("Must supply a _rev to update an access level") |
||||
|
|
||||
|
level._rev = _rev |
||||
|
|
||||
|
if (removedPermissions) { |
||||
|
level.permissions = level.permissions.filter( |
||||
|
p => |
||||
|
!removedPermissions.some( |
||||
|
rem => rem.name === p.name && rem.itemId === p.itemId |
||||
|
) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (addedPermissions) { |
||||
|
level.permissions = [ |
||||
|
...level.permissions.filter( |
||||
|
p => |
||||
|
!addedPermissions.some( |
||||
|
add => add.name === p.name && add.itemId === p.itemId |
||||
|
) |
||||
|
), |
||||
|
...addedPermissions, |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
const result = await db.put(level) |
||||
|
level._rev = result.rev |
||||
|
ctx.body = level |
||||
|
ctx.message = `Access Level ${level.name} updated successfully.` |
||||
|
} |
||||
|
|
||||
|
exports.create = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
|
||||
|
const level = { |
||||
|
name: ctx.request.body.name, |
||||
|
_rev: ctx.request.body._rev, |
||||
|
permissions: ctx.request.body.permissions || [], |
||||
|
_id: newid(), |
||||
|
type: "accesslevel", |
||||
|
} |
||||
|
|
||||
|
const result = await db.put(level) |
||||
|
level._rev = result.rev |
||||
|
ctx.body = level |
||||
|
ctx.message = `Access Level '${level.name}' created successfully.` |
||||
|
} |
||||
|
|
||||
|
exports.destroy = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
await db.remove(ctx.params.levelId, ctx.params.rev) |
||||
|
ctx.message = `Access Level ${ctx.params.id} deleted successfully` |
||||
|
ctx.status = 200 |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
const CouchDB = require("../../db") |
||||
|
const newid = require("../../db/newid") |
||||
|
|
||||
|
exports.create = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
const workflow = ctx.request.body |
||||
|
|
||||
|
workflow._id = newid() |
||||
|
|
||||
|
// TODO: Possibly validate the workflow against a schema
|
||||
|
|
||||
|
// // validation with ajv
|
||||
|
// const model = await db.get(record.modelId)
|
||||
|
// const validate = ajv.compile({
|
||||
|
// properties: model.schema,
|
||||
|
// })
|
||||
|
// const valid = validate(record)
|
||||
|
|
||||
|
// if (!valid) {
|
||||
|
// ctx.status = 400
|
||||
|
// ctx.body = {
|
||||
|
// status: 400,
|
||||
|
// errors: validate.errors,
|
||||
|
// }
|
||||
|
// return
|
||||
|
// }
|
||||
|
|
||||
|
workflow.type = "workflow" |
||||
|
const response = await db.post(workflow) |
||||
|
workflow._rev = response.rev |
||||
|
|
||||
|
ctx.status = 200 |
||||
|
ctx.body = { |
||||
|
message: "Workflow created successfully", |
||||
|
workflow: { |
||||
|
...workflow, |
||||
|
...response, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
exports.update = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
ctx.body = await db.get(ctx.params.recordId) |
||||
|
} |
||||
|
|
||||
|
exports.fetch = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
const response = await db.query(`database/by_type`, { |
||||
|
type: "workflow", |
||||
|
include_docs: true, |
||||
|
}) |
||||
|
ctx.body = response.rows.map(row => row.doc) |
||||
|
} |
||||
|
|
||||
|
exports.find = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
ctx.body = await db.get(ctx.params.id) |
||||
|
} |
||||
|
|
||||
|
exports.destroy = async function(ctx) { |
||||
|
const db = new CouchDB(ctx.params.instanceId) |
||||
|
ctx.body = await db.remove(ctx.params.id, ctx.params.rev) |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const controller = require("../controllers/accesslevel") |
||||
|
|
||||
|
const router = Router() |
||||
|
|
||||
|
router |
||||
|
.post("/api/:instanceId/accesslevels", controller.create) |
||||
|
.put("/api/:instanceId/accesslevels", controller.update) |
||||
|
.get("/api/:instanceId/accesslevels", controller.fetch) |
||||
|
.get("/api/:instanceId/accesslevels/:levelId", controller.find) |
||||
|
.delete("/api/:instanceId/accesslevels/:levelId/:rev", controller.destroy) |
||||
|
.patch("/api/:instanceId/accesslevels/:levelId", controller.patch) |
||||
|
|
||||
|
module.exports = router |
||||
@ -1,11 +1,17 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/application") |
const controller = require("../controllers/application") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { BUILDER } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router |
router |
||||
.get("/api/applications", controller.fetch) |
.get("/api/applications", authorized(BUILDER), controller.fetch) |
||||
.get("/api/:applicationId/appPackage", controller.fetchAppPackage) |
.get( |
||||
.post("/api/applications", controller.create) |
"/api/:applicationId/appPackage", |
||||
|
authorized(BUILDER), |
||||
|
controller.fetchAppPackage |
||||
|
) |
||||
|
.post("/api/applications", authorized(BUILDER), controller.create) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -1,8 +1,10 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/client") |
const controller = require("../controllers/client") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { BUILDER } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router.get("/api/client/id", controller.getClientId) |
router.get("/api/client/id", authorized(BUILDER), controller.getClientId) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -1,10 +1,12 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/instance") |
const controller = require("../controllers/instance") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { BUILDER } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router |
router |
||||
.post("/api/:applicationId/instances", controller.create) |
.post("/api/:applicationId/instances", authorized(BUILDER), controller.create) |
||||
.delete("/api/instances/:instanceId", controller.destroy) |
.delete("/api/instances/:instanceId", authorized(BUILDER), controller.destroy) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -1,12 +1,49 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/model") |
const modelController = require("../controllers/model") |
||||
|
const recordController = require("../controllers/record") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { |
||||
|
READ_MODEL, |
||||
|
WRITE_MODEL, |
||||
|
BUILDER, |
||||
|
} = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
|
// records
|
||||
|
|
||||
|
router |
||||
|
.get( |
||||
|
"/api/:instanceId/:modelId/records", |
||||
|
authorized(READ_MODEL, ctx => ctx.params.modelId), |
||||
|
recordController.fetchModel |
||||
|
) |
||||
|
.get( |
||||
|
"/api/:instanceId/:modelId/records/:recordId", |
||||
|
authorized(READ_MODEL, ctx => ctx.params.modelId), |
||||
|
recordController.find |
||||
|
) |
||||
|
.post( |
||||
|
"/api/:instanceId/:modelId/records", |
||||
|
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
||||
|
recordController.save |
||||
|
) |
||||
|
.delete( |
||||
|
"/api/:instanceId/:modelId/records/:recordId/:revId", |
||||
|
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
||||
|
recordController.destroy |
||||
|
) |
||||
|
|
||||
|
// models
|
||||
|
|
||||
router |
router |
||||
.get("/api/:instanceId/models", controller.fetch) |
.get("/api/:instanceId/models", authorized(BUILDER), modelController.fetch) |
||||
.post("/api/:instanceId/models", controller.create) |
.post("/api/:instanceId/models", authorized(BUILDER), modelController.create) |
||||
// .patch("/api/:instanceId/models", controller.update)
|
// .patch("/api/:instanceId/models", controller.update)
|
||||
.delete("/api/:instanceId/models/:modelId/:revId", controller.destroy) |
.delete( |
||||
|
"/api/:instanceId/models/:modelId/:revId", |
||||
|
authorized(BUILDER), |
||||
|
modelController.destroy |
||||
|
) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -1,12 +0,0 @@ |
|||||
const Router = require("@koa/router") |
|
||||
const controller = require("../controllers/record") |
|
||||
|
|
||||
const router = Router() |
|
||||
|
|
||||
router |
|
||||
.get("/api/:instanceId/:viewName/records", controller.fetch) |
|
||||
.get("/api/:instanceId/records/:recordId", controller.find) |
|
||||
.post("/api/:instanceId/records", controller.save) |
|
||||
.delete("/api/:instanceId/records/:recordId/:revId", controller.destroy) |
|
||||
|
|
||||
module.exports = router |
|
||||
@ -1,11 +1,17 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/screen") |
const controller = require("../controllers/screen") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { BUILDER } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router |
router |
||||
.get("/api/:instanceId/screens", controller.fetch) |
.get("/api/:instanceId/screens", authorized(BUILDER), controller.fetch) |
||||
.post("/api/:instanceId/screens", controller.save) |
.post("/api/:instanceId/screens", authorized(BUILDER), controller.save) |
||||
.delete("/api/:instanceId/:screenId/:revId", controller.destroy) |
.delete( |
||||
|
"/api/:instanceId/:screenId/:revId", |
||||
|
authorized(BUILDER), |
||||
|
controller.destroy |
||||
|
) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -0,0 +1,184 @@ |
|||||
|
const { |
||||
|
createInstance, |
||||
|
createClientDatabase, |
||||
|
createApplication, |
||||
|
createModel, |
||||
|
createView, |
||||
|
supertest, |
||||
|
defaultHeaders |
||||
|
} = require("./couchTestUtils") |
||||
|
const { |
||||
|
generateAdminPermissions, |
||||
|
generatePowerUserPermissions, |
||||
|
POWERUSER_LEVEL_ID, |
||||
|
ADMIN_LEVEL_ID, |
||||
|
READ_MODEL, |
||||
|
WRITE_MODEL, |
||||
|
} = require("../../../utilities/accessLevels") |
||||
|
|
||||
|
describe("/accesslevels", () => { |
||||
|
let appId |
||||
|
let server |
||||
|
let request |
||||
|
let instanceId |
||||
|
let model |
||||
|
let view |
||||
|
|
||||
|
beforeAll(async () => { |
||||
|
({ request, server } = await supertest()) |
||||
|
await createClientDatabase(request); |
||||
|
appId = (await createApplication(request))._id |
||||
|
}); |
||||
|
|
||||
|
afterAll(async () => { |
||||
|
server.close(); |
||||
|
}) |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
instanceId = (await createInstance(request, appId))._id |
||||
|
model = await createModel(request, instanceId) |
||||
|
view = await createView(request, instanceId) |
||||
|
}) |
||||
|
|
||||
|
describe("create", () => { |
||||
|
|
||||
|
it("returns a success message when level is successfully created", async () => { |
||||
|
const res = await request |
||||
|
.post(`/api/${instanceId}/accesslevels`) |
||||
|
.send({ name: "user" }) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.res.statusMessage).toEqual("Access Level 'user' created successfully.") |
||||
|
expect(res.body._id).toBeDefined() |
||||
|
expect(res.body._rev).toBeDefined() |
||||
|
expect(res.body.permissions).toEqual([]) |
||||
|
}) |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe("fetch", () => { |
||||
|
|
||||
|
it("should list custom levels, plus 2 default levels", async () => { |
||||
|
const createRes = await request |
||||
|
.post(`/api/${instanceId}/accesslevels`) |
||||
|
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] }) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const customLevel = createRes.body |
||||
|
|
||||
|
const res = await request |
||||
|
.get(`/api/${instanceId}/accesslevels`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.body.length).toBe(3) |
||||
|
|
||||
|
const adminLevel = res.body.find(r => r._id === ADMIN_LEVEL_ID) |
||||
|
expect(adminLevel).toBeDefined() |
||||
|
expect(adminLevel.permissions).toEqual(await generateAdminPermissions(instanceId)) |
||||
|
|
||||
|
const powerUserLevel = res.body.find(r => r._id === POWERUSER_LEVEL_ID) |
||||
|
expect(powerUserLevel).toBeDefined() |
||||
|
expect(powerUserLevel.permissions).toEqual(await generatePowerUserPermissions(instanceId)) |
||||
|
|
||||
|
const customLevelFetched = res.body.find(r => r._id === customLevel._id) |
||||
|
expect(customLevelFetched.permissions).toEqual(customLevel.permissions) |
||||
|
}) |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe("destroy", () => { |
||||
|
it("should delete custom access level", async () => { |
||||
|
const createRes = await request |
||||
|
.post(`/api/${instanceId}/accesslevels`) |
||||
|
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL } ] }) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const customLevel = createRes.body |
||||
|
|
||||
|
await request |
||||
|
.delete(`/api/${instanceId}/accesslevels/${customLevel._id}/${customLevel._rev}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect(200) |
||||
|
|
||||
|
await request |
||||
|
.get(`/api/${instanceId}/accesslevels/${customLevel._id}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect(404) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("patch", () => { |
||||
|
it("should add given permissions", async () => { |
||||
|
const createRes = await request |
||||
|
.post(`/api/${instanceId}/accesslevels`) |
||||
|
.send({ name: "user", permissions: [ { itemId: model._id, name: READ_MODEL }] }) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const customLevel = createRes.body |
||||
|
|
||||
|
await request |
||||
|
.patch(`/api/${instanceId}/accesslevels/${customLevel._id}`) |
||||
|
.send({ |
||||
|
_rev: customLevel._rev, |
||||
|
addedPermissions: [ { itemId: model._id, name: WRITE_MODEL } ] |
||||
|
}) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const finalRes = await request |
||||
|
.get(`/api/${instanceId}/accesslevels/${customLevel._id}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(finalRes.body.permissions.length).toBe(2) |
||||
|
expect(finalRes.body.permissions.some(p => p.name === WRITE_MODEL)).toBe(true) |
||||
|
expect(finalRes.body.permissions.some(p => p.name === READ_MODEL)).toBe(true) |
||||
|
}) |
||||
|
|
||||
|
it("should remove given permissions", async () => { |
||||
|
const createRes = await request |
||||
|
.post(`/api/${instanceId}/accesslevels`) |
||||
|
.send({ |
||||
|
name: "user", |
||||
|
permissions: [ |
||||
|
{ itemId: model._id, name: READ_MODEL }, |
||||
|
{ itemId: model._id, name: WRITE_MODEL }, |
||||
|
] |
||||
|
}) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const customLevel = createRes.body |
||||
|
|
||||
|
await request |
||||
|
.patch(`/api/${instanceId}/accesslevels/${customLevel._id}`) |
||||
|
.send({ |
||||
|
_rev: customLevel._rev, |
||||
|
removedPermissions: [ { itemId: model._id, name: WRITE_MODEL }] |
||||
|
}) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
const finalRes = await request |
||||
|
.get(`/api/${instanceId}/accesslevels/${customLevel._id}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(finalRes.body.permissions.length).toBe(1) |
||||
|
expect(finalRes.body.permissions.some(p => p.name === READ_MODEL)).toBe(true) |
||||
|
}) |
||||
|
}) |
||||
|
}); |
||||
@ -0,0 +1,114 @@ |
|||||
|
const { |
||||
|
createClientDatabase, |
||||
|
createApplication, |
||||
|
createInstance, |
||||
|
defaultHeaders, |
||||
|
supertest, |
||||
|
insertDocument, |
||||
|
destroyDocument |
||||
|
} = require("./couchTestUtils") |
||||
|
|
||||
|
const TEST_WORKFLOW = { |
||||
|
_id: "Test Workflow", |
||||
|
name: "My Workflow", |
||||
|
pageId: "123123123", |
||||
|
screenId: "kasdkfldsafkl", |
||||
|
live: true, |
||||
|
uiTree: { |
||||
|
|
||||
|
}, |
||||
|
definition: { |
||||
|
triggers: [ |
||||
|
|
||||
|
], |
||||
|
next: { |
||||
|
actionId: "abc123", |
||||
|
type: "SERVER", |
||||
|
conditions: { |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe("/workflows", () => { |
||||
|
let request |
||||
|
let server |
||||
|
let app |
||||
|
let instance |
||||
|
let workflow |
||||
|
|
||||
|
beforeAll(async () => { |
||||
|
({ request, server } = await supertest()) |
||||
|
await createClientDatabase(request) |
||||
|
app = await createApplication(request) |
||||
|
}) |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
instance = await createInstance(request, app._id) |
||||
|
if (workflow) await destroyDocument(workflow.id); |
||||
|
}) |
||||
|
|
||||
|
afterAll(async () => { |
||||
|
server.close() |
||||
|
}) |
||||
|
|
||||
|
const createWorkflow = async () => { |
||||
|
workflow = await insertDocument(instance._id, { |
||||
|
type: "workflow", |
||||
|
...TEST_WORKFLOW |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
describe("create", () => { |
||||
|
it("returns a success message when the workflow is successfully created", async () => { |
||||
|
const res = await request |
||||
|
.post(`/api/${instance._id}/workflows`) |
||||
|
.set(defaultHeaders) |
||||
|
.send(TEST_WORKFLOW) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.body.message).toEqual("Workflow created successfully"); |
||||
|
expect(res.body.workflow.name).toEqual("My Workflow"); |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("fetch", () => { |
||||
|
it("return all the workflows for an instance", async () => { |
||||
|
await createWorkflow(); |
||||
|
const res = await request |
||||
|
.get(`/api/${instance._id}/workflows`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW)); |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("find", () => { |
||||
|
it("returns a workflow when queried by ID", async () => { |
||||
|
await createWorkflow(); |
||||
|
const res = await request |
||||
|
.get(`/api/${instance._id}/workflows/${workflow.id}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.body).toEqual(expect.objectContaining(TEST_WORKFLOW)); |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("destroy", () => { |
||||
|
it("deletes a workflow by its ID", async () => { |
||||
|
await createWorkflow(); |
||||
|
const res = await request |
||||
|
.delete(`/api/${instance._id}/workflows/${workflow.id}/${workflow.rev}`) |
||||
|
.set(defaultHeaders) |
||||
|
.expect('Content-Type', /json/) |
||||
|
.expect(200) |
||||
|
|
||||
|
expect(res.body.id).toEqual(TEST_WORKFLOW._id); |
||||
|
}) |
||||
|
}) |
||||
|
}); |
||||
@ -1,12 +1,26 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/user") |
const controller = require("../controllers/user") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { USER_MANAGEMENT, LIST_USERS } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router |
router |
||||
.get("/api/:instanceId/users", controller.fetch) |
.get("/api/:instanceId/users", authorized(LIST_USERS), controller.fetch) |
||||
.get("/api/:instanceId/users/:username", controller.find) |
.get( |
||||
.post("/api/:instanceId/users", controller.create) |
"/api/:instanceId/users/:username", |
||||
.delete("/api/:instanceId/users/:username", controller.destroy) |
authorized(USER_MANAGEMENT), |
||||
|
controller.find |
||||
|
) |
||||
|
.post( |
||||
|
"/api/:instanceId/users", |
||||
|
authorized(USER_MANAGEMENT), |
||||
|
controller.create |
||||
|
) |
||||
|
.delete( |
||||
|
"/api/:instanceId/users/:username", |
||||
|
authorized(USER_MANAGEMENT), |
||||
|
controller.destroy |
||||
|
) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -1,12 +1,20 @@ |
|||||
const Router = require("@koa/router") |
const Router = require("@koa/router") |
||||
const controller = require("../controllers/view") |
const viewController = require("../controllers/view") |
||||
|
const recordController = require("../controllers/record") |
||||
|
const authorized = require("../../middleware/authorized") |
||||
|
const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels") |
||||
|
|
||||
const router = Router() |
const router = Router() |
||||
|
|
||||
router |
router |
||||
.get("/api/:instanceId/views", controller.fetch) |
.get( |
||||
|
"/api/:instanceId/view/:viewName", |
||||
|
authorized(READ_VIEW, ctx => ctx.params.viewName), |
||||
|
recordController.fetchView |
||||
|
) |
||||
|
.get("/api/:instanceId/views", authorized(BUILDER), viewController.fetch) |
||||
// .patch("/api/:databaseId/views", controller.update);
|
// .patch("/api/:databaseId/views", controller.update);
|
||||
// .delete("/api/:instanceId/views/:viewId/:revId", controller.destroy);
|
// .delete("/api/:instanceId/views/:viewId/:revId", controller.destroy);
|
||||
.post("/api/:instanceId/views", controller.create) |
.post("/api/:instanceId/views", authorized(BUILDER), viewController.create) |
||||
|
|
||||
module.exports = router |
module.exports = router |
||||
|
|||||
@ -0,0 +1,13 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const controller = require("../controllers/workflow") |
||||
|
|
||||
|
const router = Router() |
||||
|
|
||||
|
router |
||||
|
.get("/api/:instanceId/workflows", controller.fetch) |
||||
|
.get("/api/:instanceId/workflows/:id", controller.find) |
||||
|
.post("/api/:instanceId/workflows", controller.create) |
||||
|
.put("/api/:instanceId/workflows/:id", controller.update) |
||||
|
.delete("/api/:instanceId/workflows/:id/:rev", controller.destroy) |
||||
|
|
||||
|
module.exports = router |
||||
@ -0,0 +1,58 @@ |
|||||
|
const { |
||||
|
adminPermissions, |
||||
|
ADMIN_LEVEL_ID, |
||||
|
POWERUSER_LEVEL_ID, |
||||
|
BUILDER, |
||||
|
} = require("../utilities/accessLevels") |
||||
|
|
||||
|
module.exports = (permName, getItemId) => async (ctx, next) => { |
||||
|
if (!ctx.isAuthenticated) { |
||||
|
ctx.throw(403, "Session not authenticated") |
||||
|
} |
||||
|
|
||||
|
if (ctx.isBuilder) { |
||||
|
await next() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (permName === BUILDER) { |
||||
|
ctx.throw(403, "Not Authorized") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if (!ctx.user) { |
||||
|
ctx.throw(403, "User not found") |
||||
|
} |
||||
|
|
||||
|
const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "") |
||||
|
|
||||
|
if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) { |
||||
|
await next() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const thisPermissionId = { |
||||
|
name: permName, |
||||
|
itemId: getItemId && getItemId(ctx), |
||||
|
} |
||||
|
|
||||
|
// power user has everything, except the admin specific perms
|
||||
|
if ( |
||||
|
ctx.user.accessLevel._id === POWERUSER_LEVEL_ID && |
||||
|
!adminPermissions.map(permissionId).includes(thisPermissionId) |
||||
|
) { |
||||
|
await next() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if ( |
||||
|
ctx.user.accessLevel.permissions |
||||
|
.map(permissionId) |
||||
|
.includes(thisPermissionId) |
||||
|
) { |
||||
|
await next() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
ctx.throw(403, "Not Authorized") |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
const WORKFLOW_SCHEMA = { |
||||
|
properties: { |
||||
|
type: "workflow", |
||||
|
pageId: { |
||||
|
type: "string", |
||||
|
}, |
||||
|
screenId: { |
||||
|
type: "string", |
||||
|
}, |
||||
|
live: { |
||||
|
type: "boolean", |
||||
|
}, |
||||
|
uiTree: { |
||||
|
type: "object", |
||||
|
}, |
||||
|
definition: { |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
triggers: { type: "array" }, |
||||
|
next: { |
||||
|
type: "object", |
||||
|
properties: { |
||||
|
type: { type: "string" }, |
||||
|
actionId: { type: "string" }, |
||||
|
args: { type: "object" }, |
||||
|
conditions: { type: "array" }, |
||||
|
errorHandling: { type: "object" }, |
||||
|
next: { type: "object" }, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
WORKFLOW_SCHEMA, |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
const viewController = require("../api/controllers/view") |
||||
|
const modelController = require("../api/controllers/model") |
||||
|
|
||||
|
exports.ADMIN_LEVEL_ID = "ADMIN" |
||||
|
exports.POWERUSER_LEVEL_ID = "POWER_USER" |
||||
|
|
||||
|
exports.READ_MODEL = "read-model" |
||||
|
exports.WRITE_MODEL = "write-model" |
||||
|
exports.READ_VIEW = "read-view" |
||||
|
exports.EXECUTE_WORKFLOW = "execute-workflow" |
||||
|
exports.USER_MANAGEMENT = "user-management" |
||||
|
exports.BUILDER = "builder" |
||||
|
exports.LIST_USERS = "list-users" |
||||
|
|
||||
|
exports.adminPermissions = [ |
||||
|
{ |
||||
|
name: exports.USER_MANAGEMENT, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
exports.generateAdminPermissions = async instanceId => [ |
||||
|
...exports.adminPermissions, |
||||
|
...(await exports.generatePowerUserPermissions(instanceId)), |
||||
|
] |
||||
|
|
||||
|
exports.generatePowerUserPermissions = async instanceId => { |
||||
|
const fetchModelsCtx = { |
||||
|
params: { |
||||
|
instanceId, |
||||
|
}, |
||||
|
} |
||||
|
await modelController.fetch(fetchModelsCtx) |
||||
|
const models = fetchModelsCtx.body |
||||
|
|
||||
|
const fetchViewsCtx = { |
||||
|
params: { |
||||
|
instanceId, |
||||
|
}, |
||||
|
} |
||||
|
await viewController.fetch(fetchViewsCtx) |
||||
|
const views = fetchViewsCtx.body |
||||
|
|
||||
|
const readModelPermissions = models.map(m => ({ |
||||
|
itemId: m._id, |
||||
|
name: exports.READ_MODEL, |
||||
|
})) |
||||
|
|
||||
|
const writeModelPermissions = models.map(m => ({ |
||||
|
itemId: m._id, |
||||
|
name: exports.WRITE_MODEL, |
||||
|
})) |
||||
|
|
||||
|
const viewPermissions = views.map(v => ({ |
||||
|
itemId: v.name, |
||||
|
name: exports.READ_VIEW, |
||||
|
})) |
||||
|
|
||||
|
return [ |
||||
|
...readModelPermissions, |
||||
|
...writeModelPermissions, |
||||
|
...viewPermissions, |
||||
|
{ name: exports.LIST_USERS }, |
||||
|
] |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
dist/ |
||||
@ -0,0 +1,11 @@ |
|||||
|
{ |
||||
|
"name": "name", |
||||
|
"version": "1.0.0", |
||||
|
"description": "", |
||||
|
"author": "", |
||||
|
"license": "ISC", |
||||
|
"dependencies": { |
||||
|
"@budibase/standard-components": "0.x", |
||||
|
"@budibase/materialdesign-components": "0.x" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"title": "Test App", |
||||
|
"favicon": "./_shared/favicon.png", |
||||
|
"stylesheets": [], |
||||
|
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"], |
||||
|
"props" : { |
||||
|
"_component": "@budibase/standard-components/container", |
||||
|
"_children": [], |
||||
|
"_id": 0, |
||||
|
"type": "div", |
||||
|
"_styles": { |
||||
|
"layout": {}, |
||||
|
"position": {} |
||||
|
}, |
||||
|
"_code": "" |
||||
|
}, |
||||
|
"_css": "", |
||||
|
"uiFunctions": "" |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
{ |
||||
|
"title": "Test App", |
||||
|
"favicon": "./_shared/favicon.png", |
||||
|
"stylesheets": [], |
||||
|
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"], |
||||
|
"props" : { |
||||
|
"_component": "@budibase/standard-components/container", |
||||
|
"_children": [], |
||||
|
"_id": 1, |
||||
|
"type": "div", |
||||
|
"_styles": { |
||||
|
"layout": {}, |
||||
|
"position": {} |
||||
|
}, |
||||
|
"_code": "" |
||||
|
}, |
||||
|
"_css": "", |
||||
|
"uiFunctions": "" |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
module.exports = () => ({}) |
||||
Loading…
Reference in new issue