diff --git a/.env.production.sample b/.env.production.sample index a50d301994..e97a041260 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -238,7 +238,7 @@ SMTP_FROM_ADDRESS=notifications@example.com # SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241" # SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42" # SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4" -# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" +# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" # SAML_ATTRIBUTES_STATEMENTS_VERIFIED= # SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL= @@ -283,10 +283,11 @@ MAX_POLL_OPTION_CHARS=100 # Customize the number of hashtags shown in 'Explore' # MAX_TRENDING_TAGS=10 -# Search all public statuses -# (Normally searches only a user's own statuses, favs, bookmarks, and mentions) -# Only relevant when elasticsearch is installed -# SEARCH_ALL_PUBLIC_STATUSES=true +# Scope of full-text searches: +# - public: search any status with public visibility +# - public_or_unlisted: search any status with public or unlisted visibility +# - classic: searches only a user's own statuses, favs, bookmarks, and mentions +# SEARCH_SCOPE=public # Maximum custom emoji file sizes # If undefined or smaller than MAX_EMOJI_SIZE, the value diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb index 2984931693..0cea76813e 100644 --- a/app/chewy/statuses_index.rb +++ b/app/chewy/statuses_index.rb @@ -65,12 +65,15 @@ class StatusesIndex < Chewy::Index root date_detection: false do field :id, type: 'long' field :account_id, type: 'long' + field :created_at, type: 'date' + field :visibility, type: 'keyword' + field :from, type: 'keyword', value: ->(status) { status.account.domain ? [status.account.acct] : [status.account.acct, "#{status.account.username}@#{Rails.configuration.x.local_domain}"] } + field :domain, value: ->(status) { status.account.domain or Rails.configuration.x.local_domain } field :text, type: 'text', value: ->(status) { status.searchable_text } do field :stemmed, type: 'text', analyzer: 'content' end field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) } - field :searchable_by_anyone, type: 'boolean', value: ->(status) { status.public_visibility? } end end diff --git a/app/lib/importer/statuses_index_importer.rb b/app/lib/importer/statuses_index_importer.rb index 5b5153d5c8..fa11882a23 100644 --- a/app/lib/importer/statuses_index_importer.rb +++ b/app/lib/importer/statuses_index_importer.rb @@ -58,13 +58,21 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter end def scopes - [ + classic_scopes = [ local_statuses_scope, local_mentions_scope, local_favourites_scope, local_votes_scope, local_bookmarks_scope, ] + case Rails.configuration.x.search_scope + when :public + classic_scopes + [public_scope] + when :public_or_unlisted + classic_scopes + [public_or_unlisted_scope] + else + classic_scopes + end end def local_mentions_scope @@ -86,4 +94,14 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter def local_statuses_scope Status.local.select('"statuses"."id", COALESCE("statuses"."reblog_of_id", "statuses"."id") AS status_id') end + + # The `id` field in the above queries isn't used anywhere, so we leave it out of these. + + def public_scope + Status.with_public_visibility.select('"statuses"."id" AS status_id') + end + + def public_or_unlisted_scope + Status.with_public_or_unlisted_visibility.select('"statuses"."id" AS status_id') + end end diff --git a/app/models/status.rb b/app/models/status.rb index 6cfe19d238..fa1c95166f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -99,6 +99,7 @@ class Status < ApplicationRecord scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') } scope :with_public_visibility, -> { where(visibility: :public) } + scope :with_public_or_unlisted_visibility, -> { where(visibility: [:public, :unlisted]) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) } scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) } diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 725b1096af..bce0f2d6e9 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class SearchService < BaseService - SEARCH_ALL_PUBLIC_STATUSES = ENV['SEARCH_ALL_PUBLIC_STATUSES'] == 'true' - def call(query, account, limit, options = {}) @query = query&.strip @account = account @@ -38,8 +36,11 @@ class SearchService < BaseService def perform_statuses_search! statuses_index = StatusesIndex.filter(term: { searchable_by: @account.id }) - if SEARCH_ALL_PUBLIC_STATUSES - statuses_index = statuses_index.filter.or(term: { searchable_by_anyone: true }) + case Rails.configuration.x.search_scope + when :public + statuses_index = statuses_index.filter.or(term: { visibility: 'public' }) + when :public_or_unlisted + statuses_index = statuses_index.filter.or(term: { visibility: ['public', 'unlisted'] }) end definition = parsed_query.apply(statuses_index) diff --git a/config/initializers/search_scope.rb b/config/initializers/search_scope.rb new file mode 100644 index 0000000000..3024405709 --- /dev/null +++ b/config/initializers/search_scope.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +Rails.application.configure do + config.x.search_scope = case + when ENV['SEARCH_SCOPE'] == 'public' + :public + when ENV['SEARCH_SCOPE'] == 'public_or_unlisted' + :public_or_unlisted + else + :classic + end +end