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 af531241c4
commit 6d6eee2a93
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.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
exports.SEPARATOR = 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 * 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. * 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. * 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. * @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() const CouchDB = getCouch()
let allDbs = await CouchDB.allDbs() let allDbs = await CouchDB.allDbs()
const appDbNames = allDbs.filter(dbName => const appDbNames = allDbs.filter(dbName =>
@ -176,12 +180,19 @@ exports.getAllApps = async (devApps = false) => {
const apps = response const apps = response
.filter(result => result.status === "fulfilled") .filter(result => result.status === "fulfilled")
.map(({ value }) => value) .map(({ value }) => value)
if (!all) {
return apps.filter(app => { return apps.filter(app => {
if (devApps) { if (dev) {
return app.appId.startsWith(exports.APP_DEV_PREFIX) return isDevApp(app)
} }
return !app.appId.startsWith(exports.APP_DEV_PREFIX) return !isDevApp(app)
}) })
} else {
return apps.map(app => ({
...app,
status: isDevApp(app) ? "development" : "published"
}))
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -118,53 +118,38 @@ exports.getGlobalUsers = async (ctx, appId = null, globalId = null) => {
return users return users
} }
exports.saveGlobalUser = async (ctx, appId, body) => { exports.getGlobalSelf = async ctx => {
const globalUser = body._id const endpoint = `/api/admin/users/self`
? await exports.getGlobalUsers(ctx, appId, body._id) const response = await fetch(
: {} checkSlashesInUrl(env.WORKER_URL + endpoint),
const preRoles = globalUser.roles || {} request(ctx, { method: "GET" })
if (body.roleId) { )
preRoles[appId] = body.roleId 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 return json
const roles = {} }
for (let [appId, roleId] of Object.entries(preRoles)) {
roles[getDeployedAppID(appId)] = roleId exports.addAppRoleToSelf = async (ctx, appId, roleId) => {
} const self = await exports.getGlobalSelf(ctx)
const endpoint = `/api/admin/users` const endpoint = `/api/admin/users/self`
const reqCfg = { const reqCfg = {
method: "POST", method: "POST",
body: { body: {
...globalUser, roles: {
password: body.password || undefined, ...self.roles,
status: body.status, [appId]: roleId,
email: body.email, }
roles,
builder: {
global: true,
},
}, },
} }
const response = await fetch( const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + endpoint), checkSlashesInUrl(env.WORKER_URL + endpoint),
request(ctx, reqCfg) request(ctx, reqCfg)
) )
const json = await response.json() const json = await response.json()
if (json.status !== 200 && response.status !== 200) { if (json.status !== 200 && response.status !== 200) {
ctx.throw(400, "Unable to save global user.") ctx.throw(400, "Unable to save self globally.")
}
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,
} }
return json
} }

View File

@ -8,7 +8,7 @@ const CouchDB = require("../../../db")
exports.fetch = async ctx => { exports.fetch = async ctx => {
// always use the dev apps as they'll be most up to date (true) // 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 = [] const promises = []
for (let app of apps) { for (let app of apps) {
// use dev app IDs // use dev app IDs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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