mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 15:44:58 -05:00
fix: issue
This commit is contained in:
parent
fcb4221fca
commit
270f6efaa6
@ -42,6 +42,7 @@ We use [Atlas](https://www.mongodb.com/atlas) to host our MongoDB database, but
|
|||||||
```dotenv
|
```dotenv
|
||||||
MONGO_DB="mongodb+srv://..."
|
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)
|
## Smaller things (for production-ready servers)
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,8 @@ import TabServer from "@/components/misc/TabServer";
|
|||||||
import type { Metadata, ResolvingMetadata } from "next";
|
import type { Metadata, ResolvingMetadata } from "next";
|
||||||
import StickyTopbar from "@/components/misc/StickyTopbar";
|
import StickyTopbar from "@/components/misc/StickyTopbar";
|
||||||
import { RelativeChart } from "@/components/charts/RelativeChart";
|
import { RelativeChart } from "@/components/charts/RelativeChart";
|
||||||
|
import { MonthlyChart } from "@/components/charts/MonthlyChart";
|
||||||
|
import { DailyChart } from "@/components/charts/DailyChart";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: { server: string };
|
params: { server: string };
|
||||||
@ -113,6 +115,11 @@ export default function ServerPage({ params }: { params: { server: string } }) {
|
|||||||
<NewChart server={params.server} />
|
<NewChart server={params.server} />
|
||||||
<br />
|
<br />
|
||||||
<RelativeChart server={params.server} />
|
<RelativeChart server={params.server} />
|
||||||
|
<br />
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<MonthlyChart server={params.server} />
|
||||||
|
<DailyChart server={params.server} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ColorProvider>
|
</ColorProvider>
|
||||||
|
|||||||
@ -117,6 +117,7 @@ import { LoadingSpinner } from "./ui/loading-spinner";
|
|||||||
import StickyTopbar from "./misc/StickyTopbar";
|
import StickyTopbar from "./misc/StickyTopbar";
|
||||||
import { HoverCard } from "@radix-ui/react-hover-card";
|
import { HoverCard } from "@radix-ui/react-hover-card";
|
||||||
import { HoverCardTrigger } from "./ui/hover-card";
|
import { HoverCardTrigger } from "./ui/hover-card";
|
||||||
|
import { ExampleChart } from "./charts/ExampleChart";
|
||||||
|
|
||||||
// ag-grid
|
// ag-grid
|
||||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||||
@ -451,13 +452,22 @@ export default function ServerList() {
|
|||||||
and descriptions, making your server stand out with information
|
and descriptions, making your server stand out with information
|
||||||
that can be shown to players.
|
that can be shown to players.
|
||||||
</p>
|
</p>
|
||||||
<BentoGrid className="max-h-[100px]">
|
<BentoGrid className="max-h-[100px] mb-[300px]">
|
||||||
{features.map((feature, idx) => (
|
{features.map((feature, idx) => (
|
||||||
<BentoCard key={idx} {...feature} />
|
<BentoCard key={idx} {...feature} />
|
||||||
))}
|
))}
|
||||||
</BentoGrid>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@ -121,12 +121,13 @@ export default function ServerView(props: { server: string }) {
|
|||||||
<>
|
<>
|
||||||
{single.grabOnline() == undefined && !single.grabOffline()?.online && (
|
{single.grabOnline() == undefined && !single.grabOffline()?.online && (
|
||||||
<div className="grid pl-4 pr-4">
|
<div className="grid pl-4 pr-4">
|
||||||
|
<X />
|
||||||
<div
|
<div
|
||||||
className=" rounded p-2"
|
className=" rounded p-2"
|
||||||
style={{ backgroundColor: "rgba(244, 63, 94, .16)" }}
|
style={{ backgroundColor: "rgba(244, 63, 94, .16)" }}
|
||||||
>
|
>
|
||||||
<strong className="flex items-center">
|
<strong className="flex items-center">
|
||||||
This server is offline <X />
|
This server is offline
|
||||||
</strong>
|
</strong>
|
||||||
<p>
|
<p>
|
||||||
This means that the server can{"'"}t loading some resources, like
|
This means that the server can{"'"}t loading some resources, like
|
||||||
|
|||||||
174
src/components/charts/DailyChart.tsx
Normal file
174
src/components/charts/DailyChart.tsx
Normal file
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
191
src/components/charts/ExampleChart.tsx
Normal file
191
src/components/charts/ExampleChart.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
160
src/components/charts/MonthlyChart.tsx
Normal file
160
src/components/charts/MonthlyChart.tsx
Normal file
@ -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}
|
{chartConfig[activeChart].label} Chart for {server}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="flex items-center">
|
<CardDescription className="flex items-center">
|
||||||
Showing {filter !== "all" && <>the last</>}{" "}
|
Showing {filter !== "all" && "the last"}{" "}
|
||||||
<Select value={filter} onValueChange={setFilter}>
|
<Select value={filter} onValueChange={setFilter}>
|
||||||
{" "}
|
{" "}
|
||||||
<SelectTrigger className="max-w-[80px] mx-2">
|
<SelectTrigger className="max-w-[80px] mx-2">
|
||||||
@ -154,7 +154,7 @@ export function NewChart({ server }: { server: string }) {
|
|||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>{" "}
|
</Select>{" "}
|
||||||
{filter === "all" && <>of the</>} entries.
|
{filter === "all" && "of the"} entries.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export function MiniJoinsChart({ server }: { server: string }) {
|
|||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
getShortTermData(server, ["player_count", "date"]).then((result) => {
|
getShortTermData(server, ["player_count", "date"]).then((result) => {
|
||||||
setChartData(result.slice(-20));
|
setChartData(result.data.slice(-20));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -89,6 +89,28 @@ export async function getRelativeServerData(
|
|||||||
return json.data;
|
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(
|
export async function getCommunityServerFavorites(
|
||||||
server: string
|
server: string
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
|||||||
80
src/lib/hooks/use-daily-trend.tsx
Normal file
80
src/lib/hooks/use-daily-trend.tsx
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
95
src/lib/hooks/use-trend.tsx
Normal file
95
src/lib/hooks/use-trend.tsx
Normal file
@ -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);
|
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(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 });
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default async function handler(
|
|||||||
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(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 });
|
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);
|
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(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) {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default async function handler(
|
|||||||
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(process.env.CUSTOM_MONGO_DB ?? "mhsf");
|
||||||
const collection = db.collection("owned-servers");
|
const collection = db.collection("owned-servers");
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default async function handler(
|
|||||||
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(process.env.CUSTOM_MONGO_DB ?? "mhsf");
|
||||||
const users = db.collection("claimed-users");
|
const users = db.collection("claimed-users");
|
||||||
const user = await (await clerkClient()).users.getUser(userId);
|
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);
|
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(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");
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export default async function handler(
|
|||||||
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(process.env.CUSTOM_MONGO_DB ?? "mhsf");
|
||||||
const collection = db.collection("achievements");
|
const collection = db.collection("achievements");
|
||||||
|
|
||||||
res.send({ result: await collection.find({ name: server }).toArray() });
|
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);
|
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(process.env.CUSTOM_MONGO_DB ?? "mhsf");
|
||||||
const collection = db.collection("customization");
|
const collection = db.collection("customization");
|
||||||
|
|
||||||
res.send({ results: await collection.findOne({ server }) });
|
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);
|
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(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)) {
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export default async function handler(
|
|||||||
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(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 }[] = [];
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export default async function handler(
|
|||||||
|
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|
||||||
const db = client.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();
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export default async function handler(
|
|||||||
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(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();
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default async function handler(
|
|||||||
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(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 });
|
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);
|
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(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();
|
||||||
|
|
||||||
|
|||||||
62
src/pages/api/v0/history/[server]/get-daily-data.ts
Normal file
62
src/pages/api/v0/history/[server]/get-daily-data.ts
Normal file
@ -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)});
|
||||||
|
}
|
||||||
62
src/pages/api/v0/history/[server]/get-monthly-data.ts
Normal file
62
src/pages/api/v0/history/[server]/get-monthly-data.ts
Normal file
@ -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);
|
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(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();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user