Merge branch 'feature/handlebars-helpers' of github.com:Budibase/budibase into feature/handlebars-helpers

This commit is contained in:
Martin McKeaveney 2021-02-01 11:52:40 +00:00
commit 5702b849d1
37 changed files with 407 additions and 140 deletions

View File

@ -103,6 +103,33 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "victoriasloan",
"name": "victoriasloan",
"avatar_url": "https://avatars.githubusercontent.com/u/9913651?v=4",
"profile": "https://github.com/victoriasloan",
"contributions": [
"code"
]
},
{
"login": "yashank09",
"name": "yashank09",
"avatar_url": "https://avatars.githubusercontent.com/u/37672190?v=4",
"profile": "https://github.com/yashank09",
"contributions": [
"code"
]
},
{
"login": "SOVLOOKUP",
"name": "SOVLOOKUP",
"avatar_url": "https://avatars.githubusercontent.com/u/53158137?v=4",
"profile": "https://github.com/SOVLOOKUP",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@ -86,6 +86,10 @@ Watch "releases" of this repo to get notified of major updates, and give the sta
<img src="https://i.imgur.com/cJpgqm8.png"> <img src="https://i.imgur.com/cJpgqm8.png">
</p> </p>
### Stargazers over time
[![Stargazers over time](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase)
If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment. If you are having issues between updates of the builder, please use the guide [here](https://github.com/Budibase/budibase/blob/master/CONTRIBUTING.md#troubleshooting) to clear down your environment.
@ -107,6 +111,8 @@ Budibase wants to make sure anyone can use the tools we develop and we know a lo
Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting). Currently, you can host your apps using Docker or Digital Ocean. The documentation for self-hosting can be found [here](https://docs.budibase.com/self-hosting/introduction-to-self-hosting).
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/droplets/new?onboarding_origin=marketplace&i=09038e&fleetUuid=bb04f9c8-1de8-4687-b2ae-1d5177a0535b&appId=77729671&type=applications&size=s-4vcpu-8gb&region=nyc1&refcode=0caaa6085a82&image=budibase-20-04)
## 🎓 Learning Budibase ## 🎓 Learning Budibase
@ -154,6 +160,7 @@ If you have a question or would like to talk with other Budibase users, please h
![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield) ![Discord Shield](https://discordapp.com/api/guilds/733030666647765003/widget.png?style=shield)
## Contributors ✨ ## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@ -174,6 +181,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr> <tr>
<td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td> <td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td> <td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/victoriasloan"><img src="https://avatars.githubusercontent.com/u/9913651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>victoriasloan</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=victoriasloan" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yashank09"><img src="https://avatars.githubusercontent.com/u/37672190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yashank09</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=yashank09" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/SOVLOOKUP"><img src="https://avatars.githubusercontent.com/u/53158137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SOVLOOKUP</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=SOVLOOKUP" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

1
hosting/update.sh Normal file
View File

@ -0,0 +1 @@
docker-compose --env-file hosting.properties pull && ./start.sh

View File

@ -74,6 +74,7 @@
"codemirror": "^5.59.0", "codemirror": "^5.59.0",
"d3-selection": "^1.4.1", "d3-selection": "^1.4.1",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"downloadjs": "^1.4.7",
"fast-sort": "^2.2.0", "fast-sort": "^2.2.0",
"lodash": "^4.17.13", "lodash": "^4.17.13",
"posthog-js": "1.4.5", "posthog-js": "1.4.5",

View File

@ -3,6 +3,7 @@ import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore" import { backendUiStore, store } from "builderStore"
import { findAllMatchingComponents, findComponentPath } from "./storeUtils" import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
import { makePropSafe } from "@budibase/string-templates" import { makePropSafe } from "@budibase/string-templates"
import { TableNames } from "../constants"
// Regex to match all instances of template strings // Regex to match all instances of template strings
const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g const CAPTURE_VAR_INSIDE_TEMPLATE = /{{([^}]+)}}/g
@ -117,6 +118,37 @@ export const getContextBindings = (rootComponent, componentId) => {
}) })
}) })
}) })
// Add logged in user bindings
const tables = get(backendUiStore).tables
const userTable = tables.find(table => table._id === TableNames.USERS)
const schema = {
...userTable.schema,
_id: { type: "string" },
_rev: { type: "string" },
}
const keys = Object.keys(schema).sort()
keys.forEach(key => {
const fieldSchema = schema[key]
// Replace certain bindings with a new property to help display components
let runtimeBoundKey = key
if (fieldSchema.type === "link") {
runtimeBoundKey = `${key}_count`
} else if (fieldSchema.type === "attachment") {
runtimeBoundKey = `${key}_first`
}
contextBindings.push({
type: "context",
runtimeBinding: `user.${runtimeBoundKey}`,
readableBinding: `Current User.${key}`,
fieldSchema,
providerId: "user",
tableId: TableNames.USERS,
field: key,
})
})
return contextBindings return contextBindings
} }

View File

@ -9,6 +9,7 @@
let permissions = [] let permissions = []
let selectedRole = {} let selectedRole = {}
let errors = [] let errors = []
let builtInRoles = ['Admin', 'Power', 'Basic', 'Public']
$: selectedRoleId = selectedRole._id $: selectedRoleId = selectedRole._id
$: otherRoles = $backendUiStore.roles.filter( $: otherRoles = $backendUiStore.roles.filter(
role => role._id !== selectedRoleId role => role._id !== selectedRoleId
@ -102,7 +103,7 @@
{/each} {/each}
</Select> </Select>
{#if selectedRole} {#if selectedRole}
<Input label="Name" bind:value={selectedRole.name} thin /> <Input label="Name" bind:value={selectedRole.name} thin disabled={builtInRoles.includes(selectedRole.name)}/>
<Select <Select
thin thin
secondary secondary

View File

@ -73,7 +73,7 @@
.notOneOf(existingAppUrls), .notOneOf(existingAppUrls),
} }
} else { } else {
nameValidation = { name: string.required(nameError) } nameValidation = { name: string().required(nameError) }
} }
}) })
</script> </script>

View File

@ -2,7 +2,26 @@
import { TextButton } from "@budibase/bbui" import { TextButton } from "@budibase/bbui"
import { Heading } from "@budibase/bbui" import { Heading } from "@budibase/bbui"
import { Spacer } from "@budibase/bbui" import { Spacer } from "@budibase/bbui"
import api from "builderStore/api"
import { notifier } from "builderStore/store/notifications"
import Spinner from "components/common/Spinner.svelte"
import download from "downloadjs"
export let name, _id export let name, _id
let appExportLoading = false
async function exportApp() {
appExportLoading = true
try {
download(`/api/backups/export?appId=${_id}`)
notifier.success("App Export Complete.")
} catch (err) {
notifier.danger("App Export Failed.")
} finally {
appExportLoading = false
}
}
</script> </script>
<div class="apps-card"> <div class="apps-card">
@ -14,6 +33,9 @@
{name} {name}
</TextButton> </TextButton>
{#if appExportLoading}
<Spinner size="10" />
{:else}<i class="ri-folder-download-line" on:click={exportApp} />{/if}
</div> </div>
</div> </div>
@ -31,7 +53,17 @@
.card-footer { .card-footer {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: baseline; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
i {
font-size: var(--font-size-l);
cursor: pointer;
transition: 0.2s all;
}
i:hover {
color: var(--blue);
}
</style> </style>

View File

@ -1,14 +1,51 @@
<script> <script>
import { Label, Heading, Input } from "@budibase/bbui" import { Label, Heading, Input } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte"
const BYTES_IN_MB = 1000000
const FILE_SIZE_LIMIT = BYTES_IN_MB * 5
export let validationErrors export let validationErrors
export let template export let template
let blurred = { appName: false } let blurred = { appName: false }
let file
function handleFile(evt) {
const fileArray = Array.from(evt.target.files)
if (fileArray.some(file => file.size >= FILE_SIZE_LIMIT)) {
notifier.danger(
`Files cannot exceed ${FILE_SIZE_LIMIT /
BYTES_IN_MB}MB. Please try again with smaller files.`
)
return
}
file = fileArray[0]
template.fileImportPath = file.path
}
</script> </script>
<h2>Create your Web App</h2> {#if template?.fromFile}
<h2>Import Your Web App From A File</h2>
{:else}
<h2>Create your Web App</h2>
{/if}
<div class="container"> <div class="container">
{#if template} {#if template?.fromFile}
<div class="template">
<Label extraSmall grey>Import File</Label>
<div class="dropzone">
<input
id="file-upload"
accept=".txt"
type="file"
on:change={handleFile} />
<label for="file-upload" class:uploaded={file}>
{#if file}{file.name}{:else}Import{/if}
</label>
</div>
</div>
{:else if template}
<div class="template"> <div class="template">
<Label extraSmall grey>Selected Template</Label> <Label extraSmall grey>Selected Template</Label>
<Heading small>{template.name}</Heading> <Heading small>{template.name}</Heading>
@ -33,4 +70,48 @@
/* Fix layout due to LH 0 on heading */ /* Fix layout due to LH 0 on heading */
margin-bottom: 16px; margin-bottom: 16px;
} }
.dropzone {
text-align: center;
display: flex;
align-items: center;
flex-direction: column;
border-radius: 10px;
transition: all 0.3s;
}
.uploaded {
color: var(--blue);
}
input[type="file"] {
display: none;
}
label {
font-family: var(--font-sans);
cursor: pointer;
font-weight: 500;
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius-s);
color: var(--ink);
padding: var(--spacing-m) var(--spacing-l);
transition: all 0.2s ease 0s;
display: inline-flex;
text-rendering: optimizeLegibility;
min-width: auto;
outline: none;
font-feature-settings: "case" 1, "rlig" 1, "calt" 0;
-webkit-box-align: center;
user-select: none;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 100%;
background-color: var(--grey-2);
font-size: var(--font-size-xs);
line-height: normal;
border: var(--border-transparent);
}
</style> </style>

View File

@ -4,7 +4,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 } from "@budibase/bbui" import { Button, Heading, Modal, Spacer } 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"
@ -43,13 +43,22 @@
modal.show() modal.show()
} }
function initiateAppImport() {
template = { fromFile: true }
modal.show()
}
checkIfKeysAndApps() checkIfKeysAndApps()
</script> </script>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<Heading medium black>Welcome to the Budibase Beta</Heading> <Heading medium black>Welcome to the Budibase Beta</Heading>
<Button primary on:click={modal.show}>Create New Web App</Button> <div class="button-group">
<Button secondary on:click={initiateAppImport}>Import Web App</Button>
<Spacer medium />
<Button primary on:click={modal.show}>Create New Web App</Button>
</div>
</div> </div>
<div class="banner"> <div class="banner">
@ -103,4 +112,9 @@
color: white; color: white;
font-weight: 500; font-weight: 500;
} }
.button-group {
display: flex;
flex-direction: row;
}
</style> </style>

