forked from OpenWF/SpaceNinjaServer
feat: daily race leaderboards
This commit is contained in:
parent
0085c20e11
commit
cc3880816a
15
src/controllers/stats/leaderboardController.ts
Normal file
15
src/controllers/stats/leaderboardController.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { getLeaderboard } from "@/src/services/leaderboardService";
|
||||||
|
import { RequestHandler } from "express";
|
||||||
|
|
||||||
|
export const leaderboardController: RequestHandler = async (req, res) => {
|
||||||
|
const payload = JSON.parse(String(req.body)) as ILeaderboardRequest;
|
||||||
|
res.json({
|
||||||
|
results: await getLeaderboard(payload.field, payload.before, payload.after)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ILeaderboardRequest {
|
||||||
|
field: string;
|
||||||
|
before: number;
|
||||||
|
after: number;
|
||||||
|
}
|
17
src/models/leaderboardModel.ts
Normal file
17
src/models/leaderboardModel.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { model, Schema } from "mongoose";
|
||||||
|
import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes";
|
||||||
|
|
||||||
|
const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
|
||||||
|
{
|
||||||
|
leaderboard: { type: String, required: true },
|
||||||
|
displayName: { type: String, required: true },
|
||||||
|
score: { type: Number, required: true },
|
||||||
|
expiry: { type: Date, required: true }
|
||||||
|
},
|
||||||
|
{ id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
leaderboardEntrySchema.index({ leaderboard: 1 });
|
||||||
|
leaderboardEntrySchema.index({ expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries.
|
||||||
|
|
||||||
|
export const Leaderboard = model<ILeaderboardEntryDatabase>("Leaderboard", leaderboardEntrySchema);
|
@ -1,11 +1,12 @@
|
|||||||
import { viewController } from "../controllers/stats/viewController";
|
|
||||||
import { uploadController } from "@/src/controllers/stats/uploadController";
|
|
||||||
|
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
import { viewController } from "@/src/controllers/stats/viewController";
|
||||||
|
import { uploadController } from "@/src/controllers/stats/uploadController";
|
||||||
|
import { leaderboardController } from "@/src/controllers/stats/leaderboardController";
|
||||||
|
|
||||||
const statsRouter = express.Router();
|
const statsRouter = express.Router();
|
||||||
|
|
||||||
statsRouter.get("/view.php", viewController);
|
statsRouter.get("/view.php", viewController);
|
||||||
statsRouter.post("/upload.php", uploadController);
|
statsRouter.post("/upload.php", uploadController);
|
||||||
|
statsRouter.post("/leaderboardWeekly.php", leaderboardController);
|
||||||
|
|
||||||
export { statsRouter };
|
export { statsRouter };
|
||||||
|
49
src/services/leaderboardService.ts
Normal file
49
src/services/leaderboardService.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { toOid } from "../helpers/inventoryHelpers";
|
||||||
|
import { Leaderboard } from "../models/leaderboardModel";
|
||||||
|
import { ILeaderboardEntryClient } from "../types/leaderboardTypes";
|
||||||
|
|
||||||
|
export const submitLeaderboardScore = async (
|
||||||
|
leaderboard: string,
|
||||||
|
displayName: string,
|
||||||
|
score: number
|
||||||
|
): Promise<void> => {
|
||||||
|
const schedule = leaderboard.split(".")[0] as "daily" | "weekly";
|
||||||
|
let expiry: Date;
|
||||||
|
if (schedule == "daily") {
|
||||||
|
expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000);
|
||||||
|
} else {
|
||||||
|
const EPOCH = 1734307200 * 1000; // Monday
|
||||||
|
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
||||||
|
const week = Math.trunc(day / 7);
|
||||||
|
const weekStart = EPOCH + week * 604800000;
|
||||||
|
const weekEnd = weekStart + 604800000;
|
||||||
|
expiry = new Date(weekEnd);
|
||||||
|
}
|
||||||
|
await Leaderboard.findOneAndUpdate(
|
||||||
|
{ leaderboard, displayName },
|
||||||
|
{ $max: { score }, $set: { expiry } },
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLeaderboard = async (
|
||||||
|
leaderboard: string,
|
||||||
|
before: number,
|
||||||
|
after: number
|
||||||
|
): Promise<ILeaderboardEntryClient[]> => {
|
||||||
|
const entries = await Leaderboard.find({ leaderboard })
|
||||||
|
.sort({ score: -1 })
|
||||||
|
.skip(before)
|
||||||
|
.limit(after - before);
|
||||||
|
const res: ILeaderboardEntryClient[] = [];
|
||||||
|
let r = before;
|
||||||
|
for (const entry of entries) {
|
||||||
|
res.push({
|
||||||
|
_id: toOid(entry._id),
|
||||||
|
s: entry.score,
|
||||||
|
r: ++r,
|
||||||
|
n: entry.displayName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from "@/src/types/statTypes";
|
} from "@/src/types/statTypes";
|
||||||
import { logger } from "@/src/utils/logger";
|
import { logger } from "@/src/utils/logger";
|
||||||
import { addEmailItem, getInventory } from "@/src/services/inventoryService";
|
import { addEmailItem, getInventory } from "@/src/services/inventoryService";
|
||||||
|
import { submitLeaderboardScore } from "./leaderboardService";
|
||||||
|
|
||||||
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
|
export const createStats = async (accountId: string): Promise<TStatsDatabaseDocument> => {
|
||||||
const stats = new Stats({ accountOwnerId: accountId });
|
const stats = new Stats({ accountOwnerId: accountId });
|
||||||
@ -301,6 +302,8 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
|
|||||||
} else {
|
} else {
|
||||||
playerStats.Races.set(race, { highScore });
|
playerStats.Races.set(race, { highScore });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await submitLeaderboardScore("daily.accounts." + race, payload.displayName, highScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
15
src/types/leaderboardTypes.ts
Normal file
15
src/types/leaderboardTypes.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { IOid } from "./commonTypes";
|
||||||
|
|
||||||
|
export interface ILeaderboardEntryDatabase {
|
||||||
|
leaderboard: string;
|
||||||
|
displayName: string;
|
||||||
|
score: number;
|
||||||
|
expiry: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILeaderboardEntryClient {
|
||||||
|
_id: IOid;
|
||||||
|
s: number; // score
|
||||||
|
r: number; // rank
|
||||||
|
n: string; // displayName
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user