添加编辑应用和删除应用功能

This commit is contained in:
wood chen 2025-02-15 00:31:31 +08:00
parent bf1797c5d4
commit b025ab22be
6 changed files with 379 additions and 6 deletions

View File

@ -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 (
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div className="mb-6">
<h2 className="text-base text-muted-foreground"></h2>
</div>
<EditClientForm client={client} />
</div>
);
}

View File

@ -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() {
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Link href={`/dashboard/clients/${client.id}`}>
<Button variant="outline" size="sm">
</Button>
<Button variant="destructive" size="sm">
</Button>
</Link>
<DeleteClientButton
clientId={client.id}
clientName={client.name}
/>
</div>
</TableCell>
</TableRow>

View File

@ -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 });
}
}

View File

@ -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 (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm" disabled={isLoading}>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle></AlertDialogTitle>
<AlertDialogDescription>
"{clientName}"
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isLoading}></AlertDialogCancel>
<AlertDialogAction
onClick={onDelete}
disabled={isLoading}
className="bg-red-600 hover:bg-red-700"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}

View File

@ -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<HTMLFormElement>) {
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 (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={onSubmit} className="space-y-4">
<div className="grid gap-2">
<Label htmlFor="name"></Label>
<Input
id="name"
name="name"
defaultValue={client.name}
disabled={isLoading}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="home"></Label>
<Input
id="home"
name="home"
defaultValue={client.home}
disabled={isLoading}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="logo"></Label>
<Input
id="logo"
name="logo"
defaultValue={client.logo}
disabled={isLoading}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="redirectUri"></Label>
<Input
id="redirectUri"
name="redirectUri"
defaultValue={client.redirectUri}
disabled={isLoading}
/>
</div>
<div className="grid gap-2">
<Label htmlFor="description"></Label>
<Input
id="description"
name="description"
defaultValue={client.description || ""}
disabled={isLoading}
/>
</div>
<div className="flex justify-end space-x-4">
<Button
type="button"
variant="outline"
onClick={() => router.push("/dashboard/clients")}
disabled={isLoading}
>
</Button>
<Button type="submit" disabled={isLoading}>
</Button>
</div>
</form>
</CardContent>
</Card>
);
}

View File

@ -25,3 +25,11 @@ export async function getClientsByUserId(userId: string) {
},
});
}
export async function getClientById(id: string) {
return await prisma.client.findUnique({
where: {
id,
},
});
}