diff --git a/app/ui/package.json b/app/ui/package.json index 086e8463..ad271f26 100644 --- a/app/ui/package.json +++ b/app/ui/package.json @@ -1,7 +1,7 @@ { "name": "app", "private": true, - "version": "1.10.0", + "version": "1.10.1", "type": "module", "scripts": { "dev": "vite", diff --git a/app/ui/src/routes/settings/teams.tsx b/app/ui/src/routes/settings/teams.tsx index a03a2a39..1d345944 100644 --- a/app/ui/src/routes/settings/teams.tsx +++ b/app/ui/src/routes/settings/teams.tsx @@ -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() { @@ -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(); @@ -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, { @@ -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 ( {status === "success" && ( @@ -133,8 +168,8 @@ export default function SettingsTeamsRoot() {
- ( - - - +
+ + + + {!user.is_admin && ( + + + + )} +
), }, ]} @@ -239,9 +290,7 @@ export default function SettingsTeamsRoot() { }, ]} > - + - + - +
@@ -284,6 +328,30 @@ export default function SettingsTeamsRoot() {
+ + setDeleteUserModal(false)} + footer={null} + > +

Are you sure you want to delete this user?

+
+ + +
+
)} {status === "loading" && } diff --git a/package.json b/package.json index 9fd1a416..97a7c29e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/src/handlers/api/v1/admin/delete.handler.ts b/server/src/handlers/api/v1/admin/delete.handler.ts new file mode 100644 index 00000000..b4b3a9ea --- /dev/null +++ b/server/src/handlers/api/v1/admin/delete.handler.ts @@ -0,0 +1,151 @@ +import { FastifyReply, FastifyRequest } from "fastify"; +import { DeleteUserRequest } from "./type"; +import TelegramBot from "../../../../integration/telegram"; + +export const adminDeleteUserHandler = async ( + request: FastifyRequest, + 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", + }); + } +}; diff --git a/server/src/handlers/api/v1/admin/index.ts b/server/src/handlers/api/v1/admin/index.ts index d0d32e01..9c148a7d 100644 --- a/server/src/handlers/api/v1/admin/index.ts +++ b/server/src/handlers/api/v1/admin/index.ts @@ -1,3 +1,4 @@ export * from "./get.handler"; export * from "./post.handler"; -export * from "./model.handler"; \ No newline at end of file +export * from "./model.handler"; +export * from "./delete.handler"; \ No newline at end of file diff --git a/server/src/handlers/api/v1/admin/type.ts b/server/src/handlers/api/v1/admin/type.ts index a7514a27..ca6a9617 100644 --- a/server/src/handlers/api/v1/admin/type.ts +++ b/server/src/handlers/api/v1/admin/type.ts @@ -75,3 +75,9 @@ export type UpdateDialoqbaseRAGSettingsRequest = { defaultChunkOverlap: number; }; }; + +export type DeleteUserRequest = { + Body: { + user_id: number; + }; +}; \ No newline at end of file diff --git a/server/src/routes/api/v1/admin/root.ts b/server/src/routes/api/v1/admin/root.ts index c72967db..8dbcfbd9 100644 --- a/server/src/routes/api/v1/admin/root.ts +++ b/server/src/routes/api/v1/admin/root.ts @@ -11,7 +11,8 @@ import { deleteModelHandler, hideModelHandler, saveEmbedddingModelFromInputedUrlHandler, - updateDialoqbaseRAGSettingsHandler + updateDialoqbaseRAGSettingsHandler, + adminDeleteUserHandler } from "../../../../handlers/api/v1/admin"; import { dialoqbaseSettingsSchema, @@ -19,7 +20,8 @@ import { getAllUsersSchema, registerUserByAdminSchema, resetUserPasswordByAdminSchema, - updateDialoqbaseRAGSettings + updateDialoqbaseRAGSettings, + deleteUserSchema } from "../../../../schema/api/v1/admin"; import { @@ -140,6 +142,16 @@ const root: FastifyPluginAsync = async (fastify, _): Promise => { }, saveEmbedddingModelFromInputedUrlHandler ); + + // api for admin to delete user + fastify.delete( + "/user/delete", + { + schema: deleteUserSchema, + onRequest: [fastify.authenticateAdmin], + }, + adminDeleteUserHandler + ); }; export default root; diff --git a/server/src/schema/api/v1/admin/index.ts b/server/src/schema/api/v1/admin/index.ts index 8149cb61..e09cd4cc 100644 --- a/server/src/schema/api/v1/admin/index.ts +++ b/server/src/schema/api/v1/admin/index.ts @@ -180,3 +180,31 @@ export const updateDialoqbaseRAGSettings: FastifySchema = { }, }, }; + + +export const deleteUserSchema: FastifySchema = { + tags: ["Admin"], + summary: "API to delete user", + headers: { + type: "object", + properties: { + Authorization: { type: "string" }, + }, + required: ["Authorization"], + }, + body: { + type: "object", + properties: { + user_id: { type: "number" }, + }, + required: ["user_id"], + }, + response: { + 200: { + type: "object", + properties: { + message: { type: "string" }, + }, + }, + }, +}; \ No newline at end of file