Browse Source

feat(gui): implement user delete method

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4134/head
Pranav C 2 years ago
parent
commit
462edd4e13
  1. 7
      packages/nc-gui/components/org-user/index.vue
  2. 139
      packages/nocodb/src/lib/meta/api/orgUserApis.ts
  3. 6
      packages/nocodb/src/lib/models/ProjectUser.ts
  4. 9
      packages/nocodb/src/lib/models/SyncSource.ts
  5. 9
      packages/nocodb/src/lib/models/User.ts

7
packages/nc-gui/components/org-user/index.vue

@ -8,7 +8,7 @@ const { api, isLoading } = useApi()
let users = $ref<UserType[]>([]) let users = $ref<UserType[]>([])
const currentPage = $ref(1) let currentPage = $ref(1)
const currentLimit = $ref(10) const currentLimit = $ref(10)
@ -21,6 +21,7 @@ const pagination = reactive({
pageSize: 10, pageSize: 10,
}) })
const loadUsers = async (page = currentPage, limit = currentLimit) => { const loadUsers = async (page = currentPage, limit = currentLimit) => {
currentPage = page
try { try {
const response: any = await api.orgUsers.list({ const response: any = await api.orgUsers.list({
query: { query: {
@ -55,6 +56,8 @@ const updateRole = async (userId: string, roles: Role) => {
const deleteUser = async (userId: string) => { const deleteUser = async (userId: string) => {
Modal.confirm({ Modal.confirm({
title: 'Are you sure you want to delete this user?', title: 'Are you sure you want to delete this user?',
type:"warn",
content: 'On deleting, user will remove from from organization and any sync source(Airtable) created by user will get removed',
onOk: async () => { onOk: async () => {
try { try {
await api.orgUsers.delete(userId) await api.orgUsers.delete(userId)
@ -94,7 +97,7 @@ const deleteUser = async (userId: string) => {
:data-source="users" :data-source="users"
:pagination="pagination" :pagination="pagination"
:loading="isLoading" :loading="isLoading"
@change="loadUsers" @change="loadUsers($event.current)"
> >
<template #emptyText> <template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" /> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />

139
packages/nocodb/src/lib/meta/api/orgUserApis.ts

@ -1,47 +1,78 @@
import { Router } from 'express'; import { Router } from 'express'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid'
import validator from 'validator'; import validator from 'validator'
import { OrgUserRoles } from '../../../enums/OrgUserRoles'; import { OrgUserRoles } from '../../../enums/OrgUserRoles'
import Audit from '../../models/Audit'; import Audit from '../../models/Audit'
import User from '../../models/User'; import ProjectUser from '../../models/ProjectUser'
import { metaApiMetrics } from '../helpers/apiMetrics'; import SyncSource from '../../models/SyncSource'
import { NcError } from '../helpers/catchError'; import User from '../../models/User'
import { extractProps } from '../helpers/extractProps'; import Noco from '../../Noco'
import ncMetaAclMw from '../helpers/ncMetaAclMw'; import { metaApiMetrics } from '../helpers/apiMetrics'
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { NcError } from '../helpers/catchError'
import { randomTokenString } from '../helpers/stringHelpers'; import { extractProps } from '../helpers/extractProps'
import { Tele } from 'nc-help'; import ncMetaAclMw from '../helpers/ncMetaAclMw'
import { sendInviteEmail } from './projectUserApis'; import { PagedResponseImpl } from '../helpers/PagedResponse'
import { randomTokenString } from '../helpers/stringHelpers'
import { Tele } from 'nc-help'
import { sendInviteEmail } from './projectUserApis'
async function userList(req, res) { async function userList(req, res) {
res.json( res.json(
new PagedResponseImpl(await User.list(req.query), { new PagedResponseImpl(await User.list(req.query), {
...req.query, ...req.query,
count: await User.count(req.query), count: await User.count(req.query),
}) }),
); )
} }
async function userUpdate(req, res) { async function userUpdate(req, res) {
const updateBody = extractProps(req.body, ['roles']); const updateBody = extractProps(req.body, ['roles'])
const user = await User.get(req.params.userId); const user = await User.get(req.params.userId)
if (user.roles.includes(OrgUserRoles.SUPER)) { if (user.roles.includes(OrgUserRoles.SUPER)) {
NcError.badRequest('Cannot update super admin roles'); NcError.badRequest('Cannot update super admin roles')
} }
res.json(await User.update(req.params.userId, updateBody)); res.json(await User.update(req.params.userId, updateBody))
} }
async function userDelete(req, res) { async function userDelete(req, res) {
const user = await User.get(req.params.userId); const ncMeta = await Noco.ncMeta.startTransaction()
try {
const user = await User.get(req.params.userId, ncMeta)
if (user.roles.includes(OrgUserRoles.SUPER)) { if (user.roles.includes(OrgUserRoles.SUPER)) {
NcError.badRequest('Cannot delete super admin'); NcError.badRequest('Cannot delete super admin')
}
// delete project user entry and assign to super admin
const projectUsers = await ProjectUser.getProjectsList(
req.params.userId,
ncMeta,
)
// TODO: assign super admin as project owner
for (const projectUser of projectUsers) {
await ProjectUser.delete(
projectUser.project_id,
projectUser.fk_user_id,
ncMeta
);
}
// delete sync source entry
await SyncSource.deleteByUserId(req.params.userId, ncMeta)
// delete user
await User.delete(req.params.userId, ncMeta)
await ncMeta.commit()
} catch (e) {
await ncMeta.rollback(e)
} }
res.json(await User.delete(req.params.userId)); res.json(await User.delete(req.params.userId))
} }
async function userAdd(req, res, next) { async function userAdd(req, res, next) {
@ -50,34 +81,34 @@ async function userAdd(req, res, next) {
req.body.roles && req.body.roles &&
![OrgUserRoles.VIEWER, OrgUserRoles.CREATOR].includes(req.body.roles) ![OrgUserRoles.VIEWER, OrgUserRoles.CREATOR].includes(req.body.roles)
) { ) {
NcError.badRequest('Invalid role'); NcError.badRequest('Invalid role')
} }
// extract emails from request body // extract emails from request body
const emails = (req.body.email || '') const emails = (req.body.email || '')
.toLowerCase() .toLowerCase()
.split(/\s*,\s*/) .split(/\s*,\s*/)
.map((v) => v.trim()); .map((v) => v.trim())
// check for invalid emails // check for invalid emails
const invalidEmails = emails.filter((v) => !validator.isEmail(v)); const invalidEmails = emails.filter((v) => !validator.isEmail(v))
if (!emails.length) { if (!emails.length) {
return NcError.badRequest('Invalid email address'); return NcError.badRequest('Invalid email address')
} }
if (invalidEmails.length) { if (invalidEmails.length) {
NcError.badRequest('Invalid email address : ' + invalidEmails.join(', ')); NcError.badRequest('Invalid email address : ' + invalidEmails.join(', '))
} }
const invite_token = uuidv4(); const invite_token = uuidv4()
const error = []; const error = []
for (const email of emails) { for (const email of emails) {
// add user to project if user already exist // add user to project if user already exist
const user = await User.getByEmail(email); const user = await User.getByEmail(email)
if (user) { if (user) {
NcError.badRequest('User already exist'); NcError.badRequest('User already exist')
} else { } else {
try { try {
// create new user with invite token // create new user with invite token
@ -87,10 +118,10 @@ async function userAdd(req, res, next) {
email, email,
roles: OrgUserRoles.VIEWER, roles: OrgUserRoles.VIEWER,
token_version: randomTokenString(), token_version: randomTokenString(),
}); })
const count = await User.count(); const count = await User.count()
Tele.emit('evt', { evt_type: 'org:user:invite', count }); Tele.emit('evt', { evt_type: 'org:user:invite', count })
await Audit.insert({ await Audit.insert({
project_id: req.params.projectId, project_id: req.params.projectId,
@ -99,23 +130,23 @@ async function userAdd(req, res, next) {
user: req.user.email, user: req.user.email,
description: `invited ${email} to ${req.params.projectId} project `, description: `invited ${email} to ${req.params.projectId} project `,
ip: req.clientIp, ip: req.clientIp,
}); })
// in case of single user check for smtp failure // in case of single user check for smtp failure
// and send back token if failed // and send back token if failed
if ( if (
emails.length === 1 && emails.length === 1 &&
!(await sendInviteEmail(email, invite_token, req)) !(await sendInviteEmail(email, invite_token, req))
) { ) {
return res.json({ invite_token, email }); return res.json({ invite_token, email })
} else { } else {
sendInviteEmail(email, invite_token, req); sendInviteEmail(email, invite_token, req)
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e)
if (emails.length === 1) { if (emails.length === 1) {
return next(e); return next(e)
} else { } else {
error.push({ email, error: e.message }); error.push({ email, error: e.message })
} }
} }
} }
@ -124,31 +155,31 @@ async function userAdd(req, res, next) {
if (emails.length === 1) { if (emails.length === 1) {
res.json({ res.json({
msg: 'success', msg: 'success',
}); })
} else { } else {
return res.json({ invite_token, emails, error }); return res.json({ invite_token, emails, error })
} }
} }
const router = Router({ mergeParams: true }); const router = Router({ mergeParams: true })
router.get( router.get(
'/api/v1/users', '/api/v1/users',
metaApiMetrics, metaApiMetrics,
ncMetaAclMw(userList, 'userList', [OrgUserRoles.SUPER]) ncMetaAclMw(userList, 'userList', [OrgUserRoles.SUPER]),
); )
router.patch( router.patch(
'/api/v1/users/:userId', '/api/v1/users/:userId',
metaApiMetrics, metaApiMetrics,
ncMetaAclMw(userUpdate, 'userUpdate', [OrgUserRoles.SUPER]) ncMetaAclMw(userUpdate, 'userUpdate', [OrgUserRoles.SUPER]),
); )
router.delete( router.delete(
'/api/v1/users/:userId', '/api/v1/users/:userId',
metaApiMetrics, metaApiMetrics,
ncMetaAclMw(userDelete, 'userAdd', [OrgUserRoles.SUPER]) ncMetaAclMw(userDelete, 'userAdd', [OrgUserRoles.SUPER]),
); )
router.post( router.post(
'/api/v1/users', '/api/v1/users',
metaApiMetrics, metaApiMetrics,
ncMetaAclMw(userAdd, 'userDelete', [OrgUserRoles.SUPER]) ncMetaAclMw(userAdd, 'userDelete', [OrgUserRoles.SUPER]),
); )
export default router; export default router

6
packages/nocodb/src/lib/models/ProjectUser.ts

@ -183,4 +183,10 @@ export default class ProjectUser {
project_id: projectId, project_id: projectId,
}); });
} }
static async getProjectsList(userId:string, ncMeta = Noco.ncMeta): Promise<ProjectUser[]> {
return await ncMeta.metaList2(null, null, MetaTable.PROJECT_USERS, {
condition: { fk_user_id: userId },
});
}
} }

9
packages/nocodb/src/lib/models/SyncSource.ts

@ -1,3 +1,4 @@
import { NcError } from '../meta/helpers/catchError';
import Noco from '../Noco'; import Noco from '../Noco';
import { MetaTable } from '../utils/globals'; import { MetaTable } from '../utils/globals';
import { extractProps } from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
@ -132,4 +133,12 @@ export default class SyncSource {
syncSourceId syncSourceId
); );
} }
static async deleteByUserId(userId: string, ncMeta = Noco.ncMeta) {
if (!userId) NcError.badRequest('User Id is required');
return await ncMeta.metaDelete(null, null, MetaTable.SYNC_SOURCE, {
fk_user_id: userId,
});
}
} }

9
packages/nocodb/src/lib/models/User.ts

@ -4,19 +4,12 @@ import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import Noco from '../Noco'; import Noco from '../Noco';
import { extractProps } from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import { NcError } from '../meta/helpers/catchError';
import { UserType } from 'nocodb-sdk';
import { NcError } from '../meta/helpers/catchError';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import Noco from '../Noco';
import { extractProps } from '../meta/helpers/extractProps';
import NocoCache from '../cache/NocoCache';
export default class User implements UserType { export default class User implements UserType {
id: string; id: string;
/** @format email */ /** @format email */
email: string email: string;
password?: string; password?: string;
salt?: string; salt?: string;

Loading…
Cancel
Save