mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 17:54:59 -05:00
feat(www): make statistics faster
This commit is contained in:
parent
b36815f5f6
commit
35a0f6dca9
@ -28,35 +28,66 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||
|
||||
interface DailyAverage {
|
||||
day: string;
|
||||
result: number;
|
||||
}
|
||||
|
||||
interface ResponseData {
|
||||
result: DailyAverage[];
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<ResponseData | { message: string }>
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const client = new MongoClientImpl(process.env.MONGO_DB as string);
|
||||
|
||||
try {
|
||||
const db = client.db("mhsf").collection("history");
|
||||
const server = req.query.server as string;
|
||||
const daysOfWeek = [
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
];
|
||||
|
||||
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()
|
||||
// Use MongoDB aggregation pipeline for better performance
|
||||
const dailyAverages = (await db
|
||||
.aggregate([
|
||||
{
|
||||
$match: { server },
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: { $dayOfWeek: "$date" },
|
||||
averagePlayerCount: { $avg: "$player_count" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
day: { $arrayElemAt: [daysOfWeek, { $subtract: ["$_id", 1] }] },
|
||||
result: { $floor: "$averagePlayerCount" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: { _id: 1 },
|
||||
},
|
||||
])
|
||||
.toArray()) as DailyAverage[];
|
||||
|
||||
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) };
|
||||
res.send({ result: dailyAverages });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
return undefined;
|
||||
}));
|
||||
|
||||
client.close()
|
||||
res.send({result: result.filter((c) => c !== undefined)});
|
||||
}
|
||||
@ -28,35 +28,64 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||
|
||||
// Define types for our data
|
||||
interface ServerHistoricalRecord {
|
||||
server: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ResponseData {
|
||||
data: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<ResponseData | { message: string }>
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const client = new MongoClientImpl(process.env.MONGO_DB as string);
|
||||
|
||||
try {
|
||||
const db = client.db("mhsf").collection("historical");
|
||||
const server = req.query.server as string;
|
||||
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||
const scopes: string[] = checkForInfoOrLeave(res, req.body.scopes);
|
||||
|
||||
const allData = await db.find({ server }).toArray();
|
||||
const data: any[] = [];
|
||||
|
||||
allData.forEach((d) => {
|
||||
const result: any = {};
|
||||
scopes.forEach((b) => {
|
||||
result[b] = d[b];
|
||||
});
|
||||
data.push(result);
|
||||
});
|
||||
|
||||
client.close();
|
||||
res.send({ data });
|
||||
// Only fetch the fields we need using projection
|
||||
const projection: Record<string, 1> = { server: 1 };
|
||||
for (const scope of scopes) {
|
||||
projection[scope] = 1;
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
const allData = await db
|
||||
.find<ServerHistoricalRecord>({ server }, { projection })
|
||||
.toArray();
|
||||
|
||||
// Use map instead of forEach for better performance
|
||||
const data = allData.map((d) => {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const scope of scopes) {
|
||||
result[scope] = d[scope];
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
res.send({ data });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(
|
||||
res: NextApiResponse,
|
||||
info: string[] | undefined
|
||||
): string[] {
|
||||
if (info === undefined) {
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return [];
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -28,35 +28,78 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||
|
||||
interface MonthlyAverage {
|
||||
month: string;
|
||||
result: number;
|
||||
}
|
||||
|
||||
interface ResponseData {
|
||||
result: MonthlyAverage[];
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<ResponseData | { message: string }>
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const client = new MongoClientImpl(process.env.MONGO_DB as string);
|
||||
|
||||
try {
|
||||
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 currentYear = new Date().getFullYear();
|
||||
|
||||
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()
|
||||
// Use MongoDB aggregation pipeline for better performance
|
||||
const monthlyAverages = (await db
|
||||
.aggregate([
|
||||
{
|
||||
$match: {
|
||||
server,
|
||||
date: {
|
||||
$gte: new Date(currentYear, 0, 1),
|
||||
$lt: new Date(currentYear + 1, 0, 1),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: { $month: "$date" },
|
||||
averagePlayerCount: { $avg: "$player_count" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
month: { $arrayElemAt: [months, { $subtract: ["$_id", 1] }] },
|
||||
result: { $floor: "$averagePlayerCount" },
|
||||
},
|
||||
},
|
||||
{
|
||||
$sort: { _id: 1 },
|
||||
},
|
||||
])
|
||||
.toArray()) as MonthlyAverage[];
|
||||
|
||||
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) };
|
||||
res.send({ result: monthlyAverages });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
return undefined;
|
||||
}));
|
||||
|
||||
client.close()
|
||||
res.send({result: result.filter((c) => c !== undefined)});
|
||||
}
|
||||
@ -28,42 +28,92 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||
|
||||
interface ServerHistoryRecord {
|
||||
server: string;
|
||||
player_count: number;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
interface MHRecord {
|
||||
total_players: number;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
interface RelativeData {
|
||||
relativePrecentage: number;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
interface ResponseData {
|
||||
data: RelativeData[];
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<ResponseData | { message: string }>
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const client = new MongoClientImpl(process.env.MONGO_DB as string);
|
||||
|
||||
try {
|
||||
const db = client.db("mhsf").collection("history");
|
||||
const mh = client.db("mhsf").collection("mh");
|
||||
const server = req.query.server as string;
|
||||
|
||||
const allData = await db.find({ server }).toArray();
|
||||
const data: any[] = [];
|
||||
if (server === "peww") console.log(allData.slice(-30));
|
||||
|
||||
for (const d of allData.slice(-30)) {
|
||||
const dateOfEntry = new Date(d.date);
|
||||
const result = await mh
|
||||
.find({
|
||||
date: {
|
||||
$gte: new Date(dateOfEntry.getTime() - 1000 * 60 * 60),
|
||||
$lt: new Date(dateOfEntry.getTime() + 1000 * 60 * 60),
|
||||
},
|
||||
})
|
||||
// Get only the last 30 records with needed fields
|
||||
const recentData = await db
|
||||
.find<ServerHistoryRecord>(
|
||||
{ server },
|
||||
{
|
||||
projection: { player_count: 1, date: 1 },
|
||||
sort: { date: -1 },
|
||||
limit: 30,
|
||||
}
|
||||
)
|
||||
.toArray();
|
||||
|
||||
if (result.length > 0) {
|
||||
const resultedData = result[0];
|
||||
data.push({
|
||||
relativePrecentage: d.player_count / resultedData.total_players,
|
||||
const data: RelativeData[] = [];
|
||||
|
||||
// Process in batches to reduce the number of database queries
|
||||
const batchSize = 5;
|
||||
for (let i = 0; i < recentData.length; i += batchSize) {
|
||||
const batch = recentData.slice(i, i + batchSize);
|
||||
const batchQueries = batch.map(async (d) => {
|
||||
const dateOfEntry = new Date(d.date);
|
||||
const hourBefore = new Date(dateOfEntry.getTime() - 1000 * 60 * 60);
|
||||
const hourAfter = new Date(dateOfEntry.getTime() + 1000 * 60 * 60);
|
||||
|
||||
const result = await mh.findOne<MHRecord>(
|
||||
{
|
||||
date: {
|
||||
$gte: hourBefore,
|
||||
$lt: hourAfter,
|
||||
},
|
||||
},
|
||||
{ projection: { total_players: 1, date: 1 } }
|
||||
);
|
||||
|
||||
if (result) {
|
||||
return {
|
||||
relativePrecentage: d.player_count / result.total_players,
|
||||
date: dateOfEntry,
|
||||
});
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const batchResults = await Promise.all(batchQueries);
|
||||
data.push(
|
||||
...batchResults.filter((item): item is RelativeData => item !== null)
|
||||
);
|
||||
}
|
||||
|
||||
client.close();
|
||||
res.send({ data });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,67 +1,67 @@
|
||||
/*
|
||||
* 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) 2025 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 type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
// Define types for our data
|
||||
interface ServerHistoryRecord {
|
||||
server: string;
|
||||
player_count: number;
|
||||
date: Date;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ResponseData {
|
||||
data: Record<string, unknown>[];
|
||||
dataMax: number;
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
res: NextApiResponse<ResponseData | { message: string }>
|
||||
) {
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
const client = new MongoClientImpl(process.env.MONGO_DB as string);
|
||||
|
||||
try {
|
||||
const db = client.db("mhsf").collection("history");
|
||||
const server = req.query.server as string;
|
||||
let dataMax = 0;
|
||||
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
||||
const scopes: string[] = checkForInfoOrLeave(res, req.body.scopes);
|
||||
|
||||
const allData = await db.find({ server }).toArray();
|
||||
const data: any[] = [];
|
||||
// Run these queries in parallel for better performance
|
||||
const [allData, maxPlayerData] = await Promise.all([
|
||||
db.find<ServerHistoryRecord>({ server }).toArray(),
|
||||
db
|
||||
.find<ServerHistoryRecord>({ server })
|
||||
.sort({ player_count: -1 })
|
||||
.limit(1)
|
||||
.toArray(),
|
||||
]);
|
||||
|
||||
dataMax = (
|
||||
await db.find({ server }).sort({ player_count: -1 }).limit(1).toArray()
|
||||
)[0].player_count;
|
||||
const dataMax =
|
||||
maxPlayerData.length > 0 ? maxPlayerData[0].player_count : 0;
|
||||
|
||||
allData.forEach((d) => {
|
||||
const result: any = {};
|
||||
scopes.forEach((b) => {
|
||||
result[b] = d[b];
|
||||
});
|
||||
data.push(result);
|
||||
// Use map instead of forEach for better performance
|
||||
const data = allData.map((d) => {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const scope of scopes) {
|
||||
result[scope] = d[scope];
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
client.close();
|
||||
res.send({ data, dataMax });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
|
||||
function checkForInfoOrLeave(res: NextApiResponse, info: any) {
|
||||
if (info == undefined)
|
||||
function checkForInfoOrLeave(
|
||||
res: NextApiResponse,
|
||||
info: string[] | undefined
|
||||
): string[] {
|
||||
if (info === undefined) {
|
||||
res.status(400).json({ message: "Information wasn't supplied" });
|
||||
return [];
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user