differences_in_mastoapi_responses.md: fullname & bio are optionnal
[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 |> Map.put(:order, :desc)
186
187 activities =
188 ActivityPub.fetch_activities_query([user.ap_id], params)
189 |> Repo.all()
190
191 conn
192 |> put_view(ActivityView)
193 |> render("index.json", %{activities: activities, for: user})
194 end
195
196 def notifications(%{assigns: %{user: user}} = conn, params) do
197 notifications = Notification.for_user(user, params)
198
199 conn
200 |> put_view(NotificationView)
201 |> render("notification.json", %{notifications: notifications, for: user})
202 end
203
204 def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
205 Notification.set_read_up_to(user, latest_id)
206
207 notifications = Notification.for_user(user, params)
208
209 conn
210 |> put_view(NotificationView)
211 |> render("notification.json", %{notifications: notifications, for: user})
212 end
213
214 def notifications_read(%{assigns: %{user: _user}} = conn, _) do
215 bad_request_reply(conn, "You need to specify latest_id")
216 end
217
218 def follow(%{assigns: %{user: user}} = conn, params) do
219 case TwitterAPI.follow(user, params) do
220 {:ok, user, followed, _activity} ->
221 conn
222 |> put_view(UserView)
223 |> render("show.json", %{user: followed, for: user})
224
225 {:error, msg} ->
226 forbidden_json_reply(conn, msg)
227 end
228 end
229
230 def block(%{assigns: %{user: user}} = conn, params) do
231 case TwitterAPI.block(user, params) do
232 {:ok, user, blocked} ->
233 conn
234 |> put_view(UserView)
235 |> render("show.json", %{user: blocked, for: user})
236
237 {:error, msg} ->
238 forbidden_json_reply(conn, msg)
239 end
240 end
241
242 def unblock(%{assigns: %{user: user}} = conn, params) do
243 case TwitterAPI.unblock(user, params) do
244 {:ok, user, blocked} ->
245 conn
246 |> put_view(UserView)
247 |> render("show.json", %{user: blocked, for: user})
248
249 {:error, msg} ->
250 forbidden_json_reply(conn, msg)
251 end
252 end
253
254 def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
255 with {:ok, activity} <- TwitterAPI.delete(user, id) do
256 conn
257 |> put_view(ActivityView)
258 |> render("activity.json", %{activity: activity, for: user})
259 end
260 end
261
262 def unfollow(%{assigns: %{user: user}} = conn, params) do
263 case TwitterAPI.unfollow(user, params) do
264 {:ok, user, unfollowed} ->
265 conn
266 |> put_view(UserView)
267 |> render("show.json", %{user: unfollowed, for: user})
268
269 {:error, msg} ->
270 forbidden_json_reply(conn, msg)
271 end
272 end
273
274 def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
275 with %Activity{} = activity <- Activity.get_by_id(id),
276 true <- Visibility.visible_for_user?(activity, user) do
277 conn
278 |> put_view(ActivityView)
279 |> render("activity.json", %{activity: activity, for: user})
280 end
281 end
282
283 def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
284 with context when is_binary(context) <- Utils.conversation_id_to_context(id),
285 activities <-
286 ActivityPub.fetch_activities_for_context(context, %{
287 "blocking_user" => user,
288 "user" => user
289 }) do
290 conn
291 |> put_view(ActivityView)
292 |> render("index.json", %{activities: activities, for: user})
293 end
294 end
295
296 @doc """
297 Updates metadata of uploaded media object.
298 Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create).
299 """
300 def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
301 object = Repo.get(Object, id)
302 description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]
303
304 {conn, status, response_body} =
305 cond do
306 !object ->
307 {halt(conn), :not_found, ""}
308
309 !Object.authorize_mutation(object, user) ->
310 {halt(conn), :forbidden, "You can only update your own uploads."}
311
312 !is_binary(description) ->
313 {conn, :not_modified, ""}
314
315 true ->
316 new_data = Map.put(object.data, "name", description)
317
318 {:ok, _} =
319 object
320 |> Object.change(%{data: new_data})
321 |> Repo.update()
322
323 {conn, :no_content, ""}
324 end
325
326 conn
327 |> put_status(status)
328 |> json(response_body)
329 end
330
331 def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
332 response = TwitterAPI.upload(media, user)
333
334 conn
335 |> put_resp_content_type("application/atom+xml")
336 |> send_resp(200, response)
337 end
338
339 def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
340 response = TwitterAPI.upload(media, user, "json")
341
342 conn
343 |> json_reply(200, response)
344 end
345
346 def get_by_id_or_ap_id(id) do
347 activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
348
349 if activity.data["type"] == "Create" do
350 activity
351 else
352 Activity.get_create_by_object_ap_id(activity.data["object"])
353 end
354 end
355
356 def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
357 with {:ok, activity} <- TwitterAPI.fav(user, id) do
358 conn
359 |> put_view(ActivityView)
360 |> render("activity.json", %{activity: activity, for: user})
361 else
362 _ -> json_reply(conn, 400, Jason.encode!(%{}))
363 end
364 end
365
366 def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
367 with {:ok, activity} <- TwitterAPI.unfav(user, id) do
368 conn
369 |> put_view(ActivityView)
370 |> render("activity.json", %{activity: activity, for: user})
371 else
372 _ -> json_reply(conn, 400, Jason.encode!(%{}))
373 end
374 end
375
376 def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
377 with {:ok, activity} <- TwitterAPI.repeat(user, id) do
378 conn
379 |> put_view(ActivityView)
380 |> render("activity.json", %{activity: activity, for: user})
381 else
382 _ -> json_reply(conn, 400, Jason.encode!(%{}))
383 end
384 end
385
386 def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
387 with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
388 conn
389 |> put_view(ActivityView)
390 |> render("activity.json", %{activity: activity, for: user})
391 else
392 _ -> json_reply(conn, 400, Jason.encode!(%{}))
393 end
394 end
395
396 def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
397 with {:ok, activity} <- TwitterAPI.pin(user, id) do
398 conn
399 |> put_view(ActivityView)
400 |> render("activity.json", %{activity: activity, for: user})
401 else
402 {:error, message} -> bad_request_reply(conn, message)
403 err -> err
404 end
405 end
406
407 def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
408 with {:ok, activity} <- TwitterAPI.unpin(user, id) do
409 conn
410 |> put_view(ActivityView)
411 |> render("activity.json", %{activity: activity, for: user})
412 else
413 {:error, message} -> bad_request_reply(conn, message)
414 err -> err
415 end
416 end
417
418 def register(conn, params) do
419 with {:ok, user} <- TwitterAPI.register_user(params) do
420 conn
421 |> put_view(UserView)
422 |> render("show.json", %{user: user})
423 else
424 {:error, errors} ->
425 conn
426 |> json_reply(400, Jason.encode!(errors))
427 end
428 end
429
430 def password_reset(conn, params) do
431 nickname_or_email = params["email"] || params["nickname"]
432
433 with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
434 json_response(conn, :no_content, "")
435 end
436 end
437
438 def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
439 with %User{} = user <- User.get_cached_by_id(uid),
440 true <- user.local,
441 true <- user.info.confirmation_pending,
442 true <- user.info.confirmation_token == token,
443 info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
444 changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
445 {:ok, _} <- User.update_and_set_cache(changeset) do
446 conn
447 |> redirect(to: "/")
448 end
449 end
450
451 def resend_confirmation_email(conn, params) do
452 nickname_or_email = params["email"] || params["nickname"]
453
454 with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
455 {:ok, _} <- User.try_send_confirmation_email(user) do
456 conn
457 |> json_response(:no_content, "")
458 end
459 end
460
461 def update_avatar(%{assigns: %{user: user}} = conn, params) do
462 {:ok, object} = ActivityPub.upload(params, type: :avatar)
463 change = Changeset.change(user, %{avatar: object.data})
464 {:ok, user} = User.update_and_set_cache(change)
465 CommonAPI.update(user)
466
467 conn
468 |> put_view(UserView)
469 |> render("show.json", %{user: user, for: user})
470 end
471
472 def update_banner(%{assigns: %{user: user}} = conn, params) do
473 with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
474 new_info <- %{"banner" => object.data},
475 info_cng <- User.Info.profile_update(user.info, new_info),
476 changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
477 {:ok, user} <- User.update_and_set_cache(changeset) do
478 CommonAPI.update(user)
479 %{"url" => [%{"href" => href} | _]} = object.data
480 response = %{url: href} |> Jason.encode!()
481
482 conn
483 |> json_reply(200, response)
484 end
485 end
486
487 def update_background(%{assigns: %{user: user}} = conn, params) do
488 with {:ok, object} <- ActivityPub.upload(params, type: :background),
489 new_info <- %{"background" => object.data},
490 info_cng <- User.Info.profile_update(user.info, new_info),
491 changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
492 {:ok, _user} <- User.update_and_set_cache(changeset) do
493 %{"url" => [%{"href" => href} | _]} = object.data
494 response = %{url: href} |> Jason.encode!()
495
496 conn
497 |> json_reply(200, response)
498 end
499 end
500
501 def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
502 with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
503 response <- Jason.encode!(user_map) do
504 conn
505 |> json_reply(200, response)
506 else
507 _e ->
508 conn
509 |> put_status(404)
510 |> json(%{error: "Can't find user"})
511 end
512 end
513
514 def followers(%{assigns: %{user: for_user}} = conn, params) do
515 {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
516
517 with {:ok, user} <- TwitterAPI.get_user(for_user, params),
518 {:ok, followers} <- User.get_followers(user, page) do
519 followers =
520 cond do
521 for_user && user.id == for_user.id -> followers
522 user.info.hide_followers -> []
523 true -> followers
524 end
525
526 conn
527 |> put_view(UserView)
528 |> render("index.json", %{users: followers, for: conn.assigns[:user]})
529 else
530 _e -> bad_request_reply(conn, "Can't get followers")
531 end
532 end
533
534 def friends(%{assigns: %{user: for_user}} = conn, params) do
535 {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
536 {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)
537
538 page = if export, do: nil, else: page
539
540 with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
541 {:ok, friends} <- User.get_friends(user, page) do
542 friends =
543 cond do
544 for_user && user.id == for_user.id -> friends
545 user.info.hide_follows -> []
546 true -> friends
547 end
548
549 conn
550 |> put_view(UserView)
551 |> render("index.json", %{users: friends, for: conn.assigns[:user]})
552 else
553 _e -> bad_request_reply(conn, "Can't get friends")
554 end
555 end
556
557 def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
558 with oauth_tokens <- Token.get_user_tokens(user) do
559 conn
560 |> put_view(TokenView)
561 |> render("index.json", %{tokens: oauth_tokens})
562 end
563 end
564
565 def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
566 Token.delete_user_token(user, id)
567
568 json_reply(conn, 201, "")
569 end
570
571 def blocks(%{assigns: %{user: user}} = conn, _params) do
572 with blocked_users <- User.blocked_users(user) do
573 conn
574 |> put_view(UserView)
575 |> render("index.json", %{users: blocked_users, for: user})
576 end
577 end
578
579 def friend_requests(conn, params) do
580 with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
581 {:ok, friend_requests} <- User.get_follow_requests(user) do
582 conn
583 |> put_view(UserView)
584 |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
585 else
586 _e -> bad_request_reply(conn, "Can't get friend requests")
587 end
588 end
589
590 def approve_friend_request(conn, %{"user_id" => uid} = _params) do
591 with followed <- conn.assigns[:user],
592 %User{} = follower <- User.get_cached_by_id(uid),
593 {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
594 conn
595 |> put_view(UserView)
596 |> render("show.json", %{user: follower, for: followed})
597 else
598 e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
599 end
600 end
601
602 def deny_friend_request(conn, %{"user_id" => uid} = _params) do
603 with followed <- conn.assigns[:user],
604 %User{} = follower <- User.get_cached_by_id(uid),
605 {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
606 conn
607 |> put_view(UserView)
608 |> render("show.json", %{user: follower, for: followed})
609 else
610 e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
611 end
612 end
613
614 def friends_ids(%{assigns: %{user: user}} = conn, _params) do
615 with {:ok, friends} <- User.get_friends(user) do
616 ids =
617 friends
618 |> Enum.map(fn x -> x.id end)
619 |> Jason.encode!()
620
621 json(conn, ids)
622 else
623 _e -> bad_request_reply(conn, "Can't get friends")
624 end
625 end
626
627 def empty_array(conn, _params) do
628 json(conn, Jason.encode!([]))
629 end
630
631 def raw_empty_array(conn, _params) do
632 json(conn, [])
633 end
634
635 defp build_info_cng(user, params) do
636 info_params =
637 ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
638 |> Enum.reduce(%{}, fn key, res ->
639 if value = params[key] do
640 Map.put(res, key, value == "true")
641 else
642 res
643 end
644 end)
645
646 info_params =
647 if value = params["default_scope"] do
648 Map.put(info_params, "default_scope", value)
649 else
650 info_params
651 end
652
653 User.Info.profile_update(user.info, info_params)
654 end
655
656 defp parse_profile_bio(user, params) do
657 if bio = params["description"] do
658 emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
659
660 emojis =
661 ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
662 |> Enum.dedup()
663
664 user_info =
665 user.info
666 |> Map.put(
667 "emoji",
668 emojis
669 )
670
671 params
672 |> Map.put("bio", User.parse_bio(bio, user))
673 |> Map.put("info", user_info)
674 else
675 params
676 end
677 end
678
679 def update_profile(%{assigns: %{user: user}} = conn, params) do
680 params = parse_profile_bio(user, params)
681 info_cng = build_info_cng(user, params)
682
683 with changeset <- User.update_changeset(user, params),
684 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
685 {:ok, user} <- User.update_and_set_cache(changeset) do
686 CommonAPI.update(user)
687
688 conn
689 |> put_view(UserView)
690 |> render("user.json", %{user: user, for: user})
691 else
692 error ->
693 Logger.debug("Can't update user: #{inspect(error)}")
694 bad_request_reply(conn, "Can't update user")
695 end
696 end
697
698 def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
699 activities = TwitterAPI.search(user, params)
700
701 conn
702 |> put_view(ActivityView)
703 |> render("index.json", %{activities: activities, for: user})
704 end
705
706 def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
707 users = User.search(query, resolve: true, for_user: user)
708
709 conn
710 |> put_view(UserView)
711 |> render("index.json", %{users: users, for: user})
712 end
713
714 defp bad_request_reply(conn, error_message) do
715 json = error_json(conn, error_message)
716 json_reply(conn, 400, json)
717 end
718
719 defp json_reply(conn, status, json) do
720 conn
721 |> put_resp_content_type("application/json")
722 |> send_resp(status, json)
723 end
724
725 defp forbidden_json_reply(conn, error_message) do
726 json = error_json(conn, error_message)
727 json_reply(conn, 403, json)
728 end
729
730 def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
731
732 def only_if_public_instance(conn, _) do
733 if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
734 conn
735 else
736 conn
737 |> forbidden_json_reply("Invalid credentials.")
738 |> halt()
739 end
740 end
741
742 defp error_json(conn, error_message) do
743 %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
744 end
745
746 def errors(conn, {:param_cast, _}) do
747 conn
748 |> put_status(400)
749 |> json("Invalid parameters")
750 end
751
752 def errors(conn, _) do
753 conn
754 |> put_status(500)
755 |> json("Something went wrong")
756 end
757 end