revert functionality working

This commit is contained in:
Martin McKeaveney 2021-05-16 21:25:37 +01:00
parent 332f0555a3
commit 0f2bcf581d
26 changed files with 266 additions and 73 deletions

View File

@ -31,7 +31,7 @@ class Replication {
* Two way replication operation, intended to be promise based. * Two way replication operation, intended to be promise based.
* @param {Object} opts - PouchDB replication options * @param {Object} opts - PouchDB replication options
*/ */
sync(opts) { sync(opts = {}) {
this.replication = this.promisify(this.source.sync, opts) this.replication = this.promisify(this.source.sync, opts)
return this.replication return this.replication
} }
@ -40,7 +40,7 @@ class Replication {
* One way replication operation, intended to be promise based. * One way replication operation, intended to be promise based.
* @param {Object} opts - PouchDB replication options * @param {Object} opts - PouchDB replication options
*/ */
replicate(opts) { replicate(opts = {}) {
this.replication = this.promisify(this.source.replicate.to, opts) this.replication = this.promisify(this.source.replicate.to, opts)
return this.replication return this.replication
} }
@ -61,8 +61,13 @@ class Replication {
}) })
} }
/**
* Rollback the target DB back to the state of the source DB
*/
async rollback() { async rollback() {
await this.target.destroy() await this.target.destroy()
// Recreate the DB again
this.target = getDB(this.target.name)
await this.replicate() await this.replicate()
} }

View File