View File

@ -2995,6 +2995,11 @@ dotenv@^8.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
downloadjs@^1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/downloadjs/-/downloadjs-1.4.7.tgz#f69f96f940e0d0553dac291139865a3cd0101e3c"
integrity sha1-9p+W+UDg0FU9rCkROYZaPNAQHjw=
duplexer@~0.1.1: duplexer@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"

View File

@ -20,9 +20,10 @@ const makeApiCall = async ({ method, url, body, json = true }) => {
const requestBody = json ? JSON.stringify(body) : body const requestBody = json ? JSON.stringify(body) : body
let headers = { let headers = {
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", ...(json && { "Content-Type": "application/json" }),
"x-budibase-app-id": window["##BUDIBASE_APP_ID##"], "x-budibase-app-id": window["##BUDIBASE_APP_ID##"],
} }
if (!window["##BUDIBASE_IN_BUILDER##"]) { if (!window["##BUDIBASE_IN_BUILDER##"]) {
headers["x-budibase-type"] = "client" headers["x-budibase-type"] = "client"
} }

View File

@ -1,4 +1,6 @@
import API from "./api" import API from "./api"
import { enrichRows } from "./rows"
import { TableNames } from "../constants"
/** /**
* Performs a log in request. * Performs a log in request.
@ -15,3 +17,15 @@ export const logIn = async ({ email, password }) => {
body: { email, password }, body: { email, password },
}) })
} }
/**
* Fetches the currently logged in user object
*/
export const fetchSelf = async () => {
const user = await API.get({ url: "/api/self" })
if (user?._id) {
return (await enrichRows([user], TableNames.USERS))[0]
} else {
return null
}
}

View File

@ -4,12 +4,7 @@
import Component from "./Component.svelte" import Component from "./Component.svelte"
import NotificationDisplay from "./NotificationDisplay.svelte" import NotificationDisplay from "./NotificationDisplay.svelte"
import SDK from "../sdk" import SDK from "../sdk"
import { import { createDataStore, initialise, screenStore, authStore } from "../store"
createDataStore,
initialise,
screenStore,
notificationStore,
} from "../store"
// Provide contexts // Provide contexts
setContext("sdk", SDK) setContext("sdk", SDK)
@ -22,6 +17,7 @@
// Load app config // Load app config
onMount(async () => { onMount(async () => {
await initialise() await initialise()
await authStore.actions.fetchUser()
loaded = true loaded = true
}) })
</script> </script>

View File

@ -4,7 +4,7 @@
import * as ComponentLibrary from "@budibase/standard-components" import * as ComponentLibrary from "@budibase/standard-components"
import Router from "./Router.svelte" import Router from "./Router.svelte"
import { enrichProps, propsAreSame } from "../utils/componentProps" import { enrichProps, propsAreSame } from "../utils/componentProps"
import { bindingStore, builderStore } from "../store" import { authStore, bindingStore, builderStore } from "../store"
export let definition = {} export let definition = {}
@ -23,7 +23,7 @@
$: constructor = getComponentConstructor(definition._component) $: constructor = getComponentConstructor(definition._component)
$: children = definition._children || [] $: children = definition._children || []
$: id = definition._id $: id = definition._id
$: enrichComponentProps(definition, $dataContext, $bindingStore) $: enrichComponentProps(definition, $dataContext, $bindingStore, $authStore)
$: updateProps(enrichedProps) $: updateProps(enrichedProps)
$: styles = definition._styles $: styles = definition._styles
@ -67,8 +67,8 @@
} }
// Enriches any string component props using handlebars // Enriches any string component props using handlebars
const enrichComponentProps = async (definition, context, bindingStore) => { const enrichComponentProps = async (definition, context, bindings, user) => {
enrichedProps = await enrichProps(definition, context, bindingStore) enrichedProps = await enrichProps(definition, context, bindings, user)
} }
// Returns a unique key to let svelte know when to remount components. // Returns a unique key to let svelte know when to remount components.

