Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert portal user store to TS #15327

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -442,21 +442,19 @@

const onUpdateUserInvite = async (invite, role) => {
let updateBody = {
code: invite.code,
apps: {
...invite.apps,
[prodAppId]: role,
},
}

if (role === Constants.Roles.CREATOR) {
updateBody.builder = updateBody.builder || {}
updateBody.builder.apps = [...(updateBody.builder.apps ?? []), prodAppId]
delete updateBody?.apps?.[prodAppId]
} else if (role !== Constants.Roles.CREATOR && invite?.builder?.apps) {
invite.builder.apps = []
}
await users.updateInvite(updateBody)
await users.updateInvite(invite.code, updateBody)
await filterInvites(query)
}

Expand All @@ -470,8 +468,7 @@
let updated = { ...invite }
delete updated.info.apps[prodAppId]

return await users.updateInvite({
code: updated.code,
return await users.updateInvite(updated.code, {
apps: updated.apps,
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,77 @@
import { writable } from "svelte/store"
import { API } from "@/api"
import { update } from "lodash"
import { licensing } from "."
import { sdk } from "@budibase/shared-core"
import { Constants } from "@budibase/frontend-core"
import {
DeleteInviteUsersRequest,
InviteUsersRequest,
SearchUsersRequest,
SearchUsersResponse,
UpdateInviteRequest,
User,
UserIdentifier,
UnsavedUser,
} from "@budibase/types"
import { BudiStore } from "../BudiStore"

interface UserInfo {
email: string
password: string
forceResetPassword?: boolean
role: keyof typeof Constants.BudibaseRoles
}

type UserState = SearchUsersResponse & SearchUsersRequest

class UserStore extends BudiStore<UserState> {
constructor() {
super({
data: [],
})

export function createUsersStore() {
const { subscribe, set } = writable({})
// Update quotas after any add or remove operation
this.create = this.refreshUsage(this.create)
this.save = this.refreshUsage(this.save)
this.delete = this.refreshUsage(this.delete)
this.bulkDelete = this.refreshUsage(this.bulkDelete)
}

// opts can contain page and search params
async function search(opts = {}) {
async search(opts: SearchUsersRequest = {}) {
const paged = await API.searchUsers(opts)
set({
this.set({
...paged,
...opts,
})
return paged
}

async function get(userId) {
async get(userId: string) {
try {
return await API.getUser(userId)
} catch (err) {
return null
}
}
const fetch = async () => {

async fetch() {
return await API.getUsers()
}

// One or more users.
async function onboard(payload) {
async onboard(payload: InviteUsersRequest) {
return await API.onboardUsers(payload)
}

async function invite(payload) {
const users = payload.map(user => {
async invite(
payload: {
admin?: boolean
builder?: boolean
creator?: boolean
email: string
apps?: any[]
groups?: any[]
}[]
) {
const users: InviteUsersRequest = payload.map(user => {
let builder = undefined
if (user.admin || user.builder) {
builder = { global: true }
Expand All @@ -55,11 +91,16 @@ export function createUsersStore() {
return API.inviteUsers(users)
}

async function removeInvites(payload) {
async removeInvites(payload: DeleteInviteUsersRequest) {
return API.removeUserInvites(payload)
}

async function acceptInvite(inviteCode, password, firstName, lastName) {
async acceptInvite(
inviteCode: string,
password: string,
firstName: string,
lastName?: string
) {
return API.acceptInvite({
inviteCode,
password,
Expand All @@ -68,21 +109,25 @@ export function createUsersStore() {
})
}

async function fetchInvite(inviteCode) {
async fetchInvite(inviteCode: string) {
return API.getUserInvite(inviteCode)
}

async function getInvites() {
async getInvites() {
return API.getUserInvites()
}

async function updateInvite(invite) {
return API.updateUserInvite(invite.code, invite)
async updateInvite(code: string, invite: UpdateInviteRequest) {
return API.updateUserInvite(code, invite)
}

async function create(data) {
let mappedUsers = data.users.map(user => {
const body = {
async getUserCountByApp(appId: string) {
return await API.getUserCountByApp(appId)
}

async create(data: { users: UserInfo[]; groups: any[] }) {
let mappedUsers: UnsavedUser[] = data.users.map((user: any) => {
const body: UnsavedUser = {
email: user.email,
password: user.password,
roles: {},
Expand All @@ -92,17 +137,17 @@ export function createUsersStore() {
}

switch (user.role) {
case "appUser":
case Constants.BudibaseRoles.AppUser:
body.builder = { global: false }
body.admin = { global: false }
break
case "developer":
case Constants.BudibaseRoles.Developer:
body.builder = { global: true }
break
case "creator":
case Constants.BudibaseRoles.Creator:
body.builder = { creator: true, global: false }
break
case "admin":
case Constants.BudibaseRoles.Admin:
body.admin = { global: true }
body.builder = { global: true }
break
Expand All @@ -113,41 +158,39 @@ export function createUsersStore() {
const response = await API.createUsers(mappedUsers, data.groups)

// re-search from first page
await search()
await this.search()
return response
}

async function del(id) {
async delete(id: string) {
await API.deleteUser(id)
update(users => users.filter(user => user._id !== id))
}

async function getUserCountByApp(appId) {
return await API.getUserCountByApp(appId)
}

async function bulkDelete(users) {
async bulkDelete(users: UserIdentifier[]) {
return API.deleteUsers(users)
}

async function save(user) {
async save(user: User) {
return await API.saveUser(user)
}

async function addAppBuilder(userId, appId) {
async addAppBuilder(userId: string, appId: string) {
return await API.addAppBuilder(userId, appId)
}

async function removeAppBuilder(userId, appId) {
async removeAppBuilder(userId: string, appId: string) {
return await API.removeAppBuilder(userId, appId)
}

async function getAccountHolder() {
async getAccountHolder() {
return await API.getAccountHolder()
}

const getUserRole = user => {
if (user && user.email === user.tenantOwnerEmail) {
getUserRole(user?: User & { tenantOwnerEmail?: string }) {
if (!user) {
return Constants.BudibaseRoles.AppUser
}
if (user.email === user.tenantOwnerEmail) {
return Constants.BudibaseRoles.Owner
} else if (sdk.users.isAdmin(user)) {
return Constants.BudibaseRoles.Admin
Expand All @@ -160,37 +203,15 @@ export function createUsersStore() {
}
}

const refreshUsage =
fn =>
async (...args) => {
// Wrapper function to refresh quota usage after an operation,
// persisting argument and return types
refreshUsage<T extends any[], U>(fn: (...args: T) => Promise<U>) {
return async function (...args: T) {
const response = await fn(...args)
await licensing.setQuotaUsage()
return response
}

return {
subscribe,
search,
get,
getUserRole,
fetch,
invite,
onboard,
fetchInvite,
getInvites,
removeInvites,
updateInvite,
getUserCountByApp,
addAppBuilder,
removeAppBuilder,
// any operation that adds or deletes users
acceptInvite,
create: refreshUsage(create),
save: refreshUsage(save),
bulkDelete: refreshUsage(bulkDelete),
delete: refreshUsage(del),
getAccountHolder,
}
}

export const users = createUsersStore()
export const users = new UserStore()
14 changes: 5 additions & 9 deletions packages/frontend-core/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import {
SaveUserResponse,
SearchUsersRequest,
SearchUsersResponse,
UnsavedUser,
UpdateInviteRequest,
UpdateInviteResponse,
UpdateSelfMetadataRequest,
UpdateSelfMetadataResponse,
User,
UserIdentifier,
} from "@budibase/types"
import { BaseAPIClient } from "./types"

Expand All @@ -38,14 +39,9 @@ export interface UserEndpoints {
createAdminUser: (
user: CreateAdminUserRequest
) => Promise<CreateAdminUserResponse>
saveUser: (user: User) => Promise<SaveUserResponse>
saveUser: (user: UnsavedUser) => Promise<SaveUserResponse>
deleteUser: (userId: string) => Promise<DeleteUserResponse>
deleteUsers: (
users: Array<{
userId: string
email: string
}>
) => Promise<BulkUserDeleted | undefined>
deleteUsers: (users: UserIdentifier[]) => Promise<BulkUserDeleted | undefined>
onboardUsers: (data: InviteUsersRequest) => Promise<InviteUsersResponse>
getUserInvite: (code: string) => Promise<CheckInviteResponse>
getUserInvites: () => Promise<GetUserInvitesResponse>
Expand All @@ -60,7 +56,7 @@ export interface UserEndpoints {
getAccountHolder: () => Promise<LookupAccountHolderResponse>
searchUsers: (data: SearchUsersRequest) => Promise<SearchUsersResponse>
createUsers: (
users: User[],
users: UnsavedUser[],
groups: any[]
) => Promise<BulkUserCreated | undefined>
updateUserInvite: (
Expand Down
6 changes: 4 additions & 2 deletions packages/types/src/api/web/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface UserDetails {
password?: string
}

export type UnsavedUser = Omit<User, "tenantId">

export interface BulkUserRequest {
delete?: {
users: Array<{
Expand All @@ -31,7 +33,7 @@ export interface BulkUserRequest {
}
create?: {
roles?: any[]
users: User[]
users: UnsavedUser[]
groups: any[]
}
}
Expand Down Expand Up @@ -124,7 +126,7 @@ export interface AcceptUserInviteRequest {
inviteCode: string
password: string
firstName: string
lastName: string
lastName?: string
}

export interface AcceptUserInviteResponse {
Expand Down
Loading
Loading