Refactoring of Events context

This commit is contained in:
miffigriffy 2019-09-13 01:01:17 +02:00
parent e4a446003d
commit e358dcce77
19 changed files with 528 additions and 353 deletions

View File

@ -1,33 +1,40 @@
import EctoEnum
defenum(Mobilizon.Events.CommentVisibilityEnum, :comment_visibility_type, [
:public,
:unlisted,
:private,
:moderated,
:invite
])
defmodule Mobilizon.Events.Comment do
@moduledoc """
An actor comment (for instance on an event or on a group)
Represents an actor comment (for instance on an event or on a group).
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Comment
alias MobilizonWeb.Router.Helpers, as: Routes
alias MobilizonWeb.Endpoint
alias Mobilizon.Config
alias Mobilizon.Events.{Comment, CommentVisibility, Event}
@type t :: %__MODULE__{
text: String.t(),
url: String.t(),
local: boolean,
visibility: CommentVisibility.t(),
uuid: Ecto.UUID.t(),
actor: Actor.t(),
attributed_to: Actor.t(),
event: Event.t(),
in_reply_to_comment: t,
origin_comment: t
}
@required_attrs [:text, :actor_id, :url]
@optional_attrs [:event_id, :in_reply_to_comment_id, :origin_comment_id, :attributed_to_id]
@attrs @required_attrs ++ @optional_attrs
schema "comments" do
field(:text, :string)
field(:url, :string)
field(:local, :boolean, default: true)
field(:visibility, Mobilizon.Events.CommentVisibilityEnum, default: :public)
field(:visibility, CommentVisibility, default: :public)
field(:uuid, Ecto.UUID)
belongs_to(:actor, Actor, foreign_key: :actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
belongs_to(:event, Event, foreign_key: :event_id)
@ -37,38 +44,27 @@ defmodule Mobilizon.Events.Comment do
timestamps(type: :utc_datetime)
end
@doc false
def changeset(comment, attrs) do
uuid =
if Map.has_key?(attrs, "uuid"),
do: attrs["uuid"],
else: Ecto.UUID.generate()
# TODO : really change me right away
url =
if Map.has_key?(attrs, "url"),
do: attrs["url"],
else: Routes.page_url(Endpoint, :comment, uuid)
comment
|> Ecto.Changeset.cast(attrs, [
:url,
:text,
:actor_id,
:event_id,
:in_reply_to_comment_id,
:origin_comment_id,
:attributed_to_id
])
|> put_change(:uuid, uuid)
|> put_change(:url, url)
|> validate_required([:text, :actor_id, :url])
end
@doc """
Returns the id of the first comment in the conversation
Returns the id of the first comment in the conversation.
"""
@spec get_thread_id(t) :: integer
def get_thread_id(%Comment{id: id, origin_comment_id: origin_comment_id}) do
origin_comment_id || id
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Comment{} = comment, attrs) do
uuid = attrs["uuid"] || Ecto.UUID.generate()
url = attrs["url"] || generate_url(uuid)
comment
|> cast(attrs, @attrs)
|> put_change(:uuid, uuid)
|> put_change(:url, url)
|> validate_required(@required_attrs)
end
@spec generate_url(String.t()) :: String.t()
defp generate_url(uuid), do: "#{Config.instance_hostname()}/comments/#{uuid}"
end

View File

@ -1,43 +1,88 @@
import EctoEnum
defenum(Mobilizon.Events.EventVisibilityEnum, :event_visibility_type, [
:public,
:unlisted,
:restricted,
:private
])
defenum(Mobilizon.Events.JoinOptionsEnum, :event_join_options_type, [
:free,
:restricted,
:invite
])
defenum(Mobilizon.Events.EventStatusEnum, :event_status_type, [
:tentative,
:confirmed,
:cancelled
])
defenum(Mobilizon.Event.EventCategoryEnum, :event_category_type, [
:business,
:conference,
:birthday,
:demonstration,
:meeting
])
defmodule Mobilizon.Events.Event do
@moduledoc """
Represents an event
Represents an event.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{Event, Participant, Tag, Session, Track}
alias Mobilizon.Actors.Actor
alias Mobilizon.Media.Picture
alias Mobilizon.Addresses.Address
alias Mobilizon.Events.{
Event,
EventOptions,
EventStatus,
EventVisibility,
JoinOptions,
Participant,
Tag,
Session,
Track
}
alias Mobilizon.Media.Picture
@type t :: %__MODULE__{
url: String.t(),
local: boolean,
begins_on: DateTime.t(),
slug: String.t(),
description: String.t(),
ends_on: DateTime.t(),
title: String.t(),
status: EventStatus.t(),
visibility: EventVisibility.t(),
join_options: JoinOptions.t(),
publish_at: DateTime.t(),
uuid: Ecto.UUID.t(),
online_address: String.t(),
phone_address: String.t(),
category: String.t(),
options: EventOptions.t(),
organizer_actor: Actor.t(),
attributed_to: Actor.t(),
physical_address: Address.t(),
picture: Picture.t(),
tracks: [Track.t()],
sessions: [Session.t()],
tags: [Tag.t()],
participants: [Actor.t()]
}
@required_attrs [:title, :begins_on, :organizer_actor_id, :url, :uuid]
@optional_attrs [
:slug,
:description,
:ends_on,
:category,
:status,
: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,
:category,
:status,
:visibility,
:publish_at,
:online_address,
:phone_address,
:picture_id,
:physical_address_id
]
@update_attrs @update_required_attrs ++ @update_optional_attrs
schema "events" do
field(:url, :string)
field(:local, :boolean, default: true)
@ -46,96 +91,59 @@ defmodule Mobilizon.Events.Event do
field(:description, :string)
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(:status, EventStatus, default: :confirmed)
field(:visibility, EventVisibility, default: :public)
field(:join_options, JoinOptions, default: :free)
field(:publish_at, :utc_datetime)
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
field(:online_address, :string)
field(:phone_address, :string)
field(:category, :string)
embeds_one(:options, Mobilizon.Events.EventOptions, on_replace: :update)
embeds_one(:options, EventOptions, on_replace: :update)
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
many_to_many(:participants, Actor, join_through: Participant)
has_many(:tracks, Track)
has_many(:sessions, Session)
belongs_to(:physical_address, Address)
belongs_to(:picture, Picture)
has_many(:tracks, Track)
has_many(:sessions, Session)
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
many_to_many(:participants, Actor, join_through: Participant)
timestamps(type: :utc_datetime)
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Event{} = event, attrs) do
event
|> Ecto.Changeset.cast(attrs, [
:title,
:slug,
:description,
:url,
:begins_on,
:ends_on,
:organizer_actor_id,
:category,
:status,
:visibility,
:publish_at,
:online_address,
:phone_address,
:uuid,
:picture_id,
:physical_address_id
])
|> cast(attrs, @attrs)
|> cast_embed(:options)
|> validate_required([
:title,
:begins_on,
:organizer_actor_id,
:url,
:uuid
])
|> validate_required(@required_attrs)
end
@doc false
@spec update_changeset(t, map) :: Ecto.Changeset.t()
def update_changeset(%Event{} = event, attrs) do
event
|> Ecto.Changeset.cast(attrs, [
:title,
:slug,
:description,
:begins_on,
:ends_on,
:category,
:status,
:visibility,
:publish_at,
:online_address,
:phone_address,
:picture_id,
:physical_address_id
])
|> Ecto.Changeset.cast(attrs, @update_attrs)
|> cast_embed(:options)
|> put_tags(attrs)
|> validate_required([
:title,
:begins_on,
:organizer_actor_id,
:url,
:uuid
])
|> validate_required(@update_required_attrs)
end
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags)
defp put_tags(changeset, _), do: changeset
def can_event_be_managed_by(%Event{organizer_actor_id: organizer_actor_id}, actor_id)
@doc """
Checks whether an event can be managed.
"""
@spec can_be_managed_by(t, integer | String.t()) :: boolean
def can_be_managed_by(%Event{organizer_actor_id: organizer_actor_id}, actor_id)
when organizer_actor_id == actor_id do
{:event_can_be_managed, true}
end
def can_event_be_managed_by(_event, _actor) do
{:event_can_be_managed, false}
end
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
end

