From 44be9bf0c9fe921762ad25ebec6a639f0dbbfc46 Mon Sep 17 00:00:00 2001 From: Lea Date: Thu, 10 Aug 2023 21:57:05 +0200 Subject: [PATCH] feat: server invite list --- .../inspect/server/[id]/invites/page.tsx | 23 +++ components/cards/InviteCard.tsx | 51 +++++++ components/pages/inspector/InviteList.tsx | 143 ++++++++++++++++++ components/pages/inspector/ServerActions.tsx | 13 +- lib/accessPermissions.ts | 3 +- lib/db.ts | 28 ++++ 6 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 app/panel/inspect/server/[id]/invites/page.tsx create mode 100644 components/cards/InviteCard.tsx create mode 100644 components/pages/inspector/InviteList.tsx diff --git a/app/panel/inspect/server/[id]/invites/page.tsx b/app/panel/inspect/server/[id]/invites/page.tsx new file mode 100644 index 0000000..67724b2 --- /dev/null +++ b/app/panel/inspect/server/[id]/invites/page.tsx @@ -0,0 +1,23 @@ +import { ServerCard } from "@/components/cards/ServerCard"; +import { NavigationToolbar } from "@/components/common/NavigationToolbar"; +import ServerInviteList from "@/components/pages/inspector/InviteList"; +import { fetchInvites, fetchServerById } from "@/lib/db"; +import { notFound } from "next/navigation"; + +export default async function ServerInvites({ params }: { params: { id: string } }) { + const server = await fetchServerById(params.id); + if (!server) return notFound(); + + const { invites, channels, users } = await fetchInvites({ + type: "Server", + server: params.id, + }); + + return ( +
+ Inspecting Server Invites + + +
+ ) +} diff --git a/components/cards/InviteCard.tsx b/components/cards/InviteCard.tsx new file mode 100644 index 0000000..d4b38ac --- /dev/null +++ b/components/cards/InviteCard.tsx @@ -0,0 +1,51 @@ +"use client" + +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"; +import { ChannelInvite } from "@/lib/db"; +import Link from "next/link"; +import { Channel, User } from "revolt-api"; + +export function InviteCard({ + invite, + channel, + user, +}: { + invite: ChannelInvite; + channel?: Channel; + user?: User; +}) { + return ( + + + + rvlt.gg/{invite._id} + {" "} {/* looks better like this when for some reason the css doesnt load */} + {invite.vanity + ? + Vanity + + : <>} + + + {invite.type} + {" • "} + + {( + channel && + channel.channel_type != "DirectMessage" && + channel.channel_type != "SavedMessages" + ) + ? `#${channel.name}` + : Unknown Channel} + + {" • "} + + {user ? `${user.username}#${user.discriminator}` : Unknown Creator} + + + + + ); +} diff --git a/components/pages/inspector/InviteList.tsx b/components/pages/inspector/InviteList.tsx new file mode 100644 index 0000000..80d57de --- /dev/null +++ b/components/pages/inspector/InviteList.tsx @@ -0,0 +1,143 @@ +"use client" + +import { InviteCard } from "@/components/cards/InviteCard"; +import { Button } from "@/components/ui/button"; +import { Command, CommandItem } from "@/components/ui/command"; +import { Input } from "@/components/ui/input"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; +import { ChannelInvite } from "@/lib/db"; +import { ChevronsUpDown } from "lucide-react"; +import { useMemo, useState } from "react"; +import { Channel, Server, User } from "revolt-api"; + +export default function ServerInviteList({ server, invites, channels, users }: { + server: Server, + invites: ChannelInvite[], + channels?: Channel[], + users?: User[], +}) { + const [selectVanityOnly, setSelectVanityOnly] = useState(false); + const [selectChannel, setSelectChannel] = useState(false); + const [selectUser, setSelectUser] = useState(false); + const [vanityOnly, setVanityOnly] = useState(false); + const [channelFilter, setChannelFilter] = useState(""); + const [userFilter, setUserFilter] = useState(""); + + const filteredInvites = useMemo(() => { + return invites + ?.filter(invite => vanityOnly ? invite.vanity : true) + ?.filter(invite => channelFilter ? invite.channel == channelFilter : true) + ?.filter(invite => userFilter ? invite.creator == userFilter : true); + }, [vanityOnly, channelFilter, userFilter, invites]); + + return ( +
+
+ + + + + + + {[ + { value: false, label: "All" }, + { value: true, label: "Vanity" }, + ].map((option) => ( + { + setSelectVanityOnly(false); + setVanityOnly(option.value); + }} + >{option.label} + ))} + + + + + + + + + + + { + setSelectChannel(false); + setChannelFilter(""); + }} + >All channels + {channels?.map((channel) => ( + { + setSelectChannel(false); + setChannelFilter(channel._id); + }} + >{'#' + (channel as any).name} + ))} + + + + + + + + + + + { + setSelectUser(false); + setUserFilter(""); + }} + >All users + {users?.map((user) => ( + { + setSelectUser(false); + setUserFilter(user._id); + }} + >{user.username}#{user.discriminator} + ))} + + + +
+ + {filteredInvites.map(invite => ( c._id == invite.channel)} + user={users?.find(c => c._id == invite.creator)} + key={invite._id} + />))} +
+ ) +} diff --git a/components/pages/inspector/ServerActions.tsx b/components/pages/inspector/ServerActions.tsx index 36e299d..6d9b6c3 100644 --- a/components/pages/inspector/ServerActions.tsx +++ b/components/pages/inspector/ServerActions.tsx @@ -1,12 +1,9 @@ "use client"; import { Server } from "revolt-api"; -import { Button } from "../../ui/button"; +import { Button, buttonVariants } from "../../ui/button"; import { Command, - CommandEmpty, - CommandGroup, - CommandInput, CommandItem, } from "@/components/ui/command"; import { @@ -19,6 +16,7 @@ import { cn } from "@/lib/utils"; import { useState } from "react"; import { updateServerDiscoverability, updateServerFlags } from "@/lib/actions"; import { useToast } from "../../ui/use-toast"; +import Link from "next/link"; export function ServerActions({ server }: { server: Server }) { const [selectBadges, setSelectBadges] = useState(false); @@ -130,6 +128,13 @@ export function ServerActions({ server }: { server: Server }) { + + Invites + + diff --git a/lib/accessPermissions.ts b/lib/accessPermissions.ts index 3790b3e..be8e432 100644 --- a/lib/accessPermissions.ts +++ b/lib/accessPermissions.ts @@ -16,7 +16,7 @@ type Permission = | "" | `/fetch${"" | "/by-id" | "/by-user"}` | `/update${"" | "/discoverability"}`}` - | `channels${"" | `/fetch${"" | "/by-id" | "/dm"}` | `/create${"" | "/dm"}`}` + | `channels${"" | `/fetch${"" | "/by-id" | "/by-server" | "/dm" | "/invites"}` | `/create${"" | "/dm"}`}` | `messages${"" | `/fetch${"" | "/by-id" | "/by-user"}`}` | `reports${ | "" @@ -125,6 +125,7 @@ const PermissionSets = { "messages/fetch/by-id", "channels/fetch/by-id", "channels/fetch/dm", + "channels/fetch/invites", "channels/create/dm", "reports/fetch/related/by-user", diff --git a/lib/db.ts b/lib/db.ts index 14d810c..de6652a 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -5,6 +5,7 @@ import type { AccountStrike, Bot, Channel, + Invite, Message, Report, Server, @@ -286,6 +287,33 @@ export async function fetchServers(query: Filter) { .toArray(); } +// `vanity` should eventually be added to the backend as well +export type ChannelInvite = Invite & { vanity?: boolean } + +export async function fetchInvites(query: Filter) { + await checkPermission("channels/fetch/invites", query); + + const invites = await mongo() + .db("revolt") + .collection("channel_invites") + .find(query) + .toArray(); + + const channels = await mongo() + .db("revolt") + .collection("channels") + .find({ _id: { $in: invites.map((invite) => invite.channel) } }) + .toArray(); + + const users = await mongo() + .db("revolt") + .collection("users") + .find({ _id: { $in: invites.map((invite) => invite.creator) } }) + .toArray(); + + return { invites, channels, users } +} + export async function fetchMessageById(id: string) { await checkPermission("messages/fetch/by-id", id);