This commit is contained in:
Mike Cao 2023-03-10 18:45:51 -08:00
commit 9a3e8921a7
7 changed files with 150 additions and 21 deletions

View File

@ -1,10 +1,14 @@
import { Loading } from 'react-basics';
import useApi from 'hooks/useApi';
import { messages } from 'components/messages';
import TeamMembersTable from 'components/pages/settings/teams/TeamMembersTable';
import useApi from 'hooks/useApi';
import { Loading, useToast } from 'react-basics';
import { useIntl } from 'react-intl';
export default function TeamMembers({ teamId, readOnly }) {
const { toast, showToast } = useToast();
const { get, useQuery } = useApi();
const { data, isLoading } = useQuery(['teams:users', teamId], () =>
const { formatMessage } = useIntl();
const { data, isLoading, refetch } = useQuery(['teams:users', teamId], () =>
get(`/teams/${teamId}/users`),
);
@ -12,5 +16,15 @@ export default function TeamMembers({ teamId, readOnly }) {
return <Loading icon="dots" position="block" />;
}
return <TeamMembersTable data={data} readOnly={readOnly} />;
const handleSave = async () => {
await refetch();
showToast({ message: formatMessage(messages.saved), variant: 'success' });
};
return (
<>
{toast}
<TeamMembersTable onSave={handleSave} data={data} readOnly={readOnly} />
</>
);
}

View File

@ -15,10 +15,13 @@ import { useIntl } from 'react-intl';
import { ROLES } from 'lib/constants';
import { labels } from 'components/messages';
import useUser from 'hooks/useUser';
import useApi from 'hooks/useApi';
export default function TeamMembersTable({ data = [], readOnly }) {
export default function TeamMembersTable({ data = [], onSave, readOnly }) {
const { formatMessage } = useIntl();
const { user } = useUser();
const { del, useMutation } = useApi();
const { mutate } = useMutation(data => del(`/teamUsers/${data.teamUserId}`));
const columns = [
{ name: 'username', label: formatMessage(labels.username), style: { flex: 2 } },
@ -26,6 +29,17 @@ export default function TeamMembersTable({ data = [], readOnly }) {
{ name: 'action', label: '', style: { flex: 1 } },
];
const handleRemoveTeamMember = teamUserId => {
mutate(
{ teamUserId },
{
onSuccess: async () => {
onSave();
},
},
);
};
return (
<Table columns={columns} rows={data}>
<TableHeader>
@ -46,7 +60,10 @@ export default function TeamMembersTable({ data = [], readOnly }) {
),
action: !readOnly && (
<Flexbox flex={1} justifyContent="end">
<Button disabled={user.id === row?.user?.id || row.role === ROLES.teamOwner}>
<Button
onClick={() => handleRemoveTeamMember(row.id)}
disabled={user.id === row?.user?.id || row.role === ROLES.teamOwner}
>
<Icon>
<Icons.Close />
</Icon>

View File

@ -3,7 +3,7 @@ import cache from 'lib/cache';
import { PERMISSIONS, ROLE_PERMISSIONS, SHARE_TOKEN_HEADER } from 'lib/constants';
import { secret } from 'lib/crypto';
import { ensureArray, parseSecureToken, parseToken } from 'next-basics';
import { getTeamUser } from 'queries';
import { getTeamUser, getTeamUserById } from 'queries';
import { getTeamWebsite, getTeamWebsiteByTeamMemberId } from 'queries/admin/teamWebsite';
import { validate } from 'uuid';
import { Auth } from './types';
@ -167,6 +167,22 @@ export async function canDeleteTeam({ user }: Auth, teamId: string) {
return false;
}
export async function canDeleteTeamUser({ user }: Auth, teamUserId: string) {
if (user.isAdmin) {
return true;
}
if (validate(teamUserId)) {
const removeUser = await getTeamUserById(teamUserId);
const teamUser = await getTeamUser(removeUser.teamId, user.id);
return hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
}
return false;
}
export async function canDeleteTeamWebsite({ user }: Auth, teamWebsiteId: string) {
if (user.isAdmin) {
return true;

View File

@ -0,0 +1,28 @@
import { canDeleteTeamUser } from 'lib/auth';
import { useAuth } from 'lib/middleware';
import { NextApiRequestQueryBody } from 'lib/types';
import { NextApiResponse } from 'next';
import { methodNotAllowed, ok, unauthorized } from 'next-basics';
import { deleteTeamUser } from 'queries/admin/teamUser';
export interface TeamUserRequestQuery {
id: string;
}
export default async (req: NextApiRequestQueryBody<TeamUserRequestQuery>, res: NextApiResponse) => {
await useAuth(req, res);
const { id: teamUserId } = req.query;
if (req.method === 'DELETE') {
if (!(await canDeleteTeamUser(req.auth, teamUserId))) {
return unauthorized(res);
}
const websites = await deleteTeamUser(teamUserId);
return ok(res, websites);
}
return methodNotAllowed(res);
};

View File

@ -52,17 +52,17 @@ export async function updateTeam(
export async function deleteTeam(
teamId: string,
): Promise<Promise<[Prisma.BatchPayload, Prisma.BatchPayload, Team]>> {
const { client } = prisma;
const { client, transaction } = prisma;
return prisma.transaction([
return transaction([
client.teamWebsite.deleteMany({
where: {
id: teamId,
teamId,
},
}),
client.teamUser.deleteMany({
where: {
id: teamId,
teamId,
},
}),
client.team.delete({

View File

@ -2,6 +2,14 @@ import { Prisma, TeamUser } from '@prisma/client';
import { uuid } from 'lib/crypto';
import prisma from 'lib/prisma';
export async function getTeamUserById(teamUserId: string): Promise<TeamUser> {
return prisma.client.teamUser.findUnique({
where: {
id: teamUserId,
},
});
}
export async function getTeamUser(teamId: string, userId: string): Promise<TeamUser> {
return prisma.client.teamUser.findFirst({
where: {
@ -48,11 +56,25 @@ export async function updateTeamUser(
}
export async function deleteTeamUser(teamUserId: string): Promise<TeamUser> {
return prisma.client.teamUser.delete({
const { client, transaction } = prisma;
const teamUser = await getTeamUserById(teamUserId);
return transaction([
client.teamWebsite.deleteMany({
where: {
teamId: teamUser.teamId,
website: {
userId: teamUser.userId,
},
},
}),
client.teamUser.deleteMany({
where: {
id: teamUserId,
},
});
}),
]);
}
export async function deleteTeamUserByUserId(

View File

@ -1,5 +1,6 @@
import { Prisma, Team } from '@prisma/client';
import cache from 'lib/cache';
import { ROLES } from 'lib/constants';
import prisma from 'lib/prisma';
import { Website, User, Roles } from 'lib/types';
@ -134,6 +135,19 @@ export async function deleteUser(
websiteIds = websites.map(a => a.id);
}
const teams = await client.team.findMany({
where: {
teamUser: {
some: {
userId,
role: ROLES.teamOwner,
},
},
},
});
const teamIds = teams.map(a => a.id);
return prisma
.transaction([
client.websiteEvent.deleteMany({
@ -144,21 +158,39 @@ export async function deleteUser(
}),
client.teamWebsite.deleteMany({
where: {
website: {
userId,
OR: [
{
websiteId: {
in: websiteIds,
},
},
{
teamId: {
in: teamIds,
},
},
],
},
}),
client.teamWebsite.deleteMany({
where: {
teamId: {
in: teamIds,
},
},
}),
client.teamUser.deleteMany({
where: {
team: {
userId,
teamId: {
in: teamIds,
},
},
}),
client.team.deleteMany({
where: {
userId,
id: {
in: teamIds,
},
},
}),
cloudMode