mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 23:56:39 +08:00
增加清理用户功能
This commit is contained in:
parent
f2d6a6dfbe
commit
95fc45486b
@ -1,23 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
|
|
||||||
async function handle(
|
|
||||||
req: NextRequest,
|
|
||||||
{ params }: { params: { path: string[] } },
|
|
||||||
) {
|
|
||||||
// 判断网址和请求方法
|
|
||||||
// const method = req.method;
|
|
||||||
// // const url = req.url;
|
|
||||||
// const { pathname } = new URL(req.url);
|
|
||||||
//
|
|
||||||
// console.log('123', method, pathname,)
|
|
||||||
// const result = await prisma.user.findMany({
|
|
||||||
// orderBy: {
|
|
||||||
// createdAt: "desc",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
return NextResponse.json({ error: "暂未开发" }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET = handle;
|
|
||||||
export const POST = handle;
|
|
@ -1,16 +1,24 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { getSessionName } from "@/lib/auth";
|
||||||
|
import { ADMIN_LIST } from "@/lib/auth_list";
|
||||||
|
|
||||||
async function handle(
|
async function handle(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
{ params }: { params: { path: string[] } },
|
{ params }: { params: { path: string[] } },
|
||||||
) {
|
) {
|
||||||
|
// 认证,管理员权限
|
||||||
|
const { name } = await getSessionName();
|
||||||
|
if (!(name && ADMIN_LIST.includes(name))) {
|
||||||
|
return NextResponse.json({ error: "无权限" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
// 判断网址和请求方法
|
// 判断网址和请求方法
|
||||||
const method = req.method;
|
const method = req.method;
|
||||||
// const url = req.url;
|
// const url = req.url;
|
||||||
const { pathname, searchParams } = new URL(req.url);
|
const { pathname, searchParams } = new URL(req.url);
|
||||||
const searchText = searchParams.get("search");
|
const searchText = searchParams.get("search");
|
||||||
// console.log(req)
|
// console.log(req, '2', params.path)
|
||||||
|
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
// 是否有查询
|
// 是否有查询
|
||||||
@ -51,8 +59,27 @@ async function handle(
|
|||||||
return NextResponse.json({ count: count, results: result });
|
return NextResponse.json({ count: count, results: result });
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({ error: "当前方法不支持" }, { status: 400 });
|
if (method === "DELETE") {
|
||||||
|
if (!params.path) {
|
||||||
|
return NextResponse.json({ error: "未输入用户ID" }, { status: 400 });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const userId = params.path[0];
|
||||||
|
const user = await prisma.user.delete({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// console.log('user', user)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("[delete user]", e);
|
||||||
|
return NextResponse.json({ error: "无法删除用户" }, { status: 400 });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ result: "删除用户成功" });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ error: "当前方法不支持" }, { status: 405 });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GET = handle;
|
export const GET = handle;
|
||||||
export const POST = handle;
|
export const POST = handle;
|
||||||
|
export const DELETE = handle;
|
@ -8,12 +8,14 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
import { Space, Table, Tag, Input, Button } from "antd";
|
import { Space, Table, Tag, Input, Button, notification } from "antd";
|
||||||
import { SearchOutlined } from "@ant-design/icons";
|
import type { GetRef, TableColumnsType } from "antd";
|
||||||
import type { FilterDropdownProps } from "antd/es/table/interface";
|
|
||||||
import type { GetRef, TableColumnsType, TableColumnType } from "antd";
|
import type { NotificationArgsProps } from "antd";
|
||||||
|
|
||||||
import Highlighter from "react-highlight-words";
|
import Highlighter from "react-highlight-words";
|
||||||
// 后期考虑删除该依赖
|
// 后期考虑删除该依赖
|
||||||
|
type NotificationPlacement = NotificationArgsProps["placement"];
|
||||||
|
|
||||||
import type { SearchProps } from "antd/es/input/Search";
|
import type { SearchProps } from "antd/es/input/Search";
|
||||||
|
|
||||||
@ -77,122 +79,44 @@ function UserTableSearchInput({ users, setUsers, setLoading }: UserInterface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UsersTable({ users, setUsers, loading }: UserInterface) {
|
function UsersTable({ users, setUsers, loading }: UserInterface) {
|
||||||
// const [searchText, setSearchText] = useState("");
|
const [api, contextHolder] = notification.useNotification();
|
||||||
// const [searchedColumn, setSearchedColumn] = useState("");
|
|
||||||
// const searchInput = useRef<InputRef>(null);
|
|
||||||
// const handleSearch = (
|
|
||||||
// selectedKeys: string[],
|
|
||||||
// confirm: FilterDropdownProps["confirm"],
|
|
||||||
// dataIndex: DataIndex,
|
|
||||||
// ) => {
|
|
||||||
// confirm();
|
|
||||||
// setSearchText(selectedKeys[0]);
|
|
||||||
// setSearchedColumn(dataIndex);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleReset = (clearFilters: () => void) => {
|
|
||||||
// clearFilters();
|
|
||||||
// setSearchText("");
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getColumnSearchProps = (
|
|
||||||
// dataIndex: DataIndex,
|
|
||||||
// ): TableColumnType<User> => ({
|
|
||||||
// filterDropdown: ({
|
|
||||||
// setSelectedKeys,
|
|
||||||
// selectedKeys,
|
|
||||||
// confirm,
|
|
||||||
// clearFilters,
|
|
||||||
// close,
|
|
||||||
// }) => (
|
|
||||||
// <div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
|
|
||||||
// <Input
|
|
||||||
// ref={searchInput}
|
|
||||||
// placeholder={`Search ${dataIndex}`}
|
|
||||||
// value={selectedKeys[0]}
|
|
||||||
// onChange={(e) =>
|
|
||||||
// setSelectedKeys(e.target.value ? [e.target.value] : [])
|
|
||||||
// }
|
|
||||||
// onPressEnter={() =>
|
|
||||||
// handleSearch(selectedKeys as string[], confirm, dataIndex)
|
|
||||||
// }
|
|
||||||
// style={{ marginBottom: 8, display: "block" }}
|
|
||||||
// />
|
|
||||||
// <Space>
|
|
||||||
// <Button
|
|
||||||
// type="primary"
|
|
||||||
// onClick={() =>
|
|
||||||
// handleSearch(selectedKeys as string[], confirm, dataIndex)
|
|
||||||
// }
|
|
||||||
// icon={<SearchOutlined />}
|
|
||||||
// size="small"
|
|
||||||
// style={{ width: 90 }}
|
|
||||||
// >
|
|
||||||
// Search
|
|
||||||
// </Button>
|
|
||||||
// <Button
|
|
||||||
// onClick={() => clearFilters && handleReset(clearFilters)}
|
|
||||||
// size="small"
|
|
||||||
// style={{ width: 90 }}
|
|
||||||
// >
|
|
||||||
// Reset
|
|
||||||
// </Button>
|
|
||||||
// <Button
|
|
||||||
// type="link"
|
|
||||||
// size="small"
|
|
||||||
// onClick={() => {
|
|
||||||
// confirm({ closeDropdown: false });
|
|
||||||
// setSearchText((selectedKeys as string[])[0]);
|
|
||||||
// setSearchedColumn(dataIndex);
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// Filter
|
|
||||||
// </Button>
|
|
||||||
// <Button
|
|
||||||
// type="link"
|
|
||||||
// size="small"
|
|
||||||
// onClick={() => {
|
|
||||||
// close();
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// close
|
|
||||||
// </Button>
|
|
||||||
// </Space>
|
|
||||||
// </div>
|
|
||||||
// ),
|
|
||||||
// filterIcon: (filtered: boolean) => (
|
|
||||||
// <SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
|
|
||||||
// ),
|
|
||||||
// onFilter: (value, record: User) => {
|
|
||||||
// let result = record?.[dataIndex];
|
|
||||||
// if (result) {
|
|
||||||
// return result
|
|
||||||
// .toString()
|
|
||||||
// .toLowerCase()
|
|
||||||
// .includes((value as string).toLowerCase());
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// },
|
|
||||||
// onFilterDropdownOpenChange: (visible) => {
|
|
||||||
// if (visible) {
|
|
||||||
// // @ts-ignore
|
|
||||||
// setTimeout(() => searchInput.current?.select(), 100);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// render: (text) =>
|
|
||||||
// searchedColumn === dataIndex ? (
|
|
||||||
// // @ts-ignore
|
|
||||||
// <Highlighter
|
|
||||||
// highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
|
|
||||||
// searchWords={[searchText]}
|
|
||||||
// autoEscape
|
|
||||||
// textToHighlight={text ? text.toString() : ""}
|
|
||||||
// />
|
|
||||||
// ) : (
|
|
||||||
// text
|
|
||||||
// ),
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
const openNotification = (level: string, arms: NotificationArgsProps) => {
|
||||||
|
if (level === "error") {
|
||||||
|
api.error({
|
||||||
|
...arms,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
api.info({
|
||||||
|
...arms,
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleDeleteUser = (record: User) => {
|
||||||
|
fetch(`/api/admin/users/${record.id}`, { method: "delete" })
|
||||||
|
.then((response) => {
|
||||||
|
console.log("delete, ", record);
|
||||||
|
if (response.ok) {
|
||||||
|
openNotification("info", {
|
||||||
|
message: "删除用户",
|
||||||
|
description: `${record.email || record.name} 删除成功`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openNotification("error", {
|
||||||
|
message: "删除用户",
|
||||||
|
description: `${record.email || record.name} 删除失败`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((reason) => {
|
||||||
|
openNotification("error", {
|
||||||
|
message: "删除用户",
|
||||||
|
description: `${record.email || record.name} 删除失败\n${reason}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
const columns: TableColumnsType<User> = [
|
const columns: TableColumnsType<User> = [
|
||||||
{ title: "Name", dataIndex: "name" },
|
{ title: "Name", dataIndex: "name" },
|
||||||
{
|
{
|
||||||
@ -218,15 +142,16 @@ function UsersTable({ users, setUsers, loading }: UserInterface) {
|
|||||||
title: "Action",
|
title: "Action",
|
||||||
dataIndex: "",
|
dataIndex: "",
|
||||||
key: "id",
|
key: "id",
|
||||||
render: () => (
|
render: (_, record) => (
|
||||||
<Space size="middle">
|
<Space size="middle">
|
||||||
|
{contextHolder}
|
||||||
<a>编辑</a>
|
<a>编辑</a>
|
||||||
<a>删除</a>
|
<a onClick={() => handleDeleteUser(record)}>删除</a>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
console.log(users, "users2");
|
// console.log(users, "users2");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
@ -234,6 +159,10 @@ function UsersTable({ users, setUsers, loading }: UserInterface) {
|
|||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
loading={loading as boolean}
|
loading={loading as boolean}
|
||||||
|
scroll={{
|
||||||
|
scrollToFirstRowOnChange: true,
|
||||||
|
y: 1080,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
callbacks: {
|
callbacks: {
|
||||||
jwt: async ({ token, user }) => {
|
jwt: async ({ token, user }) => {
|
||||||
// const current_time = Math.floor(Date.now() / 1000);
|
// const current_time = Math.floor(Date.now() / 1000);
|
||||||
console.log('=============', token, user,)
|
// console.log('=============', token, user,)
|
||||||
if (user) {
|
if (user) {
|
||||||
token.user = user;
|
token.user = user;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
username: token?.user?.username || token?.user?.gh_username,
|
username: token?.user?.username || token?.user?.gh_username,
|
||||||
};
|
};
|
||||||
console.log('555555555,', session, token)
|
// console.log('555555555,', session, token)
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -74,7 +74,7 @@ model LogEntry {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
// logEntry String? @db.Text
|
// logEntry String? @db.Text
|
||||||
logToken Int? @default(0)
|
logToken Int? @default(0)
|
||||||
user User? @relation(fields: [userID], references: [id], onDelete: NoAction)
|
user User? @relation(fields: [userID], references: [id], onDelete: SetNull)
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
|
Loading…
Reference in New Issue
Block a user