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