mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-07-07 00:41:03 -05:00
feat(v2): i forgot
This commit is contained in:
parent
e4dc5f86de
commit
8541994c4d
@ -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>
|
||||||
|
|||||||
25
apps/www/src/app/(main)/server/[server]/page.tsx
Normal file
25
apps/www/src/app/(main)/server/[server]/page.tsx
Normal file
@ -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() {}
|
||||||
15
apps/www/src/app/(main)/support/page.tsx
Normal file
15
apps/www/src/app/(main)/support/page.tsx
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
69
apps/www/src/components/feat/effects/bento-grid.tsx
Normal file
69
apps/www/src/components/feat/effects/bento-grid.tsx
Normal file
@ -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 };
|
||||||
61
apps/www/src/components/feat/footer/footer.tsx
Normal file
61
apps/www/src/components/feat/footer/footer.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
apps/www/src/components/feat/footer/status.tsx
Normal file
38
apps/www/src/components/feat/footer/status.tsx
Normal file
@ -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":
|
||||||
|
|||||||
36
apps/www/src/components/feat/support/status-button.tsx
Normal file
36
apps/www/src/components/feat/support/status-button.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
70
apps/www/src/components/feat/support/support.tsx
Normal file
70
apps/www/src/components/feat/support/support.tsx
Normal file
@ -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.`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@ -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"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user