diff --git a/.vscode/settings.json b/.vscode/settings.json
index d471924fe0..f93d3f0886 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,8 @@
"editor.codeActionsOnSave": {
"source.fixAll": true
},
- "editor.defaultFormatter": "svelte.svelte-vscode"
+ "editor.defaultFormatter": "svelte.svelte-vscode",
+ "[javascript]": {
+ "editor.defaultFormatter": "vscode.typescript-language-features"
+ }
}
diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js
index 271d4f412d..0f3daa918c 100644
--- a/packages/backend-core/src/db/constants.js
+++ b/packages/backend-core/src/db/constants.js
@@ -15,6 +15,7 @@ exports.DocumentTypes = {
ROLE: "role",
MIGRATIONS: "migrations",
DEV_INFO: "devinfo",
+ GROUP: "gr"
}
exports.StaticDatabases = {
diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js
index d6eb0aa89e..212ada6e9a 100644
--- a/packages/backend-core/src/db/utils.js
+++ b/packages/backend-core/src/db/utils.js
@@ -148,6 +148,29 @@ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => {
}
}
+/**
+ * Generates a new user group ID
+ * @returns {string} The new user group ID which info can be stored under.
+ */
+exports.generateUserGroupID = () => {
+ return `${DocumentTypes.GROUP}${SEPARATOR}${newid()}`
+}
+
+/**
+ * Gets parameters for retrieving groups.
+ */
+exports.getUserGroupsParams = (groupId, otherProps = {}) => {
+ if (!groupId) {
+ groupId = ""
+ }
+ return {
+ ...otherProps,
+ startkey: `${DocumentTypes.GROUP}${SEPARATOR}${groupId}`,
+ endkey: `${DocumentTypes.GROUP}${SEPARATOR}${groupId}${UNICODE_MAX}`,
+ }
+}
+
+
/**
* Generates a new role ID.
* @returns {string} The new role ID which the role doc can be stored under.
diff --git a/packages/builder/src/pages/builder/portal/manage/groups/_components/UserGroupsRow.svelte b/packages/builder/src/pages/builder/portal/manage/groups/_components/UserGroupsRow.svelte
index c4f38f080b..a8fef775bf 100644
--- a/packages/builder/src/pages/builder/portal/manage/groups/_components/UserGroupsRow.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/groups/_components/UserGroupsRow.svelte
@@ -20,7 +20,7 @@
- {parseInt(userCount)} app{parseInt(userCount) === 1 ? "" : "s"}
+ {parseInt(userCount)} user{parseInt(userCount) === 1 ? "" : "s"}
diff --git a/packages/builder/src/pages/builder/portal/manage/groups/index.svelte b/packages/builder/src/pages/builder/portal/manage/groups/index.svelte
index 63fcd105f3..560cf273e5 100644
--- a/packages/builder/src/pages/builder/portal/manage/groups/index.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/groups/index.svelte
@@ -12,30 +12,37 @@
Tags,
IconPicker,
} from "@budibase/bbui"
+ import { API } from "api"
+ import { groups } from "stores/portal"
+ import { onMount } from "svelte"
+
import UserGroupsRow from "./_components/UserGroupsRow.svelte"
+
let modal
let selectedColor
let selectedIcon
+ let groupName
let proPlan = true
- let userGroupData = [
- {
- _id: "gr_123456",
- color: "green",
- icon: "Anchor",
- name: "Core Team",
- userCount: 5,
- appCount: 2,
- },
- {
- _id: "gr_45678",
- color: "red",
- icon: "Beaker",
- name: "QA Team",
- userCount: 3,
- appCount: 7,
- },
- ]
+ async function saveConfig() {
+ try {
+ API.saveGroup({
+ color: selectedColor,
+ icon: selectedIcon,
+ name: groupName,
+ })
+ } catch (error) {
+ notifications.error(`Failed to save group`)
+ }
+ }
+
+ onMount(async () => {
+ try {
+ await groups.init()
+ } catch (error) {
+ notifications.error("Error getting User groups")
+ }
+ })
@@ -72,7 +79,7 @@
- {#each userGroupData as group}
+ {#each $groups as group}
@@ -81,8 +88,13 @@
-
-
+
+
Icon
diff --git a/packages/builder/src/stores/portal/groups.js b/packages/builder/src/stores/portal/groups.js
new file mode 100644
index 0000000000..417438973d
--- /dev/null
+++ b/packages/builder/src/stores/portal/groups.js
@@ -0,0 +1,24 @@
+import { writable } from "svelte/store"
+import { API } from "api"
+import { update } from "lodash"
+
+export function createGroupsStore() {
+ const { subscribe, set } = writable([])
+
+ async function init() {
+ const users = await API.getGroups()
+ set(users)
+ }
+
+ async function save(data) {
+ await API.saveGroup(data)
+ }
+
+ return {
+ subscribe,
+ init,
+ save,
+ }
+}
+
+export const groups = createGroupsStore()
diff --git a/packages/builder/src/stores/portal/index.js b/packages/builder/src/stores/portal/index.js
index 8810ce6b74..2f6ee7cbde 100644
--- a/packages/builder/src/stores/portal/index.js
+++ b/packages/builder/src/stores/portal/index.js
@@ -7,3 +7,4 @@ export { auth } from "./auth"
export { oidc } from "./oidc"
export { templates } from "./templates"
export { licensing } from "./licensing"
+export { groups } from "./groups"
\ No newline at end of file
diff --git a/packages/frontend-core/src/api/groups.js b/packages/frontend-core/src/api/groups.js
new file mode 100644
index 0000000000..2462a491c5
--- /dev/null
+++ b/packages/frontend-core/src/api/groups.js
@@ -0,0 +1,18 @@
+export const buildGroupsEndpoints = API => ({
+ /**
+ * Creates or updates a user in the current tenant.
+ * @param user the new user to create
+ */
+ saveGroup: async group => {
+ return await API.post({
+ url: "/api/global/groups",
+ body: group,
+ })
+ },
+ getGroups: async () => {
+ return await API.get({
+ url: "/api/global/groups",
+ })
+
+ }
+})
diff --git a/packages/frontend-core/src/api/index.js b/packages/frontend-core/src/api/index.js
index 164f51aae5..85c0905bd6 100644
--- a/packages/frontend-core/src/api/index.js
+++ b/packages/frontend-core/src/api/index.js
@@ -23,6 +23,7 @@ import { buildUserEndpoints } from "./user"
import { buildSelfEndpoints } from "./self"
import { buildViewEndpoints } from "./views"
import { buildLicensingEndpoints } from "./licensing"
+import { buildGroupsEndpoints } from "./groups"
const defaultAPIClientConfig = {
/**
@@ -235,5 +236,6 @@ export const createAPIClient = config => {
...buildViewEndpoints(API),
...buildSelfEndpoints(API),
...buildLicensingEndpoints(API),
+ ...buildGroupsEndpoints(API)
}
}
diff --git a/packages/worker/src/api/controllers/global/groups.js b/packages/worker/src/api/controllers/global/groups.js
new file mode 100644
index 0000000000..424c112822
--- /dev/null
+++ b/packages/worker/src/api/controllers/global/groups.js
@@ -0,0 +1,58 @@
+const {
+ generateUserGroupID,
+ getUserGroupsParams
+} = require("@budibase/backend-core/db")
+const { Configs } = require("../../../constants")
+const email = require("../../../utilities/email")
+const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
+const env = require("../../../environment")
+const {
+ withCache,
+ CacheKeys,
+ bustCache,
+} = require("@budibase/backend-core/cache")
+
+
+exports.save = async function (ctx) {
+ const db = getGlobalDB()
+
+ // Config does not exist yet
+ if (!ctx.request.body._id) {
+ ctx.request.body._id = generateUserGroupID(ctx.request.body.name)
+ }
+
+ try {
+ const response = await db.put(ctx.request.body)
+ ctx.body = {
+ _id: response.id,
+ _rev: response.rev,
+ }
+ } catch (err) {
+ ctx.throw(400, err)
+ }
+}
+
+exports.fetch = async function (ctx) {
+ const db = getGlobalDB()
+ console.log('in here')
+ const response = await db.allDocs(
+ getUserGroupsParams(null, {
+ include_docs: true,
+ })
+ )
+ ctx.body = response.rows.map(row => row.doc)
+
+}
+
+
+exports.destroy = async function (ctx) {
+ const db = getGlobalDB()
+ const { id, rev } = ctx.params
+
+ try {
+ await db.remove(id, rev)
+ ctx.body = { message: "Group deleted successfully" }
+ } catch (err) {
+ ctx.throw(err.status, err)
+ }
+}
diff --git a/packages/worker/src/api/routes/global/groups.js b/packages/worker/src/api/routes/global/groups.js
new file mode 100644
index 0000000000..c468531a86
--- /dev/null
+++ b/packages/worker/src/api/routes/global/groups.js
@@ -0,0 +1,27 @@
+const Router = require("@koa/router")
+const controller = require("../../controllers/global/groups")
+const joiValidator = require("../../../middleware/joi-validator")
+const adminOnly = require("../../../middleware/adminOnly")
+const Joi = require("joi")
+
+const router = Router()
+
+function buildGroupSaveValidation() {
+ // prettier-ignore
+ return joiValidator.body(Joi.object({
+ color: Joi.string().required(),
+ icon: Joi.string().required(),
+ name: Joi.string().required()
+ }).required())
+}
+
+router.post(
+ "/api/global/groups",
+ adminOnly,
+ buildGroupSaveValidation(),
+ controller.save
+)
+ .get("/api/global/groups", controller.fetch)
+
+
+module.exports = router
diff --git a/packages/worker/src/api/routes/index.js b/packages/worker/src/api/routes/index.js
index c351455a0d..b85b18deb7 100644
--- a/packages/worker/src/api/routes/index.js
+++ b/packages/worker/src/api/routes/index.js
@@ -11,7 +11,7 @@ const tenantsRoutes = require("./system/tenants")
const statusRoutes = require("./system/status")
const selfRoutes = require("./global/self")
const licenseRoutes = require("./global/license")
-
+const userGroupRoutes = require("./global/groups")
exports.routes = [
configRoutes,
userRoutes,
@@ -26,4 +26,5 @@ exports.routes = [
statusRoutes,
selfRoutes,
licenseRoutes,
+ userGroupRoutes
]