diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 7f5be4c74fc..071b9849ac5 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -40,7 +40,11 @@ class SearchQueryTransformer < Parslet::Transform def validate_clauses! # At least one clause should be a positive match unless searching within the library - raise QueryError, 'At least one keyword or phrase is required' if @flags['in'] != 'library' && (must_clauses + filter_clauses).none? { |clause| clause.is_a?(TermClause) && clause.term.present? } + # `from:me` (or `from:`) is effectively a library-scoped query, so allow it without `in:library` + return if @flags['in'] == 'library' + return if filter_clauses.any? { |clause| clause.is_a?(PrefixClause) && clause.prefix == 'from' && !clause.negated? && clause.term == @options[:current_account].id } + + raise QueryError, 'At least one keyword or phrase is required' if (must_clauses + filter_clauses).none? { |clause| clause.is_a?(TermClause) && clause.term.present? } end def clauses_by_operator @@ -197,6 +201,10 @@ class SearchQueryTransformer < Parslet::Transform end end + def negated? + @negated + end + private def account_id_from_term(term) diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb index 540271e4f89..c973dd266fc 100644 --- a/spec/lib/search_query_transformer_spec.rb +++ b/spec/lib/search_query_transformer_spec.rb @@ -88,6 +88,72 @@ RSpec.describe SearchQueryTransformer do expect { subject }.to_not raise_error end end + + context 'with "from:me after:0000"' do + let(:query) { 'from:me after:0000' } + + it 'does not raise an exception' do + expect { subject }.to_not raise_error + end + end + + context 'with "from:me before:2026-08-28"' do + let(:query) { 'from:me before:2026-08-28' } + + it 'does not raise an exception' do + expect { subject }.to_not raise_error + end + end + + context 'with "from:me during:2024"' do + let(:query) { 'from:me during:2024' } + + it 'does not raise an exception' do + expect { subject }.to_not raise_error + end + end + + context 'with "from: after:0000"' do + let(:query) { "from:#{account.username} after:0000" } + + it 'does not raise an exception' do + expect { subject }.to_not raise_error + end + end + + context 'with "from:@ after:0000"' do + let(:query) { "from:#{account.username}@#{Rails.configuration.x.local_domain} after:0000" } + + it 'does not raise an exception' do + expect { subject }.to_not raise_error + end + end + + context 'with "from: after:0000"' do + let(:other_account) { Fabricate(:account) } + let(:query) { "from:#{other_account.username} after:0000" } + + it 'raises an exception' do + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) + end + end + + context 'with "from: after:0000"' do + let(:remote_account) { Fabricate(:account, domain: 'remote.host', username: 'remoteuser') } + let(:query) { "from:#{remote_account.username}@#{remote_account.domain} after:0000" } + + it 'raises an exception' do + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) + end + end + + context 'with "-from:me after:0000"' do + let(:query) { '-from:me after:0000' } + + it 'raises an exception' do + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) + end + end end context 'with "hello world"' do