Implement related events

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-04-11 18:25:32 +02:00
parent 20a4f7244c
commit a877e4d7d9
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
7 changed files with 181 additions and 2 deletions

View file

@ -45,6 +45,34 @@ defmodule Mobilizon.Events do
{:ok, events, count_events} {:ok, events, count_events}
end end
@doc """
Get an actor's eventual upcoming public 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],
preload: [
:organizer_actor,
:tags,
:participants,
:physical_address
]
)
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 def count_local_events do
Repo.one( Repo.one(
from( from(
@ -274,6 +302,28 @@ defmodule Mobilizon.Events do
Repo.all(query) Repo.all(query)
end 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 """ @doc """
Creates a event. Creates a event.

View file

@ -3,12 +3,14 @@ defmodule MobilizonWeb.Resolvers.Event do
Handles the event-related GraphQL calls Handles the event-related GraphQL calls
""" """
alias Mobilizon.Activity alias Mobilizon.Activity
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant} alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Users.User alias Mobilizon.Users.User
# We limit the max number of events that can be retrieved # We limit the max number of events that can be retrieved
@event_max_limit 100 @event_max_limit 100
@number_of_related_events 3
def list_events(_parent, %{page: page, limit: limit}, _resolution) def list_events(_parent, %{page: page, limit: limit}, _resolution)
when limit < @event_max_limit do when limit < @event_max_limit do
@ -43,6 +45,50 @@ defmodule MobilizonWeb.Resolvers.Event do
{:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)} {:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)}
end end
@doc """
List related events
"""
def list_related_events(
%Event{tags: tags, organizer_actor: organizer_actor},
_args,
_resolution
) do
# We get the organizer's next public event
events =
[Events.get_actor_upcoming_public_event(organizer_actor, uuid)] |> Enum.filter(&is_map/1)
# uniq_by : It's possible event_from_same_actor is inside events_from_tags
events =
(events ++
Events.find_similar_events_by_common_tags(
tags,
@number_of_related_events - length(events)
))
|> uniq_events()
# TODO: We should use tag_relations to find more appropriate events
# We've considered all recommended events, so we fetch the latest events
events =
if @number_of_related_events - length(events) > 0 do
(events ++
Events.list_events(1, @number_of_related_events, :begins_on, :asc, true, true))
|> uniq_events()
else
events
end
events =
events
# We remove the same event from the results
|> Enum.filter(fn event -> event.uuid != uuid end)
# We return only @number_of_related_events right now
|> Enum.take(@number_of_related_events)
# TODO: We should use tag_relations to find more events
{:ok, events}
end
@doc """ @doc """
Join an event for an actor Join an event for an actor
""" """

View file

@ -56,6 +56,11 @@ defmodule MobilizonWeb.Schema.EventType do
description: "The event's participants" description: "The event's participants"
) )
field(:related_events, list_of(:event),
resolve: &MobilizonWeb.Resolvers.Event.list_related_events/3,
description: "Events related to this one"
)
# field(:tracks, list_of(:track)) # field(:tracks, list_of(:track))
# field(:sessions, list_of(:session)) # field(:sessions, list_of(:session))

View file

@ -0,0 +1,23 @@
defmodule Mobilizon.Repo.Migrations.EventEventTagOnDelete do
use Ecto.Migration
def up do
drop(constraint(:events_tags, "events_tags_event_id_fkey"))
drop(constraint(:events_tags, "events_tags_tag_id_fkey"))
alter table(:events_tags) do
modify(:event_id, references(:events, on_delete: :delete_all))
modify(:tag_id, references(:tags, on_delete: :delete_all))
end
end
def down do
drop(constraint(:events_tags, "events_tags_event_id_fkey"))
drop(constraint(:events_tags, "events_tags_tag_id_fkey"))
alter table(:events_tags) do
modify(:event_id, references(:events))
modify(:tag_id, references(:tags))
end
end
end

View file

@ -20,7 +20,7 @@ defmodule Mobilizon.EventsTest do
setup do setup do
actor = insert(:actor) actor = insert(:actor)
event = insert(:event, organizer_actor: actor) event = insert(:event, organizer_actor: actor, visibility: :public)
{:ok, actor: actor, event: event} {:ok, actor: actor, event: event}
end end

View file

@ -322,5 +322,59 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete" assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete"
end end
test "list_related_events/3 should give related events", %{
conn: conn,
actor: actor
} do
tag1 = insert(:tag, title: "Elixir", slug: "elixir")
tag2 = insert(:tag, title: "PostgreSQL", slug: "postgresql")
event = insert(:event, title: "Initial event", organizer_actor: actor, tags: [tag1, tag2])
event2 =
insert(:event,
title: "Event from same actor",
organizer_actor: actor,
visibility: :public,
begins_on: Timex.shift(DateTime.utc_now(), days: 3)
)
event3 =
insert(:event,
title: "Event with same tags",
tags: [tag1, tag2],
visibility: :public,
begins_on: Timex.shift(DateTime.utc_now(), days: 3)
)
query = """
{
event(uuid: "#{event.uuid}") {
uuid,
title,
tags {
id
},
related_events {
uuid,
title,
tags {
id
}
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
assert hd(json_response(res, 200)["data"]["event"]["related_events"])["uuid"] == event2.uuid
assert hd(tl(json_response(res, 200)["data"]["event"]["related_events"]))["uuid"] ==
event3.uuid
end
end end
end end

View file

@ -95,7 +95,7 @@ defmodule Mobilizon.Factory do
def event_factory do def event_factory do
actor = build(:actor) actor = build(:actor)
start = Timex.now() start = Timex.shift(DateTime.utc_now(), hours: 2)
uuid = Ecto.UUID.generate() uuid = Ecto.UUID.generate()
%Mobilizon.Events.Event{ %Mobilizon.Events.Event{
@ -108,6 +108,7 @@ defmodule Mobilizon.Factory do
category: sequence("something"), category: sequence("something"),
physical_address: build(:address), physical_address: build(:address),
visibility: :public, visibility: :public,
tags: build_list(3, :tag),
url: "#{actor.url}/#{uuid}", url: "#{actor.url}/#{uuid}",
uuid: uuid uuid: uuid
} }