diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts index 2c2c29cee2..d0afb860a1 100644 --- a/packages/backend-core/src/db/constants.ts +++ b/packages/backend-core/src/db/constants.ts @@ -20,6 +20,7 @@ export enum ViewName { AUTOMATION_LOGS = "automation_logs", ACCOUNT_BY_EMAIL = "account_by_email", PLATFORM_USERS_LOWERCASE = "platform_users_lowercase", + USER_BY_GROUP = "by_group_user", } export const DeprecatedViews = { diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index c93c7b5662..09a7ac17d9 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -3,7 +3,7 @@ import { DEFAULT_TENANT_ID, Configs } from "../constants" import env from "../environment" import { SEPARATOR, DocumentType, UNICODE_MAX, ViewName } from "./constants" import { getTenantId, getGlobalDB } from "../context" -import { getGlobalDBName } from "../tenancy/utils" +import { getGlobalDBName } from "../tenancy" import fetch from "node-fetch" import { doWithDB, allDbs } from "./index" import { getCouchInfo } from "./pouch" diff --git a/packages/backend-core/src/db/views.ts b/packages/backend-core/src/db/views.ts index c337d26eaa..f0fff918fc 100644 --- a/packages/backend-core/src/db/views.ts +++ b/packages/backend-core/src/db/views.ts @@ -36,154 +36,91 @@ async function removeDeprecated(db: PouchDB.Database, viewName: ViewName) { } } -export const createNewUserEmailView = async () => { - const db = getGlobalDB() +export async function createView(db: any, viewJs: string, viewName: string) { let designDoc try { - designDoc = await db.get(DESIGN_DB) + designDoc = (await db.get(DESIGN_DB)) as DesignDocument } catch (err) { // no design doc, make one designDoc = DesignDoc() } const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}")) { - emit(doc.email.toLowerCase(), doc._id) - } - }`, + map: viewJs, } designDoc.views = { ...designDoc.views, - [ViewName.USER_BY_EMAIL]: view, + [viewName]: view, } await db.put(designDoc) } +export const createNewUserEmailView = async () => { + const db = getGlobalDB() + const viewJs = `function(doc) { + if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}")) { + emit(doc.email.toLowerCase(), doc._id) + } + }` + await createView(db, viewJs, ViewName.USER_BY_EMAIL) +} + export const createAccountEmailView = async () => { + const viewJs = `function(doc) { + if (doc._id.startsWith("${DocumentType.ACCOUNT_METADATA}${SEPARATOR}")) { + emit(doc.email.toLowerCase(), doc._id) + } + }` await doWithDB( StaticDatabases.PLATFORM_INFO.name, async (db: PouchDB.Database) => { - let designDoc - try { - designDoc = await db.get(DESIGN_DB) - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.ACCOUNT_METADATA}${SEPARATOR}")) { - emit(doc.email.toLowerCase(), doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.ACCOUNT_BY_EMAIL]: view, - } - await db.put(designDoc) + await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL) } ) } export const createUserAppView = async () => { const db = getGlobalDB() as PouchDB.Database - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) { - for (let prodAppId of Object.keys(doc.roles)) { - let emitted = prodAppId + "${SEPARATOR}" + doc._id - emit(emitted, null) - } + const viewJs = `function(doc) { + if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) { + for (let prodAppId of Object.keys(doc.roles)) { + let emitted = prodAppId + "${SEPARATOR}" + doc._id + emit(emitted, null) } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.USER_BY_APP]: view, - } - await db.put(designDoc) + } + }` + await createView(db, viewJs, ViewName.USER_BY_APP) } export const createApiKeyView = async () => { const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - designDoc = DesignDoc() - } - const view = { - map: `function(doc) { - if (doc._id.startsWith("${DocumentType.DEV_INFO}") && doc.apiKey) { - emit(doc.apiKey, doc.userId) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.BY_API_KEY]: view, - } - await db.put(designDoc) + const viewJs = `function(doc) { + if (doc._id.startsWith("${DocumentType.DEV_INFO}") && doc.apiKey) { + emit(doc.apiKey, doc.userId) + } + }` + await createView(db, viewJs, ViewName.BY_API_KEY) } export const createUserBuildersView = async () => { const db = getGlobalDB() - let designDoc - try { - designDoc = await db.get("_design/database") - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - map: `function(doc) { - if (doc.builder && doc.builder.global === true) { - emit(doc._id, doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.USER_BY_BUILDERS]: view, - } - await db.put(designDoc) + const viewJs = `function(doc) { + if (doc.builder && doc.builder.global === true) { + emit(doc._id, doc._id) + } + }` + await createView(db, viewJs, ViewName.USER_BY_BUILDERS) } export const createPlatformUserView = async () => { + const viewJs = `function(doc) { + if (doc.tenantId) { + emit(doc._id.toLowerCase(), doc._id) + } + }` await doWithDB( StaticDatabases.PLATFORM_INFO.name, async (db: PouchDB.Database) => { - let designDoc - try { - designDoc = await db.get(DESIGN_DB) - } catch (err) { - // no design doc, make one - designDoc = DesignDoc() - } - const view = { - // if using variables in a map function need to inject them before use - map: `function(doc) { - if (doc.tenantId) { - emit(doc._id.toLowerCase(), doc._id) - } - }`, - } - designDoc.views = { - ...designDoc.views, - [ViewName.PLATFORM_USERS_LOWERCASE]: view, - } - await db.put(designDoc) + await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE) } ) } @@ -196,7 +133,7 @@ export const queryView = async ( viewName: ViewName, params: PouchDB.Query.Options, db: PouchDB.Database, - CreateFuncByName: any, + createFunc: any, opts?: QueryViewOptions ): Promise => { try { @@ -213,10 +150,9 @@ export const queryView = async ( } } catch (err: any) { if (err != null && err.name === "not_found") { - const createFunc = CreateFuncByName[viewName] await removeDeprecated(db, viewName) await createFunc() - return queryView(viewName, params, db, CreateFuncByName, opts) + return queryView(viewName, params, db, createFunc, opts) } else { throw err } @@ -228,7 +164,7 @@ export const queryPlatformView = async ( params: PouchDB.Query.Options, opts?: QueryViewOptions ): Promise => { - const CreateFuncByName = { + const CreateFuncByName: any = { [ViewName.ACCOUNT_BY_EMAIL]: createAccountEmailView, [ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView, } @@ -236,7 +172,8 @@ export const queryPlatformView = async ( return doWithDB( StaticDatabases.PLATFORM_INFO.name, async (db: PouchDB.Database) => { - return queryView(viewName, params, db, CreateFuncByName, opts) + const createFn = CreateFuncByName[viewName] + return queryView(viewName, params, db, createFn, opts) } ) } @@ -247,7 +184,7 @@ export const queryGlobalView = async ( db?: PouchDB.Database, opts?: QueryViewOptions ): Promise => { - const CreateFuncByName = { + const CreateFuncByName: any = { [ViewName.USER_BY_EMAIL]: createNewUserEmailView, [ViewName.BY_API_KEY]: createApiKeyView, [ViewName.USER_BY_BUILDERS]: createUserBuildersView, @@ -257,5 +194,6 @@ export const queryGlobalView = async ( if (!db) { db = getGlobalDB() as PouchDB.Database } - return queryView(viewName, params, db, CreateFuncByName, opts) + const createFn = CreateFuncByName[viewName] + return queryView(viewName, params, db, createFn, opts) } diff --git a/packages/builder/src/helpers/featureFlags.js b/packages/builder/src/helpers/featureFlags.js index a0cda8d5fa..ae6646bd9f 100644 --- a/packages/builder/src/helpers/featureFlags.js +++ b/packages/builder/src/helpers/featureFlags.js @@ -1,15 +1,12 @@ import { auth } from "../stores/portal" import { get } from "svelte/store" -export const FEATURE_FLAGS = { +export const TENANT_FEATURE_FLAGS = { LICENSING: "LICENSING", USER_GROUPS: "USER_GROUPS", } export const isEnabled = featureFlag => { const user = get(auth).user - if (user?.featureFlags?.includes(featureFlag)) { - return true - } - return false + return !!user?.featureFlags?.includes(featureFlag) } diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index bdf18ab508..cf9cd55b19 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -19,7 +19,7 @@ import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte" import UpdateAPIKeyModal from "components/settings/UpdateAPIKeyModal.svelte" import Logo from "assets/bb-emblem.svg" - import { isEnabled, FEATURE_FLAGS } from "../../../helpers/featureFlags" + import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" let loaded = false let userInfoModal @@ -44,7 +44,7 @@ href: "/builder/portal/manage/users", heading: "Manage", }, - isEnabled(FEATURE_FLAGS.USER_GROUPS) + isEnabled(TENANT_FEATURE_FLAGS.USER_GROUPS) ? { title: "User Groups", href: "/builder/portal/manage/groups", @@ -103,7 +103,7 @@ ]) } - if (isEnabled(FEATURE_FLAGS.LICENSING)) { + if (isEnabled(TENANT_FEATURE_FLAGS.LICENSING)) { // always show usage in self-host or cloud if licensing enabled menu = menu.concat([ { diff --git a/packages/builder/src/stores/portal/auth.js b/packages/builder/src/stores/portal/auth.js index 8ac19ab785..0ab01bedd3 100644 --- a/packages/builder/src/stores/portal/auth.js +++ b/packages/builder/src/stores/portal/auth.js @@ -2,7 +2,7 @@ import { derived, writable, get } from "svelte/store" import { API } from "api" import { admin } from "stores/portal" import analytics from "analytics" -import { FEATURE_FLAGS } from "helpers/featureFlags" +import { TENANT_FEATURE_FLAGS } from "helpers/featureFlags" import { Constants } from "@budibase/frontend-core" export function createAuthStore() { @@ -35,7 +35,7 @@ export function createAuthStore() { isBuilder = !!user.builder?.global groupsEnabled = user?.license.features.includes(Constants.Features.USER_GROUPS) && - user?.featureFlags.includes(FEATURE_FLAGS.USER_GROUPS) + user?.featureFlags.includes(TENANT_FEATURE_FLAGS.USER_GROUPS) } return { user: $store.user,