Skip to content

Commit

Permalink
feat: Adds the ability to delete users through the admin panel, provi…
Browse files Browse the repository at this point in the history
…ding more control over user accounts.
  • Loading branch information
n4ze3m committed Sep 4, 2024
1 parent 664b0e7 commit 1c3003e
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 30 deletions.
2 changes: 1 addition & 1 deletion app/ui/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "app",
"private": true,
"version": "1.10.0",
"version": "1.10.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
118 changes: 93 additions & 25 deletions app/ui/src/routes/settings/teams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { SettingsLayout } from "../../Layout/SettingsLayout";
import { SkeletonLoading } from "../../components/Common/SkeletonLoading";
import { useNavigate } from "react-router-dom";
import { KeyIcon } from "@heroicons/react/24/outline";
import { KeyIcon, TrashIcon } from "@heroicons/react/24/outline";
import axios from "axios";

export default function SettingsTeamsRoot() {
Expand All @@ -18,6 +18,8 @@ export default function SettingsTeamsRoot() {
const [resetPasswordUserId, setResetPasswordUserId] = React.useState(0);

const [newUserModal, setNewUserModal] = React.useState(false);
const [deleteUserModal, setDeleteUserModal] = React.useState(false);
const [deleteUserId, setDeleteUserId] = React.useState(0);

const queryClient = useQueryClient();

Expand Down Expand Up @@ -80,6 +82,15 @@ export default function SettingsTeamsRoot() {
return response.data;
};

const onDeleteUser = async (value: { user_id: number }) => {
const response = await api.delete(`/admin/user/delete`, {
data: {
user_id: value.user_id,
},
});
return response.data;
};

const { mutateAsync: createUser, isLoading: createUserLoading } = useMutation(
onCreateUser,
{
Expand Down Expand Up @@ -110,6 +121,30 @@ export default function SettingsTeamsRoot() {
}
);

const { mutateAsync: deleteUser, isLoading: deleteUserLoading } = useMutation(
{
mutationFn: onDeleteUser,
onSuccess: (data) => {
queryClient.invalidateQueries(["fetchAllUsers"]);
setDeleteUserModal(false);
notification.success({
message: "Success",
description: data.message,
});
},
onError: (error) => {
if (axios.isAxiosError(error)) {
const message = error.response?.data?.message;
notification.error({
message: "Error",
description: message,
});
return;
}
},
}
);

return (
<SettingsLayout>
{status === "success" && (
Expand All @@ -133,8 +168,8 @@ export default function SettingsTeamsRoot() {

<dl className="mt-6 space-y-6 divide-y divide-gray-100 sm:overflow-x-none overflow-x-auto text-sm leading-6 ">
<div className="mt-5 md:col-span-2 md:mt-0">
<Table
bordered
<Table
bordered
columns={[
{
title: "Username",
Expand All @@ -161,18 +196,34 @@ export default function SettingsTeamsRoot() {
{
title: "Actions",
render: (_, user) => (
<Tooltip title="Reset Password">
<button
type="button"
onClick={() => {
setResetPasswordUserId(user.user_id);
setResetPasswordModal(true);
}}
className="text-red-400 hover:text-red-500"
>
<KeyIcon className="h-5 w-5" />
</button>
</Tooltip>
<div className="flex space-x-2">
<Tooltip title="Reset Password">
<button
type="button"
onClick={() => {
setResetPasswordUserId(user.user_id);
setResetPasswordModal(true);
}}
className="text-red-400 hover:text-red-500"
>
<KeyIcon className="h-5 w-5" />
</button>
</Tooltip>
{!user.is_admin && (
<Tooltip title="Delete User">
<button
type="button"
onClick={() => {
setDeleteUserId(user.user_id);
setDeleteUserModal(true);
}}
className="text-red-400 hover:text-red-500"
>
<TrashIcon className="h-5 w-5" />
</button>
</Tooltip>
)}
</div>
),
},
]}
Expand Down Expand Up @@ -239,9 +290,7 @@ export default function SettingsTeamsRoot() {
},
]}
>
<Input
size="large"
/>
<Input size="large" />
</Form.Item>
<Form.Item
label="Email"
Expand All @@ -253,10 +302,7 @@ export default function SettingsTeamsRoot() {
},
]}
>
<Input
size="large"
type="email"
/>
<Input size="large" type="email" />
</Form.Item>
<Form.Item
label="Password"
Expand All @@ -268,9 +314,7 @@ export default function SettingsTeamsRoot() {
},
]}
>
<Input.Password
size="large"
/>
<Input.Password size="large" />
</Form.Item>

<div className="flex justify-end">
Expand All @@ -284,6 +328,30 @@ export default function SettingsTeamsRoot() {
</div>
</Form>
</Modal>

<Modal
title="Delete User"
open={deleteUserModal}
onCancel={() => setDeleteUserModal(false)}
footer={null}
>
<p>Are you sure you want to delete this user?</p>
<div className="flex justify-end mt-4">
<button
onClick={() => setDeleteUserModal(false)}
className="mr-2 px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Cancel
</button>
<button
onClick={() => deleteUser({ user_id: deleteUserId })}
disabled={deleteUserLoading}
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
{deleteUserLoading ? "Deleting..." : "Delete"}
</button>
</div>
</Modal>
</>
)}
{status === "loading" && <SkeletonLoading />}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dialoqbase",
"version": "1.10.0",
"version": "1.10.1",
"description": "Create chatbots with ease",
"scripts": {
"ui:dev": "pnpm run --filter ui dev",
Expand Down
151 changes: 151 additions & 0 deletions server/src/handlers/api/v1/admin/delete.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { FastifyReply, FastifyRequest } from "fastify";
import { DeleteUserRequest } from "./type";
import TelegramBot from "../../../../integration/telegram";

export const adminDeleteUserHandler = async (
request: FastifyRequest<DeleteUserRequest>,
reply: FastifyReply
) => {
try {
const prisma = request.server.prisma;
const user = request.user;

if (!user || !user.user_id) {
return reply.status(401).send({
message: "Unauthorized",
});
}

if (user.user_id === request.body.user_id) {
return reply.status(400).send({
message: "You cannot delete yourself",
});
}

const userToDelete = await prisma.user.findUnique({
where: {
user_id: request.body.user_id,
},
select: {
user_id: true,
isAdministrator: true,
},
});

if (!userToDelete) {
return reply.status(404).send({
message: "User not found",
});
}

if (userToDelete.isAdministrator) {
return reply.status(403).send({
message: "You cannot delete an admin",
});
}

await prisma.$transaction(async (tx) => {
const bots = await tx.bot.findMany({
where: {
user_id: request.body.user_id,
},
select: {
id: true,
},
});

const botIds = bots.map((bot) => bot.id);

if (botIds.length > 0) {
const botIntegrations = await tx.botIntegration.findMany({
where: {
bot_id: {
in: botIds,
},
},
});

for (const botIntegration of botIntegrations) {
if (botIntegration.provider === "telegram") {
await TelegramBot.disconnect(botIntegration.identifier);
}
}

await tx.botIntegration.deleteMany({
where: {
bot_id: {
in: botIds,
},
},
});

await tx.botDocument.deleteMany({
where: {
botId: {
in: botIds,
},
},
});

await tx.botSource.deleteMany({
where: {
botId: {
in: botIds,
},
},
});

const botPlaygrounds = await tx.botPlayground.findMany({
where: {
botId: {
in: botIds,
},
},
select: {
id: true,
},
});

const playgroundIds = botPlaygrounds.map((bp) => bp.id);

await tx.botPlaygroundMessage.deleteMany({
where: {
botPlaygroundId: {
in: playgroundIds,
},
},
});

await tx.botPlayground.deleteMany({
where: {
botId: {
in: botIds,
},
},
});

await tx.bot.deleteMany({
where: {
id: {
in: botIds,
},
},
});
}
await tx.user.delete({
where: {
user_id: request.body.user_id,
},
});
});

return reply.status(200).send({
message: "User deleted successfully",
});
} catch (error) {
console.error("Error in adminDeleteUserHandler:", error);
return reply.status(500).send({
message: "Internal server error",
});
}
};
3 changes: 2 additions & 1 deletion server/src/handlers/api/v1/admin/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./get.handler";
export * from "./post.handler";
export * from "./model.handler";
export * from "./model.handler";
export * from "./delete.handler";
6 changes: 6 additions & 0 deletions server/src/handlers/api/v1/admin/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ export type UpdateDialoqbaseRAGSettingsRequest = {
defaultChunkOverlap: number;
};
};

export type DeleteUserRequest = {
Body: {
user_id: number;
};
};
Loading

0 comments on commit 1c3003e

Please sign in to comment.