diff --git a/server/src/module/opensource/opensource.routes.ts b/server/src/module/opensource/opensource.routes.ts index 0fe379c8d..1db7fb98a 100644 --- a/server/src/module/opensource/opensource.routes.ts +++ b/server/src/module/opensource/opensource.routes.ts @@ -1,4 +1,4 @@ -import { Router } from "express"; +import { Router } from "express"; import { prisma } from "../../database/db.js"; import { OpensourceController } from "./opensource.controller.js"; import { authMiddleware } from "../../middleware/auth.middleware.js"; @@ -21,24 +21,6 @@ opensourceRouter.get("/languages", (req, res, next) => controller.getLanguages(r // Get GSoC organizations opensourceRouter.get("/gsoc/orgs", (req, res, next) => controller.getGsocOrgs(req, res, next)); -// ─── Student Progress Tracking ───────────────────────────────── -// NOTE: must be before /:id to avoid route conflicts - -opensourceRouter.get( - "/first-pr/progress", - authMiddleware, - requireRole("STUDENT"), - (req, res, next) => controller.getFirstPrProgress(req, res, next), -); - -opensourceRouter.patch( - "/first-pr/progress", - authMiddleware, - requireRole("STUDENT"), - (req, res, next) => controller.patchFirstPrProgress(req, res, next), -); - -// ─── Repo Requests (Student-authenticated) ───────────────────── // NOTE: these must be registered BEFORE /:id to avoid route conflicts opensourceRouter.post("/requests", authMiddleware, requireRole("STUDENT"), (req, res, next) => @@ -81,6 +63,13 @@ opensourceRouter.get("/analytics/trend", authMiddleware, requireRole("STUDENT"), controller.getStudentContributionTrend(req, res, next), ); +opensourceRouter.get("/first-pr/progress", authMiddleware, requireRole("STUDENT"), (req, res, next) => + controller.getFirstPrProgress(req, res, next), +); + +opensourceRouter.patch("/first-pr/progress", authMiddleware, requireRole("STUDENT"), (req, res, next) => + controller.patchFirstPrProgress(req, res, next), +); // ─── Admin: Manage Repo Requests ─────────────────────────────── opensourceRouter.get("/requests/all", authMiddleware, requireRole("ADMIN"), (req, res, next) => diff --git a/server/src/module/opensource/opensource.service.ts b/server/src/module/opensource/opensource.service.ts index 27ff7603c..9fca8dffd 100644 --- a/server/src/module/opensource/opensource.service.ts +++ b/server/src/module/opensource/opensource.service.ts @@ -78,23 +78,25 @@ export class OpensourceService { if (language) where["language"] = { equals: language, mode: "insensitive" }; if (difficulty) where["difficulty"] = difficulty; if (domain) where["domain"] = domain; - const trimmedSearch = search?.trim(); - if (trimmedSearch) { - // Prisma's scalar-list filters can't do case-insensitive substring match - // on array elements, so resolve tag matches via a raw ILIKE-on-unnest - // subquery and merge the matching ids into the OR clause. +const trimmedSearch = search?.trim(); + +if (trimmedSearch) { + // Prisma's scalar-list filters can't do case-insensitive substring match + // on array elements, so resolve tag matches via a raw ILIKE-on-unnest + // subquery and merge the matching ids into the OR clause. const tagMatches = await prisma.$queryRaw>` SELECT id FROM "opensourceRepo" WHERE EXISTS ( SELECT 1 FROM unnest(tags) AS t WHERE t ILIKE ${`%${trimmedSearch}%`} ) `; + const tagMatchIds = tagMatches.map((r) => r.id); - where["OR"] = [ - { name: { contains: trimmedSearch, mode: "insensitive" } }, - { owner: { contains: trimmedSearch, mode: "insensitive" } }, - { description: { contains: search, mode: "insensitive" } }, - { language: { contains: search, mode: "insensitive" } }, +where["OR"] = [ + { name: { contains: trimmedSearch, mode: "insensitive" } }, + { owner: { contains: trimmedSearch, mode: "insensitive" } }, + { description: { contains: trimmedSearch, mode: "insensitive" } }, + { language: { contains: trimmedSearch, mode: "insensitive" } }, ...(tagMatchIds.length > 0 ? [{ id: { in: tagMatchIds } }] : []), ]; } @@ -162,10 +164,12 @@ export class OpensourceService { const skip = (page - 1) * limit; const where: any = {}; - if (search) { + const trimmedSearch = search?.trim(); + + if (trimmedSearch) { where.OR = [ - { name: { contains: search, mode: "insensitive" } }, - { description: { contains: search, mode: "insensitive" } }, + { name: { contains: trimmedSearch, mode: "insensitive" } }, + { description: { contains: trimmedSearch, mode: "insensitive" } }, ]; } if (category) {