Merge branch 'features/download-mastofe-build' into 'develop'
[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 |> join(:inner, [n], activity in assoc(n, :activity))
37 |> join(:left, [n, a], object in Object,
38 on:
39 fragment(
40 "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
41 object.data,
42 a.data
43 )
44 )
45 |> preload([n, a, o], activity: {a, object: o})
46 end
47
48 def for_user(user, opts \\ %{}) do
49 user
50 |> for_user_query()
51 |> Pagination.fetch_paginated(opts)
52 end
53
54 def set_read_up_to(%{id: user_id} = _user, id) do
55 query =
56 from(
57 n in Notification,
58 where: n.user_id == ^user_id,
59 where: n.id <= ^id,
60 update: [
61 set: [seen: true]
62 ]
63 )
64
65 Repo.update_all(query, [])
66 end
67
68 def read_one(%User{} = user, notification_id) do
69 with {:ok, %Notification{} = notification} <- get(user, notification_id) do
70 notification
71 |> changeset(%{seen: true})
72 |> Repo.update()
73 end
74 end
75
76 def get(%{id: user_id} = _user, id) do
77 query =
78 from(
79 n in Notification,
80 where: n.id == ^id,
81 join: activity in assoc(n, :activity),
82 preload: [activity: activity]
83 )
84
85 notification = Repo.one(query)
86
87 case notification do
88 %{user_id: ^user_id} ->
89 {:ok, notification}
90
91 _ ->
92 {:error, "Cannot get notification"}
93 end
94 end
95
96 def clear(user) do
97 from(n in Notification, where: n.user_id == ^user.id)
98 |> Repo.delete_all()
99 end
100
101 def destroy_multiple(%{id: user_id} = _user, ids) do
102 from(n in Notification,
103 where: n.id in ^ids,
104 where: n.user_id == ^user_id
105 )
106 |> Repo.delete_all()
107 end
108
109 def dismiss(%{id: user_id} = _user, id) do
110 notification = Repo.get(Notification, id)
111
112 case notification do
113 %{user_id: ^user_id} ->
114 Repo.delete(notification)
115
116 _ ->
117 {:error, "Cannot dismiss notification"}
118 end
119 end
120
121 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
122 when type in ["Create", "Like", "Announce", "Follow"] do
123 users = get_notified_from_activity(activity)
124
125 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
126 {:ok, notifications}
127 end
128
129 def create_notifications(_), do: {:ok, []}
130
131 # TODO move to sql, too.
132 def create_notification(%Activity{} = activity, %User{} = user) do
133 unless skip?(activity, user) do
134 notification = %Notification{user_id: user.id, activity: activity}
135 {:ok, notification} = Repo.insert(notification)
136 Pleroma.Web.Streamer.stream("user", notification)
137 Pleroma.Web.Push.send(notification)
138 notification
139 end
140 end
141
142 def get_notified_from_activity(activity, local_only \\ true)
143
144 def get_notified_from_activity(
145 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
146 local_only
147 )
148 when type in ["Create", "Like", "Announce", "Follow"] do
149 recipients =
150 []
151 |> Utils.maybe_notify_to_recipients(activity)
152 |> Utils.maybe_notify_mentioned_recipients(activity)
153 |> Utils.maybe_notify_subscribers(activity)
154 |> Enum.uniq()
155
156 User.get_users_from_set(recipients, local_only)
157 end
158
159 def get_notified_from_activity(_, _local_only), do: []
160
161 def skip?(activity, user) do
162 [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
163 |> Enum.any?(&skip?(&1, activity, user))
164 end
165
166 def skip?(:self, activity, user) do
167 activity.data["actor"] == user.ap_id
168 end
169
170 def skip?(:blocked, activity, user) do
171 actor = activity.data["actor"]
172 User.blocks?(user, %{ap_id: actor})
173 end
174
175 def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
176 do: true
177
178 def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
179 do: true
180
181 def skip?(:muted, activity, user) do
182 actor = activity.data["actor"]
183
184 User.mutes?(user, %{ap_id: actor}) or 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_cached_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