diff --git a/server/src/server/router/ir/usc/_playtype/router.ts b/server/src/server/router/ir/usc/_playtype/router.ts index 0e3cc5681..6802b0dad 100644 --- a/server/src/server/router/ir/usc/_playtype/router.ts +++ b/server/src/server/router/ir/usc/_playtype/router.ts @@ -43,7 +43,7 @@ enum STATUS_CODES { const ValidateUSCRequest: RequestHandler = async (req, res, next) => { const token = req.header("Authorization"); - if (!token) { + if (token === undefined) { return res.status(200).json({ statusCode: STATUS_CODES.BAD_REQ, description: "No auth token provided.", @@ -224,7 +224,7 @@ router.get("/charts/:chartHash/leaderboard", RetrieveChart, async (req, res) => try { n = AssertStrAsPositiveNonZeroInt(req.query.n, "Invalid 'N' param."); - } catch (err) { + } catch (_err) { return res.status(200).json({ statusCode: STATUS_CODES.BAD_REQ, description: `Invalid 'n' param - expected a positive non-zero integer less than or equal to ${USCIR_MAX_LEADERBOARD_N}.`, @@ -265,7 +265,7 @@ router.get("/charts/:chartHash/leaderboard", RetrieveChart, async (req, res) => }); }); -const PR_USCIRChartDoc = { +const PR_USCIR_CHART_DOC = { chartHash: "string", artist: "string", title: "string", @@ -286,7 +286,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => { const chartErr = p( req.body.chart, - PR_USCIRChartDoc, + PR_USCIR_CHART_DOC, {}, { throwOnNonObject: false, @@ -333,14 +333,19 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => { // If the import failed, AND the import failure WAS NOT that the chart didnt exist // report that error instead. if (importDoc.errors[0]?.type && importDoc.errors[0].type !== "KTDataNotFound") { - logger.info(`USC Import Failed ${importDoc.errors[0].message}`, { - importDoc, - userID, - }); + logger.info( + `USC Import Failed ${importDoc.errors[0].message ?? "with null error message?"}`, + { + importDoc, + userID, + } + ); return res.status(200).json({ statusCode: STATUS_CODES.BAD_REQ, - description: `${importDoc.errors[0].type} ${importDoc.errors[0].message}`, + description: `${importDoc.errors[0].type} ${ + importDoc.errors[0].message ?? "No Error Message" + }`, }); } @@ -355,7 +360,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => { } // If the chartDoc exists, any error is a failure here. - if (importDoc.errors[0]) { + if (importDoc.errors.length !== 0) { logger.info(`USC Import Failed ${importDoc.errors[0].message}`, { importDoc, userID, @@ -382,7 +387,7 @@ router.post("/scores", RequirePermissions("submit_score"), async (req, res) => { description: "Successfully imported score.", body, }); - } catch (err) { + } catch (_err) { return res.status(200).json({ statusCode: STATUS_CODES.SERVER_ERROR, description: "An internal server error has occured.", diff --git a/server/src/server/router/ir/usc/_playtype/types.ts b/server/src/server/router/ir/usc/_playtype/types.ts index 708dd173a..7ccab5052 100644 --- a/server/src/server/router/ir/usc/_playtype/types.ts +++ b/server/src/server/router/ir/usc/_playtype/types.ts @@ -28,7 +28,9 @@ export interface USCClientScore { gaugeOpt: integer; mirror: boolean; random: boolean; - autoFlags: integer; // ??? + + // ??? - Not sure what these are. + autoFlags: integer; }; windows: { perfect: number; diff --git a/server/src/server/router/ir/usc/_playtype/usc.ts b/server/src/server/router/ir/usc/_playtype/usc.ts index c8fe723a5..0e8ef536b 100644 --- a/server/src/server/router/ir/usc/_playtype/usc.ts +++ b/server/src/server/router/ir/usc/_playtype/usc.ts @@ -169,7 +169,7 @@ export async function CreatePOSTScoresResponseBody( const originalScore = (await db.scores.findOne({ scoreID, - })) as ScoreDocument<"usc:Controller" | "usc:Keyboard">; + })) as ScoreDocument<"usc:Controller" | "usc:Keyboard"> | null; if (!originalScore) { logger.severe( diff --git a/server/src/server/server.ts b/server/src/server/server.ts index 09ba55b80..58527cb00 100644 --- a/server/src/server/server.ts +++ b/server/src/server/server.ts @@ -1,16 +1,17 @@ +import { RequestLoggerMiddleware } from "./middleware/request-logger"; +import mainRouter from "./router/router"; import connectRedis from "connect-redis"; import express from "express"; -import type { Express } from "express"; -import "express-async-errors"; import expressSession from "express-session"; import { RedisClient } from "external/redis/redis"; import helmet from "helmet"; import { SYMBOL_TACHI_API_AUTH } from "lib/constants/tachi"; import CreateLogCtx from "lib/logger/logger"; import { Environment, ServerConfig, TachiConfig } from "lib/setup/config"; +import type { Express } from "express"; +import "express-async-errors"; import type { integer } from "tachi-common"; -import { RequestLoggerMiddleware } from "./middleware/request-logger"; -import mainRouter from "./router/router"; +import { IsNonEmptyString } from "utils/misc"; const logger = CreateLogCtx(__filename); @@ -39,13 +40,15 @@ const userSessionMiddleware = expressSession({ saveUninitialized: false, cookie: { secure: Environment.nodeEnv === "production" || ServerConfig.ENABLE_SERVER_HTTPS, - sameSite: "strict", // Very important. Without this, we're vulnerable to CSRF! + + // Very important. Without this, we're vulnerable to CSRF! + sameSite: "strict", }, }); const app: Express = express(); -if (Environment.nodeEnv !== "production" && ServerConfig.CLIENT_DEV_SERVER) { +if (Environment.nodeEnv !== "production" && IsNonEmptyString(ServerConfig.CLIENT_DEV_SERVER)) { logger.warn(`Enabling CORS requests from ${ServerConfig.CLIENT_DEV_SERVER}.`, { bootInfo: true, }); @@ -63,7 +66,7 @@ if (Environment.nodeEnv !== "production" && ServerConfig.CLIENT_DEV_SERVER) { }); // hack to allow all OPTIONS requests. Remember that this setting should not be on in production! - if (ServerConfig.OPTIONS_ALWAYS_SUCCEEDS) { + if (ServerConfig.OPTIONS_ALWAYS_SUCCEEDS === true) { app.options("*", (req, res) => res.send()); } } else { @@ -117,7 +120,7 @@ app.use("/", mainRouter); // In dev, this is a pain to setup, so we can just run it locally. if ( ServerConfig.CDN_CONFIG.SAVE_LOCATION.TYPE === "LOCAL_FILESYSTEM" && - ServerConfig.CDN_CONFIG.SAVE_LOCATION.SERVE_OWN_CDN + ServerConfig.CDN_CONFIG.SAVE_LOCATION.SERVE_OWN_CDN === true ) { if (Environment.nodeEnv === "production") { logger.warn( diff --git a/server/src/test-utils/api-common.ts b/server/src/test-utils/api-common.ts index 45dcf83bf..535900e12 100644 --- a/server/src/test-utils/api-common.ts +++ b/server/src/test-utils/api-common.ts @@ -9,43 +9,48 @@ export function RequireAuthPerms( perms: APIPermissions | Array, method: "DELETE" | "GET" | "PATCH" | "POST" | "PUT" = "GET" ) { - t.test(`Testing permissions for ${method} ${url} [${perms}]`, async (t) => { - const m = method.toLowerCase() as Lowercase; + return t.test( + `Testing permissions for ${method} ${url} [${ + Array.isArray(perms) ? perms.join(", ") : perms + }]`, + async (t) => { + const m = method.toLowerCase() as Lowercase; - const res = await mockApi[m](url); + const res = await mockApi[m](url); - // 401 if no auth given - t.equal(res.statusCode, 401); + // 401 if no auth given + t.equal(res.statusCode, 401); - await db["api-tokens"].insert({ - identifier: "temp_auth_perms", - permissions: {}, - token: "temp_auth", - userID: 1, - fromAPIClient: null, - }); + await db["api-tokens"].insert({ + identifier: "temp_auth_perms", + permissions: {}, + token: "temp_auth", + userID: 1, + fromAPIClient: null, + }); - const resAuth = await mockApi[m](url).set("Authorization", "Bearer temp_auth"); + const resAuth = await mockApi[m](url).set("Authorization", "Bearer temp_auth"); - t.equal(resAuth.statusCode, 403); + t.equal(resAuth.statusCode, 403); - const prm = Array.isArray(perms) ? perms : [perms]; + const prm = Array.isArray(perms) ? perms : [perms]; - await db["api-tokens"].insert({ - identifier: "temp_auth_perms2", - permissions: Object.fromEntries(prm.map((e) => [e, true])), - token: "temp_auth2", - userID: 1, - fromAPIClient: null, - }); + await db["api-tokens"].insert({ + identifier: "temp_auth_perms2", + permissions: Object.fromEntries(prm.map((e) => [e, true])), + token: "temp_auth2", + userID: 1, + fromAPIClient: null, + }); - const resAuthed = await mockApi[m](url).set("Authorization", "Bearer temp_auth2"); + const resAuthed = await mockApi[m](url).set("Authorization", "Bearer temp_auth2"); - t.not(resAuthed.statusCode, 401); - t.not(resAuthed.statusCode, 403); + t.not(resAuthed.statusCode, 401); + t.not(resAuthed.statusCode, 403); - await ResetDBState(); + await ResetDBState(); - t.end(); - }); + t.end(); + } + ); } diff --git a/server/src/test-utils/asserts.ts b/server/src/test-utils/asserts.ts index 3e1a513cc..2f36f6ed1 100644 --- a/server/src/test-utils/asserts.ts +++ b/server/src/test-utils/asserts.ts @@ -23,7 +23,7 @@ export function isApproximately( export function prAssert( t: Tap.Test, - obj: Record | unknown, + obj: unknown, schema: PrudenceSchema, message = "Unnamed Prudence Assertion" ) { diff --git a/server/src/test-utils/cleanup.ts b/server/src/test-utils/cleanup.ts index ceec739b3..4fed67c1e 100644 --- a/server/src/test-utils/cleanup.ts +++ b/server/src/test-utils/cleanup.ts @@ -6,7 +6,7 @@ import { CloseRedisPubSub } from "external/redis/redis-IPC"; import { CloseScoreImportQueue } from "lib/score-import/worker/queue"; export async function CleanUpAfterTests() { - if (process.env.TAP_SNAPSHOT) { + if (process.env.TAP_SNAPSHOT !== "" && process.env.TAP_SNAPSHOT !== undefined) { WriteSnapshotData(); } diff --git a/server/src/test-utils/mock-api.ts b/server/src/test-utils/mock-api.ts index 065082611..3067b15df 100644 --- a/server/src/test-utils/mock-api.ts +++ b/server/src/test-utils/mock-api.ts @@ -11,7 +11,16 @@ logger.verbose("Connecting to Supertest..."); const mockApi = supertest(connection); export function CloseServerConnection() { - return connection.close(); + return new Promise((resolve, reject) => { + connection.close((err) => { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); } export default mockApi; diff --git a/server/src/test-utils/mock-multer.ts b/server/src/test-utils/mock-multer.ts index bb464ff56..ac0694dd5 100644 --- a/server/src/test-utils/mock-multer.ts +++ b/server/src/test-utils/mock-multer.ts @@ -1,6 +1,8 @@ export function MockMulterFile(buffer: Buffer, originalname: string) { - return { + const mockFile: Express.Multer.File = { originalname, buffer, - } as Express.Multer.File; + }; + + return mockFile; } diff --git a/server/src/utils/class.ts b/server/src/utils/class.ts index 15588cb43..1835f90b1 100644 --- a/server/src/utils/class.ts +++ b/server/src/utils/class.ts @@ -80,7 +80,7 @@ export async function UpdateClassIfGreater( }); if (isGreater === null) { - EmitWebhookEvent({ + void EmitWebhookEvent({ type: "class-update/v1", content: { userID, new: classVal, old: null, set: classSet, game, playtype }, }); @@ -88,7 +88,7 @@ export async function UpdateClassIfGreater( return null; } - EmitWebhookEvent({ + void EmitWebhookEvent({ type: "class-update/v1", content: { userID, diff --git a/server/src/utils/db.ts b/server/src/utils/db.ts index ec89a392b..5ed996c7c 100644 --- a/server/src/utils/db.ts +++ b/server/src/utils/db.ts @@ -195,7 +195,7 @@ export async function GetRecentlyAchievedGoals( goalID: { $in: goalSubs.map((e) => e.goalID) }, }); - if (goals.length !== goals.length) { + if (goals.length !== goalSubs.length) { logger.error( `Found ${goals.length} goals when looking for parents of ${goalSubs.length} subscriptions. This mismatch implies a state desync.` ); diff --git a/server/src/utils/ea3id.ts b/server/src/utils/ea3id.ts index 70e057a03..c1a0019ad 100644 --- a/server/src/utils/ea3id.ts +++ b/server/src/utils/ea3id.ts @@ -11,7 +11,10 @@ export function ParseEA3SoftID(ver: string) { return { model: a[0], - dest: a[1], // region + + // region + dest: a[1], + spec: a[2], rev: a[3], ext: a[4], diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 5f212ead8..01131f173 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -135,11 +135,11 @@ export function IsValidURL(string: string) { } export function Sleep(ms: number) { - return new Promise((resolve) => + return new Promise((resolve) => { setTimeout(() => { resolve(); - }, ms) - ); + }, ms); + }); } export function GetTimeXHoursAgo(hours: integer) { @@ -228,3 +228,7 @@ export function IsSupported(game: Game) { export function ArrayDiff(left: Array, right: Array) { return right.filter((e) => !left.includes(e)); } + +export function IsNonEmptyString(maybeStr: string | null | undefined): maybeStr is string { + return Boolean(maybeStr); +}