mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 17:44: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.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { 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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
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 db = client.db("mhsf").collection("history");
|
||||||
const server = req.query.server as string;
|
const server = req.query.server as string;
|
||||||
|
const daysOfWeek = [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
];
|
||||||
|
|
||||||
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
// Use MongoDB aggregation pipeline for better performance
|
||||||
const result = await Promise.all([1,2,3,4,5,6,7].map(async (c) => {
|
const dailyAverages = (await db
|
||||||
const results = await db.find({
|
.aggregate([
|
||||||
$and: [
|
{
|
||||||
{ server },
|
$match: { server },
|
||||||
{ $expr: { $eq: [{ $dayOfWeek: "$date" }, c] } }
|
},
|
||||||
]
|
{
|
||||||
}).toArray()
|
$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) {
|
res.send({ result: dailyAverages });
|
||||||
const averageNums = (results as any as {player_count: number}[]).map((x: {player_count: number}) => x.player_count)
|
} catch (error) {
|
||||||
const average = averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length;
|
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||||
|
} finally {
|
||||||
return { day: daysOfWeek[c - 1], result: Math.floor(average) };
|
await client.close();
|
||||||
}
|
}
|
||||||
return undefined;
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
client.close()
|
|
||||||
res.send({result: result.filter((c) => c !== undefined)});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -28,35 +28,64 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { 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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
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);
|
||||||
const db = client.db("mhsf").collection("historical");
|
|
||||||
const server = req.query.server as string;
|
|
||||||
const scopes: Array<string> = checkForInfoOrLeave(res, req.body.scopes);
|
|
||||||
|
|
||||||
const allData = await db.find({ server }).toArray();
|
try {
|
||||||
const data: any[] = [];
|
const db = client.db("mhsf").collection("historical");
|
||||||
|
const server = req.query.server as string;
|
||||||
|
const scopes: string[] = checkForInfoOrLeave(res, req.body.scopes);
|
||||||
|
|
||||||
allData.forEach((d) => {
|
// Only fetch the fields we need using projection
|
||||||
const result: any = {};
|
const projection: Record<string, 1> = { server: 1 };
|
||||||
scopes.forEach((b) => {
|
for (const scope of scopes) {
|
||||||
result[b] = d[b];
|
projection[scope] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
});
|
});
|
||||||
data.push(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.close();
|
res.send({ data });
|
||||||
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: any) {
|
function checkForInfoOrLeave(
|
||||||
if (info == undefined)
|
res: NextApiResponse,
|
||||||
|
info: string[] | undefined
|
||||||
|
): string[] {
|
||||||
|
if (info === undefined) {
|
||||||
res.status(400).json({ message: "Information wasn't supplied" });
|
res.status(400).json({ message: "Information wasn't supplied" });
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,35 +28,78 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { 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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
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 db = client.db("mhsf").collection("history");
|
||||||
const server = req.query.server as string;
|
const server = req.query.server as string;
|
||||||
|
const months = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
// Use MongoDB aggregation pipeline for better performance
|
||||||
const result = await Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(async (c) => {
|
const monthlyAverages = (await db
|
||||||
const results = await db.find({
|
.aggregate([
|
||||||
$and: [
|
{
|
||||||
{ server },
|
$match: {
|
||||||
{ date: { $gte: new Date(new Date().getFullYear(), c - 1, 1), $lt: new Date(new Date().getFullYear(), c, 1) } }
|
server,
|
||||||
]
|
date: {
|
||||||
}).toArray()
|
$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) {
|
res.send({ result: monthlyAverages });
|
||||||
const averageNums = (results as any as {player_count: number}[]).map((x: {player_count: number}) => x.player_count)
|
} catch (error) {
|
||||||
const average = averageNums.reduce((sum, val) => sum + val, 0) / averageNums.length;
|
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||||
|
} finally {
|
||||||
return { month: months[c - 1], result: Math.floor(average) };
|
await client.close();
|
||||||
}
|
}
|
||||||
return undefined;
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
client.close()
|
|
||||||
res.send({result: result.filter((c) => c !== undefined)});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -28,42 +28,92 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MongoClient } from "mongodb";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { 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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
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);
|
||||||
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();
|
try {
|
||||||
const data: any[] = [];
|
const db = client.db("mhsf").collection("history");
|
||||||
if (server === "peww") console.log(allData.slice(-30));
|
const mh = client.db("mhsf").collection("mh");
|
||||||
|
const server = req.query.server as string;
|
||||||
|
|
||||||
for (const d of allData.slice(-30)) {
|
// Get only the last 30 records with needed fields
|
||||||
const dateOfEntry = new Date(d.date);
|
const recentData = await db
|
||||||
const result = await mh
|
.find<ServerHistoryRecord>(
|
||||||
.find({
|
{ server },
|
||||||
date: {
|
{
|
||||||
$gte: new Date(dateOfEntry.getTime() - 1000 * 60 * 60),
|
projection: { player_count: 1, date: 1 },
|
||||||
$lt: new Date(dateOfEntry.getTime() + 1000 * 60 * 60),
|
sort: { date: -1 },
|
||||||
},
|
limit: 30,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
if (result.length > 0) {
|
const data: RelativeData[] = [];
|
||||||
const resultedData = result[0];
|
|
||||||
data.push({
|
|
||||||
relativePrecentage: d.player_count / resultedData.total_players,
|
|
||||||
date: dateOfEntry,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.close();
|
// Process in batches to reduce the number of database queries
|
||||||
res.send({ data });
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({ data });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ message: "An error occurred while fetching data" });
|
||||||
|
} finally {
|
||||||
|
await client.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,67 +1,67 @@
|
|||||||
/*
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
* MHSF, Minehut Server List
|
import { MongoClient as MongoClientImpl } from "mongodb";
|
||||||
* 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 { MongoClient } from "mongodb";
|
// Define types for our data
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
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(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
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);
|
||||||
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 allData = await db.find({ server }).toArray();
|
try {
|
||||||
const data: any[] = [];
|
const db = client.db("mhsf").collection("history");
|
||||||
|
const server = req.query.server as string;
|
||||||
|
const scopes: string[] = checkForInfoOrLeave(res, req.body.scopes);
|
||||||
|
|
||||||
dataMax = (
|
// Run these queries in parallel for better performance
|
||||||
await db.find({ server }).sort({ player_count: -1 }).limit(1).toArray()
|
const [allData, maxPlayerData] = await Promise.all([
|
||||||
)[0].player_count;
|
db.find<ServerHistoryRecord>({ server }).toArray(),
|
||||||
|
db
|
||||||
|
.find<ServerHistoryRecord>({ server })
|
||||||
|
.sort({ player_count: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.toArray(),
|
||||||
|
]);
|
||||||
|
|
||||||
allData.forEach((d) => {
|
const dataMax =
|
||||||
const result: any = {};
|
maxPlayerData.length > 0 ? maxPlayerData[0].player_count : 0;
|
||||||
scopes.forEach((b) => {
|
|
||||||
result[b] = d[b];
|
// 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;
|
||||||
});
|
});
|
||||||
data.push(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.close();
|
res.send({ data, dataMax });
|
||||||
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) {
|
function checkForInfoOrLeave(
|
||||||
if (info == undefined)
|
res: NextApiResponse,
|
||||||
|
info: string[] | undefined
|
||||||
|
): string[] {
|
||||||
|
if (info === undefined) {
|
||||||
res.status(400).json({ message: "Information wasn't supplied" });
|
res.status(400).json({ message: "Information wasn't supplied" });
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user