summaryrefslogtreecommitdiff
path: root/app/models/notification_recipient.rb
blob: dc862565a71de19e48332c04e50b66710c2d4cd8 (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
142
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 raw_notification_level
    notification_setting&.level&.to_sym
  end

  def notification_level
    # custom is treated the same as watch if it's enabled - otherwise it's
    # set to :custom, meaning to send exactly when our type is :participating
    # or :mention.
    @notification_level ||=
      case raw_notification_level
      when :custom
        if @custom_action && notification_setting&.event_enabled?(@custom_action)
          :watch
        else
          :custom
        end
      else
        raw_notification_level
      end
  end

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

    return true if @type == :subscription

    return false if notification_level.nil? || notification_level == :disabled

    return %i[participating mention].include?(@type) if notification_level == :custom

    return false if %i[watch participating].include?(notification_level) && excluded_watcher_action?

    return false unless NotificationSetting.levels[notification_level] <= NotificationSetting.levels[@type]

    return false if unsubscribed?

    true
  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 raw_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