View File

@ -0,0 +1,19 @@
defmodule Mobilizon.Events.EventOffer do
@moduledoc """
Represents an event offer.
"""
use Ecto.Schema
@type t :: %__MODULE__{
price: float,
price_currency: String.t(),
url: String.t()
}
embedded_schema do
field(:price, :float)
field(:price_currency, :string)
field(:url, :string)
end
end

View File

@ -1,62 +1,32 @@
import EctoEnum
defenum(Mobilizon.Events.CommentModeration, :comment_moderation, [:allow_all, :moderated, :closed])
defmodule Mobilizon.Events.EventOffer do
@moduledoc """
Represents an event offer
"""
use Ecto.Schema
embedded_schema do
field(:price, :float)
field(:price_currency, :string)
field(:url, :string)
end
end
defmodule Mobilizon.Events.EventParticipationCondition do
@moduledoc """
Represents an event participation condition
"""
use Ecto.Schema
embedded_schema do
field(:title, :string)
field(:content, :string)
field(:url, :string)
end
end
defmodule Mobilizon.Events.EventOptions do
@moduledoc """
Represents an event options
Represents an event options.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{
EventOptions,
EventOffer,
EventOptions,
EventParticipationCondition,
CommentModeration
}
@primary_key false
embedded_schema do
field(:maximum_attendee_capacity, :integer)
field(:remaining_attendee_capacity, :integer)
field(:show_remaining_attendee_capacity, :boolean)
embeds_many(:offers, EventOffer)
embeds_many(:participation_condition, EventParticipationCondition)
field(:attendees, {:array, :string})
field(:program, :string)
field(:comment_moderation, CommentModeration)
field(:show_participation_price, :boolean)
end
@type t :: %__MODULE__{
maximum_attendee_capacity: integer,
remaining_attendee_capacity: integer,
show_remaining_attendee_capacity: boolean,
attendees: [String.t()],
program: String.t(),
comment_moderation: CommentModeration.t(),
show_participation_price: boolean,
offers: [EventOffer.t()],
participation_condition: [EventParticipationCondition.t()]
}
def changeset(%EventOptions{} = event_options, attrs) do
event_options
|> Ecto.Changeset.cast(attrs, [
@attrs [
:maximum_attendee_capacity,
:remaining_attendee_capacity,
:show_remaining_attendee_capacity,
@ -64,6 +34,25 @@ defmodule Mobilizon.Events.EventOptions do
:program,
:comment_moderation,
:show_participation_price
])
]
@primary_key false
embedded_schema do
field(:maximum_attendee_capacity, :integer)
field(:remaining_attendee_capacity, :integer)
field(:show_remaining_attendee_capacity, :boolean)
field(:attendees, {:array, :string})
field(:program, :string)
field(:comment_moderation, CommentModeration)
field(:show_participation_price, :boolean)
embeds_many(:offers, EventOffer)
embeds_many(:participation_condition, EventParticipationCondition)
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%EventOptions{} = event_options, attrs) do
cast(event_options, attrs, @attrs)
end
end

View File

@ -0,0 +1,19 @@
defmodule Mobilizon.Events.EventParticipationCondition do
@moduledoc """
Represents an event participation condition.
"""
use Ecto.Schema
@type t :: %__MODULE__{
title: String.t(),
content: String.t(),
url: String.t()
}
embedded_schema do
field(:title, :string)
field(:content, :string)
field(:url, :string)
end
end

View File

@ -4,6 +4,7 @@ defmodule Mobilizon.Events do
"""
import Ecto.Query
import EctoEnum
import Mobilizon.Storage.Ecto
@ -13,13 +14,62 @@ defmodule Mobilizon.Events do
alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.User
def data() do
Dataloader.Ecto.new(Repo, query: &query/2)
end
defenum(EventVisibility, :event_visibility, [
:public,
:unlisted,
:restricted,
:private
])
def query(queryable, _params) do
queryable
end
defenum(JoinOptions, :join_options, [
:free,
:restricted,
:invite
])
defenum(EventStatus, :event_status, [
:tentative,
:confirmed,
:cancelled
])
defenum(EventCategory, :event_category, [
:business,
:conference,
:birthday,
:demonstration,
:meeting
])
defenum(CommentVisibility, :comment_visibility, [
:public,
:unlisted,
:private,
:moderated,
:invite
])
defenum(CommentModeration, :comment_moderation, [
:allow_all,
:moderated,
:closed
])
defenum(ParticipantRole, :participant_role, [
:not_approved,
:participant,
:moderator,
:administrator,
:creator
])
@doc false
@spec data :: Dataloader.Ecto.t()
def data, do: Dataloader.Ecto.new(Repo, query: &query/2)
@doc false
@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 =
@ -537,6 +587,18 @@ defmodule Mobilizon.Events do
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
)
Repo.one(query)
end
@doc """
Get an existing tag or create one
"""
@ -698,6 +760,9 @@ defmodule Mobilizon.Events do
Repo.all(final_query)
end
alias Mobilizon.Events.Participant
@doc """

View File

@ -1,16 +1,30 @@
defmodule Mobilizon.Events.FeedToken do
@moduledoc """
Represents a Token for a Feed of events
Represents a token for a feed of events.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.FeedToken
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.FeedToken
alias Mobilizon.Users.User
@type t :: %__MODULE__{
token: Ecto.UUID.t(),
actor: Actor.t(),
user: User.t()
}
@required_attrs [:token, :user_id]
@optional_attrs [:actor_id]
@attrs @required_attrs ++ @optional_attrs
@primary_key false
schema "feed_tokens" do
field(:token, Ecto.UUID, primary_key: true)
belongs_to(:actor, Actor)
belongs_to(:user, User)
@ -18,9 +32,10 @@ defmodule Mobilizon.Events.FeedToken do
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%FeedToken{} = feed_token, attrs) do
feed_token
|> Ecto.Changeset.cast(attrs, [:token, :actor_id, :user_id])
|> validate_required([:token, :user_id])
|> cast(attrs, @attrs)
|> validate_required(@required_attrs)
end
end

View File

@ -1,73 +1,46 @@
import EctoEnum
defenum(Mobilizon.Events.ParticipantRoleEnum, :participant_role_type, [
:not_approved,
:participant,
:moderator,
:administrator,
:creator
])
defmodule Mobilizon.Events.Participant do
@moduledoc """
Represents a participant, an actor participating to an event
Represents a participant, an actor participating to an event.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{Participant, Event}
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant, ParticipantRole}
@type t :: %__MODULE__{
role: ParticipantRole.t(),
url: String.t(),
event: Event.t(),
actor: Actor.t()
}
@required_attrs [:url, :role, :event_id, :actor_id]
@attrs @required_attrs
@primary_key {:id, :binary_id, autogenerate: true}
schema "participants" do
field(:role, Mobilizon.Events.ParticipantRoleEnum, default: :participant)
field(:role, ParticipantRole, default: :participant)
field(:url, :string)
belongs_to(:event, Event, primary_key: true)
belongs_to(:actor, Actor, primary_key: true)
timestamps()
end
@doc false
def changeset(%Participant{} = participant, attrs) do
participant
|> Ecto.Changeset.cast(attrs, [:url, :role, :event_id, :actor_id])
|> generate_url()
|> validate_required([:url, :role, :event_id, :actor_id])
end
# If there's a blank URL that's because we're doing the first insert
defp generate_url(%Ecto.Changeset{data: %Participant{url: nil}} = changeset) do
case fetch_change(changeset, :url) do
{:ok, _url} -> changeset
:error -> do_generate_url(changeset)
end
end
# Most time just go with the given URL
defp generate_url(%Ecto.Changeset{} = changeset), do: changeset
defp do_generate_url(%Ecto.Changeset{} = changeset) do
uuid = Ecto.UUID.generate()
changeset
|> put_change(
:url,
"#{MobilizonWeb.Endpoint.url()}/join/event/#{uuid}"
)
|> put_change(
:id,
uuid
)
end
@doc """
We check that the actor asking to leave the event is not it's only organizer
We check that the actor asking to leave the event is not it's only organizer.
We start by fetching the list of organizers and if there's only one of them
and that it's the actor requesting leaving the event we return true
and that it's the actor requesting leaving the event we return true.
"""
@spec check_that_participant_is_not_only_organizer(integer(), integer()) :: boolean()
def check_that_participant_is_not_only_organizer(event_id, actor_id) do
case Mobilizon.Events.list_organizers_participants_for_event(event_id) do
@spec is_not_only_organizer(integer | String.t(), integer | String.t()) :: boolean
def is_not_only_organizer(event_id, actor_id) do
case Events.list_organizers_participants_for_event(event_id) do
[%Participant{actor: %Actor{id: participant_actor_id}}] ->
participant_actor_id == actor_id
@ -75,4 +48,39 @@ defmodule Mobilizon.Events.Participant do
false
end
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Participant{} = participant, attrs) do
participant
|> cast(attrs, @attrs)
|> ensure_url()
|> validate_required(@required_attrs)
end
# If there's a blank URL that's because we're doing the first insert
@spec ensure_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp ensure_url(%Ecto.Changeset{data: %Participant{url: nil}} = changeset) do
case fetch_change(changeset, :url) do
{:ok, _url} ->
changeset
:error ->
update_url(changeset)
end
end
defp ensure_url(%Ecto.Changeset{} = changeset), do: changeset
defp update_url(%Ecto.Changeset{} = changeset) do
uuid = Ecto.UUID.generate()
url = generate_url(uuid)
changeset
|> put_change(:id, uuid)
|> put_change(:url, url)
end
@spec generate_url(String.t()) :: String.t()
defp generate_url(uuid), do: "#{Config.instance_hostname()}/join/event/#{uuid}"
end

View File

@ -1,10 +1,41 @@
defmodule Mobilizon.Events.Session do
@moduledoc """
Represents a session for an event (such as a talk at a conference)
Represents a session for an event (such as a talk at a conference).
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{Session, Event, Track}
alias Mobilizon.Events.{Event, Session, Track}
@type t :: %__MODULE__{
audios_urls: String.t(),
language: String.t(),
long_abstract: String.t(),
short_abstract: String.t(),
slides_url: String.t(),
subtitle: String.t(),
title: String.t(),
videos_urls: String.t(),
begins_on: DateTime.t(),
ends_on: DateTime.t(),
event: Event.t(),
track: Track.t()
}
@required_attrs [
:title,
:subtitle,
:short_abstract,
:long_abstract,
:language,
:slides_url,
:videos_urls,
:audios_urls
]
@optional_attrs [:event_id, :track_id]
@attrs @required_attrs ++ @optional_attrs
schema "sessions" do
field(:audios_urls, :string)
@ -17,6 +48,7 @@ defmodule Mobilizon.Events.Session do
field(:videos_urls, :string)
field(:begins_on, :utc_datetime)
field(:ends_on, :utc_datetime)
belongs_to(:event, Event)
belongs_to(:track, Track)
@ -24,29 +56,10 @@ defmodule Mobilizon.Events.Session do
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Session{} = session, attrs) do
session
|> cast(attrs, [
:title,
:subtitle,
:short_abstract,
:long_abstract,
:language,
:slides_url,
:videos_urls,
:audios_urls,
:event_id,
:track_id
])
|> validate_required([
:title,
:subtitle,
:short_abstract,
:long_abstract,
:language,
:slides_url,
:videos_urls,
:audios_urls
])
|> cast(attrs, @attrs)
|> validate_required(@required_attrs)
end
end

View File

@ -1,40 +1,40 @@
defmodule Mobilizon.Events.Tag do
@moduledoc """
Represents a tag for events
Represents a tag for events.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.Tag
alias Mobilizon.Events.{Tag, TagRelation}
alias Mobilizon.Events.Tag.TitleSlug
alias Mobilizon.Events.TagRelation
@type t :: %__MODULE__{
title: String.t(),
slug: TitleSlug.Type.t(),
related_tags: [Tag.t()]
}
@required_attrs [:title, :slug]
@attrs @required_attrs
schema "tags" do
field(:title, :string)
field(:slug, TitleSlug.Type)
many_to_many(:related_tags, Tag, join_through: TagRelation)
timestamps()
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Tag{} = tag, attrs) do
tag
|> cast(attrs, [:title])
|> cast(attrs, @attrs)
|> TitleSlug.maybe_generate_slug()
|> validate_required([:title, :slug])
|> validate_required(@required_attrs)
|> TitleSlug.unique_constraint()
end
def increment_slug(slug) do
case List.pop_at(String.split(slug, "-"), -1) do
{nil, _} ->
slug
{suffix, slug_parts} ->
case Integer.parse(suffix) do
{id, _} -> Enum.join(slug_parts, "-") <> "-" <> Integer.to_string(id + 1)
:error -> slug <> "-1"
end
end
end
end

View File

@ -1,33 +1,53 @@
defmodule Mobilizon.Events.Tag.TitleSlug do
@moduledoc """
Generates slugs for tags
Generates slugs for tags.
"""
alias Mobilizon.Events.Tag
import Ecto.Query
alias Mobilizon.Storage.Repo
use EctoAutoslugField.Slug, from: :title, to: :slug
alias Mobilizon.Events
@slug_separator "-"
@doc """
Builds a slug.
"""
@spec build_slug(keyword, Ecto.Changeset.t()) :: String.t()
def build_slug(sources, changeset) do
slug = super(sources, changeset)
build_unique_slug(slug, changeset)
end
@spec build_unique_slug(String.t(), Ecto.Changeset.t()) :: String.t()
defp build_unique_slug(slug, changeset) do
query =
from(
t in Tag,
where: t.slug == ^slug
)
case Repo.one(query) do
case Events.get_tag_by_slug(slug) do
nil ->
slug
_tag ->
slug
|> Tag.increment_slug()
|> increment_slug()
|> build_unique_slug(changeset)
end
end
@spec increment_slug(String.t()) :: String.t()
defp increment_slug(slug) do
case List.pop_at(String.split(slug, @slug_separator), -1) do
{nil, _} ->
slug
{suffix, slug_parts} ->
case Integer.parse(suffix) do
{id, _} ->
Enum.join(slug_parts, @slug_separator) <>
@slug_separator <>
Integer.to_string(id + 1)
:error ->
"#{slug}#{@slug_separator}1"
end
end
end
end

View File

@ -1,36 +1,43 @@
defmodule Mobilizon.Events.TagRelation do
@moduledoc """
Represents a tag for events
Represents a tag relation.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.Tag
alias Mobilizon.Events.TagRelation
alias Mobilizon.Events.{Tag, TagRelation}
@type t :: %__MODULE__{
weight: integer,
tag: Tag.t(),
link: Tag.t()
}
@required_attrs [:tag_id, :link_id]
@optional_attrs [:weight]
@attrs @required_attrs ++ @optional_attrs
@primary_key false
schema "tag_relations" do
field(:weight, :integer, default: 1)
belongs_to(:tag, Tag, primary_key: true)
belongs_to(:link, Tag, primary_key: true)
field(:weight, :integer, default: 1)
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
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 =
with %Ecto.Changeset{errors: [], changes: changes} = changeset <-
tag
|> cast(attrs, @attrs)
|> validate_required(@required_attrs) do
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,

View File

@ -1,15 +1,31 @@
defmodule Mobilizon.Events.Track do
@moduledoc """
Represents a track for an event (such as a theme) having multiple sessions
Represents a track for an event (such as a theme) having multiple sessions.
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{Track, Event, Session}
@type t :: %__MODULE__{
color: String.t(),
description: String.t(),
name: String.t(),
event: Event.t(),
sessions: [Session.t()]
}
@required_attrs [:name, :description, :color]
@optional_attrs [:event_id]
@attrs @required_attrs ++ @optional_attrs
schema "tracks" do
field(:color, :string)
field(:description, :string)
field(:name, :string)
belongs_to(:event, Event)
has_many(:sessions, Session)
@ -17,9 +33,10 @@ defmodule Mobilizon.Events.Track do
end
@doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Track{} = track, attrs) do
track
|> cast(attrs, [:name, :description, :color, :event_id])
|> validate_required([:name, :description, :color])
|> cast(attrs, @attrs)
|> validate_required(@required_attrs)
end
end

View File

@ -274,7 +274,7 @@ defmodule MobilizonWeb.Resolvers.Event do
) do
with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
{:event_can_be_managed, true} <- Event.can_event_be_managed_by(event, actor_id),
{:event_can_be_managed, true} <- Event.can_be_managed_by(event, actor_id),
event <- Mobilizon.Events.delete_event!(event) do
{:ok, %{id: event.id}}
else

View File

@ -461,8 +461,7 @@ defmodule Mobilizon.Service.ActivityPub do
local
) do
with {:only_organizer, false} <-
{:only_organizer,
Participant.check_that_participant_is_not_only_organizer(event_id, actor_id)},
{:only_organizer, Participant.is_not_only_organizer(event_id, actor_id)},
{:ok, %Participant{} = participant} <-
Mobilizon.Events.get_participant(event_id, actor_id),
{:ok, %Participant{} = participant} <- Mobilizon.Events.delete_participant(participant),

12
mix.exs
View File

@ -205,12 +205,12 @@ defmodule Mobilizon.Mixfile do
Mobilizon.Events.Tag,
Mobilizon.Events.TagRelations,
Mobilizon.Events.Track,
Mobilizon.Event.EventCategoryEnum,
Mobilizon.Events.CommentVisibilityEnum,
Mobilizon.Events.EventStatusEnum,
Mobilizon.Events.EventVisibilityEnum,
Mobilizon.Events.JoinOptionsEnum,
Mobilizon.Events.ParticipantRoleEnum,
Mobilizon.Event.EventCategory,
Mobilizon.Events.CommentVisibility,
Mobilizon.Events.EventStatus,
Mobilizon.Events.EventVisibility,
Mobilizon.Events.JoinOptions,
Mobilizon.Events.ParticipantRole,
Mobilizon.Events.Tag.TitleSlug,
Mobilizon.Events.Tag.TitleSlug.Type,
Mobilizon.Events.TagRelation,

View File

@ -2,20 +2,20 @@ defmodule Mobilizon.Repo.Migrations.FixEventVisibility do
use Ecto.Migration
def up do
Mobilizon.Events.EventVisibilityEnum.create_type()
Mobilizon.Events.EventStatusEnum.create_type()
Mobilizon.Events.CommentVisibilityEnum.create_type()
Mobilizon.Events.EventVisibility.create_type()
Mobilizon.Events.EventStatus.create_type()
Mobilizon.Events.CommentVisibility.create_type()
alter table(:events) do
remove(:public)
remove(:status)
remove(:state)
add(:visibility, Mobilizon.Events.EventVisibilityEnum.type())
add(:status, Mobilizon.Events.EventStatusEnum.type())
add(:visibility, Mobilizon.Events.EventVisibility.type())
add(:status, Mobilizon.Events.EventStatus.type())
end
alter table(:comments) do
add(:visibility, Mobilizon.Events.CommentVisibilityEnum.type())
add(:visibility, Mobilizon.Events.CommentVisibility.type())
end
end
@ -32,8 +32,8 @@ defmodule Mobilizon.Repo.Migrations.FixEventVisibility do
remove(:visibility)
end
Mobilizon.Events.EventVisibilityEnum.drop_type()
Mobilizon.Events.EventStatusEnum.drop_type()
Mobilizon.Events.CommentVisibilityEnum.drop_type()
Mobilizon.Events.EventVisibility.drop_type()
Mobilizon.Events.EventStatus.drop_type()
Mobilizon.Events.CommentVisibility.drop_type()
end
end

View File

@ -1,32 +1,32 @@
defmodule Mobilizon.Repo.Migrations.SplitEventVisibilityAndJoinOptions do
use Ecto.Migration
alias Mobilizon.Events.EventVisibilityEnum
alias Mobilizon.Events.JoinOptionsEnum
alias Mobilizon.Events.EventVisibility
alias Mobilizon.Events.JoinOptions
@doc """
EventVisibilityEnum has dropped some possible values, so we need to recreate it
EventVisibility has dropped some possible values, so we need to recreate it
Visibility allowed nullable values previously
"""
def up do
execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text")
EventVisibilityEnum.drop_type()
EventVisibilityEnum.create_type()
EventVisibility.drop_type()
EventVisibility.create_type()
execute(
"ALTER TABLE events ALTER COLUMN visibility TYPE event_visibility_type USING visibility::event_visibility_type"
"ALTER TABLE events ALTER COLUMN visibility TYPE event_visibility_type USING visibility::event_visibility"
)
JoinOptionsEnum.create_type()
JoinOptions.create_type()
alter table(:events) do
add(:join_options, JoinOptionsEnum.type(), null: false, default: "free")
add(:join_options, JoinOptions.type(), null: false, default: "free")
end
execute("UPDATE events SET visibility = 'public' WHERE visibility IS NULL")
alter table(:events) do
modify(:visibility, EventVisibilityEnum.type(), null: false, default: "public")
modify(:visibility, EventVisibility.type(), null: false, default: "public")
end
end
@ -35,14 +35,14 @@ defmodule Mobilizon.Repo.Migrations.SplitEventVisibilityAndJoinOptions do
remove(:join_options)
end
JoinOptionsEnum.drop_type()
JoinOptions.drop_type()
execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text")
EventVisibilityEnum.drop_type()
EventVisibilityEnum.create_type()
EventVisibility.drop_type()
EventVisibility.create_type()
execute(
"ALTER TABLE events ALTER COLUMN visibility TYPE event_visibility_type USING visibility::event_visibility_type"
"ALTER TABLE events ALTER COLUMN visibility TYPE event_visibility_type USING visibility::event_visibility"
)
end
end

View File

@ -1,12 +1,12 @@
defmodule Mobilizon.Repo.Migrations.MoveParticipantRoleToEnum do
use Ecto.Migration
alias Mobilizon.Events.ParticipantRoleEnum
alias Mobilizon.Events.ParticipantRole
def up do
ParticipantRoleEnum.create_type()
ParticipantRole.create_type()
alter table(:participants) do
add(:role_tmp, ParticipantRoleEnum.type(), default: "participant")
add(:role_tmp, ParticipantRole.type(), default: "participant")
end
execute("UPDATE participants set role_tmp = 'not_approved' where role = 0")
@ -37,7 +37,7 @@ defmodule Mobilizon.Repo.Migrations.MoveParticipantRoleToEnum do
remove(:role)
end
ParticipantRoleEnum.drop_type()
ParticipantRole.drop_type()
rename(table(:participants), :role_tmp, to: :role)
end