diff --git a/packages/api/src/context.ts b/packages/api/src/context.ts index 90fef84..1276e3f 100644 --- a/packages/api/src/context.ts +++ b/packages/api/src/context.ts @@ -22,8 +22,8 @@ export async function createContext( ) { let session = null; - // Extract request from opts if available (priority to explicit req) - const req = opts?.req || opts?.req; + // Extract request from opts if available + const req = opts?.req; // Only attempt auth if database is available if (db) { diff --git a/packages/api/src/middleware/cache.ts b/packages/api/src/middleware/cache.ts index 78203c1..e9e43cf 100644 --- a/packages/api/src/middleware/cache.ts +++ b/packages/api/src/middleware/cache.ts @@ -67,11 +67,13 @@ export class CacheService { } /** - * Delete all keys matching a pattern + * Delete all keys matching a pattern (glob-style with * wildcard) */ deletePattern(pattern: string): number { let count = 0; - const regex = new RegExp(pattern.replace(/\*/g, '.*')); + // Escape regex special chars, then convert * to .* + const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`^${escaped.replace(/\*/g, '.*')}$`); for (const key of this.cache.keys()) { if (regex.test(key)) { diff --git a/packages/api/src/routers/judge.ts b/packages/api/src/routers/judge.ts index 98213c0..ff6ac58 100644 --- a/packages/api/src/routers/judge.ts +++ b/packages/api/src/routers/judge.ts @@ -14,6 +14,16 @@ import { import { eq, and, asc, desc, sql } from "drizzle-orm"; import { CacheKeys } from "../middleware/cache"; +// Fisher-Yates shuffle for unbiased randomization +function shuffleArray(array: T[]): T[] { + const result = [...array]; + for (let i = result.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [result[i], result[j]] = [result[j], result[i]]; + } + return result; +} + // Middleware to check if user is a judge const isJudge = protectedProcedure.use(async ({ ctx, next }) => { const judge = await ctx.db!.query.judges.findFirst({ @@ -631,9 +641,9 @@ export const judgeRouter = createTRPCRouter({ orderBy: [asc(judgingProjects.tableNumber)], }); - // Optionally shuffle + // Optionally shuffle using Fisher-Yates algorithm if (input.shuffle) { - projects = projects.sort(() => Math.random() - 0.5); + projects = shuffleArray(projects); } // Create queue entries diff --git a/sites/mainweb/components/Background/index.tsx b/sites/mainweb/components/Background/index.tsx index 1eecc31..93aedd7 100644 --- a/sites/mainweb/components/Background/index.tsx +++ b/sites/mainweb/components/Background/index.tsx @@ -4,7 +4,7 @@ import React from "react"; interface BackgroundProps extends React.HTMLAttributes {} -const Background: React.FC = (props) => { +export default function Background(props: BackgroundProps) { return (
= (props) => {
); -}; - -export default Background; +} diff --git a/sites/mainweb/eslint.config.js b/sites/mainweb/eslint.config.js index 3e77435..0879d2b 100644 --- a/sites/mainweb/eslint.config.js +++ b/sites/mainweb/eslint.config.js @@ -1,4 +1,4 @@ -import { nextJsConfig } from "@query/eslint-config/next-js"; +import nextJsConfig from "@query/eslint-config/next-js"; /** @type {import("eslint").Linter.Config} */ export default nextJsConfig; diff --git a/tooling/eslint/package.json b/tooling/eslint/package.json index 02b8ef3..a3d1534 100644 --- a/tooling/eslint/package.json +++ b/tooling/eslint/package.json @@ -5,7 +5,7 @@ "type": "module", "exports": { "./base": "./base.js", - "./nextjs": "./nextjs.js", + "./next-js": "./next.js", "./react": "./react.js" }, "scripts": {