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