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/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. + 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/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 } } 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/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/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) 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