fix: issue

This commit is contained in:
dvelo 2025-01-03 13:40:13 -06:00
parent fcb4221fca
commit 270f6efaa6
29 changed files with 887 additions and 22 deletions

@ -42,6 +42,7 @@ We use [Atlas](https://www.mongodb.com/atlas) to host our MongoDB database, but
```dotenv
MONGO_DB="mongodb+srv://..."
```
You can also set `CUSTOM_MONGO_DB` to a database name that will apply to all operations except statistical operations.
## Smaller things (for production-ready servers)

@ -36,6 +36,8 @@ import TabServer from "@/components/misc/TabServer";
import type { Metadata, ResolvingMetadata } from "next";
import StickyTopbar from "@/components/misc/StickyTopbar";
import { RelativeChart } from "@/components/charts/RelativeChart";
import { MonthlyChart } from "@/components/charts/MonthlyChart";
import { DailyChart } from "@/components/charts/DailyChart";
type Props = {
params: { server: string };
@ -113,6 +115,11 @@ export default function ServerPage({ params }: { params: { server: string } }) {
<NewChart server={params.server} />
<br />
<RelativeChart server={params.server} />
<br />
<div className="grid grid-cols-2 gap-4">
<MonthlyChart server={params.server} />
<DailyChart server={params.server} />
</div>
</div>
</div>
</ColorProvider>

@ -117,6 +117,7 @@ import { LoadingSpinner } from "./ui/loading-spinner";
import StickyTopbar from "./misc/StickyTopbar";
import { HoverCard } from "@radix-ui/react-hover-card";
import { HoverCardTrigger } from "./ui/hover-card";
import { ExampleChart } from "./charts/ExampleChart";
// ag-grid
ModuleRegistry.registerModules([AllCommunityModule]);
@ -451,13 +452,22 @@ export default function ServerList() {
and descriptions, making your server stand out with information
that can be shown to players.
</p>
<BentoGrid className="max-h-[100px]">
<BentoGrid className="max-h-[100px] mb-[300px]">
{features.map((feature, idx) => (
<BentoCard key={idx} {...feature} />
))}
</BentoGrid>
<Separator />
<br />
<br />
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-2xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-2xl md:text-3xl lg:text-4xl dark:from-white dark:to-white/40">
Monitor your success
</h1>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
Ever wondered how a server was doing? MHSF constantly monitors servers
and shows you statistics about how a server is doing at any point of time.
</p>
<ExampleChart />
</div>
)}
<br />

@ -121,12 +121,13 @@ export default function ServerView(props: { server: string }) {
<>
{single.grabOnline() == undefined && !single.grabOffline()?.online && (
<div className="grid pl-4 pr-4">
<X />
<div
className=" rounded p-2"
style={{ backgroundColor: "rgba(244, 63, 94, .16)" }}
>
<strong className="flex items-center">
This server is offline <X />
This server is offline
</strong>
<p>
This means that the server can{"'"}t loading some resources, like

@ -0,0 +1,174 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
"use client";
import { TrendingDown, TrendingUp } from "lucide-react";
import {
Bar,
BarChart,
CartesianGrid,
LabelList,
Rectangle,
XAxis,
YAxis,
} from "recharts";
import { useEffect, useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { Skeleton } from "../ui/skeleton";
import { getDailyData, getMonthlyData } from "@/lib/api";
import { useTrend } from "@/lib/hooks/use-daily-trend";
const chartConfig = {
result: {
label: "Players",
color: "hsl(var(--chart-3))",
},
} satisfies ChartConfig;
export function DailyChart({ server }: { server: string }) {
const [chartData, setChartData] = useState<{ day: string; result: number }[]>(
[],
);
const [loading, setLoading] = useState(true);
const { trend, percentage, success } = useTrend(chartData);
useEffect(() => {
getDailyData(server).then((c) => {
setChartData(c);
setLoading(false);
});
}, [server]);
if (loading) return <Skeleton className="w-full h-[437px]" />;
return (
<Card>
<CardHeader>
<CardTitle>Average daily players</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="">
<BarChart
accessibilityLayer
data={chartData}
layout="vertical"
margin={{
left: -20,
}}
>
<CartesianGrid horizontal={false} />
<XAxis type="number" dataKey="result" hide />
<YAxis
dataKey="day"
type="category"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey="result"
fill="var(--color-result)"
radius={5}
strokeWidth={2}
activeIndex={chartData.findIndex(
(c) =>
c.day ===
new Date().toLocaleDateString("en-US", { weekday: "long" }),
)}
activeBar={({ ...props }) => {
return (
<Rectangle
{...props}
fill="hsl(var(--chart-4))"
stroke={props.payload.fill}
strokeDasharray={4}
strokeDashoffset={4}
/>
);
}}
>
<LabelList
dataKey="result"
position="insideLeft"
offset={8}
className="fill-[--color-label]"
fontSize={12}
/>
</Bar>
</BarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-start gap-2 text-sm">
{success ? (
<div
className={
"flex gap-2 items-center font-medium leading-none " +
(trend === "up" ? "text-green-400" : "text-red-400")
}
>
Trending {trend} by {percentage}% today{" "}
{trend === "up" ? (
<TrendingUp className="h-4 w-4" />
) : (
<TrendingDown className="h-4 w-4" />
)}
</div>
) : (
<div className={"flex gap-2 items-center font-medium leading-none"}>
Trending up by 0% today{" "}
<span className="text-muted-foreground">(Insufficient data)</span>
</div>
)}
<div className="leading-none text-muted-foreground">
Showing an average of all data for {server}
</div>
</CardFooter>
</Card>
);
}

@ -0,0 +1,191 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
"use client"
import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
ChartConfig,
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const chartData = [
{ date: "2024-04-01", player_count: 91 },
{ date: "2024-04-02", player_count: 106 },
{ date: "2024-04-03", player_count: 104 },
{ date: "2024-04-04", player_count: 111 },
{ date: "2024-04-05", player_count: 113 },
{ date: "2024-04-06", player_count: 114 },
{ date: "2024-04-07", player_count: 108 },
{ date: "2024-04-08", player_count: 89 },
{ date: "2024-04-09", player_count: 96 },
{ date: "2024-04-10", player_count: 123 },
{ date: "2024-04-11", player_count: 120 },
{ date: "2024-04-12", player_count: 140 },
{ date: "2024-04-13", player_count: 128 },
{ date: "2024-04-14", player_count: 130 },
{ date: "2024-04-15", player_count: 114 },
{ date: "2024-04-16", player_count: 98 },
{ date: "2024-04-17", player_count: 102 },
{ date: "2024-04-18", player_count: 103 },
{ date: "2024-04-19", player_count: 102 },
{ date: "2024-04-20", player_count: 112},
{ date: "2024-04-21", player_count: 117 },
{ date: "2024-04-22", player_count: 119 },
{ date: "2024-04-23", player_count: 129 },
{ date: "2024-04-24", player_count: 121 },
{ date: "2024-04-25", player_count: 126 },
{ date: "2024-04-26", player_count: 98},
{ date: "2024-04-27", player_count: 102 },
{ date: "2024-04-28", player_count: 100 },
{ date: "2024-04-29", player_count: 101 },
{ date: "2024-04-30", player_count: 104 },
{ date: "2024-05-01", player_count: 109 },
{ date: "2024-05-02", player_count: 86 },
{ date: "2024-05-03", player_count: 93 },
{ date: "2024-05-04", player_count: 108 },
{ date: "2024-05-05", player_count: 112 },
{ date: "2024-05-06", player_count: 111 },
{ date: "2024-05-07", player_count: 96 },
{ date: "2024-05-08", player_count: 100 },
{ date: "2024-05-09", player_count: 124 },
{ date: "2024-05-10", player_count: 134 },
{ date: "2024-05-11", player_count: 144 },
{ date: "2024-05-12", player_count: 156 },
{ date: "2024-05-13", player_count: 180 },
{ date: "2024-05-14", player_count: 167 },
{ date: "2024-05-15", player_count: 154 },
{ date: "2024-05-16", player_count: 124 },
{ date: "2024-05-17", player_count: 112 },
{ date: "2024-05-18", player_count: 114 },
{ date: "2024-05-19", player_count: 121 },
{ date: "2024-05-20", player_count: 96 },
{ date: "2024-05-21", player_count: 102 },
{ date: "2024-05-22", player_count: 131 },
]
const chartConfig = {
player_count: {
label: "Players",
color: "hsl(var(--chart-1))",
}
} satisfies ChartConfig
export function ExampleChart() {
return (
<Card>
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
<div className="grid flex-1 gap-1 text-center sm:text-left">
<CardTitle className="text-sm">Player count over 3 months</CardTitle>
</div>
</CardHeader>
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
<ChartContainer
config={chartConfig}
className="aspect-auto h-[250px] w-full"
>
<AreaChart data={chartData}>
<defs>
<linearGradient id="fillPlayers" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-player_count)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="var(--color-player_count)"
stopOpacity={0.1}
/>
</linearGradient>
</defs>
<CartesianGrid vertical={false} />
<XAxis
dataKey="date"
tickLine={false}
axisLine={false}
tickMargin={8}
minTickGap={32}
tickFormatter={(value) => {
const date = new Date(value)
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
/>
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent
labelFormatter={(value) => {
return new Date(value).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
}}
indicator="dot"
/>
}
/>
<Area
dataKey="player_count"
type="natural"
fill="url(#fillPlayers)"
stroke="var(--color-player_count)"
stackId="a"
/>
<ChartLegend content={<ChartLegendContent />} />
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
)
}

@ -0,0 +1,160 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
"use client";
import { TrendingDown, TrendingUp } from "lucide-react";
import { Bar, BarChart, CartesianGrid, LabelList, Rectangle, XAxis, YAxis } from "recharts";
import { useEffect, useState } from "react";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { Skeleton } from "../ui/skeleton";
import { useTrend } from "@/lib/hooks/use-trend";
import { getMonthlyData } from "@/lib/api";
const chartConfig = {
result: {
label: "Players",
color: "hsl(var(--chart-3))",
},
} satisfies ChartConfig;
export function MonthlyChart({ server }: { server: string }) {
const [chartData, setChartData] = useState<
{ month: string; result: number }[]
>([]);
const [loading, setLoading] = useState(true);
const { trend, percentage, success } = useTrend(chartData);
useEffect(() => {
getMonthlyData(server).then((c) => {
setChartData(c);
setLoading(false);
});
}, [server]);
if (loading) return <Skeleton className="w-full h-[437px]" />;
return (
<Card>
<CardHeader>
<CardTitle>Average monthly players</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="">
<BarChart
accessibilityLayer
data={chartData}
layout="vertical"
margin={{
left: -20,
}}
>
<CartesianGrid horizontal={false} />
<XAxis type="number" dataKey="result" hide />
<YAxis
dataKey="month"
type="category"
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar dataKey="result" fill="var(--color-result)" radius={5} activeIndex={chartData.findIndex(
(c) =>
c.month ===
new Date().toLocaleDateString("en-US", { month: "long" }),
)}
activeBar={({ ...props }) => {
return (
<Rectangle
{...props}
fill="hsl(var(--chart-4))"
stroke={props.payload.fill}
strokeDasharray={4}
strokeDashoffset={4}
/>
);
}}>
<LabelList
dataKey="result"
position="insideLeft"
offset={8}
className="fill-[--color-label]"
fontSize={12}
/>
</Bar>
</BarChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col items-start gap-2 text-sm">
{success ? (
<div
className={
"flex gap-2 items-center font-medium leading-none " +
(trend === "up" ? "text-green-400" : "text-red-400")
}
>
Trending {trend} by {percentage}% this month{" "}
{trend === "up" ? (
<TrendingUp className="h-4 w-4" />
) : (
<TrendingDown className="h-4 w-4" />
)}
</div>
) : (
<div className={"flex gap-2 items-center font-medium leading-none"}>
Trending up by 0% this month{" "}
<span className="text-muted-foreground">(Insufficient data)</span>
</div>
)}
<div className="leading-none text-muted-foreground">
Showing an average of all data for {server}
</div>
</CardFooter>
</Card>
);
}

@ -134,7 +134,7 @@ export function NewChart({ server }: { server: string }) {
{chartConfig[activeChart].label} Chart for {server}
</CardTitle>
<CardDescription className="flex items-center">
Showing {filter !== "all" && <>the last</>}{" "}
Showing {filter !== "all" && "the last"}{" "}
<Select value={filter} onValueChange={setFilter}>
{" "}
<SelectTrigger className="max-w-[80px] mx-2">
@ -154,7 +154,7 @@ export function NewChart({ server }: { server: string }) {
</SelectGroup>
</SelectContent>
</Select>{" "}
{filter === "all" && <>of the</>} entries.
{filter === "all" && "of the"} entries.
</CardDescription>
</div>
<div className="flex">

@ -52,7 +52,7 @@ export function MiniJoinsChart({ server }: { server: string }) {
useEffectOnce(() => {
getShortTermData(server, ["player_count", "date"]).then((result) => {
setChartData(result.slice(-20));
setChartData(result.data.slice(-20));
setLoading(false);
});
});

@ -89,6 +89,28 @@ export async function getRelativeServerData(
return json.data;
}
export async function getMonthlyData(
server: string
): Promise<Array<{ month: string; result: number }>> {
const result = await fetch(
connector("/history/" + server + "/get-monthly-data", { version: 0 })
);
const json = await result.json();
return json.result;
}
export async function getDailyData(
server: string
): Promise<Array<{ day: string; result: number }>> {
const result = await fetch(
connector("/history/" + server + "/get-daily-data", { version: 0 })
);
const json = await result.json();
return json.result;
}
export async function getCommunityServerFavorites(
server: string
): Promise<number> {

@ -0,0 +1,80 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { useEffect, useState } from "react";
export function useTrend(data: { day: string; result: number }[]) {
const [success, setSuccess] = useState(true);
const [trend, setTrend] = useState<"up" | "down">("up");
const [percentage, setPercentage] = useState<number>(0);
useEffect(() => {
const today = new Date();
const previousDay = new Date();
previousDay.setDate(previousDay.getDate() - 1);
const previousDayData = data.find(
(x) => x.day === previousDay.toLocaleString('en-us', { weekday: 'long' }),
);
const todayData = data.find(
(x) => x.day === today.toLocaleString('en-us', { weekday: 'long' }),
);
if (previousDayData === undefined || todayData === undefined) {
setSuccess(false);
return;
}
if (previousDayData.result === 0) {
setSuccess(false);
return;
}
setSuccess(true);
setTrend(previousDayData.result < todayData.result ? "up" : "down");
setPercentage(
Math.abs(
Number(
(
((todayData.result - previousDayData.result) /
previousDayData.result) *
100
).toFixed(1),
),
),
);
}, [data]);
return {
success,
trend,
percentage,
};
}

@ -0,0 +1,95 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { useEffect, useState } from "react";
const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
export function useTrend(data: { month: string; result: number }[]) {
const [success, setSuccess] = useState(true);
const [trend, setTrend] = useState<"up" | "down">("up");
const [percentage, setPercentage] = useState<number>(0);
useEffect(() => {
const today = new Date();
const previousMonth = new Date();
previousMonth.setDate(0);
const previousMonthData = data.find(
(x) => x.month === months[previousMonth.getMonth()],
);
const todayMonthData = data.find(
(x) => x.month === months[today.getMonth()],
);
if (previousMonthData === undefined || todayMonthData === undefined) {
setSuccess(false);
return;
}
if (previousMonthData.result === 0) {
setSuccess(false);
return;
}
setSuccess(true);
setTrend(previousMonthData.result < todayMonthData.result ? "up" : "down");
setPercentage(
Math.abs(
Number(
(
((todayMonthData.result - previousMonthData.result) /
previousMonthData.result) *
100
).toFixed(1),
),
),
);
}, [data]);
return {
success,
trend,
percentage,
};
}

@ -50,7 +50,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("auth_codes");
const entry = await collection.findOne({ code });

@ -45,7 +45,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers");
res.send({ owned: (await collection.findOne({ server })) != undefined });

@ -57,7 +57,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers");
if ((await collection.findOne({ server: server })) == undefined) {

@ -56,7 +56,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers");
res.send({

@ -44,7 +44,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const users = db.collection("claimed-users");
const user = await (await clerkClient()).users.getUser(userId);

@ -56,7 +56,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers");
const customization = db.collection("customization");

@ -41,7 +41,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("achievements");
res.send({ result: await collection.find({ name: server }).toArray() });

@ -39,7 +39,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("customization");
res.send({ results: await collection.findOne({ server }) });

@ -86,7 +86,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("owned-servers");
const customizationColl = db.collection("customization");
if (!((await collection.findOne({ server, author: userId })) == undefined)) {

@ -44,7 +44,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("customization");
const results: { server: string; customization: any }[] = [];

@ -40,7 +40,7 @@ export default async function handler(
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("meta");
const find = await collection.find({ server: server }).toArray();

@ -46,7 +46,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray();

@ -45,7 +45,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray();
if (find.length == 0) res.send({ result: false });

@ -44,7 +44,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("favorites");
const find = await collection.find({ user: userId }).toArray();

@ -0,0 +1,62 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history");
const server = req.query.server as string;
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', '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) {
const averageNums = (results as any as {player_count: number}[]).map((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 undefined;
}));
client.close()
res.send({result: result.filter((c) => c !== undefined)});
}

@ -0,0 +1,62 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import { MongoClient } from "mongodb";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const client = new MongoClient(process.env.MONGO_DB as string);
const db = client.db("mhsf").collection("history");
const server = req.query.server as string;
const months = ['January', 'February', 'March', 'April', 'May', 'June', '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) {
const averageNums = (results as any as {player_count: number}[]).map((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 undefined;
}));
client.close()
res.send({result: result.filter((c) => c !== undefined)});
}

@ -38,7 +38,7 @@ export default async function handler(
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const db = client.db(process.env.CUSTOM_MONGO_DB ?? "mhsf");
const collection = db.collection("meta");
const all = await collection.find().toArray();