Merge branch 'hotfix/fix_undefined_summary' 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 dismiss(%{id: user_id} = _user, id) do
102 notification = Repo.get(Notification, id)
103
104 case notification do
105 %{user_id: ^user_id} ->
106 Repo.delete(notification)
107
108 _ ->
109 {:error, "Cannot dismiss notification"}
110 end
111 end
112
113 def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
114 when type in ["Create", "Like", "Announce", "Follow"] do
115 users = get_notified_from_activity(activity)
116
117 notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
118 {:ok, notifications}
119 end
120
121 def create_notifications(_), do: {:ok, []}
122
123 # TODO move to sql, too.
124 def create_notification(%Activity{} = activity, %User{} = user) do
125 unless skip?(activity, user) do
126 notification = %Notification{user_id: user.id, activity: activity}
127 {:ok, notification} = Repo.insert(notification)
128 Pleroma.Web.Streamer.stream("user", notification)
129 Pleroma.Web.Push.send(notification)
130 notification
131 end
132 end
133
134 def get_notified_from_activity(activity, local_only \\ true)
135
136 def get_notified_from_activity(
137 %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
138 local_only
139 )
140 when type in ["Create", "Like", "Announce", "Follow"] do
141 recipients =
142 []
143 |> Utils.maybe_notify_to_recipients(activity)
144 |> Utils.maybe_notify_mentioned_recipients(activity)
145 |> Enum.uniq()
146
147 User.get_users_from_set(recipients, local_only)
148 end
149
150 def get_notified_from_activity(_, _local_only), do: []
151
152 def skip?(activity, user) do
153 [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
154 |> Enum.any?(&skip?(&1, activity, user))
155 end
156
157 def skip?(:self, activity, user) do
158 activity.data["actor"] == user.ap_id
159 end
160
161 def skip?(:blocked, activity, user) do
162 actor = activity.data["actor"]
163 User.blocks?(user, %{ap_id: actor})
164 end
165
166 def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
167 do: true
168
169 def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
170 do: true
171
172 def skip?(:muted, activity, user) do
173 actor = activity.data["actor"]
174
175 User.mutes?(user, %{ap_id: actor}) or
176 CommonAPI.thread_muted?(user, activity)
177 end
178
179 def skip?(
180 :followers,
181 activity,
182 %{info: %{notification_settings: %{"followers" => false}}} = user
183 ) do
184 actor = activity.data["actor"]
185 follower = User.get_cached_by_ap_id(actor)
186 User.following?(follower, user)
187 end
188
189 def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
190 actor = activity.data["actor"]
191 followed = User.get_by_ap_id(actor)
192 User.following?(user, followed)
193 end
194
195 def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
196 actor = activity.data["actor"]
197
198 Notification.for_user(user)
199 |> Enum.any?(fn
200 %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
201 _ -> false
202 end)
203 end
204
205 def skip?(_, _, _), do: false
206 end