1
0
Fork 0

feat: search users by username or tag

lea-dev
Lea 2023-09-21 20:48:10 +02:00 committed by insert
parent 9c531f3d99
commit a3e5db0886
4 changed files with 134 additions and 17 deletions

View File

@ -3,7 +3,7 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast"; import { toast } from "@/components/ui/use-toast";
import { lookupEmail } from "@/lib/actions"; import { lookupEmail, searchUserByTag } from "@/lib/actions";
import { API_URL } from "@/lib/constants"; import { API_URL } from "@/lib/constants";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
@ -11,6 +11,8 @@ import { useState } from "react";
export default function Inspect() { export default function Inspect() {
const [id, setId] = useState(""); const [id, setId] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [username, setUsername] = useState("");
const [discriminator, setDiscriminator] = useState("");
const router = useRouter(); const router = useRouter();
const searchEmail = async () => { const searchEmail = async () => {
@ -31,6 +33,29 @@ export default function Inspect() {
} }
}; };
const searchUsername = async () => {
try {
if (!discriminator) {
// Display all users with this username
router.push(`/panel/inspect/search?username=${encodeURIComponent(username)}`);
} else {
// Show the specific user that matches username#discriminator
const result = await searchUserByTag(username, discriminator);
if (!result) toast({
title: "Couldn't find user",
variant: "destructive",
});
else router.push(`/panel/inspect/user/${result}`);
}
} catch(e) {
toast({
title: "Failed to search",
description: String(e),
variant: "destructive",
})
}
};
const createHandler = (type: string) => () => const createHandler = (type: string) => () =>
router.push(`/panel/inspect/${type}/${id}`); router.push(`/panel/inspect/${type}/${id}`);
@ -41,7 +66,7 @@ export default function Inspect() {
value={id} value={id}
onChange={(e) => setId(e.currentTarget.value)} onChange={(e) => setId(e.currentTarget.value)}
/> />
<div className="flex gap-2"> <div className="flex flex-col md:flex-row gap-2">
<Button <Button
className="flex-1" className="flex-1"
variant="outline" variant="outline"
@ -92,22 +117,50 @@ export default function Inspect() {
</Button> </Button>
</div> </div>
<hr /> <hr />
<div className="flex gap-2 justify-between"> <div className="flex flex-col lg:flex-row gap-2 w-full">
<Input <div className="flex gap-2 justify-between grow">
placeholder="Enter an email..." <Input
value={email} placeholder="Enter an email..."
onChange={(e) => setEmail(e.currentTarget.value)} value={email}
onKeyDown={(e) => e.key == "Enter" && email && searchEmail()} onChange={(e) => setEmail(e.currentTarget.value)}
/> onKeyDown={(e) => e.key == "Enter" && email && searchEmail()}
<Button />
className="flex" <Button
variant="outline" className="flex"
disabled={!email} variant="outline"
onClick={searchEmail} disabled={!email}
> onClick={searchEmail}
Lookup >
</Button> Lookup
</Button>
</div> </div>
<div className="flex gap-2 justify-between grow">
<div className="flex flex-row items-center w-full gap-1">
<Input
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.currentTarget.value)}
onKeyDown={(e) => e.key == "Enter" && username && searchUsername()}
/>
<span className="select-none text-gray-500">#</span>
<Input
placeholder="0000"
value={discriminator}
onChange={(e) => setDiscriminator(e.currentTarget.value)}
onKeyDown={(e) => e.key == "Enter" && username && searchUsername()}
className="flex-shrink-[2]"
/>
</div>
<Button
className="flex"
variant="outline"
disabled={!username}
onClick={searchUsername}
>
Search
</Button>
</div>
</div>
</div> </div>
); );
} }

View File

@ -0,0 +1,34 @@
import { UserCard } from "@/components/cards/UserCard";
import { fetchUsersByUsername } from "@/lib/actions";
import { SearchX } from "lucide-react";
import { redirect } from "next/navigation";
export default async function Search({ searchParams }: { searchParams: any }) {
const username = searchParams.username;
if (!username) return redirect("/panel/inspect");
const users = await fetchUsersByUsername(username);
if (!users.length) return (
<>
<h2 className="mt-8 flex justify-center">
<SearchX className="text-gray-400" />
</h2>
<h3 className="text-xs text-center pb-2 text-gray-400">
No search results
</h3>
</>
);
return (
<div className="flex flex-col gap-2">
{
users.map((user) => (
<a key={user._id} href={`/panel/inspect/user/${user._id}`}>
<UserCard user={user} subtitle={user._id} />
</a>
))
}
</div>
);
}

View File

@ -62,6 +62,8 @@ type Permission =
| `/fetch${ | `/fetch${
| "" | ""
| "/by-id" | "/by-id"
| "/by-tag"
| "/bulk-by-username"
| "/memberships" | "/memberships"
| "/strikes" | "/strikes"
| "/notices" | "/notices"
@ -163,6 +165,8 @@ const PermissionSets = {
// Moderate users // Moderate users
"moderate-users": [ "moderate-users": [
"users/fetch/by-id", "users/fetch/by-id",
"users/fetch/by-tag",
"users/fetch/bulk-by-username",
"users/fetch/strikes", "users/fetch/strikes",
"users/fetch/notices", "users/fetch/notices",

View File

@ -1027,3 +1027,29 @@ export async function deleteEmailClassification(domain: string) {
{ _id: domain }, { _id: domain },
); );
} }
export async function searchUserByTag(username: string, discriminator: string): Promise<string | false> {
await checkPermission("users/fetch/by-tag", { username, discriminator });
const result = await mongo()
.db("revolt")
.collection<User>("users")
.findOne({
username,
discriminator,
});
return result?._id || false;
}
export async function fetchUsersByUsername(username: string) {
await checkPermission("users/fetch/bulk-by-username", { username });
return await mongo()
.db("revolt")
.collection<User>("users")
.find({
username,
})
.toArray();
}