Introduce comments below events
Also add tomstones Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -29,8 +29,19 @@ defmodule Mobilizon.Events.Comment do
|
||||
origin_comment: t
|
||||
}
|
||||
|
||||
@required_attrs [:text, :actor_id, :url]
|
||||
@optional_attrs [:event_id, :in_reply_to_comment_id, :origin_comment_id, :attributed_to_id]
|
||||
# When deleting an event we only nihilify everything
|
||||
@required_attrs [:url]
|
||||
@creation_required_attrs @required_attrs ++ [:text, :actor_id]
|
||||
@deletion_required_attrs @required_attrs ++ [:deleted_at]
|
||||
@optional_attrs [
|
||||
:text,
|
||||
:actor_id,
|
||||
:event_id,
|
||||
:in_reply_to_comment_id,
|
||||
:origin_comment_id,
|
||||
:attributed_to_id,
|
||||
:deleted_at
|
||||
]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
schema "comments" do
|
||||
@@ -39,12 +50,15 @@ defmodule Mobilizon.Events.Comment do
|
||||
field(:local, :boolean, default: true)
|
||||
field(:visibility, CommentVisibility, default: :public)
|
||||
field(:uuid, Ecto.UUID)
|
||||
field(:total_replies, :integer, virtual: true, default: 0)
|
||||
field(:deleted_at, :utc_datetime)
|
||||
|
||||
belongs_to(:actor, Actor, foreign_key: :actor_id)
|
||||
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
|
||||
belongs_to(:event, Event, foreign_key: :event_id)
|
||||
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
||||
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
||||
has_many(:replies, Comment, foreign_key: :in_reply_to_comment_id)
|
||||
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||
has_many(:mentions, Mention)
|
||||
|
||||
@@ -62,16 +76,56 @@ defmodule Mobilizon.Events.Comment do
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
def changeset(%__MODULE__{} = comment, attrs) do
|
||||
uuid = Map.get(attrs, :uuid) || Ecto.UUID.generate()
|
||||
url = Map.get(attrs, :url) || generate_url(uuid)
|
||||
comment
|
||||
|> common_changeset(attrs)
|
||||
|> validate_required(@creation_required_attrs)
|
||||
end
|
||||
|
||||
@spec delete_changeset(t, map) :: Ecto.Changeset.t()
|
||||
def delete_changeset(%__MODULE__{} = comment, attrs) do
|
||||
comment
|
||||
|> common_changeset(attrs)
|
||||
|> validate_required(@deletion_required_attrs)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks whether an comment can be managed.
|
||||
"""
|
||||
@spec can_be_managed_by(t, integer | String.t()) :: boolean
|
||||
def can_be_managed_by(%__MODULE__{actor_id: creator_actor_id}, actor_id)
|
||||
when creator_actor_id == actor_id do
|
||||
{:comment_can_be_managed, true}
|
||||
end
|
||||
|
||||
def can_be_managed_by(_comment, _actor), do: {:comment_can_be_managed, false}
|
||||
|
||||
defp common_changeset(%__MODULE__{} = comment, attrs) do
|
||||
comment
|
||||
|> cast(attrs, @attrs)
|
||||
|> put_change(:uuid, uuid)
|
||||
|> put_change(:url, url)
|
||||
|> maybe_generate_uuid()
|
||||
|> maybe_generate_url()
|
||||
|> put_tags(attrs)
|
||||
|> put_mentions(attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
|
||||
@spec maybe_generate_uuid(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_generate_uuid(%Ecto.Changeset{} = changeset) do
|
||||
case fetch_field(changeset, :uuid) do
|
||||
:error -> put_change(changeset, :uuid, Ecto.UUID.generate())
|
||||
{:data, nil} -> put_change(changeset, :uuid, Ecto.UUID.generate())
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
|
||||
with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),
|
||||
{changes, uuid} when changes in [:changes, :data] <- fetch_field(changeset, :uuid),
|
||||
url <- generate_url(uuid) do
|
||||
put_change(changeset, :url, url)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
|
||||
@spec generate_url(String.t()) :: String.t()
|
||||
|
||||
@@ -14,6 +14,7 @@ defmodule Mobilizon.Events.Event do
|
||||
alias Mobilizon.Addresses
|
||||
|
||||
alias Mobilizon.Events.{
|
||||
Comment,
|
||||
EventOptions,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
@@ -111,6 +112,7 @@ defmodule Mobilizon.Events.Event do
|
||||
has_many(:tracks, Track)
|
||||
has_many(:sessions, Session)
|
||||
has_many(:mentions, Mention)
|
||||
has_many(:comments, Comment)
|
||||
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
||||
many_to_many(:participants, Actor, join_through: Participant)
|
||||
|
||||
|
||||
@@ -89,12 +89,21 @@ defmodule Mobilizon.Events do
|
||||
:sessions,
|
||||
:tracks,
|
||||
:tags,
|
||||
:comments,
|
||||
:participants,
|
||||
:physical_address,
|
||||
:picture
|
||||
]
|
||||
|
||||
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment, :tags, :mentions]
|
||||
@comment_preloads [
|
||||
:actor,
|
||||
:attributed_to,
|
||||
:in_reply_to_comment,
|
||||
:origin_comment,
|
||||
:replies,
|
||||
:tags,
|
||||
:mentions
|
||||
]
|
||||
|
||||
@doc """
|
||||
Gets a single event.
|
||||
@@ -1001,6 +1010,29 @@ defmodule Mobilizon.Events do
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def data() do
|
||||
Dataloader.Ecto.new(Repo, query: &query/2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Query for comment dataloader
|
||||
|
||||
We only get first comment of thread, and count replies.
|
||||
Read: https://hexdocs.pm/absinthe/ecto.html#dataloader
|
||||
"""
|
||||
def query(Comment, _params) do
|
||||
Comment
|
||||
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|
||||
|> where([c, _], is_nil(c.in_reply_to_comment_id))
|
||||
|> where([_, r], is_nil(r.deleted_at))
|
||||
|> group_by([c], c.id)
|
||||
|> select([c, r], %{c | total_replies: count(r.id)})
|
||||
end
|
||||
|
||||
def query(queryable, _) do
|
||||
queryable
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single comment.
|
||||
"""
|
||||
@@ -1015,6 +1047,15 @@ defmodule Mobilizon.Events do
|
||||
@spec get_comment!(integer | String.t()) :: Comment.t()
|
||||
def get_comment!(id), do: Repo.get!(Comment, id)
|
||||
|
||||
def get_comment_with_preload(nil), do: nil
|
||||
|
||||
def get_comment_with_preload(id) do
|
||||
Comment
|
||||
|> where(id: ^id)
|
||||
|> preload_for_comment()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a comment by its URL.
|
||||
"""
|
||||
@@ -1071,6 +1112,25 @@ defmodule Mobilizon.Events do
|
||||
|> Repo.preload(@comment_preloads)
|
||||
end
|
||||
|
||||
def get_threads(event_id) do
|
||||
Comment
|
||||
|> where([c, _], c.event_id == ^event_id and is_nil(c.origin_comment_id))
|
||||
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|
||||
|> group_by([c], c.id)
|
||||
|> select([c, r], %{c | total_replies: count(r.id)})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets paginated replies for root comment
|
||||
"""
|
||||
@spec get_thread_replies(integer()) :: [Comment.t()]
|
||||
def get_thread_replies(parent_id) do
|
||||
parent_id
|
||||
|> public_replies_for_thread_query()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def get_or_create_comment(%{"url" => url} = attrs) do
|
||||
case Repo.get_by(Comment, url: url) do
|
||||
%Comment{} = comment -> {:ok, Repo.preload(comment, @comment_preloads)}
|
||||
@@ -1103,10 +1163,20 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a comment.
|
||||
Deletes a comment
|
||||
|
||||
But actually just empty the fields so that threads are not broken.
|
||||
"""
|
||||
@spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||
def delete_comment(%Comment{} = comment), do: Repo.delete(comment)
|
||||
def delete_comment(%Comment{} = comment) do
|
||||
comment
|
||||
|> Comment.delete_changeset(%{
|
||||
text: nil,
|
||||
actor_id: nil,
|
||||
deleted_at: DateTime.utc_now()
|
||||
})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of public comments.
|
||||
@@ -1119,7 +1189,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Returns the list of public comments for the actor.
|
||||
"""
|
||||
@spec list_public_events_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
@spec list_public_comments_for_actor(Actor.t(), integer | nil, integer | nil) ::
|
||||
{:ok, [Comment.t()], integer}
|
||||
def list_public_comments_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do
|
||||
comments =
|
||||
@@ -1480,6 +1550,13 @@ defmodule Mobilizon.Events do
|
||||
|> preload_for_comment()
|
||||
end
|
||||
|
||||
defp public_replies_for_thread_query(comment_id) do
|
||||
Comment
|
||||
|> where([c], c.origin_comment_id == ^comment_id and c.visibility in ^@public_visibility)
|
||||
|> group_by([c], [c.in_reply_to_comment_id, c.id])
|
||||
|> preload_for_comment()
|
||||
end
|
||||
|
||||
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
|
||||
defp list_participants_for_event_query(event_id) do
|
||||
from(
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Mobilizon.Reports.Report do
|
||||
@type t :: %__MODULE__{
|
||||
content: String.t(),
|
||||
status: ReportStatus.t(),
|
||||
uri: String.t(),
|
||||
url: String.t(),
|
||||
reported: Actor.t(),
|
||||
reporter: Actor.t(),
|
||||
manager: Actor.t(),
|
||||
@@ -23,17 +23,18 @@ defmodule Mobilizon.Reports.Report do
|
||||
notes: [Note.t()]
|
||||
}
|
||||
|
||||
@required_attrs [:uri, :reported_id, :reporter_id]
|
||||
@optional_attrs [:content, :status, :manager_id, :event_id]
|
||||
@required_attrs [:url, :reported_id, :reporter_id]
|
||||
@optional_attrs [:content, :status, :manager_id, :event_id, :local]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@timestamps_opts [type: :utc_datetime]
|
||||
|
||||
@derive {Jason.Encoder, only: [:status, :uri]}
|
||||
@derive {Jason.Encoder, only: [:status, :url]}
|
||||
schema "reports" do
|
||||
field(:content, :string)
|
||||
field(:status, ReportStatus, default: :open)
|
||||
field(:uri, :string)
|
||||
field(:url, :string)
|
||||
field(:local, :boolean, default: true)
|
||||
|
||||
# The reported actor
|
||||
belongs_to(:reported, Actor)
|
||||
@@ -56,14 +57,24 @@ defmodule Mobilizon.Reports.Report do
|
||||
def changeset(%__MODULE__{} = report, attrs) do
|
||||
report
|
||||
|> cast(attrs, @attrs)
|
||||
|> maybe_generate_url()
|
||||
|> maybe_put_comments(attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec creation_changeset(t, map) :: Ecto.Changeset.t()
|
||||
def creation_changeset(%__MODULE__{} = report, attrs) do
|
||||
report
|
||||
|> changeset(attrs)
|
||||
|> put_assoc(:comments, attrs["comments"])
|
||||
defp maybe_put_comments(%Ecto.Changeset{} = changeset, %{comments: comments}) do
|
||||
put_assoc(changeset, :comments, comments)
|
||||
end
|
||||
|
||||
defp maybe_put_comments(%Ecto.Changeset{} = changeset, _), do: changeset
|
||||
|
||||
@spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
|
||||
with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),
|
||||
url <- "#{MobilizonWeb.Endpoint.url()}/report/#{Ecto.UUID.generate()}" do
|
||||
put_change(changeset, :url, url)
|
||||
else
|
||||
_ -> changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
45
lib/mobilizon/tombstone.ex
Normal file
45
lib/mobilizon/tombstone.ex
Normal file
@@ -0,0 +1,45 @@
|
||||
defmodule Mobilizon.Tombstone do
|
||||
@moduledoc """
|
||||
Represent tombstones for deleted objects. Saves only URI
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
uri: String.t(),
|
||||
actor: Actor.t()
|
||||
}
|
||||
|
||||
@required_attrs [:uri, :actor_id]
|
||||
@optional_attrs []
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
schema "tombstones" do
|
||||
field(:uri, :string)
|
||||
belongs_to(:actor, Actor)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(%__MODULE__{} = tombstone, attrs) do
|
||||
tombstone
|
||||
|> cast(attrs, @attrs)
|
||||
|> validate_required(@attrs)
|
||||
end
|
||||
|
||||
@spec create_tombstone(map()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_tombstone(attrs) do
|
||||
%__MODULE__{}
|
||||
|> changeset(attrs)
|
||||
|> Repo.insert(on_conflict: :replace_all, conflict_target: :uri)
|
||||
end
|
||||
|
||||
@spec find_tombstone(String.t()) :: Ecto.Schema.t() | nil
|
||||
def find_tombstone(uri) do
|
||||
Repo.get_by(__MODULE__, uri: uri)
|
||||
end
|
||||
end
|
||||
@@ -9,11 +9,22 @@ defmodule MobilizonWeb.API.Comments do
|
||||
@doc """
|
||||
Create a comment
|
||||
|
||||
Creates a comment from an actor and a status
|
||||
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
|
||||
|
||||
@@ -5,11 +5,7 @@ defmodule MobilizonWeb.API.Reports do
|
||||
|
||||
import Mobilizon.Service.Admin.ActionLogService
|
||||
|
||||
import MobilizonWeb.API.Utils
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Reports, as: ReportsAction
|
||||
alias Mobilizon.Reports.{Note, Report, ReportStatus}
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
@@ -20,44 +16,16 @@ defmodule MobilizonWeb.API.Reports do
|
||||
@doc """
|
||||
Create a report/flag on an actor, and optionally on an event or on comments.
|
||||
"""
|
||||
def report(
|
||||
%{
|
||||
reporter_actor_id: reporter_actor_id,
|
||||
reported_actor_id: reported_actor_id
|
||||
} = args
|
||||
) do
|
||||
with {:reporter, %Actor{url: reporter_url} = _reporter_actor} <-
|
||||
{:reporter, Actors.get_actor!(reporter_actor_id)},
|
||||
{:reported, %Actor{url: reported_actor_url} = reported_actor} <-
|
||||
{:reported, Actors.get_actor!(reported_actor_id)},
|
||||
{:ok, content} <- args |> Map.get(:content, nil) |> make_report_content_text(),
|
||||
{:ok, event} <- args |> Map.get(:event_id, nil) |> get_event(),
|
||||
{:get_report_comments, comments_urls} <-
|
||||
get_report_comments(reported_actor, Map.get(args, :comments_ids, [])),
|
||||
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} <-
|
||||
{:make_activity,
|
||||
ActivityPub.flag(%{
|
||||
reporter_url: reporter_url,
|
||||
reported_actor_url: reported_actor_url,
|
||||
event_url: (!is_nil(event) && event.url) || nil,
|
||||
comments_url: comments_urls,
|
||||
content: content,
|
||||
forward: args[:forward] || false,
|
||||
local: args[:local] || args[:forward] || false
|
||||
})} do
|
||||
{:ok, activity, report}
|
||||
else
|
||||
{:make_activity, err} -> {:error, err}
|
||||
{:error, err} -> {:error, err}
|
||||
{:actor_id, %{}} -> {:error, "Valid `actor_id` required"}
|
||||
{:reporter, nil} -> {:error, "Reporter Actor not found"}
|
||||
{:reported, nil} -> {:error, "Reported Actor not found"}
|
||||
def report(args) do
|
||||
case {:make_activity, ActivityPub.flag(args, Map.get(args, :local, false) == false)} do
|
||||
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} ->
|
||||
{:ok, activity, report}
|
||||
|
||||
{:make_activity, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_event(nil), do: {:ok, nil}
|
||||
defp get_event(event_id), do: Events.get_event(event_id)
|
||||
|
||||
@doc """
|
||||
Update the state of a report
|
||||
"""
|
||||
@@ -72,13 +40,6 @@ defmodule MobilizonWeb.API.Reports do
|
||||
end
|
||||
end
|
||||
|
||||
defp get_report_comments(%Actor{id: actor_id}, comment_ids) do
|
||||
{:get_report_comments,
|
||||
actor_id |> Events.list_comments_by_actor_and_ids(comment_ids) |> Enum.map(& &1.url)}
|
||||
end
|
||||
|
||||
defp get_report_comments(_, _), do: {:get_report_comments, nil}
|
||||
|
||||
@doc """
|
||||
Create a note on a report
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule MobilizonWeb.Resolvers.Admin do
|
||||
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Events.{Event, Comment}
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
alias Mobilizon.Service.Statistics
|
||||
alias Mobilizon.Users.User
|
||||
@@ -90,6 +90,15 @@ defmodule MobilizonWeb.Resolvers.Admin do
|
||||
}
|
||||
end
|
||||
|
||||
defp transform_action_log(Comment, :delete, %ActionLog{
|
||||
changes: changes
|
||||
}) do
|
||||
%{
|
||||
action: :comment_deletion,
|
||||
object: convert_changes_to_struct(Comment, changes)
|
||||
}
|
||||
end
|
||||
|
||||
# Changes are stored as %{"key" => "value"} so we need to convert them back as struct
|
||||
defp convert_changes_to_struct(struct, %{"report_id" => _report_id} = changes) do
|
||||
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}),
|
||||
|
||||
@@ -3,25 +3,73 @@ defmodule MobilizonWeb.Resolvers.Comment do
|
||||
Handles the comment-related GraphQL calls.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Events.Comment
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Comment, as: CommentModel
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors
|
||||
|
||||
alias MobilizonWeb.API.Comments
|
||||
import Mobilizon.Service.Admin.ActionLogService
|
||||
|
||||
require Logger
|
||||
|
||||
def create_comment(_parent, %{text: text, actor_id: actor_id}, %{
|
||||
def get_thread(_parent, %{id: thread_id}, _context) do
|
||||
{:ok, Events.get_thread_replies(thread_id)}
|
||||
end
|
||||
|
||||
def create_comment(_parent, %{actor_id: actor_id} = args, %{
|
||||
context: %{current_user: %User{} = user}
|
||||
}) do
|
||||
with {:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
|
||||
{:ok, _, %Comment{} = comment} <-
|
||||
Comments.create_comment(%{actor_id: actor_id, text: text}) do
|
||||
{:ok, _, %CommentModel{} = comment} <-
|
||||
Comments.create_comment(args) do
|
||||
{:ok, comment}
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def create_comment(_parent, _args, %{}) do
|
||||
def create_comment(_parent, _args, _context) do
|
||||
{:error, "You are not allowed to create a comment if not connected"}
|
||||
end
|
||||
|
||||
def delete_comment(_parent, %{actor_id: actor_id, comment_id: comment_id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
}) do
|
||||
with {actor_id, ""} <- Integer.parse(actor_id),
|
||||
{:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
|
||||
%CommentModel{} = comment <- Events.get_comment_with_preload(comment_id) do
|
||||
cond do
|
||||
{:comment_can_be_managed, true} == CommentModel.can_be_managed_by(comment, actor_id) ->
|
||||
do_delete_comment(comment)
|
||||
|
||||
role in [:moderator, :administrator] ->
|
||||
with {:ok, res} <- do_delete_comment(comment),
|
||||
%Actor{} = actor <- Actors.get_actor(actor_id) do
|
||||
log_action(actor, "delete", comment)
|
||||
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
true ->
|
||||
{:error, "You cannot delete this comment"}
|
||||
end
|
||||
else
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_comment(_parent, _args, %{}) do
|
||||
{:error, "You are not allowed to delete a comment if not connected"}
|
||||
end
|
||||
|
||||
defp do_delete_comment(%CommentModel{} = comment) do
|
||||
with {:ok, _, %CommentModel{} = comment} <-
|
||||
Comments.delete_comment(comment) do
|
||||
{:ok, comment}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,10 +46,10 @@ defmodule MobilizonWeb.Resolvers.Report do
|
||||
"""
|
||||
def create_report(
|
||||
_parent,
|
||||
%{reporter_actor_id: reporter_actor_id} = args,
|
||||
%{reporter_id: reporter_id} = args,
|
||||
%{context: %{current_user: user}} = _resolution
|
||||
) do
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, reporter_actor_id),
|
||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, reporter_id),
|
||||
{:ok, _, %Report{} = report} <- ReportsAPI.report(args) do
|
||||
{:ok, report}
|
||||
else
|
||||
|
||||
@@ -49,7 +49,11 @@ defmodule MobilizonWeb.Router do
|
||||
scope "/api" do
|
||||
pipe_through(:graphql)
|
||||
|
||||
forward("/", Absinthe.Plug, schema: MobilizonWeb.Schema)
|
||||
forward("/", Absinthe.Plug,
|
||||
schema: MobilizonWeb.Schema,
|
||||
analyze_complexity: true,
|
||||
max_complexity: 200
|
||||
)
|
||||
end
|
||||
|
||||
forward("/graphiql", Absinthe.Plug.GraphiQL, schema: MobilizonWeb.Schema)
|
||||
|
||||
@@ -96,7 +96,7 @@ defmodule MobilizonWeb.Schema do
|
||||
Dataloader.new()
|
||||
|> Dataloader.add_source(Actors, default_source)
|
||||
|> Dataloader.add_source(Users, default_source)
|
||||
|> Dataloader.add_source(Events, default_source)
|
||||
|> Dataloader.add_source(Events, Events.data())
|
||||
|> Dataloader.add_source(Addresses, default_source)
|
||||
|> Dataloader.add_source(Media, default_source)
|
||||
|> Dataloader.add_source(Reports, default_source)
|
||||
@@ -117,6 +117,7 @@ defmodule MobilizonWeb.Schema do
|
||||
import_fields(:person_queries)
|
||||
import_fields(:group_queries)
|
||||
import_fields(:event_queries)
|
||||
import_fields(:comment_queries)
|
||||
import_fields(:tag_queries)
|
||||
import_fields(:address_queries)
|
||||
import_fields(:config_queries)
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule MobilizonWeb.Schema.AdminType do
|
||||
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Events.{Event, Comment}
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
|
||||
alias MobilizonWeb.Resolvers.Admin
|
||||
@@ -26,6 +26,7 @@ defmodule MobilizonWeb.Schema.AdminType do
|
||||
value(:note_creation)
|
||||
value(:note_deletion)
|
||||
value(:event_deletion)
|
||||
value(:comment_deletion)
|
||||
value(:event_update)
|
||||
end
|
||||
|
||||
@@ -43,6 +44,9 @@ defmodule MobilizonWeb.Schema.AdminType do
|
||||
%Event{}, _ ->
|
||||
:event
|
||||
|
||||
%Comment{}, _ ->
|
||||
:comment
|
||||
|
||||
_, _ ->
|
||||
nil
|
||||
end)
|
||||
|
||||
@@ -4,9 +4,12 @@ defmodule MobilizonWeb.Schema.CommentType do
|
||||
"""
|
||||
use Absinthe.Schema.Notation
|
||||
alias MobilizonWeb.Resolvers.Comment
|
||||
alias Mobilizon.{Actors, Events}
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
|
||||
@desc "A comment"
|
||||
object :comment do
|
||||
interfaces([:action_log_object])
|
||||
field(:id, :id, description: "Internal ID for this comment")
|
||||
field(:uuid, :uuid)
|
||||
field(:url, :string)
|
||||
@@ -14,8 +17,20 @@ defmodule MobilizonWeb.Schema.CommentType do
|
||||
field(:visibility, :comment_visibility)
|
||||
field(:text, :string)
|
||||
field(:primaryLanguage, :string)
|
||||
field(:replies, list_of(:comment))
|
||||
|
||||
field(:replies, list_of(:comment)) do
|
||||
resolve(dataloader(Events))
|
||||
end
|
||||
|
||||
field(:total_replies, :integer)
|
||||
field(:in_reply_to_comment, :comment, resolve: dataloader(Events))
|
||||
field(:event, :event, resolve: dataloader(Events))
|
||||
field(:origin_comment, :comment, resolve: dataloader(Events))
|
||||
field(:threadLanguages, non_null(list_of(:string)))
|
||||
field(:actor, :person, resolve: dataloader(Actors))
|
||||
field(:inserted_at, :datetime)
|
||||
field(:updated_at, :datetime)
|
||||
field(:deleted_at, :datetime)
|
||||
end
|
||||
|
||||
@desc "The list of visibility options for a comment"
|
||||
@@ -31,13 +46,30 @@ defmodule MobilizonWeb.Schema.CommentType do
|
||||
value(:invite, description: "visible only to people invited")
|
||||
end
|
||||
|
||||
object :comment_queries do
|
||||
@desc "Get replies for thread"
|
||||
field :thread, type: list_of(:comment) do
|
||||
arg(:id, :id)
|
||||
resolve(&Comment.get_thread/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :comment_mutations do
|
||||
@desc "Create a comment"
|
||||
field :create_comment, type: :comment do
|
||||
arg(:text, non_null(:string))
|
||||
arg(:event_id, :id)
|
||||
arg(:in_reply_to_comment_id, :id)
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Comment.create_comment/3)
|
||||
end
|
||||
|
||||
field :delete_comment, type: :comment do
|
||||
arg(:comment_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Comment.delete_comment/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||
import MobilizonWeb.Schema.Utils
|
||||
|
||||
alias Mobilizon.{Actors, Addresses}
|
||||
alias Mobilizon.{Actors, Addresses, Events}
|
||||
|
||||
alias MobilizonWeb.Resolvers.{Event, Picture, Tag}
|
||||
|
||||
@@ -78,6 +78,10 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||
description: "Events related to this one"
|
||||
)
|
||||
|
||||
field(:comments, list_of(:comment), description: "The comments in reply to the event") do
|
||||
resolve(dataloader(Events))
|
||||
end
|
||||
|
||||
# field(:tracks, list_of(:track))
|
||||
# field(:sessions, list_of(:session))
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ defmodule MobilizonWeb.Schema.ReportType do
|
||||
@desc "Create a report"
|
||||
field :create_report, type: :report do
|
||||
arg(:content, :string)
|
||||
arg(:reporter_actor_id, non_null(:id))
|
||||
arg(:reported_actor_id, non_null(:id))
|
||||
arg(:reporter_id, non_null(:id))
|
||||
arg(:reported_id, non_null(:id))
|
||||
arg(:event_id, :id, default_value: nil)
|
||||
arg(:comments_ids, list_of(:id), default_value: [])
|
||||
resolve(&Report.create_report/3)
|
||||
|
||||
@@ -11,15 +11,18 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
import Mobilizon.Service.ActivityPub.Utils
|
||||
import Mobilizon.Service.ActivityPub.Visibility
|
||||
|
||||
alias Mobilizon.{Actors, Config, Events}
|
||||
alias Mobilizon.{Actors, Config, Events, Reports, Users}
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Events.{Comment, Event, Participant}
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Tombstone
|
||||
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Relay, Transmogrifier}
|
||||
alias Mobilizon.Service.{Federator, WebFinger}
|
||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||
alias MobilizonWeb.API.Utils, as: APIUtils
|
||||
alias Mobilizon.Service.ActivityPub.Audience
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||
alias MobilizonWeb.Email.{Admin, Mailer}
|
||||
|
||||
require Logger
|
||||
|
||||
@@ -133,7 +136,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
Logger.debug("creating an activity")
|
||||
Logger.debug(inspect(args))
|
||||
|
||||
with {:ok, entity, create_data} <-
|
||||
with {:tombstone, nil} <- {:tombstone, check_for_tombstones(args)},
|
||||
{:ok, entity, create_data} <-
|
||||
(case type do
|
||||
:event -> create_event(args, additional)
|
||||
:comment -> create_comment(args, additional)
|
||||
@@ -345,6 +349,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with {:ok, %Event{} = event} <- Events.delete_event(event),
|
||||
{:ok, %Tombstone{} = _tombstone} <-
|
||||
Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, event}
|
||||
@@ -361,6 +367,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
|
||||
{:ok, %Tombstone{} = _tombstone} <-
|
||||
Tombstone.create_tombstone(%{uri: comment.url, actor_id: actor.id}),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, comment}
|
||||
@@ -383,25 +391,25 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
end
|
||||
end
|
||||
|
||||
def flag(params) do
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
forward = !(params[:forward] == false)
|
||||
|
||||
additional = params[:additional] || %{}
|
||||
|
||||
additional =
|
||||
if forward do
|
||||
Map.merge(additional, %{"to" => [], "cc" => [params.reported_actor_url]})
|
||||
else
|
||||
Map.merge(additional, %{"to" => [], "cc" => []})
|
||||
end
|
||||
|
||||
with flag_data <- make_flag_data(params, additional),
|
||||
{:ok, activity} <- create_activity(flag_data, local),
|
||||
{:ok, object} <- insert_full_object(flag_data),
|
||||
def flag(args, local \\ false, _additional \\ %{}) do
|
||||
with {:build_args, args} <- {:build_args, prepare_args_for_report(args)},
|
||||
{:create_report, {:ok, %Report{} = report}} <-
|
||||
{:create_report, Reports.create_report(args)},
|
||||
report_as_data <- Convertible.model_to_as(report),
|
||||
{:ok, activity} <- create_activity(report_as_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
Enum.each(Users.list_moderators(), fn moderator ->
|
||||
moderator
|
||||
|> Admin.report(report)
|
||||
|> Mailer.deliver_later()
|
||||
end)
|
||||
|
||||
{:ok, activity, report}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an activity")
|
||||
Logger.debug(inspect(err))
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
@@ -776,6 +784,10 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_for_tombstones(map()) :: Tombstone.t() | nil
|
||||
defp check_for_tombstones(%{url: url}), do: Tombstone.find_tombstone(url)
|
||||
defp check_for_tombstones(_), do: nil
|
||||
|
||||
@spec update_event(Event.t(), map(), map()) ::
|
||||
{:ok, Event.t(), Activity.t()} | any()
|
||||
defp update_event(
|
||||
@@ -930,7 +942,12 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
tags: tags,
|
||||
in_reply_to_comment: in_reply_to_comment,
|
||||
in_reply_to_comment_id:
|
||||
if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id))
|
||||
if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id)),
|
||||
origin_comment_id:
|
||||
if(is_nil(in_reply_to_comment),
|
||||
do: nil,
|
||||
else: Comment.get_thread_id(in_reply_to_comment)
|
||||
)
|
||||
}) do
|
||||
args
|
||||
end
|
||||
@@ -945,4 +962,27 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
%{args | preferred_username: preferred_username, summary: summary}
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_args_for_report(args) do
|
||||
with {:reporter, %Actor{} = reporter_actor} <-
|
||||
{:reporter, Actors.get_actor!(args.reporter_id)},
|
||||
{:reported, %Actor{} = reported_actor} <-
|
||||
{:reported, Actors.get_actor!(args.reported_id)},
|
||||
content <- HtmlSanitizeEx.strip_tags(args.content),
|
||||
event <- Events.get_comment(Map.get(args, :event_id)),
|
||||
{:get_report_comments, comments} <-
|
||||
{:get_report_comments,
|
||||
Events.list_comments_by_actor_and_ids(
|
||||
reported_actor.id,
|
||||
Map.get(args, :comments_ids, [])
|
||||
)} do
|
||||
Map.merge(args, %{
|
||||
reporter: reporter_actor,
|
||||
reported: reported_actor,
|
||||
content: content,
|
||||
event: event,
|
||||
comments: comments
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,7 +73,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
|
||||
# Anything else is kind of a MP
|
||||
{:error, parent} ->
|
||||
Logger.debug("Parent object is something we don't handle")
|
||||
Logger.warn("Parent object is something we don't handle")
|
||||
Logger.debug(inspect(parent))
|
||||
data
|
||||
end
|
||||
@@ -95,7 +95,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
"""
|
||||
@impl Converter
|
||||
@spec model_to_as(CommentModel.t()) :: map
|
||||
def model_to_as(%CommentModel{} = comment) do
|
||||
def model_to_as(%CommentModel{deleted_at: nil} = comment) do
|
||||
to =
|
||||
if comment.visibility == :public,
|
||||
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
@@ -120,4 +120,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
@impl Converter
|
||||
@spec model_to_as(CommentModel.t()) :: map
|
||||
def model_to_as(%CommentModel{} = comment) do
|
||||
%{
|
||||
"type" => "Tombstone",
|
||||
"uuid" => comment.uuid,
|
||||
"id" => comment.url,
|
||||
"published" => comment.inserted_at,
|
||||
"updated" => comment.updated_at,
|
||||
"deleted" => comment.deleted_at
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,9 +14,16 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Reports.Report
|
||||
alias Mobilizon.Service.ActivityPub.Converter
|
||||
alias Mobilizon.Service.ActivityPub.Convertible
|
||||
|
||||
@behaviour Converter
|
||||
|
||||
defimpl Convertible, for: Report do
|
||||
alias Mobilizon.Service.ActivityPub.Converter.Flag, as: FlagConverter
|
||||
|
||||
defdelegate model_to_as(report), to: FlagConverter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts an AP object data to our internal data structure.
|
||||
"""
|
||||
@@ -35,18 +42,29 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Flag do
|
||||
end
|
||||
end
|
||||
|
||||
@audience %{"to" => ["https://www.w3.org/ns/activitystreams#Public"], "cc" => []}
|
||||
|
||||
@doc """
|
||||
Convert an event struct to an ActivityStream representation
|
||||
"""
|
||||
@impl Converter
|
||||
@spec model_to_as(EventModel.t()) :: map
|
||||
@spec model_to_as(Report.t()) :: map
|
||||
def model_to_as(%Report{} = report) do
|
||||
object = [report.reported.url] ++ Enum.map(report.comments, fn comment -> comment.url end)
|
||||
|
||||
object = if report.event, do: object ++ [report.event.url], else: object
|
||||
|
||||
audience =
|
||||
if report.local, do: @audience, else: Map.put(@audience, "cc", [report.reported.url])
|
||||
|
||||
%{
|
||||
"type" => "Flag",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"actor" => report.reporter.url,
|
||||
"id" => report.url
|
||||
"id" => report.url,
|
||||
"content" => report.content,
|
||||
"object" => object
|
||||
}
|
||||
|> Map.merge(audience)
|
||||
end
|
||||
|
||||
@spec as_to_model(map) :: map
|
||||
|
||||
@@ -49,7 +49,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||
object
|
||||
|> Map.put("actor", object["attributedTo"])
|
||||
|> fix_attachments
|
||||
|> fix_in_reply_to
|
||||
|
||||
# |> fix_in_reply_to
|
||||
|
||||
# |> fix_tag
|
||||
end
|
||||
@@ -127,16 +128,17 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||
def handle_incoming(%{"type" => "Flag"} = data) do
|
||||
with params <- Converter.Flag.as_to_model(data) do
|
||||
params = %{
|
||||
reporter_url: params["reporter"].url,
|
||||
reported_actor_url: params["reported"].url,
|
||||
comments_url: params["comments"] |> Enum.map(& &1.url),
|
||||
reporter_id: params["reporter"].id,
|
||||
reported_id: params["reported"].id,
|
||||
comments_ids: params["comments"] |> Enum.map(& &1.id),
|
||||
content: params["content"] || "",
|
||||
additional: %{
|
||||
"cc" => [params["reported"].url]
|
||||
}
|
||||
},
|
||||
local: false
|
||||
}
|
||||
|
||||
ActivityPub.flag(params)
|
||||
ActivityPub.flag(params, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule Mobilizon.Service.ActivityPub.Visibility do
|
||||
Utility functions related to content visibility
|
||||
"""
|
||||
|
||||
alias Mobilizon.Events.Comment
|
||||
alias Mobilizon.Service.ActivityPub.Activity
|
||||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
@@ -17,5 +18,6 @@ defmodule Mobilizon.Service.ActivityPub.Visibility do
|
||||
def is_public?(%{data: data}), do: is_public?(data)
|
||||
def is_public?(%Activity{data: data}), do: is_public?(data)
|
||||
def is_public?(data) when is_map(data), do: @public in (data["to"] ++ (data["cc"] || []))
|
||||
def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
|
||||
def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
|
||||
end
|
||||
|
||||
@@ -34,7 +34,12 @@ defmodule Mobilizon.Service.Formatter do
|
||||
|
||||
def mention_handler("@" <> nickname, buffer, _opts, acc) do
|
||||
case Actors.get_actor_by_name(nickname) do
|
||||
%Actor{id: id, url: url, preferred_username: preferred_username} = actor ->
|
||||
%Actor{preferred_username: preferred_username} = actor ->
|
||||
link = "<span class='h-card mention'>@<span>#{preferred_username}</span></span>"
|
||||
|
||||
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, actor})}}
|
||||
|
||||
%Actor{type: :Person, id: id, url: url, preferred_username: preferred_username} = actor ->
|
||||
link =
|
||||
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{url}'>@<span>#{
|
||||
preferred_username
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Mobilizon.Service.Workers.BuildSearchWorker do
|
||||
|
||||
def perform(%{"op" => "update_search_event", "event_id" => event_id}, _job) do
|
||||
with {:ok, %Event{} = event} <- Events.get_event_with_preload(event_id) do
|
||||
update_search_event(event)
|
||||
insert_search_event(event)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,30 +33,12 @@ defmodule Mobilizon.Service.Workers.BuildSearchWorker do
|
||||
setweight(to_tsvector(unaccent(coalesce($4, ' '))), 'B') ||
|
||||
setweight(to_tsvector(unaccent($3)), 'C')
|
||||
)
|
||||
);
|
||||
""",
|
||||
[
|
||||
event.id,
|
||||
event.title,
|
||||
HtmlSanitizeEx.strip_tags(event.description),
|
||||
get_tags_string(event)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def update_search_event(%Event{} = event) do
|
||||
SQL.query(
|
||||
Repo,
|
||||
"""
|
||||
UPDATE event_search
|
||||
SET document =
|
||||
(SELECT
|
||||
setweight(to_tsvector(unaccent($2)), 'A') ||
|
||||
setweight(to_tsvector(unaccent(coalesce($4, ' '))), 'B') ||
|
||||
setweight(to_tsvector(unaccent($3)), 'C')
|
||||
),
|
||||
title = $2
|
||||
WHERE id = $1;
|
||||
) ON CONFLICT (id) DO UPDATE SET title = $2, document = (
|
||||
SELECT
|
||||
setweight(to_tsvector(unaccent($2)), 'A') ||
|
||||
setweight(to_tsvector(unaccent(coalesce($4, ' '))), 'B') ||
|
||||
setweight(to_tsvector(unaccent($3)), 'C')
|
||||
);
|
||||
""",
|
||||
[
|
||||
event.id,
|
||||
|
||||
Reference in New Issue
Block a user