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