From 7efc423f30dcb9d8f48a6a800ace03489a88b0ca Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Tue, 30 Apr 2024 00:30:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20token=20add=20and=20dele?= =?UTF-8?q?te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/components/form/server-form.tsx | 8 +- .../hub/app/server/components/group/index.tsx | 2 +- .../app/server/components/table/columns.tsx | 9 +- .../app/server/components/token-dialog.tsx | 63 ++++++++------ apps/hub/app/server/store/token-dialog.ts | 2 +- apps/hub/components/copy-button.tsx | 29 +++++++ apps/hub/server/api/root.ts | 2 + apps/hub/server/api/routers/server.ts | 40 +++------ apps/hub/server/api/routers/serverToken.ts | 82 +++++++++++++++++++ apps/hub/server/api/trpc.ts | 6 +- 10 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 apps/hub/components/copy-button.tsx create mode 100644 apps/hub/server/api/routers/serverToken.ts diff --git a/apps/hub/app/server/components/form/server-form.tsx b/apps/hub/app/server/components/form/server-form.tsx index c859a7e..ce3015b 100644 --- a/apps/hub/app/server/components/form/server-form.tsx +++ b/apps/hub/app/server/components/form/server-form.tsx @@ -53,7 +53,7 @@ const NoGroup = 'no-group' export function ServerForm({ mode, id, server, onSubmit }: ServerFormProps) { const router = useRouter() const { data: groups } = api.group.list.useQuery() - const { mutateAsync } = api.server.create.useMutation() + const { mutateAsync: createServer } = api.server.create.useMutation() const { mutateAsync: updateServer } = api.server.update.useMutation() const setIsOpen = useBoundStore.use.setIsOpenServerForm() const setTokenDialogProps = useBoundStore.use.setTokenDialogProps() @@ -84,11 +84,11 @@ export function ServerForm({ mode, id, server, onSubmit }: ServerFormProps) { } if (mode === FormMode.Create) { - const token = await mutateAsync(params) + const token = await createServer(params) setTokenDialogProps({ title: 'Server created!', description: 'Copy the token for communication with the node', - tokens: [token], + serverId: token.serverId, }) setIsOpen(false) setIsOpenTokenDialog(true) @@ -198,7 +198,7 @@ export function ServerForm({ mode, id, server, onSubmit }: ServerFormProps) { {group.name} - + {group.description} diff --git a/apps/hub/app/server/components/group/index.tsx b/apps/hub/app/server/components/group/index.tsx index 641490e..c381d2f 100644 --- a/apps/hub/app/server/components/group/index.tsx +++ b/apps/hub/app/server/components/group/index.tsx @@ -16,7 +16,7 @@ import { AccordionTrigger, } from '@/components/ui/accordion' import { Badge } from '@/components/ui/badge' -import { Button, buttonVariants } from '@/components/ui/button' +import { buttonVariants } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, diff --git a/apps/hub/app/server/components/table/columns.tsx b/apps/hub/app/server/components/table/columns.tsx index db44430..7ce7f60 100644 --- a/apps/hub/app/server/components/table/columns.tsx +++ b/apps/hub/app/server/components/table/columns.tsx @@ -42,18 +42,15 @@ const Actions = ({ row }: { row: Row }) => { const setConfirmDialogProps = useBoundStore.use.setConfirmDialogProps() const setIsOpenServerForm = useBoundStore.use.setIsOpenServerForm() const setServerFormProps = useBoundStore.use.setServerFormProps() - const tokens = api.server.getTokens.useQuery({ - id: row.original.id, - }) const { mutateAsync: deleteServer } = api.server.delete.useMutation() const server = row.original return ( - @@ -72,7 +69,7 @@ const Actions = ({ row }: { row: Row }) => { onClick={() => { setTokenDialogProps({ title: 'Token list', - tokens: tokens.data ?? [], + serverId: server.id, }) setIsOpen(true) }} diff --git a/apps/hub/app/server/components/token-dialog.tsx b/apps/hub/app/server/components/token-dialog.tsx index bdee668..bd2073b 100644 --- a/apps/hub/app/server/components/token-dialog.tsx +++ b/apps/hub/app/server/components/token-dialog.tsx @@ -1,8 +1,8 @@ 'use client' -import { useState } from 'react' import { useBoundStore } from '@/store' -import { CheckIcon, Copy } from 'lucide-react' +import { api } from '@/trpc/react' +import { X } from 'lucide-react' import { Button } from '@/components/ui/button' import { @@ -16,18 +16,36 @@ import { } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' +import CopyButton from '@/components/copy-button' +import { STooltip } from '@/components/s-tooltip' export type TokenDialogProps = { title: string description?: string - tokens: string[] + serverId: string } export function TokenDialog() { const isOpen = useBoundStore.use.isOpenTokenDialog() const setIsOpen = useBoundStore.use.setIsOpenTokenDialog() const tokenDialogProps = useBoundStore.use.tokenDialogProps() - const [copySuccess, setCopySuccess] = useState(false) + + const { data: tokens, refetch } = api.serverToken.list.useQuery({ + id: tokenDialogProps.serverId, + }) + + const { mutateAsync: generateToken } = api.serverToken.create.useMutation() + const { mutateAsync: deleteToken } = api.serverToken.delete.useMutation() + + const handleGenerateToken = async () => { + await generateToken({ serverId: tokenDialogProps.serverId }) + await refetch() + } + + const handleDeleteToken = async (token: string) => { + await deleteToken({ token }) + await refetch() + } return ( @@ -39,7 +57,7 @@ export function TokenDialog() { 'Copy the token to node and use it to connect to your server.'} - {tokenDialogProps.tokens.map((token, index) => ( + {tokens?.map(({ token }, index) => (
- + + + +
))} - + +
diff --git a/apps/hub/app/server/store/token-dialog.ts b/apps/hub/app/server/store/token-dialog.ts index 7b5d382..6498d43 100644 --- a/apps/hub/app/server/store/token-dialog.ts +++ b/apps/hub/app/server/store/token-dialog.ts @@ -22,7 +22,7 @@ export const createTokenDialogSlice: StateCreator< > = (set) => ({ tokenDialogProps: { title: '', - tokens: [], + serverId: '', }, isOpenTokenDialog: false, setTokenDialogProps: (tokenDialogProps) => diff --git a/apps/hub/components/copy-button.tsx b/apps/hub/components/copy-button.tsx new file mode 100644 index 0000000..a49116d --- /dev/null +++ b/apps/hub/components/copy-button.tsx @@ -0,0 +1,29 @@ +import { useState } from 'react' +import { CheckIcon, Copy } from 'lucide-react' + +import { Button } from '@/components/ui/button' + +export default function CopyButton({ content }: { content: string }) { + const [copySuccess, setCopySuccess] = useState(false) + + return ( + + ) +} diff --git a/apps/hub/server/api/root.ts b/apps/hub/server/api/root.ts index 9e9107f..5f31ea8 100644 --- a/apps/hub/server/api/root.ts +++ b/apps/hub/server/api/root.ts @@ -1,6 +1,7 @@ import { dashboardRouter } from '@/server/api/routers/dashboard' import { groupRouter } from '@/server/api/routers/group' import { serverRouter } from '@/server/api/routers/server' +import { serverTokenRouter } from '@/server/api/routers/serverToken' import { userRouter } from '@/server/api/routers/user' import { createTRPCRouter } from '@/server/api/trpc' @@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({ dashboard: dashboardRouter, group: groupRouter, user: userRouter, + serverToken: serverTokenRouter, }) // export type definition of API diff --git a/apps/hub/server/api/routers/server.ts b/apps/hub/server/api/routers/server.ts index 82dc956..39d2cb0 100644 --- a/apps/hub/server/api/routers/server.ts +++ b/apps/hub/server/api/routers/server.ts @@ -1,12 +1,11 @@ -import { env } from '@/env' import { NotLoggedInError } from '@/server/api/error' import { createTRPCRouter, + getCaller, protectedProcedure, publicProcedure, } from '@/server/api/trpc' import { type Prisma } from '@serverbee/db' -import { sign } from 'jsonwebtoken' import { z } from 'zod' import { getLogger } from '@/lib/logging' @@ -70,20 +69,17 @@ export const serverRouter = createTRPCRouter({ data: createData, }) - const payload = { - userId: ctx.session.user.id, - serverId: server.id, - } + const caller = await getCaller() - const token = sign(payload, env.SERVER_JWT_SECRET) - - const serverToken = await ctx.db.serverToken.create({ - data: { - token, - server: { connect: { id: server.id } }, - }, + const token: { + id: number + token: string + isExpires: boolean + serverId: string + } = await caller.serverToken.create({ + serverId: server.id, }) - return serverToken.token + return token }), update: protectedProcedure .input( @@ -97,7 +93,6 @@ export const serverRouter = createTRPCRouter({ ) .mutation(async ({ ctx, input }) => { if (!ctx.session.user) throw NotLoggedInError - try { await ctx.db.server.update({ where: { @@ -122,21 +117,6 @@ export const serverRouter = createTRPCRouter({ return false } }), - getTokens: protectedProcedure - .input( - z.object({ - id: z.string(), - }) - ) - .query(async ({ input, ctx }) => { - if (!ctx.session.user) throw NotLoggedInError - const queryResult = await ctx.db.serverToken.findMany({ - where: { - serverId: input.id, - }, - }) - return queryResult.map((item) => item.token) - }), getOwnServerIds: protectedProcedure.query(async ({ ctx }) => { if (!ctx.session.user) throw NotLoggedInError const result = await ctx.db.server.findMany({ diff --git a/apps/hub/server/api/routers/serverToken.ts b/apps/hub/server/api/routers/serverToken.ts new file mode 100644 index 0000000..839ab8c --- /dev/null +++ b/apps/hub/server/api/routers/serverToken.ts @@ -0,0 +1,82 @@ +import { env } from '@/env' +import { NotLoggedInError } from '@/server/api/error' +import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc' +import { sign } from 'jsonwebtoken' +import { z } from 'zod' + +import { getLogger } from '@/lib/logging' + +const logger = getLogger('server-token.ts') + +export const serverTokenRouter = createTRPCRouter({ + list: protectedProcedure + .input( + z.object({ + id: z.string(), + }) + ) + .query(async ({ input, ctx }) => { + if (!ctx.session.user) throw NotLoggedInError + const queryResult = await ctx.db.serverToken.findMany({ + where: { + serverId: input.id, + }, + }) + return queryResult.map((item) => ({ + ...item, + })) + }), + create: protectedProcedure + .input( + z.object({ + serverId: z.string(), + }) + ) + .mutation(async ({ ctx, input }) => { + const payload = { + serverId: input.serverId, + } + + const token = sign(payload, env.SERVER_JWT_SECRET) + + const serverToken = await ctx.db.serverToken.create({ + data: { + token, + server: { connect: { id: input.serverId } }, + }, + }) + return { + id: serverToken.id, + token: serverToken.token, + isExpires: false, + serverId: serverToken.serverId, + } + }), + delete: protectedProcedure + .input( + z.object({ + token: z.string(), + }) + ) + .mutation(async ({ input, ctx }) => { + const token = await ctx.db.serverToken.findFirst({ + where: { + token: input.token, + }, + }) + if (token) { + await ctx.db.serverToken.delete({ + where: { + id: token.id, + }, + }) + + await ctx.mongo + .db('serverbee') + .collection('invalid') + .insertOne({ token: input.token }) + + logger.info(`Token ${input.token} deleted`) + } + }), +}) diff --git a/apps/hub/server/api/trpc.ts b/apps/hub/server/api/trpc.ts index 6a3ac5a..314a1e9 100644 --- a/apps/hub/server/api/trpc.ts +++ b/apps/hub/server/api/trpc.ts @@ -9,7 +9,7 @@ import { appRouter } from '@/server/api/root' import { getServerAuthSession } from '@/server/auth' -import { db } from '@/server/db' +import { db, mongo } from '@/server/db' import { initTRPC, TRPCError } from '@trpc/server' import superjson from 'superjson' import { ZodError } from 'zod' @@ -31,6 +31,7 @@ export const createTRPCContext = async (opts?: { headers: Headers }) => { return { db, + mongo, session, ...opts, } @@ -110,7 +111,8 @@ export async function getCaller() { const createCaller = createCallerFactory(appRouter) const session = await getServerAuthSession() return createCaller({ - session: session, db, + mongo, + session: session, }) }