Refactor Core things, including Ecto handling, ActivityPub & Transmogrifier modules

* Data doesn't need anymore to be converted to ActivityStream format to
be saved (this was taken from Pleroma and not at all a good idea here)
* Everything saved when creating an event is inserted into PostgreSQL in
a single transaction
This commit is contained in:
Thomas Citharel
2019-10-25 17:43:37 +02:00
parent 814cfbc8eb
commit cc820d6b63
69 changed files with 1881 additions and 1424 deletions

View File

@@ -13,6 +13,7 @@ defmodule Mobilizon.Actors.Actor do
alias Mobilizon.Media.File
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Users.User
alias Mobilizon.Mention
alias MobilizonWeb.Endpoint
alias MobilizonWeb.Router.Helpers, as: Routes
@@ -46,6 +47,7 @@ defmodule Mobilizon.Actors.Actor do
created_reports: [Report.t()],
subject_reports: [Report.t()],
report_notes: [Note.t()],
mentions: [Mention.t()],
memberships: [t]
}
@@ -139,6 +141,7 @@ defmodule Mobilizon.Actors.Actor do
has_many(:created_reports, Report, foreign_key: :reporter_id)
has_many(:subject_reports, Report, foreign_key: :reported_id)
has_many(:report_notes, Note, foreign_key: :moderator_id)
has_many(:mentions, Mention)
many_to_many(:memberships, __MODULE__, join_through: Member)
timestamps()

View File

