Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 — Product Overview (Search Module)

Global search across all indexed StudyLyon entities. Derived from src/modules/search/** (controller, service, indexer, schema, repository, DTO), src/modules/rbac/permissions.constants.ts, src/infrastructure/redis/, docs/IMPLEMENTATION_PLAN.md, and src/common/dto/pagination-query.dto.ts. Nothing is invented; plan-only capability is marked (planned), client-only or roadmap-only capability (forward-looking), analytics (proposed).


1. What the module is

A single global search endpoint that searches a dedicated search_indexes collection (a denormalized mirror of selected business entities) and returns an aggregated flat list of typed results, paginated and relevance-ranked.

Key facts from source:

  • One endpoint: GET /api/v1/search (@Controller('search') search.controller.ts:11; @Get() :15). @ApiTags('search'), bearer auth, JwtAuthGuard (search.controller.ts:8-10).
  • Guarded by the single @Permissions('search') permission (search.controller.ts:16; declared permissions.constants.ts:93 — note: it is a bare 'search' string, not a search.* wildcard family).
  • Result shape is a flat SearchResult list — { entityType, entityId, title, description, tags } (search.service.ts:10-16). Typed grouping is a client concern; the API is not grouped.
  • Query params: q (string, optional), entityType (string, optional), page (≥1, default 1), limit (1–50, default 20) (search-query.dto.ts:5-30).
  • Empty/whitespace q returns { data: [], meta } with totalItems: 0 (search.service.ts:28-30) — no error, no index scan.
  • Index maintained event-driven: SearchIndexerService subscribes to every domain event via eventBus.onAny (search-indexer.service.ts:44-46) and upserts/soft-deletes index rows (search-indexer.service.ts:48-99).
  • MongoDB full-text ($text + textScore) with a regex fallback (search-index.repository.ts:20-50); relevance sort by textScore.

2. Entities searched (from search-indexer.service.ts:7-32)

entityTypeSource eventsNotes
UserUserCreated, UserUpdated, UserDeletedindex title = name; description = email
StudentStudentCreated/Updated/Deletedtitle falls back to admissionNumber (search-indexer.service.ts:71)
TeacherTeacherCreated/Updated/Deletedtags may include role/department/grade (search-indexer.service.ts:83-86)
StaffStaffCreated/Updated/Deletedsame extraction pipeline
ParentParentCreated/Updated/Deletedsame
BookBookCreated/Updated/Deletedlibrary catalogue; description = book description
OrganizationOrganizationCreated/Updated/Deletedtenant-level entity
LeadLeadCreated only (:29)gap: no LeadUpdated/LeadDeleted mapping → stale/dead lead rows persist
AnnouncementAnnouncementCreated, AnnouncementPublished (:30-31)gap: no updated/deleted mapping

Plan-only additions ((planned)): index batches, sessions, test_series, DPP for the coaching module (docs/IMPLEMENTATION_PLAN.md:771); the plan's "Global Search" task targets "students, staff, leads, books, announcements" (docs/IMPLEMENTATION_PLAN.md:174).

3. What is matched (indexed fields)

Per index row: title, description, tags, and a denormalized text string "${title} ${description} ${tags.join(' ')}" (search-indexer.service.ts:90-96). The Mongo text index covers title, description, tags, text (search-index.schema.ts:29-34); the regex fallback matches the same four fields, case-insensitive substring (search-index.repository.ts:36-50). Match fields map from source payloads: title = nametitleadmissionNumberfirstName + lastName (search-indexer.service.ts:68-75); description = emaildescriptionbody (:77-81); tags = role, department, grade (:83-86).

4. Scope in / scope out

In scope (implemented)Out of scope / noted
Global query across indexed entities, relevance-rankedPer-entity native search endpoints — module owns a single GET /search
entityType filter + standard paginationResult grouping / typed sections — client-side concern
Event-driven index sync (create/update/delete for 7 entity types)External engine (Elasticsearch/Meilisearch) (planned) — none in code
Tenant isolation on every read/write (base.schema.ts:9-31)Search result caching — RedisCacheService exists (redis-cache.service.ts:16-19) but is not wired into SearchService (proposed)
Regex fallback when $text errorsSearch history / saved searches / suggestions (forward-looking)
Lead/Announcement update-delete sync — known gap (see §2)

5. PRD native-app exclusion (flagged)

Per the shared ledger A1 (00-shared/12): the PRD puts native mobile apps out of Phase 1 (PRODUCT_REQUIREMENTS_DOCUMENT.md:144), roadmap Phase 3 = read-only companion. This package is the forward-looking client spec for the Flutter app against the v1 API; the backend (controller/service/repo) is fully implemented and is the only Phase-1 surface. QR-code entry (scan a book/student barcode into the search bar) is (forward-looking) — no QR backend support (ledger B4).

6. Terminology

  • Index row — one search_indexes document per (tenantId, entityType, entityId) (search-index.schema.ts:7-25).
  • Scoring — MongoDB textScore used for sort only; not returned to the client (search-index.repository.ts:28-33).
  • Fallback — case-insensitive regex $or scan used when $text throws (search.service.ts:35-41).