feat: Support finding and promoting roles by username

This commit is contained in:
beilunyang
2025-01-15 19:34:35 +08:00
parent 075a34239b
commit 086ad28f6a
4 changed files with 77 additions and 51 deletions
+39 -33
View File
@@ -1,43 +1,49 @@
import { createDb } from "@/lib/db" import { createDb } from "@/lib/db"
import { userRoles, users } from "@/lib/schema" import { users } from "@/lib/schema"
import { eq } from "drizzle-orm" import { eq } from "drizzle-orm"
export const runtime = "edge" export const runtime = "edge"
export async function GET(request: Request) { export async function POST(request: Request) {
const url = new URL(request.url) try {
const email = url.searchParams.get('email') const json = await request.json()
const { searchText } = json as { searchText: string }
if (!email) { if (!searchText) {
return Response.json({ error: "请提供用户名或邮箱地址" }, { status: 400 })
}
const db = createDb()
const user = await db.query.users.findFirst({
where: searchText.includes('@') ? eq(users.email, searchText) : eq(users.username, searchText),
with: {
userRoles: {
with: {
role: true
}
}
}
});
if (!user) {
return Response.json({ error: "未找到用户" }, { status: 404 })
}
return Response.json({
user: {
id: user.id,
name: user.name,
username: user.username,
email: user.email,
role: user.userRoles[0]?.role.name
}
})
} catch (error) {
console.error("Failed to find user:", error)
return Response.json( return Response.json(
{ error: "邮箱地址不能为空" }, { error: "查询用户失败" },
{ status: 400 } { status: 500 }
) )
} }
const db = createDb()
const user = await db.query.users.findFirst({
where: eq(users.email, email),
})
if (!user) {
return Response.json({ user: null })
}
const userRole = await db.query.userRoles.findFirst({
where: eq(userRoles.userId, user.id),
with: {
role: true
}
})
return Response.json({
user: {
id: user.id,
name: user.name,
email: user.email,
role: userRole?.role.name
}
})
} }
+3 -1
View File
@@ -58,7 +58,9 @@ export function ProfileCard({ user }: ProfileCardProps) {
} }
</div> </div>
<p className="text-sm text-muted-foreground truncate mt-1"> <p className="text-sm text-muted-foreground truncate mt-1">
{user.email} {
user.email ? user.email : `用户名: ${user.username}`
}
</p> </p>
{user.roles && ( {user.roles && (
<div className="flex gap-2 mt-2"> <div className="flex gap-2 mt-2">
+26 -12
View File
@@ -8,20 +8,34 @@ import { useToast } from "@/components/ui/use-toast"
import { ROLES } from "@/lib/permissions" import { ROLES } from "@/lib/permissions"
export function PromotePanel() { export function PromotePanel() {
const [email, setEmail] = useState("") const [searchText, setSearchText] = useState("")
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [action, setAction] = useState<"promote" | "demote">("promote") const [action, setAction] = useState<"promote" | "demote">("promote")
const { toast } = useToast() const { toast } = useToast()
const handleAction = async () => { const handleAction = async () => {
if (!email) return if (!searchText) return
setLoading(true) setLoading(true)
try { try {
const res = await fetch(`/api/roles/users?email=${encodeURIComponent(email)}`) const res = await fetch("/api/roles/users", {
const data = await res.json() as { user?: { id: string; name?: string; email: string; role?: string }; error?: string } method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ searchText })
})
const data = await res.json() as {
user?: {
id: string
name?: string
username?: string
email: string
role?: string
}
error?: string
}
if (!res.ok) throw new Error(data.error || "未知错误")
if (!res.ok) throw new Error(data.error || '未知错误')
if (!data.user) { if (!data.user) {
toast({ toast({
title: "未找到用户", title: "未找到用户",
@@ -61,9 +75,9 @@ export function PromotePanel() {
toast({ toast({
title: action === "promote" ? "册封成功" : "贬为平民", title: action === "promote" ? "册封成功" : "贬为平民",
description: `已将 ${data.user.email} ${action === "promote" ? "册封为骑士" : "贬为平民"}` description: `已将 ${data.user.email || data.user.username} ${action === "promote" ? "册封为骑士" : "贬为平民"}`
}) })
setEmail("") setSearchText("")
} catch (error) { } catch (error) {
toast({ toast({
@@ -86,9 +100,9 @@ export function PromotePanel() {
<div className="flex flex-col sm:flex-row gap-2"> <div className="flex flex-col sm:flex-row gap-2">
<div className="flex-1"> <div className="flex-1">
<Input <Input
placeholder="输入用户邮箱" placeholder="输入用户名或邮箱"
value={email} value={searchText}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
disabled={loading} disabled={loading}
/> />
</div> </div>
@@ -98,7 +112,7 @@ export function PromotePanel() {
setAction("promote") setAction("promote")
handleAction() handleAction()
}} }}
disabled={!email || loading} disabled={!searchText || loading}
className="flex-1 sm:flex-initial gap-2" className="flex-1 sm:flex-initial gap-2"
> >
{loading && action === "promote" ? ( {loading && action === "promote" ? (
@@ -113,7 +127,7 @@ export function PromotePanel() {
setAction("demote") setAction("demote")
handleAction() handleAction()
}} }}
disabled={!email || loading} disabled={!searchText || loading}
variant="destructive" variant="destructive"
className="flex-1 sm:flex-initial gap-2" className="flex-1 sm:flex-initial gap-2"
> >
+9 -5
View File
@@ -85,10 +85,6 @@ export const roles = sqliteTable("role", {
updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()), updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn(() => new Date()),
}); });
export const rolesRelations = relations(roles, ({ many }) => ({
userRoles: many(userRoles),
}));
export const userRoles = sqliteTable("user_role", { export const userRoles = sqliteTable("user_role", {
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
roleId: text("role_id").notNull().references(() => roles.id, { onDelete: "cascade" }), roleId: text("role_id").notNull().references(() => roles.id, { onDelete: "cascade" }),
@@ -106,4 +102,12 @@ export const userRolesRelations = relations(userRoles, ({ one }) => ({
fields: [userRoles.roleId], fields: [userRoles.roleId],
references: [roles.id], references: [roles.id],
}), }),
})); }));
export const usersRelations = relations(users, ({ many }) => ({
userRoles: many(userRoles),
}));
export const rolesRelations = relations(roles, ({ many }) => ({
userRoles: many(userRoles),
}));