diff --git a/app/controllers/api/v1/notifications/policies_controller.rb b/app/controllers/api/v1/notifications/policies_controller.rb new file mode 100644 index 000000000..1ec336f9a --- /dev/null +++ b/app/controllers/api/v1/notifications/policies_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class Api::V1::Notifications::PoliciesController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :show + before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: :update + + before_action :require_user! + before_action :set_policy + + def show + render json: @policy, serializer: REST::NotificationPolicySerializer + end + + def update + @policy.update!(resource_params) + render json: @policy, serializer: REST::NotificationPolicySerializer + end + + private + + def set_policy + @policy = NotificationPolicy.find_or_initialize_by(account: current_account) + + with_read_replica do + @policy.summarize! + end + end + + def resource_params + params.permit( + :filter_not_following, + :filter_not_followers, + :filter_new_accounts, + :filter_private_mentions + ) + end +end diff --git a/app/controllers/api/v1/notifications/requests_controller.rb b/app/controllers/api/v1/notifications/requests_controller.rb new file mode 100644 index 000000000..dbb987153 --- /dev/null +++ b/app/controllers/api/v1/notifications/requests_controller.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +class Api::V1::Notifications::RequestsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, except: :index + + before_action :require_user! + before_action :set_request, except: :index + + after_action :insert_pagination_headers, only: :index + + def index + with_read_replica do + @requests = load_requests + @relationships = relationships + end + + render json: @requests, each_serializer: REST::NotificationRequestSerializer, relationships: @relationships + end + + def accept + AcceptNotificationRequestService.new.call(@request) + render_empty + end + + def dismiss + @request.update!(dismissed: true) + render_empty + end + + private + + def load_requests + requests = NotificationRequest.where(account: current_account).where(dismissed: truthy_param?(:dismissed)).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params_slice(:max_id, :since_id, :min_id) + ) + + NotificationRequest.preload_cache_collection(requests) do |statuses| + cache_collection(statuses, Status) + end + end + + def relationships + StatusRelationshipsPresenter.new(@requests.map(&:last_status), current_user&.account_id) + end + + def set_request + @request = NotificationRequest.where(account: current_account).find(params[:id]) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_notifications_requests_url pagination_params(max_id: pagination_max_id) unless @requests.empty? + end + + def prev_path + api_v1_notifications_requests_url pagination_params(min_id: pagination_since_id) unless @requests.empty? + end + + def pagination_max_id + @requests.last.id + end + + def pagination_since_id + @requests.first.id + end + + def pagination_params(core_params) + params.slice(:dismissed).permit(:dismissed).merge(core_params) + end +end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 406ab9753..52280ef60 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -49,7 +49,8 @@ class Api::V1::NotificationsController < Api::BaseController current_account.notifications.without_suspended.browserable( types: Array(browserable_params[:types]), exclude_types: Array(browserable_params[:exclude_types]), - from_account_id: browserable_params[:account_id] + from_account_id: browserable_params[:account_id], + include_filtered: truthy_param?(:include_filtered) ) end @@ -78,10 +79,10 @@ class Api::V1::NotificationsController < Api::BaseController end def browserable_params - params.permit(:account_id, types: [], exclude_types: []) + params.permit(:account_id, :include_filtered, types: [], exclude_types: []) end def pagination_params(core_params) - params.slice(:limit, :account_id, :types, :exclude_types).permit(:limit, :account_id, types: [], exclude_types: []).merge(core_params) + params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered).permit(:limit, :account_id, :include_filtered, types: [], exclude_types: []).merge(core_params) end end diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 2bb6fed5a..b2e9d255f 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -15,10 +15,15 @@ module Account::Associations has_many :favourites, inverse_of: :account, dependent: :destroy has_many :bookmarks, inverse_of: :account, dependent: :destroy has_many :mentions, inverse_of: :account, dependent: :destroy - has_many :notifications, inverse_of: :account, dependent: :destroy has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy + # Notifications + has_many :notifications, inverse_of: :account, dependent: :destroy + has_one :notification_policy, inverse_of: :account, dependent: :destroy + has_many :notification_permissions, inverse_of: :account, dependent: :destroy + has_many :notification_requests, inverse_of: :account, dependent: :destroy + # Pinned statuses has_many :status_pins, inverse_of: :account, dependent: :destroy has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status diff --git a/app/models/notification.rb b/app/models/notification.rb index 54212d675..e322daea4 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -12,6 +12,7 @@ # account_id :bigint(8) not null # from_account_id :bigint(8) not null # type :string +# filtered :boolean default(FALSE), not null # class Notification < ApplicationRecord @@ -89,7 +90,7 @@ class Notification < ApplicationRecord end class << self - def browserable(types: [], exclude_types: [], from_account_id: nil) + def browserable(types: [], exclude_types: [], from_account_id: nil, include_filtered: false) requested_types = if types.empty? TYPES else @@ -99,6 +100,7 @@ class Notification < ApplicationRecord requested_types -= exclude_types.map(&:to_sym) all.tap do |scope| + scope.merge!(where(filtered: false)) unless include_filtered || from_account_id.present? scope.merge!(where(from_account_id: from_account_id)) if from_account_id.present? scope.merge!(where(type: requested_types)) unless requested_types.size == TYPES.size end @@ -144,6 +146,8 @@ class Notification < ApplicationRecord after_initialize :set_from_account before_validation :set_from_account + after_destroy :remove_from_notification_request + private def set_from_account @@ -158,4 +162,9 @@ class Notification < ApplicationRecord self.from_account_id = activity&.id end end + + def remove_from_notification_request + notification_request = NotificationRequest.find_by(account_id: account_id, from_account_id: from_account_id) + notification_request&.reconsider_existence! + end end diff --git a/app/models/notification_permission.rb b/app/models/notification_permission.rb new file mode 100644 index 000000000..e0001473f --- /dev/null +++ b/app/models/notification_permission.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: notification_permissions +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# from_account_id :bigint(8) not null +# created_at :datetime not null +# updated_at :datetime not null +# +class NotificationPermission < ApplicationRecord + belongs_to :account + belongs_to :from_account, class_name: 'Account' +end diff --git a/app/models/notification_policy.rb b/app/models/notification_policy.rb new file mode 100644 index 000000000..f10b0c2a8 --- /dev/null +++ b/app/models/notification_policy.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: notification_policies +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# filter_not_following :boolean default(FALSE), not null +# filter_not_followers :boolean default(FALSE), not null +# filter_new_accounts :boolean default(FALSE), not null +# filter_private_mentions :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class NotificationPolicy < ApplicationRecord + belongs_to :account + + has_many :notification_requests, primary_key: :account_id, foreign_key: :account_id, dependent: nil, inverse_of: false + + attr_reader :pending_requests_count, :pending_notifications_count + + MAX_MEANINGFUL_COUNT = 100 + + def summarize! + @pending_requests_count = pending_notification_requests.first + @pending_notifications_count = pending_notification_requests.last + end + + private + + def pending_notification_requests + @pending_notification_requests ||= notification_requests.where(dismissed: false).limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint')) + end +end diff --git a/app/models/notification_request.rb b/app/models/notification_request.rb new file mode 100644 index 000000000..7ae7e46d1 --- /dev/null +++ b/app/models/notification_request.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: notification_requests +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# from_account_id :bigint(8) not null +# last_status_id :bigint(8) not null +# notifications_count :bigint(8) default(0), not null +# dismissed :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class NotificationRequest < ApplicationRecord + include Paginable + + MAX_MEANINGFUL_COUNT = 100 + + belongs_to :account + belongs_to :from_account, class_name: 'Account' + belongs_to :last_status, class_name: 'Status' + + before_save :prepare_notifications_count + + def self.preload_cache_collection(requests) + cached_statuses_by_id = yield(requests.filter_map(&:last_status)).index_by(&:id) # Call cache_collection in block + + requests.each do |request| + request.last_status = cached_statuses_by_id[request.last_status_id] unless request.last_status_id.nil? + end + end + + def reconsider_existence! + return if dismissed? + + prepare_notifications_count + + if notifications_count.positive? + save + else + destroy + end + end + + private + + def prepare_notifications_count + self.notifications_count = Notification.where(account: account, from_account: from_account).limit(MAX_MEANINGFUL_COUNT).count + end +end diff --git a/app/serializers/rest/notification_policy_serializer.rb b/app/serializers/rest/notification_policy_serializer.rb new file mode 100644 index 000000000..4967c3e32 --- /dev/null +++ b/app/serializers/rest/notification_policy_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class REST::NotificationPolicySerializer < ActiveModel::Serializer + attributes :filter_not_following, + :filter_not_followers, + :filter_new_accounts, + :filter_private_mentions, + :summary + + def summary + { + pending_requests_count: object.pending_requests_count.to_s, + pending_notifications_count: object.pending_notifications_count.to_s, + } + end +end diff --git a/app/serializers/rest/notification_request_serializer.rb b/app/serializers/rest/notification_request_serializer.rb new file mode 100644 index 000000000..581959d82 --- /dev/null +++ b/app/serializers/rest/notification_request_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class REST::NotificationRequestSerializer < ActiveModel::Serializer + attributes :id, :created_at, :updated_at, :notifications_count + + belongs_to :from_account, key: :account, serializer: REST::AccountSerializer + belongs_to :last_status, serializer: REST::StatusSerializer + + def id + object.id.to_s + end + + def notifications_count + object.notifications_count.to_s + end +end diff --git a/app/services/accept_notification_request_service.rb b/app/services/accept_notification_request_service.rb new file mode 100644 index 000000000..e49eae6fd --- /dev/null +++ b/app/services/accept_notification_request_service.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AcceptNotificationRequestService < BaseService + def call(request) + NotificationPermission.create!(account: request.account, from_account: request.from_account) + UnfilterNotificationsWorker.perform_async(request.id) + end +end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 13eb20986..428fdb4d4 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -11,126 +11,195 @@ class NotifyService < BaseService status ).freeze + class DismissCondition + def initialize(notification) + @recipient = notification.account + @sender = notification.from_account + @notification = notification + end + + def dismiss? + blocked = @recipient.unavailable? + blocked ||= from_self? && @notification.type != :poll + + return blocked if message? && from_staff? + + blocked ||= domain_blocking? + blocked ||= @recipient.blocking?(@sender) + blocked ||= @recipient.muting_notifications?(@sender) + blocked ||= conversation_muted? + blocked ||= blocked_mention? if message? + blocked + end + + private + + def blocked_mention? + FeedManager.instance.filter?(:mentions, @notification.target_status, @recipient) + end + + def message? + @notification.type == :mention + end + + def from_staff? + @sender.local? && @sender.user.present? && @sender.user_role&.overrides?(@recipient.user_role) + end + + def from_self? + @recipient.id == @sender.id + end + + def domain_blocking? + @recipient.domain_blocking?(@sender.domain) && !following_sender? + end + + def conversation_muted? + @notification.target_status && @recipient.muting_conversation?(@notification.target_status.conversation) + end + + def following_sender? + @recipient.following?(@sender) + end + end + + class FilterCondition + NEW_ACCOUNT_THRESHOLD = 30.days.freeze + + NEW_FOLLOWER_THRESHOLD = 3.days.freeze + + def initialize(notification) + @notification = notification + @recipient = notification.account + @sender = notification.from_account + @policy = NotificationPolicy.find_or_initialize_by(account: @recipient) + end + + def filter? + return false if override_for_sender? + + from_limited? || + filtered_by_not_following_policy? || + filtered_by_not_followers_policy? || + filtered_by_new_accounts_policy? || + filtered_by_private_mentions_policy? + end + + private + + def filtered_by_not_following_policy? + @policy.filter_not_following? && not_following? + end + + def filtered_by_not_followers_policy? + @policy.filter_not_followers? && not_follower? + end + + def filtered_by_new_accounts_policy? + @policy.filter_new_accounts? && new_account? + end + + def filtered_by_private_mentions_policy? + @policy.filter_private_mentions? && not_following? && private_mention_not_in_response? + end + + def not_following? + !@recipient.following?(@sender) + end + + def not_follower? + follow = Follow.find_by(account: @sender, target_account: @recipient) + follow.nil? || follow.created_at > NEW_FOLLOWER_THRESHOLD.ago + end + + def new_account? + @sender.created_at > NEW_ACCOUNT_THRESHOLD.ago + end + + def override_for_sender? + NotificationPermission.exists?(account: @recipient, from_account: @sender) + end + + def from_limited? + @sender.silenced? && not_following? + end + + def private_mention_not_in_response? + @notification.type == :mention && @notification.target_status.direct_visibility? && !response_to_recipient? + end + + def response_to_recipient? + return false if @notification.target_status.in_reply_to_id.nil? + + statuses_that_mention_sender.positive? + end + + def statuses_that_mention_sender + Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100]) + WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS ( + SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0 + FROM statuses s + LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id + WHERE s.id = :id + UNION ALL + SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1 + FROM ancestors st + JOIN statuses s ON s.id = st.in_reply_to_id + LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id + WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit + ) + SELECT COUNT(*) + FROM ancestors st + JOIN statuses s ON s.id = st.id + WHERE st.mention_id IS NOT NULL AND s.visibility = 3 + SQL + end + end + def call(recipient, type, activity) + return if recipient.user.nil? + @recipient = recipient @activity = activity @notification = Notification.new(account: @recipient, type: type, activity: @activity) - return if recipient.user.nil? || blocked? + # For certain conditions we don't need to create a notification at all + return if dismiss? + @notification.filtered = filter? @notification.save! # It's possible the underlying activity has been deleted # between the save call and now return if @notification.activity.nil? - push_notification! - push_to_conversation! if direct_message? - send_email! if email_needed? + if @notification.filtered? + update_notification_request! + else + push_notification! + push_to_conversation! if direct_message? + send_email! if email_needed? + end rescue ActiveRecord::RecordInvalid nil end private - def blocked_mention? - FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient) + def dismiss? + DismissCondition.new(@notification).dismiss? end - def following_sender? - return @following_sender if defined?(@following_sender) - - @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) + def filter? + FilterCondition.new(@notification).filter? end - def optional_non_follower? - @recipient.user.settings['interactions.must_be_follower'] && !@notification.from_account.following?(@recipient) - end + def update_notification_request! + return unless @notification.type == :mention - def optional_non_following? - @recipient.user.settings['interactions.must_be_following'] && !following_sender? - end - - def message? - @notification.type == :mention - end - - def direct_message? - message? && @notification.target_status.direct_visibility? - end - - # Returns true if the sender has been mentioned by the recipient up the thread - def response_to_recipient? - return false if @notification.target_status.in_reply_to_id.nil? - - # Using an SQL CTE to avoid unneeded back-and-forth with SQL server in case of long threads - !Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id, depth_limit: 100]).zero? - WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS ( - SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0 - FROM statuses s - LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id - WHERE s.id = :id - UNION ALL - SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1 - FROM ancestors st - JOIN statuses s ON s.id = st.in_reply_to_id - LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id - WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit - ) - SELECT COUNT(*) - FROM ancestors st - JOIN statuses s ON s.id = st.id - WHERE st.mention_id IS NOT NULL AND s.visibility = 3 - SQL - end - - def from_staff? - @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user_role&.overrides?(@recipient.user_role) - end - - def optional_non_following_and_direct? - direct_message? && - @recipient.user.settings['interactions.must_be_following_dm'] && - !following_sender? && - !response_to_recipient? - end - - def hellbanned? - @notification.from_account.silenced? && !following_sender? - end - - def from_self? - @recipient.id == @notification.from_account.id - end - - def domain_blocking? - @recipient.domain_blocking?(@notification.from_account.domain) && !following_sender? - end - - def blocked? - blocked = @recipient.unavailable? - blocked ||= from_self? && @notification.type != :poll - - return blocked if message? && from_staff? - - blocked ||= domain_blocking? - blocked ||= @recipient.blocking?(@notification.from_account) - blocked ||= @recipient.muting_notifications?(@notification.from_account) - blocked ||= hellbanned? - blocked ||= optional_non_follower? - blocked ||= optional_non_following? - blocked ||= optional_non_following_and_direct? - blocked ||= conversation_muted? - blocked ||= blocked_mention? if @notification.type == :mention - blocked - end - - def conversation_muted? - if @notification.target_status - @recipient.muting_conversation?(@notification.target_status.conversation) - else - false - end + notification_request = NotificationRequest.find_or_initialize_by(account_id: @recipient.id, from_account_id: @notification.from_account_id) + notification_request.last_status_id = @notification.target_status.id + notification_request.save end def push_notification! @@ -150,6 +219,10 @@ class NotifyService < BaseService AccountConversation.add_status(@recipient, @notification.target_status) end + def direct_message? + @notification.type == :mention && @notification.target_status.direct_visibility? + end + def push_to_web_push_subscriptions! ::Web::PushNotificationWorker.push_bulk(web_push_subscriptions.select { |subscription| subscription.pushable?(@notification) }) { |subscription| [subscription.id, @notification.id] } end diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index d9d496c7f..de318dda5 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -40,11 +40,3 @@ label_method: ->(setting) { I18n.t("simple_form.labels.notification_emails.software_updates.#{setting}") }, label: I18n.t('simple_form.labels.notification_emails.software_updates.label'), wrapper: :with_label - - %h4= t 'notifications.other_settings' - - .fields-group - = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :'interactions.must_be_follower', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_follower') - = ff.input :'interactions.must_be_following', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following') - = ff.input :'interactions.must_be_following_dm', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following_dm') diff --git a/app/workers/unfilter_notifications_worker.rb b/app/workers/unfilter_notifications_worker.rb new file mode 100644 index 000000000..223654aa1 --- /dev/null +++ b/app/workers/unfilter_notifications_worker.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class UnfilterNotificationsWorker + include Sidekiq::Worker + + def perform(notification_request_id) + @notification_request = NotificationRequest.find(notification_request_id) + + push_to_conversations! + unfilter_notifications! + remove_request! + rescue ActiveRecord::RecordNotFound + true + end + + private + + def push_to_conversations! + notifications_with_private_mentions.find_each { |notification| AccountConversation.add_status(@notification_request.account, notification.target_status) } + end + + def unfilter_notifications! + filtered_notifications.in_batches.update_all(filtered: false) + end + + def remove_request! + @notification_request.destroy! + end + + def filtered_notifications + Notification.where(account: @notification_request.account, from_account: @notification_request.from_account, filtered: true) + end + + def notifications_with_private_mentions + filtered_notifications.joins(mention: :status).merge(Status.where(visibility: :direct)).includes(mention: :status) + end +end diff --git a/config/locales/an.yml b/config/locales/an.yml index edfdb44b3..7ad1986b2 100644 --- a/config/locales/an.yml +++ b/config/locales/an.yml @@ -1288,7 +1288,6 @@ an: notifications: email_events: Eventos pa notificacions per correu electronico email_events_hint: 'Tría los eventos pa los quals deseyas recibir notificacions:' - other_settings: Atros achustes de notificacions number: human: decimal_units: diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 2b2052172..8e9338d80 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1594,7 +1594,6 @@ ar: administration_emails: إشعارات البريد الإلكتروني الإدارية email_events: الأحداث للإشعارات عبر البريد الإلكتروني email_events_hint: 'اختر الأحداث التي تريد أن تصِلَك اشعارات عنها:' - other_settings: إعدادات أخرى للإشعارات number: human: decimal_units: diff --git a/config/locales/ast.yml b/config/locales/ast.yml index da7e99c2f..ee9105b05 100644 --- a/config/locales/ast.yml +++ b/config/locales/ast.yml @@ -695,7 +695,6 @@ ast: notifications: email_events: Unviu d'avisos per corréu electrónicu email_events_hint: 'Seleiciona los eventos de los que quies recibir avisos:' - other_settings: Configuración d'otros avisos number: human: decimal_units: diff --git a/config/locales/be.yml b/config/locales/be.yml index 34e0722ba..bd75870a1 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1547,7 +1547,6 @@ be: administration_emails: Апавяшчэнні эл. пошты для адміністратара email_events: Падзеі для апавяшчэнняў эл. пошты email_events_hint: 'Выберыце падзеі, аб якіх вы хочаце атрымліваць апавяшчэнні:' - other_settings: Іншыя налады апавяшчэнняў number: human: decimal_units: diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 15c671d53..3d1b6d291 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1490,7 +1490,6 @@ bg: administration_emails: Администраторски известия по имейла email_events: Събития за известия по имейл email_events_hint: 'Изберете събития, за които искате да получавате известия:' - other_settings: Настройки за други известия number: human: decimal_units: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index d80fb598e..d4213a258 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1495,7 +1495,6 @@ ca: administration_emails: Notificacions per correu-e de l'Admin email_events: Esdeveniments per a notificacions de correu electrònic email_events_hint: 'Selecciona els esdeveniments per als quals vols rebre notificacions:' - other_settings: Altres opcions de notificació number: human: decimal_units: diff --git a/config/locales/ckb.yml b/config/locales/ckb.yml index fec8f7332..7f1c28def 100644 --- a/config/locales/ckb.yml +++ b/config/locales/ckb.yml @@ -840,7 +840,6 @@ ckb: notifications: email_events: رووداوەکان بۆ ئاگاداری ئیمەیلی email_events_hint: 'ئەو ڕووداوانە دیاریبکە کە دەتەوێت ئاگانامەکان وەربگری بۆ:' - other_settings: ڕێکبەندەکانی ئاگانامەکانی تر otp_authentication: code_hint: کۆدێک داخڵ بکە کە دروست کراوە لەلایەن ئەپی ڕەسەنایەتیەوە بۆ دڵنیابوون description_html: ئەگەر تۆ هاتنەژوورەوەی دوو قۆناغی بە یارمەتی ئەپێکی پەسەندکردن چالاک بکەن، پێویستە بۆ چوونەژوورەوە ، بە تەلەفۆنەکەتان کە کۆدیکتان بۆ دروستدەکات دەستپێگەیشتنتان هەبێت. diff --git a/config/locales/co.yml b/config/locales/co.yml index c3c185c2f..ecf860645 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -809,7 +809,6 @@ co: notifications: email_events: Avvenimenti da nutificà cù l'e-mail email_events_hint: 'Selezziunate l''avvenimenti per quelli vulete riceve nutificazione:' - other_settings: Altri parametri di nutificazione number: human: decimal_units: diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 69640a261..fd24ac05c 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1547,7 +1547,6 @@ cs: administration_emails: E-mailová oznámení administrátora email_events: Události pro e-mailová oznámení email_events_hint: 'Vyberte události, pro které chcete dostávat oznámení:' - other_settings: Další nastavení oznámení number: human: decimal_units: diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 19343146f..241d59946 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -1599,7 +1599,6 @@ cy: administration_emails: Hysbysiadau e-bost gweinyddol email_events: Digwyddiadau ar gyfer hysbysiadau e-bost email_events_hint: 'Dewiswch ddigwyddiadau yr ydych am dderbyn hysbysiadau ar eu cyfer:' - other_settings: Gosodiadau hysbysiadau arall number: human: decimal_units: diff --git a/config/locales/da.yml b/config/locales/da.yml index 62e28cef1..6bc0d9679 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1495,7 +1495,6 @@ da: administration_emails: Admin e-mailnotifikationer email_events: Begivenheder for e-mailnotifikationer email_events_hint: 'Vælg begivenheder, for hvilke notifikationer skal modtages:' - other_settings: Andre notifikationsindstillinger number: human: decimal_units: diff --git a/config/locales/de.yml b/config/locales/de.yml index 123942672..063612237 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1495,7 +1495,6 @@ de: administration_emails: Admin-E-Mail-Benachrichtigungen email_events: Benachrichtigungen per E-Mail email_events_hint: 'Bitte die Ereignisse auswählen, für die du Benachrichtigungen per E-Mail erhalten möchtest:' - other_settings: Weitere Benachrichtigungseinstellungen number: human: decimal_units: diff --git a/config/locales/el.yml b/config/locales/el.yml index c641d4dca..12d70df97 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1383,7 +1383,6 @@ el: notifications: email_events: Συμβάντα για ειδοποιήσεις μέσω email email_events_hint: 'Επέλεξε συμβάντα για τα οποία θέλεις να λαμβάνεις ειδοποιήσεις μέσω email:' - other_settings: Άλλες ρυθμίσεις ειδοποιήσεων number: human: decimal_units: diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 6b2554fe1..ac7e50827 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1490,7 +1490,6 @@ en-GB: administration_emails: Admin e-mail notifications email_events: Events for e-mail notifications email_events_hint: 'Select events that you want to receive notifications for:' - other_settings: Other notifications settings number: human: decimal_units: diff --git a/config/locales/en.yml b/config/locales/en.yml index cff244a4b..8199fa52c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1495,7 +1495,6 @@ en: administration_emails: Admin e-mail notifications email_events: Events for e-mail notifications email_events_hint: 'Select events that you want to receive notifications for:' - other_settings: Other notifications settings number: human: decimal_units: diff --git a/config/locales/eo.yml b/config/locales/eo.yml index bc694578b..4e518cd19 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -1425,7 +1425,6 @@ eo: administration_emails: Admin retpoŝtaj sciigoj email_events: Eventoj por retpoŝtaj sciigoj email_events_hint: 'Elekti la eventojn pri kioj vi volas ricevi sciigojn:' - other_settings: Aliaj agordoj de sciigoj number: human: decimal_units: diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 37fcc11d7..06bacc79d 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1495,7 +1495,6 @@ es-AR: administration_emails: Notificaciones de administración por correo electrónico email_events: Eventos para notificaciones por correo electrónico email_events_hint: 'Seleccioná los eventos para los que querés recibir notificaciones:' - other_settings: Configuración de otras notificaciones number: human: decimal_units: diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 26dbc2dfb..4d2e8ff25 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1495,7 +1495,6 @@ es-MX: administration_emails: Notificaciones de administración por correo electrónico email_events: Eventos para notificaciones por correo electrónico email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:' - other_settings: Otros ajustes de notificaciones number: human: decimal_units: diff --git a/config/locales/es.yml b/config/locales/es.yml index 795658013..5204b116e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1495,7 +1495,6 @@ es: administration_emails: Notificaciones de administración por correo electrónico email_events: Eventos para notificaciones por correo electrónico email_events_hint: 'Selecciona los eventos para los que deseas recibir notificaciones:' - other_settings: Otros ajustes de notificaciones number: human: decimal_units: diff --git a/config/locales/et.yml b/config/locales/et.yml index ed25488ce..6bdb54a50 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1490,7 +1490,6 @@ et: administration_emails: Admini e-postiteavitused email_events: E-posti teadete sündmused email_events_hint: 'Vali sündmused, mille kohta soovid teavitusi:' - other_settings: Muud teadete sätted number: human: decimal_units: diff --git a/config/locales/eu.yml b/config/locales/eu.yml index fb3013e00..f49456afb 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1496,7 +1496,6 @@ eu: administration_emails: Administratzailearen posta elektroniko bidezko jakinarazpenak email_events: E-mail jakinarazpenentzako gertaerak email_events_hint: 'Hautatu jaso nahi dituzun gertaeren jakinarazpenak:' - other_settings: Bezte jakinarazpen konfigurazioak number: human: decimal_units: diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 4339e06c3..7fab495af 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -1271,7 +1271,6 @@ fa: notifications: email_events: رویدادها برای آگاهی‌های رایانامه‌ای email_events_hint: 'گزینش رویدادهایی که می‌خواهید برایشان آگاهی دریافت کنید:' - other_settings: سایر تنظیمات آگاهی‌ها number: human: decimal_units: diff --git a/config/locales/fi.yml b/config/locales/fi.yml index dc303991b..4cd255c2f 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1495,7 +1495,6 @@ fi: administration_emails: Ylläpitäjän sähköposti-ilmoitukset email_events: Sähköposti-ilmoitusten tapahtumat email_events_hint: 'Valitse tapahtumat, joista haluat saada ilmoituksia:' - other_settings: Muut ilmoitusasetukset number: human: decimal_units: diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 6a8dd9b2e..c7dca591f 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1495,7 +1495,6 @@ fo: administration_emails: Fráboðanir um teldupost til umsitarar email_events: Hendingar fyri teldupostfráboðanir email_events_hint: 'Vel hendingar, sum tú vil hava fráboðanir um:' - other_settings: Aðrar fráboðanarstillingar number: human: decimal_units: diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 97cb08c91..381a8f582 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1490,7 +1490,6 @@ fr-CA: administration_emails: Notifications par e-mail de l’admin email_events: Événements pour les notifications par courriel email_events_hint: 'Sélectionnez les événements pour lesquels vous souhaitez recevoir des notifications :' - other_settings: Autres paramètres de notifications number: human: decimal_units: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b085937c7..0d706accf 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1490,7 +1490,6 @@ fr: administration_emails: Notifications par e-mail de l’admin email_events: Événements pour les notifications par courriel email_events_hint: 'Sélectionnez les événements pour lesquels vous souhaitez recevoir des notifications :' - other_settings: Autres paramètres de notifications number: human: decimal_units: diff --git a/config/locales/fy.yml b/config/locales/fy.yml index caa88dcfe..2fb9f3e4d 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1490,7 +1490,6 @@ fy: administration_emails: E-mailmeldingen behearder email_events: E-mailmeldingen foar eveneminten email_events_hint: 'Selektearje eveneminten wêrfoar’t jo meldingen ûntfange wolle:' - other_settings: Oare meldingsynstellingen number: human: decimal_units: diff --git a/config/locales/gd.yml b/config/locales/gd.yml index e2a43564c..ede38df0d 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -1532,7 +1532,6 @@ gd: administration_emails: Brathan puist-d na rianachd email_events: Tachartasan nam brathan puist-d email_events_hint: 'Tagh na tachartasan dhan a bheil thu airson brathan fhaighinn:' - other_settings: Roghainnean eile nam brathan number: human: decimal_units: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 8520b90ea..c3d0f1d99 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1495,7 +1495,6 @@ gl: administration_emails: Notificacións de Admin por correo electrónico email_events: Eventos para os correos de notificación email_events_hint: 'Escolle os eventos sobre os que queres recibir notificacións:' - other_settings: Outros axustes das notificacións number: human: decimal_units: diff --git a/config/locales/he.yml b/config/locales/he.yml index 55ab57614..766a65377 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1547,7 +1547,6 @@ he: administration_emails: התראות לדוא"ל חשבון מנהל email_events: ארועים להתראות דוא"ל email_events_hint: 'בחר/י ארועים עבורים תרצה/י לקבל התראות:' - other_settings: הגדרות התראות אחרות number: human: decimal_units: diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 733d883da..00566514c 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1495,7 +1495,6 @@ hu: administration_emails: Adminisztrátori e-mail-értesítések email_events: Események email értesítésekhez email_events_hint: 'Válaszd ki azokat az eseményeket, melyekről értesítést szeretnél:' - other_settings: Értesítések egyéb beállításai number: human: decimal_units: diff --git a/config/locales/hy.yml b/config/locales/hy.yml index b0d8f0086..d870f5073 100644 --- a/config/locales/hy.yml +++ b/config/locales/hy.yml @@ -672,7 +672,6 @@ hy: subject: "%{name}-ը փոխել է գրառումը" notifications: email_events_hint: Ընտրիր իրադարձութիւնները, որոնց վերաբերեալ ցանկանում ես ստանալ ծանուցումներ․ - other_settings: Ծանուցումների այլ կարգաւորումներ number: human: decimal_units: diff --git a/config/locales/id.yml b/config/locales/id.yml index e7f42e115..a32ee8407 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -1257,7 +1257,6 @@ id: notifications: email_events: Event untuk notifikasi email email_events_hint: 'Pilih event yang ingin Anda terima notifikasinya:' - other_settings: Pengaturan notifikasi lain number: human: decimal_units: diff --git a/config/locales/ie.yml b/config/locales/ie.yml index eec8569bb..55aba94d1 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -1495,7 +1495,6 @@ ie: administration_emails: Email-notificationes pri administration email_events: Evenimentes por email-notificationes email_events_hint: 'Selecte li evenimentes pri queles tu vole reciver notificationes:' - other_settings: Parametres pri altri notificationes number: human: decimal_units: diff --git a/config/locales/io.yml b/config/locales/io.yml index 189f616a4..e85641b92 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -1472,7 +1472,6 @@ io: administration_emails: Jerala retpostonotifiki email_events: Eventi por retpostoavizi email_events_hint: 'Selektez eventi quon vu volas ganar avizi:' - other_settings: Altra avizopcioni number: human: decimal_units: diff --git a/config/locales/is.yml b/config/locales/is.yml index da6eee9e2..771ed2fbf 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1499,7 +1499,6 @@ is: administration_emails: Kerfisstjórnunartilkynningar í tölvupósti email_events: Atburðir fyrir tilkynningar í tölvupósti email_events_hint: 'Veldu þá atburði sem þú vilt fá tilkynningar í tölvupósti þegar þeir koma upp:' - other_settings: Aðrar stillingar varðandi tilkynningar number: human: decimal_units: diff --git a/config/locales/it.yml b/config/locales/it.yml index 3adb4f6c6..d85f0359d 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1497,7 +1497,6 @@ it: administration_emails: Notifiche email amministratore email_events: Eventi per notifiche via email email_events_hint: 'Seleziona gli eventi per i quali vuoi ricevere le notifiche:' - other_settings: Altre impostazioni delle notifiche number: human: decimal_units: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 460f02d0a..15e66631f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1469,7 +1469,6 @@ ja: administration_emails: 管理にかかわるメール通知 email_events: メールによる通知 email_events_hint: '受信する通知を選択:' - other_settings: その他の通知設定 number: human: decimal_units: diff --git a/config/locales/kab.yml b/config/locales/kab.yml index d42481092..d278c15c9 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -626,8 +626,6 @@ kab: subject: Yuder-ik·ikem-id %{name} reblog: subject: "%{name} yesselha addad-ik·im" - notifications: - other_settings: Iɣewwaṛen nniḍen n yilɣa number: human: decimal_units: diff --git a/config/locales/kk.yml b/config/locales/kk.yml index 2cd894bef..9528c0950 100644 --- a/config/locales/kk.yml +++ b/config/locales/kk.yml @@ -539,7 +539,6 @@ kk: notifications: email_events: E-mail ескертпелеріне шаралар email_events_hint: 'Ескертпе болып келетін шараларды таңда:' - other_settings: Ескертпелердің басқа баптаулары number: human: decimal_units: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 45a95139f..1e53006cb 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1471,7 +1471,6 @@ ko: administration_emails: 관리자 이메일 알림 email_events: 이메일 알림에 대한 이벤트 email_events_hint: '알림 받을 이벤트를 선택해주세요:' - other_settings: 기타 알림 설정 number: human: decimal_units: diff --git a/config/locales/ku.yml b/config/locales/ku.yml index d744aaa90..91ab75ceb 100644 --- a/config/locales/ku.yml +++ b/config/locales/ku.yml @@ -1285,7 +1285,6 @@ ku: notifications: email_events: Bûyer bo agahdariyên e-nameyê email_events_hint: 'Bûyera ku tu dixwazî agahdariyan jê wergerî hilbijêre:' - other_settings: Sazkariya agahdariyên din number: human: decimal_units: diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 13e29f927..bed6b44d3 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -1495,7 +1495,6 @@ lad: administration_emails: Avizos de administrasyon por posta email_events: Evenimyentos para avizos por posta email_events_hint: 'Eskoje los evenimientos para los kualos keres risivir avizos:' - other_settings: Otras preferensyas de avizos number: human: decimal_units: diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 89bdf5f05..dc4c39083 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1513,7 +1513,6 @@ lv: administration_emails: Administrators e-pasta paziņojumi email_events: E-pasta paziņojumu notikumi email_events_hint: 'Atlasi notikumus, par kuriem vēlies saņemt paziņojumus:' - other_settings: Citu paziņojumu iestatījumi number: human: decimal_units: diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 81f1851b1..e20dfd09e 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -1442,7 +1442,6 @@ ms: administration_emails: Notifikasi e-mel pentadbir email_events: Acara untuk pemberitahuan e-mel email_events_hint: 'Pilih acara yang ingin anda terima pemberitahuan:' - other_settings: Tetapan notifikasi lain number: human: decimal_units: diff --git a/config/locales/my.yml b/config/locales/my.yml index 18f5c6a2d..f2c115c17 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1445,7 +1445,6 @@ my: administration_emails: စီမံခန့်ခွဲသူ အီးမေးလ် အသိပေးချက်များ email_events: အီးမေးလ်သတိပေးချက်များအတွက်အကြောင်းအရာများ email_events_hint: အသိပေးချက်များရယူမည့် အစီအစဉ်များကို ရွေးပါ - - other_settings: အခြားအသိပေးချက်များ၏ သတ်မှတ်ချက်များ number: human: decimal_units: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index c935e9f4a..ac49efddf 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1495,7 +1495,6 @@ nl: administration_emails: E-mailmeldingen beheerder email_events: E-mailmeldingen voor gebeurtenissen email_events_hint: 'Selecteer gebeurtenissen waarvoor je meldingen wilt ontvangen:' - other_settings: Andere meldingsinstellingen number: human: decimal_units: diff --git a/config/locales/nn.yml b/config/locales/nn.yml index aecd148e2..52ed45a67 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1495,7 +1495,6 @@ nn: administration_emails: Administrator sine epost-varsler email_events: E-postvarslinger for hendelser email_events_hint: 'Velg hendelser som du vil motta varslinger for:' - other_settings: Andre varslingsinnstillinger number: human: decimal_units: diff --git a/config/locales/no.yml b/config/locales/no.yml index 481e28e86..db56a065f 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1490,7 +1490,6 @@ administration_emails: Administrators e-postvarslinger email_events: E-postvarslinger for hendelser email_events_hint: 'Velg hendelser som du vil motta varslinger for:' - other_settings: Andre varslingsinnstillinger number: human: decimal_units: diff --git a/config/locales/oc.yml b/config/locales/oc.yml index b8330992c..32b7da627 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -724,7 +724,6 @@ oc: notifications: email_events: Eveniments per las notificacions per corrièl email_events_hint: 'Seleccionatz los eveniments que volètz recebre :' - other_settings: Autres paramètres de notificacion number: human: decimal_units: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f7c5d60f6..9253f2d02 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1547,7 +1547,6 @@ pl: administration_emails: Administracyjne powiadomienia e-mail email_events: 'Powiadamiaj e-mailem o:' email_events_hint: 'Wybierz wydarzenia, o których chcesz otrzymywać powiadomienia:' - other_settings: Inne ustawienia powiadomień number: human: decimal_units: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 86972f9ef..58e734d9e 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1491,7 +1491,6 @@ pt-BR: administration_emails: Notificações por e-mail sobre administração email_events: Eventos para notificações por e-mail email_events_hint: 'Selecione os eventos que deseja receber notificações:' - other_settings: Outras opções para notificações number: human: decimal_units: diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 0786ba2ed..ebc7f84f4 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1495,7 +1495,6 @@ pt-PT: administration_emails: Notificções administrativas por e-mail email_events: Eventos para notificações por e-mail email_events_hint: 'Selecione os casos para os quais deseja receber notificações:' - other_settings: Outras opções de notificações number: human: decimal_units: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 3f15b064f..1c718c95b 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1542,7 +1542,6 @@ ru: administration_emails: Уведомления администратора по электронной почте email_events: События для e-mail уведомлений email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:' - other_settings: Остальные настройки уведомлений number: human: decimal_units: diff --git a/config/locales/sc.yml b/config/locales/sc.yml index 533764606..33ca7ab1d 100644 --- a/config/locales/sc.yml +++ b/config/locales/sc.yml @@ -865,7 +865,6 @@ sc: notifications: email_events: Eventos pro notìficas cun posta eletrònica email_events_hint: 'Seletziona eventos pro is chi boles retzire notìficas:' - other_settings: Àteras configuratziones de notìficas number: human: decimal_units: diff --git a/config/locales/sco.yml b/config/locales/sco.yml index dc273d2d8..d5628c01b 100644 --- a/config/locales/sco.yml +++ b/config/locales/sco.yml @@ -1275,7 +1275,6 @@ sco: notifications: email_events: Events fir email notes email_events_hint: 'Pick events thit ye''r wantin tae get notes fir:' - other_settings: Ither notes settins number: human: decimal_units: diff --git a/config/locales/si.yml b/config/locales/si.yml index ac292d6cf..28488197c 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -1144,7 +1144,6 @@ si: notifications: email_events: ඊමේල් දැනුම්දීම් සඳහා සිදුවීම් email_events_hint: 'ඔබට දැනුම්දීම් ලැබීමට අවශ්‍ය සිදුවීම් තෝරන්න:' - other_settings: වෙනත් දැනුම්දීම් සැකසුම් number: human: decimal_units: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index d97cfac0e..e93cec19f 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -1085,7 +1085,6 @@ sk: notifications: email_events: Udalosti oznamované emailom email_events_hint: 'Vyber si udalosti, pre ktoré chceš dostávať oboznámenia:' - other_settings: Ostatné oboznamovacie nastavenia otp_authentication: enable: Povoľ pagination: diff --git a/config/locales/sl.yml b/config/locales/sl.yml index c9b2343b6..863b3d724 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1547,7 +1547,6 @@ sl: administration_emails: E-poštna obvestila skrbnika email_events: Dogodki za e-obvestila email_events_hint: 'Izberite dogodke, za katere želite prejmati obvestila:' - other_settings: Druge nastavitve obvestil number: human: decimal_units: diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 4293271bb..d3d5a262f 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1491,7 +1491,6 @@ sq: administration_emails: Njoftime email për përgjegjësin email_events: Akte për njoftim me email email_events_hint: 'Përzgjidhni akte për të cilët doni të merrni njoftime:' - other_settings: Rregullimet të tjera njoftimesh number: human: decimal_units: diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index a1f6df067..7c7fb390f 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -1516,7 +1516,6 @@ sr-Latn: administration_emails: Obaveštenja e-poštom od administratora email_events: Događaji za obaveštenja e-poštom email_events_hint: 'Izaberite dešavanja za koja želite da primate obaveštenja:' - other_settings: Ostala podešavanja obaveštenja number: human: decimal_units: diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 494b41a75..c5b42e6b9 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1516,7 +1516,6 @@ sr: administration_emails: Обавештења е-поштом од администратора email_events: Догађаји за обавештења е-поштом email_events_hint: 'Изаберите дешавања за која желите да примате обавештења:' - other_settings: Остала подешавања обавештења number: human: decimal_units: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index d0f3994b4..108c17fc9 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1489,7 +1489,6 @@ sv: administration_emails: Admin e-postaviseringar email_events: Händelser för e-postnotiser email_events_hint: 'Välj händelser som du vill ta emot aviseringar för:' - other_settings: Andra aviseringsinställningar number: human: decimal_units: diff --git a/config/locales/ta.yml b/config/locales/ta.yml index b035a602c..3a22b572b 100644 --- a/config/locales/ta.yml +++ b/config/locales/ta.yml @@ -212,7 +212,6 @@ ta: notifications: email_events: மின்னஞ்சல் அறிவிப்புகளுக்கான நிகழ்வுகள் email_events_hint: 'எந்த நிகழ்வுகளுக்கு அறிவிப்புகளைப் பெற வேண்டும் என்று தேர்வு செய்க:' - other_settings: அறிவிப்புகள் குறித்த பிற அமைப்புகள் polls: errors: invalid_choice: நீங்கள் தேர்வு செய்த விருப்பம் கிடைக்கவில்லை diff --git a/config/locales/th.yml b/config/locales/th.yml index 5253dd6b7..b21230022 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1469,7 +1469,6 @@ th: administration_emails: การแจ้งเตือนอีเมลผู้ดูแล email_events: เหตุการณ์สำหรับการแจ้งเตือนอีเมล email_events_hint: 'เลือกเหตุการณ์ที่คุณต้องการรับการแจ้งเตือน:' - other_settings: การตั้งค่าการแจ้งเตือนอื่น ๆ number: human: decimal_units: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 7dbec9abb..3732e53ab 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1495,7 +1495,6 @@ tr: administration_emails: Yönetici e-posta bildirimleri email_events: E-posta bildirimi gönderilecek etkinlikler email_events_hint: 'Bildirim almak istediğiniz olayları seçin:' - other_settings: Diğer bildirim ayarları number: human: decimal_units: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 7273a0ff2..231f0028f 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1547,7 +1547,6 @@ uk: administration_emails: Сповіщення е-пошти адміністратора email_events: Події, про які сповіщати електронною поштою email_events_hint: 'Оберіть події, про які ви хочете отримувати сповіщення:' - other_settings: Інші налаштування сповіщень number: human: decimal_units: diff --git a/config/locales/vi.yml b/config/locales/vi.yml index e362c97a4..2afb6aa4c 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1467,7 +1467,6 @@ vi: administration_emails: Email thông báo admin email_events: Email email_events_hint: 'Chọn những hoạt động sẽ gửi thông báo qua email:' - other_settings: Chặn thông báo từ number: human: decimal_units: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 46a0e4015..93c741915 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1469,7 +1469,6 @@ zh-CN: administration_emails: 管理员电子邮件通知 email_events: 电子邮件通知事件 email_events_hint: 选择你想要收到通知的事件: - other_settings: 其它通知设置 number: human: decimal_units: diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index e666e4965..62649864c 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1464,7 +1464,6 @@ zh-HK: administration_emails: 管理員電郵通知 email_events: 電郵通知活動 email_events_hint: 選擇你想接收通知的活動: - other_settings: 其他通知設定 number: human: decimal_units: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 3cd358383..f50f685bb 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1471,7 +1471,6 @@ zh-TW: administration_emails: 管理員電子郵件通知 email_events: 電子郵件通知設定 email_events_hint: 選取您想接收通知的事件: - other_settings: 其他通知設定 number: human: decimal_units: diff --git a/config/routes/api.rb b/config/routes/api.rb index 853a44e0e..18a247e9f 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -150,6 +150,17 @@ namespace :api, format: false do end end + namespace :notifications do + resources :requests, only: :index do + member do + post :accept + post :dismiss + end + end + + resource :policy, only: [:show, :update] + end + resources :notifications, only: [:index, :show] do collection do post :clear diff --git a/db/migrate/20240221195424_add_filtered_to_notifications.rb b/db/migrate/20240221195424_add_filtered_to_notifications.rb new file mode 100644 index 000000000..99e98a58b --- /dev/null +++ b/db/migrate/20240221195424_add_filtered_to_notifications.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddFilteredToNotifications < ActiveRecord::Migration[7.1] + def change + add_column :notifications, :filtered, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20240221195828_create_notification_requests.rb b/db/migrate/20240221195828_create_notification_requests.rb new file mode 100644 index 000000000..98aa63040 --- /dev/null +++ b/db/migrate/20240221195828_create_notification_requests.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateNotificationRequests < ActiveRecord::Migration[7.1] + def change + create_table :notification_requests do |t| + t.references :account, null: false, foreign_key: { on_delete: :cascade }, index: false + t.references :from_account, null: false, foreign_key: { to_table: :accounts, on_delete: :cascade } + t.references :last_status, null: false, foreign_key: { to_table: :statuses, on_delete: :nullify } + t.bigint :notifications_count, null: false, default: 0 + t.boolean :dismissed, null: false, default: false + + t.timestamps + end + + add_index :notification_requests, [:account_id, :from_account_id], unique: true + add_index :notification_requests, [:account_id, :id], where: 'dismissed = false', order: { id: :desc } + end +end diff --git a/db/migrate/20240221211359_notification_request_ids_to_timestamp_ids.rb b/db/migrate/20240221211359_notification_request_ids_to_timestamp_ids.rb new file mode 100644 index 000000000..8503f452f --- /dev/null +++ b/db/migrate/20240221211359_notification_request_ids_to_timestamp_ids.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class NotificationRequestIdsToTimestampIds < ActiveRecord::Migration[7.1] + def up + safety_assured do + execute("ALTER TABLE notification_requests ALTER COLUMN id SET DEFAULT timestamp_id('notification_requests')") + end + end + + def down + execute('LOCK notification_requests') + execute("SELECT setval('notification_requests_id_seq', (SELECT MAX(id) FROM notification_requests))") + execute("ALTER TABLE notification_requests ALTER COLUMN id SET DEFAULT nextval('notification_requests_id_seq')") + end +end diff --git a/db/migrate/20240222193403_create_notification_permissions.rb b/db/migrate/20240222193403_create_notification_permissions.rb new file mode 100644 index 000000000..6e2b6196c --- /dev/null +++ b/db/migrate/20240222193403_create_notification_permissions.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CreateNotificationPermissions < ActiveRecord::Migration[7.1] + def change + create_table :notification_permissions do |t| + t.references :account, null: false, foreign_key: true + t.references :from_account, null: false, foreign_key: { to_table: :accounts } + + t.timestamps + end + end +end diff --git a/db/migrate/20240222203722_create_notification_policies.rb b/db/migrate/20240222203722_create_notification_policies.rb new file mode 100644 index 000000000..e9d35510a --- /dev/null +++ b/db/migrate/20240222203722_create_notification_policies.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class CreateNotificationPolicies < ActiveRecord::Migration[7.1] + def change + create_table :notification_policies do |t| + t.references :account, null: false, foreign_key: true, index: { unique: true } + t.boolean :filter_not_following, null: false, default: false + t.boolean :filter_not_followers, null: false, default: false + t.boolean :filter_new_accounts, null: false, default: false + t.boolean :filter_private_mentions, null: false, default: true + + t.timestamps + end + end +end diff --git a/db/migrate/20240227191620_add_filtered_index_on_notifications.rb b/db/migrate/20240227191620_add_filtered_index_on_notifications.rb new file mode 100644 index 000000000..ca3445247 --- /dev/null +++ b/db/migrate/20240227191620_add_filtered_index_on_notifications.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddFilteredIndexOnNotifications < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_index :notifications, [:account_id, :id, :type], where: 'filtered = false', order: { id: :desc }, name: 'index_notifications_on_filtered', algorithm: :concurrently + end +end diff --git a/db/migrate/20240304090449_migrate_interaction_settings_to_policy.rb b/db/migrate/20240304090449_migrate_interaction_settings_to_policy.rb new file mode 100644 index 000000000..a167baadc --- /dev/null +++ b/db/migrate/20240304090449_migrate_interaction_settings_to_policy.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class MigrateInteractionSettingsToPolicy < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + # Dummy classes, to make migration possible across version changes + class Account < ApplicationRecord + has_one :user, inverse_of: :account + has_one :notification_policy, inverse_of: :account + end + + class User < ApplicationRecord + belongs_to :account + end + + class NotificationPolicy < ApplicationRecord + belongs_to :account + end + + def up + User.includes(account: :notification_policy).find_each do |user| + deserialized_settings = Oj.load(user.attributes_before_type_cast['settings']) + policy = user.account.notification_policy || user.account.build_notification_policy + requires_new_policy = false + + if deserialized_settings['interactions.must_be_follower'] + policy.filter_not_followers = true + requires_new_policy = true + end + + if deserialized_settings['interactions.must_be_following'] + policy.filter_not_following = true + requires_new_policy = true + end + + if deserialized_settings['interactions.must_be_following_dm'] + policy.filter_private_mentions = true + requires_new_policy = true + end + + policy.save if requires_new_policy && policy.changed? + end + end + + def down; end +end diff --git a/db/schema.rb b/db/schema.rb index 50f4e7189..97917d045 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do +ActiveRecord::Schema[7.1].define(version: 2024_03_04_090449) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -666,6 +666,40 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do t.index ["target_account_id"], name: "index_mutes_on_target_account_id" end + create_table "notification_permissions", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "from_account_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_notification_permissions_on_account_id" + t.index ["from_account_id"], name: "index_notification_permissions_on_from_account_id" + end + + create_table "notification_policies", force: :cascade do |t| + t.bigint "account_id", null: false + t.boolean "filter_not_following", default: false, null: false + t.boolean "filter_not_followers", default: false, null: false + t.boolean "filter_new_accounts", default: false, null: false + t.boolean "filter_private_mentions", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_notification_policies_on_account_id", unique: true + end + + create_table "notification_requests", id: :bigint, default: -> { "timestamp_id('notification_requests'::text)" }, force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "from_account_id", null: false + t.bigint "last_status_id", null: false + t.bigint "notifications_count", default: 0, null: false + t.boolean "dismissed", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "from_account_id"], name: "index_notification_requests_on_account_id_and_from_account_id", unique: true + t.index ["account_id", "id"], name: "index_notification_requests_on_account_id_and_id", order: { id: :desc }, where: "(dismissed = false)" + t.index ["from_account_id"], name: "index_notification_requests_on_from_account_id" + t.index ["last_status_id"], name: "index_notification_requests_on_last_status_id" + end + create_table "notifications", force: :cascade do |t| t.bigint "activity_id", null: false t.string "activity_type", null: false @@ -674,7 +708,9 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do t.bigint "account_id", null: false t.bigint "from_account_id", null: false t.string "type" + t.boolean "filtered", default: false, null: false t.index ["account_id", "id", "type"], name: "index_notifications_on_account_id_and_id_and_type", order: { id: :desc } + t.index ["account_id", "id", "type"], name: "index_notifications_on_filtered", order: { id: :desc }, where: "(filtered = false)" t.index ["activity_id", "activity_type"], name: "index_notifications_on_activity_id_and_activity_type" t.index ["from_account_id"], name: "index_notifications_on_from_account_id" end @@ -1255,6 +1291,12 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do add_foreign_key "mentions", "statuses", on_delete: :cascade add_foreign_key "mutes", "accounts", column: "target_account_id", name: "fk_eecff219ea", on_delete: :cascade add_foreign_key "mutes", "accounts", name: "fk_b8d8daf315", on_delete: :cascade + add_foreign_key "notification_permissions", "accounts" + add_foreign_key "notification_permissions", "accounts", column: "from_account_id" + add_foreign_key "notification_policies", "accounts" + add_foreign_key "notification_requests", "accounts", column: "from_account_id", on_delete: :cascade + add_foreign_key "notification_requests", "accounts", on_delete: :cascade + add_foreign_key "notification_requests", "statuses", column: "last_status_id", on_delete: :nullify add_foreign_key "notifications", "accounts", column: "from_account_id", name: "fk_fbd6b0bf9e", on_delete: :cascade add_foreign_key "notifications", "accounts", name: "fk_c141c8ee55", on_delete: :cascade add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id", name: "fk_34d54b0a33", on_delete: :cascade diff --git a/spec/controllers/settings/preferences/notifications_controller_spec.rb b/spec/controllers/settings/preferences/notifications_controller_spec.rb index b61d7461c..e0f0bc55a 100644 --- a/spec/controllers/settings/preferences/notifications_controller_spec.rb +++ b/spec/controllers/settings/preferences/notifications_controller_spec.rb @@ -24,14 +24,13 @@ describe Settings::Preferences::NotificationsController do describe 'PUT #update' do it 'updates notifications settings' do - user.settings.update('notification_emails.follow': false, 'interactions.must_be_follower': true) + user.settings.update('notification_emails.follow': false) user.save put :update, params: { user: { settings_attributes: { 'notification_emails.follow': '1', - 'interactions.must_be_follower': '0', }, }, } @@ -39,7 +38,6 @@ describe Settings::Preferences::NotificationsController do expect(response).to redirect_to(settings_preferences_notifications_path) user.reload expect(user.settings['notification_emails.follow']).to be true - expect(user.settings['interactions.must_be_follower']).to be false end end end diff --git a/spec/fabricators/notification_permission_fabricator.rb b/spec/fabricators/notification_permission_fabricator.rb new file mode 100644 index 000000000..d421ddd81 --- /dev/null +++ b/spec/fabricators/notification_permission_fabricator.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Fabricator(:notification_permission) do + account + from_account { Fabricate.build(:account) } +end diff --git a/spec/fabricators/notification_policy_fabricator.rb b/spec/fabricators/notification_policy_fabricator.rb new file mode 100644 index 000000000..f33438fec --- /dev/null +++ b/spec/fabricators/notification_policy_fabricator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +Fabricator(:notification_policy) do + account + filter_not_following false + filter_not_followers false + filter_new_accounts false + filter_private_mentions true +end diff --git a/spec/fabricators/notification_request_fabricator.rb b/spec/fabricators/notification_request_fabricator.rb new file mode 100644 index 000000000..05a13b8ef --- /dev/null +++ b/spec/fabricators/notification_request_fabricator.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +Fabricator(:notification_request) do + account + from_account { Fabricate.build(:account) } + last_status { Fabricate.build(:status) } + dismissed false +end diff --git a/spec/models/notification_policy_spec.rb b/spec/models/notification_policy_spec.rb new file mode 100644 index 000000000..bbfa548cf --- /dev/null +++ b/spec/models/notification_policy_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe NotificationPolicy do + describe '#summarize!' do + subject { Fabricate(:notification_policy) } + + let(:sender) { Fabricate(:account) } + + before do + Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender)) + Fabricate(:notification_request, account: subject.account, from_account: sender) + subject.summarize! + end + + it 'sets pending_requests_count' do + expect(subject.pending_requests_count).to eq 1 + end + + it 'sets pending_notifications_count' do + expect(subject.pending_notifications_count).to eq 2 + end + end +end diff --git a/spec/models/notification_request_spec.rb b/spec/models/notification_request_spec.rb new file mode 100644 index 000000000..f4613aaed --- /dev/null +++ b/spec/models/notification_request_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe NotificationRequest do + describe '#reconsider_existence!' do + subject { Fabricate(:notification_request, dismissed: dismissed) } + + let(:dismissed) { false } + + context 'when there are remaining notifications' do + before do + Fabricate(:notification, account: subject.account, activity: Fabricate(:status, account: subject.from_account)) + subject.reconsider_existence! + end + + it 'leaves request intact' do + expect(subject.destroyed?).to be false + end + + it 'updates notifications_count' do + expect(subject.notifications_count).to eq 1 + end + end + + context 'when there are no notifications' do + before do + subject.reconsider_existence! + end + + context 'when dismissed' do + let(:dismissed) { true } + + it 'leaves request intact' do + expect(subject.destroyed?).to be false + end + end + + it 'removes the request' do + expect(subject.destroyed?).to be true + end + end + end +end diff --git a/spec/requests/api/v1/conversations_spec.rb b/spec/requests/api/v1/conversations_spec.rb index e2327d9a9..caa0f5c52 100644 --- a/spec/requests/api/v1/conversations_spec.rb +++ b/spec/requests/api/v1/conversations_spec.rb @@ -12,6 +12,7 @@ RSpec.describe 'API V1 Conversations' do describe 'GET /api/v1/conversations', :sidekiq_inline do before do + user.account.follow!(other.account) PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct') PostStatusService.new.call(user.account, text: 'Hey, nobody here', visibility: 'direct') end diff --git a/spec/requests/api/v1/notifications/policies_spec.rb b/spec/requests/api/v1/notifications/policies_spec.rb new file mode 100644 index 000000000..fe6bdbd97 --- /dev/null +++ b/spec/requests/api/v1/notifications/policies_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Policies' do + let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:notifications write:notifications' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/notifications/policy', :sidekiq_inline do + subject do + get '/api/v1/notifications/policy', headers: headers, params: params + end + + let(:params) { {} } + + before do + Fabricate(:notification_request, account: user.account) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:notifications' + + context 'with no options' do + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + end + end + end + + describe 'PUT /api/v1/notifications/policy' do + subject do + put '/api/v1/notifications/policy', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:notifications' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/requests/api/v1/notifications/requests_spec.rb b/spec/requests/api/v1/notifications/requests_spec.rb new file mode 100644 index 000000000..64675d562 --- /dev/null +++ b/spec/requests/api/v1/notifications/requests_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Requests' do + let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:notifications write:notifications' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/notifications/requests', :sidekiq_inline do + subject do + get '/api/v1/notifications/requests', headers: headers, params: params + end + + let(:params) { {} } + + before do + Fabricate(:notification_request, account: user.account) + Fabricate(:notification_request, account: user.account, dismissed: true) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:notifications' + + context 'with no options' do + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with dismissed' do + let(:params) { { dismissed: '1' } } + + it 'returns http success', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + end + end + end + + describe 'POST /api/v1/notifications/requests/:id/accept' do + subject do + post "/api/v1/notifications/requests/#{notification_request.id}/accept", headers: headers + end + + let(:notification_request) { Fabricate(:notification_request, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:notifications' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'creates notification permission' do + subject + + expect(NotificationPermission.find_by(account: notification_request.account, from_account: notification_request.from_account)).to_not be_nil + end + + context 'when notification request belongs to someone else' do + let(:notification_request) { Fabricate(:notification_request) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/notifications/requests/:id/dismiss' do + subject do + post "/api/v1/notifications/requests/#{notification_request.id}/dismiss", headers: headers + end + + let(:notification_request) { Fabricate(:notification_request, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:notifications' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'dismisses the notification request' do + subject + + expect(notification_request.reload.dismissed?).to be true + end + + context 'when notification request belongs to someone else' do + let(:notification_request) { Fabricate(:notification_request) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index e818fadcb..57ff326c7 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -41,7 +41,8 @@ RSpec.describe NotifyService, type: :service do it 'does not notify when sender is silenced and not followed' do sender.silence! - expect { subject }.to_not change(Notification, :count) + subject + expect(Notification.find_by(activity: activity).filtered?).to be true end it 'does not notify when recipient is suspended' do @@ -49,66 +50,6 @@ RSpec.describe NotifyService, type: :service do expect { subject }.to_not change(Notification, :count) end - context 'with direct messages' do - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) } - let(:type) { :mention } - - before do - user.settings.update('interactions.must_be_following_dm': enabled) - user.save - end - - context 'when recipient is supposed to be following sender' do - let(:enabled) { true } - - it 'does not notify' do - expect { subject }.to_not change(Notification, :count) - end - - context 'when the message chain is initiated by recipient, but is not direct message' do - let(:reply_to) { Fabricate(:status, account: recipient) } - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } - - before { Fabricate(:mention, account: sender, status: reply_to) } - - it 'does not notify' do - expect { subject }.to_not change(Notification, :count) - end - end - - context 'when the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do - let(:reply_to) { Fabricate(:status, account: recipient) } - let(:dummy_reply) { Fabricate(:status, account: sender, visibility: :direct, thread: reply_to) } - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) } - - before { Fabricate(:mention, account: sender, status: reply_to) } - - it 'does not notify' do - expect { subject }.to_not change(Notification, :count) - end - end - - context 'when the message chain is initiated by the recipient with a mention to the sender' do - let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) } - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } - - before { Fabricate(:mention, account: sender, status: reply_to) } - - it 'does notify' do - expect { subject }.to change(Notification, :count) - end - end - end - - context 'when recipient is NOT supposed to be following sender' do - let(:enabled) { false } - - it 'does notify' do - expect { subject }.to change(Notification, :count) - end - end - end - describe 'reblogs' do let(:status) { Fabricate(:status, account: Fabricate(:account)) } let(:activity) { Fabricate(:status, account: sender, reblog: status) } @@ -187,4 +128,189 @@ RSpec.describe NotifyService, type: :service do end end end + + describe NotifyService::FilterCondition do + subject { described_class.new(notification) } + + let(:activity) { Fabricate(:mention, status: Fabricate(:status)) } + let(:notification) { Fabricate(:notification, type: :mention, activity: activity, from_account: activity.status.account, account: activity.account) } + + describe '#filter?' do + context 'when sender is silenced' do + before do + notification.from_account.silence! + end + + it 'returns true' do + expect(subject.filter?).to be true + end + + context 'when recipient follows sender' do + before do + notification.account.follow!(notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + end + + context 'when recipient is filtering not-followed senders' do + before do + Fabricate(:notification_policy, account: notification.account, filter_not_following: true) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + + context 'when sender has permission' do + before do + Fabricate(:notification_permission, account: notification.account, from_account: notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + + context 'when sender is followed by recipient' do + before do + notification.account.follow!(notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + end + + context 'when recipient is filtering not-followers' do + before do + Fabricate(:notification_policy, account: notification.account, filter_not_followers: true) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + + context 'when sender has permission' do + before do + Fabricate(:notification_permission, account: notification.account, from_account: notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + + context 'when sender follows recipient' do + before do + notification.from_account.follow!(notification.account) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + end + + context 'when sender follows recipient for longer than 3 days' do + before do + follow = notification.from_account.follow!(notification.account) + follow.update(created_at: 4.days.ago) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + end + + context 'when recipient is filtering new accounts' do + before do + Fabricate(:notification_policy, account: notification.account, filter_new_accounts: true) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + + context 'when sender has permission' do + before do + Fabricate(:notification_permission, account: notification.account, from_account: notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + + context 'when sender is older than 30 days' do + before do + notification.from_account.update(created_at: 31.days.ago) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + end + + context 'when recipient is not filtering anyone' do + before do + Fabricate(:notification_policy, account: notification.account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + + context 'when recipient is filtering unsolicited private mentions' do + before do + Fabricate(:notification_policy, account: notification.account, filter_private_mentions: true) + end + + context 'when notification is not a private mention' do + it 'returns false' do + expect(subject.filter?).to be false + end + end + + context 'when notification is a private mention' do + before do + notification.target_status.update(visibility: :direct) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + + context 'when the message chain is initiated by recipient, but sender is not mentioned' do + before do + original_status = Fabricate(:status, account: notification.account, visibility: :direct) + notification.target_status.update(thread: original_status) + end + + it 'returns true' do + expect(subject.filter?).to be true + end + end + + context 'when the message chain is initiated by recipient, and sender is mentioned' do + before do + original_status = Fabricate(:status, account: notification.account, visibility: :direct) + notification.target_status.update(thread: original_status) + Fabricate(:mention, status: original_status, account: notification.from_account) + end + + it 'returns false' do + expect(subject.filter?).to be false + end + end + end + end + end + end end