diff --git a/src/app/(dashboard)/dashboard/clients/[id]/page.tsx b/src/app/(dashboard)/dashboard/clients/[id]/page.tsx new file mode 100644 index 0000000..a100cda --- /dev/null +++ b/src/app/(dashboard)/dashboard/clients/[id]/page.tsx @@ -0,0 +1,38 @@ +import { notFound, redirect } from "next/navigation"; + +import { getClientById } from "@/lib/dto/client"; +import { getCurrentUser } from "@/lib/session"; +import { EditClientForm } from "@/components/clients/edit-client"; + +interface EditClientPageProps { + params: { + id: string; + }; +} + +export default async function EditClientPage({ params }: EditClientPageProps) { + const user = await getCurrentUser(); + if (!user) { + redirect("/sign-in"); + } + + const client = await getClientById(params.id); + if (!client) { + notFound(); + } + + // 检查是否是应用的所有者 + if (client.userId !== user.id) { + redirect("/dashboard/clients"); + } + + return ( +
+
+

编辑应用

+
+ + +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/clients/page.tsx b/src/app/(dashboard)/dashboard/clients/page.tsx index c703aee..2567ffa 100644 --- a/src/app/(dashboard)/dashboard/clients/page.tsx +++ b/src/app/(dashboard)/dashboard/clients/page.tsx @@ -1,3 +1,4 @@ +import Link from "next/link"; import { redirect } from "next/navigation"; import { getClientsByUserId } from "@/lib/dto/client"; @@ -12,6 +13,7 @@ import { TableRow, } from "@/components/ui/table"; import { AddClientButton } from "@/components/clients/add-client"; +import { DeleteClientButton } from "@/components/clients/delete-client"; // 创建 Prisma 客户端实例 async function fetchClients(userId: string) { @@ -62,12 +64,15 @@ export default async function ClientsPage() {
- - + + + +
diff --git a/src/app/api/clients/[id]/route.ts b/src/app/api/clients/[id]/route.ts new file mode 100644 index 0000000..4b94409 --- /dev/null +++ b/src/app/api/clients/[id]/route.ts @@ -0,0 +1,105 @@ +import { NextRequest } from "next/server"; + +import { prisma } from "@/lib/prisma"; +import { getCurrentUser } from "@/lib/session"; + +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } }, +) { + try { + const user = await getCurrentUser(); + if (!user) { + return new Response("Unauthorized", { status: 401 }); + } + + const client = await prisma.client.findUnique({ + where: { id: params.id }, + }); + + if (!client) { + return new Response("Not Found", { status: 404 }); + } + + if (client.userId !== user.id) { + return new Response("Forbidden", { status: 403 }); + } + + const formData = await request.formData(); + const name = formData.get("name") as string; + const home = formData.get("home") as string; + const logo = formData.get("logo") as string; + const redirectUri = formData.get("redirectUri") as string; + const description = formData.get("description") as string; + + // 验证必填字段 + if (!name || !home || !logo || !redirectUri) { + return new Response("Missing required fields", { status: 400 }); + } + + const updatedClient = await prisma.client.update({ + where: { id: params.id }, + data: { + name, + home, + logo, + redirectUri, + description, + }, + }); + + return Response.json(updatedClient); + } catch (error) { + console.error("Error updating client:", error); + return new Response("Internal Server Error", { status: 500 }); + } +} + +export async function DELETE( + _request: NextRequest, + { params }: { params: { id: string } }, +) { + try { + const user = await getCurrentUser(); + if (!user) { + return new Response("Unauthorized", { status: 401 }); + } + + const client = await prisma.client.findUnique({ + where: { id: params.id }, + }); + + if (!client) { + return new Response("Not Found", { status: 404 }); + } + + if (client.userId !== user.id) { + return new Response("Forbidden", { status: 403 }); + } + + // 删除相关的授权记录 + await prisma.authorization.deleteMany({ + where: { clientId: params.id }, + }); + + // 删除相关的访问令牌 + await prisma.accessToken.deleteMany({ + where: { clientId: params.id }, + }); + + // 删除相关的授权码 + await prisma.code.deleteMany({ + where: { clientId: params.id }, + }); + + // 删除客户端 + await prisma.client.delete({ + where: { id: params.id }, + }); + + return new Response(null, { status: 204 }); + } catch (error) { + console.error("Error deleting client:", error); + return new Response("Internal Server Error", { status: 500 }); + } +} diff --git a/src/components/clients/delete-client.tsx b/src/components/clients/delete-client.tsx new file mode 100644 index 0000000..8f16f39 --- /dev/null +++ b/src/components/clients/delete-client.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +import { useToast } from "@/hooks/use-toast"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; + +interface DeleteClientButtonProps { + clientId: string; + clientName: string; +} + +export function DeleteClientButton({ + clientId, + clientName, +}: DeleteClientButtonProps) { + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + const router = useRouter(); + + async function onDelete() { + setIsLoading(true); + try { + const response = await fetch(`/api/clients/${clientId}`, { + method: "DELETE", + }); + + if (!response.ok) { + throw new Error("删除失败"); + } + + router.refresh(); + toast({ + title: "删除成功", + description: "应用已成功删除", + }); + } catch (error) { + toast({ + variant: "destructive", + title: "删除失败", + description: error instanceof Error ? error.message : "未知错误", + }); + } finally { + setIsLoading(false); + } + } + + return ( + + + + + + + 确认删除应用? + + 您确定要删除应用"{clientName}"吗?此操作不可撤销,删除后将无法恢复。 + + + + 取消 + + 删除 + + + + + ); +} diff --git a/src/components/clients/edit-client.tsx b/src/components/clients/edit-client.tsx new file mode 100644 index 0000000..3e581c6 --- /dev/null +++ b/src/components/clients/edit-client.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Client } from "@prisma/client"; + +import { useToast } from "@/hooks/use-toast"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +interface EditClientFormProps { + client: Client; +} + +export function EditClientForm({ client }: EditClientFormProps) { + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + const router = useRouter(); + + async function onSubmit(event: React.FormEvent) { + event.preventDefault(); + setIsLoading(true); + + try { + const formData = new FormData(event.currentTarget); + const response = await fetch(`/api/clients/${client.id}`, { + method: "PUT", + body: formData, + }); + + if (!response.ok) { + throw new Error("更新失败"); + } + + router.refresh(); + toast({ + title: "更新成功", + description: "应用信息已更新", + }); + router.push("/dashboard/clients"); + } catch (error) { + toast({ + variant: "destructive", + title: "更新失败", + description: error instanceof Error ? error.message : "未知错误", + }); + } finally { + setIsLoading(false); + } + } + + return ( + + + 编辑应用 + 修改应用的基本信息 + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ ); +} diff --git a/src/lib/dto/client.ts b/src/lib/dto/client.ts index 25f0bcf..1900a2b 100644 --- a/src/lib/dto/client.ts +++ b/src/lib/dto/client.ts @@ -25,3 +25,11 @@ export async function getClientsByUserId(userId: string) { }, }); } + +export async function getClientById(id: string) { + return await prisma.client.findUnique({ + where: { + id, + }, + }); +}