261cc4462443d5ba82c5fe12bd0174a205ff917b
[akkoma] / lib / pleroma / web / twitter_api / twitter_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.TwitterAPI.Controller do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
9
10 alias Ecto.Changeset
11 alias Pleroma.Activity
12 alias Pleroma.Formatter
13 alias Pleroma.Notification
14 alias Pleroma.Object
15 alias Pleroma.Repo
16 alias Pleroma.User
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Visibility
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.CommonAPI.Utils
21 alias Pleroma.Web.OAuth.Token
22 alias Pleroma.Web.TwitterAPI.ActivityView
23 alias Pleroma.Web.TwitterAPI.NotificationView
24 alias Pleroma.Web.TwitterAPI.TokenView
25 alias Pleroma.Web.TwitterAPI.TwitterAPI
26 alias Pleroma.Web.TwitterAPI.UserView
27
28 require Logger
29
30 plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
31 action_fallback(:errors)
32
33 def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
34 token = Phoenix.Token.sign(conn, "user socket", user.id)
35
36 conn
37 |> put_view(UserView)
38 |> render("show.json", %{user: user, token: token, for: user})
39 end
40
41 def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
42 with media_ids <- extract_media_ids(status_data),
43 {:ok, activity} <-
44 TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
45 conn
46 |> json(ActivityView.render("activity.json", activity: activity, for: user))
47 else
48 _ -> empty_status_reply(conn)
49 end
50 end
51
52 def status_update(conn, _status_data) do
53 empty_status_reply(conn)
54 end
55
56 defp empty_status_reply(conn) do
57 bad_request_reply(conn, "Client must provide a 'status' parameter with a value.")
58 end
59
60 defp extract_media_ids(status_data) do
61 with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
62 split_ids <- String.split(media_ids, ","),
63 clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
64 clean_ids
65 else
66 _e -> []
67 end
68 end
69
70 def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
71 params =
72 params
73 |> Map.put("type", ["Create", "Announce"])
74 |> Map.put("blocking_user", user)
75
76 activities = ActivityPub.fetch_public_activities(params)
77
78 conn
79 |> put_view(ActivityView)
80 |> render("index.json", %{activities: activities, for: user})
81 end
82
83 def public_timeline(%{assigns: %{user: user}} = conn, params) do
84 params =
85 params
86 |> Map.put("type", ["Create", "Announce"])
87 |> Map.put("local_only", true)
88 |> Map.put("blocking_user", user)
89
90 activities = ActivityPub.fetch_public_activities(params)
91
92 conn
93 |> put_view(ActivityView)
94 |> render("index.json", %{activities: activities, for: user})
95 end
96
97 def friends_timeline(%{assigns: %{user: user}} = conn, params) do
98 params =
99 params
100 |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
101 |> Map.put("blocking_user", user)
102 |> Map.put("user", user)
103
104 activities =
105 ActivityPub.fetch_activities([user.ap_id | user.following], params)
106 |> ActivityPub.contain_timeline(user)
107
108 conn
109 |> put_view(ActivityView)
110 |> render("index.json", %{activities: activities, for: user})
111 end
112
113 def show_user(conn, params) do
114 for_user = conn.assigns.user
115
116 with {:ok, shown} <- TwitterAPI.get_user(params),
117 true <-
118 User.auth_active?(shown) ||
119 (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
120 params =
121 if for_user do
122 %{user: shown, for: for_user}
123 else
124 %{user: shown}
125 end
126
127 conn
128 |> put_view(UserView)
129 |> render("show.json", params)
130 else
131 {:error, msg} ->
132 bad_request_reply(conn, msg)
133
134 false ->
135 conn
136 |> put_status(404)
137 |> json(%{error: "Unconfirmed user"})
138 end
139 end
140
141 def user_timeline(%{assigns: %{user: user}} = conn, params) do
142 case TwitterAPI.get_user(user, params) do
143 {:ok, target_user} ->
144 # Twitter and ActivityPub use a different name and sense for this parameter.
145 {include_rts, params} = Map.pop(params, "include_rts")
146
147 params =
148 case include_rts do
149 x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true")
150 _ -> params
151 end
152
153 activities = ActivityPub.fetch_user_activities(target_user, user, params)
154
155 conn
156 |> put_view(ActivityView)
157 |> render("index.json", %{activities: activities, for: user})
158
159 {:error, msg} ->
160 bad_request_reply(conn, msg)
161 end
162 end
163
164 def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
165 params =
166 params
167 |> Map.put("type", ["Create", "Announce", "Follow", "Like"])
168 |> Map.put("blocking_user", user)
169 |> Map.put(:visibility, ~w[unlisted public private])
170
171 activities = ActivityPub.fetch_activities([user.ap_id], params)
172
173 conn
174 |> put_view(ActivityView)
175 |> render("index.json", %{activities: activities, for: user})
176 end
177
178 def dm_timeline(%{assigns: %{user: user}} = conn, params) do
179 params =
180 params
181 |> Map.put("type", "Create")
182 |> Map.put("blocking_user", user)
183 |> Map.put("user", user)
184 |> Map.put(:visibility, "direct")
185
186 activities =
187 ActivityPub.fetch_activities_query([user.ap_id], params)
188 |> Repo.all()
189
190 conn
191 |> put_view(ActivityView)
192 |> render("index.json", %{activities: activities, for: user})
193 end
194
195 def notifications(%{assigns: %{user: user}} = conn, params) do
196 notifications = Notification.for_user(user, params)
197
198 conn
199 |> put_view(NotificationView)
200 |> render("notification.json", %{notifications: notifications, for: user})
201 end
202
203 def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
204 Notification.set_read_up_to(user, latest_id)
205
206 notifications = Notification.for_user(user, params)
207
208 conn
209 |> put_view(NotificationView)
210 |> render("notification.json", %{notifications: notifications, for: user})
211 end
212
213 def notifications_read(%{assigns: %{user: _user}} = conn, _) do
214 bad_request_reply(conn, "You need to specify latest_id")
215 end
216
217 def follow(%{assigns: %{user: user}} = conn, params) do
218 case TwitterAPI.follow(user, params) do
219 {:ok, user, followed, _activity} ->
220 conn
221 |> put_view(UserView)
222 |> render("show.json", %{user: followed, for: user})
223
224 {:error, msg} ->
225 forbidden_json_reply(conn, msg)
226 end
227 end
228
229 def block(%{assigns: %{user: user}} = conn, params) do
230 case TwitterAPI.block(user, params) do
231 {:ok, user, blocked} ->
232 conn
233 |> put_view(UserView)
234 |> render("show.json", %{user: blocked, for: user})
235
236 {:error, msg} ->
237 forbidden_json_reply(conn, msg)
238 end
239 end
240
241 def unblock(%{assigns: %{user: user}} = conn, params) do
242 case TwitterAPI.unblock(user, params) do
243 {:ok, user, blocked} ->
244 conn
245 |> put_view(UserView)
246 |> render("show.json", %{user: blocked, for: user})
247
248 {:error, msg} ->
249 forbidden_json_reply(conn, msg)
250 end
251 end
252
253 def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
254 with {:ok, activity} <- TwitterAPI.delete(user, id) do
255 conn
256 |> put_view(ActivityView)
257 |> render("activity.json", %{activity: activity, for: user})
258 end
259 end
260
261 def unfollow(%{assigns: %{user: user}} = conn, params) do
262 case TwitterAPI.unfollow(user, params) do
263 {:ok, user, unfollowed} ->
264 conn
265 |> put_view(UserView)
266 |> render("show.json", %{user: unfollowed, for: user})
267
268 {:error, msg} ->
269 forbidden_json_reply(conn, msg)
270 end
271 end
272
273 def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
274 with %Activity{} = activity <- Activity.get_by_id(id),
275 true <- Visibility.visible_for_user?(activity, user) do
276 conn
277 |> put_view(ActivityView)
278 |> render("activity.json", %{activity: activity, for: user})
279 end
280 end
281
282 def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
283 with context when is_binary(context) <- Utils.conversation_id_to_context(id),
284 activities <-
285 ActivityPub.fetch_activities_for_context(context, %{
286 "blocking_user" => user,
287 "user" => user
288 }) do
289 conn
290 |> put_view(ActivityView)
291 |> render("index.json", %{activities: activities, for: user})
292 end
293 end
294
295 @doc """
296 Updates metadata of uploaded media object.
297 Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create).
298 """
299 def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
300 object = Repo.get(Object, id)
301 description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]
302
303 {conn, status, response_body} =
304 cond do
305 !object ->
306 {halt(conn), :not_found, ""}
307
308 !Object.authorize_mutation(object, user) ->
309 {halt(conn), :forbidden, "You can only update your own uploads."}
310
311 !is_binary(description) ->
312 {conn, :not_modified, ""}
313
314 true ->
315 new_data = Map.put(object.data, "name", description)
316
317 {:ok, _} =
318 object
319 |> Object.change(%{data: new_data})
320 |> Repo.update()
321
322 {conn, :no_content, ""}
323 end
324
325 conn
326 |> put_status(status)
327 |> json(response_body)
328 end
329
330 def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
331 response = TwitterAPI.upload(media, user)
332
333 conn
334 |> put_resp_content_type("application/atom+xml")
335 |> send_resp(200, response)
336 end
337
338 def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
339 response = TwitterAPI.upload(media, user, "json")
340
341 conn
342 |> json_reply(200, response)
343 end
344
345 def get_by_id_or_ap_id(id) do
346 activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
347
348 if activity.data["type"] == "Create" do
349 activity
350 else
351 Activity.get_create_by_object_ap_id(activity.data["object"])
352 end
353 end
354
355 def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
356 with {:ok, activity} <- TwitterAPI.fav(user, id) do
357 conn
358 |> put_view(ActivityView)
359 |> render("activity.json", %{activity: activity, for: user})
360 else
361 _ -> json_reply(conn, 400, Jason.encode!(%{}))
362 end
363 end
364
365 def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
366 with {:ok, activity} <- TwitterAPI.unfav(user, id) do
367 conn
368 |> put_view(ActivityView)
369 |> render("activity.json", %{activity: activity, for: user})
370 else
371 _ -> json_reply(conn, 400, Jason.encode!(%{}))
372 end
373 end
374
375 def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
376 with {:ok, activity} <- TwitterAPI.repeat(user, id) do
377 conn
378 |> put_view(ActivityView)
379 |> render("activity.json", %{activity: activity, for: user})
380 else
381 _ -> json_reply(conn, 400, Jason.encode!(%{}))
382 end
383 end
384
385 def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
386 with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
387 conn
388 |> put_view(ActivityView)
389 |> render("activity.json", %{activity: activity, for: user})
390 else
391 _ -> json_reply(conn, 400, Jason.encode!(%{}))
392 end
393 end
394
395 def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
396 with {:ok, activity} <- TwitterAPI.pin(user, id) do
397 conn
398 |> put_view(ActivityView)
399 |> render("activity.json", %{activity: activity, for: user})
400 else
401 {:error, message} -> bad_request_reply(conn, message)
402 err -> err
403 end
404 end
405
406 def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
407 with {:ok, activity} <- TwitterAPI.unpin(user, id) do
408 conn
409 |> put_view(ActivityView)
410 |> render("activity.json", %{activity: activity, for: user})
411 else
412 {:error, message} -> bad_request_reply(conn, message)
413 err -> err
414 end
415 end
416
417 def register(conn, params) do
418 with {:ok, user} <- TwitterAPI.register_user(params) do
419 conn
420 |> put_view(UserView)
421 |> render("show.json", %{user: user})
422 else
423 {:error, errors} ->
424 conn
425 |> json_reply(400, Jason.encode!(errors))
426 end
427 end
428
429 def password_reset(conn, params) do
430 nickname_or_email = params["email"] || params["nickname"]
431
432 with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
433 json_response(conn, :no_content, "")
434 end
435 end
436
437 def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
438 with %User{} = user <- User.get_cached_by_id(uid),
439 true <- user.local,
440 true <- user.info.confirmation_pending,
441 true <- user.info.confirmation_token == token,
442 info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
443 changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
444 {:ok, _} <- User.update_and_set_cache(changeset) do
445 conn
446 |> redirect(to: "/")
447 end
448 end
449
450 def resend_confirmation_email(conn, params) do
451 nickname_or_email = params["email"] || params["nickname"]
452
453 with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
454 {:ok, _} <- User.try_send_confirmation_email(user) do
455 conn
456 |> json_response(:no_content, "")
457 end
458 end
459
460 def update_avatar(%{assigns: %{user: user}} = conn, params) do
461 {:ok, object} = ActivityPub.upload(params, type: :avatar)
462 change = Changeset.change(user, %{avatar: object.data})
463 {:ok, user} = User.update_and_set_cache(change)
464 CommonAPI.update(user)
465
466 conn
467 |> put_view(UserView)
468 |> render("show.json", %{user: user, for: user})
469 end
470
471 def update_banner(%{assigns: %{user: user}} = conn, params) do
472 with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
473 new_info <- %{"banner" => object.data},
474 info_cng <- User.Info.profile_update(user.info, new_info),
475 changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
476 {:ok, user} <- User.update_and_set_cache(changeset) do
477 CommonAPI.update(user)
478 %{"url" => [%{"href" => href} | _]} = object.data
479 response = %{url: href} |> Jason.encode!()
480
481 conn
482 |> json_reply(200, response)
483 end
484 end
485
486 def update_background(%{assigns: %{user: user}} = conn, params) do
487 with {:ok, object} <- ActivityPub.upload(params, type: :background),
488 new_info <- %{"background" => object.data},
489 info_cng <- User.Info.profile_update(user.info, new_info),
490 changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
491 {:ok, _user} <- User.update_and_set_cache(changeset) do
492 %{"url" => [%{"href" => href} | _]} = object.data
493 response = %{url: href} |> Jason.encode!()
494
495 conn
496 |> json_reply(200, response)
497 end
498 end
499
500 def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
501 with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
502 response <- Jason.encode!(user_map) do
503 conn
504 |> json_reply(200, response)
505 else
506 _e ->
507 conn
508 |> put_status(404)
509 |> json(%{error: "Can't find user"})
510 end
511 end
512
513 def followers(%{assigns: %{user: for_user}} = conn, params) do
514 {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
515
516 with {:ok, user} <- TwitterAPI.get_user(for_user, params),
517 {:ok, followers} <- User.get_followers(user, page) do
518 followers =
519 cond do
520 for_user && user.id == for_user.id -> followers
521 user.info.hide_followers -> []
522 true -> followers
523 end
524
525 conn
526 |> put_view(UserView)
527 |> render("index.json", %{users: followers, for: conn.assigns[:user]})
528 else
529 _e -> bad_request_reply(conn, "Can't get followers")
530 end
531 end
532
533 def friends(%{assigns: %{user: for_user}} = conn, params) do
534 {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
535 {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)
536
537 page = if export, do: nil, else: page
538
539 with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
540 {:ok, friends} <- User.get_friends(user, page) do
541 friends =
542 cond do
543 for_user && user.id == for_user.id -> friends
544 user.info.hide_follows -> []
545 true -> friends
546 end
547
548 conn
549 |> put_view(UserView)
550 |> render("index.json", %{users: friends, for: conn.assigns[:user]})
551 else
552 _e -> bad_request_reply(conn, "Can't get friends")
553 end
554 end
555
556 def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
557 with oauth_tokens <- Token.get_user_tokens(user) do
558 conn
559 |> put_view(TokenView)
560 |> render("index.json", %{tokens: oauth_tokens})
561 end
562 end
563
564 def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
565 Token.delete_user_token(user, id)
566
567 json_reply(conn, 201, "")
568 end
569
570 def blocks(%{assigns: %{user: user}} = conn, _params) do
571 with blocked_users <- User.blocked_users(user) do
572 conn
573 |> put_view(UserView)
574 |> render("index.json", %{users: blocked_users, for: user})
575 end
576 end
577
578 def friend_requests(conn, params) do
579 with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
580 {:ok, friend_requests} <- User.get_follow_requests(user) do
581 conn
582 |> put_view(UserView)
583 |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
584 else
585 _e -> bad_request_reply(conn, "Can't get friend requests")
586 end
587 end
588
589 def approve_friend_request(conn, %{"user_id" => uid} = _params) do
590 with followed <- conn.assigns[:user],
591 %User{} = follower <- User.get_cached_by_id(uid),
592 {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
593 conn
594 |> put_view(UserView)
595 |> render("show.json", %{user: follower, for: followed})
596 else
597 e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
598 end
599 end
600
601 def deny_friend_request(conn, %{"user_id" => uid} = _params) do
602 with followed <- conn.assigns[:user],
603 %User{} = follower <- User.get_cached_by_id(uid),
604 {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
605 conn
606 |> put_view(UserView)
607 |> render("show.json", %{user: follower, for: followed})
608 else
609 e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
610 end
611 end
612
613 def friends_ids(%{assigns: %{user: user}} = conn, _params) do
614 with {:ok, friends} <- User.get_friends(user) do
615 ids =
616 friends
617 |> Enum.map(fn x -> x.id end)
618 |> Jason.encode!()
619
620 json(conn, ids)
621 else
622 _e -> bad_request_reply(conn, "Can't get friends")
623 end
624 end
625
626 def empty_array(conn, _params) do
627 json(conn, Jason.encode!([]))
628 end
629
630 def raw_empty_array(conn, _params) do
631 json(conn, [])
632 end
633
634 defp build_info_cng(user, params) do
635 info_params =
636 ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
637 |> Enum.reduce(%{}, fn key, res ->
638 if value = params[key] do
639 Map.put(res, key, value == "true")
640 else
641 res
642 end
643 end)
644
645 info_params =
646 if value = params["default_scope"] do
647 Map.put(info_params, "default_scope", value)
648 else
649 info_params
650 end
651
652 User.Info.profile_update(user.info, info_params)
653 end
654
655 defp parse_profile_bio(user, params) do
656 if bio = params["description"] do
657 user_info =
658 user.info
659 |> Map.put(
660 "emojis",
661 Formatter.get_emoji_map(params["description"])
662 )
663
664 params
665 |> Map.put("bio", User.parse_bio(bio, user))
666 |> Map.put("info", user_info)
667 else
668 params
669 end
670 end
671
672 def update_profile(%{assigns: %{user: user}} = conn, params) do
673 params = parse_profile_bio(user, params)
674 info_cng = build_info_cng(user, params)
675
676 with changeset <- User.update_changeset(user, params),
677 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
678 {:ok, user} <- User.update_and_set_cache(changeset) do
679 CommonAPI.update(user)
680
681 conn
682 |> put_view(UserView)
683 |> render("user.json", %{user: user, for: user})
684 else
685 error ->
686 Logger.debug("Can't update user: #{inspect(error)}")
687 bad_request_reply(conn, "Can't update user")
688 end
689 end
690
691 def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
692 activities = TwitterAPI.search(user, params)
693
694 conn
695 |> put_view(ActivityView)
696 |> render("index.json", %{activities: activities, for: user})
697 end
698
699 def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
700 users = User.search(query, resolve: true, for_user: user)
701
702 conn
703 |> put_view(UserView)
704 |> render("index.json", %{users: users, for: user})
705 end
706
707 defp bad_request_reply(conn, error_message) do
708 json = error_json(conn, error_message)
709 json_reply(conn, 400, json)
710 end
711
712 defp json_reply(conn, status, json) do
713 conn
714 |> put_resp_content_type("application/json")
715 |> send_resp(status, json)
716 end
717
718 defp forbidden_json_reply(conn, error_message) do
719 json = error_json(conn, error_message)
720 json_reply(conn, 403, json)
721 end
722
723 def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
724
725 def only_if_public_instance(conn, _) do
726 if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
727 conn
728 else
729 conn
730 |> forbidden_json_reply("Invalid credentials.")
731 |> halt()
732 end
733 end
734
735 defp error_json(conn, error_message) do
736 %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
737 end
738
739 def errors(conn, {:param_cast, _}) do
740 conn
741 |> put_status(400)
742 |> json("Invalid parameters")
743 end
744
745 def errors(conn, _) do
746 conn
747 |> put_status(500)
748 |> json("Something went wrong")
749 end
750 end