-
Notifications
You must be signed in to change notification settings - Fork 8
Add a display for current session/match in event page #1849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| 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
a database access
Show autofix suggestion
Hide autofix suggestion
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.
-
Copy modified line R14 -
Copy modified lines R18-R22 -
Copy modified lines R107-R113 -
Copy modified lines R115-R117 -
Copy modified lines R119-R120 -
Copy modified lines R122-R126 -
Copy modified lines R129-R133 -
Copy modified lines R136-R145 -
Copy modified lines R147-R153
| @@ -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; |
closes #1382