Merge develop
[akkoma] / lib / pleroma / subscription_notification.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.SubscriptionNotification do
6 use Ecto.Schema
7
8 alias Pleroma.Activity
9 alias Pleroma.Object
10 alias Pleroma.Pagination
11 alias Pleroma.Repo
12 alias Pleroma.SubscriptionNotification
13 alias Pleroma.User
14 alias Pleroma.Web.CommonAPI.Utils
15 alias Pleroma.Web.Push
16 alias Pleroma.Web.Streamer
17
18 import Ecto.Query
19 import Ecto.Changeset
20
21 @type t :: %__MODULE__{}
22
23 schema "subscription_notifications" do
24 belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
25 belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
26
27 timestamps()
28 end
29
30 def changeset(%SubscriptionNotification{} = notification, attrs) do
31 cast(notification, attrs, [])
32 end
33
34 def for_user_query(user, opts \\ []) do
35 query =
36 SubscriptionNotification
37 |> where(user_id: ^user.id)
38 |> where(
39 [n, a],
40 fragment(
41 "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
42 a.actor
43 )
44 )
45 |> join(:inner, [n], activity in assoc(n, :activity))
46 |> join(:left, [n, a], object in Object,
47 on:
48 fragment(
49 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
50 object.data,
51 a.data
52 )
53 )
54 |> preload([n, a, o], activity: {a, object: o})
55
56 if opts[:with_muted] do
57 query
58 else
59 query
60 |> where([n, a], a.actor not in ^user.info.muted_notifications)
61 |> where([n, a], a.actor not in ^user.info.blocks)
62 |> where(
63 [n, a],
64 fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
65 )
66 |> join(:left, [n, a], tm in Pleroma.ThreadMute,
67 on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
68 )
69 |> where([n, a, o, tm], is_nil(tm.user_id))
70 end
71 end
72
73 def for_user(user, opts \\ %{}) do
74 user
75 |> for_user_query(opts)
76 |> Pagination.fetch_paginated(opts)
77 end
78
79 @doc """
80 Returns notifications for user received since given date.
81
82 ## Examples
83
84 iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
85 [%Pleroma.SubscriptionNotification{}, %Pleroma.SubscriptionNotification{}]
86
87 iex> Pleroma.SubscriptionNotification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
88 []
89 """
90 @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
91 def for_user_since(user, date) do
92 user
93 |> for_user_query()
94 |> where([n], n.updated_at > ^date)
95 |> Repo.all()
96 end
97
98 def clear_up_to(%{id: user_id} = _user, id) do
99 from(
100 n in SubscriptionNotification,
101 where: n.user_id == ^user_id,
102 where: n.id <= ^id
103 )
104 |> Repo.delete_all([])
105 end
106
107 def get(%{id: user_id} = _user, id) do
108 query =
109 from(
110 n in SubscriptionNotification,
111 where: n.id == ^id,
112 join: activity in assoc(n, :activity),
113 preload: [activity: activity]
114 )
115
116 case Repo.one(query) do
117 %{user_id: ^user_id} = notification ->
118 {:ok, notification}
119
120 _ ->
121 {:error, "Cannot get notification"}
122 end
123 end
124
125 def clear(user) do
126 from(n in SubscriptionNotification, where: n.user_id == ^user.id)
127 |> Repo.delete_all()
128 end
129
130 def destroy_multiple(%{id: user_id} = _user, ids) do
131 from(n in SubscriptionNotification,
132 where: n.id in ^ids,
133 where: n.user_id == ^user_id
134 )
135 |> Repo.delete_all()
136 end
137
138 def dismiss(%{id: user_id} = _user, id) do
139 case Repo.get(SubscriptionNotification, id) do
140 %{user_id: ^user_id} = notification ->
141 Repo.delete(notification)
142
143 _ ->
144 {:error, "Cannot dismiss notification"}
145 end
146 end
147
148 def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
149 case Object.normalize(activity) do
150 %{data: %{"type" => "Answer"}} ->
151 {:ok, []}
152
153 _ ->
154 users = get_notified_from_activity(activity)
155 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
156 {:ok, notifications}
157 end
158 end
159
160 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
161 when type in ["Like", "Announce", "Follow"] do
162 notifications =
163 activity
164 |> get_notified_from_activity()
165 |> Enum.map(&create_notification(activity, &1))
166
167 {:ok, notifications}
168 end
169
170 def create_notifications(_), do: {:ok, []}
171
172 # TODO move to sql, too.
173 def create_notification(%Activity{} = activity, %User{} = user) do
174 unless skip?(activity, user) do
175 notification = %SubscriptionNotification{user_id: user.id, activity: activity}
176 {:ok, notification} = Repo.insert(notification)
177 Streamer.stream("user", notification)
178 Streamer.stream("user:subscription_notification", notification)
179 Push.send(notification)
180 notification
181 end
182 end
183
184 def get_notified_from_activity(activity, local_only \\ true)
185
186 def get_notified_from_activity(
187 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
188 local_only
189 )
190 when type in ["Create", "Like", "Announce", "Follow"] do
191 []
192 |> Utils.maybe_notify_subscribers(activity)
193 |> Enum.uniq()
194 |> User.get_users_from_set(local_only)
195 end
196
197 def get_notified_from_activity(_, _local_only), do: []
198
199 @spec skip?(Activity.t(), User.t()) :: boolean()
200 def skip?(activity, user) do
201 [
202 :self,
203 :followers,
204 :follows,
205 :non_followers,
206 :non_follows,
207 :recently_followed
208 ]
209 |> Enum.any?(&skip?(&1, activity, user))
210 end
211
212 @spec skip?(atom(), Activity.t(), User.t()) :: boolean()
213 def skip?(:self, activity, user) do
214 activity.data["actor"] == user.ap_id
215 end
216
217 def skip?(
218 :followers,
219 %{data: %{"actor" => actor}},
220 %{info: %{notification_settings: %{"followers" => false}}} = user
221 ) do
222 actor
223 |> User.get_cached_by_ap_id()
224 |> User.following?(user)
225 end
226
227 def skip?(
228 :non_followers,
229 activity,
230 %{info: %{notification_settings: %{"non_followers" => false}}} = user
231 ) do
232 actor = activity.data["actor"]
233 follower = User.get_cached_by_ap_id(actor)
234 !User.following?(follower, user)
235 end
236
237 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
238 actor = activity.data["actor"]
239 followed = User.get_cached_by_ap_id(actor)
240 User.following?(user, followed)
241 end
242
243 def skip?(
244 :non_follows,
245 activity,
246 %{info: %{notification_settings: %{"non_follows" => false}}} = user
247 ) do
248 actor = activity.data["actor"]
249 followed = User.get_cached_by_ap_id(actor)
250 !User.following?(user, followed)
251 end
252
253 def skip?(:recently_followed, %{data: %{"type" => "Follow", "actor" => actor}}, user) do
254 user
255 |> SubscriptionNotification.for_user()
256 |> Enum.any?(&match?(%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}}, &1))
257 end
258
259 def skip?(_, _, _), do: false
260 end