@@ -88,7 +88,10 @@ defmodule Mobilizon.Actors do
"""
@spec get_actor_by_url(String.t(), boolean) ::
{:ok, Actor.t()} | {:error, :actor_not_found}
def get_actor_by_url(url, preload \\ false) do
def get_actor_by_url(url, preload \\ false)
def get_actor_by_url(nil, _preload), do: {:error, :actor_not_found}
def get_actor_by_url(url, preload) do
case Repo.get_by(Actor, url: url) do
nil ->
{:error, :actor_not_found}

View File

@@ -65,8 +65,7 @@ defmodule Mobilizon.Addresses.Address do
@spec set_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp set_url(%Ecto.Changeset{changes: changes} = changeset) do
uuid = Ecto.UUID.generate()
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{uuid}")
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{Ecto.UUID.generate()}")
put_change(changeset, :url, url)
end

View File

@@ -8,7 +8,8 @@ defmodule Mobilizon.Events.Comment do
import Ecto.Changeset
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Comment, CommentVisibility, Event}
alias Mobilizon.Events.{Comment, CommentVisibility, Event, Tag}
alias Mobilizon.Mention
alias MobilizonWeb.Endpoint
alias MobilizonWeb.Router.Helpers, as: Routes
@@ -22,6 +23,8 @@ defmodule Mobilizon.Events.Comment do
actor: Actor.t(),
attributed_to: Actor.t(),
event: Event.t(),
tags: [Tag.t()],
mentions: [Mention.t()],
in_reply_to_comment: t,
origin_comment: t
}
@@ -42,6 +45,8 @@ defmodule Mobilizon.Events.Comment do
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)
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
has_many(:mentions, Mention)
timestamps(type: :utc_datetime)
end
@@ -57,16 +62,45 @@ defmodule Mobilizon.Events.Comment do
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%__MODULE__{} = comment, attrs) do
uuid = attrs["uuid"] || Ecto.UUID.generate()
url = attrs["url"] || generate_url(uuid)
uuid = Map.get(attrs, :uuid) || Ecto.UUID.generate()
url = Map.get(attrs, :url) || generate_url(uuid)
comment
|> cast(attrs, @attrs)
|> put_change(:uuid, uuid)
|> put_change(:url, url)
|> put_tags(attrs)
|> put_mentions(attrs)
|> validate_required(@required_attrs)
end
@spec generate_url(String.t()) :: String.t()
defp generate_url(uuid), do: Routes.page_url(Endpoint, :comment, uuid)
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
defp put_tags(changeset, %{"tags" => tags}),
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
defp put_tags(changeset, %{tags: tags}),
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
defp put_tags(changeset, _), do: changeset
@spec put_mentions(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
defp put_mentions(changeset, %{"mentions" => mentions}),
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
defp put_mentions(changeset, %{mentions: mentions}),
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
defp put_mentions(changeset, _), do: changeset
# We need a changeset instead of a raw struct because of slug which is generated in changeset
defp process_tag(tag) do
Tag.changeset(%Tag{}, tag)
end
defp process_mention(tag) do
Mention.changeset(%Mention{}, tag)
end
end

View File

@@ -6,22 +6,31 @@ defmodule Mobilizon.Events.Event do
use Ecto.Schema
import Ecto.Changeset
alias Ecto.Changeset
alias Mobilizon.Actors.Actor
alias Mobilizon.Addresses.Address
alias Mobilizon.Addresses
alias Mobilizon.Events.{
EventOptions,
EventStatus,
EventVisibility,
JoinOptions,
EventParticipantStats,
Participant,
Session,
Tag,
Track
}
alias Mobilizon.Media
alias Mobilizon.Media.Picture
alias Mobilizon.Mention
alias MobilizonWeb.Endpoint
alias MobilizonWeb.Router.Helpers, as: Routes
@type t :: %__MODULE__{
url: String.t(),
@@ -47,30 +56,15 @@ defmodule Mobilizon.Events.Event do
picture: Picture.t(),
tracks: [Track.t()],
sessions: [Session.t()],
mentions: [Mention.t()],
tags: [Tag.t()],
participants: [Actor.t()]
}
@required_attrs [:title, :begins_on, :organizer_actor_id, :url, :uuid]
@update_required_attrs [:title, :begins_on, :organizer_actor_id]
@required_attrs @update_required_attrs ++ [:url, :uuid]
@optional_attrs [
:slug,
:description,
:ends_on,
:category,
:status,
:draft,
:visibility,
:publish_at,
:online_address,
:phone_address,
:picture_id,
:physical_address_id
]
@attrs @required_attrs ++ @optional_attrs
@update_required_attrs @required_attrs
@update_optional_attrs [
:slug,
:description,
:ends_on,
@@ -85,7 +79,9 @@ defmodule Mobilizon.Events.Event do
:picture_id,
:physical_address_id
]
@update_attrs @update_required_attrs ++ @update_optional_attrs
@attrs @required_attrs ++ @optional_attrs
@update_attrs @update_required_attrs ++ @optional_attrs
schema "events" do
field(:url, :string)
@@ -105,13 +101,15 @@ defmodule Mobilizon.Events.Event do
field(:phone_address, :string)
field(:category, :string)
embeds_one(:options, EventOptions, on_replace: :update)
embeds_one(:options, EventOptions, on_replace: :delete)
embeds_one(:participant_stats, EventParticipantStats, on_replace: :update)
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
belongs_to(:physical_address, Address)
belongs_to(:picture, Picture)
belongs_to(:physical_address, Address, on_replace: :update)
belongs_to(:picture, Picture, on_replace: :update)
has_many(:tracks, Track)
has_many(:sessions, Session)
has_many(:mentions, Mention)
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
many_to_many(:participants, Actor, join_through: Participant)
@@ -119,28 +117,41 @@ defmodule Mobilizon.Events.Event do
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
@spec changeset(t, map) :: Changeset.t()
def changeset(%__MODULE__{} = event, attrs) do
attrs = Map.update(attrs, :uuid, Ecto.UUID.generate(), & &1)
attrs = Map.update(attrs, :url, Routes.page_url(Endpoint, :event, attrs.uuid), & &1)
event
|> cast(attrs, @attrs)
|> cast_embed(:options)
|> common_changeset(attrs)
|> put_creator_if_published(:create)
|> validate_required(@required_attrs)
|> validate_lengths()
end
@doc false
@spec update_changeset(t, map) :: Ecto.Changeset.t()
@spec update_changeset(t, map) :: Changeset.t()
def update_changeset(%__MODULE__{} = event, attrs) do
event
|> Ecto.Changeset.cast(attrs, @update_attrs)
|> cast_embed(:options)
|> put_tags(attrs)
|> cast(attrs, @update_attrs)
|> common_changeset(attrs)
|> put_creator_if_published(:update)
|> validate_required(@update_required_attrs)
|> validate_lengths()
end
@spec validate_lengths(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp validate_lengths(%Ecto.Changeset{} = changeset) do
@spec common_changeset(Changeset.t(), map) :: Changeset.t()
defp common_changeset(%Changeset{} = changeset, attrs) do
changeset
|> cast_embed(:options)
|> put_tags(attrs)
|> put_address(attrs)
|> put_picture(attrs)
end
@spec validate_lengths(Changeset.t()) :: Changeset.t()
defp validate_lengths(%Changeset{} = changeset) do
changeset
|> validate_length(:title, min: 3, max: 200)
|> validate_length(:online_address, min: 3, max: 2000)
@@ -161,7 +172,80 @@ defmodule Mobilizon.Events.Event do
def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags)
defp put_tags(changeset, _), do: changeset
@spec put_tags(Changeset.t(), map) :: Changeset.t()
defp put_tags(%Changeset{} = changeset, %{tags: tags}),
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
defp put_tags(%Changeset{} = changeset, _), do: changeset
# We need a changeset instead of a raw struct because of slug which is generated in changeset
defp process_tag(tag) do
Tag.changeset(%Tag{}, tag)
end
# In case the provided addresses is an existing one
@spec put_address(Changeset.t(), map) :: Changeset.t()
defp put_address(%Changeset{} = changeset, %{physical_address: %{id: id} = _physical_address}) do
case Addresses.get_address!(id) do
%Address{} = address ->
put_assoc(changeset, :physical_address, address)
_ ->
changeset
end
end
# In case it's a new address
defp put_address(%Changeset{} = changeset, _attrs) do
cast_assoc(changeset, :physical_address)
end
# In case the provided picture is an existing one
@spec put_picture(Changeset.t(), map) :: Changeset.t()
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
case Media.get_picture!(id) do
%Picture{} = picture ->
put_assoc(changeset, :picture, picture)
_ ->
changeset
end
end
# In case it's a new picture
defp put_picture(%Changeset{} = changeset, _attrs) do
cast_assoc(changeset, :picture)
end
# Created or updated with draft parameter: don't publish
defp put_creator_if_published(
%Changeset{changes: %{draft: true}} = changeset,
_action
) do
cast_embed(changeset, :participant_stats)
end
# Created with any other value: publish
defp put_creator_if_published(
%Changeset{} = changeset,
:create
) do
changeset
|> put_embed(:participant_stats, %{creator: 1})
end
# Updated from draft false to true: publish
defp put_creator_if_published(
%Changeset{
data: %{draft: false},
changes: %{draft: true}
} = changeset,
:update
) do
changeset
|> put_embed(:participant_stats, %{creator: 1})
end
defp put_creator_if_published(%Changeset{} = changeset, _),
do: cast_embed(changeset, :participant_stats)
end

View File

@@ -0,0 +1,44 @@
defmodule Mobilizon.Events.EventParticipantStats do
@moduledoc """
Participation stats on event
"""
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{
not_approved: integer(),
rejected: integer(),
participant: integer(),
moderator: integer(),
administrator: integer(),
creator: integer()
}
@attrs [
:not_approved,
:rejected,
:participant,
:moderator,
:administrator,
:moderator,
:creator
]
@primary_key false
@derive Jason.Encoder
embedded_schema do
field(:not_approved, :integer, default: 0)
field(:rejected, :integer, default: 0)
field(:participant, :integer, default: 0)
field(:moderator, :integer, default: 0)
field(:administrator, :integer, default: 0)
field(:creator, :integer, default: 0)
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%__MODULE__{} = event_options, attrs) do
cast(event_options, attrs, @attrs)
end
end

View File

@@ -7,6 +7,7 @@ defmodule Mobilizon.Events do
import Ecto.Query
import EctoEnum
alias Ecto.{Multi, Changeset}
import Mobilizon.Storage.Ecto
@@ -17,6 +18,7 @@ defmodule Mobilizon.Events do
alias Mobilizon.Events.{
Comment,
Event,
EventParticipantStats,
FeedToken,
Participant,
Session,
@@ -82,6 +84,8 @@ defmodule Mobilizon.Events do
@event_preloads [
:organizer_actor,
:attributed_to,
:mentions,
:sessions,
:tracks,
:tags,
@@ -90,7 +94,7 @@ defmodule Mobilizon.Events do
:picture
]
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment]
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment, :tags, :mentions]
@doc """
Gets a single event.
@@ -235,81 +239,103 @@ defmodule Mobilizon.Events do
|> Repo.one()
end
def get_or_create_event(%{"url" => url} = attrs) do
case Repo.get_by(Event, url: url) do
%Event{} = event -> {:ok, Repo.preload(event, @event_preloads)}
nil -> create_event(attrs)
end
end
@doc """
Creates an event.
"""
@spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
@spec create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
def create_event(attrs \\ %{}) do
with {:ok, %Event{draft: false} = event} <- do_create_event(attrs),
{:ok, %Participant{} = _participant} <-
create_participant(%{
actor_id: event.organizer_actor_id,
role: :creator,
event_id: event.id
}) do
with {:ok, %{insert: %Event{} = event}} <- do_create_event(attrs),
%Event{} = event <- Repo.preload(event, @event_preloads) do
Task.start(fn -> Search.insert_search_event(event) end)
{:ok, event}
else
# We don't create a creator participant if the event is a draft
{:ok, %Event{draft: true} = event} -> {:ok, event}
err -> err
end
end
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
# We start by inserting the event and then insert a first participant if the event is not a draft
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
defp do_create_event(attrs) do
with {:ok, %Event{} = event} <-
%Event{}
|> Event.changeset(attrs)
|> Ecto.Changeset.put_assoc(:tags, Map.get(attrs, "tags", []))
|> Repo.insert(),
%Event{} = event <-
Repo.preload(event, [:tags, :organizer_actor, :physical_address, :picture]) do
{:ok, event}
end
Multi.new()
|> Multi.insert(:insert, Event.changeset(%Event{}, attrs))
|> Multi.run(:write, fn _repo, %{insert: %Event{draft: draft} = event} ->
with {:is_draft, false} <- {:is_draft, draft},
{:ok, %Participant{} = participant} <-
create_participant(
%{
event_id: event.id,
role: :creator,
actor_id: event.organizer_actor_id
},
false
) do
{:ok, participant}
else
{:is_draft, true} -> {:ok, nil}
err -> err
end
end)
|> Repo.transaction()
end
@doc """
Updates an event.
We start by updating the event and then insert a first participant if the event is not a draft anymore
"""
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
def update_event(
%Event{draft: old_draft_status, id: event_id, organizer_actor_id: organizer_actor_id} =
old_event,
attrs
) do
with %Ecto.Changeset{changes: changes} = changeset <-
old_event |> Repo.preload(:tags) |> Event.update_changeset(attrs) do
with {:ok, %Event{draft: new_draft_status} = new_event} <- Repo.update(changeset) do
# If the event is no longer a draft
if old_draft_status == true && new_draft_status == false do
{:ok, %Participant{} = _participant} =
create_participant(%{
event_id: event_id,
role: :creator,
actor_id: organizer_actor_id
})
end
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
def update_event(%Event{} = old_event, attrs) do
with %Changeset{changes: changes} = changeset <-
Event.update_changeset(Repo.preload(old_event, :tags), attrs),
{:ok, %{update: %Event{} = new_event}} <-
Multi.new()
|> Multi.update(
:update,
changeset
)
|> Multi.run(:write, fn _repo, %{update: %Event{draft: draft} = event} ->
with {:is_draft, false} <- {:is_draft, draft},
{:ok, %Participant{} = participant} <-
create_participant(
%{
event_id: event.id,
role: :creator,
actor_id: event.organizer_actor_id
},
false
) do
{:ok, participant}
else
{:is_draft, true} -> {:ok, nil}
err -> err
end
end)
|> Repo.transaction() do
Cachex.del(:ics, "event_#{new_event.uuid}")
Cachex.del(:ics, "event_#{new_event.uuid}")
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
old_event,
new_event,
changes
)
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
old_event,
new_event,
changes
)
Task.start(fn -> Search.update_search_event(new_event) end)
Task.start(fn -> Search.update_search_event(new_event) end)
{:ok, new_event}
end
{:ok, Repo.preload(new_event, @event_preloads)}
end
end
@doc """
Deletes an event.
"""
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Changeset.t()}
def delete_event(%Event{} = event), do: Repo.delete(event)
@doc """
@@ -450,7 +476,7 @@ defmodule Mobilizon.Events do
@doc """
Gets an existing tag or creates the new one.
"""
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
def get_or_create_tag(%{"name" => "#" <> title}) do
case Repo.get_by(Tag, title: title) do
%Tag{} = tag ->
@@ -461,10 +487,24 @@ defmodule Mobilizon.Events do
end
end
@doc """
Gets an existing tag or creates the new one.
"""
@spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
def get_or_create_tag(title) do
case Repo.get_by(Tag, title: title) do
%Tag{} = tag ->
{:ok, tag}
nil ->
create_tag(%{"title" => title})
end
end
@doc """
Creates a tag.
"""
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
def create_tag(attrs \\ %{}) do
%Tag{}
|> Tag.changeset(attrs)
@@ -474,7 +514,7 @@ defmodule Mobilizon.Events do
@doc """
Updates a tag.
"""
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
def update_tag(%Tag{} = tag, attrs) do
tag
|> Tag.changeset(attrs)
@@ -484,7 +524,7 @@ defmodule Mobilizon.Events do
@doc """
Deletes a tag.
"""
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
def delete_tag(%Tag{} = tag), do: Repo.delete(tag)
@doc """
@@ -524,7 +564,7 @@ defmodule Mobilizon.Events do
@doc """
Creates a relation between two tags.
"""
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Changeset.t()}
def create_tag_relation(attrs \\ {}) do
%TagRelation{}
|> TagRelation.changeset(attrs)
@@ -538,7 +578,7 @@ defmodule Mobilizon.Events do
Removes a tag relation.
"""
@spec delete_tag_relation(TagRelation.t()) ::
{:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
{:ok, TagRelation.t()} | {:error, Changeset.t()}
def delete_tag_relation(%TagRelation{} = tag_relation) do
Repo.delete(tag_relation)
end
@@ -763,7 +803,7 @@ defmodule Mobilizon.Events do
end
@doc """
Counts participant participants.
Counts participant participants (participants with no extra role)
"""
@spec count_participant_participants(integer | String.t()) :: integer
def count_participant_participants(event_id) do
@@ -773,17 +813,6 @@ defmodule Mobilizon.Events do
|> Repo.aggregate(:count, :id)
end
@doc """
Counts unapproved participants.
"""
@spec count_unapproved_participants(integer | String.t()) :: integer
def count_unapproved_participants(event_id) do
event_id
|> count_participants_query()
|> filter_unapproved_role()
|> Repo.aggregate(:count, :id)
end
@doc """
Counts rejected participants.
"""
@@ -805,12 +834,40 @@ defmodule Mobilizon.Events do
@doc """
Creates a participant.
"""
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
def create_participant(attrs \\ %{}) do
with {:ok, %Participant{} = participant} <-
%Participant{}
|> Participant.changeset(attrs)
|> Repo.insert() do
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Changeset.t()}
def create_participant(attrs \\ %{}, update_event_participation_stats \\ true) do
with {:ok, %{participant: %Participant{} = participant}} <-
Multi.new()
|> Multi.insert(:participant, Participant.changeset(%Participant{}, attrs))
|> Multi.run(:update_event_participation_stats, fn _repo,
%{
participant:
%Participant{
role: role,
event_id: event_id
} = _participant
} ->
with {:update_event_participation_stats, true} <-
{:update_event_participation_stats, update_event_participation_stats},
{:ok, %Event{} = event} <- get_event(event_id),
%EventParticipantStats{} = participant_stats <-
Map.get(event, :participant_stats),
%EventParticipantStats{} = participant_stats <-
Map.update(participant_stats, role, 0, &(&1 + 1)),
{:ok, %Event{} = event} <-
event
|> Event.update_changeset(%{
participant_stats: Map.from_struct(participant_stats)
})
|> Repo.update() do
{:ok, event}
else
{:update_event_participation_stats, false} -> {:ok, nil}
{:error, :event_not_found} -> {:error, :event_not_found}
err -> {:error, err}
end
end)
|> Repo.transaction() do
{:ok, Repo.preload(participant, [:event, :actor])}
end
end
@@ -819,7 +876,7 @@ defmodule Mobilizon.Events do
Updates a participant.
"""
@spec update_participant(Participant.t(), map) ::
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
{:ok, Participant.t()} | {:error, Changeset.t()}
def update_participant(%Participant{} = participant, attrs) do
participant
|> Participant.changeset(attrs)
@@ -830,7 +887,7 @@ defmodule Mobilizon.Events do
Deletes a participant.
"""
@spec delete_participant(Participant.t()) ::
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
{:ok, Participant.t()} | {:error, Changeset.t()}
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
@doc """
@@ -843,7 +900,7 @@ defmodule Mobilizon.Events do
@doc """
Creates a session.
"""
@spec create_session(map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
@spec create_session(map) :: {:ok, Session.t()} | {:error, Changeset.t()}
def create_session(attrs \\ %{}) do
%Session{}
|> Session.changeset(attrs)
@@ -853,7 +910,7 @@ defmodule Mobilizon.Events do
@doc """
Updates a session.
"""
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Changeset.t()}
def update_session(%Session{} = session, attrs) do
session
|> Session.changeset(attrs)
@@ -863,7 +920,7 @@ defmodule Mobilizon.Events do
@doc """
Deletes a session.
"""
@spec delete_session(Session.t()) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
@spec delete_session(Session.t()) :: {:ok, Session.t()} | {:error, Changeset.t()}
def delete_session(%Session{} = session), do: Repo.delete(session)
@doc """
@@ -892,7 +949,7 @@ defmodule Mobilizon.Events do
@doc """
Creates a track.
"""
@spec create_track(map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
@spec create_track(map) :: {:ok, Track.t()} | {:error, Changeset.t()}
def create_track(attrs \\ %{}) do
%Track{}
|> Track.changeset(attrs)
@@ -902,7 +959,7 @@ defmodule Mobilizon.Events do
@doc """
Updates a track.
"""
@spec update_track(Track.t(), map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
@spec update_track(Track.t(), map) :: {:ok, Track.t()} | {:error, Changeset.t()}
def update_track(%Track{} = track, attrs) do
track
|> Track.changeset(attrs)
@@ -912,7 +969,7 @@ defmodule Mobilizon.Events do
@doc """
Deletes a track.
"""
@spec delete_track(Track.t()) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
@spec delete_track(Track.t()) :: {:ok, Track.t()} | {:error, Changeset.t()}
def delete_track(%Track{} = track), do: Repo.delete(track)
@doc """
@@ -931,6 +988,13 @@ defmodule Mobilizon.Events do
|> Repo.all()
end
@doc """
Gets a single comment.
"""
@spec get_comment(integer | String.t()) :: Comment.t()
def get_comment(nil), do: nil
def get_comment(id), do: Repo.get(Comment, id)
@doc """
Gets a single comment.
Raises `Ecto.NoResultsError` if the comment does not exist.
@@ -994,10 +1058,17 @@ defmodule Mobilizon.Events do
|> Repo.preload(@comment_preloads)
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)}
nil -> create_comment(attrs)
end
end
@doc """
Creates a comment.
"""
@spec create_comment(map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
@spec create_comment(map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
def create_comment(attrs \\ %{}) do
with {:ok, %Comment{} = comment} <-
%Comment{}
@@ -1011,7 +1082,7 @@ defmodule Mobilizon.Events do
@doc """
Updates a comment.
"""
@spec update_comment(Comment.t(), map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
@spec update_comment(Comment.t(), map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
def update_comment(%Comment{} = comment, attrs) do
comment
|> Comment.changeset(attrs)
@@ -1021,7 +1092,7 @@ defmodule Mobilizon.Events do
@doc """
Deletes a comment.
"""
@spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
@spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Changeset.t()}
def delete_comment(%Comment{} = comment), do: Repo.delete(comment)
@doc """
@@ -1097,7 +1168,7 @@ defmodule Mobilizon.Events do
@doc """
Creates a feed token.
"""
@spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
@spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Changeset.t()}
def create_feed_token(attrs \\ %{}) do
attrs = Map.put(attrs, "token", Ecto.UUID.generate())
@@ -1110,7 +1181,7 @@ defmodule Mobilizon.Events do
Updates a feed token.
"""
@spec update_feed_token(FeedToken.t(), map) ::
{:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
{:ok, FeedToken.t()} | {:error, Changeset.t()}
def update_feed_token(%FeedToken{} = feed_token, attrs) do
feed_token
|> FeedToken.changeset(attrs)
@@ -1120,7 +1191,7 @@ defmodule Mobilizon.Events do
@doc """
Deletes a feed token.
"""
@spec delete_feed_token(FeedToken.t()) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
@spec delete_feed_token(FeedToken.t()) :: {:ok, FeedToken.t()} | {:error, Changeset.t()}
def delete_feed_token(%FeedToken{} = feed_token), do: Repo.delete(feed_token)
@doc """
@@ -1354,7 +1425,7 @@ defmodule Mobilizon.Events do
end
@spec count_participants_query(integer) :: Ecto.Query.t()
defp count_participants_query(event_id) do
def count_participants_query(event_id) do
from(p in Participant, where: p.event_id == ^event_id)
end
@@ -1385,17 +1456,10 @@ defmodule Mobilizon.Events do
end
defp public_comments_for_actor_query(actor_id) do
from(
c in Comment,
where: c.actor_id == ^actor_id and c.visibility in ^@public_visibility,
order_by: [desc: :id],
preload: [
:actor,
:in_reply_to_comment,
:origin_comment,
:event
]
)
Comment
|> where([c], c.actor_id == ^actor_id and c.visibility in ^@public_visibility)
|> order_by([c], desc: :id)
|> preload_for_comment()
end
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
@@ -1498,31 +1562,30 @@ defmodule Mobilizon.Events do
@spec filter_approved_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_approved_role(query) do
from(p in query, where: p.role not in ^[:not_approved, :rejected])
filter_role(query, [:not_approved, :rejected])
end
@spec filter_participant_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_participant_role(query) do
from(p in query, where: p.role == ^:participant)
end
@spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_unapproved_role(query) do
from(p in query, where: p.role == ^:not_approved)
filter_role(query, :participant)
end
@spec filter_rejected_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_rejected_role(query) do
from(p in query, where: p.role == ^:rejected)
filter_role(query, :rejected)
end
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
defp filter_role(query, []), do: query
def filter_role(query, []), do: query
defp filter_role(query, roles) do
def filter_role(query, roles) when is_list(roles) do
where(query, [p], p.role in ^roles)
end
def filter_role(query, role) when is_atom(role) do
from(p in query, where: p.role == ^role)
end
defp participation_filter_begins_on(query, nil, nil),
do: participation_order_begins_on_desc(query)

View File

@@ -0,0 +1,53 @@
defmodule Mobilizon.Mention do
@moduledoc """
The Mentions context.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
alias Mobilizon.Events.Comment
alias Mobilizon.Storage.Repo
@type t :: %__MODULE__{
silent: boolean,
actor: Actor.t(),
event: Event.t(),
comment: Comment.t()
}
@required_attrs [:actor_id]
@optional_attrs [:silent, :event_id, :comment_id]
@attrs @required_attrs ++ @optional_attrs
schema "mentions" do
field(:silent, :boolean, default: false)
belongs_to(:actor, Actor)
belongs_to(:event, Event)
belongs_to(:comment, Comment)
timestamps()
end
@doc false
def changeset(event, attrs) do
event
|> cast(attrs, @attrs)
# TODO: Enforce having either event_id or comment_id
|> validate_required(@required_attrs)
end
@doc """
Creates a new mention
"""
@spec create_mention(map()) :: {:ok, t} | {:error, Ecto.Changeset.t()}
def create_mention(args) do
with {:ok, %__MODULE__{} = mention} <-
%__MODULE__{}
|> changeset(args)
|> Repo.insert() do
{:ok, Repo.preload(mention, [:actor, :event, :comment])}
end
end
end