Move API under GraphQL context
This commit is contained in:
30
lib/graphql/api/comments.ex
Normal file
30
lib/graphql/api/comments.ex
Normal file
@@ -0,0 +1,30 @@
|
||||
defmodule Mobilizon.GraphQL.API.Comments do
|
||||
@moduledoc """
|
||||
API for Comments.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Events.Comment
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
|
||||
@doc """
|
||||
Create a comment
|
||||
|
||||
Creates a comment from an actor
|
||||
"""
|
||||
@spec create_comment(map) :: {:ok, Activity.t(), Comment.t()} | any
|
||||
def create_comment(args) do
|
||||
ActivityPub.create(:comment, args, true)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a comment
|
||||
|
||||
Deletes a comment from an actor
|
||||
"""
|
||||
@spec delete_comment(Comment.t()) :: {:ok, Activity.t(), Comment.t()} | any
|
||||
def delete_comment(%Comment{} = comment) do
|
||||
ActivityPub.delete(comment, true)
|
||||
end
|
||||
end
|
||||
62
lib/graphql/api/events.ex
Normal file
62
lib/graphql/api/events.ex
Normal file
@@ -0,0 +1,62 @@
|
||||
defmodule Mobilizon.GraphQL.API.Events do
|
||||
@moduledoc """
|
||||
API for Events.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Event
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Utils}
|
||||
|
||||
@doc """
|
||||
Create an event
|
||||
"""
|
||||
@spec create_event(map) :: {:ok, Activity.t(), Event.t()} | any
|
||||
def create_event(args) do
|
||||
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||
args <-
|
||||
Map.update(args, :picture, nil, fn picture ->
|
||||
process_picture(picture, organizer_actor)
|
||||
end) do
|
||||
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
||||
ActivityPub.create(:event, args, args.draft == false)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update an event
|
||||
"""
|
||||
@spec update_event(map, Event.t()) :: {:ok, Activity.t(), Event.t()} | any
|
||||
def update_event(args, %Event{} = event) do
|
||||
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||
args <-
|
||||
Map.update(args, :picture, nil, fn picture ->
|
||||
process_picture(picture, organizer_actor)
|
||||
end) do
|
||||
ActivityPub.update(:event, event, args, Map.get(args, :draft, false) == false)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Trigger the deletion of an event
|
||||
|
||||
If the event is deleted by
|
||||
"""
|
||||
def delete_event(%Event{} = event, federate \\ true) do
|
||||
ActivityPub.delete(event, federate)
|
||||
end
|
||||
|
||||
defp process_picture(nil, _), do: nil
|
||||
defp process_picture(%{picture_id: _picture_id} = args, _), do: args
|
||||
|
||||
defp process_picture(%{picture: picture}, %Actor{id: actor_id}) do
|
||||
%{
|
||||
file:
|
||||
picture
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_picture_data(description: Map.get(picture, :name)),
|
||||
actor_id: actor_id
|
||||
}
|
||||
end
|
||||
end
|
||||
71
lib/graphql/api/follows.ex
Normal file
71
lib/graphql/api/follows.ex
Normal file
@@ -0,0 +1,71 @@
|
||||
defmodule Mobilizon.GraphQL.API.Follows do
|
||||
@moduledoc """
|
||||
Common API for following, unfollowing, accepting and rejecting stuff.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
|
||||
require Logger
|
||||
|
||||
def follow(%Actor{} = follower, %Actor{} = followed) do
|
||||
case ActivityPub.follow(follower, followed) do
|
||||
{:ok, activity, follow} ->
|
||||
{:ok, activity, follow}
|
||||
|
||||
e ->
|
||||
Logger.warn("Error while following actor: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def unfollow(%Actor{} = follower, %Actor{} = followed) do
|
||||
case ActivityPub.unfollow(follower, followed) do
|
||||
{:ok, activity, follow} ->
|
||||
{:ok, activity, follow}
|
||||
|
||||
e ->
|
||||
Logger.warn("Error while unfollowing actor: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def accept(%Actor{} = follower, %Actor{} = followed) do
|
||||
Logger.debug("We're trying to accept a follow")
|
||||
|
||||
with %Follower{approved: false} = follow <-
|
||||
Actors.is_following(follower, followed),
|
||||
{:ok, %Activity{} = activity, %Follower{approved: true} = follow} <-
|
||||
ActivityPub.accept(
|
||||
:follow,
|
||||
follow,
|
||||
true
|
||||
) do
|
||||
{:ok, activity, follow}
|
||||
else
|
||||
%Follower{approved: true} ->
|
||||
{:error, "Follow already accepted"}
|
||||
end
|
||||
end
|
||||
|
||||
def reject(%Actor{} = follower, %Actor{} = followed) do
|
||||
Logger.debug("We're trying to reject a follow")
|
||||
|
||||
with %Follower{} = follow <-
|
||||
Actors.is_following(follower, followed),
|
||||
{:ok, %Activity{} = activity, %Follower{} = follow} <-
|
||||
ActivityPub.reject(
|
||||
:follow,
|
||||
follow,
|
||||
true
|
||||
) do
|
||||
{:ok, activity, follow}
|
||||
else
|
||||
%Follower{approved: true} ->
|
||||
{:error, "Follow already accepted"}
|
||||
end
|
||||
end
|
||||
end
|
||||
32
lib/graphql/api/groups.ex
Normal file
32
lib/graphql/api/groups.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule Mobilizon.GraphQL.API.Groups do
|
||||
@moduledoc """
|
||||
API for Groups.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
|
||||
@doc """
|
||||
Create a group
|
||||
"""
|
||||
@spec create_group(map) :: {:ok, Activity.t(), Actor.t()} | any
|
||||
def create_group(args) do
|
||||
with preferred_username <-
|
||||
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
|
||||
{:existing_group, nil} <-
|
||||
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
|
||||
{:ok, %Activity{} = activity, %Actor{} = group} <-
|
||||
ActivityPub.create(:group, args, true, %{"actor" => args.creator_actor.url}) do
|
||||
{:ok, activity, group}
|
||||
else
|
||||
{:existing_group, _} ->
|
||||
{:error, "A group with this name already exists"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
end
|
||||
68
lib/graphql/api/participations.ex
Normal file
68
lib/graphql/api/participations.ex
Normal file
@@ -0,0 +1,68 @@
|
||||
defmodule Mobilizon.GraphQL.API.Participations do
|
||||
@moduledoc """
|
||||
Common API to join events and groups.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
|
||||
alias MobilizonWeb.Email.Participation
|
||||
|
||||
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
|
||||
def join(%Event{id: event_id} = event, %Actor{id: actor_id} = actor) do
|
||||
with {:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id),
|
||||
{:ok, activity, participant} <- ActivityPub.join(event, actor, true) do
|
||||
{:ok, activity, participant}
|
||||
end
|
||||
end
|
||||
|
||||
def leave(%Event{} = event, %Actor{} = actor) do
|
||||
with {:ok, activity, participant} <- ActivityPub.leave(event, actor, true) do
|
||||
{:ok, activity, participant}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update participation status
|
||||
"""
|
||||
def update(%Participant{} = participation, %Actor{} = moderator, :participant) do
|
||||
accept(participation, moderator)
|
||||
end
|
||||
|
||||
def update(%Participant{} = participation, %Actor{} = moderator, :rejected) do
|
||||
reject(participation, moderator)
|
||||
end
|
||||
|
||||
defp accept(
|
||||
%Participant{} = participation,
|
||||
%Actor{} = moderator
|
||||
) do
|
||||
with {:ok, activity, %Participant{role: :participant} = participation} <-
|
||||
ActivityPub.accept(
|
||||
:join,
|
||||
participation,
|
||||
true,
|
||||
%{"actor" => moderator.url}
|
||||
),
|
||||
:ok <- Participation.send_emails_to_local_user(participation) do
|
||||
{:ok, activity, participation}
|
||||
end
|
||||
end
|
||||
|
||||
defp reject(
|
||||
%Participant{} = participation,
|
||||
%Actor{} = moderator
|
||||
) do
|
||||
with {:ok, activity, %Participant{role: :rejected} = participation} <-
|
||||
ActivityPub.reject(
|
||||
:join,
|
||||
participation,
|
||||
%{"actor" => moderator.url}
|
||||
),
|
||||
:ok <- Participation.send_emails_to_local_user(participation) do
|
||||
{:ok, activity, participation}
|
||||
end
|
||||
end
|
||||
end
|
||||
90
lib/graphql/api/reports.ex
Normal file
90
lib/graphql/api/reports.ex
Normal file
@@ -0,0 +1,90 @@
|
||||
defmodule Mobilizon.GraphQL.API.Reports do
|
||||
@moduledoc """
|
||||
API for Reports.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.{Admin, Users}
|
||||
alias Mobilizon.Reports, as: ReportsAction
|
||||
alias Mobilizon.Reports.{Note, Report, ReportStatus}
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
|
||||
@doc """
|
||||
Create a report/flag on an actor, and optionally on an event or on comments.
|
||||
"""
|
||||
def report(args) do
|
||||
case {:make_activity, ActivityPub.flag(args, Map.get(args, :forward, false) == true)} do
|
||||
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} ->
|
||||
{:ok, activity, report}
|
||||
|
||||
{:make_activity, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update the state of a report
|
||||
"""
|
||||
def update_report_status(%Actor{} = actor, %Report{} = report, state) do
|
||||
with {:valid_state, true} <-
|
||||
{:valid_state, ReportStatus.valid_value?(state)},
|
||||
{:ok, report} <- ReportsAction.update_report(report, %{"status" => state}),
|
||||
{:ok, _} <- Admin.log_action(actor, "update", report) do
|
||||
{:ok, report}
|
||||
else
|
||||
{:valid_state, false} -> {:error, "Unsupported state"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a note on a report
|
||||
"""
|
||||
@spec create_report_note(Report.t(), Actor.t(), String.t()) :: {:ok, Note.t()}
|
||||
def create_report_note(
|
||||
%Report{id: report_id},
|
||||
%Actor{id: moderator_id, user_id: user_id} = moderator,
|
||||
content
|
||||
) do
|
||||
with %User{role: role} <- Users.get_user!(user_id),
|
||||
{:role, true} <- {:role, role in [:administrator, :moderator]},
|
||||
{:ok, %Note{} = note} <-
|
||||
Mobilizon.Reports.create_note(%{
|
||||
"report_id" => report_id,
|
||||
"moderator_id" => moderator_id,
|
||||
"content" => content
|
||||
}),
|
||||
{:ok, _} <- Admin.log_action(moderator, "create", note) do
|
||||
{:ok, note}
|
||||
else
|
||||
{:role, false} ->
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delete a report note
|
||||
"""
|
||||
@spec delete_report_note(Note.t(), Actor.t()) :: {:ok, Note.t()}
|
||||
def delete_report_note(
|
||||
%Note{moderator_id: note_moderator_id} = note,
|
||||
%Actor{id: moderator_id, user_id: user_id} = moderator
|
||||
) do
|
||||
with {:same_actor, true} <- {:same_actor, note_moderator_id == moderator_id},
|
||||
%User{role: role} <- Users.get_user!(user_id),
|
||||
{:role, true} <- {:role, role in [:administrator, :moderator]},
|
||||
{:ok, %Note{} = note} <-
|
||||
Mobilizon.Reports.delete_note(note),
|
||||
{:ok, _} <- Admin.log_action(moderator, "delete", note) do
|
||||
{:ok, note}
|
||||
else
|
||||
{:role, false} ->
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
|
||||
{:same_actor, false} ->
|
||||
{:error, "You can only remove your own notes"}
|
||||
end
|
||||
end
|
||||
end
|
||||
109
lib/graphql/api/search.ex
Normal file
109
lib/graphql/api/search.ex
Normal file
@@ -0,0 +1,109 @@
|
||||
defmodule Mobilizon.GraphQL.API.Search do
|
||||
@moduledoc """
|
||||
API for search.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.ActorType
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Storage.Page
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Searches actors.
|
||||
"""
|
||||
@spec search_actors(String.t(), integer | nil, integer | nil, ActorType.t()) ::
|
||||
{:ok, Page.t()} | {:error, String.t()}
|
||||
def search_actors(search, page \\ 1, limit \\ 10, result_type) do
|
||||
search = String.trim(search)
|
||||
|
||||
cond do
|
||||
search == "" ->
|
||||
{:error, "Search can't be empty"}
|
||||
|
||||
# Some URLs could be domain.tld/@username, so keep this condition above
|
||||
# the `is_handle` function
|
||||
is_url(search) ->
|
||||
# skip, if it's not an actor
|
||||
case process_from_url(search) do
|
||||
%Page{total: _total, elements: _elements} = page ->
|
||||
{:ok, page}
|
||||
|
||||
_ ->
|
||||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
|
||||
is_handle(search) ->
|
||||
{:ok, process_from_username(search)}
|
||||
|
||||
true ->
|
||||
page = Actors.build_actors_by_username_or_name_page(search, [result_type], page, limit)
|
||||
|
||||
{:ok, page}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Search events
|
||||
"""
|
||||
@spec search_events(String.t(), integer | nil, integer | nil) ::
|
||||
{:ok, Page.t()} | {:error, String.t()}
|
||||
def search_events(search, page \\ 1, limit \\ 10) do
|
||||
search = String.trim(search)
|
||||
|
||||
cond do
|
||||
search == "" ->
|
||||
{:error, "Search can't be empty"}
|
||||
|
||||
is_url(search) ->
|
||||
# skip, if it's w not an actor
|
||||
case process_from_url(search) do
|
||||
%Page{total: _total, elements: _elements} = page ->
|
||||
{:ok, page}
|
||||
|
||||
_ ->
|
||||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
|
||||
true ->
|
||||
{:ok, Events.build_events_for_search(search, page, limit)}
|
||||
end
|
||||
end
|
||||
|
||||
# If the search string is an username
|
||||
@spec process_from_username(String.t()) :: Page.t()
|
||||
defp process_from_username(search) do
|
||||
case ActivityPub.find_or_make_actor_from_nickname(search) do
|
||||
{:ok, actor} ->
|
||||
%Page{total: 1, elements: [actor]}
|
||||
|
||||
{:error, _err} ->
|
||||
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
||||
|
||||
%Page{total: 0, elements: []}
|
||||
end
|
||||
end
|
||||
|
||||
# If the search string is an URL
|
||||
@spec process_from_url(String.t()) :: Page.t()
|
||||
defp process_from_url(search) do
|
||||
case ActivityPub.fetch_object_from_url(search) do
|
||||
{:ok, object} ->
|
||||
%Page{total: 1, elements: [object]}
|
||||
|
||||
{:error, _err} ->
|
||||
Logger.debug(fn -> "Unable to find or make object from URL '#{search}'" end)
|
||||
|
||||
%Page{total: 0, elements: []}
|
||||
end
|
||||
end
|
||||
|
||||
@spec is_url(String.t()) :: boolean
|
||||
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"])
|
||||
|
||||
@spec is_handle(String.t()) :: boolean
|
||||
defp is_handle(search), do: String.match?(search, ~r/@/)
|
||||
end
|
||||
43
lib/graphql/api/utils.ex
Normal file
43
lib/graphql/api/utils.ex
Normal file
@@ -0,0 +1,43 @@
|
||||
defmodule Mobilizon.GraphQL.API.Utils do
|
||||
@moduledoc """
|
||||
Utils for API.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Service.Formatter
|
||||
|
||||
@doc """
|
||||
Creates HTML content from text and mentions
|
||||
"""
|
||||
@spec make_content_html(String.t(), list(), String.t()) :: String.t()
|
||||
def make_content_html(text, additional_tags, content_type) do
|
||||
with {text, mentions, tags} <- format_input(text, content_type, []) do
|
||||
{text, mentions, additional_tags ++ Enum.map(tags, fn {_, tag} -> tag end)}
|
||||
end
|
||||
end
|
||||
|
||||
def format_input(text, "text/plain", options) do
|
||||
text
|
||||
|> Formatter.html_escape("text/plain")
|
||||
|> Formatter.linkify(options)
|
||||
|> (fn {text, mentions, tags} -> {String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags} end).()
|
||||
end
|
||||
|
||||
def format_input(text, "text/html", options) do
|
||||
text
|
||||
|> Formatter.html_escape("text/html")
|
||||
|> Formatter.linkify(options)
|
||||
end
|
||||
|
||||
def make_report_content_text(nil), do: {:ok, nil}
|
||||
|
||||
def make_report_content_text(comment) do
|
||||
max_size = Config.get([:instance, :max_report_comment_size], 1000)
|
||||
|
||||
if String.length(comment) <= max_size do
|
||||
{:ok, Formatter.html_escape(comment, "text/plain")}
|
||||
else
|
||||
{:error, "Comment must be up to #{max_size} characters"}
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user