Restricted embedding of relationships where applicable (statuses / notifications...
[akkoma] / lib / pleroma / web / pleroma_api / controllers / pleroma_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
9
10 alias Pleroma.Activity
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Notification
13 alias Pleroma.Object
14 alias Pleroma.Plugs.OAuthScopesPlug
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.CommonAPI
18 alias Pleroma.Web.MastodonAPI.AccountView
19 alias Pleroma.Web.MastodonAPI.ConversationView
20 alias Pleroma.Web.MastodonAPI.NotificationView
21 alias Pleroma.Web.MastodonAPI.StatusView
22
23 plug(
24 OAuthScopesPlug,
25 %{scopes: ["read:statuses"]}
26 when action in [:conversation, :conversation_statuses]
27 )
28
29 plug(
30 OAuthScopesPlug,
31 %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
32 when action == :emoji_reactions_by
33 )
34
35 plug(
36 OAuthScopesPlug,
37 %{scopes: ["write:statuses"]}
38 when action in [:react_with_emoji, :unreact_with_emoji]
39 )
40
41 plug(
42 OAuthScopesPlug,
43 %{scopes: ["write:conversations"]}
44 when action in [:update_conversation, :mark_conversations_as_read]
45 )
46
47 plug(
48 OAuthScopesPlug,
49 %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
50 )
51
52 def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do
53 with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
54 %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <-
55 Object.normalize(activity) do
56 reactions =
57 emoji_reactions
58 |> Enum.map(fn [emoji, user_ap_ids] ->
59 if params["emoji"] && params["emoji"] != emoji do
60 nil
61 else
62 users =
63 Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
64 |> Enum.filter(& &1)
65
66 %{
67 name: emoji,
68 count: length(users),
69 accounts:
70 AccountView.render("index.json", %{
71 users: users,
72 for: user,
73 as: :user,
74 skip_relationships: true
75 }),
76 me: !!(user && user.ap_id in user_ap_ids)
77 }
78 end
79 end)
80 |> Enum.filter(& &1)
81
82 conn
83 |> json(reactions)
84 else
85 _e ->
86 conn
87 |> json([])
88 end
89 end
90
91 def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do
92 with {:ok, _activity, _object} <- CommonAPI.react_with_emoji(activity_id, user, emoji),
93 activity <- Activity.get_by_id(activity_id) do
94 conn
95 |> put_view(StatusView)
96 |> render("show.json", %{activity: activity, for: user, as: :activity})
97 end
98 end
99
100 def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{
101 "id" => activity_id,
102 "emoji" => emoji
103 }) do
104 with {:ok, _activity, _object} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji),
105 activity <- Activity.get_by_id(activity_id) do
106 conn
107 |> put_view(StatusView)
108 |> render("show.json", %{activity: activity, for: user, as: :activity})
109 end
110 end
111
112 def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
113 with %Participation{} = participation <- Participation.get(participation_id),
114 true <- user.id == participation.user_id do
115 conn
116 |> put_view(ConversationView)
117 |> render("participation.json", %{participation: participation, for: user})
118 else
119 _error ->
120 conn
121 |> put_status(404)
122 |> json(%{"error" => "Unknown conversation id"})
123 end
124 end
125
126 def conversation_statuses(
127 %{assigns: %{user: %{id: user_id} = user}} = conn,
128 %{"id" => participation_id} = params
129 ) do
130 with %Participation{user_id: ^user_id} = participation <-
131 Participation.get(participation_id, preload: [:conversation]) do
132 params =
133 params
134 |> Map.put("blocking_user", user)
135 |> Map.put("muting_user", user)
136 |> Map.put("user", user)
137
138 activities =
139 participation.conversation.ap_id
140 |> ActivityPub.fetch_activities_for_context_query(params)
141 |> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
142 |> Enum.reverse()
143
144 conn
145 |> add_link_headers(activities)
146 |> put_view(StatusView)
147 |> render("index.json",
148 activities: activities,
149 for: user,
150 as: :activity,
151 skip_relationships: skip_relationships?(params)
152 )
153 else
154 _error ->
155 conn
156 |> put_status(404)
157 |> json(%{"error" => "Unknown conversation id"})
158 end
159 end
160
161 def update_conversation(
162 %{assigns: %{user: user}} = conn,
163 %{"id" => participation_id, "recipients" => recipients}
164 ) do
165 with %Participation{} = participation <- Participation.get(participation_id),
166 true <- user.id == participation.user_id,
167 {:ok, participation} <- Participation.set_recipients(participation, recipients) do
168 conn
169 |> put_view(ConversationView)
170 |> render("participation.json", %{participation: participation, for: user})
171 else
172 {:error, message} ->
173 conn
174 |> put_status(:bad_request)
175 |> json(%{"error" => message})
176
177 _error ->
178 conn
179 |> put_status(404)
180 |> json(%{"error" => "Unknown conversation id"})
181 end
182 end
183
184 def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do
185 with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
186 conn
187 |> add_link_headers(participations)
188 |> put_view(ConversationView)
189 |> render("participations.json", participations: participations, for: user)
190 end
191 end
192
193 def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
194 with {:ok, notification} <- Notification.read_one(user, notification_id) do
195 conn
196 |> put_view(NotificationView)
197 |> render("show.json", %{notification: notification, for: user})
198 else
199 {:error, message} ->
200 conn
201 |> put_status(:bad_request)
202 |> json(%{"error" => message})
203 end
204 end
205
206 def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
207 with notifications <- Notification.set_read_up_to(user, max_id) do
208 notifications = Enum.take(notifications, 80)
209
210 conn
211 |> put_view(NotificationView)
212 |> render("index.json",
213 notifications: notifications,
214 for: user,
215 skip_relationships: skip_relationships?(params)
216 )
217 end
218 end
219 end