Merge branch 'develop' into budi-6158/prevent_duplicated_group_names
This commit is contained in:
commit
9b6cb5d09d
|
@ -1,47 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
import { url, goto } from "@roxi/routify"
|
|
||||||
import {
|
import {
|
||||||
Button,
|
ActionMenu,
|
||||||
Layout,
|
|
||||||
Heading,
|
Heading,
|
||||||
Icon,
|
Icon,
|
||||||
Popover,
|
Layout,
|
||||||
notifications,
|
|
||||||
Table,
|
|
||||||
ActionMenu,
|
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
|
Table,
|
||||||
|
notifications,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import UserGroupPicker from "components/settings/UserGroupPicker.svelte"
|
import { goto, url } from "@roxi/routify"
|
||||||
import { createPaginationStore } from "helpers/pagination"
|
|
||||||
import { users, apps, groups, auth, features } from "stores/portal"
|
|
||||||
import { onMount, setContext } from "svelte"
|
|
||||||
import { roles } from "stores/backend"
|
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
|
import { Breadcrumb, Breadcrumbs } from "components/portal/page"
|
||||||
|
import { roles } from "stores/backend"
|
||||||
|
import { apps, auth, features, groups } from "stores/portal"
|
||||||
|
import { onMount, setContext } from "svelte"
|
||||||
|
import AppNameTableRenderer from "../users/_components/AppNameTableRenderer.svelte"
|
||||||
|
import AppRoleTableRenderer from "../users/_components/AppRoleTableRenderer.svelte"
|
||||||
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
import CreateEditGroupModal from "./_components/CreateEditGroupModal.svelte"
|
||||||
import GroupIcon from "./_components/GroupIcon.svelte"
|
import GroupIcon from "./_components/GroupIcon.svelte"
|
||||||
import { Breadcrumbs, Breadcrumb } from "components/portal/page"
|
import GroupUsers from "./_components/GroupUsers.svelte"
|
||||||
import AppNameTableRenderer from "../users/_components/AppNameTableRenderer.svelte"
|
|
||||||
import RemoveUserTableRenderer from "./_components/RemoveUserTableRenderer.svelte"
|
|
||||||
import AppRoleTableRenderer from "../users/_components/AppRoleTableRenderer.svelte"
|
|
||||||
import ScimBanner from "../_components/SCIMBanner.svelte"
|
|
||||||
|
|
||||||
export let groupId
|
export let groupId
|
||||||
|
|
||||||
$: userSchema = {
|
|
||||||
email: {
|
|
||||||
width: "1fr",
|
|
||||||
},
|
|
||||||
...(readonly
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
_id: {
|
|
||||||
displayName: "",
|
|
||||||
width: "auto",
|
|
||||||
borderLeft: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
const appSchema = {
|
const appSchema = {
|
||||||
name: {
|
name: {
|
||||||
width: "2fr",
|
width: "2fr",
|
||||||
|
@ -50,12 +31,6 @@
|
||||||
width: "1fr",
|
width: "1fr",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const customUserTableRenderers = [
|
|
||||||
{
|
|
||||||
column: "_id",
|
|
||||||
component: RemoveUserTableRenderer,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const customAppTableRenderers = [
|
const customAppTableRenderers = [
|
||||||
{
|
{
|
||||||
column: "name",
|
column: "name",
|
||||||
|
@ -67,20 +42,12 @@
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
let popoverAnchor
|
|
||||||
let popover
|
|
||||||
let searchTerm = ""
|
|
||||||
let prevSearch = undefined
|
|
||||||
let pageInfo = createPaginationStore()
|
|
||||||
let loaded = false
|
let loaded = false
|
||||||
let editModal, deleteModal
|
let editModal, deleteModal
|
||||||
|
|
||||||
$: scimEnabled = $features.isScimEnabled
|
$: scimEnabled = $features.isScimEnabled
|
||||||
$: readonly = !$auth.isAdmin || scimEnabled
|
$: readonly = !$auth.isAdmin || scimEnabled
|
||||||
$: page = $pageInfo.page
|
|
||||||
$: fetchUsers(page, searchTerm)
|
|
||||||
$: group = $groups.find(x => x._id === groupId)
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
$: filtered = $users.data
|
|
||||||
$: groupApps = $apps
|
$: groupApps = $apps
|
||||||
.filter(app =>
|
.filter(app =>
|
||||||
groups.actions
|
groups.actions
|
||||||
|
@ -97,25 +64,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUsers(page, search) {
|
|
||||||
if ($pageInfo.loading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// need to remove the page if they've started searching
|
|
||||||
if (search && !prevSearch) {
|
|
||||||
pageInfo.reset()
|
|
||||||
page = undefined
|
|
||||||
}
|
|
||||||
prevSearch = search
|
|
||||||
try {
|
|
||||||
pageInfo.loading()
|
|
||||||
await users.search({ page, email: search })
|
|
||||||
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error getting user list")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteGroup() {
|
async function deleteGroup() {
|
||||||
try {
|
try {
|
||||||
await groups.actions.delete(group)
|
await groups.actions.delete(group)
|
||||||
|
@ -138,17 +86,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUser = async id => {
|
|
||||||
await groups.actions.removeUser(groupId, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeApp = async app => {
|
const removeApp = async app => {
|
||||||
await groups.actions.removeApp(groupId, apps.getProdAppID(app.devId))
|
await groups.actions.removeApp(groupId, apps.getProdAppID(app.devId))
|
||||||
}
|
}
|
||||||
|
|
||||||
setContext("users", {
|
|
||||||
removeUser,
|
|
||||||
})
|
|
||||||
setContext("roles", {
|
setContext("roles", {
|
||||||
updateRole: () => {},
|
updateRole: () => {},
|
||||||
removeRole: removeApp,
|
removeRole: removeApp,
|
||||||
|
@ -190,41 +130,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
<div class="header">
|
<GroupUsers {groupId} />
|
||||||
<Heading size="S">Users</Heading>
|
|
||||||
{#if !scimEnabled}
|
|
||||||
<div bind:this={popoverAnchor}>
|
|
||||||
<Button disabled={readonly} on:click={popover.show()} cta
|
|
||||||
>Add user</Button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<ScimBanner />
|
|
||||||
{/if}
|
|
||||||
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
|
||||||
<UserGroupPicker
|
|
||||||
bind:searchTerm
|
|
||||||
labelKey="email"
|
|
||||||
selected={group.users?.map(user => user._id)}
|
|
||||||
list={$users.data}
|
|
||||||
on:select={e => groups.actions.addUser(groupId, e.detail)}
|
|
||||||
on:deselect={e => groups.actions.removeUser(groupId, e.detail)}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Table
|
|
||||||
schema={userSchema}
|
|
||||||
data={group?.users}
|
|
||||||
allowEditRows={false}
|
|
||||||
customPlaceholder
|
|
||||||
customRenderers={customUserTableRenderers}
|
|
||||||
on:click={e => $goto(`../users/${e.detail._id}`)}
|
|
||||||
>
|
|
||||||
<div class="placeholder" slot="placeholder">
|
|
||||||
<Heading size="S">This user group doesn't have any users</Heading>
|
|
||||||
</div>
|
|
||||||
</Table>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script>
|
||||||
|
import { Button, Popover, notifications } from "@budibase/bbui"
|
||||||
|
import UserGroupPicker from "components/settings/UserGroupPicker.svelte"
|
||||||
|
import { createPaginationStore } from "helpers/pagination"
|
||||||
|
import { auth, groups, users } from "stores/portal"
|
||||||
|
|
||||||
|
export let groupId
|
||||||
|
export let onUsersUpdated
|
||||||
|
|
||||||
|
let popoverAnchor
|
||||||
|
let popover
|
||||||
|
let searchTerm = ""
|
||||||
|
let prevSearch = undefined
|
||||||
|
let pageInfo = createPaginationStore()
|
||||||
|
|
||||||
|
$: readonly = !$auth.isAdmin
|
||||||
|
$: page = $pageInfo.page
|
||||||
|
$: searchUsers(page, searchTerm)
|
||||||
|
$: group = $groups.find(x => x._id === groupId)
|
||||||
|
|
||||||
|
async function searchUsers(page, search) {
|
||||||
|
if ($pageInfo.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// need to remove the page if they've started searching
|
||||||
|
if (search && !prevSearch) {
|
||||||
|
pageInfo.reset()
|
||||||
|
page = undefined
|
||||||
|
}
|
||||||
|
prevSearch = search
|
||||||
|
try {
|
||||||
|
pageInfo.loading()
|
||||||
|
await users.search({ page, email: search })
|
||||||
|
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error("Error getting user list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={popoverAnchor}>
|
||||||
|
<Button disabled={readonly} on:click={popover.show()} cta>Add user</Button>
|
||||||
|
</div>
|
||||||
|
<Popover align="right" bind:this={popover} anchor={popoverAnchor}>
|
||||||
|
<UserGroupPicker
|
||||||
|
bind:searchTerm
|
||||||
|
labelKey="email"
|
||||||
|
selected={group.users?.map(user => user._id)}
|
||||||
|
list={$users.data}
|
||||||
|
on:select={async e => {
|
||||||
|
await groups.actions.addUser(groupId, e.detail)
|
||||||
|
onUsersUpdated()
|
||||||
|
}}
|
||||||
|
on:deselect={async e => {
|
||||||
|
await groups.actions.removeUser(groupId, e.detail)
|
||||||
|
onUsersUpdated()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popover>
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script>
|
||||||
|
import EditUserPicker from "./EditUserPicker.svelte"
|
||||||
|
|
||||||
|
import { Heading, Pagination, Table } from "@budibase/bbui"
|
||||||
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
|
import { goto } from "@roxi/routify"
|
||||||
|
import { API } from "api"
|
||||||
|
import { auth, features, groups } from "stores/portal"
|
||||||
|
import { setContext } from "svelte"
|
||||||
|
import ScimBanner from "../../_components/SCIMBanner.svelte"
|
||||||
|
import RemoveUserTableRenderer from "../_components/RemoveUserTableRenderer.svelte"
|
||||||
|
|
||||||
|
export let groupId
|
||||||
|
|
||||||
|
const fetchGroupUsers = fetchData({
|
||||||
|
API,
|
||||||
|
datasource: {
|
||||||
|
type: "groupUser",
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
query: {
|
||||||
|
groupId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
$: userSchema = {
|
||||||
|
email: {
|
||||||
|
width: "1fr",
|
||||||
|
},
|
||||||
|
...(readonly
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
_id: {
|
||||||
|
displayName: "",
|
||||||
|
width: "auto",
|
||||||
|
borderLeft: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
const customUserTableRenderers = [
|
||||||
|
{
|
||||||
|
column: "_id",
|
||||||
|
component: RemoveUserTableRenderer,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: scimEnabled = $features.isScimEnabled
|
||||||
|
$: readonly = !$auth.isAdmin || scimEnabled
|
||||||
|
|
||||||
|
const removeUser = async id => {
|
||||||
|
await groups.actions.removeUser(groupId, id)
|
||||||
|
fetchGroupUsers.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext("users", {
|
||||||
|
removeUser,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<Heading size="S">Users</Heading>
|
||||||
|
{#if !scimEnabled}
|
||||||
|
<EditUserPicker {groupId} onUsersUpdated={fetchGroupUsers.getInitialData} />
|
||||||
|
{:else}
|
||||||
|
<ScimBanner />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
schema={userSchema}
|
||||||
|
data={$fetchGroupUsers?.rows}
|
||||||
|
allowEditRows={false}
|
||||||
|
customPlaceholder
|
||||||
|
customRenderers={customUserTableRenderers}
|
||||||
|
on:click={e => $goto(`../users/${e.detail._id}`)}
|
||||||
|
>
|
||||||
|
<div class="placeholder" slot="placeholder">
|
||||||
|
<Heading size="S">This user group doesn't have any users</Heading>
|
||||||
|
</div>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<Pagination
|
||||||
|
page={$fetchGroupUsers.pageNumber + 1}
|
||||||
|
hasPrevPage={$fetchGroupUsers.loading
|
||||||
|
? false
|
||||||
|
: $fetchGroupUsers.hasPrevPage}
|
||||||
|
hasNextPage={$fetchGroupUsers.loading
|
||||||
|
? false
|
||||||
|
: $fetchGroupUsers.hasNextPage}
|
||||||
|
goToPrevPage={$fetchGroupUsers.loading ? null : fetchGroupUsers.prevPage}
|
||||||
|
goToNextPage={$fetchGroupUsers.loading ? null : fetchGroupUsers.nextPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.header :global(.spectrum-Heading) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -28,7 +28,7 @@ export function createGroupsStore() {
|
||||||
// on the backend anyway
|
// on the backend anyway
|
||||||
if (get(licensing).groupsEnabled) {
|
if (get(licensing).groupsEnabled) {
|
||||||
const groups = await API.getGroups()
|
const groups = await API.getGroups()
|
||||||
store.set(groups)
|
store.set(groups.data)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,20 @@ export const buildGroupsEndpoints = API => {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a group users by the group id
|
||||||
|
*/
|
||||||
|
getGroupUsers: async ({ id, bookmark }) => {
|
||||||
|
let url = `/api/global/groups/${id}/users?`
|
||||||
|
if (bookmark) {
|
||||||
|
url += `bookmark=${bookmark}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return await API.get({
|
||||||
|
url,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds users to a group
|
* Adds users to a group
|
||||||
* @param groupId The group to update
|
* @param groupId The group to update
|
||||||
|
|
|
@ -362,13 +362,35 @@ export default class DataFetch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.store.update($store => ({ ...$store, loading: true }))
|
this.store.update($store => ({ ...$store, loading: true }))
|
||||||
const { rows, info, error } = await this.getPage()
|
const { rows, info, error, cursor } = await this.getPage()
|
||||||
|
|
||||||
|
let { cursors } = get(this.store)
|
||||||
|
const { pageNumber } = get(this.store)
|
||||||
|
|
||||||
|
if (!rows.length && pageNumber > 0) {
|
||||||
|
// If the full page is gone but we have previous pages, navigate to the previous page
|
||||||
|
this.store.update($store => ({
|
||||||
|
...$store,
|
||||||
|
loading: false,
|
||||||
|
cursors: cursors.slice(0, pageNumber),
|
||||||
|
}))
|
||||||
|
return await this.prevPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentNextCursor = cursors[pageNumber + 1]
|
||||||
|
if (currentNextCursor != cursor) {
|
||||||
|
// If the current cursor changed, all the next pages need to be updated, so we mark them as stale
|
||||||
|
cursors = cursors.slice(0, pageNumber + 1)
|
||||||
|
cursors[pageNumber + 1] = cursor
|
||||||
|
}
|
||||||
|
|
||||||
this.store.update($store => ({
|
this.store.update($store => ({
|
||||||
...$store,
|
...$store,
|
||||||
rows,
|
rows,
|
||||||
info,
|
info,
|
||||||
loading: false,
|
loading: false,
|
||||||
error,
|
error,
|
||||||
|
cursors,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { TableNames } from "../constants"
|
||||||
|
|
||||||
|
export default class GroupUserFetch extends DataFetch {
|
||||||
|
constructor(opts) {
|
||||||
|
super({
|
||||||
|
...opts,
|
||||||
|
datasource: {
|
||||||
|
tableId: TableNames.USERS,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
determineFeatureFlags() {
|
||||||
|
return {
|
||||||
|
supportsSearch: true,
|
||||||
|
supportsSort: false,
|
||||||
|
supportsPagination: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefinition() {
|
||||||
|
return {
|
||||||
|
schema: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
const { query, cursor } = get(this.store)
|
||||||
|
try {
|
||||||
|
const res = await this.API.getGroupUsers({
|
||||||
|
id: query.groupId,
|
||||||
|
bookmark: cursor,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: res?.users || [],
|
||||||
|
hasNextPage: res?.hasNextPage || false,
|
||||||
|
cursor: res?.bookmark || null,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
hasNextPage: false,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import NestedProviderFetch from "./NestedProviderFetch.js"
|
||||||
import FieldFetch from "./FieldFetch.js"
|
import FieldFetch from "./FieldFetch.js"
|
||||||
import JSONArrayFetch from "./JSONArrayFetch.js"
|
import JSONArrayFetch from "./JSONArrayFetch.js"
|
||||||
import UserFetch from "./UserFetch.js"
|
import UserFetch from "./UserFetch.js"
|
||||||
|
import GroupUserFetch from "./GroupUserFetch.js"
|
||||||
|
|
||||||
const DataFetchMap = {
|
const DataFetchMap = {
|
||||||
table: TableFetch,
|
table: TableFetch,
|
||||||
|
@ -13,6 +14,7 @@ const DataFetchMap = {
|
||||||
query: QueryFetch,
|
query: QueryFetch,
|
||||||
link: RelationshipFetch,
|
link: RelationshipFetch,
|
||||||
user: UserFetch,
|
user: UserFetch,
|
||||||
|
groupUser: GroupUserFetch,
|
||||||
|
|
||||||
// Client specific datasource types
|
// Client specific datasource types
|
||||||
provider: NestedProviderFetch,
|
provider: NestedProviderFetch,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { PaginationResponse } from "../../api"
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
|
||||||
export interface UserGroup extends Document {
|
export interface UserGroup extends Document {
|
||||||
|
@ -21,3 +22,15 @@ export interface GroupUser {
|
||||||
export interface UserGroupRoles {
|
export interface UserGroupRoles {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchGroupRequest {}
|
||||||
|
export interface SearchGroupResponse {
|
||||||
|
data: UserGroup[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchUserGroupResponse extends PaginationResponse {
|
||||||
|
users: {
|
||||||
|
_id: any
|
||||||
|
email: any
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ export type DatabaseQueryOpts = {
|
||||||
key?: string
|
key?: string
|
||||||
keys?: string[]
|
keys?: string[]
|
||||||
group?: boolean
|
group?: boolean
|
||||||
|
startkey_docid?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isDocument = (doc: any): doc is Document => {
|
export const isDocument = (doc: any): doc is Document => {
|
||||||
|
|
|
@ -69,9 +69,11 @@ const bulkCreate = async (users: User[], groupIds: string[]) => {
|
||||||
return await userSdk.bulkCreate(users, groupIds)
|
return await userSdk.bulkCreate(users, groupIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkUpdate = async (ctx: any) => {
|
export const bulkUpdate = async (
|
||||||
|
ctx: Ctx<BulkUserRequest, BulkUserResponse>
|
||||||
|
) => {
|
||||||
const currentUserId = ctx.user._id
|
const currentUserId = ctx.user._id
|
||||||
const input = ctx.request.body as BulkUserRequest
|
const input = ctx.request.body
|
||||||
let created, deleted
|
let created, deleted
|
||||||
try {
|
try {
|
||||||
if (input.create) {
|
if (input.create) {
|
||||||
|
@ -83,7 +85,7 @@ export const bulkUpdate = async (ctx: any) => {
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
ctx.throw(err.status || 400, err?.message || err)
|
ctx.throw(err.status || 400, err?.message || err)
|
||||||
}
|
}
|
||||||
ctx.body = { created, deleted } as BulkUserResponse
|
ctx.body = { created, deleted }
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseBooleanParam = (param: any) => {
|
const parseBooleanParam = (param: any) => {
|
||||||
|
@ -184,15 +186,15 @@ export const destroy = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAppUsers = async (ctx: any) => {
|
export const getAppUsers = async (ctx: Ctx<SearchUsersRequest>) => {
|
||||||
const body = ctx.request.body as SearchUsersRequest
|
const body = ctx.request.body
|
||||||
const users = await userSdk.getUsersByAppAccess(body?.appId)
|
const users = await userSdk.getUsersByAppAccess(body?.appId)
|
||||||
|
|
||||||
ctx.body = { data: users }
|
ctx.body = { data: users }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const search = async (ctx: any) => {
|
export const search = async (ctx: Ctx<SearchUsersRequest>) => {
|
||||||
const body = ctx.request.body as SearchUsersRequest
|
const body = ctx.request.body
|
||||||
|
|
||||||
if (body.paginated === false) {
|
if (body.paginated === false) {
|
||||||
await getAppUsers(ctx)
|
await getAppUsers(ctx)
|
||||||
|
@ -238,8 +240,8 @@ export const tenantUserLookup = async (ctx: any) => {
|
||||||
/*
|
/*
|
||||||
Encapsulate the app user onboarding flows here.
|
Encapsulate the app user onboarding flows here.
|
||||||
*/
|
*/
|
||||||
export const onboardUsers = async (ctx: any) => {
|
export const onboardUsers = async (ctx: Ctx<InviteUsersRequest>) => {
|
||||||
const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
|
const request = ctx.request.body
|
||||||
const isBulkCreate = "create" in request
|
const isBulkCreate = "create" in request
|
||||||
|
|
||||||
const emailConfigured = await isEmailConfigured()
|
const emailConfigured = await isEmailConfigured()
|
||||||
|
@ -255,7 +257,7 @@ export const onboardUsers = async (ctx: any) => {
|
||||||
} else if (emailConfigured) {
|
} else if (emailConfigured) {
|
||||||
onboardingResponse = await inviteMultiple(ctx)
|
onboardingResponse = await inviteMultiple(ctx)
|
||||||
} else if (!emailConfigured) {
|
} else if (!emailConfigured) {
|
||||||
const inviteRequest = ctx.request.body as InviteUsersRequest
|
const inviteRequest = ctx.request.body
|
||||||
|
|
||||||
let createdPasswords: any = {}
|
let createdPasswords: any = {}
|
||||||
|
|
||||||
|
@ -295,10 +297,10 @@ export const onboardUsers = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const invite = async (ctx: any) => {
|
export const invite = async (ctx: Ctx<InviteUserRequest>) => {
|
||||||
const request = ctx.request.body as InviteUserRequest
|
const request = ctx.request.body
|
||||||
|
|
||||||
let multiRequest = [request] as InviteUsersRequest
|
let multiRequest = [request]
|
||||||
const response = await userSdk.invite(multiRequest)
|
const response = await userSdk.invite(multiRequest)
|
||||||
|
|
||||||
// explicitly throw for single user invite
|
// explicitly throw for single user invite
|
||||||
|
@ -318,8 +320,8 @@ export const invite = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inviteMultiple = async (ctx: any) => {
|
export const inviteMultiple = async (ctx: Ctx<InviteUsersRequest>) => {
|
||||||
const request = ctx.request.body as InviteUsersRequest
|
const request = ctx.request.body
|
||||||
ctx.body = await userSdk.invite(request)
|
ctx.body = await userSdk.invite(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +426,6 @@ export const inviteAccept = async (
|
||||||
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
if (err.code === ErrorCode.USAGE_LIMIT_EXCEEDED) {
|
||||||
// explicitly re-throw limit exceeded errors
|
// explicitly re-throw limit exceeded errors
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
console.warn("Error inviting user", err)
|
console.warn("Error inviting user", err)
|
||||||
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
ctx.throw(400, "Unable to create new user, invitation invalid.")
|
||||||
|
|
Loading…
Reference in New Issue