TwitterAPI: profile update with emoji_map
[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 emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
658
659 emojis =
660 ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
661 |> Enum.dedup()
662
663 user_info =
664 user.info
665 |> Map.put(
666 "emoji",
667 emojis
668 )
669
670 params
671 |> Map.put("bio", User.parse_bio(bio, user))
672 |> Map.put("info", user_info)
673 else
674 params
675 end
676 end
677
678 def update_profile(%{assigns: %{user: user}} = conn, params) do
679 params = parse_profile_bio(user, params)
680 info_cng = build_info_cng(user, params)
681
682 with changeset <- User.update_changeset(user, params),
683 changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
684 {:ok, user} <- User.update_and_set_cache(changeset) do
685 CommonAPI.update(user)
686
687 conn
688 |> put_view(UserView)
689 |> render("user.json", %{user: user, for: user})
690 else
691 error ->
692 Logger.debug("Can't update user: #{inspect(error)}")
693 bad_request_reply(conn, "Can't update user")
694 end
695 end
696
697 def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
698 activities = TwitterAPI.search(user, params)
699
700 conn
701 |> put_view(ActivityView)
702 |> render("index.json", %{activities: activities, for: user})
703 end
704
705 def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
706 users = User.search(query, resolve: true, for_user: user)
707
708 conn
709 |> put_view(UserView)
710 |> render("index.json", %{users: users, for: user})
711 end
712
713 defp bad_request_reply(conn, error_message) do
714 json = error_json(conn, error_message)
715 json_reply(conn, 400, json)
716 end
717
718 defp json_reply(conn, status, json) do
719 conn
720 |> put_resp_content_type("application/json")
721 |> send_resp(status, json)
722 end
723
724 defp forbidden_json_reply(conn, error_message) do
725 json = error_json(conn, error_message)
726 json_reply(conn, 403, json)
727 end
728
729 def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
730
731 def only_if_public_instance(conn, _) do
732 if Keyword.get(Application.get_env(:pleroma, :instance), :public) do
733 conn
734 else
735 conn
736 |> forbidden_json_reply("Invalid credentials.")
737 |> halt()
738 end
739 end
740
741 defp error_json(conn, error_message) do
742 %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
743 end
744
745 def errors(conn, {:param_cast, _}) do
746 conn
747 |> put_status(400)
748 |> json("Invalid parameters")
749 end
750
751 def errors(conn, _) do
752 conn
753 |> put_status(500)
754 |> json("Something went wrong")
755 end
756 end