diff --git a/lerna.json b/lerna.json
index aaafa6837a..39e368fc2f 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "0.1.22",
+ "version": "0.1.23",
"npmClient": "yarn",
"packages": [
"packages/*"
diff --git a/package.json b/package.json
index 10cebbd720..d99718ca02 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"test:e2e:ci": "lerna run cy:ci"
},
"dependencies": {
- "@fortawesome/fontawesome": "^1.1.8"
+ "@fortawesome/fontawesome": "^1.1.8",
+ "pouchdb-replication-stream": "^1.2.9"
}
}
diff --git a/packages/builder/cypress/integration/createAutomation.spec.js b/packages/builder/cypress/integration/createAutomation.spec.js
index 3acd5a7f7a..10556989de 100644
--- a/packages/builder/cypress/integration/createAutomation.spec.js
+++ b/packages/builder/cypress/integration/createAutomation.spec.js
@@ -28,7 +28,7 @@ context("Create a automation", () => {
})
// Create action
- cy.get("[data-cy=SAVE_RECORD]").click()
+ cy.get("[data-cy=CREATE_RECORD]").click()
cy.get("[data-cy=automation-block-setup]").within(() => {
cy.get("select")
.first()
diff --git a/packages/builder/package.json b/packages/builder/package.json
index a983119423..19f8a5a9cf 100644
--- a/packages/builder/package.json
+++ b/packages/builder/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
- "version": "0.1.22",
+ "version": "0.1.23",
"license": "AGPL-3.0",
"private": true,
"scripts": {
@@ -64,7 +64,7 @@
},
"dependencies": {
"@budibase/bbui": "^1.34.6",
- "@budibase/client": "^0.1.22",
+ "@budibase/client": "^0.1.23",
"@budibase/colorpicker": "^1.0.1",
"@fortawesome/fontawesome-free": "^5.14.0",
"@sentry/browser": "5.19.1",
diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js
index 8761d463c6..60a41e42c2 100644
--- a/packages/builder/src/analytics.js
+++ b/packages/builder/src/analytics.js
@@ -3,6 +3,8 @@ import posthog from "posthog-js"
import api from "builderStore/api"
let analyticsEnabled
+const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL
+const sentryConfigured = process.env.SENTRY_DSN
async function activate() {
if (analyticsEnabled === undefined) {
@@ -13,21 +15,22 @@ async function activate() {
analyticsEnabled = (await response.json()) === true
}
if (!analyticsEnabled) return
- Sentry.init({ dsn: process.env.SENTRY_DSN })
- if (!process.env.POSTHOG_TOKEN) return
- posthog.init(process.env.POSTHOG_TOKEN, {
- api_host: process.env.POSTHOG_URL,
- })
- posthog.set_config({ persistence: "cookie" })
+ if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN })
+ if (posthogConfigured) {
+ posthog.init(process.env.POSTHOG_TOKEN, {
+ api_host: process.env.POSTHOG_URL,
+ })
+ posthog.set_config({ persistence: "cookie" })
+ }
}
function identify(id) {
- if (!analyticsEnabled) return
- if (!id) return
- posthog.identify(id)
- Sentry.configureScope(scope => {
- scope.setUser({ id: id })
- })
+ if (!analyticsEnabled || !id) return
+ if (posthogConfigured) posthog.identify(id)
+ if (sentryConfigured)
+ Sentry.configureScope(scope => {
+ scope.setUser({ id: id })
+ })
}
async function identifyByApiKey(apiKey) {
diff --git a/packages/builder/src/components/start/AppList.svelte b/packages/builder/src/components/start/AppList.svelte
index efd448b94a..0a87e63cbb 100644
--- a/packages/builder/src/components/start/AppList.svelte
+++ b/packages/builder/src/components/start/AppList.svelte
@@ -1,20 +1,44 @@
-
-
+
Your Apps
+ {#await promise}
+
+
+
+ {:then apps}
+
-
- {#each apps as app}
-
- {/each}
+
+
+ {#each apps as app}
+
+ {/each}
+
-
+ {:catch err}
+
{err}
+ {/await}
diff --git a/packages/builder/src/pages/index.svelte b/packages/builder/src/pages/index.svelte
index d0c471d30e..8bec57be69 100644
--- a/packages/builder/src/pages/index.svelte
+++ b/packages/builder/src/pages/index.svelte
@@ -5,25 +5,12 @@
import AppList from "components/start/AppList.svelte"
import { onMount } from "svelte"
import ActionButton from "components/common/ActionButton.svelte"
- import { get } from "builderStore/api"
import Spinner from "components/common/Spinner.svelte"
import CreateAppModal from "components/start/CreateAppModal.svelte"
+ import TemplateList from "components/start/TemplateList.svelte"
import { Button } from "@budibase/bbui"
import analytics from "analytics"
- let promise = getApps()
-
- async function getApps() {
- const res = await get("/api/applications")
- const json = await res.json()
-
- if (res.ok) {
- return json
- } else {
- throw new Error(json)
- }
- }
-
let hasKey
async function fetchKeys() {
@@ -37,7 +24,9 @@
if (keys.userId) {
hasKey = true
analytics.identify(keys.userId)
- } else {
+ }
+
+ if (!keys.budibase) {
showCreateAppModal()
}
}
@@ -45,11 +34,12 @@
// Handle create app modal
const { open } = getContext("simple-modal")
- const showCreateAppModal = () => {
+ const showCreateAppModal = template => {
open(
CreateAppModal,
{
hasKey,
+ template,
},
{
closeButton: false,
@@ -66,7 +56,7 @@
@@ -78,15 +68,8 @@
-{#await promise}
-
-
-
-{:then result}
-
-{:catch err}
- {err}
-{/await}
+
+
diff --git a/packages/cli/package.json b/packages/cli/package.json
index ad5614005e..d035553f10 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "budibase",
- "version": "0.1.22",
+ "version": "0.1.23",
"description": "Budibase CLI",
"repository": "https://github.com/Budibase/Budibase",
"homepage": "https://www.budibase.com",
@@ -17,7 +17,7 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@budibase/server": "^0.1.22",
+ "@budibase/server": "^0.1.23",
"@inquirer/password": "^0.0.6-alpha.0",
"chalk": "^2.4.2",
"dotenv": "^8.2.0",
diff --git a/packages/client/package.json b/packages/client/package.json
index 0cfdf53f4d..7b6ea75def 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/client",
- "version": "0.1.22",
+ "version": "0.1.23",
"license": "MPL-2.0",
"main": "dist/budibase-client.js",
"module": "dist/budibase-client.esm.mjs",
diff --git a/packages/client/src/state/eventHandlers.js b/packages/client/src/state/eventHandlers.js
index 788ebf857f..05d8ef2fa3 100644
--- a/packages/client/src/state/eventHandlers.js
+++ b/packages/client/src/state/eventHandlers.js
@@ -1,3 +1,4 @@
+import api from "../api"
import renderTemplateString from "./renderTemplateString"
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
@@ -5,6 +6,9 @@ export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"
export const eventHandlers = routeTo => {
const handlers = {
"Navigate To": param => routeTo(param && param.url),
+ "Create Record": api.createRecord,
+ "Update Record": api.updateRecord,
+ "Trigger Workflow": api.triggerWorkflow,
}
// when an event is called, this is what gets run
diff --git a/packages/server/build/icon.icns b/packages/server/build/icon.icns
index 82da3dde6e..0b609a3bb4 100644
Binary files a/packages/server/build/icon.icns and b/packages/server/build/icon.icns differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff b/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff
deleted file mode 100644
index 72f1207930..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff2 b/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff2
deleted file mode 100644
index 05fda6ab5c..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-300.woff2 and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff b/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff
deleted file mode 100644
index fb70b7e915..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff2 b/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff2
deleted file mode 100644
index 96981bc870..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-500.woff2 and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff b/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff
deleted file mode 100644
index d023f3de32..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff2 b/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff2
deleted file mode 100644
index fd49210352..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-700.woff2 and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff b/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff
deleted file mode 100644
index 397450501b..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff2 b/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff2
deleted file mode 100644
index 5cd76971f1..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-900.woff2 and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff b/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff
deleted file mode 100644
index f9849dfbee..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff and /dev/null differ
diff --git a/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff2 b/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff2
deleted file mode 100644
index 1f7dd5b890..0000000000
Binary files a/packages/server/builder/assets/roboto-v20-latin-ext_latin-regular.woff2 and /dev/null differ
diff --git a/packages/server/builder/assets/rocket.jpg b/packages/server/builder/assets/rocket.jpg
deleted file mode 100644
index cc2edf02a4..0000000000
Binary files a/packages/server/builder/assets/rocket.jpg and /dev/null differ
diff --git a/packages/server/builder/assets/spacex.jpg b/packages/server/builder/assets/spacex.jpg
deleted file mode 100644
index 30004eadd9..0000000000
Binary files a/packages/server/builder/assets/spacex.jpg and /dev/null differ
diff --git a/packages/server/package.json b/packages/server/package.json
index cf55758d8d..384be7a3d1 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -1,6 +1,6 @@
{
"name": "@budibase/server",
- "version": "0.1.22",
+ "version": "0.1.23",
"description": "Budibase Web Server",
"main": "src/electron.js",
"repository": {
@@ -42,7 +42,7 @@
"author": "Michael Shanks",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@budibase/client": "^0.1.22",
+ "@budibase/client": "^0.1.23",
"@koa/router": "^8.0.0",
"@sendgrid/mail": "^7.1.1",
"@sentry/node": "^5.19.2",
diff --git a/packages/server/scripts/exportAppTemplate.js b/packages/server/scripts/exportAppTemplate.js
new file mode 100755
index 0000000000..95ab8c718c
--- /dev/null
+++ b/packages/server/scripts/exportAppTemplate.js
@@ -0,0 +1,40 @@
+#!/usr/bin/env node
+const { exportTemplateFromApp } = require("../src/utilities/templates")
+const yargs = require("yargs")
+
+// Script to export a chosen budibase app into a package
+// Usage: ./scripts/exportAppTemplate.js export --name=Funky --instanceId=someInstanceId --appId=appId
+
+yargs
+ .command(
+ "export",
+ "Export an existing budibase application to the .budibase/templates directory",
+ {
+ name: {
+ description: "The name of the newly exported template",
+ alias: "n",
+ type: "string",
+ },
+ instanceId: {
+ description: "The instanceId to dump the database for",
+ alias: "inst",
+ type: "string",
+ },
+ appId: {
+ description: "The appId of the application you want to export",
+ alias: "app",
+ type: "string",
+ },
+ },
+ async args => {
+ console.log("Exporting app..")
+ const exportPath = await exportTemplateFromApp({
+ templateName: args.name,
+ instanceId: args.instanceId,
+ appId: args.appId,
+ })
+ console.log(`Template ${args.name} exported to ${exportPath}`)
+ }
+ )
+ .help()
+ .alias("help", "h").argv
diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js
index 44b638ed41..ab145a1b76 100644
--- a/packages/server/src/api/controllers/accesslevel.js
+++ b/packages/server/src/api/controllers/accesslevel.js
@@ -1,18 +1,22 @@
const CouchDB = require("../../db")
-const newid = require("../../db/newid")
const {
generateAdminPermissions,
generatePowerUserPermissions,
POWERUSER_LEVEL_ID,
ADMIN_LEVEL_ID,
} = require("../../utilities/accessLevels")
+const {
+ generateAccessLevelID,
+ getAccessLevelParams,
+} = require("../../db/utils")
exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
- const body = await db.query("database/by_type", {
- include_docs: true,
- key: ["accesslevel"],
- })
+ const body = await db.allDocs(
+ getAccessLevelParams(null, {
+ include_docs: true,
+ })
+ )
const customAccessLevels = body.rows.map(row => row.doc)
const staticAccessLevels = [
@@ -90,7 +94,7 @@ exports.create = async function(ctx) {
name: ctx.request.body.name,
_rev: ctx.request.body._rev,
permissions: ctx.request.body.permissions || [],
- _id: newid(),
+ _id: generateAccessLevelID(),
type: "accesslevel",
}
diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js
index eb5ca5ee95..75d6f9544f 100644
--- a/packages/server/src/api/controllers/application.js
+++ b/packages/server/src/api/controllers/application.js
@@ -1,7 +1,6 @@
const CouchDB = require("../../db")
const ClientDb = require("../../db/clientDb")
const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
-const newid = require("../../db/newid")
const env = require("../../environment")
const instanceController = require("./instance")
const { resolve, join } = require("path")
@@ -12,17 +11,18 @@ const setBuilderToken = require("../../utilities/builder/setBuilderToken")
const fs = require("fs-extra")
const { promisify } = require("util")
const chmodr = require("chmodr")
+const { generateAppID, getAppParams } = require("../../db/utils")
const {
downloadExtractComponentLibraries,
} = require("../../utilities/createAppPackage")
exports.fetch = async function(ctx) {
const db = new CouchDB(ClientDb.name(getClientId(ctx)))
- const body = await db.query("client/by_type", {
- include_docs: true,
- key: ["app"],
- })
-
+ const body = await db.allDocs(
+ getAppParams(null, {
+ include_docs: true,
+ })
+ )
ctx.body = body.rows.map(row => row.doc)
}
@@ -48,7 +48,7 @@ exports.create = async function(ctx) {
if (!clientId) {
ctx.throw(400, "ClientId not suplied")
}
- const appId = newid()
+ const appId = generateAppID()
// insert an appId -> clientId lookup
const masterDb = new CouchDB("client_app_lookup")
@@ -66,6 +66,7 @@ exports.create = async function(ctx) {
userInstanceMap: {},
componentLibraries: ["@budibase/standard-components"],
name: ctx.request.body.name,
+ template: ctx.request.body.template,
}
const { rev } = await db.put(newApplication)
@@ -75,9 +76,13 @@ exports.create = async function(ctx) {
appId: newApplication._id,
},
request: {
- body: { name: `dev-${clientId}` },
+ body: {
+ name: `dev-${clientId}`,
+ template: ctx.request.body.template,
+ },
},
}
+
await instanceController.create(createInstCtx)
newApplication.instances.push(createInstCtx.body)
@@ -154,6 +159,19 @@ const createEmptyAppPackage = async (ctx, app) => {
name: npmFriendlyAppName(app.name),
})
+ // if this app is being created from a template,
+ // copy the frontend page definition files from
+ // the template directory.
+ if (app.template) {
+ const templatePageDefinitions = join(
+ appsFolder,
+ "templates",
+ app.template.key,
+ "pages"
+ )
+ await copy(templatePageDefinitions, join(appsFolder, app._id, "pages"))
+ }
+
const mainJson = await updateJsonFile(
join(appsFolder, app._id, "pages", "main", "page.json"),
app
diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js
index ea12e5cc24..828a88bb9b 100644
--- a/packages/server/src/api/controllers/auth.js
+++ b/packages/server/src/api/controllers/auth.js
@@ -2,6 +2,7 @@ const jwt = require("jsonwebtoken")
const CouchDB = require("../../db")
const ClientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt")
+const { generateUserID } = require("../../db/utils")
exports.authenticate = async ctx => {
if (!ctx.user.appId) ctx.throw(400, "No appId")
@@ -35,7 +36,7 @@ exports.authenticate = async ctx => {
let dbUser
try {
- dbUser = await instanceDb.get(`user_${username}`)
+ dbUser = await instanceDb.get(generateUserID(username))
} catch (_) {
// do not want to throw a 404 - as this could be
// used to dtermine valid usernames
diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js
index 2c415fec8b..5414f3878c 100644
--- a/packages/server/src/api/controllers/automation.js
+++ b/packages/server/src/api/controllers/automation.js
@@ -1,8 +1,8 @@
const CouchDB = require("../../db")
-const newid = require("../../db/newid")
const actions = require("../../automations/actions")
const logic = require("../../automations/logic")
const triggers = require("../../automations/triggers")
+const { getAutomationParams, generateAutomationID } = require("../../db/utils")
/*************************
* *
@@ -34,7 +34,7 @@ exports.create = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
let automation = ctx.request.body
- automation._id = newid()
+ automation._id = generateAutomationID()
automation.type = "automation"
automation = cleanAutomationInputs(automation)
@@ -72,10 +72,11 @@ exports.update = async function(ctx) {
exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
- const response = await db.query(`database/by_type`, {
- key: ["automation"],
- include_docs: true,
- })
+ const response = await db.allDocs(
+ getAutomationParams(null, {
+ include_docs: true,
+ })
+ )
ctx.body = response.rows.map(row => row.doc)
}
diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js
index eb7b205240..c0997596e2 100644
--- a/packages/server/src/api/controllers/instance.js
+++ b/packages/server/src/api/controllers/instance.js
@@ -1,9 +1,12 @@
+const fs = require("fs")
const CouchDB = require("../../db")
const client = require("../../db/clientDb")
const newid = require("../../db/newid")
+const { downloadTemplate } = require("../../utilities/templates")
exports.create = async function(ctx) {
const instanceName = ctx.request.body.name
+ const template = ctx.request.body.template
const { appId } = ctx.user
const appShortId = appId.substring(0, 7)
const instanceId = `inst_${appShortId}_${newid()}`
@@ -18,30 +21,7 @@ exports.create = async function(ctx) {
clientId,
applicationId: appId,
},
- views: {
- by_username: {
- map: function(doc) {
- if (doc.type === "user") {
- emit([doc.username], doc._id)
- }
- }.toString(),
- },
- by_type: {
- map: function(doc) {
- emit([doc.type], doc._id)
- }.toString(),
- },
- by_automation_trigger: {
- map: function(doc) {
- if (doc.type === "automation") {
- const trigger = doc.definition.trigger
- if (trigger) {
- emit([trigger.event], trigger)
- }
- }
- }.toString(),
- },
- },
+ views: {},
})
// Add the new instance under the app clientDB
@@ -51,6 +31,16 @@ exports.create = async function(ctx) {
budibaseApp.instances.push(instance)
await clientDb.put(budibaseApp)
+ // replicate the template data to the instance DB
+ if (template) {
+ const templatePath = await downloadTemplate(...template.key.split("/"))
+ const dbDumpReadStream = fs.createReadStream(`${templatePath}/db/dump.txt`)
+ const { ok } = await db.load(dbDumpReadStream)
+ if (!ok) {
+ ctx.throw(500, "Error loading database dump from template.")
+ }
+ }
+
ctx.status = 200
ctx.message = `Instance Database ${instanceName} successfully provisioned.`
ctx.body = instance
diff --git a/packages/server/src/api/controllers/model.js b/packages/server/src/api/controllers/model.js
index 3c3be374c3..1b4c8d7304 100644
--- a/packages/server/src/api/controllers/model.js
+++ b/packages/server/src/api/controllers/model.js
@@ -1,21 +1,24 @@
const CouchDB = require("../../db")
-const newid = require("../../db/newid")
const csvParser = require("../../utilities/csvParser")
+const {
+ getRecordParams,
+ getModelParams,
+ generateModelID,
+} = require("../../db/utils")
exports.fetch = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
- const body = await db.query("database/by_type", {
- include_docs: true,
- key: ["model"],
- })
-
+ const body = await db.allDocs(
+ getModelParams(null, {
+ include_docs: true,
+ })
+ )
ctx.body = body.rows.map(row => row.doc)
}
exports.find = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
- const model = await db.get(ctx.params.id)
- ctx.body = model
+ ctx.body = await db.get(ctx.params.id)
}
exports.save = async function(ctx) {
@@ -23,7 +26,7 @@ exports.save = async function(ctx) {
const { dataImport, ...rest } = ctx.request.body
const modelToSave = {
type: "model",
- _id: newid(),
+ _id: generateModelID(),
views: {},
...rest,
}
@@ -31,9 +34,11 @@ exports.save = async function(ctx) {
// rename record fields when table column is renamed
const { _rename } = modelToSave
if (_rename) {
- const records = await db.query(`database/all_${modelToSave._id}`, {
- include_docs: true,
- })
+ const records = await db.allDocs(
+ getRecordParams(modelToSave._id, null, {
+ include_docs: true,
+ })
+ )
const docs = records.rows.map(({ doc }) => {
doc[_rename.updated] = doc[_rename.old]
delete doc[_rename.old]
@@ -57,7 +62,7 @@ exports.save = async function(ctx) {
modelToSave._rev = result.rev
const { schema } = ctx.request.body
- for (let key in schema) {
+ for (let key of Object.keys(schema)) {
// model has a linked record
if (schema[key].type === "link") {
// create the link field in the other model
@@ -74,19 +79,6 @@ exports.save = async function(ctx) {
}
}
- const designDoc = await db.get("_design/database")
- designDoc.views = {
- ...designDoc.views,
- [`all_${modelToSave._id}`]: {
- map: `function(doc) {
- if (doc.modelId === "${modelToSave._id}") {
- emit(doc._id);
- }
- }`,
- },
- }
- await db.put(designDoc)
-
if (dataImport && dataImport.path) {
// Populate the table with records imported from CSV in a bulk update
const data = await csvParser.transform(dataImport)
@@ -108,16 +100,18 @@ exports.destroy = async function(ctx) {
await db.remove(modelToDelete)
- const modelViewId = `all_${ctx.params.modelId}`
-
// Delete all records for that model
- const records = await db.query(`database/${modelViewId}`)
+ const records = await db.allDocs(
+ getRecordParams(ctx.params.modelId, null, {
+ include_docs: true,
+ })
+ )
await db.bulkDocs(
records.rows.map(record => ({ _id: record.id, _deleted: true }))
)
// Delete linked record fields in dependent models
- for (let key in modelToDelete.schema) {
+ for (let key of Object.keys(modelToDelete.schema)) {
const { type, modelId } = modelToDelete.schema[key]
if (type === "link") {
const linkedModel = await db.get(modelId)
@@ -126,11 +120,6 @@ exports.destroy = async function(ctx) {
}
}
- // delete the "all" view
- const designDoc = await db.get("_design/database")
- delete designDoc.views[modelViewId]
- await db.put(designDoc)
-
ctx.status = 200
ctx.message = `Model ${ctx.params.modelId} deleted.`
}
diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js
index 2626667ef3..7071070fc7 100644
--- a/packages/server/src/api/controllers/record.js
+++ b/packages/server/src/api/controllers/record.js
@@ -1,6 +1,8 @@
const CouchDB = require("../../db")
const validateJs = require("validate.js")
-const newid = require("../../db/newid")
+const { getRecordParams, generateRecordID } = require("../../db/utils")
+
+const MODEL_VIEW_BEGINS_WITH = "all_model:"
function emitEvent(eventType, ctx, record) {
let event = {
@@ -66,7 +68,7 @@ exports.save = async function(ctx) {
record.modelId = ctx.params.modelId
if (!record._rev && !record._id) {
- record._id = newid()
+ record._id = generateRecordID(record.modelId)
}
const model = await db.get(record.modelId)
@@ -133,7 +135,16 @@ exports.save = async function(ctx) {
exports.fetchView = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
const { stats, group, field } = ctx.query
- const response = await db.query(`database/${ctx.params.viewName}`, {
+ const viewName = ctx.params.viewName
+
+ // if this is a model view being looked for just transfer to that
+ if (viewName.indexOf(MODEL_VIEW_BEGINS_WITH) === 0) {
+ ctx.params.modelId = viewName.substring(4)
+ await exports.fetchModelRecords(ctx)
+ return
+ }
+
+ const response = await db.query(`database/${viewName}`, {
include_docs: !stats,
group,
})
@@ -154,9 +165,11 @@ exports.fetchView = async function(ctx) {
exports.fetchModelRecords = async function(ctx) {
const db = new CouchDB(ctx.user.instanceId)
- const response = await db.query(`database/all_${ctx.params.modelId}`, {
- include_docs: true,
- })
+ const response = await db.allDocs(
+ getRecordParams(ctx.params.modelId, null, {
+ include_docs: true,
+ })
+ )
ctx.body = response.rows.map(row => row.doc)
}
diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js
index 663c4c7257..f0388fdb86 100644
--- a/packages/server/src/api/controllers/static.js
+++ b/packages/server/src/api/controllers/static.js
@@ -2,7 +2,7 @@ const send = require("koa-send")
const { resolve, join } = require("path")
const jwt = require("jsonwebtoken")
const fetch = require("node-fetch")
-const fs = require("fs")
+const fs = require("fs-extra")
const uuid = require("uuid")
const AWS = require("aws-sdk")
const { prepareUploadForS3 } = require("./deploy/aws")
diff --git a/packages/server/src/api/controllers/templates.js b/packages/server/src/api/controllers/templates.js
new file mode 100644
index 0000000000..c7be43b4e3
--- /dev/null
+++ b/packages/server/src/api/controllers/templates.js
@@ -0,0 +1,44 @@
+const fetch = require("node-fetch")
+const {
+ downloadTemplate,
+ exportTemplateFromApp,
+} = require("../../utilities/templates")
+
+const DEFAULT_TEMPLATES_BUCKET =
+ "prod-budi-templates.s3-eu-west-1.amazonaws.com"
+
+exports.fetch = async function(ctx) {
+ const { type = "app" } = ctx.query
+
+ const response = await fetch(
+ `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json`
+ )
+ const json = await response.json()
+ ctx.body = Object.values(json.templates[type])
+}
+
+exports.downloadTemplate = async function(ctx) {
+ const { type, name } = ctx.params
+
+ await downloadTemplate(type, name)
+
+ ctx.body = {
+ message: `template ${type}:${name} downloaded successfully.`,
+ }
+}
+
+exports.exportTemplateFromApp = async function(ctx) {
+ const { appId, instanceId } = ctx.user
+ const { templateName } = ctx.request.body
+
+ await exportTemplateFromApp({
+ appId,
+ instanceId,
+ templateName,
+ })
+
+ ctx.status = 200
+ ctx.body = {
+ message: `Created template: ${templateName}`,
+ }
+}
diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js
index 3933e4f790..4e749f0ff7 100644
--- a/packages/server/src/api/controllers/user.js
+++ b/packages/server/src/api/controllers/user.js
@@ -1,7 +1,7 @@
const CouchDB = require("../../db")
const clientDb = require("../../db/clientDb")
const bcrypt = require("../../utilities/bcrypt")
-const getUserId = userName => `user_${userName}`
+const { generateUserID, getUserParams } = require("../../db/utils")
const {
POWERUSER_LEVEL_ID,
ADMIN_LEVEL_ID,
@@ -9,11 +9,11 @@ const {
exports.fetch = async function(ctx) {
const database = new CouchDB(ctx.user.instanceId)
- const data = await database.query("database/by_type", {
- include_docs: true,
- key: ["user"],
- })
-
+ const data = await database.allDocs(
+ getUserParams(null, {
+ include_docs: true,
+ })
+ )
ctx.body = data.rows.map(row => row.doc)
}
@@ -31,7 +31,7 @@ exports.create = async function(ctx) {
if (!accessLevel) ctx.throw(400, "Invalid Access Level")
const user = {
- _id: getUserId(username),
+ _id: generateUserID(username),
username,
password: await bcrypt.hash(password),
name: name || username,
@@ -80,14 +80,14 @@ exports.update = async function(ctx) {
exports.destroy = async function(ctx) {
const database = new CouchDB(ctx.user.instanceId)
- await database.destroy(getUserId(ctx.params.username))
+ await database.destroy(generateUserID(ctx.params.username))
ctx.message = `User ${ctx.params.username} deleted.`
ctx.status = 200
}
exports.find = async function(ctx) {
const database = new CouchDB(ctx.user.instanceId)
- const user = await database.get(getUserId(ctx.params.username))
+ const user = await database.get(generateUserID(ctx.params.username))
ctx.body = {
username: user.username,
name: user.name,
diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js
index 260197aae7..d2ce3b0835 100644
--- a/packages/server/src/api/controllers/view/index.js
+++ b/packages/server/src/api/controllers/view/index.js
@@ -11,18 +11,11 @@ const controller = {
const designDoc = await db.get("_design/database")
const response = []
- for (let name in designDoc.views) {
- if (
- !name.startsWith("all") &&
- name !== "by_type" &&
- name !== "by_username" &&
- name !== "by_automation_trigger"
- ) {
- response.push({
- name,
- ...designDoc.views[name],
- })
- }
+ for (let name of Object.keys(designDoc.views)) {
+ response.push({
+ name,
+ ...designDoc.views[name],
+ })
}
ctx.body = response
diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js
index 0ab10e3e4d..2688baf832 100644
--- a/packages/server/src/api/index.js
+++ b/packages/server/src/api/index.js
@@ -19,6 +19,7 @@ const {
automationRoutes,
accesslevelRoutes,
apiKeysRoutes,
+ templatesRoutes,
analyticsRoutes,
} = require("./routes")
@@ -90,6 +91,9 @@ router.use(automationRoutes.allowedMethods())
router.use(deployRoutes.routes())
router.use(deployRoutes.allowedMethods())
+
+router.use(templatesRoutes.routes())
+router.use(templatesRoutes.allowedMethods())
// end auth routes
router.use(pageRoutes.routes())
diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js
index 0a5b0b1934..d24ef50935 100644
--- a/packages/server/src/api/routes/index.js
+++ b/packages/server/src/api/routes/index.js
@@ -13,6 +13,7 @@ const automationRoutes = require("./automation")
const accesslevelRoutes = require("./accesslevel")
const deployRoutes = require("./deploy")
const apiKeysRoutes = require("./apikeys")
+const templatesRoutes = require("./templates")
const analyticsRoutes = require("./analytics")
module.exports = {
@@ -31,5 +32,6 @@ module.exports = {
automationRoutes,
accesslevelRoutes,
apiKeysRoutes,
+ templatesRoutes,
analyticsRoutes,
}
diff --git a/packages/server/src/api/routes/templates.js b/packages/server/src/api/routes/templates.js
new file mode 100644
index 0000000000..3e481610ce
--- /dev/null
+++ b/packages/server/src/api/routes/templates.js
@@ -0,0 +1,17 @@
+const Router = require("@koa/router")
+const controller = require("../controllers/templates")
+const authorized = require("../../middleware/authorized")
+const { BUILDER } = require("../../utilities/accessLevels")
+
+const router = Router()
+
+router
+ .get("/api/templates", authorized(BUILDER), controller.fetch)
+ .get(
+ "/api/templates/:type/:name",
+ authorized(BUILDER),
+ controller.downloadTemplate
+ )
+ .post("/api/templates", authorized(BUILDER), controller.exportTemplateFromApp)
+
+module.exports = router
diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js
index d7a7200f9a..52fe59ad29 100644
--- a/packages/server/src/api/routes/tests/automation.spec.js
+++ b/packages/server/src/api/routes/tests/automation.spec.js
@@ -10,12 +10,14 @@ const {
destroyDocument,
builderEndpointShouldBlockNormalUsers
} = require("./couchTestUtils")
+let { generateAutomationID } = require("../../../db/utils")
const { delay } = require("./testUtils")
const MAX_RETRIES = 4
+const AUTOMATION_ID = generateAutomationID()
const TEST_AUTOMATION = {
- _id: "Test Automation",
+ _id: AUTOMATION_ID,
name: "My Automation",
pageId: "123123123",
screenId: "kasdkfldsafkl",
@@ -126,14 +128,14 @@ describe("/automations", () => {
it("should setup the automation fully", () => {
let trigger = TRIGGER_DEFINITIONS["RECORD_SAVED"]
trigger.id = "wadiawdo34"
- let saveAction = ACTION_DEFINITIONS["SAVE_RECORD"]
- saveAction.inputs.record = {
+ let createAction = ACTION_DEFINITIONS["CREATE_RECORD"]
+ createAction.inputs.record = {
name: "{{trigger.name}}",
description: "{{trigger.description}}"
}
- saveAction.id = "awde444wk"
+ createAction.id = "awde444wk"
- TEST_AUTOMATION.definition.steps.push(saveAction)
+ TEST_AUTOMATION.definition.steps.push(createAction)
TEST_AUTOMATION.definition.trigger = trigger
})
@@ -206,7 +208,7 @@ describe("/automations", () => {
.expect('Content-Type', /json/)
.expect(200)
- expect(res.body.message).toEqual("Automation Test Automation updated successfully.")
+ expect(res.body.message).toEqual(`Automation ${AUTOMATION_ID} updated successfully.`)
expect(res.body.automation.name).toEqual("Updated Name")
})
})
diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js
index b626919ff6..7d86250f56 100644
--- a/packages/server/src/automations/actions.js
+++ b/packages/server/src/automations/actions.js
@@ -1,5 +1,5 @@
const sendEmail = require("./steps/sendEmail")
-const saveRecord = require("./steps/saveRecord")
+const createRecord = require("./steps/createRecord")
const updateRecord = require("./steps/updateRecord")
const deleteRecord = require("./steps/deleteRecord")
const createUser = require("./steps/createUser")
@@ -17,14 +17,14 @@ const DEFAULT_DIRECTORY = ".budibase-automations"
const AUTOMATION_MANIFEST = "manifest.json"
const BUILTIN_ACTIONS = {
SEND_EMAIL: sendEmail.run,
- SAVE_RECORD: saveRecord.run,
+ CREATE_RECORD: createRecord.run,
UPDATE_RECORD: updateRecord.run,
DELETE_RECORD: deleteRecord.run,
CREATE_USER: createUser.run,
}
const BUILTIN_DEFINITIONS = {
SEND_EMAIL: sendEmail.definition,
- SAVE_RECORD: saveRecord.definition,
+ CREATE_RECORD: createRecord.definition,
UPDATE_RECORD: updateRecord.definition,
DELETE_RECORD: deleteRecord.definition,
CREATE_USER: createUser.definition,
diff --git a/packages/server/src/automations/steps/saveRecord.js b/packages/server/src/automations/steps/createRecord.js
similarity index 92%
rename from packages/server/src/automations/steps/saveRecord.js
rename to packages/server/src/automations/steps/createRecord.js
index ae99511a7c..379aaa02b4 100644
--- a/packages/server/src/automations/steps/saveRecord.js
+++ b/packages/server/src/automations/steps/createRecord.js
@@ -2,12 +2,12 @@ const recordController = require("../../api/controllers/record")
const automationUtils = require("../automationUtils")
module.exports.definition = {
- name: "Save Record",
- tagline: "Save a {{inputs.enriched.model.name}} record",
+ name: "Create Record",
+ tagline: "Create a {{inputs.enriched.model.name}} record",
icon: "ri-save-3-fill",
- description: "Save a record to your database",
+ description: "Create a record to your database",
type: "ACTION",
- stepId: "SAVE_RECORD",
+ stepId: "CREATE_RECORD",
inputs: {},
schema: {
inputs: {
diff --git a/packages/server/src/automations/steps/filter.js b/packages/server/src/automations/steps/filter.js
index d6aa5eb522..4286cd44e8 100644
--- a/packages/server/src/automations/steps/filter.js
+++ b/packages/server/src/automations/steps/filter.js
@@ -55,7 +55,15 @@ module.exports.definition = {
}
module.exports.run = async function filter({ inputs }) {
- const { field, condition, value } = inputs
+ let { field, condition, value } = inputs
+ // coerce types so that we can use them
+ if (!isNaN(value) && !isNaN(field)) {
+ value = parseFloat(value)
+ field = parseFloat(field)
+ } else if (!isNaN(Date.parse(value)) && !isNaN(Date.parse(field))) {
+ value = Date.parse(value)
+ field = Date.parse(field)
+ }
let success
if (typeof field !== "object" && typeof value !== "object") {
switch (condition) {
diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js
index d1d393347c..7da4ae3616 100644
--- a/packages/server/src/automations/triggers.js
+++ b/packages/server/src/automations/triggers.js
@@ -1,6 +1,7 @@
const CouchDB = require("../db")
const emitter = require("../events/index")
const InMemoryQueue = require("./queue/inMemoryQueue")
+const { getAutomationParams } = require("../db/utils")
let automationQueue = new InMemoryQueue()
@@ -89,15 +90,18 @@ async function queueRelevantRecordAutomations(event, eventType) {
throw `No instanceId specified for ${eventType} - check event emitters.`
}
const db = new CouchDB(event.instanceId)
- const automationsToTrigger = await db.query(
- "database/by_automation_trigger",
- {
- key: [eventType],
- include_docs: true,
- }
+ let automations = await db.allDocs(
+ getAutomationParams(null, { include_docs: true })
)
- const automations = automationsToTrigger.rows.map(wf => wf.doc)
+ // filter down to the correct event type
+ automations = automations.rows
+ .map(automation => automation.doc)
+ .filter(automation => {
+ const trigger = automation.definition.trigger
+ return trigger && trigger.event === eventType
+ })
+
for (let automation of automations) {
let automationDef = automation.definition
let automationTrigger = automationDef ? automationDef.trigger : {}
diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js
index 182cd8e032..a3051eea7f 100644
--- a/packages/server/src/db/client.js
+++ b/packages/server/src/db/client.js
@@ -1,4 +1,5 @@
const PouchDB = require("pouchdb")
+const replicationStream = require("pouchdb-replication-stream")
const allDbs = require("pouchdb-all-dbs")
const { budibaseAppsDir } = require("../utilities/budibaseDir")
const env = require("../environment")
@@ -6,6 +7,9 @@ const env = require("../environment")
const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/`
const isInMemory = env.NODE_ENV === "jest"
+PouchDB.plugin(replicationStream.plugin)
+PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
+
let POUCH_DB_DEFAULTS = {
prefix: COUCH_DB_URL,
}
diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js
new file mode 100644
index 0000000000..e66eb7624f
--- /dev/null
+++ b/packages/server/src/db/utils.js
@@ -0,0 +1,152 @@
+const newid = require("./newid")
+
+const DocumentTypes = {
+ MODEL: "model",
+ RECORD: "record",
+ USER: "user",
+ AUTOMATION: "automation",
+ LINK: "link",
+ APP: "app",
+ ACCESS_LEVEL: "accesslevel",
+}
+
+exports.DocumentTypes = DocumentTypes
+
+const UNICODE_MAX = "\ufff0"
+
+/**
+ * If creating DB allDocs/query params with only a single top level ID this can be used, this
+ * is usually the case as most of our docs are top level e.g. models, automations, users and so on.
+ * More complex cases such as link docs and records which have multiple levels of IDs that their
+ * ID consists of need their own functions to build the allDocs parameters.
+ * @param {string} docType The type of document which input params are being built for, e.g. user,
+ * link, app, model and so on.
+ * @param {string|null} docId The ID of the document minus its type - this is only needed if looking
+ * for a singular document.
+ * @param {object} otherProps Add any other properties onto the request, e.g. include_docs.
+ * @returns {object} Parameters which can then be used with an allDocs request.
+ */
+function getDocParams(docType, docId = null, otherProps = {}) {
+ if (docId == null) {
+ docId = ""
+ }
+ return {
+ ...otherProps,
+ startkey: `${docType}:${docId}`,
+ endkey: `${docType}:${docId}${UNICODE_MAX}`,
+ }
+}
+
+/**
+ * Gets parameters for retrieving models, this is a utility function for the getDocParams function.
+ */
+exports.getModelParams = (modelId = null, otherProps = {}) => {
+ return getDocParams(DocumentTypes.MODEL, modelId, otherProps)
+}
+
+/**
+ * Generates a new model ID.
+ * @returns {string} The new model ID which the model doc can be stored under.
+ */
+exports.generateModelID = () => {
+ return `${DocumentTypes.MODEL}:${newid()}`
+}
+
+/**
+ * Gets the DB allDocs/query params for retrieving a record.
+ * @param {string} modelId The model in which the records have been stored.
+ * @param {string|null} recordId The ID of the record which is being specifically queried for. This can be
+ * left null to get all the records in the model.
+ * @param {object} otherProps Any other properties to add to the request.
+ * @returns {object} Parameters which can then be used with an allDocs request.
+ */
+exports.getRecordParams = (modelId, recordId = null, otherProps = {}) => {
+ if (modelId == null) {
+ throw "Cannot build params for records without a model ID"
+ }
+ const endOfKey = recordId == null ? `${modelId}:` : `${modelId}:${recordId}`
+ return getDocParams(DocumentTypes.RECORD, endOfKey, otherProps)
+}
+
+/**
+ * Gets a new record ID for the specified model.
+ * @param {string} modelId The model which the record is being created for.
+ * @returns {string} The new ID which a record doc can be stored under.
+ */
+exports.generateRecordID = modelId => {
+ return `${DocumentTypes.RECORD}:${modelId}:${newid()}`
+}
+
+/**
+ * Gets parameters for retrieving users, this is a utility function for the getDocParams function.
+ */
+exports.getUserParams = (username = null, otherProps = {}) => {
+ return getDocParams(DocumentTypes.USER, username, otherProps)
+}
+
+/**
+ * Generates a new user ID based on the passed in username.
+ * @param {string} username The username which the ID is going to be built up of.
+ * @returns {string} The new user ID which the user doc can be stored under.
+ */
+exports.generateUserID = username => {
+ return `${DocumentTypes.USER}:${username}`
+}
+
+/**
+ * Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
+ */
+exports.getAutomationParams = (automationId = null, otherProps = {}) => {
+ return getDocParams(DocumentTypes.AUTOMATION, automationId, otherProps)
+}
+
+/**
+ * Generates a new automation ID.
+ * @returns {string} The new automation ID which the automation doc can be stored under.
+ */
+exports.generateAutomationID = () => {
+ return `${DocumentTypes.AUTOMATION}:${newid()}`
+}
+
+/**
+ * Generates a new link doc ID. This is currently not usable with the alldocs call,
+ * instead a view is built to make walking to tree easier.
+ * @param {string} modelId1 The ID of the linker model.
+ * @param {string} modelId2 The ID of the linked model.
+ * @param {string} recordId1 The ID of the linker record.
+ * @param {string} recordId2 The ID of the linked record.
+ * @returns {string} The new link doc ID which the automation doc can be stored under.
+ */
+exports.generateLinkID = (modelId1, modelId2, recordId1, recordId2) => {
+ return `${DocumentTypes.AUTOMATION}:${modelId1}:${modelId2}:${recordId1}:${recordId2}`
+}
+
+/**
+ * Generates a new app ID.
+ * @returns {string} The new app ID which the app doc can be stored under.
+ */
+exports.generateAppID = () => {
+ return `${DocumentTypes.APP}:${newid()}`
+}
+
+/**
+ * Gets parameters for retrieving apps, this is a utility function for the getDocParams function.
+ */
+exports.getAppParams = (appId = null, otherProps = {}) => {
+ return getDocParams(DocumentTypes.APP, appId, otherProps)
+}
+
+/**
+ * Generates a new access level ID.
+ * @returns {string} The new access level ID which the access level doc can be stored under.
+ */
+exports.generateAccessLevelID = () => {
+ return `${DocumentTypes.ACCESS_LEVEL}:${newid()}`
+}
+
+/**
+ * Gets parameters for retrieving an access level, this is a utility function for the getDocParams function.
+ */
+exports.getAccessLevelParams = (accessLevelId = null, otherProps = {}) => {
+ return getDocParams(DocumentTypes.ACCESS_LEVEL, accessLevelId, otherProps)
+}
diff --git a/packages/server/src/utilities/templates.js b/packages/server/src/utilities/templates.js
new file mode 100644
index 0000000000..4aef7f7db3
--- /dev/null
+++ b/packages/server/src/utilities/templates.js
@@ -0,0 +1,57 @@
+const path = require("path")
+const fs = require("fs-extra")
+const os = require("os")
+const fetch = require("node-fetch")
+const stream = require("stream")
+const tar = require("tar-fs")
+const zlib = require("zlib")
+const { promisify } = require("util")
+const streamPipeline = promisify(stream.pipeline)
+const { budibaseAppsDir } = require("./budibaseDir")
+const CouchDB = require("../db")
+
+const DEFAULT_TEMPLATES_BUCKET =
+ "prod-budi-templates.s3-eu-west-1.amazonaws.com"
+
+exports.downloadTemplate = async function(type, name) {
+ const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz`
+ const response = await fetch(templateUrl)
+
+ if (!response.ok) {
+ throw new Error(
+ `Error downloading template ${type}:${name}: ${response.statusText}`
+ )
+ }
+
+ // stream the response, unzip and extract
+ await streamPipeline(
+ response.body,
+ zlib.Unzip(),
+ tar.extract(path.join(budibaseAppsDir(), "templates", type))
+ )
+
+ return path.join(budibaseAppsDir(), "templates", type, name)
+}
+
+exports.exportTemplateFromApp = async function({
+ appId,
+ templateName,
+ instanceId,
+}) {
+ // Copy frontend files
+ const appToExport = path.join(os.homedir(), ".budibase", appId, "pages")
+ const templatesDir = path.join(os.homedir(), ".budibase", "templates")
+ fs.ensureDirSync(templatesDir)
+
+ const templateOutputPath = path.join(templatesDir, templateName)
+ fs.copySync(appToExport, `${templateOutputPath}/pages`)
+
+ fs.ensureDirSync(path.join(templateOutputPath, "db"))
+ const writeStream = fs.createWriteStream(`${templateOutputPath}/db/dump.txt`)
+
+ // perform couch dump
+ const instanceDb = new CouchDB(instanceId)
+
+ await instanceDb.dump(writeStream)
+ return templateOutputPath
+}
diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json
index e3fac8c3d6..53ea1883da 100644
--- a/packages/standard-components/package.json
+++ b/packages/standard-components/package.json
@@ -13,7 +13,7 @@
"dev:builder": "rollup -cw"
},
"devDependencies": {
- "@budibase/client": "^0.1.22",
+ "@budibase/client": "^0.1.23",
"@rollup/plugin-commonjs": "^11.1.0",
"lodash": "^4.17.15",
"rollup": "^1.11.0",
@@ -31,7 +31,7 @@
"keywords": [
"svelte"
],
- "version": "0.1.22",
+ "version": "0.1.23",
"license": "MIT",
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
"dependencies": {
diff --git a/readme.md b/readme.md
index fe2eeeaad4..ec3c39bd59 100644
--- a/readme.md
+++ b/readme.md
@@ -1,3 +1,9 @@
+# Budibase is in Beta
+
+Budibase is currently beta software. Until our official launch, we cannot ensure backwards compatibility for your budibase applications between versions. Issues may arise when trying to edit apps created with old versions of the budibase builder.
+
+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.
+
# What is Budibase?
diff --git a/yarn.lock b/yarn.lock
index 8c37dc6f01..2022a94d16 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -913,6 +913,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+argsarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb"
+ integrity sha1-bnIHtOzbObCviDA/pa4ivajfYcs=
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -2331,6 +2336,11 @@ ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+immediate@~3.0.5:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+ integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
+
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
@@ -2377,7 +2387,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
@@ -2628,6 +2638,11 @@ is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -2790,6 +2805,13 @@ libnpmpublish@^1.1.1:
semver "^5.5.1"
ssri "^6.0.1"
+lie@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
+ integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
+ dependencies:
+ immediate "~3.0.5"
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -2839,6 +2861,11 @@ lodash.ismatch@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
+lodash.pick@^4.0.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
+ integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
+
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
@@ -3177,6 +3204,16 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+ndjson@^1.4.3:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/ndjson/-/ndjson-1.5.0.tgz#ae603b36b134bcec347b452422b0bf98d5832ec8"
+ integrity sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=
+ dependencies:
+ json-stringify-safe "^5.0.1"
+ minimist "^1.2.0"
+ split2 "^2.1.0"
+ through2 "^2.0.3"
+
neo-async@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
@@ -3680,6 +3717,34 @@ posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
+pouch-stream@^0.4.0:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/pouch-stream/-/pouch-stream-0.4.1.tgz#0c6d8475c9307677627991a2f079b301c3b89bdd"
+ integrity sha1-DG2EdckwdndieZGi8HmzAcO4m90=
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "^1.0.27-1"
+
+pouchdb-promise@^6.0.4:
+ version "6.4.3"
+ resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.4.3.tgz#74516f4acf74957b54debd0fb2c0e5b5a68ca7b3"
+ integrity sha512-ruJaSFXwzsxRHQfwNHjQfsj58LBOY1RzGzde4PM5CWINZwFjCQAhZwfMrch2o/0oZT6d+Xtt0HTWhq35p3b0qw==
+ dependencies:
+ lie "3.1.1"
+
+pouchdb-replication-stream@^1.2.9:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/pouchdb-replication-stream/-/pouchdb-replication-stream-1.2.9.tgz#aa4fa5d8f52df4825392f18e07c7e11acffc650a"
+ integrity sha1-qk+l2PUt9IJTkvGOB8fhGs/8ZQo=
+ dependencies:
+ argsarray "0.0.1"
+ inherits "^2.0.3"
+ lodash.pick "^4.0.0"
+ ndjson "^1.4.3"
+ pouch-stream "^0.4.0"
+ pouchdb-promise "^6.0.4"
+ through2 "^2.0.0"
+
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -3862,6 +3927,16 @@ read@1, read@~1.0.1:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
+readable-stream@^1.0.27-1:
+ version "1.1.14"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+ integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
readdir-scoped-modules@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
@@ -4216,7 +4291,7 @@ split-string@^3.0.1, split-string@^3.0.2:
dependencies:
extend-shallow "^3.0.0"
-split2@^2.0.0:
+split2@^2.0.0, split2@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493"
dependencies:
@@ -4321,6 +4396,11 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -4441,7 +4521,7 @@ text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-through2@^2.0.0, through2@^2.0.2:
+through2@^2.0.0, through2@^2.0.2, through2@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
dependencies: