Allow tag relations + bump ecto deps

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-02-14 14:19:55 +01:00
parent 4caa998ae0
commit 256d50e855
37 changed files with 515 additions and 132 deletions

View File

@@ -32,16 +32,16 @@ defmodule Mobilizon.Events.Event do
schema "events" do
field(:url, :string)
field(:local, :boolean, default: true)
field(:begins_on, Timex.Ecto.DateTimeWithTimezone)
field(:begins_on, :utc_datetime)
field(:description, :string)
field(:ends_on, Timex.Ecto.DateTimeWithTimezone)
field(:ends_on, :utc_datetime)
field(:title, :string)
field(:status, Mobilizon.Events.EventStatusEnum, default: :confirmed)
field(:visibility, Mobilizon.Events.EventVisibilityEnum, default: :public)
field(:join_options, Mobilizon.Events.JoinOptionsEnum, default: :free)
field(:thumbnail, :string)
field(:large_image, :string)
field(:publish_at, Timex.Ecto.DateTimeWithTimezone)
field(:publish_at, :utc_datetime)
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
field(:online_address, :string)
field(:phone_address, :string)

View File

@@ -458,8 +458,32 @@ defmodule Mobilizon.Events do
[%Tag{}, ...]
"""
def list_tags do
Repo.all(Tag)
def list_tags(page \\ nil, limit \\ nil) do
Repo.all(
Tag
|> paginate(page, limit)
)
end
@doc """
Returns the list of tags for an event.
## Examples
iex> list_tags_for_event(id)
[%Participant{}, ...]
"""
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
)
|> paginate(page, limit)
)
end
@doc """
@@ -543,6 +567,85 @@ defmodule Mobilizon.Events do
Tag.changeset(tag, %{})
end
alias Mobilizon.Events.TagRelation
@doc """
Create a relation between two tags
"""
def create_tag_relation(attrs \\ {}) do
%TagRelation{}
|> TagRelation.changeset(attrs)
|> Repo.insert(conflict_target: [:tag_id, :link_id], on_conflict: [inc: [weight: 1]])
end
@doc """
Remove a tag relation
"""
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
The SQL query looks like this:
```sql
SELECT * FROM tags t
RIGHT JOIN (
SELECT weight, link_id AS id
FROM tag_relations t2
WHERE tag_id = 1
UNION ALL
SELECT tag_id AS id, weight
FROM tag_relations t2
WHERE link_id = 1
) tr
ON t.id = tr.id
ORDER BY tr.weight
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 """

View File

@@ -15,8 +15,8 @@ defmodule Mobilizon.Events.Session do
field(:subtitle, :string)
field(:title, :string)
field(:videos_urls, :string)
field(:begins_on, Timex.Ecto.DateTimeWithTimezone)
field(:ends_on, Timex.Ecto.DateTimeWithTimezone)
field(:begins_on, :utc_datetime)
field(:ends_on, :utc_datetime)
belongs_to(:event, Event)
belongs_to(:track, Track)

View File

@@ -39,10 +39,12 @@ defmodule Mobilizon.Events.Tag do
import Ecto.Changeset
alias Mobilizon.Events.Tag
alias Mobilizon.Events.Tag.TitleSlug
alias Mobilizon.Events.TagRelation
schema "tags" do
field(:title, :string)
field(:slug, TitleSlug.Type)
many_to_many(:related_tags, Tag, join_through: TagRelation)
timestamps()
end

View File

@@ -0,0 +1,41 @@
defmodule Mobilizon.Events.TagRelation do
@moduledoc """
Represents a tag for events
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.Tag
alias Mobilizon.Events.TagRelation
@primary_key false
schema "tag_relations" do
belongs_to(:tag, Tag, primary_key: true)
belongs_to(:link, Tag, primary_key: true)
field(:weight, :integer, default: 1)
end
@doc false
def changeset(%TagRelation{} = tag, attrs) do
changeset =
tag
|> cast(attrs, [:tag_id, :link_id, :weight])
|> validate_required([:tag_id, :link_id])
# Return if tag_id or link_id are not set because it will fail later otherwise
with %Ecto.Changeset{errors: []} <- changeset do
changes = changeset.changes
changeset =
changeset
|> put_change(:tag_id, min(changes.tag_id, changes.link_id))
|> put_change(:link_id, max(changes.tag_id, changes.link_id))
changeset
|> unique_constraint(:tag_id, name: :tag_relations_pkey)
|> check_constraint(:tag_id,
name: :no_self_loops_check,
message: "Can't add a relation on self"
)
end
end
end