summaryrefslogtreecommitdiff
path: root/app/models/notification_recipient.rb
blob: 183e098d819076cadbd660d77674b9e036a560f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class NotificationRecipient
  attr_reader :user, :type
  def initialize(
    user, type,
    custom_action: nil,
    target: nil,
    acting_user: nil,
    project: nil,
    group: nil,
    skip_read_ability: false
  )
    unless NotificationSetting.levels.key?(type) || type == :subscription
      raise ArgumentError, "invalid type: #{type.inspect}"
    end

    @custom_action = custom_action
    @acting_user = acting_user
    @target = target
    @project = project || default_project
    @group = group || @project&.group
    @user = user
    @type = type
    @skip_read_ability = skip_read_ability
  end

  def notification_setting
    @notification_setting ||= find_notification_setting
  end

  def notification_level
    @notification_level ||= notification_setting&.level&.to_sym
  end

  def notifiable?
    return false unless has_access?
    return false if own_activity?

    # even users with :disabled notifications receive manual subscriptions
    return !unsubscribed? if @type == :subscription

    return false unless suitable_notification_level?

    # check this last because it's expensive
    # nobody should receive notifications if they've specifically unsubscribed
    return false if unsubscribed?

    true
  end

  def suitable_notification_level?
    case notification_level
    when :disabled, nil
      false
    when :custom
      custom_enabled? || %i[participating mention].include?(@type)
    when :watch, :participating
      !excluded_watcher_action?
    when :mention
      @type == :mention
    else
      false
    end
  end

  def custom_enabled?
    @custom_action && notification_setting&.event_enabled?(@custom_action)
  end

  def unsubscribed?
    return false unless @target
    return false unless @target.respond_to?(:subscriptions)

    subscription = @target.subscriptions.find_by_user_id(@user.id)
    subscription && !subscription.subscribed
  end

  def own_activity?
    return false unless @acting_user
    return false if @acting_user.notified_of_own_activity?

    user == @acting_user
  end

  def has_access?
    DeclarativePolicy.subject_scope do
      return false unless user.can?(:receive_notifications)
      return true if @skip_read_ability

      return false if @project && !user.can?(:read_project, @project)

      return true unless read_ability
      return true unless DeclarativePolicy.has_policy?(@target)

      user.can?(read_ability, @target)
    end
  end

  def excluded_watcher_action?
    return false unless @custom_action
    return false if notification_level == :custom

    NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(@custom_action)
  end

  private

  def read_ability
    return nil if @skip_read_ability
    return @read_ability if instance_variable_defined?(:@read_ability)

    @read_ability =
      case @target
      when Issuable
        :"read_#{@target.to_ability_name}"
      when Ci::Pipeline
        :read_build # We have build trace in pipeline emails
      when ActiveRecord::Base
        :"read_#{@target.class.model_name.name.underscore}"
      else
        nil
      end
  end

  def default_project
    return nil if @target.nil?
    return @target if @target.is_a?(Project)
    return @target.project if @target.respond_to?(:project)
  end

  def find_notification_setting
    project_setting = @project && user.notification_settings_for(@project)

    return project_setting unless project_setting.nil? || project_setting.global?

    group_setting = @group && user.notification_settings_for(@group)

    return group_setting unless group_setting.nil? || group_setting.global?

    user.global_notification_setting
  end
end