Conversation
WalkthroughThis update introduces full-text search capabilities for polls by adding a Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Controller
participant PollService
participant Database
Client->>Controller: GET /polls?search=term
Controller->>PollService: getPolls({ search: "term", ... })
PollService->>PollService: searchPolls("term")
PollService->>Database: SELECT poll IDs WHERE searchVector @@ to_tsquery('term')
Database-->>PollService: poll IDs
PollService->>Database: SELECT polls WHERE id IN (poll IDs) AND other filters
Database-->>PollService: poll data
PollService-->>Controller: filtered polls
Controller-->>Client: poll results
sequenceDiagram
participant Client
participant Controller
participant UserService
participant PollService
participant Database
Client->>Controller: GET /user/activities?search=term
Controller->>UserService: getUserActivities({ search: "term", ... })
UserService->>PollService: searchPolls("term")
PollService->>Database: SELECT poll IDs WHERE searchVector @@ to_tsquery('term')
Database-->>PollService: poll IDs
UserService->>Database: SELECT user actions WHERE pollId IN (poll IDs)
Database-->>UserService: user actions
UserService-->>Controller: user activities DTO
Controller-->>Client: user activities response
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
prisma/migrations/20250416649699_add_search_vector/migration.sql (1)
21-22: Note about updating existing recordsSetting all existing records'
searchVectorto NULL will trigger recalculation on subsequent updates, but existing polls won't be searchable until they're updated. Consider adding another statement to immediately populate search vectors for existing records if immediate searchability is required.-- Update existing records UPDATE "Poll" SET "searchVector" = NULL; + -- Immediately populate search vectors for existing records + UPDATE "Poll" SET "searchVector" = + setweight(to_tsvector('english', COALESCE(title, '')), 'A') || + setweight(to_tsvector('english', COALESCE(description, '')), 'B') || + setweight(to_tsvector('english', array_to_string(tags, ' ')), 'C');src/user/user.service.ts (2)
134-147: Consider removing bracket notation for the private method call.
AccessingsearchPollswiththis.pollService['searchPolls']might raise concerns about calling a private method, depending on TS configurations. A direct call (this.pollService.searchPolls(...)) is typically more conventional and ensures long-term maintainability.- pollIds = await this.pollService['searchPolls'](dto.search); + pollIds = await this.pollService.searchPolls(dto.search);
168-169: Address the TODO comment regarding authorWorldId.
This workaround for retrievingauthorWorldIdfrom theUserActioncan be improved by introducing the property directly in the entity or via a joined query.Do you want help creating a pull request or migration that adds
authorWorldIdtoUserAction?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
prisma/migrations/20250416649699_add_search_vector/migration.sql(1 hunks)prisma/schema.prisma(1 hunks)src/poll/Poll.dto.ts(1 hunks)src/poll/poll.service.ts(6 hunks)src/user/user.dto.ts(2 hunks)src/user/user.module.ts(1 hunks)src/user/user.service.ts(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/user/user.module.ts (1)
src/app.module.ts (1)
Module(11-16)
🔇 Additional comments (27)
src/poll/Poll.dto.ts (1)
55-57: Clean implementation of search parameterThe addition of the optional
searchproperty with proper validation decorators follows the application's patterns and enables the full-text search functionality introduced in this PR.src/user/user.module.ts (2)
2-2: Proper import of PollServiceThe import of
PollServiceis correctly defined with a relative path.
8-8: Appropriate module configuration for cross-service functionalityAdding
PollServiceto the providers array properly enables dependency injection, allowing theUserServiceto utilize poll search functionality.prisma/schema.prisma (2)
52-52: Appropriate use of PostgreSQL tsvector for search functionalityThe
searchVectorfield is correctly defined usingUnsupported("tsvector")with the@db.TsVectorannotation, which is the proper way to implement full-text search in PostgreSQL via Prisma.
57-57: Efficient indexing for search performanceThe GIN index on the
searchVectorfield is essential for optimizing full-text search queries. This will ensure search operations remain efficient even as the number of polls grows.prisma/migrations/20250416649699_add_search_vector/migration.sql (2)
1-13: Well-structured implementation of PostgreSQL full-text searchThe implementation correctly adds a tsvector column and creates a function that generates a weighted search vector from different poll fields. The weighting approach (A for title, B for description, C for tags) will provide relevant search results with titles prioritized. The function also properly handles NULL values using COALESCE.
15-20: Efficient automatic updating of search vectorsThe trigger setup ensures that search vectors are automatically updated whenever poll records are inserted or modified, eliminating the need for application-level maintenance of search data.
src/user/user.service.ts (4)
15-15: Good addition of PollService import.
Injecting thePollServiceallows for streamlined integration of the poll search functionality in theUserService.
40-42: Poll ID filter addition looks appropriate.
AddingpollId?: { in?: number[] }toUserActionFiltersenhances the filtering options for user actions by specific polls.
48-51: Constructor injection of PollService is well-structured.
This improves modularity and enables theUserServiceto delegate search functionality toPollService.
180-184: Enhanced user action details look consistent.
ExposingendDate,isActive,votersParticipated,authorWorldId, andcreatedAtas strings and booleans aligns well with the updated DTO pattern.src/poll/poll.service.ts (5)
2-2: Prisma import is properly utilized for type safety.
IntroducingPrismafor typed filtering and sorting is a best practice to maintain clarity in query logic.
90-90: Search parameter integration is well-designed.
Makingsearchoptional in the method signature aligns with typical search flows where a user may or may not input a search term.
120-120: Improved error handling with BadRequestException.
Raising aBadRequestException('worldId Not Provided')is clearer to API consumers than a generic error.
145-152: Combined poll ID filtering logic is cohesive.
Merging the search-based ID filter (pollId: { in: pollIds }) with existing filters usingANDis a straightforward approach for multi-criteria queries.
154-154: Type-safe orderBy definition is beneficial.
Explicitly typingorderByasPrisma.PollOrderByWithRelationInputensures compile-time checks for valid fields and directions.src/user/user.dto.ts (11)
1-2: Imports from class-transformer and prisma client are properly organized.
These imports set the stage for advanced transformations and validations.
4-6: Added decorators for arrays, booleans, and date strings.
Using@IsArray(),@IsBoolean(), and@IsDateString()clarifies type expectations, improving validation coverage.
8-8: Migration from @IsNumber() to @ISINT() is precise.
Restricting numerical fields (e.g., IDs) to integers prevents invalid floating-point assignments.
49-52: Optional search parameter addition is consistent.
Thesearch?: stringproperty allows user activity queries to incorporate full-text search terms without breaking backward compatibility.
56-56: Strict integer ID ensures resilience.
Enforcing@IsInt()foridinUserActionDtois a good practice for entity identification.
62-62: Ensuring pollId is an integer.
@IsInt()aligns with the database schema where poll IDs are numeric.
71-72: Switching endDate to string is consistent with ISO date representation.
Returning ISO-formatted strings ensures easy consumption on the client side.
74-75: New isActive boolean field.
@IsBoolean()appropriately validates the state of a poll or user action.
77-78: Defining votersParticipated as an integer.
This ensures clarity and consistency when representing participant counts.
83-84: Representing createdAt as a string.
ISO date strings allow consistent serialization results from the backend.
88-89: Refined userActions array validation.
Applying@IsArray()and@Type(() => UserActionDto)guarantees more robust schema conformance for nested user actions.
| private async searchPolls(searchTerm: string): Promise<number[]> { | ||
| const searchQuery = searchTerm | ||
| .split(' ') | ||
| .map((word) => `${word}:*`) | ||
| .join(' & '); | ||
| const searchResults = await this.databaseService.$queryRaw< | ||
| { pollId: number }[] | ||
| >` | ||
| SELECT "pollId" FROM "Poll" | ||
| WHERE "searchVector" @@ to_tsquery('english', ${searchQuery}) | ||
| ORDER BY ts_rank("searchVector", to_tsquery('english', ${searchQuery})) DESC | ||
| `; | ||
| return searchResults.map((result) => result.pollId); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Ensure input sanitization for raw PostgreSQL full-text search.
While $queryRaw with parameter interpolation is typically safe, consider guarding against invalid or malicious search strings by pre-validating or escaping user input. This prevents unexpected query parser errors or injection attempts.
private async searchPolls(searchTerm: string): Promise<number[]> {
// Potential approach: replace non-alphanumeric characters or handle special tokens
+ const safeTerm = searchTerm.replace(/[^a-zA-Z0-9\s]/g, '');
+ const searchQuery = safeTerm
.split(' ')
// ...
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private async searchPolls(searchTerm: string): Promise<number[]> { | |
| const searchQuery = searchTerm | |
| .split(' ') | |
| .map((word) => `${word}:*`) | |
| .join(' & '); | |
| const searchResults = await this.databaseService.$queryRaw< | |
| { pollId: number }[] | |
| >` | |
| SELECT "pollId" FROM "Poll" | |
| WHERE "searchVector" @@ to_tsquery('english', ${searchQuery}) | |
| ORDER BY ts_rank("searchVector", to_tsquery('english', ${searchQuery})) DESC | |
| `; | |
| return searchResults.map((result) => result.pollId); | |
| } | |
| private async searchPolls(searchTerm: string): Promise<number[]> { | |
| // Potential approach: replace non-alphanumeric characters or handle special tokens | |
| const safeTerm = searchTerm.replace(/[^a-zA-Z0-9\s]/g, ''); | |
| const searchQuery = safeTerm | |
| .split(' ') | |
| .map((word) => `${word}:*`) | |
| .join(' & '); | |
| const searchResults = await this.databaseService.$queryRaw< | |
| { pollId: number }[] | |
| >` | |
| SELECT "pollId" FROM "Poll" | |
| WHERE "searchVector" @@ to_tsquery('english', ${searchQuery}) | |
| ORDER BY ts_rank("searchVector", to_tsquery('english', ${searchQuery})) DESC | |
| `; | |
| return searchResults.map((result) => result.pollId); | |
| } |
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Style