Skip to content

Conversation

@CoolGame8
Copy link
Collaborator

closes #1382

@github-project-automation github-project-automation bot moved this to Backlog in LEMS Feb 5, 2026
Comment on lines +104 to +146
router.get('/:divisionId/current-activity', async (req: PortalDivisionRequest, res: Response) => {
const divisionState = await db.raw.mongo
.collection('division_states')
.findOne({ divisionId: req.divisionId });

const teams = await db.teams.byDivisionId(req.divisionId).getAll();
const tables = await db.tables.byDivisionId(req.divisionId).getAll();
const rooms = await db.rooms.byDivisionId(req.divisionId).getAll();

let activeMatch = null;
let loadedMatch = null;

if (divisionState?.field?.activeMatch) {
const match = await db.robotGameMatches.byId(divisionState.field.activeMatch).get();
if (match) {
activeMatch = makePortalMatchResponse(match, tables, teams);
}
}

if (divisionState?.field?.loadedMatch) {
const match = await db.robotGameMatches.byId(divisionState.field.loadedMatch).get();
if (match) {
loadedMatch = makePortalMatchResponse(match, tables, teams);
}
}

// Get current judging sessions (sessions happening now)
const now = new Date();
const allSessions = await db.judgingSessions.byDivision(req.divisionId).getAll();
const currentSessions = allSessions
.filter(session => {
const sessionStart = new Date(session.scheduled_time);
const sessionEnd = new Date(sessionStart.getTime() + 30 * 60 * 1000); // 30 min sessions
return now >= sessionStart && now <= sessionEnd;
})
.map(session => makePortalJudgingSessionResponse(session, rooms, teams));

res.status(200).json({
activeMatch,
loadedMatch,
currentSessions
});
});

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI 5 days ago

In general, the problem is solved by applying a rate-limiting middleware to the route (or router) so that each client can only call the expensive endpoint a limited number of times per time window. In Express, a common solution is the well-known express-rate-limit package, which provides a simple middleware you can attach to specific routes without changing their existing behavior besides rejecting excessive requests.

For this code, the least invasive and clearest fix is to import express-rate-limit, define a limiter configured for the “current activity” endpoint, and apply it specifically to the / :divisionId/current-activity route. This avoids changing the logic of the handler and does not affect other endpoints in this router. Concretely, in apps/backend/src/routers/portal/divisions/index.ts we will: (1) add an import for express-rate-limit; (2) define a currentActivityLimiter constant near the top of the file, for example allowing a reasonable number of requests per IP per minute; and (3) update the router.get('/:divisionId/current-activity', ...) call to insert currentActivityLimiter as middleware before the async handler. No other routes or behavior need to be changed.

Suggested changeset 1
apps/backend/src/routers/portal/divisions/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/backend/src/routers/portal/divisions/index.ts b/apps/backend/src/routers/portal/divisions/index.ts
--- a/apps/backend/src/routers/portal/divisions/index.ts
+++ b/apps/backend/src/routers/portal/divisions/index.ts
@@ -11,9 +11,15 @@
   makePortalMatchResponse,
   makePortalAgendaResponse
 } from './util';
+import rateLimit from 'express-rate-limit';
 
 const router = express.Router({ mergeParams: true });
 
+const currentActivityLimiter = rateLimit({
+  windowMs: 60 * 1000, // 1 minute
+  max: 30              // limit each IP to 30 requests per windowMs for this endpoint
+});
+
 router.use('/:divisionId', attachDivision());
 
 router.get('/:divisionId', async (req: PortalDivisionRequest, res: Response) => {
@@ -101,48 +104,52 @@
   res.status(200).json(awards.map(makePortalAwardsResponse));
 });
 
-router.get('/:divisionId/current-activity', async (req: PortalDivisionRequest, res: Response) => {
-  const divisionState = await db.raw.mongo
-    .collection('division_states')
-    .findOne({ divisionId: req.divisionId });
+router.get(
+  '/:divisionId/current-activity',
+  currentActivityLimiter,
+  async (req: PortalDivisionRequest, res: Response) => {
+    const divisionState = await db.raw.mongo
+      .collection('division_states')
+      .findOne({ divisionId: req.divisionId });
 
-  const teams = await db.teams.byDivisionId(req.divisionId).getAll();
-  const tables = await db.tables.byDivisionId(req.divisionId).getAll();
-  const rooms = await db.rooms.byDivisionId(req.divisionId).getAll();
+    const teams = await db.teams.byDivisionId(req.divisionId).getAll();
+    const tables = await db.tables.byDivisionId(req.divisionId).getAll();
+    const rooms = await db.rooms.byDivisionId(req.divisionId).getAll();
 
-  let activeMatch = null;
-  let loadedMatch = null;
+    let activeMatch = null;
+    let loadedMatch = null;
 
-  if (divisionState?.field?.activeMatch) {
-    const match = await db.robotGameMatches.byId(divisionState.field.activeMatch).get();
-    if (match) {
-      activeMatch = makePortalMatchResponse(match, tables, teams);
+    if (divisionState?.field?.activeMatch) {
+      const match = await db.robotGameMatches.byId(divisionState.field.activeMatch).get();
+      if (match) {
+        activeMatch = makePortalMatchResponse(match, tables, teams);
+      }
     }
-  }
 
-  if (divisionState?.field?.loadedMatch) {
-    const match = await db.robotGameMatches.byId(divisionState.field.loadedMatch).get();
-    if (match) {
-      loadedMatch = makePortalMatchResponse(match, tables, teams);
+    if (divisionState?.field?.loadedMatch) {
+      const match = await db.robotGameMatches.byId(divisionState.field.loadedMatch).get();
+      if (match) {
+        loadedMatch = makePortalMatchResponse(match, tables, teams);
+      }
     }
-  }
 
-  // Get current judging sessions (sessions happening now)
-  const now = new Date();
-  const allSessions = await db.judgingSessions.byDivision(req.divisionId).getAll();
-  const currentSessions = allSessions
-    .filter(session => {
-      const sessionStart = new Date(session.scheduled_time);
-      const sessionEnd = new Date(sessionStart.getTime() + 30 * 60 * 1000); // 30 min sessions
-      return now >= sessionStart && now <= sessionEnd;
-    })
-    .map(session => makePortalJudgingSessionResponse(session, rooms, teams));
+    // Get current judging sessions (sessions happening now)
+    const now = new Date();
+    const allSessions = await db.judgingSessions.byDivision(req.divisionId).getAll();
+    const currentSessions = allSessions
+      .filter(session => {
+        const sessionStart = new Date(session.scheduled_time);
+        const sessionEnd = new Date(sessionStart.getTime() + 30 * 60 * 1000); // 30 min sessions
+        return now >= sessionStart && now <= sessionEnd;
+      })
+      .map(session => makePortalJudgingSessionResponse(session, rooms, teams));
 
-  res.status(200).json({
-    activeMatch,
-    loadedMatch,
-    currentSessions
-  });
-});
+    res.status(200).json({
+      activeMatch,
+      loadedMatch,
+      currentSessions
+    });
+  }
+);
 
 export default router;
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
@johnmeshulam johnmeshulam marked this pull request as draft February 7, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Add a display for current session/match in event page

1 participant