2024-10-08 14:32:02 +00:00
|
|
|
import { Elysia } from "elysia";
|
|
|
|
import cors from "@elysiajs/cors";
|
|
|
|
import { decorators } from "elysia-decorators";
|
|
|
|
import { logger } from "@tqman/nice-logger";
|
2024-10-09 00:17:00 +00:00
|
|
|
import { swagger } from "@elysiajs/swagger";
|
|
|
|
import { helmet } from "elysia-helmet";
|
|
|
|
import { etag } from "@bogeychan/elysia-etag";
|
2024-10-08 15:36:52 +00:00
|
|
|
import AppController from "./controller/app.controller";
|
2024-10-09 00:17:00 +00:00
|
|
|
import * as dotenv from "@dotenvx/dotenvx";
|
|
|
|
import mongoose from "mongoose";
|
|
|
|
import PlayerController from "./controller/player.controller";
|
|
|
|
import { PlayerService } from "./service/player.service";
|
2024-10-10 00:22:09 +00:00
|
|
|
import { cron } from "@elysiajs/cron";
|
2024-10-25 16:37:56 +00:00
|
|
|
import { isProduction } from "@ssr/common/utils/utils";
|
2024-10-15 18:31:50 +00:00
|
|
|
import ImageController from "./controller/image.controller";
|
2024-10-16 06:31:52 +00:00
|
|
|
import { ScoreService } from "./service/score.service";
|
2024-10-17 06:12:03 +00:00
|
|
|
import { Config } from "@ssr/common/config";
|
2024-10-17 14:30:14 +00:00
|
|
|
import ScoresController from "./controller/scores.controller";
|
|
|
|
import LeaderboardController from "./controller/leaderboard.controller";
|
2024-10-19 06:15:28 +00:00
|
|
|
import { getAppVersion } from "./common/app.util";
|
2024-10-22 14:59:41 +00:00
|
|
|
import { connectScoresaberWebsocket } from "@ssr/common/websocket/scoresaber-websocket";
|
|
|
|
import { connectBeatLeaderWebsocket } from "@ssr/common/websocket/beatleader-websocket";
|
2024-10-22 16:32:09 +00:00
|
|
|
import { DiscordChannels, initDiscordBot, logToChannel } from "./bot/bot";
|
|
|
|
import { EmbedBuilder } from "discord.js";
|
2024-10-08 14:32:02 +00:00
|
|
|
|
2024-10-09 00:17:00 +00:00
|
|
|
// Load .env file
|
|
|
|
dotenv.config({
|
2024-10-13 00:14:51 +00:00
|
|
|
logLevel: (await Bun.file(".env").exists()) ? "success" : "warn",
|
2024-10-09 00:17:00 +00:00
|
|
|
path: ".env",
|
|
|
|
override: true,
|
|
|
|
});
|
|
|
|
|
2024-10-23 19:20:57 +00:00
|
|
|
// Connect to Mongo
|
2024-10-09 00:17:00 +00:00
|
|
|
await mongoose.connect(Config.mongoUri!); // Connect to MongoDB
|
2024-10-08 14:32:02 +00:00
|
|
|
|
2024-10-23 19:20:57 +00:00
|
|
|
// Connect to websockets
|
2024-10-22 14:59:41 +00:00
|
|
|
connectScoresaberWebsocket({
|
|
|
|
onScore: async score => {
|
2024-10-25 16:37:56 +00:00
|
|
|
await ScoreService.trackScoreSaberScore(score.score, score.leaderboard);
|
|
|
|
await ScoreService.updatePlayerScoresSet(score);
|
|
|
|
|
2024-10-22 14:59:41 +00:00
|
|
|
await ScoreService.notifyNumberOne(score);
|
2024-10-15 03:09:47 +00:00
|
|
|
},
|
2024-10-22 16:32:09 +00:00
|
|
|
onDisconnect: async error => {
|
|
|
|
await logToChannel(
|
|
|
|
DiscordChannels.backendLogs,
|
|
|
|
new EmbedBuilder().setDescription(`ScoreSaber websocket disconnected: ${JSON.stringify(error)}`)
|
|
|
|
);
|
|
|
|
},
|
2024-10-22 14:59:41 +00:00
|
|
|
});
|
|
|
|
connectBeatLeaderWebsocket({
|
|
|
|
onScore: async score => {
|
|
|
|
await ScoreService.trackBeatLeaderScore(score);
|
2024-10-17 17:29:30 +00:00
|
|
|
},
|
2024-10-22 16:32:09 +00:00
|
|
|
onDisconnect: async error => {
|
|
|
|
await logToChannel(
|
|
|
|
DiscordChannels.backendLogs,
|
|
|
|
new EmbedBuilder().setDescription(`BeatLeader websocket disconnected: ${JSON.stringify(error)}`)
|
|
|
|
);
|
|
|
|
},
|
2024-10-15 03:09:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
export const app = new Elysia();
|
2024-10-10 00:22:09 +00:00
|
|
|
app.use(
|
|
|
|
cron({
|
|
|
|
name: "player-statistics-tracker-cron",
|
2024-10-13 00:14:51 +00:00
|
|
|
pattern: "1 0 * * *", // Every day at 00:01
|
|
|
|
timezone: "Europe/London", // UTC time
|
2024-10-10 00:22:09 +00:00
|
|
|
run: async () => {
|
2024-10-25 16:37:56 +00:00
|
|
|
await PlayerService.updatePlayerStatistics();
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
app.use(
|
|
|
|
cron({
|
|
|
|
name: "scores-background-refresh",
|
|
|
|
pattern: "0 4 * * *", // Every day at 04:00
|
|
|
|
timezone: "Europe/London", // UTC time
|
|
|
|
protect: true,
|
|
|
|
run: async () => {
|
|
|
|
await PlayerService.refreshPlayerScores();
|
2024-10-10 00:22:09 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2024-10-08 14:32:02 +00:00
|
|
|
/**
|
|
|
|
* Custom error handler
|
|
|
|
*/
|
|
|
|
app.onError({ as: "global" }, ({ code, error }) => {
|
|
|
|
// Return default error for type validation
|
|
|
|
if (code === "VALIDATION") {
|
|
|
|
return error.all;
|
|
|
|
}
|
|
|
|
|
2024-10-16 06:47:52 +00:00
|
|
|
const status = "status" in error ? error.status : undefined;
|
2024-10-08 14:32:02 +00:00
|
|
|
return {
|
|
|
|
...((status && { statusCode: status }) || { status: code }),
|
|
|
|
...(error.message != code && { message: error.message }),
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-10-08 15:36:52 +00:00
|
|
|
/**
|
|
|
|
* Enable E-Tags
|
|
|
|
*/
|
|
|
|
app.use(etag());
|
|
|
|
|
2024-10-08 14:32:02 +00:00
|
|
|
/**
|
|
|
|
* Enable CORS
|
|
|
|
*/
|
|
|
|
app.use(cors());
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request logger
|
|
|
|
*/
|
|
|
|
app.use(
|
|
|
|
logger({
|
2024-10-16 10:27:04 +00:00
|
|
|
enabled: true,
|
2024-10-08 14:32:02 +00:00
|
|
|
mode: "combined",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2024-10-08 15:36:52 +00:00
|
|
|
/**
|
|
|
|
* Security settings
|
|
|
|
*/
|
2024-10-09 00:17:00 +00:00
|
|
|
app.use(
|
|
|
|
helmet({
|
|
|
|
hsts: false, // Disable HSTS
|
|
|
|
contentSecurityPolicy: false, // Disable CSP
|
|
|
|
dnsPrefetchControl: true, // Enable DNS prefetch
|
|
|
|
})
|
|
|
|
);
|
2024-10-08 15:36:52 +00:00
|
|
|
|
2024-10-08 14:32:02 +00:00
|
|
|
/**
|
|
|
|
* Controllers
|
|
|
|
*/
|
|
|
|
app.use(
|
|
|
|
decorators({
|
2024-10-19 16:51:24 +00:00
|
|
|
controllers: [AppController, PlayerController, ImageController, ScoresController, LeaderboardController],
|
2024-10-08 14:32:02 +00:00
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2024-10-08 15:36:52 +00:00
|
|
|
/**
|
|
|
|
* Swagger Documentation
|
|
|
|
*/
|
2024-10-19 05:56:07 +00:00
|
|
|
app.use(
|
|
|
|
swagger({
|
2024-10-19 06:15:28 +00:00
|
|
|
documentation: {
|
|
|
|
info: {
|
|
|
|
title: "ScoreSaber Reloaded Documentation",
|
|
|
|
version: await getAppVersion(),
|
|
|
|
},
|
|
|
|
},
|
2024-10-23 15:19:29 +00:00
|
|
|
scalarConfig: {
|
|
|
|
servers: [
|
|
|
|
{
|
|
|
|
url: "https://ssr.fascinated.cc/api",
|
|
|
|
description: "Production server",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
2024-10-19 05:56:07 +00:00
|
|
|
})
|
|
|
|
);
|
2024-10-08 15:36:52 +00:00
|
|
|
|
2024-10-08 14:32:02 +00:00
|
|
|
app.onStart(() => {
|
|
|
|
console.log("Listening on port http://localhost:8080");
|
2024-10-17 17:29:30 +00:00
|
|
|
if (isProduction()) {
|
|
|
|
initDiscordBot();
|
|
|
|
}
|
2024-10-08 14:32:02 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
app.listen(8080);
|