feat(v2): i forgot

This commit is contained in:
dvelo 2025-03-02 18:28:20 -06:00
parent e4dc5f86de
commit 8541994c4d
43 changed files with 1368 additions and 640 deletions

@ -26,10 +26,11 @@
"@radix-ui/react-collapsible": "1.1.1", "@radix-ui/react-collapsible": "1.1.1",
"@radix-ui/react-context-menu": "^2.2.6", "@radix-ui/react-context-menu": "^2.2.6",
"@radix-ui/react-hover-card": "1.1.1", "@radix-ui/react-hover-card": "1.1.1",
"@radix-ui/react-icons": "1.3.0", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-menubar": "1.1.1", "@radix-ui/react-menubar": "1.1.1",
"@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-select": "2.1.2", "@radix-ui/react-select": "2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "1.1.0", "@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
@ -38,6 +39,7 @@
"@unocss/postcss": "^0.61.5", "@unocss/postcss": "^0.61.5",
"@unocss/transformer-directives": "^0.61.5", "@unocss/transformer-directives": "^0.61.5",
"@unocss/webpack": "^0.61.5", "@unocss/webpack": "^0.61.5",
"@vercel/functions": "^2.0.0",
"ag-grid-react": "^33.0.3", "ag-grid-react": "^33.0.3",
"contentlayer": "^0.3.4", "contentlayer": "^0.3.4",
"cron": "^3.1.7", "cron": "^3.1.7",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -42,6 +42,7 @@ import { ThemeProvider } from "@/components/util/theme-provider";
import { FontBoundary } from "@/components/util/font-boundary"; import { FontBoundary } from "@/components/util/font-boundary";
import { ClerkProvider } from "@/components/util/clerk-provider"; import { ClerkProvider } from "@/components/util/clerk-provider";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import { Footer } from "@/components/feat/footer/footer";
export default function RootLayout({ export default function RootLayout({
children, children,
@ -79,7 +80,8 @@ export default function RootLayout({
<Toaster richColors position="top-center" /> <Toaster richColors position="top-center" />
<ClerkProvider> <ClerkProvider>
<NavBar /> <NavBar />
<div className="pt-16">{children}</div> <div className="pt-16 min-h-screen">{children}</div>
<Footer />
</ClerkProvider> </ClerkProvider>
</TooltipProvider> </TooltipProvider>
</FontBoundary> </FontBoundary>

@ -0,0 +1,25 @@
import { ServerResponse } from "@/lib/types/mh-server";
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ server: string }>;
}): Promise<Metadata> {
const id = (await params).server;
const { server }: { server: ServerResponse } = await (
await fetch("https://api.minehut.com/server/" + id)
).json();
return {
applicationName: "MHSF",
title: `${server.name} | MHSF`,
openGraph: {
title: server.name,
description: "A server on Minehut, find it on MHSF!",
},
description: "A server on Minehut, find it on MHSF!",
};
}
export default function ServerPage() {}

@ -0,0 +1,15 @@
import { Support } from "@/components/feat/support/support";
import type { Metadata } from "next";
export const metadata: Metadata = {
applicationName: "MHSF",
title: "Support · MHSF",
};
export default function SupportPage() {
return (
<div>
<Support />
</div>
);
}

@ -56,7 +56,7 @@
--color-destructive-foreground: hsl(var(--destructive-foreground)); --color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted)); --color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground)); --color-muted-foreground: hsl(0 0% 45.1%);
--color-accent: hsl(var(--accent)); --color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground)); --color-accent-foreground: hsl(var(--accent-foreground));
@ -9286,3 +9286,14 @@ body {
.icon-minecraft-sm.icon-minecraft-mob-zombified-piglin-face { .icon-minecraft-sm.icon-minecraft-mob-zombified-piglin-face {
background-position: -144px -736px; background-position: -144px -736px;
} }
/*
---break---
*/
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

@ -0,0 +1,69 @@
import { ComponentPropsWithoutRef, ReactNode } from "react";
import { cn } from "@/lib/utils";
interface BentoGridProps extends ComponentPropsWithoutRef<"div"> {
children: ReactNode;
className?: string;
}
interface BentoCardProps extends ComponentPropsWithoutRef<"div"> {
name: string;
className: string;
background: ReactNode;
Icon: React.ElementType;
description: string;
href: string;
cta: string;
}
const BentoGrid = ({ children, className, ...props }: BentoGridProps) => {
return (
<div
className={cn(
"grid w-full auto-rows-[22rem] grid-cols-3 gap-4",
className
)}
{...props}
>
{children}
</div>
);
};
const BentoCard = ({
name,
className,
background,
Icon,
description,
href,
cta,
...props
}: BentoCardProps) => (
<div
key={name}
className={cn(
"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl",
// light styles
"bg-background [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]",
// dark styles
"transform-gpu dark:bg-background dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]",
className
)}
{...props}
>
<div>{background}</div>
<div className="pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10">
<Icon className="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75" />
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
{name}
</h3>
<p className="max-w-lg text-neutral-400">{description}</p>
</div>
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
</div>
);
export { BentoCard, BentoGrid };

@ -0,0 +1,61 @@
import { BrandingGenericIcon } from "../icons/branding-icons";
import { Link } from "../../util/link";
import { FooterStatus } from "./status";
export function Footer() {
return (
<footer className="w-full border-t p-[20px] mt-15">
<div className="flex justify-between items-center">
<span className="flex items-center gap-4 text-muted-foreground">
<Link href="Special:Root">
<BrandingGenericIcon className="max-w-[32px] max-h-[32px]" />
</Link>
<ul className="grid grid-cols-2 md:flex gap-4 p-0 m-0 w-full md:items-center items-start list-none">
<li className="text-sm">
<Link
href="/home"
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
>
Home
</Link>
</li>
<li className="text-sm">
<Link
href="/servers"
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
>
Servers
</Link>
</li>
<li className="text-sm">
<Link
href="/support"
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
>
Contact
</Link>
</li>
</ul>
</span>
<FooterStatus />
</div>
<span className="block mt-4">
<small className="text-muted-foreground text-[0.75rem]">
MHSF is an open-source project licensed under the MIT license. MHSF is
not officially affiliated with with Minehut, Super League Enterprise,
or GamerSafer in any way. <br className="spacing-3" />
Spamming, abusing or misusing the Minehut API and/or MHSF will get
your IP blocked, we are not responsible for IP blocks.{" "}
<strong>You have been warned.</strong>
<br className="spacing-3" />
If you're worried, please review the{" "}
<Link href="https://support.minehut.com/hc/en-us/articles/27075816947731-Minehut-Rules">
Rules
</Link>
, <Link href="https://minehut.com/terms-of-service">ToS</Link> &{" "}
<Link href="https://support.mhsf.app/ECA">ECA</Link>.
</small>
</span>
</footer>
);
}

@ -0,0 +1,38 @@
"use client";
import { Button } from "@/components/ui/button";
import { Link } from "@/components/util/link";
import useStatus from "@/lib/hooks/use-status";
import { cn } from "@/lib/utils";
export function FooterStatus() {
const { loading, incidents, statusURL } = useStatus();
if (!loading)
return (
<Link
href={`https://${statusURL as string}`}
noExtraIcons
target="_blank"
>
<Button variant="tertiary">
<span
className={cn(
"text-sm flex items-center gap-2 font-normal",
"text-blue-600"
)}
>
<div
className="items-center bg-blue-600 dark:bg-blue-600"
style={{
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
All systems normal
</span>
</Button>
</Link>
);
}

@ -37,6 +37,21 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { toast } from "sonner"; import { toast } from "sonner";
import { useEffectOnce } from "@/lib/useEffectOnce";
import { allTags } from "@/config/tags";
import { ReactNode, useState } from "react";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Copy } from "lucide-react";
import useClipboard from "@/lib/useClipboard";
export default function ServerCard({ export default function ServerCard({
server, server,
@ -45,6 +60,8 @@ export default function ServerCard({
server: OnlineServer; server: OnlineServer;
motd: string | undefined; motd: string | undefined;
}) { }) {
const clipboard = useClipboard();
return ( return (
<Material <Material
key={server.name} key={server.name}
@ -66,6 +83,30 @@ export default function ServerCard({
<span className="flex gap-2 items-center"> <span className="flex gap-2 items-center">
<IconDisplay server={server} /> <IconDisplay server={server} />
<strong className="text-lg">{server.name}</strong> <strong className="text-lg">{server.name}</strong>
<Tooltip>
<TooltipTrigger>
<Button
variant="secondary"
size="square-md"
className="flex items-center"
onClick={(e) => {
e.stopPropagation();
toast.success("Copied IP address to clipboard.");
clipboard.writeText(`${server.name}.mhsf.minehut.gg`);
}}
>
<Copy size={16} />
</Button>
</TooltipTrigger>
<TooltipContent
className="max-w-[390px] break-words"
onClick={(e) => e.stopPropagation()}
>
Copy the server address to your clipboard. MHSF automatically adds
.mhsf in-between the server name and minehut.gg to tell server
owners where you came from.
</TooltipContent>
</Tooltip>
</span> </span>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
@ -74,7 +115,10 @@ export default function ServerCard({
</span> </span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-[390px] break-words"> <TooltipContent
className="max-w-[390px] break-words"
onClick={(e) => e.stopPropagation()}
>
{server.author ? ( {server.author ? (
<span> <span>
{server.name} is owned by{" "} {server.name} is owned by{" "}
@ -88,6 +132,7 @@ export default function ServerCard({
)} )}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<TagShower server={server} className="mt-1" />
{motd && ( {motd && (
<span <span
className="block break-all overflow-hidden mt-3" className="block break-all overflow-hidden mt-3"
@ -98,6 +143,129 @@ export default function ServerCard({
); );
} }
export type BadgeColor =
| "default"
| "red"
| "green"
| "yellow"
| "gray"
| "blue"
| "purple"
| "red-subtle"
| "green-subtle"
| "yellow-subtle"
| "gray-subtle"
| "blue-subtle"
| "purple-subtle"
| "custom";
export function TagShower(props: {
server: OnlineServer;
className?: string;
unclickable?: boolean;
}) {
const [loading, setLoading] = useState(true);
const [compatiableTags, setCompatiableTags] = useState<
Array<{
name: ReactNode;
docsName?: string;
tooltip: string;
htmlDocs: string;
role: BadgeColor;
}>
>([]);
useEffectOnce(() => {
if (loading) {
allTags.forEach((tag) => {
if (!tag.condition) {
tag.name(props.server).then((n) => {
compatiableTags.push({
name: n,
docsName: tag.docsName,
tooltip: tag.tooltipDesc,
htmlDocs: tag.htmlDocs,
role: tag.role === undefined ? "default" : tag.role,
});
setLoading(false);
});
} else
tag.condition(props.server).then((b) => {
if (b && tag.primary) {
tag.name(props.server).then((n) => {
compatiableTags.push({
name: n,
docsName: tag.docsName,
tooltip: tag.tooltipDesc,
htmlDocs: tag.htmlDocs,
role: tag.role === undefined ? "default" : tag.role,
});
setLoading(false);
});
}
});
});
}
});
if (loading) {
return <></>;
}
return (
<div
className="font-normal tracking-normal flex flex-wrap"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
{compatiableTags.map((t, i) => (
<span key={t.docsName} className="mr-1">
{props.unclickable && (
<Badge variant={t.role} className={props.className}>
{t.name}
</Badge>
)}
{!props.unclickable && (
<Dialog key={t.docsName}>
<DialogTrigger>
<Tooltip>
<TooltipTrigger>
<Badge variant={t.role} className={props.className}>
{t.name}
</Badge>
</TooltipTrigger>
<TooltipContent>
<div className="font-normal">
{t.tooltip}
<br />
Click the tag to learn more about it.
</div>
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{'"'}
{t.docsName == undefined ? t.name : t.docsName}
{'"'} documentation
</DialogTitle>
<DialogDescription
className="font-normal"
dangerouslySetInnerHTML={{
__html: t.htmlDocs,
}}
/>
</DialogHeader>
</DialogContent>
</Dialog>
)}
</span>
))}
</div>
);
}
function RankColoring({ rank, author }: { rank: string; author: string }) { function RankColoring({ rank, author }: { rank: string; author: string }) {
switch (rank.toLocaleLowerCase()) { switch (rank.toLocaleLowerCase()) {
case "default": case "default":

@ -0,0 +1,36 @@
"use client";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { Link } from "@/components/util/link";
import useStatus from "@/lib/hooks/use-status";
import { cn } from "@/lib/utils";
export function StatusButton() {
const { loading, incidents, statusURL } = useStatus();
if (loading) return <Spinner />;
return (
<Link href={`https://${statusURL as string}`} noExtraIcons target="_blank">
<Button variant="secondary" className="rounded-xl">
<span
className={cn(
"text-sm flex items-center gap-2 font-normal",
"text-blue-600"
)}
>
<div
className="items-center bg-blue-600 dark:bg-blue-600"
style={{
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
All systems normal
</span>
</Button>
</Link>
);
}

@ -0,0 +1,70 @@
import { Button } from "@/components/ui/button";
import { GithubNoTheme } from "@/components/ui/github";
import { Material } from "@/components/ui/material";
import { Link } from "@/components/util/link";
import { Inbox, Sticker } from "lucide-react";
import { StatusButton } from "./status-button";
import { Separator } from "@/components/ui/separator";
export function Support() {
return (
<article className="2xl:px-[30rem] xl:px-[15rem] lg:px-[10rem] px-4 border-l border-r">
<Material className="w-full py-10">
<h1 className="text-4xl font-bold w-full text-center justify-center flex items-center">
Contact
<span className="text-[0.4rem]">🧇</span>
</h1>
</Material>
<br />
<span className="grid grid-cols-2 gap-4">
<Material>
<GithubNoTheme className="w-[32px] h-[32px] dark:fill-white" />
<h2 className="text-xl font-bold">GitHub Issues</h2>
<span>
If you have a bug issue that is reproducible or suggestion please
make a new issue on{" "}
<Link href="GitHub:Issues" target="_blank">
GitHub Issues
</Link>
</span>
<br />
<Link href="GitHub:Issues">
<Button className="mt-2">Open an issue</Button>
</Link>
</Material>
<Material>
<Inbox className="w-[32px] h-[32px]" />
<h2 className="text-xl font-bold">Contact Support</h2>
<span>
If you do not have a GitHub account and/or need help with something
MHSF-related, feel free to contact{" "}
<Link href="mailto:support@mhsf.app" className="underline">
support@mhsf.app
</Link>
</span>
<br />
<span className="flex items-center mt-2 gap-2">
<Link href="mailto:support@mhsf.app">
<Button>Contact</Button>
</Link>
<StatusButton />
</span>
</Material>
</span>
<Material className="mt-4">
<Sticker className="w-[32px] h-[32px]" />
<h2 className="text-xl font-bold">Ask</h2>
<span>
For anything else where you'll need to contact somebody, contact me @{" "}
<Link href="mailto:dvelo@mhsf.app" className="underline">
dvelo@mhsf.app
</Link>
</span>
<Separator className="my-4" />
<Link href="mailto:dvelo@mhsf.app">
<Button>Give me an email!</Button>
</Link>
</Material>
</article>
);
}

@ -40,6 +40,8 @@ const badgeVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
default:
"bg-shadcn-primary text-shadcn-primary-foreground ring-transparent",
red: "bg-red-500 text-white ring-transparent", red: "bg-red-500 text-white ring-transparent",
green: "bg-green-500 text-white dark:text-black ring-transparent", green: "bg-green-500 text-white dark:text-black ring-transparent",
yellow: "bg-yellow-500 text-black ring-transparent", yellow: "bg-yellow-500 text-black ring-transparent",

@ -47,4 +47,19 @@ const Github = (props: SVGProps<SVGSVGElement>) => {
</svg> </svg>
); );
}; };
export const GithubNoTheme = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
viewBox="0 0 256 250"
width="1em"
height="1em"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
{...props}
>
<path d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z" />
</svg>
);
};
export default Github; export default Github;

@ -35,20 +35,28 @@ export function Link(
props: LinkProps & { props: LinkProps & {
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
noExtraIcons?: boolean;
target?: string;
} }
) { ) {
const href = props.href as string; const href = props.href as string;
return ( return (
<NextLink {...props} href={pageFind(href || "") || "#"} title={href}> <NextLink {...props} href={pageFind(href || "") || "#"} title={href}>
{(href || "").startsWith("Docs:") && ( {!props.noExtraIcons && (
<Book size={16} className="mr-[2px] inline-flex" /> <>
)} {(href || "").startsWith("Docs:") && (
{(href || "").startsWith("Wiki:") && ( <Book size={16} className="mr-[2px] inline-flex" />
<NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" /> )}
{(href || "").startsWith("Wiki:") && (
<NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" />
)}
</>
)} )}
{props.children} {props.children}
{(href || "").startsWith("https") && (
{!props.noExtraIcons && (href || "").startsWith("https") && (
<ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" /> <ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" />
)} )}
</NextLink> </NextLink>
@ -69,6 +77,8 @@ export const pageFind = (text: string) => {
if (text.startsWith("Server:")) return "/server/" + text.substring(7); if (text.startsWith("Server:")) return "/server/" + text.substring(7);
if (text.startsWith("Wiki:")) if (text.startsWith("Wiki:"))
return "https://minehut.wiki.gg/wiki/" + text.substring(5); return "https://minehut.wiki.gg/wiki/" + text.substring(5);
if (text === "GitHub:Issues")
return "https://github.com/DeveloLongScript/MHSF/issues/new/choose";
if (text.startsWith("GitHub:")) if (text.startsWith("GitHub:"))
return "https://github.com/" + text.substring(7); return "https://github.com/" + text.substring(7);
if (text === "Special:GitHub") if (text === "Special:GitHub")

@ -28,7 +28,10 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { BadgeColor } from "@/components/feat/server-list/server-card";
import { OnlineServer, ServerResponse } from "@/lib/types/mh-server"; import { OnlineServer, ServerResponse } from "@/lib/types/mh-server";
import { ServerCog } from "lucide-react";
import { ReactNode } from "react";
const serverCache: any = {}; const serverCache: any = {};
@ -46,36 +49,70 @@ const serverCache: any = {};
// //
// You may also use `requestServer()` to grab the offline version of the server from the API, which may get you more information about the server (ServerResponse) // You may also use `requestServer()` to grab the offline version of the server from the API, which may get you more information about the server (ServerResponse)
export const allTags: Array<{ export const allTags: Array<{
name: (server: OnlineServer) => Promise<string>; name: (server: OnlineServer) => Promise<string | ReactNode>;
condition: (server: OnlineServer) => Promise<boolean>; condition?: (server: OnlineServer) => Promise<boolean>;
listCondition?: (server: ServerResponse) => Promise<boolean>; listCondition?: (server: ServerResponse) => Promise<boolean>;
tooltipDesc: string; tooltipDesc: string;
htmlDocs: string; htmlDocs: string;
docsName: string; docsName: string;
primary: boolean; primary: boolean;
role?: role?: BadgeColor;
| "default"
| "destructive"
| "outline"
| "secondary"
| "red"
| "orange"
| "yellow"
| "green"
| "lime"
| "blue"
| "teal"
| "cyan"
| "violet"
| "indigo"
| "purple"
| "fuchsia"
| "pink";
__disab?: boolean; __disab?: boolean;
__filter?: boolean; __filter?: boolean;
}> = [ }> = [
{ {
name: async () => "Always Online", name: async (c) => (
<>
<div
className="items-center bg-green-700 dark:bg-green-400"
style={{
width: ".4rem",
height: ".4rem",
borderRadius: "9999px",
}}
/>
{c.playerData.playerCount} online
</>
),
condition: async (c) => c.playerData.playerCount !== 0,
htmlDocs:
"'Players Online' specifies the amount of players currently online. If this server is a network, the amount of players may not be accurate as this counter only counts the number of players coming directly from Minehut",
tooltipDesc:
"'Players Online' specifies the amount of players currently online.",
primary: true,
role: "green-subtle",
docsName: "Players Online",
__filter: true,
},
{
name: async (c) => (
<>
<div
className="items-center bg-gray-700 dark:bg-gray-300"
style={{
width: ".4rem",
height: ".4rem",
borderRadius: "9999px",
}}
/>{" "}
0 online
</>
),
condition: async (c) => c.playerData.playerCount === 0,
htmlDocs: "Nobody is online this server.",
tooltipDesc: "Nobody is online this server.",
primary: true,
role: "gray-subtle",
docsName: "Nobody Online",
__filter: true,
},
{
name: async () => (
<>
<ServerCog size={16} />
Always Online
</>
),
condition: async (b: any) => b.staticInfo.alwaysOnline, condition: async (b: any) => b.staticInfo.alwaysOnline,
tooltipDesc: tooltipDesc:
'"Always online" means that the server will not shut down until the plan associated with it expires.', '"Always online" means that the server will not shut down until the plan associated with it expires.',
@ -84,7 +121,7 @@ export const allTags: Array<{
`, `,
primary: true, primary: true,
docsName: "Always Online", docsName: "Always Online",
role: "secondary", role: "blue",
__disab: true, __disab: true,
}, },
{ {
@ -96,7 +133,7 @@ export const allTags: Array<{
htmlDocs: htmlDocs:
"This tag represents the maximum amount of players the server can have at one time. This doesn't mean the amount of players before the server crashes, it means the amount Minehut said the server can handle or the plan the server is on. <em>However, sometimes it might not appear because the server is external.</em>", "This tag represents the maximum amount of players the server can have at one time. This doesn't mean the amount of players before the server crashes, it means the amount Minehut said the server can handle or the plan the server is on. <em>However, sometimes it might not appear because the server is external.</em>",
primary: true, primary: true,
role: "secondary", role: "default",
__filter: true, __filter: true,
}, },
{ {
@ -108,6 +145,16 @@ export const allTags: Array<{
primary: true, primary: true,
role: "purple", role: "purple",
}, },
{
name: async (s) => <>{s.staticInfo.serverPlan.split(" ")[0]}</>,
tooltipDesc: "This tag represents the server plan this server is using.",
docsName: "Server Plan",
htmlDocs:
"This tag represents the maximum amount of players the server can have at one time. This doesn't mean the amount of players before the server crashes, it means the amount Minehut said the server can handle or the plan the server is on. <em>However, sometimes it might not appear because the server is external.</em>",
primary: true,
role: "red-subtle",
__filter: true,
},
// deprecated // deprecated
/**{ /**{
name: async () => "Velocity", name: async () => "Velocity",

@ -33,7 +33,7 @@ import { useEffect, useState } from "react";
export default function useStatus() { export default function useStatus() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [incidents, setIncidents] = useState(null); const [incidents, setIncidents] = useState(null);
const [statusURL, setStatusURL] = useState(null); const [statusURL, setStatusURL] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
fetch("/api/v1/get-status") fetch("/api/v1/get-status")

@ -31,44 +31,47 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { getAuth, clerkClient } from "@clerk/nextjs/server"; import { getAuth, clerkClient } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const { code } = req.body; const { code } = req.body;
if (code == null) { if (code == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("auth_codes"); const collection = db.collection("auth_codes");
const entry = await collection.findOne({ code }); const entry = await collection.findOne({ code });
if (entry == null) { if (entry == null) {
res.status(400).send({ message: "Couldn't find code" }); res.status(400).send({ message: "Couldn't find code" });
return; return;
} }
collection.findOneAndDelete({ code }); collection.findOneAndDelete({ code });
const users = db.collection("claimed-users"); const users = db.collection("claimed-users");
await users.insertOne({ player: entry.player, userId }); await users.insertOne({ player: entry.player, userId });
(await clerkClient()).users.updateUserMetadata(userId, { (await clerkClient()).users.updateUserMetadata(userId, {
publicMetadata: { publicMetadata: {
player: entry.player, player: entry.player,
}, },
}); });
res.send({ player: entry.player }); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
client.close(); res.send({ player: entry.player });
} }

@ -30,25 +30,28 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { server } = req.body; const { server } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers"); const collection = db.collection("owned-servers");
res.send({ owned: (await collection.findOne({ server })) != undefined }); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
client.close(); res.send({ owned: (await collection.findOne({ server })) != undefined });
} }

@ -32,82 +32,96 @@ import { NextApiRequest, NextApiResponse } from "next";
import { clerkClient, getAuth } from "@clerk/nextjs/server"; import { clerkClient, getAuth } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { OnlineServer } from "@/lib/types/mh-server"; import { OnlineServer } from "@/lib/types/mh-server";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const { server } = req.body; const { server } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
if ( if (
(await (await clerkClient()).users.getUser(userId)).publicMetadata.player == (await (await clerkClient()).users.getUser(userId)).publicMetadata.player ==
undefined undefined
) { ) {
return res.status(401).json({ error: "Account not linked" }); return res.status(401).json({ error: "Account not linked" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers"); const collection = db.collection("owned-servers");
if ((await collection.findOne({ server: server })) == undefined) { if ((await collection.findOne({ server: server })) == undefined) {
const mh = await fetch( const mh = await fetch(
process.env.MHSF_BACKEND_API_LOCATION ?? process.env.MHSF_BACKEND_API_LOCATION ??
"https://api.minehut.com/servers", "https://api.minehut.com/servers",
{ {
headers: { headers: {
accept: "*/*", accept: "*/*",
"accept-language": Math.random().toString(), "accept-language": Math.random().toString(),
priority: "u=1, i", priority: "u=1, i",
"sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126"', "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126"',
"sec-ch-ua-mobile": "?0", "sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"', "sec-ch-ua-platform": '"macOS"',
"sec-fetch-dest": "empty", "sec-fetch-dest": "empty",
"sec-fetch-mode": "cors", "sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site", "sec-fetch-site": "cross-site",
Referer: "http://localhost:3000/", Referer: "http://localhost:3000/",
"Referrer-Policy": "strict-origin-when-cross-origin", "Referrer-Policy": "strict-origin-when-cross-origin",
Authentication: `MHSF-Backend-Server ${process.env.MHSF_BACKEND_API_LOCATION ? process.env.MHSF_BACKEND_SECRET : "Sorry Minehut Devs."}`, Authentication: `MHSF-Backend-Server ${process.env.MHSF_BACKEND_API_LOCATION ? process.env.MHSF_BACKEND_SECRET : "Sorry Minehut Devs."}`,
}, },
body: null, body: null,
method: "GET", method: "GET",
} },
); );
const servers: Array<OnlineServer> = (await mh.json()).servers; const servers: Array<OnlineServer> = (await mh.json()).servers;
servers.forEach(async (c, i) => { servers.forEach(async (c, i) => {
if (c.name == server) { if (c.name === server) {
const MCUsername = (await (await clerkClient()).users.getUser(userId)) const MCUsername = (await (await clerkClient()).users.getUser(userId))
.publicMetadata.player; .publicMetadata.player;
if (MCUsername == c.author) { if (MCUsername === c.author) {
await collection.insertOne({ server, author: userId }); await collection.insertOne({ server, author: userId });
res.send({ message: "Successfully owned server!" });
client.close(); // Close the database, but don't close this
} else { // serverless instance until it happens
res waitUntil(client.close());
.status(400)
.send({ message: "The linked account doesn't own the server." }); res.send({ message: "Successfully owned server!" });
client.close(); } else {
} // Close the database, but don't close this
} // serverless instance until it happens
if (i == servers.length) { waitUntil(client.close());
res.status(400).send({ message: "The server needs to be online." });
client.close(); res
} .status(400)
}); .send({ message: "The linked account doesn't own the server." });
} else { }
res.status(400).send({ message: "This server has already been owned." }); }
client.close(); if (i == servers.length) {
} // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.status(400).send({ message: "The server needs to be online." });
}
});
} else {
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.status(400).send({ message: "This server has already been owned." });
}
} }

@ -31,37 +31,40 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { clerkClient, getAuth } from "@clerk/nextjs/server"; import { clerkClient, getAuth } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const { server } = req.body; const { server } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
if ( if (
(await (await clerkClient()).users.getUser(userId)).publicMetadata.player == (await (await clerkClient()).users.getUser(userId)).publicMetadata.player ==
undefined undefined
) { ) {
return res.status(401).json({ error: "Account not linked" }); return res.status(401).json({ error: "Account not linked" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers"); const collection = db.collection("owned-servers");
res.send({ // Close the database, but don't close this
result: (await collection.findOne({ server, author: userId })) != null, // serverless instance until it happens
}); waitUntil(client.close());
client.close(); res.send({
result: (await collection.findOne({ server, author: userId })) != null,
});
} }

@ -31,35 +31,36 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { getAuth, clerkClient } from "@clerk/nextjs/server"; import { getAuth, clerkClient } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const users = db.collection("claimed-users"); const users = db.collection("claimed-users");
if ((await users.find({ userId }).toArray()).length === 0) { if ((await users.find({ userId }).toArray()).length === 0) {
res.status(400).send({ result: "Hasn't linked yet!" }); res.status(400).send({ result: "Hasn't linked yet!" });
return; return;
} }
await users.findOneAndDelete({ userId }); await users.findOneAndDelete({ userId });
const user = await (await clerkClient()).users.getUser(userId); const user = await (await clerkClient()).users.getUser(userId);
await ( await (await clerkClient()).users.updateUserMetadata(userId, {
await clerkClient() publicMetadata: { player: null },
).users.updateUserMetadata(userId, { });
publicMetadata: { player: null },
});
res.send({ result: "Unlinked!" }); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
client.close(); res.send({ result: "Unlinked!" });
} }

@ -31,43 +31,50 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { clerkClient, getAuth } from "@clerk/nextjs/server"; import { clerkClient, getAuth } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const { server } = req.body; const { server } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
if ( if (
(await (await clerkClient()).users.getUser(userId)).publicMetadata.player == (await (await clerkClient()).users.getUser(userId)).publicMetadata.player ==
undefined undefined
) { ) {
return res.status(401).json({ error: "Account not linked" }); return res.status(401).json({ error: "Account not linked" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers"); const collection = db.collection("owned-servers");
const customization = db.collection("customization"); const customization = db.collection("customization");
if ( if (
(await collection.findOne({ server: server, author: userId })) != undefined (await collection.findOne({ server: server, author: userId })) != undefined
) { ) {
collection.findOneAndDelete({ server }); collection.findOneAndDelete({ server });
customization.findOneAndDelete({ server }); customization.findOneAndDelete({ server });
res.send({ message: "Un-owned server!" });
} else { // Close the database, but don't close this
res.status(400).send({ message: "This server hasn't been owned." }); // serverless instance until it happens
client.close(); waitUntil(client.close());
} res.send({ message: "Un-owned server!" });
} else {
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.status(400).send({ message: "This server hasn't been owned." });
}
} }

@ -28,6 +28,7 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { waitUntil } from "@vercel/functions";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
@ -44,5 +45,9 @@ export default async function handler(
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("achievements"); const collection = db.collection("achievements");
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.send({ result: await collection.find({ name: server }).toArray() }); res.send({ result: await collection.find({ name: server }).toArray() });
} }

@ -30,19 +30,24 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const server = req.query.server as string; const server = req.query.server as string;
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("customization"); const collection = db.collection("customization");
res.send({ results: await collection.findOne({ server }) }); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
client.close(); res.send({ results: await collection.findOne({ server }) });
client.close();
} }

@ -31,104 +31,111 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { getAuth } from "@clerk/nextjs/server"; import { getAuth } from "@clerk/nextjs/server";
import { Document, MongoClient, WithId } from "mongodb"; import { Document, MongoClient, WithId } from "mongodb";
import { waitUntil } from "@vercel/functions";
const validColors = [ const validColors = [
"zinc", "zinc",
"slate", "slate",
"stone", "stone",
"gray", "gray",
"neutral", "neutral",
"red", "red",
"rose", "rose",
"orange", "orange",
"green", "green",
"blue", "blue",
"yellow", "yellow",
"violet", "violet",
]; ];
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const server = req.query.server as string; const server = req.query.server as string;
const { customization }: { customization: any } = req.body; const { customization }: { customization: any } = req.body;
if ( if (
customization.description != undefined && customization.description != undefined &&
(!(Array.from(customization.description).length < 1250) || (!(Array.from(customization.description).length < 1250) ||
!(Array.from(customization.description).length > 2)) !(Array.from(customization.description).length > 2))
) )
return res.status(400).send({ message: "Description is incorrect length" }); return res.status(400).send({ message: "Description is incorrect length" });
if ( if (
customization.discord != undefined && customization.discord != undefined &&
!/^\d*\.?\d*$/.test(customization.discord) !/^\d*\.?\d*$/.test(customization.discord)
) )
return res return res
.status(400) .status(400)
.send({ message: "Discord server has invalid chars" }); .send({ message: "Discord server has invalid chars" });
if ( if (
customization.colorScheme != undefined && customization.colorScheme != undefined &&
!validColors.includes(customization.colorScheme) !validColors.includes(customization.colorScheme)
) )
return res.status(400).send({ message: "Color doesn't exist" }); return res.status(400).send({ message: "Color doesn't exist" });
if (customization == null) { if (customization == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers"); const collection = db.collection("owned-servers");
const customizationColl = db.collection("customization"); const customizationColl = db.collection("customization");
if (!((await collection.findOne({ server, author: userId })) == undefined)) { if (!((await collection.findOne({ server, author: userId })) == undefined)) {
const alreadyExists = const alreadyExists =
(await customizationColl.findOne({ server })) != undefined; (await customizationColl.findOne({ server })) != undefined;
if (!alreadyExists) { if (!alreadyExists) {
await customizationColl.insertOne({ await customizationColl.insertOne({
server, server,
colorScheme: customization.colorScheme, colorScheme: customization.colorScheme,
description: customization.description, description: customization.description,
banner: customization.banner, banner: customization.banner,
discord: customization.discord, discord: customization.discord,
}); });
} else { } else {
const find = (await customizationColl.findOne({ const find = (await customizationColl.findOne({
server, server,
})) as WithId<Document>; })) as WithId<Document>;
if (customization.colorScheme != undefined) if (customization.colorScheme != undefined)
await customizationColl.findOneAndUpdate( await customizationColl.findOneAndUpdate(
{ server }, { server },
{ $set: { colorScheme: customization.colorScheme } } { $set: { colorScheme: customization.colorScheme } },
); );
if (customization.description != undefined) if (customization.description != undefined)
await customizationColl.findOneAndUpdate( await customizationColl.findOneAndUpdate(
{ server }, { server },
{ $set: { description: customization.description } } { $set: { description: customization.description } },
); );
if (customization.banner != undefined) if (customization.banner != undefined)
await customizationColl.findOneAndUpdate( await customizationColl.findOneAndUpdate(
{ server }, { server },
{ $set: { banner: customization.banner } } { $set: { banner: customization.banner } },
); );
if (customization.discord != undefined) if (customization.discord != undefined)
await customizationColl.findOneAndUpdate( await customizationColl.findOneAndUpdate(
{ server }, { server },
{ $set: { discord: customization.discord } } { $set: { discord: customization.discord } },
); );
} }
res.send({ message: "Done!" }); // Close the database, but don't close this
} else { // serverless instance until it happens
res.status(400).send({ message: "You don't own this server." }); waitUntil(client.close());
}
client.close(); res.send({ message: "Done!" });
} else {
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.status(400).send({ message: "You don't own this server." });
}
} }

@ -30,29 +30,32 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { server }: { server: Array<string> | undefined } = req.body; const { server }: { server: Array<string> | undefined } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("customization"); const collection = db.collection("customization");
const results: { server: string; customization: any }[] = []; const results: { server: string; customization: any }[] = [];
server.forEach(async (c) => { server.forEach(async (c) => {
results.push({ server: c, customization: await collection.findOne({ c }) }); results.push({ server: c, customization: await collection.findOne({ c }) });
}); });
res.send({ results }); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
client.close(); res.send({ results });
} }

@ -30,59 +30,62 @@
import type { NextApiResponse, NextApiRequest } from "next"; import type { NextApiResponse, NextApiRequest } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { server } = req.query; const { server } = req.query;
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("meta"); const collection = db.collection("meta");
const find = await collection.find({ server: server }).toArray(); const find = await collection.find({ server: server }).toArray();
if (find.length != 0) { // Close the database, but don't close this
const entry = find[0]; // serverless instance until it happens
res.send({ result: entry.favorites }); waitUntil(client.close());
} else {
res.send({ result: 0 });
}
client.close(); if (find.length != 0) {
const entry = find[0];
res.send({ result: entry.favorites });
} else {
res.send({ result: 0 });
}
} }
export async function increaseNum(client: MongoClient, server: string) { export async function increaseNum(client: MongoClient, server: string) {
const db = client.db("mhsf"); const db = client.db("mhsf");
const collection = db.collection("meta"); const collection = db.collection("meta");
const find = await collection.find({ server: server }).toArray(); const find = await collection.find({ server: server }).toArray();
if (find.length == 0) { if (find.length == 0) {
collection.insertOne({ server: server, favorites: 1, date: new Date() }); collection.insertOne({ server: server, favorites: 1, date: new Date() });
} else { } else {
const entry = find[0]; const entry = find[0];
collection.findOneAndReplace( collection.findOneAndReplace(
{ server: server }, { server: server },
{ server: server, favorites: entry.favorites + 1, date: new Date() } { server: server, favorites: entry.favorites + 1, date: new Date() },
); );
} }
} }
export async function decreaseNum(client: MongoClient, server: string) { export async function decreaseNum(client: MongoClient, server: string) {
const db = client.db("mhsf"); const db = client.db("mhsf");
const collection = db.collection("meta"); const collection = db.collection("meta");
const find = await collection.find({ server: server }).toArray(); const find = await collection.find({ server: server }).toArray();
if (find.length == 0) { if (find.length == 0) {
return; return;
// Physically is impossible // Physically is impossible
} else { } else {
const entry = find[0]; const entry = find[0];
collection.findOneAndReplace( collection.findOneAndReplace(
{ server: server }, { server: server },
{ server: server, favorites: entry.favorites - 1 } { server: server, favorites: entry.favorites - 1 },
); );
} }
} }

@ -32,59 +32,74 @@ import type { NextApiResponse, NextApiRequest } from "next";
import { MongoClient, ObjectId } from "mongodb"; import { MongoClient, ObjectId } from "mongodb";
import { getAuth } from "@clerk/nextjs/server"; import { getAuth } from "@clerk/nextjs/server";
import { decreaseNum, increaseNum } from "./community-favorites"; import { decreaseNum, increaseNum } from "./community-favorites";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const server = req.query.server as string; const server = req.query.server as string;
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites"); const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray(); const find = await collection.find({ user: userId }).toArray();
if (find.length == 0) { if (find.length == 0) {
collection.insertOne({ user: userId, favorites: [server] }); collection.insertOne({ user: userId, favorites: [server] });
await increaseNum(client, server); await increaseNum(client, server);
res.send({ message: "Favorited " + server });
} else {
const collect = find[0];
let existingFavorites: Array<string> = collect.favorites;
console.log(collect);
if (existingFavorites.includes(server)) { // Close the database, but don't close this
// remove that favorite from the list // serverless instance until it happens
const index = existingFavorites.indexOf(server); waitUntil(client.close());
await decreaseNum(client, server);
if (index > -1) { res.send({ message: "Favorited " + server });
existingFavorites.splice(index, 1); } else {
} const collect = find[0];
collection.replaceOne( let existingFavorites: Array<string> = collect.favorites;
{ _id: new ObjectId(collect._id) }, console.log(collect);
{
user: userId, if (existingFavorites.includes(server)) {
favorites: existingFavorites, // remove that favorite from the list
} const index = existingFavorites.indexOf(server);
); await decreaseNum(client, server);
res.send({ message: "Unfavorited " + server }); if (index > -1) {
} else { existingFavorites.splice(index, 1);
existingFavorites.push(server); }
await increaseNum(client, server); collection.replaceOne(
collection.replaceOne( { _id: new ObjectId(collect._id) },
{ _id: new ObjectId(collect._id) }, {
{ user: userId,
user: userId, favorites: existingFavorites,
favorites: existingFavorites, },
} );
);
res.send({ message: "Favorited " + server }); // Close the database, but don't close this
} // serverless instance until it happens
} waitUntil(client.close());
res.send({ message: "Unfavorited " + server });
} else {
existingFavorites.push(server);
await increaseNum(client, server);
collection.replaceOne(
{ _id: new ObjectId(collect._id) },
{
user: userId,
favorites: existingFavorites,
},
);
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.send({ message: "Favorited " + server });
}
}
} }

@ -31,26 +31,31 @@
import type { NextApiResponse, NextApiRequest } from "next"; import type { NextApiResponse, NextApiRequest } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { getAuth } from "@clerk/nextjs/server"; import { getAuth } from "@clerk/nextjs/server";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const server = req.query.server as string; const server = req.query.server as string;
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites"); const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray(); const find = await collection.find({ user: userId }).toArray();
if (find.length == 0) res.send({ result: false });
else { // Close the database, but don't close this
res.send({ result: find[0].favorites.includes(server) }); // serverless instance until it happens
} waitUntil(client.close());
client.close();
if (find.length == 0) res.send({ result: false });
else {
res.send({ result: find[0].favorites.includes(server) });
}
} }

@ -31,27 +31,31 @@
import type { NextApiResponse, NextApiRequest } from "next"; import type { NextApiResponse, NextApiRequest } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { getAuth } from "@clerk/nextjs/server"; import { getAuth } from "@clerk/nextjs/server";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites"); const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray(); const find = await collection.find({ user: userId }).toArray();
if (find.length == 0) { // Close the database, but don't close this
res.send({ result: [] }); // serverless instance until it happens
} else { waitUntil(client.close());
res.send({ result: find[0].favorites });
} if (find.length == 0) {
client.close(); res.send({ result: [] });
} else {
res.send({ result: find[0].favorites });
}
} }

@ -30,33 +30,48 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history"); const db = client.db("mhsf").collection("history");
const server = req.query.server as string; const server = req.query.server as string;
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] const daysOfWeek = [
const result = await Promise.all([1,2,3,4,5,6,7].map(async (c) => { "Sunday",
const results = await db.find({ "Monday",
$and: [ "Tuesday",
{ server }, "Wednesday",
{ $expr: { $eq: [{ $dayOfWeek: "$date" }, c] } } "Thursday",
] "Friday",
}).toArray() "Saturday",
];
const result = await Promise.all(
[1, 2, 3, 4, 5, 6, 7].map(async (c) => {
const results = await db
.find({
$and: [{ server }, { $expr: { $eq: [{ $dayOfWeek: "$date" }, c] } }],
})
.toArray();
if (results.length !== 0) { if (results.length !== 0) {
const averageNums = (results as any as {player_count: number}[]).map((x: {player_count: number}) => x.player_count) const averageNums = (results as any as { player_count: number }[]).map(
const average = averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length; (x: { player_count: number }) => x.player_count,
);
const average =
averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length;
return { day: daysOfWeek[c - 1], result: Math.floor(average) }; return { day: daysOfWeek[c - 1], result: Math.floor(average) };
} }
return undefined; return undefined;
})); }),
);
client.close() // Close the database, but don't close this
res.send({result: result.filter((c) => c !== undefined)}); // serverless instance until it happens
} waitUntil(client.close());
res.send({ result: result.filter((c) => c !== undefined) });
}

@ -30,33 +30,36 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("historical"); const db = client.db("mhsf").collection("historical");
const server = req.query.server as string; const server = req.query.server as string;
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes); const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
const allData = await db.find({ server }).toArray(); const allData = await db.find({ server }).toArray();
const data: any[] = []; const data: any[] = [];
allData.forEach((d) => { allData.forEach((d) => {
const result: any = {}; const result: any = {};
scopes.forEach((b) => { scopes.forEach((b) => {
result[b] = d[b]; result[b] = d[b];
}); });
data.push(result); data.push(result);
}); });
client.close(); // Close the database, but don't close this
res.send({ data }); // serverless instance until it happens
waitUntil(client.close());
res.send({ data });
} }
function checkForInfoOrLeave(res: NextApiResponse, info: any) { function checkForInfoOrLeave(res: NextApiResponse, info: any) {
if (info == undefined) if (info == undefined)
res.status(400).json({ message: "Information wasn't supplied" }); res.status(400).json({ message: "Information wasn't supplied" });
return info; return info;
} }

@ -30,33 +30,61 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history"); const db = client.db("mhsf").collection("history");
const server = req.query.server as string; const server = req.query.server as string;
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] const months = [
const result = await Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(async (c) => { "January",
const results = await db.find({ "February",
$and: [ "March",
{ server }, "April",
{ date: { $gte: new Date(new Date().getFullYear(), c - 1, 1), $lt: new Date(new Date().getFullYear(), c, 1) } } "May",
] "June",
}).toArray() "July",
"August",
"September",
"October",
"November",
"December",
];
const result = await Promise.all(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(async (c) => {
const results = await db
.find({
$and: [
{ server },
{
date: {
$gte: new Date(new Date().getFullYear(), c - 1, 1),
$lt: new Date(new Date().getFullYear(), c, 1),
},
},
],
})
.toArray();
if (results.length !== 0) { if (results.length !== 0) {
const averageNums = (results as any as {player_count: number}[]).map((x: {player_count: number}) => x.player_count) const averageNums = (results as any as { player_count: number }[]).map(
const average = averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length; (x: { player_count: number }) => x.player_count,
);
const average =
averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length;
return { month: months[c - 1], result: Math.floor(average) }; return { month: months[c - 1], result: Math.floor(average) };
} }
return undefined; return undefined;
})); }),
);
client.close() // Close the database, but don't close this
res.send({result: result.filter((c) => c !== undefined)}); // serverless instance until it happens
} waitUntil(client.close());
res.send({ result: result.filter((c) => c !== undefined) });
}

@ -30,40 +30,43 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history"); const db = client.db("mhsf").collection("history");
const mh = client.db("mhsf").collection("mh"); const mh = client.db("mhsf").collection("mh");
const server = req.query.server as string; const server = req.query.server as string;
const allData = await db.find({ server }).toArray(); const allData = await db.find({ server }).toArray();
const data: any[] = []; const data: any[] = [];
if (server === "peww") console.log(allData.slice(-30)); if (server === "peww") console.log(allData.slice(-30));
for (const d of allData.slice(-30)) { for (const d of allData.slice(-30)) {
const dateOfEntry = new Date(d.date); const dateOfEntry = new Date(d.date);
const result = await mh const result = await mh
.find({ .find({
date: { date: {
$gte: new Date(dateOfEntry.getTime() - 1000 * 60 * 60), $gte: new Date(dateOfEntry.getTime() - 1000 * 60 * 60),
$lt: new Date(dateOfEntry.getTime() + 1000 * 60 * 60), $lt: new Date(dateOfEntry.getTime() + 1000 * 60 * 60),
}, },
}) })
.toArray(); .toArray();
if (result.length > 0) { if (result.length > 0) {
const resultedData = result[0]; const resultedData = result[0];
data.push({ data.push({
relativePrecentage: d.player_count / resultedData.total_players, relativePrecentage: d.player_count / resultedData.total_players,
date: dateOfEntry, date: dateOfEntry,
}); });
} }
} }
client.close(); // Close the database, but don't close this
res.send({ data }); // serverless instance until it happens
waitUntil(client.close());
res.send({ data });
} }

@ -30,38 +30,41 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history"); const db = client.db("mhsf").collection("history");
const server = req.query.server as string; const server = req.query.server as string;
let dataMax = 0; let dataMax = 0;
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes); const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
const allData = await db.find({ server }).toArray(); const allData = await db.find({ server }).toArray();
const data: any[] = []; const data: any[] = [];
dataMax = ( dataMax = (
await db.find({ server }).sort({ player_count: -1 }).limit(1).toArray() await db.find({ server }).sort({ player_count: -1 }).limit(1).toArray()
)[0].player_count; )[0].player_count;
allData.forEach((d) => { allData.forEach((d) => {
const result: any = {}; const result: any = {};
scopes.forEach((b) => { scopes.forEach((b) => {
result[b] = d[b]; result[b] = d[b];
}); });
data.push(result); data.push(result);
}); });
client.close(); // Close the database, but don't close this
res.send({ data, dataMax }); // serverless instance until it happens
waitUntil(client.close());
res.send({ data, dataMax });
} }
function checkForInfoOrLeave(res: NextApiResponse, info: any) { function checkForInfoOrLeave(res: NextApiResponse, info: any) {
if (info == undefined) if (info == undefined)
res.status(400).json({ message: "Information wasn't supplied" }); res.status(400).json({ message: "Information wasn't supplied" });
return info; return info;
} }

@ -30,6 +30,7 @@
import { MongoClient, type WithId } from "mongodb"; import { MongoClient, type WithId } from "mongodb";
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -50,5 +51,9 @@ export default async function handler(
array.slice(0, 100).reduce((acc, curr) => acc + curr.total_players, 0) / array.slice(0, 100).reduce((acc, curr) => acc + curr.total_players, 0) /
array.slice(0, 100).length; array.slice(0, 100).length;
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.send({ totalServerAverage, totalPlayerAverage }); res.send({ totalServerAverage, totalPlayerAverage });
} }

@ -30,6 +30,7 @@
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { waitUntil } from "@vercel/functions"
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -49,7 +50,11 @@ export default async function handler(
}); });
data.push(result); data.push(result);
}); });
client.close();
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close())
res.send({ data }); res.send({ data });
} }

@ -30,21 +30,25 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf"); const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("meta"); const collection = db.collection("meta");
const all = await collection.find().toArray(); const all = await collection.find().toArray();
const sorted = all.sort((a, b) => a.favorites - b.favorites); const sorted = all.sort((a, b) => a.favorites - b.favorites);
sorted.reverse(); sorted.reverse();
res.send({ results: sorted });
client.close(); // Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.send({ results: sorted });
} }

@ -28,8 +28,9 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
@ -46,6 +47,9 @@ export default async function handler(
const collection = db.collection("claimed-users"); const collection = db.collection("claimed-users");
await collection.findOneAndDelete({ userId: id }); await collection.findOneAndDelete({ userId: id });
// Close the database, but don't close this
// serverless instance until it happens
waitUntil(client.close());
res.send({ message: "Done!" }); res.send({ message: "Done!" });
client.close();
} }

@ -32,49 +32,52 @@ import { NextApiRequest, NextApiResponse } from "next";
import { getAuth } from "@clerk/nextjs/server"; import { getAuth } from "@clerk/nextjs/server";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { inngest } from "../inngest"; import { inngest } from "../inngest";
import { waitUntil } from "@vercel/functions";
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse,
) { ) {
const { userId } = getAuth(req); const { userId } = getAuth(req);
const { server } = req.body; const { server } = req.body;
if (server == null) { if (server == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
const { reason } = req.body; const { reason } = req.body;
if (reason == null) { if (reason == null) {
res.status(400).send({ message: "Couldn't find data" }); res.status(400).send({ message: "Couldn't find data" });
return; return;
} }
if (!userId) { if (!userId) {
return res.status(401).json({ error: "Unauthorized" }); return res.status(401).json({ error: "Unauthorized" });
} }
const client = new MongoClient(process.env.MONGO_DB as string); const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect(); await client.connect();
const db = client.db("mhsf"); const db = client.db("mhsf");
const collection = db.collection("reports"); const collection = db.collection("reports");
const entry = await collection.insertOne({ const entry = await collection.insertOne({
server: server, server: server,
reason: reason, reason: reason,
userId: userId, userId: userId,
}); });
// Don't wait for this to finish, just continue anyway // Don't wait for this to finish, just continue anyway
inngest.send({ inngest.send({
name: "report-server", name: "report-server",
data: { data: {
_id: entry.insertedId.toString(), _id: entry.insertedId.toString(),
server, server,
reason, reason,
userId, userId,
}, },
}); });
client.close(); // Close the database, but don't close this
res.send({ msg: "Successfully reported server!" }); // serverless instance until it happens
waitUntil(client.close());
res.send({ msg: "Successfully reported server!" });
} }

@ -28,8 +28,14 @@
* OTHER DEALINGS IN THE SOFTWARE. * OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { version } from "@/config/version";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) { export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.send({ up: true }); res.send({
up: true,
apiNotice: `MHSF v${version} is an open-source, MIT-licensed Minehut API wrapper.
MHSF is not officially affiliated with with Minehut, Super League Enterprise, or GamerSafer in any way.
Spamming, abusing or misusing the Minehut API and/or MHSF will get your IP blocked, we are not responsible for IP blocks.`,
});
} }

@ -1859,12 +1859,7 @@
"@radix-ui/react-primitive" "2.0.2" "@radix-ui/react-primitive" "2.0.2"
"@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-icons@1.3.0": "@radix-ui/react-icons@^1.3.0", "@radix-ui/react-icons@^1.3.2":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69"
integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==
"@radix-ui/react-icons@^1.3.0":
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz#09be63d178262181aeca5fb7f7bc944b10a7f441" resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.2.tgz#09be63d178262181aeca5fb7f7bc944b10a7f441"
integrity sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g== integrity sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==
@ -2282,7 +2277,7 @@
dependencies: dependencies:
"@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-compose-refs" "1.1.1"
"@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.0": "@radix-ui/react-slot@1.1.2", "@radix-ui/react-slot@^1.1.0", "@radix-ui/react-slot@^1.1.2":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz#daffff7b2bfe99ade63b5168407680b93c00e1c6" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz#daffff7b2bfe99ade63b5168407680b93c00e1c6"
integrity sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ== integrity sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==
@ -3283,6 +3278,11 @@
resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-1.5.0.tgz#073f93694897414b21a8495e2619bbf64447dcaa" resolved "https://registry.yarnpkg.com/@vercel/analytics/-/analytics-1.5.0.tgz#073f93694897414b21a8495e2619bbf64447dcaa"
integrity sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g== integrity sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==
"@vercel/functions@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@vercel/functions/-/functions-2.0.0.tgz#be72057235a667b8c5b2713880fba717cd5a372b"
integrity sha512-BSwIihLHoV18gerKZJyGuqd3rtaYM6rJvET1kOwKktshucyaHXTJel7Cxegs+sdX0NZqsX4LO2MFnMU2jG01Cw==
"@vercel/speed-insights@^1.0.12": "@vercel/speed-insights@^1.0.12":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@vercel/speed-insights/-/speed-insights-1.2.0.tgz#1656c3596d4ec02d93d301ca45944c1b9b245186" resolved "https://registry.yarnpkg.com/@vercel/speed-insights/-/speed-insights-1.2.0.tgz#1656c3596d4ec02d93d301ca45944c1b9b245186"