From 65a60f6fb93c06d84baeff207fcb7f9bbd643ebe Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 29 Mar 2026 16:53:15 +0000 Subject: [PATCH] refactor: remove unsafe type assertions and extract search filter helper Replace `Record` with typed `FilterQuery` and `FilterQuery` intermediates, removing all `as FilterQuery` casts in ListPrograms and ListCourses (#210). Extract duplicated $and/$or/$ilike search block into a shared ApplySearchFilter helper used by all three list methods (#211). Closes #210 Closes #211 https://claude.ai/code/session_01Y6eWhKjmgLfPJQMJa4VQQB --- .../curriculum/services/curriculum.service.ts | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/src/modules/curriculum/services/curriculum.service.ts b/src/modules/curriculum/services/curriculum.service.ts index 527413a..64d4c01 100644 --- a/src/modules/curriculum/services/curriculum.service.ts +++ b/src/modules/curriculum/services/curriculum.service.ts @@ -45,19 +45,7 @@ export class CurriculumService { Object.assign(filter, { id: { $in: departmentIds } }); } - if (query.search) { - const escaped = this.EscapeLikeWildcards(query.search); - Object.assign(filter, { - $and: [ - { - $or: [ - { code: { $ilike: `%${escaped}%` } }, - { name: { $ilike: `%${escaped}%` } }, - ], - }, - ], - }); - } + this.ApplySearchFilter(filter, query.search, ['code', 'name']); const departments = await this.em.find(Department, filter, { orderBy: { name: QueryOrder.ASC_NULLS_LAST }, @@ -83,7 +71,7 @@ export class CurriculumService { } } - const departmentFilter: Record = { + const departmentFilter: FilterQuery = { semester: query.semesterId, }; @@ -98,21 +86,9 @@ export class CurriculumService { const filter: FilterQuery = { department: departmentFilter, - } as FilterQuery; - - if (query.search) { - const escaped = this.EscapeLikeWildcards(query.search); - Object.assign(filter, { - $and: [ - { - $or: [ - { code: { $ilike: `%${escaped}%` } }, - { name: { $ilike: `%${escaped}%` } }, - ], - }, - ], - }); - } + }; + + this.ApplySearchFilter(filter, query.search, ['code', 'name']); const programs = await this.em.find(Program, filter, { populate: ['department'], @@ -177,7 +153,7 @@ export class CurriculumService { } // Build filter - const departmentFilter: Record = { + const departmentFilter: FilterQuery = { semester: query.semesterId, }; @@ -190,7 +166,7 @@ export class CurriculumService { departmentFilter.id = { $in: departmentIds }; } - const programFilter: Record = { + const programFilter: FilterQuery = { department: departmentFilter, }; @@ -200,21 +176,9 @@ export class CurriculumService { const filter: FilterQuery = { program: programFilter, - } as FilterQuery; - - if (query.search) { - const escaped = this.EscapeLikeWildcards(query.search); - Object.assign(filter, { - $and: [ - { - $or: [ - { shortname: { $ilike: `%${escaped}%` } }, - { fullname: { $ilike: `%${escaped}%` } }, - ], - }, - ], - }); - } + }; + + this.ApplySearchFilter(filter, query.search, ['shortname', 'fullname']); const courses = await this.em.find(Course, filter, { populate: ['program'], @@ -233,6 +197,24 @@ export class CurriculumService { } } + private ApplySearchFilter( + filter: Record, + search: string | undefined, + fields: [string, string], + ): void { + if (!search) return; + const escaped = this.EscapeLikeWildcards(search); + Object.assign(filter, { + $and: [ + { + $or: fields.map((field) => ({ + [field]: { $ilike: `%${escaped}%` }, + })), + }, + ], + }); + } + private EscapeLikeWildcards(input: string): string { return input .replace(/\\/g, '\\\\')