Add GET /api/v1/apps/verify_credentials
[akkoma] / lib / pleroma / web / mastodon_api / mastodon_api_controller.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.Web.MastodonAPI.MastodonAPIController do
6 use Pleroma.Web, :controller
7
8 alias Pleroma.Activity
9 alias Pleroma.Config
10 alias Pleroma.Filter
11 alias Pleroma.Notification
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.Stats
15 alias Pleroma.User
16 alias Pleroma.Web
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.MastodonAPI.AccountView
21 alias Pleroma.Web.MastodonAPI.AppView
22 alias Pleroma.Web.MastodonAPI.FilterView
23 alias Pleroma.Web.MastodonAPI.ListView
24 alias Pleroma.Web.MastodonAPI.MastodonAPI
25 alias Pleroma.Web.MastodonAPI.MastodonView
26 alias Pleroma.Web.MastodonAPI.NotificationView
27 alias Pleroma.Web.MastodonAPI.ReportView
28 alias Pleroma.Web.MastodonAPI.StatusView
29 alias Pleroma.Web.MediaProxy
30 alias Pleroma.Web.OAuth.App
31 alias Pleroma.Web.OAuth.Authorization
32 alias Pleroma.Web.OAuth.Token
33
34 import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
35 import Ecto.Query
36
37 require Logger
38
39 @httpoison Application.get_env(:pleroma, :httpoison)
40 @local_mastodon_name "Mastodon-Local"
41
42 action_fallback(:errors)
43
44 def create_app(conn, params) do
45 scopes = oauth_scopes(params, ["read"])
46
47 app_attrs =
48 params
49 |> Map.drop(["scope", "scopes"])
50 |> Map.put("scopes", scopes)
51
52 with cs <- App.register_changeset(%App{}, app_attrs),
53 false <- cs.changes[:client_name] == @local_mastodon_name,
54 {:ok, app} <- Repo.insert(cs) do
55 res = %{
56 id: app.id |> to_string,
57 name: app.client_name,
58 client_id: app.client_id,
59 client_secret: app.client_secret,
60 redirect_uri: app.redirect_uris,
61 website: app.website
62 }
63
64 json(conn, res)
65 end
66 end
67
68 defp add_if_present(
69 map,
70 params,
71 params_field,
72 map_field,
73 value_function \\ fn x -> {:ok, x} end
74 ) do
75 if Map.has_key?(params, params_field) do
76 case value_function.(params[params_field]) do
77 {:ok, new_value} -> Map.put(map, map_field, new_value)
78 :error -> map
79 end
80 else
81 map
82 end
83 end
84
85 def update_credentials(%{assigns: %{user: user}} = conn, params) do
86 original_user = user
87
88 user_params =
89 %{}
90 |> add_if_present(params, "display_name", :name)
91 |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end)
92 |> add_if_present(params, "avatar", :avatar, fn value ->
93 with %Plug.Upload{} <- value,
94 {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
95 {:ok, object.data}
96 else
97 _ -> :error
98 end
99 end)
100
101 info_params =
102 %{}
103 |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end)
104 |> add_if_present(params, "header", :banner, fn value ->
105 with %Plug.Upload{} <- value,
106 {:ok, object} <- ActivityPub.upload(value, type: :banner) do
107 {:ok, object.data}
108 else
109 _ -> :error
110 end
111 end)
112
113 info_cng = User.Info.mastodon_profile_update(user.info, info_params)
114
115 with changeset <- User.update_changeset(user, user_params),
116 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
117 {:ok, user} <- User.update_and_set_cache(changeset) do
118 if original_user != user do
119 CommonAPI.update(user)
120 end
121
122 json(conn, AccountView.render("account.json", %{user: user, for: user}))
123 else
124 _e ->
125 conn
126 |> put_status(403)
127 |> json(%{error: "Invalid request"})
128 end
129 end
130
131 def verify_credentials(%{assigns: %{user: user}} = conn, _) do
132 account = AccountView.render("account.json", %{user: user, for: user})
133 json(conn, account)
134 end
135
136 def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
137 with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
138 conn
139 |> put_view(AppView)
140 |> render("show.json", %{app: app})
141 end
142 end
143
144 def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
145 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
146 true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
147 account = AccountView.render("account.json", %{user: user, for: for_user})
148 json(conn, account)
149 else
150 _e ->
151 conn
152 |> put_status(404)
153 |> json(%{error: "Can't find user"})
154 end
155 end
156
157 @mastodon_api_level "2.5.0"
158
159 def masto_instance(conn, _params) do
160 instance = Config.get(:instance)
161
162 response = %{
163 uri: Web.base_url(),
164 title: Keyword.get(instance, :name),
165 description: Keyword.get(instance, :description),
166 version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
167 email: Keyword.get(instance, :email),
168 urls: %{
169 streaming_api: Pleroma.Web.Endpoint.websocket_url()
170 },
171 stats: Stats.get_stats(),
172 thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
173 languages: ["en"],
174 registrations: Pleroma.Config.get([:instance, :registrations_open]),
175 # Extra (not present in Mastodon):
176 max_toot_chars: Keyword.get(instance, :limit)
177 }
178
179 json(conn, response)
180 end
181
182 def peers(conn, _params) do
183 json(conn, Stats.get_peers())
184 end
185
186 defp mastodonized_emoji do
187 Pleroma.Emoji.get_all()
188 |> Enum.map(fn {shortcode, relative_url} ->
189 url = to_string(URI.merge(Web.base_url(), relative_url))
190
191 %{
192 "shortcode" => shortcode,
193 "static_url" => url,
194 "visible_in_picker" => true,
195 "url" => url
196 }
197 end)
198 end
199
200 def custom_emojis(conn, _params) do
201 mastodon_emoji = mastodonized_emoji()
202 json(conn, mastodon_emoji)
203 end
204
205 defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do
206 params =
207 conn.params
208 |> Map.drop(["since_id", "max_id"])
209 |> Map.merge(params)
210
211 last = List.last(activities)
212 first = List.first(activities)
213
214 if last do
215 min = last.id
216 max = first.id
217
218 {next_url, prev_url} =
219 if param do
220 {
221 mastodon_api_url(
222 Pleroma.Web.Endpoint,
223 method,
224 param,
225 Map.merge(params, %{max_id: min})
226 ),
227 mastodon_api_url(
228 Pleroma.Web.Endpoint,
229 method,
230 param,
231 Map.merge(params, %{since_id: max})
232 )
233 }
234 else
235 {
236 mastodon_api_url(
237 Pleroma.Web.Endpoint,
238 method,
239 Map.merge(params, %{max_id: min})
240 ),
241 mastodon_api_url(
242 Pleroma.Web.Endpoint,
243 method,
244 Map.merge(params, %{since_id: max})
245 )
246 }
247 end
248
249 conn
250 |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
251 else
252 conn
253 end
254 end
255
256 def home_timeline(%{assigns: %{user: user}} = conn, params) do
257 params =
258 params
259 |> Map.put("type", ["Create", "Announce"])
260 |> Map.put("blocking_user", user)
261 |> Map.put("muting_user", user)
262 |> Map.put("user", user)
263
264 activities =
265 [user.ap_id | user.following]
266 |> ActivityPub.fetch_activities(params)
267 |> ActivityPub.contain_timeline(user)
268 |> Enum.reverse()
269
270 conn
271 |> add_link_headers(:home_timeline, activities)
272 |> put_view(StatusView)
273 |> render("index.json", %{activities: activities, for: user, as: :activity})
274 end
275
276 def public_timeline(%{assigns: %{user: user}} = conn, params) do
277 local_only = params["local"] in [true, "True", "true", "1"]
278
279 activities =
280 params
281 |> Map.put("type", ["Create", "Announce"])
282 |> Map.put("local_only", local_only)
283 |> Map.put("blocking_user", user)
284 |> Map.put("muting_user", user)
285 |> ActivityPub.fetch_public_activities()
286 |> Enum.reverse()
287
288 conn
289 |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
290 |> put_view(StatusView)
291 |> render("index.json", %{activities: activities, for: user, as: :activity})
292 end
293
294 def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
295 with %User{} = user <- Repo.get(User, params["id"]) do
296 activities = ActivityPub.fetch_user_activities(user, reading_user, params)
297
298 conn
299 |> add_link_headers(:user_statuses, activities, params["id"])
300 |> put_view(StatusView)
301 |> render("index.json", %{
302 activities: activities,
303 for: reading_user,
304 as: :activity
305 })
306 end
307 end
308
309 def dm_timeline(%{assigns: %{user: user}} = conn, params) do
310 params =
311 params
312 |> Map.put("type", "Create")
313 |> Map.put("blocking_user", user)
314 |> Map.put("user", user)
315 |> Map.put(:visibility, "direct")
316
317 activities =
318 [user.ap_id]
319 |> ActivityPub.fetch_activities_query(params)
320 |> Repo.all()
321
322 conn
323 |> add_link_headers(:dm_timeline, activities)
324 |> put_view(StatusView)
325 |> render("index.json", %{activities: activities, for: user, as: :activity})
326 end
327
328 def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
329 with %Activity{} = activity <- Repo.get(Activity, id),
330 true <- Visibility.visible_for_user?(activity, user) do
331 conn
332 |> put_view(StatusView)
333 |> try_render("status.json", %{activity: activity, for: user})
334 end
335 end
336
337 def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
338 with %Activity{} = activity <- Repo.get(Activity, id),
339 activities <-
340 ActivityPub.fetch_activities_for_context(activity.data["context"], %{
341 "blocking_user" => user,
342 "user" => user
343 }),
344 activities <-
345 activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
346 activities <-
347 activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
348 grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
349 result = %{
350 ancestors:
351 StatusView.render(
352 "index.json",
353 for: user,
354 activities: grouped_activities[true] || [],
355 as: :activity
356 )
357 |> Enum.reverse(),
358 # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
359 descendants:
360 StatusView.render(
361 "index.json",
362 for: user,
363 activities: grouped_activities[false] || [],
364 as: :activity
365 )
366 |> Enum.reverse()
367 # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart
368 }
369
370 json(conn, result)
371 end
372 end
373
374 def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
375 when length(media_ids) > 0 do
376 params =
377 params
378 |> Map.put("status", ".")
379
380 post_status(conn, params)
381 end
382
383 def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
384 params =
385 params
386 |> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
387
388 idempotency_key =
389 case get_req_header(conn, "idempotency-key") do
390 [key] -> key
391 _ -> Ecto.UUID.generate()
392 end
393
394 {:ok, activity} =
395 Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
396
397 conn
398 |> put_view(StatusView)
399 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
400 end
401
402 def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
403 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
404 json(conn, %{})
405 else
406 _e ->
407 conn
408 |> put_status(403)
409 |> json(%{error: "Can't delete this post"})
410 end
411 end
412
413 def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
414 with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do
415 conn
416 |> put_view(StatusView)
417 |> try_render("status.json", %{activity: announce, for: user, as: :activity})
418 end
419 end
420
421 def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
422 with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
423 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
424 conn
425 |> put_view(StatusView)
426 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
427 end
428 end
429
430 def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
431 with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
432 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
433 conn
434 |> put_view(StatusView)
435 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
436 end
437 end
438
439 def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
440 with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
441 %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
442 conn
443 |> put_view(StatusView)
444 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
445 end
446 end
447
448 def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
449 with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
450 conn
451 |> put_view(StatusView)
452 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
453 else
454 {:error, reason} ->
455 conn
456 |> put_resp_content_type("application/json")
457 |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
458 end
459 end
460
461 def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
462 with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
463 conn
464 |> put_view(StatusView)
465 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
466 end
467 end
468
469 def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
470 with %Activity{} = activity <- Repo.get(Activity, id),
471 %User{} = user <- User.get_by_nickname(user.nickname),
472 true <- Visibility.visible_for_user?(activity, user),
473 {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
474 conn
475 |> put_view(StatusView)
476 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
477 end
478 end
479
480 def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
481 with %Activity{} = activity <- Repo.get(Activity, id),
482 %User{} = user <- User.get_by_nickname(user.nickname),
483 true <- Visibility.visible_for_user?(activity, user),
484 {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
485 conn
486 |> put_view(StatusView)
487 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
488 end
489 end
490
491 def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
492 activity = Activity.get_by_id(id)
493
494 with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
495 conn
496 |> put_view(StatusView)
497 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
498 else
499 {:error, reason} ->
500 conn
501 |> put_resp_content_type("application/json")
502 |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
503 end
504 end
505
506 def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
507 activity = Activity.get_by_id(id)
508
509 with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
510 conn
511 |> put_view(StatusView)
512 |> try_render("status.json", %{activity: activity, for: user, as: :activity})
513 end
514 end
515
516 def notifications(%{assigns: %{user: user}} = conn, params) do
517 notifications = MastodonAPI.get_notifications(user, params)
518
519 conn
520 |> add_link_headers(:notifications, notifications)
521 |> put_view(NotificationView)
522 |> render("index.json", %{notifications: notifications, for: user})
523 end
524
525 def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
526 with {:ok, notification} <- Notification.get(user, id) do
527 conn
528 |> put_view(NotificationView)
529 |> render("show.json", %{notification: notification, for: user})
530 else
531 {:error, reason} ->
532 conn
533 |> put_resp_content_type("application/json")
534 |> send_resp(403, Jason.encode!(%{"error" => reason}))
535 end
536 end
537
538 def clear_notifications(%{assigns: %{user: user}} = conn, _params) do
539 Notification.clear(user)
540 json(conn, %{})
541 end
542
543 def dismiss_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
544 with {:ok, _notif} <- Notification.dismiss(user, id) do
545 json(conn, %{})
546 else
547 {:error, reason} ->
548 conn
549 |> put_resp_content_type("application/json")
550 |> send_resp(403, Jason.encode!(%{"error" => reason}))
551 end
552 end
553
554 def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
555 id = List.wrap(id)
556 q = from(u in User, where: u.id in ^id)
557 targets = Repo.all(q)
558
559 conn
560 |> put_view(AccountView)
561 |> render("relationships.json", %{user: user, targets: targets})
562 end
563
564 # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
565 def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
566
567 def update_media(%{assigns: %{user: user}} = conn, data) do
568 with %Object{} = object <- Repo.get(Object, data["id"]),
569 true <- Object.authorize_mutation(object, user),
570 true <- is_binary(data["description"]),
571 description <- data["description"] do
572 new_data = %{object.data | "name" => description}
573
574 {:ok, _} =
575 object
576 |> Object.change(%{data: new_data})
577 |> Repo.update()
578
579 attachment_data = Map.put(new_data, "id", object.id)
580
581 conn
582 |> put_view(StatusView)
583 |> render("attachment.json", %{attachment: attachment_data})
584 end
585 end
586
587 def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
588 with {:ok, object} <-
589 ActivityPub.upload(
590 file,
591 actor: User.ap_id(user),
592 description: Map.get(data, "description")
593 ) do
594 attachment_data = Map.put(object.data, "id", object.id)
595
596 conn
597 |> put_view(StatusView)
598 |> render("attachment.json", %{attachment: attachment_data})
599 end
600 end
601
602 def favourited_by(conn, %{"id" => id}) do
603 with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
604 q = from(u in User, where: u.ap_id in ^likes)
605 users = Repo.all(q)
606
607 conn
608 |> put_view(AccountView)
609 |> render(AccountView, "accounts.json", %{users: users, as: :user})
610 else
611 _ -> json(conn, [])
612 end
613 end
614
615 def reblogged_by(conn, %{"id" => id}) do
616 with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
617 q = from(u in User, where: u.ap_id in ^announces)
618 users = Repo.all(q)
619
620 conn
621 |> put_view(AccountView)
622 |> render("accounts.json", %{users: users, as: :user})
623 else
624 _ -> json(conn, [])
625 end
626 end
627
628 def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
629 local_only = params["local"] in [true, "True", "true", "1"]
630
631 tags =
632 [params["tag"], params["any"]]
633 |> List.flatten()
634 |> Enum.uniq()
635 |> Enum.filter(& &1)
636 |> Enum.map(&String.downcase(&1))
637
638 tag_all =
639 params["all"] ||
640 []
641 |> Enum.map(&String.downcase(&1))
642
643 tag_reject =
644 params["none"] ||
645 []
646 |> Enum.map(&String.downcase(&1))
647
648 activities =
649 params
650 |> Map.put("type", "Create")
651 |> Map.put("local_only", local_only)
652 |> Map.put("blocking_user", user)
653 |> Map.put("muting_user", user)
654 |> Map.put("tag", tags)
655 |> Map.put("tag_all", tag_all)
656 |> Map.put("tag_reject", tag_reject)
657 |> ActivityPub.fetch_public_activities()
658 |> Enum.reverse()
659
660 conn
661 |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only})
662 |> put_view(StatusView)
663 |> render("index.json", %{activities: activities, for: user, as: :activity})
664 end
665
666 def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
667 with %User{} = user <- Repo.get(User, id),
668 followers <- MastodonAPI.get_followers(user, params) do
669 followers =
670 cond do
671 for_user && user.id == for_user.id -> followers
672 user.info.hide_followers -> []
673 true -> followers
674 end
675
676 conn
677 |> add_link_headers(:followers, followers, user)
678 |> put_view(AccountView)
679 |> render("accounts.json", %{users: followers, as: :user})
680 end
681 end
682
683 def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
684 with %User{} = user <- Repo.get(User, id),
685 followers <- MastodonAPI.get_friends(user, params) do
686 followers =
687 cond do
688 for_user && user.id == for_user.id -> followers
689 user.info.hide_follows -> []
690 true -> followers
691 end
692
693 conn
694 |> add_link_headers(:following, followers, user)
695 |> put_view(AccountView)
696 |> render("accounts.json", %{users: followers, as: :user})
697 end
698 end
699
700 def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
701 with {:ok, follow_requests} <- User.get_follow_requests(followed) do
702 conn
703 |> put_view(AccountView)
704 |> render("accounts.json", %{users: follow_requests, as: :user})
705 end
706 end
707
708 def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
709 with %User{} = follower <- Repo.get(User, id),
710 {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
711 conn
712 |> put_view(AccountView)
713 |> render("relationship.json", %{user: followed, target: follower})
714 else
715 {:error, message} ->
716 conn
717 |> put_resp_content_type("application/json")
718 |> send_resp(403, Jason.encode!(%{"error" => message}))
719 end
720 end
721
722 def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
723 with %User{} = follower <- Repo.get(User, id),
724 {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
725 conn
726 |> put_view(AccountView)
727 |> render("relationship.json", %{user: followed, target: follower})
728 else
729 {:error, message} ->
730 conn
731 |> put_resp_content_type("application/json")
732 |> send_resp(403, Jason.encode!(%{"error" => message}))
733 end
734 end
735
736 def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
737 with %User{} = followed <- Repo.get(User, id),
738 false <- User.following?(follower, followed),
739 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
740 conn
741 |> put_view(AccountView)
742 |> render("relationship.json", %{user: follower, target: followed})
743 else
744 true ->
745 followed = User.get_cached_by_id(id)
746
747 {:ok, follower} =
748 case conn.params["reblogs"] do
749 true -> CommonAPI.show_reblogs(follower, followed)
750 false -> CommonAPI.hide_reblogs(follower, followed)
751 end
752
753 conn
754 |> put_view(AccountView)
755 |> render("relationship.json", %{user: follower, target: followed})
756
757 {:error, message} ->
758 conn
759 |> put_resp_content_type("application/json")
760 |> send_resp(403, Jason.encode!(%{"error" => message}))
761 end
762 end
763
764 def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
765 with %User{} = followed <- Repo.get_by(User, nickname: uri),
766 {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
767 conn
768 |> put_view(AccountView)
769 |> render("account.json", %{user: followed, for: follower})
770 else
771 {:error, message} ->
772 conn
773 |> put_resp_content_type("application/json")
774 |> send_resp(403, Jason.encode!(%{"error" => message}))
775 end
776 end
777
778 def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
779 with %User{} = followed <- Repo.get(User, id),
780 {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
781 conn
782 |> put_view(AccountView)
783 |> render("relationship.json", %{user: follower, target: followed})
784 end
785 end
786
787 def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
788 with %User{} = muted <- Repo.get(User, id),
789 {:ok, muter} <- User.mute(muter, muted) do
790 conn
791 |> put_view(AccountView)
792 |> render("relationship.json", %{user: muter, target: muted})
793 else
794 {:error, message} ->
795 conn
796 |> put_resp_content_type("application/json")
797 |> send_resp(403, Jason.encode!(%{"error" => message}))
798 end
799 end
800
801 def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
802 with %User{} = muted <- Repo.get(User, id),
803 {:ok, muter} <- User.unmute(muter, muted) do
804 conn
805 |> put_view(AccountView)
806 |> render("relationship.json", %{user: muter, target: muted})
807 else
808 {:error, message} ->
809 conn
810 |> put_resp_content_type("application/json")
811 |> send_resp(403, Jason.encode!(%{"error" => message}))
812 end
813 end
814
815 def mutes(%{assigns: %{user: user}} = conn, _) do
816 with muted_accounts <- User.muted_users(user) do
817 res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user)
818 json(conn, res)
819 end
820 end
821
822 def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
823 with %User{} = blocked <- Repo.get(User, id),
824 {:ok, blocker} <- User.block(blocker, blocked),
825 {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
826 conn
827 |> put_view(AccountView)
828 |> render("relationship.json", %{user: blocker, target: blocked})
829 else
830 {:error, message} ->
831 conn
832 |> put_resp_content_type("application/json")
833 |> send_resp(403, Jason.encode!(%{"error" => message}))
834 end
835 end
836
837 def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
838 with %User{} = blocked <- Repo.get(User, id),
839 {:ok, blocker} <- User.unblock(blocker, blocked),
840 {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
841 conn
842 |> put_view(AccountView)
843 |> render("relationship.json", %{user: blocker, target: blocked})
844 else
845 {:error, message} ->
846 conn
847 |> put_resp_content_type("application/json")
848 |> send_resp(403, Jason.encode!(%{"error" => message}))
849 end
850 end
851
852 def blocks(%{assigns: %{user: user}} = conn, _) do
853 with blocked_accounts <- User.blocked_users(user) do
854 res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
855 json(conn, res)
856 end
857 end
858
859 def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
860 json(conn, info.domain_blocks || [])
861 end
862
863 def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
864 User.block_domain(blocker, domain)
865 json(conn, %{})
866 end
867
868 def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
869 User.unblock_domain(blocker, domain)
870 json(conn, %{})
871 end
872
873 def status_search(user, query) do
874 fetched =
875 if Regex.match?(~r/https?:/, query) do
876 with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
877 %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
878 true <- Visibility.visible_for_user?(activity, user) do
879 [activity]
880 else
881 _e -> []
882 end
883 end || []
884
885 q =
886 from(
887 a in Activity,
888 where: fragment("?->>'type' = 'Create'", a.data),
889 where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
890 where:
891 fragment(
892 "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
893 a.data,
894 ^query
895 ),
896 limit: 20,
897 order_by: [desc: :id]
898 )
899
900 Repo.all(q) ++ fetched
901 end
902
903 def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
904 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
905
906 statuses = status_search(user, query)
907
908 tags_path = Web.base_url() <> "/tag/"
909
910 tags =
911 query
912 |> String.split()
913 |> Enum.uniq()
914 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
915 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
916 |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
917
918 res = %{
919 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
920 "statuses" =>
921 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
922 "hashtags" => tags
923 }
924
925 json(conn, res)
926 end
927
928 def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
929 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
930
931 statuses = status_search(user, query)
932
933 tags =
934 query
935 |> String.split()
936 |> Enum.uniq()
937 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
938 |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
939
940 res = %{
941 "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
942 "statuses" =>
943 StatusView.render("index.json", activities: statuses, for: user, as: :activity),
944 "hashtags" => tags
945 }
946
947 json(conn, res)
948 end
949
950 def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
951 accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
952
953 res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
954
955 json(conn, res)
956 end
957
958 def favourites(%{assigns: %{user: user}} = conn, params) do
959 params =
960 params
961 |> Map.put("type", "Create")
962 |> Map.put("favorited_by", user.ap_id)
963 |> Map.put("blocking_user", user)
964
965 activities =
966 ActivityPub.fetch_activities([], params)
967 |> Enum.reverse()
968
969 conn
970 |> add_link_headers(:favourites, activities)
971 |> put_view(StatusView)
972 |> render("index.json", %{activities: activities, for: user, as: :activity})
973 end
974
975 def bookmarks(%{assigns: %{user: user}} = conn, _) do
976 user = Repo.get(User, user.id)
977
978 activities =
979 user.bookmarks
980 |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
981 |> Enum.reverse()
982
983 conn
984 |> put_view(StatusView)
985 |> render("index.json", %{activities: activities, for: user, as: :activity})
986 end
987
988 def get_lists(%{assigns: %{user: user}} = conn, opts) do
989 lists = Pleroma.List.for_user(user, opts)
990 res = ListView.render("lists.json", lists: lists)
991 json(conn, res)
992 end
993
994 def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
995 with %Pleroma.List{} = list <- Pleroma.List.get(id, user) do
996 res = ListView.render("list.json", list: list)
997 json(conn, res)
998 else
999 _e ->
1000 conn
1001 |> put_status(404)
1002 |> json(%{error: "Record not found"})
1003 end
1004 end
1005
1006 def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
1007 lists = Pleroma.List.get_lists_account_belongs(user, account_id)
1008 res = ListView.render("lists.json", lists: lists)
1009 json(conn, res)
1010 end
1011
1012 def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1013 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1014 {:ok, _list} <- Pleroma.List.delete(list) do
1015 json(conn, %{})
1016 else
1017 _e ->
1018 json(conn, "error")
1019 end
1020 end
1021
1022 def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
1023 with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
1024 res = ListView.render("list.json", list: list)
1025 json(conn, res)
1026 end
1027 end
1028
1029 def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1030 accounts
1031 |> Enum.each(fn account_id ->
1032 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1033 %User{} = followed <- Repo.get(User, account_id) do
1034 Pleroma.List.follow(list, followed)
1035 end
1036 end)
1037
1038 json(conn, %{})
1039 end
1040
1041 def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
1042 accounts
1043 |> Enum.each(fn account_id ->
1044 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1045 %User{} = followed <- Repo.get(Pleroma.User, account_id) do
1046 Pleroma.List.unfollow(list, followed)
1047 end
1048 end)
1049
1050 json(conn, %{})
1051 end
1052
1053 def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1054 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1055 {:ok, users} = Pleroma.List.get_following(list) do
1056 conn
1057 |> put_view(AccountView)
1058 |> render("accounts.json", %{users: users, as: :user})
1059 end
1060 end
1061
1062 def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
1063 with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
1064 {:ok, list} <- Pleroma.List.rename(list, title) do
1065 res = ListView.render("list.json", list: list)
1066 json(conn, res)
1067 else
1068 _e ->
1069 json(conn, "error")
1070 end
1071 end
1072
1073 def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
1074 with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
1075 params =
1076 params
1077 |> Map.put("type", "Create")
1078 |> Map.put("blocking_user", user)
1079 |> Map.put("muting_user", user)
1080
1081 # we must filter the following list for the user to avoid leaking statuses the user
1082 # does not actually have permission to see (for more info, peruse security issue #270).
1083 activities =
1084 following
1085 |> Enum.filter(fn x -> x in user.following end)
1086 |> ActivityPub.fetch_activities_bounded(following, params)
1087 |> Enum.reverse()
1088
1089 conn
1090 |> put_view(StatusView)
1091 |> render("index.json", %{activities: activities, for: user, as: :activity})
1092 else
1093 _e ->
1094 conn
1095 |> put_status(403)
1096 |> json(%{error: "Error."})
1097 end
1098 end
1099
1100 def index(%{assigns: %{user: user}} = conn, _params) do
1101 token =
1102 conn
1103 |> get_session(:oauth_token)
1104
1105 if user && token do
1106 mastodon_emoji = mastodonized_emoji()
1107
1108 limit = Config.get([:instance, :limit])
1109
1110 accounts =
1111 Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user}))
1112
1113 flavour = get_user_flavour(user)
1114
1115 initial_state =
1116 %{
1117 meta: %{
1118 streaming_api_base_url:
1119 String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
1120 access_token: token,
1121 locale: "en",
1122 domain: Pleroma.Web.Endpoint.host(),
1123 admin: "1",
1124 me: "#{user.id}",
1125 unfollow_modal: false,
1126 boost_modal: false,
1127 delete_modal: true,
1128 auto_play_gif: false,
1129 display_sensitive_media: false,
1130 reduce_motion: false,
1131 max_toot_chars: limit
1132 },
1133 rights: %{
1134 delete_others_notice: present?(user.info.is_moderator),
1135 admin: present?(user.info.is_admin)
1136 },
1137 compose: %{
1138 me: "#{user.id}",
1139 default_privacy: user.info.default_scope,
1140 default_sensitive: false,
1141 allow_content_types: Config.get([:instance, :allowed_post_formats])
1142 },
1143 media_attachments: %{
1144 accept_content_types: [
1145 ".jpg",
1146 ".jpeg",
1147 ".png",
1148 ".gif",
1149 ".webm",
1150 ".mp4",
1151 ".m4v",
1152 "image\/jpeg",
1153 "image\/png",
1154 "image\/gif",
1155 "video\/webm",
1156 "video\/mp4"
1157 ]
1158 },
1159 settings:
1160 user.info.settings ||
1161 %{
1162 onboarded: true,
1163 home: %{
1164 shows: %{
1165 reblog: true,
1166 reply: true
1167 }
1168 },
1169 notifications: %{
1170 alerts: %{
1171 follow: true,
1172 favourite: true,
1173 reblog: true,
1174 mention: true
1175 },
1176 shows: %{
1177 follow: true,
1178 favourite: true,
1179 reblog: true,
1180 mention: true
1181 },
1182 sounds: %{
1183 follow: true,
1184 favourite: true,
1185 reblog: true,
1186 mention: true
1187 }
1188 }
1189 },
1190 push_subscription: nil,
1191 accounts: accounts,
1192 custom_emojis: mastodon_emoji,
1193 char_limit: limit
1194 }
1195 |> Jason.encode!()
1196
1197 conn
1198 |> put_layout(false)
1199 |> put_view(MastodonView)
1200 |> render("index.html", %{initial_state: initial_state, flavour: flavour})
1201 else
1202 conn
1203 |> redirect(to: "/web/login")
1204 end
1205 end
1206
1207 def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
1208 info_cng = User.Info.mastodon_settings_update(user.info, settings)
1209
1210 with changeset <- Ecto.Changeset.change(user),
1211 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
1212 {:ok, _user} <- User.update_and_set_cache(changeset) do
1213 json(conn, %{})
1214 else
1215 e ->
1216 conn
1217 |> put_resp_content_type("application/json")
1218 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1219 end
1220 end
1221
1222 @supported_flavours ["glitch", "vanilla"]
1223
1224 def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params)
1225 when flavour in @supported_flavours do
1226 flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour)
1227
1228 with changeset <- Ecto.Changeset.change(user),
1229 changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng),
1230 {:ok, user} <- User.update_and_set_cache(changeset),
1231 flavour <- user.info.flavour do
1232 json(conn, flavour)
1233 else
1234 e ->
1235 conn
1236 |> put_resp_content_type("application/json")
1237 |> send_resp(500, Jason.encode!(%{"error" => inspect(e)}))
1238 end
1239 end
1240
1241 def set_flavour(conn, _params) do
1242 conn
1243 |> put_status(400)
1244 |> json(%{error: "Unsupported flavour"})
1245 end
1246
1247 def get_flavour(%{assigns: %{user: user}} = conn, _params) do
1248 json(conn, get_user_flavour(user))
1249 end
1250
1251 defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do
1252 flavour
1253 end
1254
1255 defp get_user_flavour(_) do
1256 "glitch"
1257 end
1258
1259 def login(conn, %{"code" => code}) do
1260 with {:ok, app} <- get_or_make_app(),
1261 %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
1262 {:ok, token} <- Token.exchange_token(app, auth) do
1263 conn
1264 |> put_session(:oauth_token, token.token)
1265 |> redirect(to: "/web/getting-started")
1266 end
1267 end
1268
1269 def login(conn, _) do
1270 with {:ok, app} <- get_or_make_app() do
1271 path =
1272 o_auth_path(
1273 conn,
1274 :authorize,
1275 response_type: "code",
1276 client_id: app.client_id,
1277 redirect_uri: ".",
1278 scope: Enum.join(app.scopes, " ")
1279 )
1280
1281 conn
1282 |> redirect(to: path)
1283 end
1284 end
1285
1286 defp get_or_make_app do
1287 find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
1288 scopes = ["read", "write", "follow", "push"]
1289
1290 with %App{} = app <- Repo.get_by(App, find_attrs) do
1291 {:ok, app} =
1292 if app.scopes == scopes do
1293 {:ok, app}
1294 else
1295 app
1296 |> Ecto.Changeset.change(%{scopes: scopes})
1297 |> Repo.update()
1298 end
1299
1300 {:ok, app}
1301 else
1302 _e ->
1303 cs =
1304 App.register_changeset(
1305 %App{},
1306 Map.put(find_attrs, :scopes, scopes)
1307 )
1308
1309 Repo.insert(cs)
1310 end
1311 end
1312
1313 def logout(conn, _) do
1314 conn
1315 |> clear_session
1316 |> redirect(to: "/")
1317 end
1318
1319 def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
1320 Logger.debug("Unimplemented, returning unmodified relationship")
1321
1322 with %User{} = target <- Repo.get(User, id) do
1323 conn
1324 |> put_view(AccountView)
1325 |> render("relationship.json", %{user: user, target: target})
1326 end
1327 end
1328
1329 def empty_array(conn, _) do
1330 Logger.debug("Unimplemented, returning an empty array")
1331 json(conn, [])
1332 end
1333
1334 def empty_object(conn, _) do
1335 Logger.debug("Unimplemented, returning an empty object")
1336 json(conn, %{})
1337 end
1338
1339 def get_filters(%{assigns: %{user: user}} = conn, _) do
1340 filters = Filter.get_filters(user)
1341 res = FilterView.render("filters.json", filters: filters)
1342 json(conn, res)
1343 end
1344
1345 def create_filter(
1346 %{assigns: %{user: user}} = conn,
1347 %{"phrase" => phrase, "context" => context} = params
1348 ) do
1349 query = %Filter{
1350 user_id: user.id,
1351 phrase: phrase,
1352 context: context,
1353 hide: Map.get(params, "irreversible", nil),
1354 whole_word: Map.get(params, "boolean", true)
1355 # expires_at
1356 }
1357
1358 {:ok, response} = Filter.create(query)
1359 res = FilterView.render("filter.json", filter: response)
1360 json(conn, res)
1361 end
1362
1363 def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1364 filter = Filter.get(filter_id, user)
1365 res = FilterView.render("filter.json", filter: filter)
1366 json(conn, res)
1367 end
1368
1369 def update_filter(
1370 %{assigns: %{user: user}} = conn,
1371 %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
1372 ) do
1373 query = %Filter{
1374 user_id: user.id,
1375 filter_id: filter_id,
1376 phrase: phrase,
1377 context: context,
1378 hide: Map.get(params, "irreversible", nil),
1379 whole_word: Map.get(params, "boolean", true)
1380 # expires_at
1381 }
1382
1383 {:ok, response} = Filter.update(query)
1384 res = FilterView.render("filter.json", filter: response)
1385 json(conn, res)
1386 end
1387
1388 def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
1389 query = %Filter{
1390 user_id: user.id,
1391 filter_id: filter_id
1392 }
1393
1394 {:ok, _} = Filter.delete(query)
1395 json(conn, %{})
1396 end
1397
1398 # fallback action
1399 #
1400 def errors(conn, _) do
1401 conn
1402 |> put_status(500)
1403 |> json("Something went wrong")
1404 end
1405
1406 def suggestions(%{assigns: %{user: user}} = conn, _) do
1407 suggestions = Config.get(:suggestions)
1408
1409 if Keyword.get(suggestions, :enabled, false) do
1410 api = Keyword.get(suggestions, :third_party_engine, "")
1411 timeout = Keyword.get(suggestions, :timeout, 5000)
1412 limit = Keyword.get(suggestions, :limit, 23)
1413
1414 host = Config.get([Pleroma.Web.Endpoint, :url, :host])
1415
1416 user = user.nickname
1417
1418 url =
1419 api
1420 |> String.replace("{{host}}", host)
1421 |> String.replace("{{user}}", user)
1422
1423 with {:ok, %{status: 200, body: body}} <-
1424 @httpoison.get(
1425 url,
1426 [],
1427 adapter: [
1428 recv_timeout: timeout,
1429 pool: :default
1430 ]
1431 ),
1432 {:ok, data} <- Jason.decode(body) do
1433 data =
1434 data
1435 |> Enum.slice(0, limit)
1436 |> Enum.map(fn x ->
1437 Map.put(
1438 x,
1439 "id",
1440 case User.get_or_fetch(x["acct"]) do
1441 %{id: id} -> id
1442 _ -> 0
1443 end
1444 )
1445 end)
1446 |> Enum.map(fn x ->
1447 Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
1448 end)
1449 |> Enum.map(fn x ->
1450 Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
1451 end)
1452
1453 conn
1454 |> json(data)
1455 else
1456 e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
1457 end
1458 else
1459 json(conn, [])
1460 end
1461 end
1462
1463 def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
1464 with %Activity{} = activity <- Repo.get(Activity, status_id),
1465 true <- Visibility.visible_for_user?(activity, user) do
1466 data =
1467 StatusView.render(
1468 "card.json",
1469 Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
1470 )
1471
1472 json(conn, data)
1473 else
1474 _e ->
1475 %{}
1476 end
1477 end
1478
1479 def reports(%{assigns: %{user: user}} = conn, params) do
1480 case CommonAPI.report(user, params) do
1481 {:ok, activity} ->
1482 conn
1483 |> put_view(ReportView)
1484 |> try_render("report.json", %{activity: activity})
1485
1486 {:error, err} ->
1487 conn
1488 |> put_status(:bad_request)
1489 |> json(%{error: err})
1490 end
1491 end
1492
1493 def try_render(conn, target, params)
1494 when is_binary(target) do
1495 res = render(conn, target, params)
1496
1497 if res == nil do
1498 conn
1499 |> put_status(501)
1500 |> json(%{error: "Can't display this activity"})
1501 else
1502 res
1503 end
1504 end
1505
1506 def try_render(conn, _, _) do
1507 conn
1508 |> put_status(501)
1509 |> json(%{error: "Can't display this activity"})
1510 end
1511
1512 defp present?(nil), do: false
1513 defp present?(false), do: false
1514 defp present?(_), do: true
1515 end