Merge branch 'feature/draft-apps' of github.com:Budibase/budibase into feature/forgot-password

This commit is contained in:
mike12345567 2021-05-17 14:54:03 +01:00
commit e7e7687aae
29 changed files with 315 additions and 156 deletions

View File

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

View File

@ -13,6 +13,9 @@ exports.StaticDatabases = {
GLOBAL: {
name: "global-db",
},
DEPLOYMENTS: {
name: "deployments",
},
}
const DocumentTypes = {
@ -22,6 +25,7 @@ const DocumentTypes = {
TEMPLATE: "template",
APP: "app",
APP_DEV: "app_dev",
APP_METADATA: "app_metadata",
ROLE: "role",
}
@ -137,6 +141,18 @@ exports.getRoleParams = (roleId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.ROLE, roleId, otherProps)
}
/**
* Convert a development app ID to a deployed app ID.
*/
exports.getDeployedAppID = appId => {
// if dev, convert it
if (appId.startsWith(exports.APP_DEV_PREFIX)) {
const id = appId.split(exports.APP_DEV_PREFIX)[1]
return `${exports.APP_PREFIX}${id}`
}
return appId
}
/**
* Lots of different points in the system need to find the full list of apps, this will
* enumerate the entire CouchDB cluster and get the list of databases (every app).
@ -150,7 +166,7 @@ exports.getAllApps = async (devApps = false) => {
const appDbNames = allDbs.filter(dbName =>
dbName.startsWith(exports.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) {
return []
} else {
@ -160,9 +176,9 @@ exports.getAllApps = async (devApps = false) => {
.map(({ value }) => value)
return apps.filter(app => {
if (devApps) {
return app._id.startsWith(exports.APP_DEV_PREFIX)
return app.appId.startsWith(exports.APP_DEV_PREFIX)
}
return !app._id.startsWith(exports.APP_DEV_PREFIX)
return !app.appId.startsWith(exports.APP_DEV_PREFIX)
})
}
}

View File

@ -51,14 +51,14 @@ export const getFrontendStore = () => {
store.actions = {
initialise: async pkg => {
const { layouts, screens, application, clientLibPath } = pkg
const components = await fetchComponentLibDefinitions(application._id)
const components = await fetchComponentLibDefinitions(application.appId)
store.update(state => ({
...state,
libraries: application.componentLibraries,
components,
name: application.name,
description: application.description,
appId: application._id,
appId: application.appId,
url: application.url,
layouts,
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,44 @@
<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
// Reset frontend state after revert
const applicationPkg = await api.get(`/api/applications/${appId}/appPackage`)
const pkg = await applicationPkg.json()
if (applicationPkg.ok) {
await store.actions.initialise(pkg)
} else {
throw new Error(pkg)
}
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>
{/if}
{#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
</MenuItem>
{/if}

View File

@ -50,7 +50,7 @@
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
{/if}
{#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
</MenuItem>
{/if}

View File

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

View File

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

View File

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

View File

@ -1,79 +0,0 @@
<script>
import { Button, Modal, notifications, Heading } from "@budibase/bbui"
import { store } from "builderStore"
import api from "builderStore/api"
import DeploymentHistory from "components/deploy/DeploymentHistory.svelte"
import analytics from "analytics"
import FeedbackIframe from "components/feedback/FeedbackIframe.svelte"
import Rocket from "/assets/deploy-rocket.jpg"
let loading = false
let deployments = []
let poll
let feedbackModal
$: 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.")
}
}
</script>
<section>
<img src={Rocket} alt="Rocket flying through sky" />
<div>
<Heading size="M">It's time to shine!</Heading>
<Button size="XL" cta medium on:click={deployApp}>Publish App</Button>
</div>
</section>
<Modal bind:this={feedbackModal}>
<FeedbackIframe on:finished={() => feedbackModal.hide()} />
</Modal>
<DeploymentHistory {appId} />
<style>
img {
width: 100%;
height: 100%;
object-fit: cover;
filter: brightness(80%);
}
section {
position: relative;
min-height: 100%;
}
div {
position: absolute;
display: flex;
text-align: center;
flex-direction: column;
align-items: center;
justify-content: center;
left: 0;
right: 0;
top: 20%;
margin-left: auto;
margin-right: auto;
width: 50%;
gap: var(--spacing-xl);
}
div :global(h1) {
color: white;
}
</style>

View File

@ -18,6 +18,7 @@
import analytics from "analytics"
import { onMount } from "svelte"
import { apps } from "stores/portal"
import { auth } from "stores/backend"
import download from "downloadjs"
import { goto } from "@roxi/routify"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
@ -61,17 +62,22 @@
}
const openApp = app => {
if (app.lockedBy && app.lockedBy?.email === $auth.user?.email) {
notifications.error(`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`)
return
}
if (appStatus === AppStatus.DEV) {
$goto(`../../app/${app._id}`)
$goto(`../../app/${app.appId}`)
} else {
window.open(`/${app._id}`, "_blank")
window.open(`/${app.appId}`, '_blank');
}
}
const exportApp = app => {
try {
download(
`/api/backups/export?appId=${app._id}&appname=${encodeURIComponent(
`/api/backups/export?appId=${app.appId}&appname=${encodeURIComponent(
app.name
)}`
)
@ -157,7 +163,7 @@
class:appGrid={layout === "grid"}
class:appTable={layout === "table"}
>
{#each $apps as app, idx (app._id)}
{#each $apps as app, idx (app.appId)}
<svelte:component
this={layout === "grid" ? AppCard : AppRow}
deletable={appStatus === AppStatus.PUBLISHED}

View File

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

View File

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

View File

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

View File

@ -85,7 +85,6 @@ async function getAppUrlIfNotInUse(ctx) {
}
async function createInstance(template) {
// TODO: Do we need the normal app ID?
const baseAppId = generateAppID()
const appId = generateDevAppID(baseAppId)
@ -125,7 +124,7 @@ exports.fetch = async function (ctx) {
if (isDev) {
const locks = await getAllLocks()
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) {
app.lockedBy = lock.user
} else {
@ -156,7 +155,7 @@ exports.fetchAppDefinition = async function (ctx) {
exports.fetchAppPackage = async function (ctx) {
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)])
ctx.body = {
@ -181,7 +180,8 @@ exports.create = async function (ctx) {
const url = await getAppUrlIfNotInUse(ctx)
const appId = instance._id
const newApplication = {
_id: appId,
_id: DocumentTypes.APP_METADATA,
appId: instance._id,
type: "app",
version: packageJson.version,
componentLibraries: ["@budibase/standard-components"],
@ -244,7 +244,7 @@ exports.delete = async function (ctx) {
}
const createEmptyAppPackage = async (ctx, app) => {
const db = new CouchDB(app._id)
const db = new CouchDB(app.instance._id)
let screensAndLayouts = []
for (let layout of BASE_LAYOUTS) {

View File

@ -1,10 +1,11 @@
const CouchDB = require("../../db")
const { DocumentTypes } = require("../../db/utils")
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
exports.fetchAppComponentDefinitions = async function (ctx) {
const appId = ctx.params.appId || ctx.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(
app.componentLibraries.map(async library => {

View File

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

View File

@ -1,8 +1,11 @@
const fetch = require("node-fetch")
const CouchDB = require("../../db")
const env = require("../../environment")
const { checkSlashesInUrl } = require("../../utilities")
const { request } = require("../../utilities/workerRequests")
const { clearLock } = require("../../utilities/redis")
const { Replication } = require("@budibase/auth").db
const { DocumentTypes } = require("../../db/utils")
async function redirect(ctx, method) {
const { devPath } = ctx.params
@ -45,3 +48,37 @@ exports.clearLock = async ctx => {
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 { upload } = require("../../../utilities/fileSystem")
const { attachmentsRelativeURL } = require("../../../utilities")
const { DocumentTypes } = require("../../../db/utils")
async function prepareUpload({ s3Key, bucket, metadata, file }) {
const response = await upload({
@ -85,7 +86,7 @@ exports.serveApp = async function (ctx) {
}
const App = require("./templates/BudibaseApp.svelte").default
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({
title: appInfo.name,
@ -125,7 +126,7 @@ exports.serveComponentLibrary = async function (ctx) {
return send(ctx, "/index.js", { root: componentLibraryPath })
}
const db = new CouchDB(appId)
const appInfo = await db.get(appId)
const appInfo = await db.get(DocumentTypes.APP_METADATA)
let componentLib = "componentlibrary"
if (appInfo && appInfo.version) {

View File

@ -37,7 +37,6 @@ router
})
)
.use(currentApp)
// .use(development)
// error handling middleware
router.use(async (ctx, next) => {

View File

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

View File

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

View File

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

View File

@ -89,7 +89,7 @@ class TestConfiguration {
if (this.server) {
this.server.close()
}
cleanup(this.allApps.map(app => app._id))
cleanup(this.allApps.map(app => app.appId))
}
defaultHeaders() {
@ -141,7 +141,7 @@ class TestConfiguration {
async createApp(appName) {
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)
return this.app
}

View File

@ -1,6 +1,4 @@
const env = require("../environment")
const { APP_PREFIX } = require("../db/utils")
const CouchDB = require("../db")
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
const { getAllApps } = require("@budibase/auth/db")

View File

@ -2,6 +2,7 @@ const fetch = require("node-fetch")
const env = require("../environment")
const { checkSlashesInUrl } = require("./index")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { getDeployedAppID } = require("@budibase/auth/db")
function getAppRole(appId, user) {
if (!user.roles) {
@ -95,6 +96,8 @@ exports.deleteGlobalUser = async (ctx, globalId) => {
}
exports.getGlobalUsers = async (ctx, appId = null, globalId = null) => {
// always use the deployed app
appId = getDeployedAppID(appId)
const endpoint = globalId
? `/api/admin/users/${globalId}`
: `/api/admin/users`
@ -119,9 +122,14 @@ exports.saveGlobalUser = async (ctx, appId, body) => {
const globalUser = body._id
? await exports.getGlobalUsers(ctx, appId, body._id)
: {}
const roles = globalUser.roles || {}
const preRoles = globalUser.roles || {}
if (body.roleId) {
roles[appId] = body.roleId
preRoles[appId] = body.roleId
}
// make sure no dev app IDs in roles
const roles = {}
for (let [appId, roleId] of Object.entries(preRoles)) {
roles[getDeployedAppID(appId)] = roleId
}
const endpoint = `/api/admin/users`
const reqCfg = {

View File

@ -1,24 +1,37 @@
const { getAllRoles } = require("@budibase/auth/roles")
const { getAllApps } = require("@budibase/auth/db")
const { getAllApps, getDeployedAppID, DocumentTypes } = require("@budibase/auth/db")
const CouchDB = require("../../../db")
exports.fetch = async ctx => {
// always use the dev apps as they'll be most up to date (true)
const apps = await getAllApps(true)
const promises = []
for (let app of apps) {
promises.push(getAllRoles(app._id))
// use dev app IDs
promises.push(getAllRoles(app.appId))
}
const roles = await Promise.all(promises)
const response = {}
for (let app of apps) {
response[app._id] = roles.shift()
const deployedAppId = getDeployedAppID(app.appId)
response[deployedAppId] = {
roles: roles.shift(),
name: app.name,
version: app.version,
url: app.url,
}
}
ctx.body = response
}
exports.find = async ctx => {
const appId = ctx.params.appId
const db = new CouchDB(appId)
const app = await db.get(DocumentTypes.APP_METADATA)
ctx.body = {
roles: await getAllRoles(appId),
name: app.name,
version: app.version,
url: app.url,
}
}

View File

@ -1,4 +1,5 @@
const fetch = require("node-fetch")
const { DocumentTypes } = require("@budibase/auth").db
const CouchDB = require("../../db")
const env = require("../../environment")
@ -14,7 +15,9 @@ exports.getApps = async ctx => {
allDbs = await CouchDB.allDbs()
}
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 body = {}
@ -26,7 +29,7 @@ exports.getApps = async ctx => {
let url = app.url || encodeURI(`${app.name}`)
url = `/${url.replace(URL_REGEX_SLASH, "")}`
body[url] = {
appId: app._id,
appId: app.instance._id,
name: app.name,
url,
}