Adding the ability to get all apps, with the status attached.

This commit is contained in:
mike12345567 2021-05-19 15:09:57 +01:00
parent 91ed6679c4
commit 0bbd45b413
16 changed files with 123 additions and 96 deletions

View File

@ -34,6 +34,10 @@ exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR
exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
exports.SEPARATOR = SEPARATOR
function isDevApp(app) {
return app.appId.startsWith(exports.APP_DEV_PREFIX)
}
/**
* If creating DB allDocs/query params with only a single top level ID this can be used, this
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
@ -160,7 +164,7 @@ exports.getDeployedAppID = appId => {
* different users/companies apps as there is no security around it - all apps are returned.
* @return {Promise<object[]>} returns the app information document stored in each app database.
*/
exports.getAllApps = async (devApps = false) => {
exports.getAllApps = async ({ dev, all } = {}) => {
const CouchDB = getCouch()
let allDbs = await CouchDB.allDbs()
const appDbNames = allDbs.filter(dbName =>
@ -176,12 +180,19 @@ exports.getAllApps = async (devApps = false) => {
const apps = response
.filter(result => result.status === "fulfilled")
.map(({ value }) => value)
return apps.filter(app => {
if (devApps) {
return app.appId.startsWith(exports.APP_DEV_PREFIX)
}
return !app.appId.startsWith(exports.APP_DEV_PREFIX)
})
if (!all) {
return apps.filter(app => {
if (dev) {
return isDevApp(app)
}
return !isDevApp(app)
})
} else {
return apps.map(app => ({
...app,
status: isDevApp(app) ? "development" : "published"
}))
}
}
}

View File

@ -8,7 +8,7 @@
</script>
<h1
style="{textAlign ? `text-align:${textAlign}` : ``}"
style={textAlign ? `text-align:${textAlign}` : ``}
class:noPadding
class="spectrum-Heading spectrum-Heading--size{size}"
>

View File

@ -12,7 +12,7 @@
FAILURE: "FAILURE",
}
const POLL_INTERVAL = 1000
const POLL_INTERVAL = 10000
let loading = false
let feedbackModal

View File

@ -117,13 +117,17 @@ async function createInstance(template) {
}
exports.fetch = async function (ctx) {
const isDev = ctx.query && ctx.query.status === AppStatus.DEV
const apps = await getAllApps(isDev)
const dev = ctx.query && ctx.query.status === AppStatus.DEV
const all = ctx.query && ctx.query.status === AppStatus.ALL
const apps = await getAllApps({ dev, all })
// get the locks for all the dev apps
if (isDev) {
if (dev || all) {
const locks = await getAllLocks()
for (let app of apps) {
if (app.status !== "development") {
continue
}
const lock = locks.find(lock => lock.appId === app.appId)
if (lock) {
app.lockedBy = lock.user

View File

@ -5,9 +5,17 @@ const {
} = require("../../db/utils")
const { InternalTables } = require("../../db/utils")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { getGlobalUsers } = require("../../utilities/workerRequests")
const { getGlobalUsers, addAppRoleToSelf } = require("../../utilities/workerRequests")
const { getFullUser } = require("../../utilities/users")
function removeGlobalProps(user) {
// make sure to always remove some of the global user props
delete user.password
delete user.roles
delete user.builder
return user
}
exports.fetchMetadata = async function (ctx) {
const database = new CouchDB(ctx.appId)
const global = await getGlobalUsers(ctx, ctx.appId)
@ -37,7 +45,8 @@ exports.updateSelfMetadata = async function (ctx) {
// overwrite the ID with current users
ctx.request.body._id = ctx.user._id
if (ctx.user.builder && ctx.user.builder.global) {
ctx.request.body.roleId = BUILTIN_ROLE_IDS.ADMIN
// specific case, update self role in global user
await addAppRoleToSelf(ctx, ctx.appId, BUILTIN_ROLE_IDS.ADMIN)
}
// make sure no stale rev
delete ctx.request.body._rev
@ -47,11 +56,7 @@ exports.updateSelfMetadata = async function (ctx) {
exports.updateMetadata = async function (ctx) {
const appId = ctx.appId
const db = new CouchDB(appId)
const user = ctx.request.body
// make sure to always remove some of the global user props
delete user.password
delete user.roles
delete user.builder
const user = removeGlobalProps(ctx.request.body)
const metadata = {
tableId: InternalTables.USER_METADATA,
_id: user._id,

View File

@ -117,9 +117,6 @@ describe("/users", () => {
describe("update", () => {
beforeEach(() => {
workerRequests.saveGlobalUser.mockImplementationOnce(() => ({
_id: "us_test@test.com"
}))
})
it("should be able to update the user", async () => {
@ -151,9 +148,6 @@ describe("/users", () => {
describe("find", () => {
beforeEach(() => {
jest.resetAllMocks()
workerRequests.saveGlobalUser.mockImplementationOnce(() => ({
_id: "us_uuid1",
}))
workerRequests.getGlobalUsers.mockImplementationOnce(() => ({
_id: "us_uuid1",
roleId: BUILTIN_ROLE_IDS.POWER,

View File

@ -19,6 +19,7 @@ const StaticDatabases = {
const AppStatus = {
DEV: "dev",
ALL: "all",
DEPLOYED: "PUBLISHED",
}

View File

@ -118,53 +118,38 @@ exports.getGlobalUsers = async (ctx, appId = null, globalId = null) => {
return users
}
exports.saveGlobalUser = async (ctx, appId, body) => {
const globalUser = body._id
? await exports.getGlobalUsers(ctx, appId, body._id)
: {}
const preRoles = globalUser.roles || {}
if (body.roleId) {
preRoles[appId] = body.roleId
exports.getGlobalSelf = async ctx => {
const endpoint = `/api/admin/users/self`
const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + endpoint),
request(ctx, { method: "GET" })
)
const json = await response.json()
if (json.status !== 200 && response.status !== 200) {
ctx.throw(400, "Unable to get self globally.")
}
// 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`
return json
}
exports.addAppRoleToSelf = async (ctx, appId, roleId) => {
const self = await exports.getGlobalSelf(ctx)
const endpoint = `/api/admin/users/self`
const reqCfg = {
method: "POST",
body: {
...globalUser,
password: body.password || undefined,
status: body.status,
email: body.email,
roles,
builder: {
global: true,
},
roles: {
...self.roles,
[appId]: roleId,
}
},
}
const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + endpoint),
request(ctx, reqCfg)
)
const json = await response.json()
if (json.status !== 200 && response.status !== 200) {
ctx.throw(400, "Unable to save global user.")
}
delete body.password
delete body.roles
delete body.builder
// TODO: for now these have been left in as they are
// TODO: pretty important to keeping relationships working
// TODO: however if user metadata is changed this should be removed
// delete body.email
// delete body.roleId
// delete body.status
return {
...body,
_id: json._id,
ctx.throw(400, "Unable to save self globally.")
}
return json
}

View File

@ -8,7 +8,7 @@ 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 apps = await getAllApps({ dev: true })
const promises = []
for (let app of apps) {
// use dev app IDs

View File

@ -98,7 +98,7 @@ exports.destroy = async ctx => {
exports.getSelf = async ctx => {
ctx.params = {
id: ctx.user._id
id: ctx.user._id,
}
// this will set the body
await exports.find(ctx)
@ -172,12 +172,16 @@ exports.invite = async ctx => {
}
exports.inviteAccept = async ctx => {
const { inviteCode } = ctx.request.body
const { inviteCode, password, firstName, lastName } = ctx.request.body
try {
const email = await checkInviteCode(inviteCode)
// redirect the request
delete ctx.request.body.inviteCode
ctx.request.body.email = email
// only pass through certain props for accepting
ctx.request.body = {
firstName,
lastName,
password,
email,
}
// this will flesh out the body response
await exports.save(ctx)
} catch (err) {

View File

@ -78,8 +78,13 @@ function buildConfigGetValidation() {
}
router
.post("/api/admin/configs", buildConfigSaveValidation(), controller.save)
.delete("/api/admin/configs/:id", controller.destroy)
.post(
"/api/admin/configs",
adminOnly,
buildConfigSaveValidation(),
controller.save
)
.delete("/api/admin/configs/:id", adminOnly, controller.destroy)
.get("/api/admin/configs", controller.fetch)
.get("/api/admin/configs/checklist", controller.configChecklist)
.get(
@ -90,6 +95,7 @@ router
.get("/api/admin/configs/:type", buildConfigGetValidation(), controller.find)
.post(
"/api/admin/configs/upload/:type/:name",
adminOnly,
buildUploadValidation(),
controller.upload
)

View File

@ -1,6 +1,7 @@
const Router = require("@koa/router")
const controller = require("../../controllers/admin/groups")
const joiValidator = require("../../../middleware/joi-validator")
const adminOnly = require("../../../middleware/adminOnly")
const Joi = require("joi")
const router = Router()
@ -24,9 +25,14 @@ function buildGroupSaveValidation() {
}
router
.post("/api/admin/groups", buildGroupSaveValidation(), controller.save)
.post(
"/api/admin/groups",
adminOnly,
buildGroupSaveValidation(),
controller.save
)
.get("/api/admin/groups", controller.fetch)
.delete("/api/admin/groups/:id", controller.destroy)
.delete("/api/admin/groups/:id", adminOnly, controller.destroy)
.get("/api/admin/groups/:id", controller.find)
module.exports = router

View File

@ -1,10 +1,11 @@
const Router = require("@koa/router")
const controller = require("../../controllers/admin/roles")
const adminOnly = require("../../../middleware/adminOnly")
const router = Router()
router
.get("/api/admin/roles", controller.fetch)
.get("/api/admin/roles/:appId", controller.find)
.get("/api/admin/roles", adminOnly, controller.fetch)
.get("/api/admin/roles/:appId", adminOnly, controller.find)
module.exports = router

View File

@ -3,6 +3,7 @@ const controller = require("../../controllers/admin/templates")
const joiValidator = require("../../../middleware/joi-validator")
const Joi = require("joi")
const { TemplatePurpose, TemplateTypes } = require("../../../constants")
const adminOnly = require("../../../middleware/adminOnly")
const router = Router()
@ -21,11 +22,16 @@ function buildTemplateSaveValidation() {
router
.get("/api/admin/template/definitions", controller.definitions)
.post("/api/admin/template", buildTemplateSaveValidation(), controller.save)
.post(
"/api/admin/template",
adminOnly,
buildTemplateSaveValidation(),
controller.save
)
.get("/api/admin/template", controller.fetch)
.get("/api/admin/template/:type", controller.fetchByType)
.get("/api/admin/template/:ownerId", controller.fetchByOwner)
.get("/api/admin/template/:id", controller.find)
.delete("/api/admin/template/:id/:rev", controller.destroy)
.delete("/api/admin/template/:id/:rev", adminOnly, controller.destroy)
module.exports = router

View File

@ -1,6 +1,7 @@
const Router = require("@koa/router")
const controller = require("../../controllers/admin/users")
const joiValidator = require("../../../middleware/joi-validator")
const adminOnly = require("../../../middleware/adminOnly")
const Joi = require("joi")
const router = Router()
@ -14,12 +15,11 @@ function buildUserSaveValidation(isSelf = false) {
builder: Joi.object({
global: Joi.boolean().optional(),
apps: Joi.array().optional(),
}).unknown(true).optional(),
// maps appId -> roleId for the user
roles: Joi.object()
.pattern(/.*/, Joi.string())
.required()
})
.unknown(true)
.optional(),
// maps appId -> roleId for the user
roles: Joi.object().pattern(/.*/, Joi.string()).required().unknown(true),
}
if (!isSelf) {
schema = {
@ -28,9 +28,7 @@ function buildUserSaveValidation(isSelf = false) {
_rev: Joi.string(),
}
}
return joiValidator.body(Joi.object(schema)
.required()
.unknown(true))
return joiValidator.body(Joi.object(schema).required().unknown(true))
}
function buildInviteValidation() {
@ -48,24 +46,30 @@ function buildInviteAcceptValidation() {
}).required().unknown(true))
}
function buildUpdateSelfValidation() {
// prettier-ignore
return joiValidator.body(Joi.object({
inviteCode: Joi.string().required(),
password: Joi.string().required(),
}).required().unknown(true))
}
router
.post("/api/admin/users", buildUserSaveValidation(), controller.save)
.post(
"/api/admin/users",
adminOnly,
buildUserSaveValidation(),
controller.save
)
.get("/api/admin/users", controller.fetch)
.post("/api/admin/users/init", controller.adminUser)
.get("/api/admin/users/self", controller.getSelf)
.post("/api/admin/users/self", buildUserSaveValidation(true), controller.updateSelf)
.delete("/api/admin/users/:id", controller.destroy)
.post(
"/api/admin/users/self",
buildUserSaveValidation(true),
controller.updateSelf
)
.delete("/api/admin/users/:id", adminOnly, controller.destroy)
.get("/api/admin/users/:id", controller.find)
.get("/api/admin/roles/:appId")
.post("/api/admin/users/invite", buildInviteValidation(), controller.invite)
.post(
"/api/admin/users/invite",
adminOnly,
buildInviteValidation(),
controller.invite
)
.post(
"/api/admin/users/invite/accept",
buildInviteAcceptValidation(),

View File

@ -3,4 +3,4 @@ module.exports = async (ctx, next) => {
ctx.throw(403, "Admin user only endpoint.")
}
return next()
}
}