From 4794a5374e2b8104789c2f1cb2d83655a6b36a1c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 21 Oct 2021 17:23:10 +0100 Subject: [PATCH 1/4] Fixing an issue with user metadata not always being present when user accessing app, causing weird issues. --- packages/server/src/api/controllers/auth.js | 15 ++++++++++++--- packages/server/src/db/linkedRows/index.js | 21 ++++++++++++++------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index ac88599713..2bcdae4c05 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -28,14 +28,23 @@ exports.fetchSelf = async ctx => { ...metadata, }) } catch (err) { + let response // user didn't exist in app, don't pretend they do if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { - ctx.body = {} + response = {} } // user has a role of some sort, return them - else { - ctx.body = user + else if (err.status === 404) { + const metadata = { + _id: userId, + } + const dbResp = await db.put(metadata) + user._rev = dbResp.rev + response = user + } else { + response = user } + ctx.body = response } } else { ctx.body = user diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 303cd085c1..6835719e5f 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -81,7 +81,9 @@ async function getFullLinkedDocs(ctx, appId, links) { row => row.doc ) // convert the unique db rows back to a full list of linked rows - const linked = linkedRowIds.map(id => dbRows.find(row => row._id === id)) + const linked = linkedRowIds + .map(id => dbRows.find(row => row && row._id === id)) + .filter(row => row != null) // need to handle users as specific cases let [users, other] = partition(linked, linkRow => linkRow._id.startsWith(USER_METDATA_PREFIX) @@ -172,13 +174,18 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { row[link.fieldName] = [] } const linkedRow = linked.find(row => row._id === link.id) - const linkedTableId = - linkedRow.tableId || getRelatedTableForField(table, link.fieldName) - const linkedTable = await getLinkedTable(db, linkedTableId, linkedTables) - if (!linkedRow || !linkedTable) { - continue + if (linkedRow) { + const linkedTableId = + linkedRow.tableId || getRelatedTableForField(table, link.fieldName) + const linkedTable = await getLinkedTable( + db, + linkedTableId, + linkedTables + ) + if (linkedTable) { + row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) + } } - row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) } } return rows From 47ebc393c7718d36de74319d4ee1eac24dfc2af5 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 21 Oct 2021 17:25:29 +0100 Subject: [PATCH 2/4] When adding a user through the basic onboarding flow they get a temporary password, but we didn't set force password reset, meaning the user wouldn't necessarily have to change the temp password. --- .../users/_components/BasicOnboardingModal.svelte | 8 +++++++- packages/builder/src/stores/portal/users.js | 11 ++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte index 9c7b9aa0ea..ff958d542b 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte @@ -16,7 +16,13 @@ admin = false async function createUser() { - const res = await users.create({ email: $email, password, builder, admin }) + const res = await users.create({ + email: $email, + password, + builder, + admin, + forceResetPassword: true, + }) if (res.status) { notifications.error(res.message) } else { diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 0535b2626d..9a3df120e0 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -35,12 +35,21 @@ export function createUsersStore() { return await response.json() } - async function create({ email, password, admin, builder }) { + async function create({ + email, + password, + admin, + builder, + forceResetPassword, + }) { const body = { email, password, roles: {}, } + if (forceResetPassword) { + body.forceResetPassword = forceResetPassword + } if (builder) { body.builder = { global: true } } From 9617477dcd5cb121dd595ddb583cdb88864763b2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 21 Oct 2021 17:32:01 +0100 Subject: [PATCH 3/4] Adding email address disabled to update user info, so you can see which user you are currently logged into. --- .../builder/src/components/settings/UpdateUserInfoModal.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/components/settings/UpdateUserInfoModal.svelte b/packages/builder/src/components/settings/UpdateUserInfoModal.svelte index 96e2aa3743..ea0cb827f8 100644 --- a/packages/builder/src/components/settings/UpdateUserInfoModal.svelte +++ b/packages/builder/src/components/settings/UpdateUserInfoModal.svelte @@ -26,6 +26,7 @@ Personalise the platform by adding your first name and last name. + From a2d302cd579b5f4296670c686126b3bc56a216dc Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 22 Oct 2021 14:34:20 +0100 Subject: [PATCH 4/4] Removing live replication between deployed and development app, instead it is replicated when the app is opened in the builder. Live replication was not working and only actually pulled back the data on deploy which was a little confusing, this way it can easily be controlled. --- packages/auth/src/db/Replication.js | 16 ------ .../builder/app/[application]/_layout.svelte | 17 +++++- .../server/src/api/controllers/application.js | 52 +++++++++++++++---- .../src/api/controllers/deploy/index.js | 21 ++------ packages/server/src/api/routes/application.js | 1 + 5 files changed, 62 insertions(+), 45 deletions(-) diff --git a/packages/auth/src/db/Replication.js b/packages/auth/src/db/Replication.js index 931bc3d496..7af3c2eb9d 100644 --- a/packages/auth/src/db/Replication.js +++ b/packages/auth/src/db/Replication.js @@ -45,22 +45,6 @@ class Replication { return this.replication } - /** - * Set up an ongoing live sync between 2 CouchDB databases. - * @param {Object} opts - PouchDB replication options - */ - subscribe(opts = {}) { - this.replication = this.source.replicate - .to(this.target, { - live: true, - retry: true, - ...opts, - }) - .on("error", function (err) { - throw new Error(`Replication Error: ${err}`) - }) - } - /** * Rollback the target DB back to the state of the source DB */ diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 603fb62d99..da4317c8b7 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -1,21 +1,24 @@ {#await promise} diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index e3aac8bd63..e2e42c20f9 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -25,7 +25,12 @@ const { BASE_LAYOUTS } = require("../../constants/layouts") const { createHomeScreen } = require("../../constants/screens") const { cloneDeep } = require("lodash/fp") const { processObject } = require("@budibase/string-templates") -const { getAllApps } = require("@budibase/auth/db") +const { + getAllApps, + isDevAppID, + getDeployedAppID, + Replication, +} = require("@budibase/auth/db") const { USERS_TABLE_SCHEMA } = require("../../constants") const { getDeployedApps, @@ -134,7 +139,7 @@ async function createInstance(template) { return { _id: appId } } -exports.fetch = async function (ctx) { +exports.fetch = async ctx => { const dev = ctx.query && ctx.query.status === AppStatus.DEV const all = ctx.query && ctx.query.status === AppStatus.ALL const apps = await getAllApps(CouchDB, { dev, all }) @@ -159,7 +164,7 @@ exports.fetch = async function (ctx) { ctx.body = apps } -exports.fetchAppDefinition = async function (ctx) { +exports.fetchAppDefinition = async ctx => { const db = new CouchDB(ctx.params.appId) const layouts = await getLayouts(db) const userRoleId = getUserRoleId(ctx) @@ -175,7 +180,7 @@ exports.fetchAppDefinition = async function (ctx) { } } -exports.fetchAppPackage = async function (ctx) { +exports.fetchAppPackage = async ctx => { const db = new CouchDB(ctx.params.appId) const application = await db.get(DocumentTypes.APP_METADATA) const layouts = await getLayouts(db) @@ -196,7 +201,7 @@ exports.fetchAppPackage = async function (ctx) { } } -exports.create = async function (ctx) { +exports.create = async ctx => { const { useTemplate, templateKey, templateString } = ctx.request.body const instanceConfig = { useTemplate, @@ -252,13 +257,13 @@ exports.create = async function (ctx) { ctx.body = newApplication } -exports.update = async function (ctx) { +exports.update = async ctx => { const data = await updateAppPackage(ctx, ctx.request.body, ctx.params.appId) ctx.status = 200 ctx.body = data } -exports.updateClient = async function (ctx) { +exports.updateClient = async ctx => { // Get current app version const db = new CouchDB(ctx.params.appId) const application = await db.get(DocumentTypes.APP_METADATA) @@ -280,7 +285,7 @@ exports.updateClient = async function (ctx) { ctx.body = data } -exports.revertClient = async function (ctx) { +exports.revertClient = async ctx => { // Check app can be reverted const db = new CouchDB(ctx.params.appId) const application = await db.get(DocumentTypes.APP_METADATA) @@ -303,7 +308,7 @@ exports.revertClient = async function (ctx) { ctx.body = data } -exports.delete = async function (ctx) { +exports.delete = async ctx => { const db = new CouchDB(ctx.params.appId) const result = await db.destroy() @@ -318,6 +323,35 @@ exports.delete = async function (ctx) { ctx.body = result } +exports.sync = async ctx => { + const appId = ctx.params.appId + if (!isDevAppID(appId)) { + ctx.throw(400, "This action cannot be performed for production apps") + } + const prodAppId = getDeployedAppID(appId) + const replication = new Replication({ + source: prodAppId, + target: appId, + }) + let error + try { + await replication.replicate({ + filter: function (doc) { + return doc._id !== DocumentTypes.APP_METADATA + }, + }) + } catch (err) { + error = err + } + if (error) { + ctx.throw(400, error) + } else { + ctx.body = { + message: "App sync completed successfully.", + } + } +} + const updateAppPackage = async (ctx, appPackage, appId) => { const url = await getAppUrlIfNotInUse(ctx) const db = new CouchDB(appId) diff --git a/packages/server/src/api/controllers/deploy/index.js b/packages/server/src/api/controllers/deploy/index.js index d68b2064d7..13002476fc 100644 --- a/packages/server/src/api/controllers/deploy/index.js +++ b/packages/server/src/api/controllers/deploy/index.js @@ -1,6 +1,6 @@ const CouchDB = require("../../../db") const Deployment = require("./Deployment") -const { Replication } = require("@budibase/auth/db") +const { Replication, getDeployedAppID } = require("@budibase/auth/db") const { DocumentTypes, getAutomationParams } = require("../../../db/utils") const { disableAllCrons, @@ -87,7 +87,7 @@ async function initDeployedApp(prodAppId) { async function deployApp(deployment) { try { - const productionAppId = deployment.appId.replace("_dev", "") + const productionAppId = getDeployedAppID(deployment.appId) const replication = new Replication({ source: deployment.appId, @@ -104,23 +104,8 @@ async function deployApp(deployment) { appDoc.instance._id = productionAppId await db.put(appDoc) console.log("New app doc written successfully.") - - console.log("Setting up live repl between dev and prod") - // Set up live sync between the live and dev instances - const liveReplication = new Replication({ - source: productionAppId, - target: deployment.appId, - }) - liveReplication.subscribe({ - filter: function (doc) { - return doc._id !== DocumentTypes.APP_METADATA - }, - }) - console.log("Set up live repl between dev and prod") - - console.log("Initialising deployed app") await initDeployedApp(productionAppId) - console.log("Init complete, setting deployment to successful") + console.log("Deployed app initialised, setting deployment to successful") deployment.setStatus(DeploymentStatus.SUCCESS) await storeDeploymentHistory(deployment) } catch (err) { diff --git a/packages/server/src/api/routes/application.js b/packages/server/src/api/routes/application.js index 4d67a0f4f4..1a21cc8216 100644 --- a/packages/server/src/api/routes/application.js +++ b/packages/server/src/api/routes/application.js @@ -7,6 +7,7 @@ const usage = require("../../middleware/usageQuota") const router = Router() router + .post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync) .post("/api/applications", authorized(BUILDER), usage, controller.create) .get("/api/applications/:appId/definition", controller.fetchAppDefinition) .get("/api/applications", controller.fetch)