diff --git a/__tests__/bookmarks.ts b/__tests__/bookmarks.ts index 0b2131815..60eeb63d7 100644 --- a/__tests__/bookmarks.ts +++ b/__tests__/bookmarks.ts @@ -1069,6 +1069,46 @@ describe('query searchBookmarksSuggestions', () => { const res = await client.query(QUERY('p1')); expect(res.data).toMatchSnapshot(); }); + + it('should handle special characters in search (C++)', async () => { + loggedUser = '1'; + await con.getRepository(ArticlePost).save({ + id: 'cpp-post', + title: 'Learn C++ programming', + shortId: 'cpp', + url: 'http://cpp.com', + sourceId: 'a', + visible: true, + }); + await con.getRepository(Bookmark).save({ + userId: loggedUser, + postId: 'cpp-post', + createdAt: now, + }); + const res = await client.query(QUERY('c++')); + expect(res.data.searchBookmarksSuggestions.query).toBe('c++'); + expect(res.data.searchBookmarksSuggestions.hits.length).toBeGreaterThan(0); + }); + + it('should handle special characters in search (C#)', async () => { + loggedUser = '1'; + await con.getRepository(ArticlePost).save({ + id: 'csharp-post', + title: 'Learn C# programming', + shortId: 'csharp', + url: 'http://csharp.com', + sourceId: 'a', + visible: true, + }); + await con.getRepository(Bookmark).save({ + userId: loggedUser, + postId: 'csharp-post', + createdAt: now, + }); + const res = await client.query(QUERY('c#')); + expect(res.data.searchBookmarksSuggestions.query).toBe('c#'); + expect(res.data.searchBookmarksSuggestions.hits.length).toBeGreaterThan(0); + }); }); describe('query searchBookmarks', () => { diff --git a/__tests__/users.ts b/__tests__/users.ts index 7bb3286c4..1547be180 100644 --- a/__tests__/users.ts +++ b/__tests__/users.ts @@ -3440,6 +3440,50 @@ describe('query searchReadingHistorySuggestions', () => { const res = await client.query(QUERY('p1')); expect(res.data).toMatchSnapshot(); }); + + it('should handle special characters in search (C++)', async () => { + loggedUser = '1'; + await con.getRepository(ArticlePost).save({ + id: 'cpp-post', + title: 'Learn C++ programming', + shortId: 'cpp', + url: 'http://cpp.com', + sourceId: 'a', + visible: true, + }); + await con.getRepository(View).save({ + userId: loggedUser, + timestamp: now, + postId: 'cpp-post', + }); + const res = await client.query(QUERY('c++')); + expect(res.data.searchReadingHistorySuggestions.query).toBe('c++'); + expect( + res.data.searchReadingHistorySuggestions.hits.length, + ).toBeGreaterThan(0); + }); + + it('should handle special characters in search (C#)', async () => { + loggedUser = '1'; + await con.getRepository(ArticlePost).save({ + id: 'csharp-post', + title: 'Learn C# programming', + shortId: 'csharp', + url: 'http://csharp.com', + sourceId: 'a', + visible: true, + }); + await con.getRepository(View).save({ + userId: loggedUser, + timestamp: now, + postId: 'csharp-post', + }); + const res = await client.query(QUERY('c#')); + expect(res.data.searchReadingHistorySuggestions.query).toBe('c#'); + expect( + res.data.searchReadingHistorySuggestions.hits.length, + ).toBeGreaterThan(0); + }); }); describe('query search reading history', () => { diff --git a/src/schema/common.ts b/src/schema/common.ts index 84789410a..90807c9b0 100644 --- a/src/schema/common.ts +++ b/src/schema/common.ts @@ -190,8 +190,23 @@ export interface PageGenerator< export const getSearchQuery = (param: string): string => `SELECT to_tsquery('english', ${param}) AS query`; -export const processSearchQuery = (query: string): string => - query.trim().split(' ').join(' & ') + ':*'; +export const processSearchQuery = (query: string): string => { + const trimmed = query.trim(); + + // Check if query contains special characters that should be preserved + // Common programming language patterns: c#, c++, f#, .net, node.js, etc. + const hasSpecialChars = /[#+.\-]/.test(trimmed); + + if (hasSpecialChars) { + // For queries with special characters, use phrase search to preserve them + // Replace single quotes with double single quotes to escape them + const escaped = trimmed.replace(/'/g, "''"); + return `'${escaped}':*`; + } + + // For regular queries, use the original AND logic with prefix matching + return trimmed.split(' ').join(' & ') + ':*'; +}; export const mimirOffsetGenerator = < TReturn extends { id: string },