View File

@ -0,0 +1,3 @@
export const TableNames = {
USERS: "ta_users",
}

View File

@ -3,9 +3,10 @@ import { writable, get } from "svelte/store"
import { initialise } from "./initialise" import { initialise } from "./initialise"
import { routeStore } from "./routes" import { routeStore } from "./routes"
import { builderStore } from "./builder" import { builderStore } from "./builder"
import { TableNames } from "../constants"
const createAuthStore = () => { const createAuthStore = () => {
const store = writable("") const store = writable(null)
const goToDefaultRoute = () => { const goToDefaultRoute = () => {
// Setting the active route forces an update of the active screen ID, // Setting the active route forces an update of the active screen ID,
@ -15,16 +16,20 @@ const createAuthStore = () => {
// Navigating updates the URL to reflect this route // Navigating updates the URL to reflect this route
routeStore.actions.navigate("/") routeStore.actions.navigate("/")
} }
// Logs a user in
const logIn = async ({ email, password }) => { const logIn = async ({ email, password }) => {
const user = await API.logIn({ email, password }) const user = await API.logIn({ email, password })
if (!user.error) { if (!user.error) {
store.set(user.token) store.set(user)
await initialise() await initialise()
goToDefaultRoute() goToDefaultRoute()
} }
} }
// Logs a user out
const logOut = async () => { const logOut = async () => {
store.set("") store.set(null)
const appId = get(builderStore).appId const appId = get(builderStore).appId
if (appId) { if (appId) {
for (let environment of ["local", "cloud"]) { for (let environment of ["local", "cloud"]) {
@ -35,9 +40,26 @@ const createAuthStore = () => {
goToDefaultRoute() goToDefaultRoute()
} }
// Fetches the user object if someone is logged in and has reloaded the page
const fetchUser = async () => {
// Fetch the first user if inside the builder
if (get(builderStore).inBuilder) {
const users = await API.fetchTableData(TableNames.USERS)
if (!users.error && users[0] != null) {
store.set(users[0])
}
}
// Or fetch the current user from localstorage in a real app
else {
const user = await API.fetchSelf()
store.set(user)
}
}
return { return {
subscribe: store.subscribe, subscribe: store.subscribe,
actions: { logIn, logOut }, actions: { logIn, logOut, fetchUser },
} }
} }

View File

@ -21,7 +21,7 @@ export const propsAreSame = (a, b) => {
* Enriches component props. * Enriches component props.
* Data bindings are enriched, and button actions are enriched. * Data bindings are enriched, and button actions are enriched.
*/ */
export const enrichProps = async (props, dataContexts, dataBindings) => { export const enrichProps = async (props, dataContexts, dataBindings, user) => {
// Exclude all private props that start with an underscore // Exclude all private props that start with an underscore
let validProps = {} let validProps = {}
Object.entries(props) Object.entries(props)
@ -35,6 +35,7 @@ export const enrichProps = async (props, dataContexts, dataBindings) => {
const context = { const context = {
...dataContexts, ...dataContexts,
...dataBindings, ...dataBindings,
user,
data: dataContexts[dataContexts.closestComponentId], data: dataContexts[dataContexts.closestComponentId],
data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`], data_draft: dataContexts[`${dataContexts.closestComponentId}_draft`],
} }

View File

@ -22,7 +22,8 @@
"maintainer": "Budibase", "maintainer": "Budibase",
"icon": "./build/icons/", "icon": "./build/icons/",
"target": [ "target": [
"deb" "deb",
"AppImage"
], ],
"category": "Development" "category": "Development"
}, },

View File

@ -105,10 +105,16 @@ async function createInstance(template) {
// replicate the template data to the instance DB // replicate the template data to the instance DB
if (template) { if (template) {
const templatePath = await downloadTemplate(...template.key.split("/")) let dbDumpReadStream
const dbDumpReadStream = fs.createReadStream(
join(templatePath, "db", "dump.txt") if (template.fileImportPath) {
) dbDumpReadStream = fs.createReadStream(template.fileImportPath)
} else {
const templatePath = await downloadTemplate(...template.key.split("/"))
dbDumpReadStream = fs.createReadStream(
join(templatePath, "db", "dump.txt")
)
}
const { ok } = await db.load(dbDumpReadStream) const { ok } = await db.load(dbDumpReadStream)
if (!ok) { if (!ok) {
throw "Error loading database dump from template." throw "Error loading database dump from template."

View File

@ -57,3 +57,17 @@ exports.authenticate = async ctx => {
ctx.throw(401, "Invalid credentials.") ctx.throw(401, "Invalid credentials.")
} }
} }
exports.fetchSelf = async ctx => {
const { userId, appId } = ctx.user
if (!userId || !appId) {
ctx.body = {}
} else {
const database = new CouchDB(appId)
const user = await database.get(userId)
if (user) {
delete user.password
}
ctx.body = user
}
}

View File

@ -0,0 +1,41 @@
const { performDump } = require("../../utilities/templates")
const path = require("path")
const os = require("os")
const fs = require("fs-extra")
exports.exportAppDump = async function(ctx) {
const { appId } = ctx.query
const backupsDir = path.join(os.homedir(), ".budibase", "backups")
fs.ensureDirSync(backupsDir)
const backupIdentifier = `${appId} Backup: ${new Date()}.txt`
await performDump({
dir: backupsDir,
appId,
name: backupIdentifier,
})
ctx.status = 200
const backupFile = path.join(backupsDir, backupIdentifier)
ctx.attachment(backupIdentifier)
ctx.body = fs.createReadStream(backupFile)
// ctx.body = {
// url: `/api/backups/download/${backupIdentifier}`,
// }
}
// exports.downloadAppDump = async function(ctx) {
// const fileName = ctx.params.fileName
// const backupsDir = path.join(os.homedir(), ".budibase", "backups")
// fs.ensureDirSync(backupsDir)
// const backupFile = path.join(backupsDir, fileName)
// ctx.attachment(fileName)
// ctx.body = fs.createReadStream(backupFile)
// }

View File

@ -177,7 +177,6 @@ exports.serveApp = async function(ctx) {
}) })
const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8") const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8")
console.log(appHbs)
ctx.body = await processString(appHbs, { ctx.body = await processString(appHbs, {
head, head,
body: html, body: html,

View File

@ -83,7 +83,7 @@ const controller = {
ctx.message = `View ${ctx.params.viewName} saved successfully.` ctx.message = `View ${ctx.params.viewName} saved successfully.`
}, },
exportView: async ctx => { exportView: async ctx => {
const view = ctx.request.body const view = ctx.query.view
const format = ctx.query.format const format = ctx.query.format
// Fetch view rows // Fetch view rows
@ -102,14 +102,6 @@ const controller = {
const filename = `${view.name}.${format}` const filename = `${view.name}.${format}`
fs.writeFileSync(join(os.tmpdir(), filename), exportedFile) fs.writeFileSync(join(os.tmpdir(), filename), exportedFile)
ctx.body = {
url: `/api/views/export/download/${filename}`,
name: view.name,
}
},
downloadExport: async ctx => {
const filename = ctx.params.fileName
ctx.attachment(filename) ctx.attachment(filename)
ctx.body = fs.createReadStream(join(os.tmpdir(), filename)) ctx.body = fs.createReadStream(join(os.tmpdir(), filename))
}, },

View File

@ -5,6 +5,7 @@ const zlib = require("zlib")
const { budibaseAppsDir } = require("../utilities/budibaseDir") const { budibaseAppsDir } = require("../utilities/budibaseDir")
const { isDev } = require("../utilities") const { isDev } = require("../utilities")
const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const { mainRoutes, authRoutes, staticRoutes } = require("./routes")
const pkg = require("../../package.json")
const router = new Router() const router = new Router()
const env = require("../environment") const env = require("../environment")
@ -32,6 +33,7 @@ router
await next() await next()
}) })
.use("/health", ctx => (ctx.status = 200)) .use("/health", ctx => (ctx.status = 200))
.use("/version", ctx => (ctx.body = pkg.version))
.use(authenticated) .use(authenticated)
// error handling middleware // error handling middleware

View File

@ -4,5 +4,6 @@ const controller = require("../controllers/auth")
const router = Router() const router = Router()
router.post("/api/authenticate", controller.authenticate) router.post("/api/authenticate", controller.authenticate)
router.get("/api/self", controller.fetchSelf)
module.exports = router module.exports = router

View File

@ -0,0 +1,15 @@
const Router = require("@koa/router")
const controller = require("../controllers/backup")
const authorized = require("../../middleware/authorized")
const { BUILDER } = require("../../utilities/security/permissions")
const router = Router()
router.get("/api/backups/export", authorized(BUILDER), controller.exportAppDump)
// .get(
// "/api/backups/download/:fileName",
// authorized(BUILDER),
// controller.downloadAppDump
// )
module.exports = router

View File

@ -21,6 +21,7 @@ const permissionRoutes = require("./permission")
const datasourceRoutes = require("./datasource") const datasourceRoutes = require("./datasource")
const queryRoutes = require("./query") const queryRoutes = require("./query")
const hostingRoutes = require("./hosting") const hostingRoutes = require("./hosting")
const backupRoutes = require("./backup")
exports.mainRoutes = [ exports.mainRoutes = [
deployRoutes, deployRoutes,
@ -42,6 +43,7 @@ exports.mainRoutes = [
datasourceRoutes, datasourceRoutes,
queryRoutes, queryRoutes,
hostingRoutes, hostingRoutes,
backupRoutes,
// these need to be handled last as they still use /api/:tableId // these need to be handled last as they still use /api/:tableId
// this could be breaking as koa may recognise other routes as this // this could be breaking as koa may recognise other routes as this
tableRoutes, tableRoutes,

View File

@ -26,10 +26,5 @@ router
) )
.post("/api/views", authorized(BUILDER), usage, viewController.save) .post("/api/views", authorized(BUILDER), usage, viewController.save)
.post("/api/views/export", authorized(BUILDER), viewController.exportView) .post("/api/views/export", authorized(BUILDER), viewController.exportView)
.get(
"/api/views/export/download/:fileName",
authorized(BUILDER),
viewController.downloadExport
)
module.exports = router module.exports = router

View File

@ -10,7 +10,6 @@ const streamPipeline = promisify(stream.pipeline)
const { budibaseAppsDir } = require("./budibaseDir") const { budibaseAppsDir } = require("./budibaseDir")
const env = require("../environment") const env = require("../environment")
const CouchDB = require("../db") const CouchDB = require("../db")
const { DocumentTypes } = require("../db/utils")
const DEFAULT_TEMPLATES_BUCKET = const DEFAULT_TEMPLATES_BUCKET =
"prod-budi-templates.s3-eu-west-1.amazonaws.com" "prod-budi-templates.s3-eu-west-1.amazonaws.com"
@ -56,6 +55,15 @@ exports.downloadTemplate = async function(type, name) {
return dirName return dirName
} }
async function performDump({ dir, appId, name = "dump.txt" }) {
const writeStream = fs.createWriteStream(join(dir, name))
// perform couch dump
const instanceDb = new CouchDB(appId)
await instanceDb.dump(writeStream, {})
}
exports.performDump = performDump
exports.exportTemplateFromApp = async function({ templateName, appId }) { exports.exportTemplateFromApp = async function({ templateName, appId }) {
// Copy frontend files // Copy frontend files
const templatesDir = join( const templatesDir = join(
@ -67,13 +75,6 @@ exports.exportTemplateFromApp = async function({ templateName, appId }) {
"db" "db"
) )
fs.ensureDirSync(templatesDir) fs.ensureDirSync(templatesDir)
const writeStream = fs.createWriteStream(join(templatesDir, "dump.txt")) await performDump({ dir: templatesDir, appId })
// perform couch dump
const instanceDb = new CouchDB(appId)
await instanceDb.dump(writeStream, {
filter: doc => {
return !doc._id.startsWith(DocumentTypes.USER)
},
})
return templatesDir return templatesDir
} }

View File

@ -38,7 +38,7 @@
"license": "MIT", "license": "MIT",
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491", "gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.52.4", "@budibase/bbui": "^1.55.1",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"apexcharts": "^3.22.1", "apexcharts": "^3.22.1",
"flatpickr": "^4.6.6", "flatpickr": "^4.6.6",

View File

@ -135,8 +135,7 @@
} }
const updateRow = async row => { const updateRow = async row => {
const schema = (await API.fetchTableDefinition(row.tableId)).schema await API.updateRow(row)
await API.updateRow(schema, { data: row })
} }
const deleteRows = async () => { const deleteRows = async () => {

View File

@ -39,10 +39,10 @@
lodash "^4.17.19" lodash "^4.17.19"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@budibase/bbui@^1.52.4": "@budibase/bbui@^1.55.1":
version "1.52.4" version "1.55.1"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.52.4.tgz#ae3c17e1f49f14e65831703958bcddc6e64afd24" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.55.1.tgz#291fb6fa10479b49f078d3a911ad0ed42c2e6b12"
integrity sha512-/wiv5dSyvXLgy2/zGEslnCsjwE8qqng1D8k5ScSOPEyMab8tzzd1XxfZAN9rp84zIMgAXeH6s5a4j4riR+jVkg== integrity sha512-bxsHBwkOqCtuFz89e0hAXwvwycfS4xPPrEge5PxK1Lh3uqetO4bXoIxYaIDjfi2Ku7CYIzEmOwSloNaQWeTF4g==
dependencies: dependencies:
markdown-it "^12.0.2" markdown-it "^12.0.2"
quill "^1.3.7" quill "^1.3.7"

View File

@ -13,8 +13,8 @@
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
"@budibase/handlebars-helpers": "^0.11.1",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"handlebars-helpers": "^0.10.0",
"handlebars-utils": "^1.0.6", "handlebars-utils": "^1.0.6",
"helper-date": "^1.0.1", "helper-date": "^1.0.1",
"lodash": "^4.17.20" "lodash": "^4.17.20"
@ -22,7 +22,6 @@
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-replace": "^2.3.4",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"rollup": "^2.36.2", "rollup": "^2.36.2",

View File

@ -3,7 +3,6 @@ import resolve from "rollup-plugin-node-resolve"
import builtins from "rollup-plugin-node-builtins" import builtins from "rollup-plugin-node-builtins"
import globals from "rollup-plugin-node-globals" import globals from "rollup-plugin-node-globals"
import json from "@rollup/plugin-json" import json from "@rollup/plugin-json"
import replace from "@rollup/plugin-replace"
import { terser } from "rollup-plugin-terser" import { terser } from "rollup-plugin-terser"
const production = !process.env.ROLLUP_WATCH const production = !process.env.ROLLUP_WATCH
@ -19,18 +18,6 @@ export default {
}, },
], ],
plugins: [ plugins: [
// this replacement is a crazy hack to fix an issue that
// rollup has with the handlebars-helper package
// if we don't do this then the browser will always error
// with the isNumber function being unavailable
replace({
include: [
"node_modules/handlebars-helpers/lib/**",
"node_modules/handlebar-utils/lib/**",
],
"utils.isNumber(": "!isNaN(",
"isNumber(": "!isNaN(",
}),
resolve({ resolve({
mainFields: ["module", "main"], mainFields: ["module", "main"],
preferBuiltins: true, preferBuiltins: true,

View File

@ -1,10 +1,10 @@
const helpers = require("handlebars-helpers") const helpers = require("@budibase/handlebars-helpers")
const dateHelper = require("helper-date") const dateHelper = require("helper-date")
const { HelperFunctionBuiltin } = require("./constants") const { HelperFunctionBuiltin } = require("./constants")
/** /**
* full list of supported helpers can be found here: * full list of supported helpers can be found here:
* https://github.com/helpers/handlebars-helpers * https://github.com/Budibase/handlebars-helpers
*/ */
const EXTERNAL_FUNCTION_COLLECTIONS = [ const EXTERNAL_FUNCTION_COLLECTIONS = [

View File

@ -270,6 +270,38 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/handlebars-helpers@^0.11.0":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.1.tgz#fe8672612fb4ad8fd3bad338ee15759f45e421eb"
integrity sha512-s72LhOhlHk43QYGVcqjogaGa4h4e6y6X1AfshqWBWceTRYBs/9tUnoYDUiRJ/Mt3WoTmx8Hj5GOZekSqrUsOFQ==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
define-property "^1.0.0"
extend-shallow "^3.0.2"
"falsey" "^0.3.2"
for-in "^1.0.2"
for-own "^1.0.0"
get-object "^0.2.0"
get-value "^2.0.6"
handlebars "^4.0.11"
handlebars-utils "^1.0.6"
has-value "^1.0.0"
helper-date "^1.0.1"
helper-markdown "^1.0.0"
helper-md "^0.2.2"
html-tag "^2.0.0"
is-even "^1.0.0"
is-glob "^4.0.0"
is-number "^4.0.0"
kind-of "^6.0.0"
logging-helpers "^1.0.0"
micromatch "^3.1.4"
relative "^3.0.2"
striptags "^3.1.0"
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@cnakazawa/watch@^1.0.3": "@cnakazawa/watch@^1.0.3":
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@ -485,14 +517,6 @@
dependencies: dependencies:
"@rollup/pluginutils" "^3.0.8" "@rollup/pluginutils" "^3.0.8"
"@rollup/plugin-replace@^2.3.4":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz#7dd84c17755d62b509577f2db37eb524d7ca88ca"
integrity sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ==
dependencies:
"@rollup/pluginutils" "^3.1.0"
magic-string "^0.25.7"
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
@ -1472,16 +1496,6 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0" bn.js "^4.1.0"
elliptic "^6.5.3" elliptic "^6.5.3"
create-frame@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/create-frame/-/create-frame-1.0.0.tgz#8b95f2691e3249b6080443e33d0bad9f8f6975aa"
integrity sha1-i5XyaR4ySbYIBEPjPQutn49pdao=
dependencies:
define-property "^0.2.5"
extend-shallow "^2.0.1"
isobject "^3.0.0"
lazy-cache "^2.0.2"
create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
@ -2153,48 +2167,6 @@ gulp-header@^1.7.1:
lodash.template "^4.4.0" lodash.template "^4.4.0"
through2 "^2.0.0" through2 "^2.0.0"
handlebars-helper-create-frame@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/handlebars-helper-create-frame/-/handlebars-helper-create-frame-0.1.0.tgz#8aa51d10aeb6408fcc6605d40d77356288487a03"
integrity sha1-iqUdEK62QI/MZgXUDXc1YohIegM=
dependencies:
create-frame "^1.0.0"
isobject "^3.0.0"
handlebars-helpers@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/handlebars-helpers/-/handlebars-helpers-0.10.0.tgz#663d49e718928eafbead1473419ed7bc24bcd45a"
integrity sha512-QiyhQz58u/DbuV41VnfpE0nhy6YCH4vB514ajysV8SoKmP+DxU+pR+fahVyNECHj+jiwEN2VrvxD/34/yHaLUg==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
create-frame "^1.0.0"
define-property "^1.0.0"
"falsey" "^0.3.2"
for-in "^1.0.2"
for-own "^1.0.0"
get-object "^0.2.0"
get-value "^2.0.6"
handlebars "^4.0.11"
handlebars-helper-create-frame "^0.1.0"
handlebars-utils "^1.0.6"
has-value "^1.0.0"
helper-date "^1.0.1"
helper-markdown "^1.0.0"
helper-md "^0.2.2"
html-tag "^2.0.0"
is-even "^1.0.0"
is-glob "^4.0.0"
is-number "^4.0.0"
kind-of "^6.0.0"
lazy-cache "^2.0.2"
logging-helpers "^1.0.0"
micromatch "^3.1.4"
relative "^3.0.2"
striptags "^3.1.0"
to-gfm-code-block "^0.1.1"
year "^0.2.1"
handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6: handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9"
@ -3219,7 +3191,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lazy-cache@^2.0.1, lazy-cache@^2.0.2: lazy-cache@^2.0.1:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ= integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
@ -4790,9 +4762,9 @@ typescript@^4.1.3:
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.12.4" version "3.12.6"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.4.tgz#93de48bb76bb3ec0fc36563f871ba46e2ee5c7ee" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.6.tgz#f884584fcc42e10bca70db5cb32e8625c2c42535"
integrity sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A== integrity sha512-aqWHe3DfQmZUDGWBbabZ2eQnJlQd1fKlMUu7gV+MiTuDzdgDw31bI3wA2jLLsV/hNcDP26IfyEgSVoft5+0SVw==
union-value@^1.0.0: union-value@^1.0.0:
version "1.0.1" version "1.0.1"