Adding sync user endpoint to server which can be used by the worker.

This commit is contained in:
Michael Drury 2021-11-03 23:15:38 +00:00
parent 1aeb12b0aa
commit 8bf2e7278e
5 changed files with 90 additions and 27 deletions

View File

@ -152,6 +152,17 @@ exports.getDeployedAppID = appId => {
return appId return appId
} }
/**
* Convert a deployed app ID to a development app ID.
*/
exports.getDevelopmentAppID = appId => {
if (!appId.startsWith(exports.APP_DEV_PREFIX)) {
const id = appId.split(exports.APP_PREFIX)[1]
return `${exports.APP_DEV_PREFIX}${id}`
}
return appId
}
exports.getCouchUrl = () => { exports.getCouchUrl = () => {
if (!env.COUCH_DB_URL) return if (!env.COUCH_DB_URL) return

View File

@ -4,12 +4,14 @@ const {
getUserMetadataParams, getUserMetadataParams,
} = require("../../db/utils") } = require("../../db/utils")
const { InternalTables } = require("../../db/utils") const { InternalTables } = require("../../db/utils")
const { getGlobalUsers } = require("../../utilities/global") const { getGlobalUsers, getRawGlobalUser } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users") const { getFullUser } = require("../../utilities/users")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { getDevelopmentAppID } = require("@budibase/auth/db")
const { doesDatabaseExist } = require("../../utilities")
exports.rawMetadata = async db => { async function rawMetadata(db) {
return ( return (
await db.allDocs( await db.allDocs(
getUserMetadataParams(null, { getUserMetadataParams(null, {
@ -19,45 +21,80 @@ exports.rawMetadata = async db => {
).rows.map(row => row.doc) ).rows.map(row => row.doc)
} }
async function combineMetadataAndUser(user, metadata) {
// skip users with no access
if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) {
return null
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
}
if (found == null || !isEqual(newDoc, found)) {
return {
...found,
...newDoc,
}
}
return null
}
exports.syncGlobalUsers = async appId => { exports.syncGlobalUsers = async appId => {
// sync user metadata // sync user metadata
const db = new CouchDB(appId) const db = new CouchDB(appId)
const [users, metadata] = await Promise.all([ const [users, metadata] = await Promise.all([
getGlobalUsers(appId), getGlobalUsers(appId),
exports.rawMetadata(db), rawMetadata(db),
]) ])
const toWrite = [] const toWrite = []
for (let user of users) { for (let user of users) {
// skip users with no access const combined = await combineMetadataAndUser(user, metadata)
if (user.roleId === BUILTIN_ROLE_IDS.PUBLIC) { if (combined) {
continue toWrite.push(combined)
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = metadata.find(doc => doc._id === metadataId)
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
}
if (found == null || !isEqual(newDoc, found)) {
toWrite.push({
...found,
...newDoc,
})
} }
} }
await db.bulkDocs(toWrite) await db.bulkDocs(toWrite)
} }
exports.syncUser = async function (ctx) {
const user = await getRawGlobalUser(ctx.params.id)
const roles = user.roles
delete user.roles
for (let [prodAppId, roleId] of Object.entries(roles)) {
if (roleId === BUILTIN_ROLE_IDS.PUBLIC) {
continue
}
const devAppId = getDevelopmentAppID(prodAppId)
for (let appId of [prodAppId, devAppId]) {
if (!(await doesDatabaseExist(appId))) {
continue
}
const db = new CouchDB(appId)
const userId = generateUserMetadataID(user._id)
const metadata = await db.get(userId)
const combined = combineMetadataAndUser(user, metadata)
await db.put(combined)
}
}
ctx.body = {
message: "User synced.",
}
}
exports.fetchMetadata = async function (ctx) { exports.fetchMetadata = async function (ctx) {
const database = new CouchDB(ctx.appId) const database = new CouchDB(ctx.appId)
const global = await getGlobalUsers(ctx.appId) const global = await getGlobalUsers(ctx.appId)
const metadata = await exports.rawMetadata(database) const metadata = await rawMetadata(database)
const users = [] const users = []
for (let user of global) { for (let user of global) {
// find the metadata that matches up to the global ID // find the metadata that matches up to the global ID

View File

@ -34,5 +34,10 @@ router
authorized(PermissionTypes.USER, PermissionLevels.WRITE), authorized(PermissionTypes.USER, PermissionLevels.WRITE),
controller.destroyMetadata controller.destroyMetadata
) )
.post(
"/api/users/sync/:id",
authorized(PermissionTypes.USER, PermissionLevels.WRITE),
controller.syncUser
)
module.exports = router module.exports = router

View File

@ -46,9 +46,13 @@ exports.getCachedSelf = async (ctx, appId) => {
return processUser(appId, user) return processUser(appId, user)
} }
exports.getGlobalUser = async (appId, userId) => { exports.getRawGlobalUser = async userId => {
const db = getGlobalDB() const db = getGlobalDB()
let user = await db.get(getGlobalIDFromUserMetadataID(userId)) return db.get(getGlobalIDFromUserMetadataID(userId))
}
exports.getGlobalUser = async (appId, userId) => {
let user = await exports.getRawGlobalUser(userId)
return processUser(appId, user) return processUser(appId, user)
} }

View File

@ -134,3 +134,9 @@ exports.stringToReadStream = string => {
}, },
}) })
} }
exports.doesDatabaseExist = async dbName => {
const db = new CouchDB(dbName, { skip_setup: true })
const info = await db.info()
return info && !info.error
}