Skip to content

feat(search): implement full-text course search with Meilisearch - #112

Open
islarmeeyah-cyber wants to merge 1 commit into
Hamplard-Hub:mainfrom
islarmeeyah-cyber:fix/issue-30-fulltext-search
Open

feat(search): implement full-text course search with Meilisearch#112
islarmeeyah-cyber wants to merge 1 commit into
Hamplard-Hub:mainfrom
islarmeeyah-cyber:fix/issue-30-fulltext-search

Conversation

@islarmeeyah-cyber

Copy link
Copy Markdown

Overview

This PR implements a dedicated full-text search engine for course content using Meilisearch, replacing the existing basic WHERE title ILIKE approach with proper indexing, relevance ranking, query sanitization, and highlighted results.

Related Issue

Closes #30

Changes

📦 New Search Module

  • [ADD] src/modules/search/search.module.ts — NestJS module exporting SearchService, importing ConfigModule for Meilisearch connection config.

  • [ADD] src/modules/search/search.service.ts

    • Meilisearch client initialization with configurable host/api key via env vars.
    • Auto-creates and configures the courses index on startup (searchable/filterable/sortable/displayed attributes, ranking rules).
    • indexCourse() / removeCourse() / bulkIndexCourses() — document sync operations with lastIndexedAt freshness tracking.
    • search() — full-text search with optional category/level/language filters, highlighted matches, pagination, and relevance sorting.
    • Query sanitization: profanity filtering via bad-words, empty query rejection, filter value escaping.
    • getIndexFreshness() — returns epoch ms of last index update per course.
    • Ranking rules: words → typo → proximity → attribute → sort → exactness.
  • [ADD] src/modules/search/search.controller.ts

    • GET /search — public search with query params (query, category, level, language, page, limit).
    • POST /search — same via request body with validation DTO.
    • GET /search/freshness/:courseId — check when a course was last indexed.
  • [ADD] src/modules/search/dto/search-query.dto.ts — validated DTO for POST body.

🔗 Integration with Courses Module

  • [MODIFY] src/modules/courses/courses.module.ts — imports SearchModule.
  • [MODIFY] src/modules/courses/courses.service.ts — injects SearchService; auto-indexes courses on:
    • approve() — indexes when a course is approved (status → ACTIVE).
    • update() — re-indexes when an active course is updated.
    • Private indexCourseForSearch() helper builds CourseSearchDocument from Prisma data.

⚙️ Configuration

  • [ADD] MEILISEARCH_HOST and MEILISEARCH_API_KEY env vars in .env.example.

📋 Dependencies

  • meilisearch (^0.40.0) — Meilisearch HTTP client.
  • bad-words and @types/bad-words — profanity filter for query sanitization.

Verification Results

npm run build
✅ Build successful — zero errors

npm test
✅ 47/47 tests passed across 10 test suites
   PASS  src/modules/courses/courses.service.spec.ts
   PASS  src/modules/certificates/certificates.service.spec.ts
   PASS  src/modules/admin/impersonation.service.spec.ts
   PASS  src/modules/exams/exams.service.spec.ts
   PASS  src/modules/gamification/leaderboard.service.spec.ts
   PASS  src/modules/live-sessions/live-sessions.service.spec.ts
   PASS  src/modules/live-sessions/live-session-reminders.service.spec.ts
   PASS  src/modules/payouts/payouts.service.spec.ts
   PASS  src/modules/courses/recommendations.service.spec.ts
   PASS  src/modules/moderation/moderation.service.spec.ts
Acceptance Criteria Status
Index sync is handled indexCourse() / removeCourse() / bulkIndexCourses() — auto-sync on course approval and update
Index freshness is tracked lastIndexedAt field; GET /search/freshness/:courseId endpoint returns it
Query sanitization is validated ✅ Profanity filter (bad-words), empty query rejection, filter value escaping
Relevance ranking works ✅ Meilisearch ranking rules configured; sort by avgRating:desc default
Results with highlights are provided attributesToHighlight on title/description with showMatchesPosition: true

Add full-text search integration for courses using Meilisearch.
- New SearchModule with Meilisearch client, index management, and query sanitization
- Search endpoint with GET/POST, highlights, filters, and pagination
- Auto-sync course index on approval and update
- Index freshness tracking per course via lastIndexedAt timestamp
- Profanity filtering via bad-words library
- Relevance ranking configuration with sortable/filterable attributes

Closes Hamplard-Hub#30
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@islarmeeyah-cyber Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@SYLVIANNORUKA

Copy link
Copy Markdown
Contributor

@islarmeeyah-cyber Kindly resolve conflict

1 similar comment
@SYLVIANNORUKA

Copy link
Copy Markdown
Contributor

@islarmeeyah-cyber Kindly resolve conflict

@SYLVIANNORUKA

Copy link
Copy Markdown
Contributor

Hi @islarmeeyah-cyber ,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Backend] Create full-text course search integration

2 participants