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