@@ -46,14 +46,29 @@ def page_count
4646 end
4747
4848 def search_for_user! ( user )
49- # Extract domain query since it must be done separately
49+ # Extract special search operators
5050 domain = nil
51+ submitter = nil
5152 words = q . to_s . split ( " " ) . reject { |w |
5253 if ( m = w . match ( /^domain:(.+)$/ ) )
5354 domain = m [ 1 ]
55+ elsif ( m = w . match ( /^submitter:(.+)$/i ) )
56+ submitter = m [ 1 ]
5457 end
5558 } . join ( " " )
5659
60+ # Handle submitter search - find user by username
61+ submitter_user = nil
62+ if submitter . present?
63+ submitter_user = User . find_by ( "LOWER(username) = ?" , submitter . downcase )
64+ if submitter_user . nil? && words . blank? && domain . blank?
65+ self . results = [ ]
66+ self . total_results = 0
67+ self . page = 0
68+ return false
69+ end
70+ end
71+
5772 # Handle domain search
5873 story_ids = [ ]
5974 if domain . present?
@@ -80,18 +95,20 @@ def search_for_user!(user)
8095 end
8196 end
8297
83- # Escape query for FULLTEXT search
84- query = ActiveRecord ::Base . connection . quote_string ( words )
98+ # Sanitize query for FULLTEXT BOOLEAN MODE
99+ # Escape special characters that have meaning in boolean mode
100+ sanitized_words = sanitize_fulltext_query ( words )
101+ query = ActiveRecord ::Base . connection . quote_string ( sanitized_words )
85102
86103 # Build search based on 'what' parameter
87104 results_array = [ ]
88105
89106 if what == "all" || what == "stories"
90- results_array . concat ( search_stories ( query , story_ids ) )
107+ results_array . concat ( search_stories ( query , story_ids , submitter_user ) )
91108 end
92109
93110 if what == "all" || what == "comments"
94- results_array . concat ( search_comments ( query ) )
111+ results_array . concat ( search_comments ( query , submitter_user ) )
95112 end
96113
97114 # Sort results
@@ -145,12 +162,17 @@ def search_for_user!(user)
145162
146163 private
147164
148- def search_stories ( query , story_ids = [ ] )
149- # Return empty if no query AND no story_ids (domain search)
150- return [ ] if query . blank? && story_ids . empty?
165+ def search_stories ( query , story_ids = [ ] , submitter_user = nil )
166+ # Return empty if no query AND no story_ids AND no submitter
167+ return [ ] if query . blank? && story_ids . empty? && submitter_user . nil?
151168
152169 relation = Story . joins ( :user ) . where ( is_expired : false )
153170
171+ # Filter by submitter if specified
172+ if submitter_user
173+ relation = relation . where ( user_id : submitter_user . id )
174+ end
175+
154176 # Filter by story_ids if domain search
155177 if story_ids . any?
156178 relation = relation . where ( id : story_ids )
@@ -162,23 +184,43 @@ def search_stories(query, story_ids = [])
162184 . where ( "MATCH(stories.title, stories.description, stories.url) AGAINST(? IN BOOLEAN MODE)" , query )
163185 . select ( "stories.*, MATCH(stories.title, stories.description, stories.url) AGAINST('#{ query } ' IN BOOLEAN MODE) as relevance" )
164186 else
165- # Domain-only search: no relevance score
187+ # Domain-only or submitter-only search: no relevance score
166188 relation . select ( "stories.*, 0 as relevance" )
167189 end
168190
169191 relation . includes ( :user , :tags ) . to_a
170192 end
171193
172- def search_comments ( query )
173- return [ ] if query . blank?
194+ def search_comments ( query , submitter_user = nil )
195+ # Return empty if no query AND no submitter
196+ return [ ] if query . blank? && submitter_user . nil?
174197
175- Comment . joins ( :user , :story )
198+ relation = Comment . joins ( :user , :story )
176199 . where ( is_deleted : false , is_moderated : false )
177- . where ( "MATCH(comment) AGAINST(? IN BOOLEAN MODE)" , query )
178- . select ( "comments.*,
179- MATCH(comment) AGAINST('#{ query } ' IN BOOLEAN MODE) as relevance" )
180- . includes ( :user , :story )
181- . to_a
200+
201+ # Filter by submitter if specified
202+ if submitter_user
203+ relation = relation . where ( user_id : submitter_user . id )
204+ end
205+
206+ # Add FULLTEXT search only if we have a query
207+ relation = if query . present?
208+ relation
209+ . where ( "MATCH(comment) AGAINST(? IN BOOLEAN MODE)" , query )
210+ . select ( "comments.*, MATCH(comment) AGAINST('#{ query } ' IN BOOLEAN MODE) as relevance" )
211+ else
212+ # Submitter-only search: no relevance score
213+ relation . select ( "comments.*, 0 as relevance" )
214+ end
215+
216+ relation . includes ( :user , :story ) . to_a
217+ end
218+
219+ def sanitize_fulltext_query ( query )
220+ # In MySQL FULLTEXT BOOLEAN MODE, certain characters have special meaning:
221+ # + = must include, - = must exclude, * = wildcard, " = phrase, etc.
222+ # We escape these to treat them as literal characters
223+ query . to_s . gsub ( /[+\- <>()~*"]/ , " " )
182224 end
183225
184226 def sort_results ( results )
0 commit comments