From 5f30334bcb76b1f03d3082a0b8cd3c9691130549 Mon Sep 17 00:00:00 2001 From: miffigriffi Date: Mon, 16 Sep 2019 02:07:44 +0200 Subject: [PATCH] Refactoring of Events context --- lib/mobilizon.ex | 5 +- lib/mobilizon/actors/actors.ex | 3 +- lib/mobilizon/events/activity.ex | 12 +- lib/mobilizon/events/events.ex | 1929 ++++++++--------- .../{tag_relations.ex => tag_relation.ex} | 0 lib/mobilizon_web/api/reports.ex | 2 +- lib/mobilizon_web/api/search.ex | 2 +- .../controllers/page_controller.ex | 4 +- lib/mobilizon_web/resolvers/event.ex | 12 +- lib/mobilizon_web/resolvers/tag.ex | 4 +- lib/service/activity_pub/activity_pub.ex | 12 +- lib/service/export/feed.ex | 2 +- lib/service/export/icalendar.ex | 4 +- test/mobilizon/events/events_test.exs | 61 +- .../activity_pub/activity_pub_test.exs | 4 +- test/mobilizon_web/api/search_test.exs | 4 +- .../resolvers/report_resolver_test.exs | 4 +- .../resolvers/tag_resolver_test.exs | 4 +- 18 files changed, 910 insertions(+), 1158 deletions(-) rename lib/mobilizon/events/{tag_relations.ex => tag_relation.ex} (100%) diff --git a/lib/mobilizon.ex b/lib/mobilizon.ex index 7461a267c..d454b9669 100644 --- a/lib/mobilizon.ex +++ b/lib/mobilizon.ex @@ -65,7 +65,10 @@ defmodule Mobilizon do {Cachex, :start_link, [ name, - Keyword.merge(cachex_options(limit, default, interval), fallback_options(fallback)) + Keyword.merge( + cachex_options(limit, default, interval), + fallback_options(fallback) + ) ]} } end diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index a174f0045..a4f455dd7 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -45,6 +45,7 @@ defmodule Mobilizon.Actors do :creator ]) + @public_visibility [:public, :unlisted] @administrator_roles [:creator, :administrator] @doc false @@ -836,7 +837,7 @@ defmodule Mobilizon.Actors do from( a in Actor, where: a.type == ^:Group, - where: a.visibility in ^[:public, :unlisted] + where: a.visibility in ^@public_visibility ) end diff --git a/lib/mobilizon/events/activity.ex b/lib/mobilizon/events/activity.ex index 7daf805e7..f3febd324 100644 --- a/lib/mobilizon/events/activity.ex +++ b/lib/mobilizon/events/activity.ex @@ -3,11 +3,19 @@ defmodule Mobilizon.Events.Activity do Represents an activity. """ + @type t :: %__MODULE__{ + data: String.t(), + local: boolean, + actor: Actor.t(), + recipients: [String.t()] + # notifications: [???] + } + defstruct [ :data, :local, :actor, - :recipients, - :notifications + :recipients + # :notifications ] end diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 24c93fa5e..45a84007d 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -3,6 +3,8 @@ defmodule Mobilizon.Events do The Events context. """ + import Geo.PostGIS + import Ecto.Query import EctoEnum @@ -10,7 +12,18 @@ defmodule Mobilizon.Events do alias Mobilizon.Actors.Actor alias Mobilizon.Addresses.Address - alias Mobilizon.Events.{Event, Comment, Participant} + + alias Mobilizon.Events.{ + Comment, + Event, + FeedToken, + Participant, + Session, + Tag, + TagRelation, + Track + } + alias Mobilizon.Storage.{Page, Repo} alias Mobilizon.Users.User @@ -63,6 +76,20 @@ defmodule Mobilizon.Events do :creator ]) + @public_visibility [:public, :unlisted] + + @event_preloads [ + :organizer_actor, + :sessions, + :tracks, + :tags, + :participants, + :physical_address, + :picture + ] + + @comment_preloads [:actor, :attributed_to, :in_reply_to_comment] + @doc false @spec data :: Dataloader.Ecto.t() def data, do: Dataloader.Ecto.new(Repo, query: &query/2) @@ -71,378 +98,154 @@ defmodule Mobilizon.Events do @spec query(Ecto.Query.t(), map) :: Ecto.Query.t() def query(queryable, _params), do: queryable - def get_public_events_for_actor(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do - query = - from( - e in Event, - where: e.organizer_actor_id == ^actor_id and e.visibility in [^:public, ^:unlisted], - order_by: [desc: :id], - preload: [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address, - :picture - ] - ) - |> Page.paginate(page, limit) - - events = Repo.all(query) - - count_events = - Repo.one(from(e in Event, select: count(e.id), where: e.organizer_actor_id == ^actor_id)) - - {:ok, events, count_events} - end - @doc """ - Get an actor's eventual upcoming public event + Gets a single event. """ - @spec get_actor_upcoming_public_event(Actor.t(), String.t()) :: Event.t() | nil - def get_actor_upcoming_public_event(%Actor{id: actor_id} = _actor, not_event_uuid \\ nil) do - query = - from( - e in Event, - where: - e.organizer_actor_id == ^actor_id and e.visibility in [^:public, ^:unlisted] and - e.begins_on > ^DateTime.utc_now(), - order_by: [asc: :begins_on], - limit: 1, - preload: [ - :organizer_actor, - :tags, - :participants, - :physical_address - ] - ) + @spec get_event(integer | String.t()) :: {:ok, Event.t()} | {:error, :event_not_found} + def get_event(id) do + case Repo.get(Event, id) do + %Event{} = event -> + {:ok, event} - query = - if is_nil(not_event_uuid), - do: query, - else: from(q in query, where: q.uuid != ^not_event_uuid) - - Repo.one(query) - end - - def count_local_events do - Repo.one( - from( - e in Event, - select: count(e.id), - where: e.local == ^true and e.visibility in [^:public, ^:unlisted] - ) - ) - end - - def count_local_comments do - Repo.one( - from( - c in Comment, - select: count(c.id), - where: c.local == ^true and c.visibility in [^:public, ^:unlisted] - ) - ) - end - - import Geo.PostGIS - - @doc """ - Find close events to coordinates - - Radius is in meters and defaults to 50km. - """ - @spec find_close_events(number(), number(), number(), number()) :: list(Event.t()) - def find_close_events(lon, lat, radius \\ 50_000, srid \\ 4326) do - with {:ok, point} <- Geo.WKT.decode("SRID=#{srid};POINT(#{lon} #{lat})") do - Repo.all( - from( - e in Event, - join: a in Address, - on: a.id == e.physical_address_id, - where: e.visibility == ^:public and st_dwithin_in_meters(^point, a.geom, ^radius), - preload: :organizer_actor - ) - ) + nil -> + {:error, :event_not_found} end end @doc """ Gets a single event. - - Raises `Ecto.NoResultsError` if the Event does not exist. - - ## Examples - - iex> get_event!(123) - %Event{} - - iex> get_event!(456) - ** (Ecto.NoResultsError) - + Raises `Ecto.NoResultsError` if the event does not exist. """ + @spec get_event!(integer | String.t()) :: Event.t() def get_event!(id), do: Repo.get!(Event, id) @doc """ - Gets a single event. + Gets a single event, with all associations loaded. """ - def get_event(id) do + @spec get_event_with_preload(integer | String.t()) :: + {:ok, Event.t()} | {:error, :event_not_found} + def get_event_with_preload(id) do case Repo.get(Event, id) do - nil -> {:error, :event_not_found} - event -> {:ok, event} + %Event{} = event -> + {:ok, Repo.preload(event, @event_preloads)} + + nil -> + {:error, :event_not_found} end end @doc """ - Gets an event by it's URL + Gets a single event, with all associations loaded. + Raises `Ecto.NoResultsError` if the event does not exist. """ + @spec get_event_with_preload!(integer | String.t()) :: Event.t() + def get_event_with_preload!(id) do + Event + |> Repo.get!(id) + |> Repo.preload(@event_preloads) + end + + @doc """ + Gets an event by its URL. + """ + @spec get_event_by_url(String.t()) :: Event.t() | nil def get_event_by_url(url) do - Repo.get_by(Event, url: url) - end - - @doc """ - Gets an event by it's URL - """ - def get_event_by_url!(url) do - Repo.get_by!(Event, url: url) - end - - # @doc """ - # Gets an event by it's UUID - # """ - # @depreciated "Use get_event_full_by_uuid/3 instead" - # def get_event_by_uuid(uuid) do - # Repo.get_by(Event, uuid: uuid) - # end - - @doc """ - Gets a full event by it's UUID - """ - @spec get_event_full_by_uuid(String.t()) :: Event.t() - def get_event_full_by_uuid(uuid) do - from( - e in Event, - where: e.uuid == ^uuid and e.visibility in [^:public, ^:unlisted], - preload: [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address, - :picture - ] - ) + url + |> event_by_url_query() |> Repo.one() end - def get_cached_event_full_by_uuid(uuid) do + @doc """ + Gets an event by its URL. + Raises `Ecto.NoResultsError` if the event does not exist. + """ + @spec get_event_by_url!(String.t()) :: Event.t() + def get_event_by_url!(url) do + url + |> event_by_url_query() + |> Repo.one!() + end + + @doc """ + Gets an event by its URL, with all associations loaded. + """ + @spec get_public_event_by_url_with_preload(String.t()) :: + {:ok, Event.t()} | {:error, :event_not_found} + def get_public_event_by_url_with_preload(url) do + event = + url + |> event_by_url_query() + |> filter_public_visibility() + |> preload_for_event() + |> Repo.one() + + case event do + %Event{} = event -> + {:ok, event} + + nil -> + {:error, :event_not_found} + end + end + + @doc """ + Gets an event by its URL, with all associations loaded. + Raises `Ecto.NoResultsError` if the event does not exist. + """ + @spec get_public_event_by_url_with_preload(String.t()) :: Event.t() + def get_public_event_by_url_with_preload!(url) do + url + |> event_by_url_query() + |> filter_public_visibility() + |> preload_for_event() + |> Repo.one!() + end + + @doc """ + Gets an event by its UUID, with all associations loaded. + """ + @spec get_public_event_by_uuid_with_preload(String.t()) :: Event.t() | nil + def get_public_event_by_uuid_with_preload(uuid) do + uuid + |> event_by_uuid_query() + |> filter_public_visibility() + |> preload_for_event() + |> Repo.one() + end + + @doc """ + Gets an actor's eventual upcoming public event. + """ + @spec get_upcoming_public_event_for_actor(Actor.t(), String.t() | nil) :: Event.t() | nil + def get_upcoming_public_event_for_actor(%Actor{id: actor_id}, not_event_uuid \\ nil) do + actor_id + |> upcoming_public_event_for_actor_query() + |> filter_public_visibility() + |> filter_not_event_uuid(not_event_uuid) + |> Repo.one() + end + + # TODO: move to MobilizonWeb + @spec get_cached_public_event_by_uuid_with_preload(String.t()) :: + {:commit, Event.t()} | {:ignore, nil} + def get_cached_public_event_by_uuid_with_preload(uuid) do Cachex.fetch(:activity_pub, "event_" <> uuid, fn "event_" <> uuid -> - case get_event_full_by_uuid(uuid) do + case get_public_event_by_uuid_with_preload(uuid) do %Event{} = event -> {:commit, event} - _ -> + nil -> {:ignore, nil} end end) end @doc """ - Gets a single event, with all associations loaded. - """ - def get_event_full!(id) do - event = Repo.get!(Event, id) - - Repo.preload(event, [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address - ]) - end - - @doc """ - Gets a single event, with all associations loaded. - """ - def get_event_full(id) do - case Repo.get(Event, id) do - %Event{} = event -> - {:ok, - Repo.preload(event, [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address, - :picture - ])} - - _err -> - {:error, :event_not_found} - end - end - - @doc """ - Gets an event by it's URL - """ - def get_event_full_by_url(url) do - case Repo.one( - from(e in Event, - where: e.url == ^url and e.visibility in [^:public, ^:unlisted], - preload: [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address - ] - ) - ) do - nil -> {:error, :event_not_found} - event -> {:ok, event} - end - end - - @doc """ - Gets an event by it's URL - """ - def get_event_full_by_url!(url) do - Repo.one( - from(e in Event, - where: e.url == ^url and e.visibility in [^:public, ^:unlisted], - preload: [ - :organizer_actor, - :sessions, - :tracks, - :tags, - :participants, - :physical_address, - :picture - ] - ) - ) - end - - @doc """ - Returns the list of events. - - ## Examples - - iex> list_events() - [%Event{}, ...] - - """ - @spec list_events(integer(), integer(), atom(), atom()) :: list(Event.t()) - def list_events( - page \\ nil, - limit \\ nil, - sort \\ :begins_on, - direction \\ :asc, - unlisted \\ false, - future \\ true - ) do - query = - from( - e in Event, - preload: [:organizer_actor, :participants] - ) - |> Page.paginate(page, limit) - |> sort(sort, direction) - |> restrict_future_events(future) - |> allow_unlisted(unlisted) - - Repo.all(query) - end - - # Make sure we only show future events - @spec restrict_future_events(Ecto.Query.t(), boolean()) :: Ecto.Query.t() - defp restrict_future_events(query, true), - do: from(q in query, where: q.begins_on > ^DateTime.utc_now()) - - defp restrict_future_events(query, false), do: query - - # Make sure unlisted events don't show up where they're not allowed - @spec allow_unlisted(Ecto.Query.t(), boolean()) :: Ecto.Query.t() - defp allow_unlisted(query, true), - do: from(q in query, where: q.visibility in [^:public, ^:unlisted]) - - defp allow_unlisted(query, false), do: from(q in query, where: q.visibility == ^:public) - - @doc """ - Find events by name - """ - def find_and_count_events_by_name(name, page \\ nil, limit \\ nil) - - def find_and_count_events_by_name(name, page, limit) do - name = String.trim(name) - - query = - from(e in Event, - where: - e.visibility == ^:public and - fragment( - "f_unaccent(?) %> f_unaccent(?)", - e.title, - ^name - ), - order_by: - fragment( - "word_similarity(?, ?) desc", - e.title, - ^name - ), - preload: [:organizer_actor] - ) - |> Page.paginate(page, limit) - - total = Task.async(fn -> Repo.aggregate(query, :count, :id) end) - elements = Task.async(fn -> Repo.all(query) end) - - %{total: Task.await(total), elements: Task.await(elements)} - end - - @doc """ - Find events with the same tags - """ - @spec find_similar_events_by_common_tags(list(), integer()) :: {:ok, list(Event.t())} - def find_similar_events_by_common_tags(tags, limit \\ 2) do - tags_ids = Enum.map(tags, & &1.id) - - query = - from(e in Event, - distinct: e.uuid, - join: te in "events_tags", - on: e.id == te.event_id, - where: e.begins_on > ^DateTime.utc_now(), - where: e.visibility in [^:public, ^:unlisted], - where: te.tag_id in ^tags_ids, - order_by: [asc: e.begins_on], - limit: ^limit - ) - - Repo.all(query) - end - - @doc """ - Creates a event. - - ## Examples - - iex> create_event(%{field: value}) - {:ok, %Event{}} - - iex> create_event(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - + Creates an event. """ + @spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()} def create_event(attrs \\ %{}) do - with %Event{} = event <- do_create_event(attrs), + with {:ok, %Event{} = event} <- do_create_event(attrs), {:ok, %Participant{} = _participant} <- %Participant{} |> Participant.changeset(%{ @@ -455,10 +258,14 @@ defmodule Mobilizon.Events do end end + @spec do_create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()} defp do_create_event(attrs) do - with {:ok, %Event{} = event} <- %Event{} |> Event.changeset(attrs) |> Repo.insert(), + with {:ok, %Event{} = event} <- + %Event{} + |> Event.changeset(attrs) + |> Repo.insert(), %Event{} = event <- - event |> Repo.preload([:tags, :organizer_actor, :physical_address, :picture]), + Repo.preload(event, [:tags, :organizer_actor, :physical_address, :picture]), {:has_tags, true, _} <- {:has_tags, Map.has_key?(attrs, "tags"), event} do event |> Ecto.Changeset.change() @@ -466,7 +273,7 @@ defmodule Mobilizon.Events do |> Repo.update() else {:has_tags, false, event} -> - event + {:ok, event} error -> error @@ -474,17 +281,9 @@ defmodule Mobilizon.Events do end @doc """ - Updates a event. - - ## Examples - - iex> update_event(event, %{field: new_value}) - {:ok, %Event{}} - - iex> update_event(event, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - + Updates an event. """ + @spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()} def update_event(%Event{} = event, attrs) do event |> Repo.preload(:tags) @@ -493,117 +292,132 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Event. - - ## Examples - - iex> delete_event(event) - {:ok, %Event{}} - - iex> delete_event(event) - {:error, %Ecto.Changeset{}} - + Deletes an event. """ - def delete_event(%Event{} = event) do - Repo.delete(event) - end + @spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()} + def delete_event(%Event{} = event), do: Repo.delete(event) @doc """ - Deletes a Event. - + Deletes an event. Raises an exception if it fails. """ - def delete_event!(%Event{} = event) do - Repo.delete!(event) + @spec delete_event(Event.t()) :: Event.t() + def delete_event!(%Event{} = event), do: Repo.delete!(event) + + @doc """ + Returns the list of events. + """ + @spec list_events(integer | nil, integer | nil, atom, atom, boolean, boolean) :: [Event.t()] + def list_events( + page \\ nil, + limit \\ nil, + sort \\ :begins_on, + direction \\ :asc, + is_unlisted \\ false, + is_future \\ true + ) do + from(e in Event, preload: [:organizer_actor, :participants]) + |> Page.paginate(page, limit) + |> sort(sort, direction) + |> filter_future_events(is_future) + |> filter_unlisted(is_unlisted) + |> Repo.all() end @doc """ - Returns an `%Ecto.Changeset{}` for tracking event changes. - - ## Examples - - iex> change_event(event) - %Ecto.Changeset{source: %Event{}} - + Returns the list of events with the same tags. """ - def change_event(%Event{} = event) do - Event.changeset(event, %{}) + @spec list_events_by_tags([Tag.t()], integer) :: [Event.t()] + def list_events_by_tags(tags, limit \\ 2) do + tags + |> Enum.map(& &1.id) + |> events_by_tags_query(limit) + |> Repo.all() end - alias Mobilizon.Events.Tag - @doc """ - Returns the list of tags. - - ## Examples - - iex> list_tags() - [%Tag{}, ...] - + Lists public events for the actor, with all associations loaded. """ - def list_tags(page \\ nil, limit \\ nil) do - Repo.all( - Tag + @spec list_public_events_for_actor(Actor.t(), integer | nil, integer | nil) :: + {:ok, [Event.t()], integer} + def list_public_events_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do + events = + actor_id + |> event_for_actor_query() + |> filter_public_visibility() + |> preload_for_event() |> Page.paginate(page, limit) - ) + |> Repo.all() + + events_count = + actor_id + |> count_events_for_actor_query() + |> Repo.one() + + {:ok, events, events_count} end @doc """ - Returns the list of tags for an event. - - ## Examples - - iex> list_tags_for_event(id) - [%Participant{}, ...] - + Finds close events to coordinates. + Radius is in meters and defaults to 50km. """ - def list_tags_for_event(id, page \\ nil, limit \\ nil) do - Repo.all( - from( - t in Tag, - join: e in "events_tags", - on: t.id == e.tag_id, - where: e.event_id == ^id - ) - |> Page.paginate(page, limit) - ) + @spec find_close_events(number, number, number, number) :: [Event.t()] + def find_close_events(lon, lat, radius \\ 50_000, srid \\ 4326) do + "SRID=#{srid};POINT(#{lon} #{lat})" + |> Geo.WKT.decode!() + |> close_events_query(radius) + |> Repo.all() + end + + @doc """ + Counts local events. + """ + @spec count_local_events :: integer + def count_local_events do + count_local_events_query() + |> filter_public_visibility() + |> Repo.one() + end + + @doc """ + Builds a page struct for events by their name. + """ + @spec build_events_by_name(String.t(), integer | nil, integer | nil) :: Page.t() + def build_events_by_name(name, page \\ nil, limit \\ nil) do + name + |> String.trim() + |> events_by_name_query() + |> Page.build_page(page, limit) end @doc """ Gets a single tag. - - Raises `Ecto.NoResultsError` if the Tag does not exist. - - ## Examples - - iex> get_tag!(123) - %Tag{} - - iex> get_tag!(456) - ** (Ecto.NoResultsError) - """ - def get_tag!(id), do: Repo.get!(Tag, id) - + @spec get_tag(integer | String.t()) :: Tag.t() | nil def get_tag(id), do: Repo.get(Tag, id) - def get_tag_by_slug(slug) do - query = - from( - t in Tag, - where: t.slug == ^slug - ) + @doc """ + Gets a single tag. + Raises `Ecto.NoResultsError` if the tag does not exist. + """ + @spec get_tag!(integer | String.t()) :: Tag.t() + def get_tag!(id), do: Repo.get!(Tag, id) - Repo.one(query) + @doc """ + Gets a tag by its slug. + """ + @spec get_tag_by_slug(String.t()) :: Tag.t() | nil + def get_tag_by_slug(slug) do + slug + |> tag_by_slug_query() + |> Repo.one() end @doc """ - Get an existing tag or create one + Gets an existing tag or creates the new one. """ - @spec get_or_create_tag(map()) :: {:ok, Tag.t()} | {:error, any()} - def get_or_create_tag(tag) do - "#" <> title = tag["name"] - + @spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()} + def get_or_create_tag(%{"name" => "#" <> title}) do case Repo.get_by(Tag, title: title) do %Tag{} = tag -> {:ok, tag} @@ -615,16 +429,8 @@ defmodule Mobilizon.Events do @doc """ Creates a tag. - - ## Examples - - iex> create_tag(%{field: value}) - {:ok, %Tag{}} - - iex> create_tag(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()} def create_tag(attrs \\ %{}) do %Tag{} |> Tag.changeset(attrs) @@ -633,16 +439,8 @@ defmodule Mobilizon.Events do @doc """ Updates a tag. - - ## Examples - - iex> update_tag(tag, %{field: new_value}) - {:ok, %Tag{}} - - iex> update_tag(tag, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()} def update_tag(%Tag{} = tag, attrs) do tag |> Tag.changeset(attrs) @@ -650,70 +448,72 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Tag. - - ## Examples - - iex> delete_tag(tag) - {:ok, %Tag{}} - - iex> delete_tag(tag) - {:error, %Ecto.Changeset{}} - + Deletes a tag. """ - def delete_tag(%Tag{} = tag) do - Repo.delete(tag) + @spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()} + def delete_tag(%Tag{} = tag), do: Repo.delete(tag) + + @doc """ + Returns the list of tags. + """ + @spec list_tags(integer | nil, integer | nil) :: [Tag.t()] + def list_tags(page \\ nil, limit \\ nil) do + Tag + |> Page.paginate(page, limit) + |> Repo.all() end @doc """ - Returns an `%Ecto.Changeset{}` for tracking tag changes. - - ## Examples - - iex> change_tag(tag) - %Ecto.Changeset{source: %Tag{}} - + Returns the list of tags for the event. """ - def change_tag(%Tag{} = tag) do - Tag.changeset(tag, %{}) + @spec list_tags_for_event(integer | String.t(), integer | nil, integer | nil) :: [Tag.t()] + def list_tags_for_event(event_id, page \\ nil, limit \\ nil) do + event_id + |> tags_for_event_query() + |> Page.paginate(page, limit) + |> Repo.all() end - alias Mobilizon.Events.TagRelation + @doc """ + Checks whether two tags are linked or not. + """ + @spec are_tags_linked(Tag.t(), Tag.t()) :: boolean + def are_tags_linked(%Tag{id: tag1_id}, %Tag{id: tag2_id}) do + tag_relation = + tag1_id + |> tags_linked_query(tag2_id) + |> Repo.one() + + !!tag_relation + end @doc """ - Create a relation between two tags + 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, Ecto.Changeset.t()} def create_tag_relation(attrs \\ {}) do %TagRelation{} |> TagRelation.changeset(attrs) - |> Repo.insert(conflict_target: [:tag_id, :link_id], on_conflict: [inc: [weight: 1]]) + |> Repo.insert( + conflict_target: [:tag_id, :link_id], + on_conflict: [inc: [weight: 1]] + ) end @doc """ - Remove a tag relation + Removes a tag relation. """ + @spec delete_tag_relation(TagRelation.t()) :: + {:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()} def delete_tag_relation(%TagRelation{} = tag_relation) do Repo.delete(tag_relation) end - @doc """ - Returns whether two tags are linked or not - """ - def are_tags_linked(%Tag{id: tag1_id}, %Tag{id: tag2_id}) do - case from(tr in TagRelation, - where: tr.tag_id == ^min(tag1_id, tag2_id) and tr.link_id == ^max(tag1_id, tag2_id) - ) - |> Repo.one() do - %TagRelation{} -> true - _ -> false - end - end - @doc """ Returns the tags neighbors for a given tag - We can't rely on the single many_to_many relation since we also want tags that link to our tag, not just tags linked by this one + We can't rely on the single many_to_many relation since we also want tags that + link to our tag, not just tags linked by this one. The SQL query looks like this: ```sql @@ -732,196 +532,74 @@ defmodule Mobilizon.Events do DESC; ``` """ - def tag_neighbors(%Tag{id: id}, relation_minimum \\ 1, limit \\ 10) do - query2 = - from(tr in TagRelation, - select: %{id: tr.tag_id, weight: tr.weight}, - where: tr.link_id == ^id - ) - - query = - from(tr in TagRelation, - select: %{id: tr.link_id, weight: tr.weight}, - union_all: ^query2, - where: tr.tag_id == ^id - ) - - final_query = - from(t in Tag, - right_join: q in subquery(query), - on: [id: t.id], - where: q.weight >= ^relation_minimum, - limit: ^limit, - order_by: [desc: q.weight] - ) - - Repo.all(final_query) - end - - alias Mobilizon.Events.Participant - - @doc """ - Returns the list of participants. - - ## Examples - - iex> list_participants() - [%Participant{}, ...] - - """ - def list_participants do - Repo.all(Participant) - end - - @doc """ - Returns the list of participants for an event. - - Default behaviour is to not return :not_approved participants - - ## Examples - - iex> list_participants_for_event(some_uuid) - [%Participant{}, ...] - - """ - def list_participants_for_event(uuid, page \\ nil, limit \\ nil, include_not_improved \\ false) - - def list_participants_for_event(uuid, page, limit, false) do - query = do_list_participants_for_event(uuid, page, limit) - query = from(p in query, where: p.role != ^:not_approved) - Repo.all(query) - end - - def list_participants_for_event(uuid, page, limit, true) do - query = do_list_participants_for_event(uuid, page, limit) - Repo.all(query) - end - - defp do_list_participants_for_event(uuid, page, limit) do - from( - p in Participant, - join: e in Event, - on: p.event_id == e.id, - where: e.uuid == ^uuid, - preload: [:actor] - ) - |> Page.paginate(page, limit) - end - - @doc """ - Returns the list of participations for an actor. - - Default behaviour is to not return :not_approved participants - - ## Examples - - iex> list_participants_for_actor(%Actor{}) - [%Participant{}, ...] - - """ - def list_event_participations_for_actor(%Actor{id: id}, page \\ nil, limit \\ nil) do - Repo.all( - from( - e in Event, - join: p in Participant, - join: a in Actor, - on: p.actor_id == a.id, - on: p.event_id == e.id, - where: a.id == ^id and p.role != ^:not_approved, - preload: [:picture, :tags] - ) - |> Page.paginate(page, limit) - ) - end - - @doc """ - Returns the list of organizers participants for an event. - - ## Examples - - iex> list_organizers_participants_for_event(id) - [%Participant{role: :creator}, ...] - - """ - def list_organizers_participants_for_event(id, page \\ nil, limit \\ nil) do - Repo.all( - from( - p in Participant, - where: p.event_id == ^id and p.role == ^:creator, - preload: [:actor] - ) - |> Page.paginate(page, limit) - ) + @spec list_tag_neighbors(Tag.t(), integer, integer) :: [Tag.t()] + def list_tag_neighbors(%Tag{id: tag_id}, relation_minimum \\ 1, limit \\ 10) do + tag_id + |> tag_relation_subquery() + |> tag_relation_union_subquery(tag_id) + |> tag_neighbors_query(relation_minimum, limit) + |> Repo.all() end @doc """ Gets a single participant. - - Raises `Ecto.NoResultsError` if the Participant does not exist. - - ## Examples - - iex> get_participant!(123) - %Participant{} - - iex> get_participant!(456) - ** (Ecto.NoResultsError) - """ + @spec get_participant(integer | String.t(), integer | String.t()) :: + {:ok, Participant.t()} | {:error, :participant_not_found} + def get_participant(event_id, actor_id) do + case Repo.get_by(Participant, event_id: event_id, actor_id: actor_id) do + %Participant{} = participant -> + {:ok, participant} + + nil -> + {:error, :participant_not_found} + end + end + + @doc """ + Gets a single participant. + Raises `Ecto.NoResultsError` if the participant does not exist. + """ + @spec get_participant!(integer | String.t(), integer | String.t()) :: Participant.t() def get_participant!(event_id, actor_id) do Repo.get_by!(Participant, event_id: event_id, actor_id: actor_id) end @doc """ - Get a single participant + Gets a participant by its URL. """ - def get_participant(event_id, actor_id) do - case Repo.get_by(Participant, event_id: event_id, actor_id: actor_id) do - nil -> {:error, :participant_not_found} - participant -> {:ok, participant} - end - end - + @spec get_participant_by_url(String.t()) :: Participant.t() | nil def get_participant_by_url(url) do - Repo.one( - from(p in Participant, - where: p.url == ^url, - preload: [:actor, :event] - ) - ) + url + |> participant_by_url_query() + |> Repo.one() end @doc """ - Creates a participant. - - ## Examples - - iex> create_participant(%{field: value}) - {:ok, %Participant{}} - - iex> create_participant(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - + Gets the default participant role depending on the event join options. """ + @spec get_default_participant_role(Event.t()) :: :participant | :not_approved + def get_default_participant_role(%Event{join_options: :free}), do: :participant + def get_default_participant_role(%Event{join_options: _}), do: :not_approved + + @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 + %Participant{} + |> Participant.changeset(attrs) + |> Repo.insert() do {:ok, Repo.preload(participant, [:event, :actor])} end end @doc """ Updates a participant. - - ## Examples - - iex> update_participant(participant, %{field: new_value}) - {:ok, %Participant{}} - - iex> update_participant(participant, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_participant(Participant.t(), map) :: + {:ok, Participant.t()} | {:error, Ecto.Changeset.t()} def update_participant(%Participant{} = participant, attrs) do participant |> Participant.changeset(attrs) @@ -929,112 +607,88 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Participant. - - ## Examples - - iex> delete_participant(participant) - {:ok, %Participant{}} - - iex> delete_participant(participant) - {:error, %Ecto.Changeset{}} - + Deletes a participant. """ - def delete_participant(%Participant{} = participant) do - Repo.delete(participant) - end + @spec delete_participant(Participant.t()) :: + {:ok, Participant.t()} | {:error, Ecto.Changeset.t()} + def delete_participant(%Participant{} = participant), do: Repo.delete(participant) @doc """ - Returns an `%Ecto.Changeset{}` for tracking participant changes. - - ## Examples - - iex> change_participant(participant) - %Ecto.Changeset{source: %Participant{}} - + Returns the list of participants. """ - def change_participant(%Participant{} = participant) do - Participant.changeset(participant, %{}) - end + @spec list_participants :: [Participant.t()] + def list_participants, do: Repo.all(Participant) @doc """ - Get the default participant role depending on the event join options + Returns the list of participants for an event. + Default behaviour is to not return :not_approved participants """ - def get_default_participant_role(%Event{} = event) do - case event.join_options do - # Participant - :free -> :participant - # Not approved - _ -> :not_approved - end - end - - @doc """ - List event participation requests for an actor - """ - @spec list_requests_for_actor(Actor.t()) :: list(Participant.t()) - def list_requests_for_actor(%Actor{id: actor_id}) do - Repo.all(from(p in Participant, where: p.actor_id == ^actor_id and p.role == ^:not_approved)) - end - - alias Mobilizon.Events.Session - - @doc """ - Returns the list of sessions. - - ## Examples - - iex> list_sessions() - [%Session{}, ...] - - """ - def list_sessions do - Repo.all(Session) - end - - @doc """ - Returns the list of sessions for an event - """ - @spec list_sessions_for_event(Event.t()) :: list(Session.t()) - def list_sessions_for_event(%Event{id: event_id}) do - Repo.all( - from( - s in Session, - join: e in Event, - on: s.event_id == e.id, - where: e.id == ^event_id + @spec list_participants_for_event(String.t(), integer | nil, integer | nil, boolean) :: + [Participant.t()] + def list_participants_for_event( + event_uuid, + page \\ nil, + limit \\ nil, + include_not_improved \\ false ) - ) + + def list_participants_for_event(event_uuid, page, limit, include_not_improved) do + event_uuid + |> participants_for_event() + |> filter_role(include_not_improved) + |> Page.paginate(page, limit) + |> Repo.all() + end + + @doc """ + Returns the list of organizers participants for an event. + """ + @spec list_organizers_participants_for_event( + integer | String.t(), + integer | nil, + integer | nil + ) :: + [Participant.t()] + def list_organizers_participants_for_event(event_id, page \\ nil, limit \\ nil) do + event_id + |> organizers_participants_for_event() + |> Page.paginate(page, limit) + |> Repo.all() + end + + @doc """ + Returns the list of event participation requests for an actor. + """ + @spec list_requests_for_actor(Actor.t()) :: [Participant.t()] + def list_requests_for_actor(%Actor{id: actor_id}) do + actor_id + |> requests_for_actor_query() + |> Repo.all() + end + + @doc """ + Returns the list of participations for an actor. + """ + @spec list_event_participations_for_actor(Actor.t(), integer | nil, integer | nil) :: + [Event.t()] + def list_event_participations_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do + actor_id + |> event_participations_for_actor_query() + |> Page.paginate(page, limit) + |> Repo.all() end @doc """ Gets a single session. - - Raises `Ecto.NoResultsError` if the Session does not exist. - - ## Examples - - iex> get_session!(123) - %Session{} - - iex> get_session!(456) - ** (Ecto.NoResultsError) - + Raises `Ecto.NoResultsError` if the session does not exist. """ + @spec get_session!(integer | String.t()) :: Session.t() def get_session!(id), do: Repo.get!(Session, id) @doc """ Creates a session. - - ## Examples - - iex> create_session(%{field: value}) - {:ok, %Session{}} - - iex> create_session(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec create_session(map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()} def create_session(attrs \\ %{}) do %Session{} |> Session.changeset(attrs) @@ -1043,16 +697,8 @@ defmodule Mobilizon.Events do @doc """ Updates a session. - - ## Examples - - iex> update_session(session, %{field: new_value}) - {:ok, %Session{}} - - iex> update_session(session, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()} def update_session(%Session{} = session, attrs) do session |> Session.changeset(attrs) @@ -1060,85 +706,38 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Session. - - ## Examples - - iex> delete_session(session) - {:ok, %Session{}} - - iex> delete_session(session) - {:error, %Ecto.Changeset{}} - + Deletes a session. """ - def delete_session(%Session{} = session) do - Repo.delete(session) - end + @spec delete_session(Session.t()) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()} + def delete_session(%Session{} = session), do: Repo.delete(session) @doc """ - Returns an `%Ecto.Changeset{}` for tracking session changes. - - ## Examples - - iex> change_session(session) - %Ecto.Changeset{source: %Session{}} - + Returns the list of sessions. """ - def change_session(%Session{} = session) do - Session.changeset(session, %{}) - end - - alias Mobilizon.Events.Track + @spec list_sessions :: [Session.t()] + def list_sessions, do: Repo.all(Session) @doc """ - Returns the list of tracks. - - ## Examples - - iex> list_tracks() - [%Track{}, ...] - + Returns the list of sessions for the event. """ - def list_tracks do - Repo.all(Track) - end - - @doc """ - Returns the list of sessions for a track - """ - @spec list_sessions_for_track(Track.t()) :: list(Session.t()) - def list_sessions_for_track(%Track{id: track_id}) do - Repo.all(from(s in Session, where: s.track_id == ^track_id)) + @spec list_sessions_for_event(Event.t()) :: [Session.t()] + def list_sessions_for_event(%Event{id: event_id}) do + event_id + |> sessions_for_event_query() + |> Repo.all() end @doc """ Gets a single track. - - Raises `Ecto.NoResultsError` if the Track does not exist. - - ## Examples - - iex> get_track!(123) - %Track{} - - iex> get_track!(456) - ** (Ecto.NoResultsError) - + Raises `Ecto.NoResultsError` if the track does not exist. """ + @spec get_track!(integer | String.t()) :: Track.t() def get_track!(id), do: Repo.get!(Track, id) @doc """ Creates a track. - - ## Examples - - iex> create_track(%{field: value}) - {:ok, %Track{}} - - iex> create_track(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec create_track(map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()} def create_track(attrs \\ %{}) do %Track{} |> Track.changeset(attrs) @@ -1147,16 +746,8 @@ defmodule Mobilizon.Events do @doc """ Updates a track. - - ## Examples - - iex> update_track(track, %{field: new_value}) - {:ok, %Track{}} - - iex> update_track(track, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_track(Track.t(), map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()} def update_track(%Track{} = track, attrs) do track |> Track.changeset(attrs) @@ -1164,196 +755,121 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Track. - - ## Examples - - iex> delete_track(track) - {:ok, %Track{}} - - iex> delete_track(track) - {:error, %Ecto.Changeset{}} - + Deletes a track. """ - def delete_track(%Track{} = track) do - Repo.delete(track) - end + @spec delete_track(Track.t()) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()} + def delete_track(%Track{} = track), do: Repo.delete(track) @doc """ - Returns an `%Ecto.Changeset{}` for tracking track changes. - - ## Examples - - iex> change_track(track) - %Ecto.Changeset{source: %Track{}} - + Returns the list of tracks. """ - def change_track(%Track{} = track) do - Track.changeset(track, %{}) - end - - alias Mobilizon.Events.Comment + @spec list_tracks :: [Track.t()] + def list_tracks, do: Repo.all(Track) @doc """ - Returns the list of public comments. - - ## Examples - - iex> list_comments() - [%Comment{}, ...] - + Returns the list of sessions for the track. """ - def list_comments do - Repo.all(from(c in Comment, where: c.visibility == ^:public)) - end - - def get_public_comments_for_actor(%Actor{id: actor_id}, page \\ nil, limit \\ nil) do - query = - from( - c in Comment, - where: c.actor_id == ^actor_id and c.visibility in [^:public, ^:unlisted], - order_by: [desc: :id], - preload: [ - :actor, - :in_reply_to_comment, - :origin_comment, - :event - ] - ) - |> Page.paginate(page, limit) - - comments = Repo.all(query) - - count_comments = - Repo.one(from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id)) - - {:ok, comments, count_comments} + @spec list_sessions_for_track(Track.t()) :: [Session.t()] + def list_sessions_for_track(%Track{id: track_id}) do + track_id + |> sessions_for_track_query() + |> Repo.all() end @doc """ Gets a single comment. - - Raises `Ecto.NoResultsError` if the Comment does not exist. - - ## Examples - - iex> get_comment!(123) - %Comment{} - - iex> get_comment!(456) - ** (Ecto.NoResultsError) - + Raises `Ecto.NoResultsError` if the comment does not exist. """ + @spec get_comment!(integer | String.t()) :: Comment.t() def get_comment!(id), do: Repo.get!(Comment, id) - # @doc """ - # Gets a single comment from it's UUID + @doc """ + Gets a comment by its URL. + """ + @spec get_comment_from_url(String.t()) :: Comment.t() | nil + def get_comment_from_url(url), do: Repo.get_by(Comment, url: url) - # """ - # @spec get_comment_from_uuid(String.t) :: {:ok, Comment.t} | {:error, nil} - # def get_comment_from_uuid(uuid), do: Repo.get_by(Comment, uuid: uuid) + @doc """ + Gets a comment by its URL. + Raises `Ecto.NoResultsError` if the comment does not exist. + """ + @spec get_comment_from_url!(String.t()) :: Comment.t() + def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url) - # @doc """ - # Gets a single comment by it's UUID. + @doc """ + Gets a comment by its URL, with all associations loaded. + """ + @spec get_comment_from_url_with_preload(String.t()) :: + {:ok, Comment.t()} | {:error, :comment_not_found} + def get_comment_from_url_with_preload(url) do + comment = + from(c in Comment, where: c.url == ^url) + |> preload_for_comment() + |> Repo.one() - # Raises `Ecto.NoResultsError` if the Comment does not exist. + case comment do + %Comment{} = comment -> + {:ok, comment} - # ## Examples - - # iex> get_comment_from_uuid!("123AFV13") - # %Comment{} - - # iex> get_comment_from_uuid!("20R9HKDJHF") - # ** (Ecto.NoResultsError) - - # """ - # @spec get_comment_from_uuid(String.t) :: Comment.t - # def get_comment_from_uuid!(uuid), do: Repo.get_by!(Comment, uuid: uuid) - - def get_comment_full_from_uuid(uuid) do - with %Comment{} = comment <- Repo.get_by!(Comment, uuid: uuid) do - Repo.preload(comment, [:actor, :attributed_to, :in_reply_to_comment]) + nil -> + {:error, :comment_not_found} end end - def get_cached_comment_full_by_uuid(uuid) do + @doc """ + Gets a comment by its URL, with all associations loaded. + Raises `Ecto.NoResultsError` if the comment does not exist. + """ + @spec get_comment_from_url_with_preload(String.t()) :: Comment.t() + def get_comment_from_url_with_preload!(url) do + Comment + |> Repo.get_by!(url: url) + |> Repo.preload(@comment_preloads) + end + + @doc """ + Gets a comment by its UUID, with all associations loaded. + """ + @spec get_comment_from_uuid_with_preload(String.t()) :: Comment.t() + def get_comment_from_uuid_with_preload(uuid) do + Comment + |> Repo.get_by(uuid: uuid) + |> Repo.preload(@comment_preloads) + end + + # TODO: move to MobilizonWeb + @spec get_cached_comment_by_uuid_with_preload(String.t()) :: + {:commit, Comment.t()} | {:ignore, nil} + def get_cached_comment_by_uuid_with_preload(uuid) do Cachex.fetch(:activity_pub, "comment_" <> uuid, fn "comment_" <> uuid -> - case get_comment_full_from_uuid(uuid) do + case get_comment_from_uuid_with_preload(uuid) do %Comment{} = comment -> {:commit, comment} - _ -> + nil -> {:ignore, nil} end end) end - def get_comment_from_url(url), do: Repo.get_by(Comment, url: url) - - def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url) - - def get_comment_full_from_url(url) do - case Repo.one( - from(c in Comment, where: c.url == ^url, preload: [:actor, :in_reply_to_comment]) - ) do - nil -> {:error, :comment_not_found} - comment -> {:ok, comment} - end - end - - def get_comment_full_from_url!(url) do - with %Comment{} = comment <- Repo.get_by!(Comment, url: url) do - Repo.preload(comment, [:actor, :in_reply_to_comment]) - end - end - - @doc """ - Get all comments by an actor and a list of ids - """ - def get_all_comments_by_actor_and_ids(actor_id, comment_ids \\ []) - def get_all_comments_by_actor_and_ids(_actor_id, []), do: [] - - def get_all_comments_by_actor_and_ids(actor_id, comment_ids) do - Comment - |> where([c], c.id in ^comment_ids) - |> where([c], c.actor_id == ^actor_id) - |> Repo.all() - end - @doc """ Creates a comment. - - ## Examples - - iex> create_comment(%{field: value}) - {:ok, %Comment{}} - - iex> create_comment(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec create_comment(map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()} def create_comment(attrs \\ %{}) do with {:ok, %Comment{} = comment} <- %Comment{} |> Comment.changeset(attrs) |> Repo.insert(), - %Comment{} = comment <- Repo.preload(comment, [:actor, :in_reply_to_comment]) do + %Comment{} = comment <- Repo.preload(comment, @comment_preloads) do {:ok, comment} end end @doc """ Updates a comment. - - ## Examples - - iex> update_comment(comment, %{field: new_value}) - {:ok, %Comment{}} - - iex> update_comment(comment, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_comment(Comment.t(), map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()} def update_comment(%Comment{} = comment, attrs) do comment |> Comment.changeset(attrs) @@ -1361,114 +877,85 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a Comment. - - ## Examples - - iex> delete_comment(comment) - {:ok, %Comment{}} - - iex> delete_comment(comment) - {:error, %Ecto.Changeset{}} - + Deletes a comment. """ - def delete_comment(%Comment{} = comment) do - Repo.delete(comment) + @spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()} + def delete_comment(%Comment{} = comment), do: Repo.delete(comment) + + @doc """ + Returns the list of public comments. + """ + @spec list_comments :: [Comment.t()] + def list_comments do + Repo.all(from(c in Comment, where: c.visibility == ^:public)) end @doc """ - Returns an `%Ecto.Changeset{}` for tracking comment changes. - - ## Examples - - iex> change_comment(comment) - %Ecto.Changeset{source: %Comment{}} - + Returns the list of public comments for the actor. """ - def change_comment(%Comment{} = comment) do - Comment.changeset(comment, %{}) + @spec list_public_events_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 = + actor_id + |> public_comments_for_actor_query() + |> Page.paginate(page, limit) + |> Repo.all() + + count_comments = + actor_id + |> count_comments_query() + |> Repo.one() + + {:ok, comments, count_comments} end - alias Mobilizon.Events.FeedToken + @doc """ + Returns the list of comments by an actor and a list of ids. + """ + @spec list_comments_by_actor_and_ids(integer | String.t(), [integer | String.t()]) :: + [Comment.t()] + def list_comments_by_actor_and_ids(actor_id, comment_ids \\ []) + def list_comments_by_actor_and_ids(_actor_id, []), do: [] + + def list_comments_by_actor_and_ids(actor_id, comment_ids) do + Comment + |> where([c], c.id in ^comment_ids) + |> where([c], c.actor_id == ^actor_id) + |> Repo.all() + end + + @doc """ + Counts local comments. + """ + @spec count_local_comments :: integer + def count_local_comments, do: Repo.one(count_local_comments_query()) @doc """ Gets a single feed token. - - ## Examples - - iex> get_feed_token("123") - {:ok, %FeedToken{}} - - iex> get_feed_token("456") - {:error, nil} - """ + @spec get_feed_token(String.t()) :: FeedToken.t() | nil def get_feed_token(token) do - from(ftk in FeedToken, where: ftk.token == ^token, preload: [:actor, :user]) + token + |> feed_token_query() |> Repo.one() end @doc """ Gets a single feed token. - - Raises `Ecto.NoResultsError` if the FeedToken does not exist. - - ## Examples - - iex> get_feed_token!(123) - %FeedToken{} - - iex> get_feed_token!(456) - ** (Ecto.NoResultsError) - + Raises `Ecto.NoResultsError` if the feed token does not exist. """ + @spec get_feed_token!(String.t()) :: FeedToken.t() def get_feed_token!(token) do - from( - tk in FeedToken, - where: tk.token == ^token, - preload: [:actor, :user] - ) + token + |> feed_token_query() |> Repo.one!() end - @doc """ - Get feed tokens for an user - """ - @spec get_feed_tokens(User.t()) :: list(FeedTokens.t()) - def get_feed_tokens(%User{id: id}) do - from( - tk in FeedToken, - where: tk.user_id == ^id, - preload: [:actor, :user] - ) - |> Repo.all() - end - - @doc """ - Get feed tokens for an actor - """ - @spec get_feed_tokens(Actor.t()) :: list(FeedTokens.t()) - def get_feed_tokens(%Actor{id: id, domain: nil}) do - from( - tk in FeedToken, - where: tk.actor_id == ^id, - preload: [:actor, :user] - ) - |> Repo.all() - end - @doc """ Creates a feed token. - - ## Examples - - iex> create_feed_token(%{field: value}) - {:ok, %FeedToken{}} - - iex> create_feed_token(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()} def create_feed_token(attrs \\ %{}) do attrs = Map.put(attrs, "token", Ecto.UUID.generate()) @@ -1479,16 +966,9 @@ defmodule Mobilizon.Events do @doc """ Updates a feed token. - - ## Examples - - iex> update_feed_token(feed_token, %{field: new_value}) - {:ok, %FeedToken{}} - - iex> update_feed_token(feed_token, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ + @spec update_feed_token(FeedToken.t(), map) :: + {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()} def update_feed_token(%FeedToken{} = feed_token, attrs) do feed_token |> FeedToken.changeset(attrs) @@ -1496,31 +976,318 @@ defmodule Mobilizon.Events do end @doc """ - Deletes a FeedToken. - - ## Examples - - iex> delete_feed_token(feed_token) - {:ok, %FeedToken{}} - - iex> delete_feed_token(feed_token) - {:error, %Ecto.Changeset{}} - + Deletes a feed token. """ - def delete_feed_token(%FeedToken{} = feed_token) do - Repo.delete(feed_token) + @spec delete_feed_token(FeedToken.t()) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()} + def delete_feed_token(%FeedToken{} = feed_token), do: Repo.delete(feed_token) + + @doc """ + Returns the list of feed tokens for an user. + """ + @spec list_feed_tokens_for_user(User.t()) :: [FeedTokens.t()] + def list_feed_tokens_for_user(%User{id: user_id}) do + user_id + |> feed_token_for_user_query() + |> Repo.all() end @doc """ - Returns an `%Ecto.Changeset{}` for tracking feed_token changes. - - ## Examples - - iex> change_feed_token(feed_token) - %Ecto.Changeset{source: %FeedToken{}} - + Returns the list of feed tokens for an actor. """ - def change_feed_token(%FeedToken{} = feed_token) do - FeedToken.changeset(feed_token, %{}) + @spec list_feed_tokens_for_actor(Actor.t()) :: [FeedTokens.t()] + def list_feed_tokens_for_actor(%Actor{id: actor_id, domain: nil}) do + actor_id + |> feed_token_for_actor_query() + |> Repo.all() end + + @spec event_by_url_query(String.t()) :: Ecto.Query.t() + defp event_by_url_query(url) do + from(e in Event, where: e.url == ^url) + end + + @spec event_by_uuid_query(String.t()) :: Ecto.Query.t() + defp event_by_uuid_query(uuid) do + from(e in Event, where: e.uuid == ^uuid) + end + + @spec event_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp event_for_actor_query(actor_id) do + from( + e in Event, + where: e.organizer_actor_id == ^actor_id, + order_by: [desc: :id] + ) + end + + @spec upcoming_public_event_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp upcoming_public_event_for_actor_query(actor_id) do + from( + e in Event, + where: + e.organizer_actor_id == ^actor_id and + e.begins_on > ^DateTime.utc_now(), + order_by: [asc: :begins_on], + limit: 1, + preload: [ + :organizer_actor, + :tags, + :participants, + :physical_address + ] + ) + end + + @spec close_events_query(Geo.geometry(), number) :: Ecto.Query.t() + defp close_events_query(point, radius) do + from( + e in Event, + join: a in Address, + on: a.id == e.physical_address_id, + where: e.visibility == ^:public and st_dwithin_in_meters(^point, a.geom, ^radius), + preload: :organizer_actor + ) + end + + @spec events_by_name_query(String.t()) :: Ecto.Query.t() + defp events_by_name_query(name) do + from( + e in Event, + where: + e.visibility == ^:public and + fragment("f_unaccent(?) %> f_unaccent(?)", e.title, ^name), + order_by: fragment("word_similarity(?, ?) desc", e.title, ^name), + preload: [:organizer_actor] + ) + end + + @spec events_by_tags_query([integer], integer) :: Ecto.Query.t() + def events_by_tags_query(tags_ids, limit) do + from( + e in Event, + distinct: e.uuid, + join: te in "events_tags", + on: e.id == te.event_id, + where: e.begins_on > ^DateTime.utc_now(), + where: e.visibility in ^@public_visibility, + where: te.tag_id in ^tags_ids, + order_by: [asc: e.begins_on], + limit: ^limit + ) + end + + @spec count_events_for_actor_query(integer | String.t()) :: Ecto.Query.t() + defp count_events_for_actor_query(actor_id) do + from( + e in Event, + select: count(e.id), + where: e.organizer_actor_id == ^actor_id + ) + end + + @spec count_local_events_query :: Ecto.Query.t() + defp count_local_events_query do + from(e in Event, select: count(e.id), where: e.local == ^true) + end + + @spec tag_by_slug_query(String.t()) :: Ecto.Query.t() + defp tag_by_slug_query(slug) do + from(t in Tag, where: t.slug == ^slug) + end + + @spec tags_for_event_query(integer) :: Ecto.Query.t() + defp tags_for_event_query(event_id) do + from( + t in Tag, + join: e in "events_tags", + on: t.id == e.tag_id, + where: e.event_id == ^event_id + ) + end + + @spec tags_linked_query(integer, integer) :: Ecto.Query.t() + defp tags_linked_query(tag1_id, tag2_id) do + from( + tr in TagRelation, + where: + tr.tag_id == ^min(tag1_id, tag2_id) and + tr.link_id == ^max(tag1_id, tag2_id) + ) + end + + @spec tag_relation_subquery(integer) :: Ecto.Query.t() + defp tag_relation_subquery(tag_id) do + from( + tr in TagRelation, + select: %{id: tr.tag_id, weight: tr.weight}, + where: tr.link_id == ^tag_id + ) + end + + @spec tag_relation_union_subquery(Ecto.Query.t(), integer) :: Ecto.Query.t() + defp tag_relation_union_subquery(subquery, tag_id) do + from( + tr in TagRelation, + select: %{id: tr.link_id, weight: tr.weight}, + union_all: ^subquery, + where: tr.tag_id == ^tag_id + ) + end + + @spec tag_neighbors_query(Ecto.Query.t(), integer, integer) :: Ecto.Query.t() + defp tag_neighbors_query(subquery, relation_minimum, limit) do + from( + t in Tag, + right_join: q in subquery(subquery), + on: [id: t.id], + where: q.weight >= ^relation_minimum, + limit: ^limit, + order_by: [desc: q.weight] + ) + end + + @spec participant_by_url_query(String.t()) :: Ecto.Query.t() + defp participant_by_url_query(url) do + from( + p in Participant, + where: p.url == ^url, + preload: [:actor, :event] + ) + end + + @spec participants_for_event(String.t()) :: Ecto.Query.t() + defp participants_for_event(event_uuid) do + from( + p in Participant, + join: e in Event, + on: p.event_id == e.id, + where: e.uuid == ^event_uuid, + preload: [:actor] + ) + end + + defp organizers_participants_for_event(event_id) do + from( + p in Participant, + where: p.event_id == ^event_id and p.role == ^:creator, + preload: [:actor] + ) + end + + @spec requests_for_actor_query(integer) :: Ecto.Query.t() + defp requests_for_actor_query(actor_id) do + from(p in Participant, where: p.actor_id == ^actor_id and p.role == ^:not_approved) + end + + @spec event_participations_for_actor_query(integer) :: Ecto.Query.t() + def event_participations_for_actor_query(actor_id) do + from( + e in Event, + join: p in Participant, + join: a in Actor, + on: p.actor_id == a.id, + on: p.event_id == e.id, + where: a.id == ^actor_id and p.role != ^:not_approved, + preload: [:picture, :tags] + ) + end + + @spec sessions_for_event_query(integer) :: Ecto.Query.t() + defp sessions_for_event_query(event_id) do + from( + s in Session, + join: e in Event, + on: s.event_id == e.id, + where: e.id == ^event_id + ) + end + + @spec sessions_for_track_query(integer) :: Ecto.Query.t() + defp sessions_for_track_query(track_id) do + from(s in Session, where: s.track_id == ^track_id) + 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 + ] + ) + end + + @spec count_comments_query(integer) :: Ecto.Query.t() + defp count_comments_query(actor_id) do + from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id) + end + + @spec count_local_comments_query :: Ecto.Query.t() + defp count_local_comments_query do + from( + c in Comment, + select: count(c.id), + where: c.local == ^true and c.visibility in ^@public_visibility + ) + end + + @spec feed_token_query(String.t()) :: Ecto.Query.t() + defp feed_token_query(token) do + from(ftk in FeedToken, where: ftk.token == ^token, preload: [:actor, :user]) + end + + @spec feed_token_for_user_query(integer) :: Ecto.Query.t() + defp feed_token_for_user_query(user_id) do + from(tk in FeedToken, where: tk.user_id == ^user_id, preload: [:actor, :user]) + end + + @spec feed_token_for_actor_query(integer) :: Ecto.Query.t() + defp feed_token_for_actor_query(actor_id) do + from(tk in FeedToken, where: tk.actor_id == ^actor_id, preload: [:actor, :user]) + end + + @spec filter_public_visibility(Ecto.Query.t()) :: Ecto.Query.t() + defp filter_public_visibility(query) do + from(e in query, where: e.visibility in ^@public_visibility) + end + + @spec filter_not_event_uuid(Ecto.Query.t(), String.t() | nil) :: Ecto.Query.t() + defp filter_not_event_uuid(query, nil), do: query + + defp filter_not_event_uuid(query, not_event_uuid) do + from(e in query, where: e.uuid != ^not_event_uuid) + end + + @spec filter_future_events(Ecto.Query.t(), boolean) :: Ecto.Query.t() + defp filter_future_events(query, true) do + from(q in query, where: q.begins_on > ^DateTime.utc_now()) + end + + defp filter_future_events(query, false), do: query + + @spec filter_unlisted(Ecto.Query.t(), boolean) :: Ecto.Query.t() + defp filter_unlisted(query, true) do + from(q in query, where: q.visibility in ^@public_visibility) + end + + defp filter_unlisted(query, false) do + from(q in query, where: q.visibility == ^:public) + end + + @spec filter_role(Ecto.Query.t(), boolean) :: Ecto.Query.t() + defp filter_role(query, false) do + from(p in query, where: p.role != ^:not_approved) + end + + defp filter_role(query, true), do: query + + @spec preload_for_event(Ecto.Query.t()) :: Ecto.Query.t() + defp preload_for_event(query), do: preload(query, ^@event_preloads) + + @spec preload_for_comment(Ecto.Query.t()) :: Ecto.Query.t() + defp preload_for_comment(query), do: preload(query, ^@comment_preloads) end diff --git a/lib/mobilizon/events/tag_relations.ex b/lib/mobilizon/events/tag_relation.ex similarity index 100% rename from lib/mobilizon/events/tag_relations.ex rename to lib/mobilizon/events/tag_relation.ex diff --git a/lib/mobilizon_web/api/reports.ex b/lib/mobilizon_web/api/reports.ex index 171e70b04..57925562b 100644 --- a/lib/mobilizon_web/api/reports.ex +++ b/lib/mobilizon_web/api/reports.ex @@ -73,7 +73,7 @@ defmodule MobilizonWeb.API.Reports do defp get_report_comments(%Actor{id: actor_id}, comment_ids) do {:get_report_comments, - Events.get_all_comments_by_actor_and_ids(actor_id, comment_ids) |> Enum.map(& &1.url)} + Events.list_comments_by_actor_and_ids(actor_id, comment_ids) |> Enum.map(& &1.url)} end defp get_report_comments(_, _), do: {:get_report_comments, nil} diff --git a/lib/mobilizon_web/api/search.ex b/lib/mobilizon_web/api/search.ex index 67e9a2726..835bbce5a 100644 --- a/lib/mobilizon_web/api/search.ex +++ b/lib/mobilizon_web/api/search.ex @@ -68,7 +68,7 @@ defmodule MobilizonWeb.API.Search do end true -> - {:ok, Events.find_and_count_events_by_name(search, page, limit)} + {:ok, Events.build_events_by_name(search, page, limit)} end end diff --git a/lib/mobilizon_web/controllers/page_controller.ex b/lib/mobilizon_web/controllers/page_controller.ex index 0c608a6df..d09a4f4bf 100644 --- a/lib/mobilizon_web/controllers/page_controller.ex +++ b/lib/mobilizon_web/controllers/page_controller.ex @@ -17,12 +17,12 @@ defmodule MobilizonWeb.PageController do end def event(conn, %{"uuid" => uuid}) do - {status, event} = Events.get_cached_event_full_by_uuid(uuid) + {status, event} = Events.get_cached_public_event_by_uuid_with_preload(uuid) render_or_error(conn, &ok_status_and_is_visible?/2, status, :event, event) end def comment(conn, %{"uuid" => uuid}) do - {status, comment} = Events.get_cached_comment_full_by_uuid(uuid) + {status, comment} = Events.get_cached_comment_by_uuid_with_preload(uuid) render_or_error(conn, &ok_status_and_is_visible?/2, status, :comment, comment) end diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index 370b507a2..9fc527d68 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -25,7 +25,7 @@ defmodule MobilizonWeb.Resolvers.Event do end def find_event(_parent, %{uuid: uuid}, _resolution) do - case Mobilizon.Events.get_event_full_by_uuid(uuid) do + case Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid) do nil -> {:error, "Event with UUID #{uuid} not found"} @@ -58,14 +58,14 @@ defmodule MobilizonWeb.Resolvers.Event do ) do # We get the organizer's next public event events = - [Events.get_actor_upcoming_public_event(organizer_actor, uuid)] + [Events.get_upcoming_public_event_for_actor(organizer_actor, uuid)] |> Enum.filter(&is_map/1) # We find similar events with the same tags # uniq_by : It's possible event_from_same_actor is inside events_from_tags events = events - |> Enum.concat(Events.find_similar_events_by_common_tags(tags, @number_of_related_events)) + |> Enum.concat(Events.list_events_by_tags(tags, @number_of_related_events)) |> uniq_events() # TODO: We should use tag_relations to find more appropriate events @@ -104,7 +104,7 @@ defmodule MobilizonWeb.Resolvers.Event do ) do with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), {:has_event, {:ok, %Event{} = event}} <- - {:has_event, Mobilizon.Events.get_event_full(event_id)}, + {:has_event, Mobilizon.Events.get_event_with_preload(event_id)}, {:error, :participant_not_found} <- Mobilizon.Events.get_participant(event_id, actor_id), {:ok, _activity, participant} <- MobilizonWeb.API.Participations.join(event, actor), participant <- @@ -141,7 +141,7 @@ defmodule MobilizonWeb.Resolvers.Event do ) do with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), {:has_event, {:ok, %Event{} = event}} <- - {:has_event, Mobilizon.Events.get_event_full(event_id)}, + {:has_event, Mobilizon.Events.get_event_with_preload(event_id)}, {:ok, _activity, _participant} <- MobilizonWeb.API.Participations.leave(event, actor) do {:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}} else @@ -200,7 +200,7 @@ defmodule MobilizonWeb.Resolvers.Event do ) do # See https://github.com/absinthe-graphql/absinthe/issues/490 with args <- Map.put(args, :options, args[:options] || %{}), - {:ok, %Event{} = event} <- Mobilizon.Events.get_event_full(event_id), + {:ok, %Event{} = event} <- Mobilizon.Events.get_event_with_preload(event_id), {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, event.organizer_actor_id), {:ok, args} <- save_attached_picture(args), diff --git a/lib/mobilizon_web/resolvers/tag.ex b/lib/mobilizon_web/resolvers/tag.ex index ecbef7720..75f26a6cb 100644 --- a/lib/mobilizon_web/resolvers/tag.ex +++ b/lib/mobilizon_web/resolvers/tag.ex @@ -33,7 +33,7 @@ defmodule MobilizonWeb.Resolvers.Tag do # """ # def get_related_tags(_parent, %{tag_id: tag_id}, _resolution) do # with %Tag{} = tag <- Mobilizon.Events.get_tag!(tag_id), - # tags <- Mobilizon.Events.tag_neighbors(tag) do + # tags <- Mobilizon.Events.list_tag_neighbors(tag) do # {:ok, tags} # end # end @@ -42,7 +42,7 @@ defmodule MobilizonWeb.Resolvers.Tag do Retrieve the list of related tags for a parent tag """ def get_related_tags(%Tag{} = tag, _args, _resolution) do - with tags <- Mobilizon.Events.tag_neighbors(tag) do + with tags <- Mobilizon.Events.list_tag_neighbors(tag) do {:ok, tags} end end diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 68e23ec59..e27123005 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -84,10 +84,10 @@ defmodule Mobilizon.Service.ActivityPub do {:ok, _activity, %{url: object_url} = _object} <- Transmogrifier.handle_incoming(params) do case data["type"] do "Event" -> - {:ok, Events.get_event_full_by_url!(object_url)} + {:ok, Events.get_public_event_by_url_with_preload!(object_url)} "Note" -> - {:ok, Events.get_comment_full_from_url!(object_url)} + {:ok, Events.get_comment_from_url_with_preload!(object_url)} "Actor" -> {:ok, Actors.get_actor_by_url!(object_url, true)} @@ -97,10 +97,10 @@ defmodule Mobilizon.Service.ActivityPub do end else {:existing_event, %Event{url: event_url}} -> - {:ok, Events.get_event_full_by_url!(event_url)} + {:ok, Events.get_public_event_by_url_with_preload!(event_url)} {:existing_comment, %Comment{url: comment_url}} -> - {:ok, Events.get_comment_full_from_url!(comment_url)} + {:ok, Events.get_comment_from_url_with_preload!(comment_url)} {:existing_actor, {:ok, %Actor{url: actor_url}}} -> {:ok, Actors.get_actor_by_url!(actor_url, true)} @@ -682,8 +682,8 @@ defmodule Mobilizon.Service.ActivityPub do """ @spec fetch_public_activities_for_actor(Actor.t(), integer(), integer()) :: map() def fetch_public_activities_for_actor(%Actor{} = actor, page \\ 1, limit \\ 10) do - {:ok, events, total_events} = Events.get_public_events_for_actor(actor, page, limit) - {:ok, comments, total_comments} = Events.get_public_comments_for_actor(actor, page, limit) + {:ok, events, total_events} = Events.list_public_events_for_actor(actor, page, limit) + {:ok, comments, total_comments} = Events.list_public_comments_for_actor(actor, page, limit) event_activities = Enum.map(events, &event_to_activity/1) diff --git a/lib/service/export/feed.ex b/lib/service/export/feed.ex index 39fe709d2..ce33e73bb 100644 --- a/lib/service/export/feed.ex +++ b/lib/service/export/feed.ex @@ -45,7 +45,7 @@ defmodule Mobilizon.Service.Export.Feed do defp fetch_actor_event_feed(name) do with %Actor{} = actor <- Actors.get_local_actor_by_name(name), {:visibility, true} <- {:visibility, Actor.is_public_visibility(actor)}, - {:ok, events, _count} <- Events.get_public_events_for_actor(actor) do + {:ok, events, _count} <- Events.list_public_events_for_actor(actor) do {:ok, build_actor_feed(actor, events)} else err -> diff --git a/lib/service/export/icalendar.ex b/lib/service/export/icalendar.ex index 6a67a3072..70d865f48 100644 --- a/lib/service/export/icalendar.ex +++ b/lib/service/export/icalendar.ex @@ -45,7 +45,7 @@ defmodule Mobilizon.Service.Export.ICalendar do @spec export_public_actor(Actor.t()) :: String.t() def export_public_actor(%Actor{} = actor) do with true <- Actor.is_public_visibility(actor), - {:ok, events, _} <- Events.get_public_events_for_actor(actor) do + {:ok, events, _} <- Events.list_public_events_for_actor(actor) do {:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()} end end @@ -74,7 +74,7 @@ defmodule Mobilizon.Service.Export.ICalendar do Create cache for an actor """ def create_cache("event_" <> uuid) do - with %Event{} = event <- Events.get_event_full_by_uuid(uuid), + with %Event{} = event <- Events.get_public_event_by_uuid_with_preload(uuid), {:ok, res} <- export_public_event(event) do {:commit, res} else diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs index 29a396906..440c1029b 100644 --- a/test/mobilizon/events/events_test.exs +++ b/test/mobilizon/events/events_test.exs @@ -4,6 +4,7 @@ defmodule Mobilizon.EventsTest do import Mobilizon.Factory alias Mobilizon.Events + alias Mobilizon.Storage.Page @event_valid_attrs %{ begins_on: "2010-04-17 14:00:00Z", @@ -47,29 +48,29 @@ defmodule Mobilizon.EventsTest do refute Ecto.assoc_loaded?(Events.get_event!(event.id).organizer_actor) end - test "get_event_full!/1 returns the event with given id", %{event: event} do - assert Events.get_event_full!(event.id).organizer_actor.preferred_username == + test "get_event_with_preload!/1 returns the event with given id", %{event: event} do + assert Events.get_event_with_preload!(event.id).organizer_actor.preferred_username == event.organizer_actor.preferred_username - assert Events.get_event_full!(event.id).participants == [] + assert Events.get_event_with_preload!(event.id).participants == [] end - test "find_and_count_events_by_name/1 returns events for a given name", %{ + test "build_events_by_name/1 returns events for a given name", %{ event: %Event{title: title} = event } do - assert title == hd(Events.find_and_count_events_by_name(event.title).elements).title + assert title == hd(Events.build_events_by_name(event.title).elements).title %Event{} = event2 = insert(:event, title: "Special event") assert event2.title == - Events.find_and_count_events_by_name("Special").elements |> hd() |> Map.get(:title) + Events.build_events_by_name("Special").elements |> hd() |> Map.get(:title) assert event2.title == - Events.find_and_count_events_by_name(" Special ").elements + Events.build_events_by_name(" Special ").elements |> hd() |> Map.get(:title) - assert %{elements: [], total: 0} == Events.find_and_count_events_by_name("") + assert %Page{elements: [], total: 0} == Events.build_events_by_name("") end test "find_close_events/3 returns events in the area" do @@ -127,19 +128,15 @@ defmodule Mobilizon.EventsTest do assert_raise Ecto.NoResultsError, fn -> Events.get_event!(event.id) end end - test "change_event/1 returns a event changeset", %{event: event} do - assert %Ecto.Changeset{} = Events.change_event(event) - end - - test "get_public_events_for_actor/1", %{actor: actor, event: event} do - assert {:ok, [event_found], 1} = Events.get_public_events_for_actor(actor) + test "list_public_events_for_actor/1", %{actor: actor, event: event} do + assert {:ok, [event_found], 1} = Events.list_public_events_for_actor(actor) assert event_found.title == event.title end - test "get_public_events_for_actor/3", %{actor: actor, event: event} do + test "list_public_events_for_actor/3", %{actor: actor, event: event} do event1 = insert(:event, organizer_actor: actor) - case Events.get_public_events_for_actor(actor, 1, 10) do + case Events.list_public_events_for_actor(actor, 1, 10) do {:ok, events_found, 2} -> event_ids = MapSet.new(events_found |> Enum.map(& &1.id)) assert event_ids == MapSet.new([event.id, event1.id]) @@ -149,10 +146,10 @@ defmodule Mobilizon.EventsTest do end end - test "get_public_events_for_actor/3 with limited results", %{actor: actor, event: event} do + test "list_public_events_for_actor/3 with limited results", %{actor: actor, event: event} do event1 = insert(:event, organizer_actor: actor) - case Events.get_public_events_for_actor(actor, 1, 1) do + case Events.list_public_events_for_actor(actor, 1, 1) do {:ok, [%Event{id: event_found_id}], 2} -> assert event_found_id in [event.id, event1.id] @@ -229,11 +226,6 @@ defmodule Mobilizon.EventsTest do assert {:ok, %Tag{}} = Events.delete_tag(tag) assert_raise Ecto.NoResultsError, fn -> Events.get_tag!(tag.id) end end - - test "change_tag/1 returns a tag changeset" do - tag = insert(:tag) - assert %Ecto.Changeset{} = Events.change_tag(tag) - end end describe "tags_relations" do @@ -272,7 +264,7 @@ defmodule Mobilizon.EventsTest do assert {:ok, %TagRelation{}} = Events.delete_tag_relation(tag_relation) end - test "tag_neighbors/2 return the connected tags for a given tag", %{ + test "list_tag_neighbors/2 return the connected tags for a given tag", %{ tag1: %Tag{} = tag1, tag2: %Tag{} = tag2 } do @@ -307,7 +299,7 @@ defmodule Mobilizon.EventsTest do }} = Events.create_tag_relation(%{tag_id: tag1.id, link_id: tag1.id}) # The order is preserved, since tag4 has one more relation than tag2 - assert [tag4, tag2] == Events.tag_neighbors(tag1) + assert [tag4, tag2] == Events.list_tag_neighbors(tag1) end end @@ -383,10 +375,6 @@ defmodule Mobilizon.EventsTest do test "delete_participant/1 deletes the participant", %{participant: participant} do assert {:ok, %Participant{}} = Events.delete_participant(participant) end - - test "change_participant/1 returns a participant changeset", %{participant: participant} do - assert %Ecto.Changeset{} = Events.change_participant(participant) - end end describe "sessions" do @@ -481,11 +469,6 @@ defmodule Mobilizon.EventsTest do assert {:ok, %Session{}} = Events.delete_session(session) assert_raise Ecto.NoResultsError, fn -> Events.get_session!(session.id) end end - - test "change_session/1 returns a session changeset" do - session = insert(:session) - assert %Ecto.Changeset{} = Events.change_session(session) - end end describe "tracks" do @@ -548,11 +531,6 @@ defmodule Mobilizon.EventsTest do assert {:ok, %Track{}} = Events.delete_track(track) assert_raise Ecto.NoResultsError, fn -> Events.get_track!(track.id) end end - - test "change_track/1 returns a track changeset" do - track = insert(:track) - assert %Ecto.Changeset{} = Events.change_track(track) - end end describe "comments" do @@ -616,10 +594,5 @@ defmodule Mobilizon.EventsTest do assert {:ok, %Comment{}} = Events.delete_comment(comment) assert_raise Ecto.NoResultsError, fn -> Events.get_comment!(comment.id) end end - - test "change_comment/1 returns a comment changeset" do - comment = insert(:comment) - assert %Ecto.Changeset{} = Events.change_comment(comment) - end end end diff --git a/test/mobilizon/service/activity_pub/activity_pub_test.exs b/test/mobilizon/service/activity_pub/activity_pub_test.exs index b0712cb87..c3da687e6 100644 --- a/test/mobilizon/service/activity_pub/activity_pub_test.exs +++ b/test/mobilizon/service/activity_pub/activity_pub_test.exs @@ -113,7 +113,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do # TODO: The delete activity it relayed and fetched once again (and then not found /o\) test "it creates a delete activity and deletes the original event" do event = insert(:event) - event = Events.get_event_full_by_url!(event.url) + event = Events.get_public_event_by_url_with_preload!(event.url) {:ok, delete, _} = ActivityPub.delete(event) assert delete.data["type"] == "Delete" @@ -125,7 +125,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do test "it creates a delete activity and deletes the original comment" do comment = insert(:comment) - comment = Events.get_comment_full_from_url!(comment.url) + comment = Events.get_comment_from_url_with_preload!(comment.url) {:ok, delete, _} = ActivityPub.delete(comment) assert delete.data["type"] == "Delete" diff --git a/test/mobilizon_web/api/search_test.exs b/test/mobilizon_web/api/search_test.exs index d654a48e3..af26b4018 100644 --- a/test/mobilizon_web/api/search_test.exs +++ b/test/mobilizon_web/api/search_test.exs @@ -46,13 +46,13 @@ defmodule MobilizonWeb.API.SearchTest do test "search events" do with_mock Events, - find_and_count_events_by_name: fn "toto", 1, 10 -> + build_events_by_name: fn "toto", 1, 10 -> %Page{total: 1, elements: [%Event{title: "super toto event"}]} end do assert {:ok, %{total: 1, elements: [%Event{title: "super toto event"}]}} = Search.search_events("toto", 1, 10) - assert_called(Events.find_and_count_events_by_name("toto", 1, 10)) + assert_called(Events.build_events_by_name("toto", 1, 10)) end end end diff --git a/test/mobilizon_web/resolvers/report_resolver_test.exs b/test/mobilizon_web/resolvers/report_resolver_test.exs index cf369a4dd..71396041a 100644 --- a/test/mobilizon_web/resolvers/report_resolver_test.exs +++ b/test/mobilizon_web/resolvers/report_resolver_test.exs @@ -197,7 +197,9 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do assert json_response(res, 200)["data"]["reports"] |> Enum.map(fn report -> Map.get(report, "id") end) |> Enum.sort() == - Enum.map([report_1_id, report_2_id, report_3_id], &to_string/1) + [report_1_id, report_2_id, report_3_id] + |> Enum.map(&to_string/1) + |> Enum.sort() query = """ { diff --git a/test/mobilizon_web/resolvers/tag_resolver_test.exs b/test/mobilizon_web/resolvers/tag_resolver_test.exs index fe5a1f081..ee84f0ee2 100644 --- a/test/mobilizon_web/resolvers/tag_resolver_test.exs +++ b/test/mobilizon_web/resolvers/tag_resolver_test.exs @@ -39,9 +39,7 @@ defmodule MobilizonWeb.Resolvers.TagResolverTest do |> Enum.map(fn tag -> tag["slug"] end) |> MapSet.new() == [tag2, tag3] - |> Enum.map(fn - tag -> tag.slug - end) + |> Enum.map(fn tag -> tag.slug end) |> MapSet.new() end end