From 1996b1e29c0e4e8f156a7afaf4a5457169994476 Mon Sep 17 00:00:00 2001 From: JamesFlare1212 Date: Mon, 12 May 2025 16:32:56 -0400 Subject: [PATCH] feat: new filters on /v1/activity/list --- index.ts | 113 +++++++++++++++++++++++++++++++++++++++++---- models/activity.ts | 2 +- 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/index.ts b/index.ts index 0bdf38b..f6c4bfe 100644 --- a/index.ts +++ b/index.ts @@ -122,21 +122,47 @@ async function fetchProcessAndStoreActivity(activityId: string): Promise { - res.send('Welcome to the DSAS CCA API!

\ - API Endpoints:
\ + res.send('Welcome to the DSAS CCA API!
\ GET /v1/activity/list
\ + GET /v1/activity/list?category=
\ + GET /v1/activity/list?academicYear=
\ + GET /v1/activity/list?grade=
\ GET /v1/activity/category
\ GET /v1/activity/academicYear
\ - GET /v1/activity/:activityId (ID must be 1-4 digits)
\ + GET /v1/activity/:activityId
\ GET /v1/staffs'); }); -// Activity list endpoint -app.get('/v1/activity/list', async (_req: Request, res: Response) => { +// Activity list endpoint with filtering capabilities +app.get('/v1/activity/list', async (req: Request, res: Response) => { try { - logger.info('Request received for /v1/activity/list'); + const category = req.query.category as string | undefined; + const academicYear = req.query.academicYear as string | undefined; + const grade = req.query.grade as string | undefined; + + // Validate academicYear format if provided (YYYY/YYYY) + if (academicYear !== undefined) { + const academicYearRegex = /^\d{4}\/\d{4}$/; + if (!academicYearRegex.test(academicYear)) { + return res.status(400).json({ error: 'Invalid academicYear format. Expected format: YYYY/YYYY' }); + } + } + + // Validate grade if provided + let validGrade: number | null = null; + if (grade !== undefined) { + const parsedGrade = parseInt(grade, 10); + if (!isNaN(parsedGrade) && parsedGrade > 0 && parsedGrade <= 12) { + validGrade = parsedGrade; + } else { + return res.status(400).json({ error: 'Invalid grade parameter. Must be a number between 1 and 12.' }); + } + } + + logger.info(`Request received for /v1/activity/list with filters: ${JSON.stringify({category, academicYear, grade: validGrade})}`); + const activityKeys = await getAllActivityKeys(); - const clubList: Record = {}; + const clubList: Record = {}; if (!activityKeys || activityKeys.length === 0) { logger.info('No activity keys found in Redis for list.'); @@ -150,18 +176,85 @@ app.get('/v1/activity/list', async (_req: Request, res: Response) => { }); const allActivities = await Promise.all(allActivityDataPromises); - + + // First pass: collect all available categories for validation + const availableCategories = new Set(); + const availableAcademicYears = new Set(); + + allActivities.forEach((activityData: ActivityData | null) => { + if (activityData && + !activityData.error && + activityData.source !== 'api-fetch-empty') { + if (activityData.category) { + availableCategories.add(activityData.category); + } + if (activityData.academicYear) { + availableAcademicYears.add(activityData.academicYear); + } + } + }); + + // Validate category against available categories + if (category && !availableCategories.has(category)) { + return res.status(400).json({ + error: 'Invalid category parameter. Category not found.', + availableCategories: Array.from(availableCategories) + }); + } + + // Validate academicYear against available years + if (academicYear && !availableAcademicYears.has(academicYear)) { + return res.status(400).json({ + error: 'Invalid academicYear parameter. Academic year not found.', + availableAcademicYears: Array.from(availableAcademicYears) + }); + } + + // Apply filters and collect club data allActivities.forEach((activityData: ActivityData | null) => { if (activityData && activityData.id && activityData.name && !activityData.error && activityData.source !== 'api-fetch-empty') { - clubList[activityData.id] = activityData.name; + + // Check if it matches category filter if provided + if (category && activityData.category !== category) { + return; // Skip this activity + } + + // Check if it matches academicYear filter if provided + if (academicYear && activityData.academicYear !== academicYear) { + return; // Skip this activity + } + + // Check if it matches grade filter if provided + if (validGrade !== null) { + // Skip if grades are null + if (!activityData.grades || + activityData.grades.min === null || + activityData.grades.max === null) { + return; // Skip this activity + } + + const minGrade = parseInt(activityData.grades.min, 10); + const maxGrade = parseInt(activityData.grades.max, 10); + + // Skip if grade is out of range or if parsing fails + if (isNaN(minGrade) || isNaN(maxGrade) || validGrade < minGrade || validGrade > maxGrade) { + return; // Skip this activity + } + } + + // Add to result object with name and photo + clubList[activityData.id] = { + name: activityData.name, + photo: activityData.photo || "" + }; } }); - logger.info(`Returning list of ${Object.keys(clubList).length} valid clubs.`); + logger.info(`Returning list of ${Object.keys(clubList).length} valid clubs after filtering.`); res.json(clubList); } catch (error) { diff --git a/models/activity.ts b/models/activity.ts index 059ad97..35e1b6e 100644 --- a/models/activity.ts +++ b/models/activity.ts @@ -1,7 +1,7 @@ // src/models/activity.ts export interface ActivityData { // Include all common properties - id?: string | null; + id?: string | null | undefined; name?: string | null; description?: string | null; photo?: string | null | undefined;