Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
[akkoma] / lib / pleroma / 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.Notification do
6 use Ecto.Schema
7
8 alias Pleroma.Activity
9 alias Pleroma.Notification
10 alias Pleroma.Object
11 alias Pleroma.Pagination
12 alias Pleroma.Repo
13 alias Pleroma.User
14 alias Pleroma.Web.CommonAPI
15 alias Pleroma.Web.CommonAPI.Utils
16
17 import Ecto.Query
18 import Ecto.Changeset
19
20 schema "notifications" do
21 field(:seen, :boolean, default: false)
22 belongs_to(:user, User, type: Pleroma.FlakeId)
23 belongs_to(:activity, Activity, type: Pleroma.FlakeId)
24
25 timestamps()
26 end
27
28 def changeset(%Notification{} = notification, attrs) do
29 notification
30 |> cast(attrs, [:seen])
31 end
32
33 def for_user_query(user) do
34 Notification
35 |> where(user_id: ^user.id)
36 |> where(
37 [n, a],
38 fragment(
39 "? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
40 a.actor
41 )
42 )
43 |> join(:inner, [n], activity in assoc(n, :activity))
44 |> join(:left, [n, a], object in Object,
45 on:
46 fragment(
47 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
48 object.data,
49 a.data
50 )
51 )
52 |> preload([n, a, o], activity: {a, object: o})
53 end
54
55 def for_user(user, opts \\ %{}) do
56 user
57 |> for_user_query()
58 |> Pagination.fetch_paginated(opts)
59 end
60
61 def set_read_up_to(%{id: user_id} = _user, id) do
62 query =
63 from(
64 n in Notification,
65 where: n.user_id == ^user_id,
66 where: n.id <= ^id,
67 update: [
68 set: [seen: true]
69 ]
70 )
71
72 Repo.update_all(query, [])
73 end
74
75 def read_one(%User{} = user, notification_id) do
76 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
77 notification
78 |> changeset(%{seen: true})
79 |> Repo.update()
80 end
81 end
82
83 def get(%{id: user_id} = _user, id) do
84 query =
85 from(
86 n in Notification,
87 where: n.id == ^id,
88 join: activity in assoc(n, :activity),
89 preload: [activity: activity]
90 )
91
92 notification = Repo.one(query)
93
94 case notification do
95 %{user_id: ^user_id} ->
96 {:ok, notification}
97
98 _ ->
99 {:error, "Cannot get notification"}
100 end
101 end
102
103 def clear(user) do
104 from(n in Notification, where: n.user_id == ^user.id)
105 |> Repo.delete_all()
106 end
107
108 def dismiss(%{id: user_id} = _user, id) do
109 notification = Repo.get(Notification, id)
110
111 case notification do
112 %{user_id: ^user_id} ->
113 Repo.delete(notification)
114
115 _ ->
116 {:error, "Cannot dismiss notification"}
117 end
118 end
119
120 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
121 when type in ["Create", "Like", "Announce", "Follow"] do
122 users = get_notified_from_activity(activity)
123
124 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
125 {:ok, notifications}
126 end
127
128 def create_notifications(_), do: {:ok, []}
129
130 # TODO move to sql, too.
131 def create_notification(%Activity{} = activity, %User{} = user) do
132 unless skip?(activity, user) do
133 notification = %Notification{user_id: user.id, activity: activity}
134 {:ok, notification} = Repo.insert(notification)
135 Pleroma.Web.Streamer.stream("user", notification)
136 Pleroma.Web.Push.send(notification)
137 notification
138 end
139 end
140
141 def get_notified_from_activity(activity, local_only \\ true)
142
143 def get_notified_from_activity(
144 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
145 local_only
146 )
147 when type in ["Create", "Like", "Announce", "Follow"] do
148 recipients =
149 []
150 |> Utils.maybe_notify_to_recipients(activity)
151 |> Utils.maybe_notify_mentioned_recipients(activity)
152 |> Utils.maybe_notify_subscribers(activity)
153 |> Enum.uniq()
154
155 User.get_users_from_set(recipients, local_only)
156 end
157
158 def get_notified_from_activity(_, _local_only), do: []
159
160 def skip?(activity, user) do
161 [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
162 |> Enum.any?(&skip?(&1, activity, user))
163 end
164
165 def skip?(:self, activity, user) do
166 activity.data["actor"] == user.ap_id
167 end
168
169 def skip?(:blocked, activity, user) do
170 actor = activity.data["actor"]
171 User.blocks?(user, %{ap_id: actor})
172 end
173
174 def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
175 do: true
176
177 def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
178 do: true
179
180 def skip?(:muted, activity, user) do
181 actor = activity.data["actor"]
182
183 User.mutes?(user, %{ap_id: actor}) or
184 CommonAPI.thread_muted?(user, activity)
185 end
186
187 def skip?(
188 :followers,
189 activity,
190 %{info: %{notification_settings: %{"followers" => false}}} = user
191 ) do
192 actor = activity.data["actor"]
193 follower = User.get_cached_by_ap_id(actor)
194 User.following?(follower, user)
195 end
196
197 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
198 actor = activity.data["actor"]
199 followed = User.get_by_ap_id(actor)
200 User.following?(user, followed)
201 end
202
203 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
204 actor = activity.data["actor"]
205
206 Notification.for_user(user)
207 |> Enum.any?(fn
208 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
209 _ -> false
210 end)
211 end
212
213 def skip?(_, _, _), do: false
214 end