@ -12,6 +12,9 @@ exports.StaticDatabases = {
GLOBAL: { GLOBAL: {
name: "global-db", name: "global-db",
}, },
DEPLOYMENTS: {
name: "deployments",
},
} }
const DocumentTypes = { const DocumentTypes = {
@ -21,6 +24,7 @@ const DocumentTypes = {
TEMPLATE: "template", TEMPLATE: "template",
APP: "app", APP: "app",
APP_DEV: "app_dev", APP_DEV: "app_dev",
APP_METADATA: "app_metadata",
} }
exports.DocumentTypes = DocumentTypes exports.DocumentTypes = DocumentTypes

View File

@ -51,14 +51,14 @@ export const getFrontendStore = () => {
store.actions = { store.actions = {
initialise: async pkg => { initialise: async pkg => {
const { layouts, screens, application, clientLibPath } = pkg const { layouts, screens, application, clientLibPath } = pkg
const components = await fetchComponentLibDefinitions(application._id) const components = await fetchComponentLibDefinitions(application.appId)
store.update(state => ({ store.update(state => ({
...state, ...state,
libraries: application.componentLibraries, libraries: application.componentLibraries,
components, components,
name: application.name, name: application.name,
description: application.description, description: application.description,
appId: application._id, appId: application.appId,
url: application.url, url: application.url,
layouts, layouts,
screens, screens,

View File

@ -0,0 +1,110 @@
<script>
import { onMount, onDestroy } from "svelte"
import { Button, Modal, notifications, ModalContent } from "@budibase/bbui"
import { store } from "builderStore"
import api from "builderStore/api"
import analytics from "analytics"
import FeedbackIframe from "components/feedback/FeedbackIframe.svelte"
const DeploymentStatus = {
SUCCESS: "SUCCESS",
PENDING: "PENDING",
FAILURE: "FAILURE",
}
const POLL_INTERVAL = 1000
let loading = false
let feedbackModal
let deployments = []
let poll
let publishModal
$: appId = $store.appId
async function deployApp() {
try {
notifications.info(`Deployment started. Please wait.`)
const response = await api.post("/api/deploy")
const json = await response.json()
if (response.status !== 200) {
throw new Error()
}
if (analytics.requestFeedbackOnDeploy()) {
feedbackModal.show()
}
} catch (err) {
analytics.captureException(err)
notifications.error("Deployment unsuccessful. Please try again later.")
}
}
async function fetchDeployments() {
try {
const response = await api.get(`/api/deployments`)
const json = await response.json()
if (deployments.length > 0) {
checkIncomingDeploymentStatus(deployments, json)
}
deployments = json
} catch (err) {
console.error(err)
clearInterval(poll)
notifications.error(
"Error fetching deployment history. Please try again."
)
}
}
// Required to check any updated deployment statuses between polls
function checkIncomingDeploymentStatus(current, incoming) {
for (let incomingDeployment of incoming) {
if (incomingDeployment.status === DeploymentStatus.FAILURE || incomingDeployment.status === DeploymentStatus.SUCCESS) {
const currentDeployment = current.find(
deployment => deployment._id === incomingDeployment._id
)
// We have just been notified of an ongoing deployments status change
if (
!currentDeployment ||
currentDeployment.status === DeploymentStatus.PENDING
) {
if (incomingDeployment.status === DeploymentStatus.FAILURE) {
notifications.error(incomingDeployment.err)
} else {
notifications.send("Published to Production.", "success", "CheckmarkCircle")
}
}
}
}
}
onMount(() => {
fetchDeployments()
poll = setInterval(fetchDeployments, POLL_INTERVAL)
})
onDestroy(() => clearInterval(poll))
</script>
<Button
secondary
on:click={publishModal.show}
>
Publish
</Button>
<Modal bind:this={publishModal}>
<ModalContent
title="Publish to Production"
confirmText="Publish"
onConfirm={deployApp}
>
<span>The changes you have made will be published to the production version of the application.</span>
</ModalContent>
</Modal>

View File

@ -0,0 +1,35 @@
<script>
import { onMount, onDestroy } from "svelte"
import { Button, Icon, Modal, notifications, ModalContent } from "@budibase/bbui"
import { store } from "builderStore"
import { apps } from "stores/portal"
import api from "builderStore/api"
let revertModal
$: appId = $store.appId
const revert = async () => {
try {
const response = await api.post(`/api/dev/${appId}/revert`)
const json = await response.json()
if (response.status !== 200) throw json.message
notifications.info("Changes reverted.")
} catch (err) {
notifications.error(`Error reverting changes: ${err}`)
}
}
</script>
<Icon name="Revert" hoverable on:click={revertModal.show} />
<Modal bind:this={revertModal}>
<ModalContent
title="Revert Changes"
confirmText="Revert"
onConfirm={revert}
>
<span>The changes you have made will be deleted and the application reverted back to its production state.</span>
</ModalContent>
</Modal>

View File

@ -41,7 +41,7 @@
</MenuItem> </MenuItem>
{/if} {/if}
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email} {#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen"> <MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
Release Lock Release Lock
</MenuItem> </MenuItem>
{/if} {/if}

View File

@ -50,7 +50,7 @@
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem> <MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
{/if} {/if}
{#if app.lockedBy && app.lockedBy?.email === $auth.user?.email} {#if app.lockedBy && app.lockedBy?.email === $auth.user?.email}
<MenuItem on:click={() => releaseLock(app._id)} icon="LockOpen"> <MenuItem on:click={() => releaseLock(app.appId)} icon="LockOpen">
Release Lock Release Lock
</MenuItem> </MenuItem>
{/if} {/if}

View File

@ -96,7 +96,7 @@
// Select Correct Application/DB in prep for creating user // Select Correct Application/DB in prep for creating user
const applicationPkg = await get( const applicationPkg = await get(
`/api/applications/${appJson._id}/appPackage` `/api/applications/${appJson.instance._id}/appPackage`
) )
const pkg = await applicationPkg.json() const pkg = await applicationPkg.json()
if (applicationPkg.ok) { if (applicationPkg.ok) {
@ -112,7 +112,7 @@
} }
const userResp = await api.post(`/api/users/metadata/self`, user) const userResp = await api.post(`/api/users/metadata/self`, user)
await userResp.json() await userResp.json()
$goto(`/builder/app/${appJson._id}`) $goto(`/builder/app/${appJson.instance._id}`)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
notifications.error(error) notifications.error(error)

View File

@ -1,10 +1,12 @@
<script> <script>
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { roles } from "stores/backend" import { roles } from "stores/backend"
import { Button, ActionGroup, ActionButton, Tabs, Tab } from "@budibase/bbui" import { Button, Icon, Modal, ModalContent, ActionGroup, ActionButton, Tabs, Tab } from "@budibase/bbui"
import SettingsLink from "components/settings/Link.svelte" import SettingsLink from "components/settings/Link.svelte"
import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte" import ThemeEditorDropdown from "components/settings/ThemeEditorDropdown.svelte"
import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte" import FeedbackNavLink from "components/feedback/FeedbackNavLink.svelte"
import DeployModal from "components/deploy/DeployModal.svelte"
import RevertModal from "components/deploy/RevertModal.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
import { isActive, goto, layout } from "@roxi/routify" import { isActive, goto, layout } from "@roxi/routify"
import Logo from "/assets/bb-logo.svg" import Logo from "/assets/bb-logo.svg"
@ -81,25 +83,13 @@
<ActionGroup /> <ActionGroup />
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<ThemeEditorDropdown /> <RevertModal />
<FeedbackNavLink /> <Icon name="Play" hoverable
<div class="topnavitemright">
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions"
>
<i class="ri-github-fill" />
</a>
</div>
<SettingsLink />
<Button
secondary
on:click={() => { on:click={() => {
window.open(`/${application}`) window.open(`/${application}`)
}} }}
> />
Preview <DeployModal />
</Button>
</div> </div>
</div> </div>
<div class="beta"> <div class="beta">
@ -153,6 +143,7 @@
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
gap: var(--spacing-xl);
} }
.topleftnav { .topleftnav {

View File

@ -1,2 +1,2 @@
<!-- routify:options index=4 --> <!-- routify:options index=4 -->
<slot /> <!-- <slot /> -->

View File

@ -62,16 +62,16 @@
const openApp = app => { const openApp = app => {
if (appStatus === AppStatus.DEV) { if (appStatus === AppStatus.DEV) {
$goto(`../../app/${app._id}`) $goto(`../../app/${app.appId}`)
} else { } else {
window.open(`/${app._id}`, '_blank'); window.open(`/${app.appId}`, '_blank');
} }
} }
const exportApp = app => { const exportApp = app => {
try { try {
download( download(
`/api/backups/export?appId=${app._id}&appname=${encodeURIComponent( `/api/backups/export?appId=${app.appId}&appname=${encodeURIComponent(
app.name app.name
)}` )}`
) )
@ -157,7 +157,7 @@
class:appGrid={layout === "grid"} class:appGrid={layout === "grid"}
class:appTable={layout === "table"} class:appTable={layout === "table"}
> >
{#each $apps as app, idx (app._id)} {#each $apps as app, idx (app.appId)}
<svelte:component <svelte:component
this={layout === "grid" ? AppCard : AppRow} this={layout === "grid" ? AppCard : AppRow}
deletable={appStatus === AppStatus.PUBLISHED} deletable={appStatus === AppStatus.PUBLISHED}

View File

@ -13,6 +13,7 @@ export function createAppStore() {
} else { } else {
store.set([]) store.set([])
} }
return json
} catch (error) { } catch (error) {
store.set([]) store.set([])
} }

View File

@ -1,4 +1,4 @@
export { organisation } from "./organisation" export { organisation } from "./organisation"
export { admin } from "./admin" export { admin } from "./admin"
export { apps } from "./apps" export { apps } from "./apps"
export { email } from "./email" export { email } from "./email"

View File

@ -6,6 +6,7 @@
*/ */
const CouchDB = require("../src/db") const CouchDB = require("../src/db")
const { DocumentTypes } = require("../src/db/utils")
const appName = process.argv[2].toLowerCase() const appName = process.argv[2].toLowerCase()
const remoteUrl = process.argv[3] const remoteUrl = process.argv[3]
@ -18,7 +19,7 @@ const run = async () => {
let apps = [] let apps = []
for (let dbName of appDbNames) { for (let dbName of appDbNames) {
const db = new CouchDB(dbName) const db = new CouchDB(dbName)
apps.push(db.get(dbName)) apps.push(db.get(DocumentTypes.APP_METADATA))
} }
apps = await Promise.all(apps) apps = await Promise.all(apps)
const app = apps.find( const app = apps.find(
@ -32,7 +33,7 @@ const run = async () => {
return return
} }
const instanceDb = new CouchDB(app._id) const instanceDb = new CouchDB(app.appId)
const remoteDb = new CouchDB(`${remoteUrl}/${appName}`) const remoteDb = new CouchDB(`${remoteUrl}/${appName}`)
instanceDb.replicate instanceDb.replicate

View File

@ -88,7 +88,6 @@ async function getAppUrlIfNotInUse(ctx) {
} }
async function createInstance(template) { async function createInstance(template) {
// TODO: Do we need the normal app ID?
const baseAppId = generateAppID() const baseAppId = generateAppID()
const appId = generateDevAppID(baseAppId) const appId = generateDevAppID(baseAppId)
@ -126,16 +125,16 @@ exports.fetch = async function (ctx) {
const isDev = ctx.query.status === AppStatus.DEV const isDev = ctx.query.status === AppStatus.DEV
apps = apps.filter(app => { apps = apps.filter(app => {
if (isDev) { if (isDev) {
return app._id.startsWith(DocumentTypes.APP_DEV) return app.appId.startsWith(DocumentTypes.APP_DEV)
} }
return !app._id.startsWith(DocumentTypes.APP_DEV) return !app.appId.startsWith(DocumentTypes.APP_DEV)
}) })
// get the locks for all the dev apps // get the locks for all the dev apps
if (isDev) { if (isDev) {
const locks = await getAllLocks() const locks = await getAllLocks()
for (let app of apps) { for (let app of apps) {
const lock = locks.find(lock => lock.appId === app._id) const lock = locks.find(lock => lock.appId === app.appId)
if (lock) { if (lock) {
app.lockedBy = lock.user app.lockedBy = lock.user
} else { } else {
@ -166,7 +165,7 @@ exports.fetchAppDefinition = async function (ctx) {
exports.fetchAppPackage = async function (ctx) { exports.fetchAppPackage = async function (ctx) {
const db = new CouchDB(ctx.params.appId) const db = new CouchDB(ctx.params.appId)
const application = await db.get(ctx.params.appId) const application = await db.get(DocumentTypes.APP_METADATA)
const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)]) const [layouts, screens] = await Promise.all([getLayouts(db), getScreens(db)])
ctx.body = { ctx.body = {
@ -191,7 +190,8 @@ exports.create = async function (ctx) {
const url = await getAppUrlIfNotInUse(ctx) const url = await getAppUrlIfNotInUse(ctx)
const appId = instance._id const appId = instance._id
const newApplication = { const newApplication = {
_id: appId, _id: DocumentTypes.APP_METADATA,
appId: instance._id,
type: "app", type: "app",
version: packageJson.version, version: packageJson.version,
componentLibraries: ["@budibase/standard-components"], componentLibraries: ["@budibase/standard-components"],
@ -254,7 +254,7 @@ exports.delete = async function (ctx) {
} }
const createEmptyAppPackage = async (ctx, app) => { const createEmptyAppPackage = async (ctx, app) => {
const db = new CouchDB(app._id) const db = new CouchDB(app.instance._id)
let screensAndLayouts = [] let screensAndLayouts = []
for (let layout of BASE_LAYOUTS) { for (let layout of BASE_LAYOUTS) {

View File

@ -1,10 +1,11 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const { DocumentTypes } = require("../../db/utils")
const { getComponentLibraryManifest } = require("../../utilities/fileSystem") const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
exports.fetchAppComponentDefinitions = async function (ctx) { exports.fetchAppComponentDefinitions = async function (ctx) {
const appId = ctx.params.appId || ctx.appId const appId = ctx.params.appId || ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const app = await db.get(appId) const app = await db.get(DocumentTypes.APP_METADATA)
let componentManifests = await Promise.all( let componentManifests = await Promise.all(
app.componentLibraries.map(async library => { app.componentLibraries.map(async library => {

View File

@ -1,6 +1,8 @@
const PouchDB = require("../../../db") const PouchDB = require("../../../db")
const Deployment = require("./Deployment") const Deployment = require("./Deployment")
const { Replication } = require("@budibase/auth").db const { Replication, StaticDatabases } = require("@budibase/auth").db
const { DocumentTypes } = require("../../../db/utils")
// the max time we can wait for an invalidation to complete before considering it failed // the max time we can wait for an invalidation to complete before considering it failed
const MAX_PENDING_TIME_MS = 30 * 60000 const MAX_PENDING_TIME_MS = 30 * 60000
const DeploymentStatus = { const DeploymentStatus = {
@ -26,16 +28,16 @@ async function checkAllDeployments(deployments) {
return { updated, deployments } return { updated, deployments }
} }
async function storeLocalDeploymentHistory(deployment) { async function storeDeploymentHistory(deployment) {
const appId = deployment.getAppId() const appId = deployment.getAppId()
const deploymentJSON = deployment.getJSON() const deploymentJSON = deployment.getJSON()
const db = new PouchDB(appId) const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
let deploymentDoc let deploymentDoc
try { try {
deploymentDoc = await db.get("_local/deployments") deploymentDoc = await db.get(appId)
} catch (err) { } catch (err) {
deploymentDoc = { _id: "_local/deployments", history: {} } deploymentDoc = { _id: appId, history: {} }
} }
const deploymentId = deploymentJSON._id const deploymentId = deploymentJSON._id
@ -65,12 +67,9 @@ async function deployApp(deployment) {
}) })
await replication.replicate() await replication.replicate()
// Strip the _dev prefix and update the appID document in the new DB
const db = new PouchDB(productionAppId) const db = new PouchDB(productionAppId)
const appDoc = await db.get(deployment.appId) const appDoc = await db.get(DocumentTypes.APP_METADATA)
appDoc._id = productionAppId appDoc.appId = productionAppId
delete appDoc._rev
appDoc.instance._id = productionAppId appDoc.instance._id = productionAppId
await db.put(appDoc) await db.put(appDoc)
@ -79,13 +78,17 @@ async function deployApp(deployment) {
source: productionAppId, source: productionAppId,
target: deployment.appId, target: deployment.appId,
}) })
liveReplication.subscribe() liveReplication.subscribe({
filter: function (doc) {
return doc._id !== DocumentTypes.APP_METADATA
},
})
deployment.setStatus(DeploymentStatus.SUCCESS) deployment.setStatus(DeploymentStatus.SUCCESS)
await storeLocalDeploymentHistory(deployment) await storeDeploymentHistory(deployment)
} catch (err) { } catch (err) {
deployment.setStatus(DeploymentStatus.FAILURE, err.message) deployment.setStatus(DeploymentStatus.FAILURE, err.message)
await storeLocalDeploymentHistory(deployment) await storeDeploymentHistory(deployment)
throw { throw {
...err, ...err,
message: `Deployment Failed: ${err.message}`, message: `Deployment Failed: ${err.message}`,
@ -95,8 +98,8 @@ async function deployApp(deployment) {
exports.fetchDeployments = async function (ctx) { exports.fetchDeployments = async function (ctx) {
try { try {
const db = new PouchDB(ctx.appId) const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
const deploymentDoc = await db.get("_local/deployments") const deploymentDoc = await db.get(ctx.appId)
const { updated, deployments } = await checkAllDeployments( const { updated, deployments } = await checkAllDeployments(
deploymentDoc, deploymentDoc,
ctx.user ctx.user
@ -112,8 +115,8 @@ exports.fetchDeployments = async function (ctx) {
exports.deploymentProgress = async function (ctx) { exports.deploymentProgress = async function (ctx) {
try { try {
const db = new PouchDB(ctx.appId) const db = new PouchDB(StaticDatabases.DEPLOYMENTS.name)
const deploymentDoc = await db.get("_local/deployments") const deploymentDoc = await db.get(ctx.appId)
ctx.body = deploymentDoc[ctx.params.deploymentId] ctx.body = deploymentDoc[ctx.params.deploymentId]
} catch (err) { } catch (err) {
ctx.throw( ctx.throw(
@ -126,7 +129,7 @@ exports.deploymentProgress = async function (ctx) {
exports.deployApp = async function (ctx) { exports.deployApp = async function (ctx) {
let deployment = new Deployment(ctx.appId) let deployment = new Deployment(ctx.appId)
deployment.setStatus(DeploymentStatus.PENDING) deployment.setStatus(DeploymentStatus.PENDING)
deployment = await storeLocalDeploymentHistory(deployment) deployment = await storeDeploymentHistory(deployment)
await deployApp(deployment) await deployApp(deployment)

View File

@ -1,8 +1,11 @@
const fetch = require("node-fetch") const fetch = require("node-fetch")
const CouchDB = require("../../db")
const env = require("../../environment") const env = require("../../environment")
const { checkSlashesInUrl } = require("../../utilities") const { checkSlashesInUrl } = require("../../utilities")
const { request } = require("../../utilities/workerRequests") const { request } = require("../../utilities/workerRequests")
const { clearLock } = require("../../utilities/redis") const { clearLock } = require("../../utilities/redis")
const { Replication } = require("@budibase/auth").db
const { DocumentTypes } = require("../../db/utils")
async function redirect(ctx, method) { async function redirect(ctx, method) {
const { devPath } = ctx.params const { devPath } = ctx.params
@ -45,3 +48,37 @@ exports.clearLock = async ctx => {
message: "Lock released successfully.", message: "Lock released successfully.",
} }
} }
exports.revert = async ctx => {
const { appId } = ctx.params
const productionAppId = appId.replace("_dev", "")
// App must have been deployed first
try {
const db = new CouchDB(productionAppId, { skip_setup: true })
const info = await db.info()
if (info.error) throw info.error
} catch (err) {
return ctx.throw(400, "App has not yet been deployed")
}
try {
const replication = new Replication({
source: productionAppId,
target: appId,
})
await replication.rollback()
// update appID in reverted app to be dev version again
const db = new CouchDB(appId)
const appDoc = await db.get(DocumentTypes.APP_METADATA)
appDoc.appId = appId
appDoc.instance._id = appId
await db.put(appDoc)
ctx.body = {
message: "Reverted changes successfully.",
}
} catch (err) {
ctx.throw(400, `Unable to revert. ${err}`)
}
}

View File

@ -18,6 +18,7 @@ const env = require("../../../environment")
const { objectStoreUrl, clientLibraryPath } = require("../../../utilities") const { objectStoreUrl, clientLibraryPath } = require("../../../utilities")
const { upload } = require("../../../utilities/fileSystem") const { upload } = require("../../../utilities/fileSystem")
const { attachmentsRelativeURL } = require("../../../utilities") const { attachmentsRelativeURL } = require("../../../utilities")
const { DocumentTypes } = require("../../../db/utils")
async function prepareUpload({ s3Key, bucket, metadata, file }) { async function prepareUpload({ s3Key, bucket, metadata, file }) {
const response = await upload({ const response = await upload({
@ -85,7 +86,7 @@ exports.serveApp = async function (ctx) {
} }
const App = require("./templates/BudibaseApp.svelte").default const App = require("./templates/BudibaseApp.svelte").default
const db = new CouchDB(appId, { skip_setup: true }) const db = new CouchDB(appId, { skip_setup: true })
const appInfo = await db.get(appId) const appInfo = await db.get(DocumentTypes.APP_METADATA)
const { head, html, css } = App.render({ const { head, html, css } = App.render({
title: appInfo.name, title: appInfo.name,
@ -125,7 +126,7 @@ exports.serveComponentLibrary = async function (ctx) {
return send(ctx, "/index.js", { root: componentLibraryPath }) return send(ctx, "/index.js", { root: componentLibraryPath })
} }
const db = new CouchDB(appId) const db = new CouchDB(appId)
const appInfo = await db.get(appId) const appInfo = await db.get(DocumentTypes.APP_METADATA)
let componentLib = "componentlibrary" let componentLib = "componentlibrary"
if (appInfo && appInfo.version) { if (appInfo && appInfo.version) {

View File

@ -13,10 +13,8 @@ if (env.isDev() || env.isTest()) {
.delete("/api/admin/:devPath(.*)", controller.redirectDelete) .delete("/api/admin/:devPath(.*)", controller.redirectDelete)
} }
router.delete( router
"/api/dev/:appId/lock", .delete("/api/dev/:appId/lock", authorized(BUILDER), controller.clearLock)
authorized(BUILDER), .post("/api/dev/:appId/revert", authorized(BUILDER), controller.revert)
controller.clearLock
)
module.exports = router module.exports = router

View File

@ -21,7 +21,7 @@ exports.clearAllApps = async () => {
return return
} }
for (let app of apps) { for (let app of apps) {
const appId = app._id const { appId } = app
await appController.delete(new Request(null, { appId })) await appController.delete(new Request(null, { appId }))
} }
} }

View File

@ -23,6 +23,7 @@ const AppStatus = {
const DocumentTypes = { const DocumentTypes = {
APP: CoreDocTypes.APP, APP: CoreDocTypes.APP,
APP_DEV: CoreDocTypes.APP_DEV, APP_DEV: CoreDocTypes.APP_DEV,
APP_METADATA: CoreDocTypes.APP_METADATA,
TABLE: "ta", TABLE: "ta",
ROW: "ro", ROW: "ro",
USER: "us", USER: "us",

View File

@ -89,7 +89,7 @@ class TestConfiguration {
if (this.server) { if (this.server) {
this.server.close() this.server.close()
} }
cleanup(this.allApps.map(app => app._id)) cleanup(this.allApps.map(app => app.appId))
} }
defaultHeaders() { defaultHeaders() {
@ -141,7 +141,7 @@ class TestConfiguration {
async createApp(appName) { async createApp(appName) {
this.app = await this._req({ name: appName }, null, controllers.app.create) this.app = await this._req({ name: appName }, null, controllers.app.create)
this.appId = this.app._id this.appId = this.app.appId
this.allApps.push(this.app) this.allApps.push(this.app)
return this.app return this.app
} }

View File

@ -1,5 +1,5 @@
const env = require("../environment") const env = require("../environment")
const { APP_PREFIX } = require("../db/utils") const { APP_PREFIX, DocumentTypes } = require("../db/utils")
const CouchDB = require("../db") const CouchDB = require("../db")
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
@ -19,7 +19,9 @@ exports.isDev = env.isDev
exports.getAllApps = async () => { exports.getAllApps = async () => {
let allDbs = await CouchDB.allDbs() let allDbs = await CouchDB.allDbs()
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
const appPromises = appDbNames.map(db => new CouchDB(db).get(db)) const appPromises = appDbNames.map(db =>
new CouchDB(db).get(DocumentTypes.APP_METADATA)
)
if (appPromises.length === 0) { if (appPromises.length === 0) {
return [] return []
} else { } else {

View File

@ -1,4 +1,5 @@
const fetch = require("node-fetch") const fetch = require("node-fetch")
const { DocumentTypes } = require("@budibase/auth").db
const CouchDB = require("../../db") const CouchDB = require("../../db")
const env = require("../../environment") const env = require("../../environment")
@ -14,7 +15,9 @@ exports.getApps = async ctx => {
allDbs = await CouchDB.allDbs() allDbs = await CouchDB.allDbs()
} }
const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX))
const appPromises = appDbNames.map(db => new CouchDB(db).get(db)) const appPromises = appDbNames.map(db =>
new CouchDB(db).get(DocumentTypes.APP_METADATA)
)
const apps = await Promise.allSettled(appPromises) const apps = await Promise.allSettled(appPromises)
const body = {} const body = {}
@ -26,7 +29,7 @@ exports.getApps = async ctx => {
let url = app.url || encodeURI(`${app.name}`) let url = app.url || encodeURI(`${app.name}`)
url = `/${url.replace(URL_REGEX_SLASH, "")}` url = `/${url.replace(URL_REGEX_SLASH, "")}`
body[url] = { body[url] = {
appId: app._id, appId: app.instance._id,
name: app.name, name: app.name,
url, url,
} }