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 defmodule Mobilizon.Events.Comment do
@moduledoc """ @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 use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Comment alias Mobilizon.Config
alias MobilizonWeb.Router.Helpers, as: Routes alias Mobilizon.Events.{Comment, CommentVisibility, Event}
alias MobilizonWeb.Endpoint
@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 schema "comments" do
field(:text, :string) field(:text, :string)
field(:url, :string) field(:url, :string)
field(:local, :boolean, default: true) field(:local, :boolean, default: true)
field(:visibility, Mobilizon.Events.CommentVisibilityEnum, default: :public) field(:visibility, CommentVisibility, default: :public)
field(:uuid, Ecto.UUID) field(:uuid, Ecto.UUID)
belongs_to(:actor, Actor, foreign_key: :actor_id) belongs_to(:actor, Actor, foreign_key: :actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id) belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
belongs_to(:event, Event, foreign_key: :event_id) belongs_to(:event, Event, foreign_key: :event_id)
@ -37,38 +44,27 @@ defmodule Mobilizon.Events.Comment do
timestamps(type: :utc_datetime) timestamps(type: :utc_datetime)
end 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 """ @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 def get_thread_id(%Comment{id: id, origin_comment_id: origin_comment_id}) do
origin_comment_id || id origin_comment_id || id
end 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 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 defmodule Mobilizon.Events.Event do
@moduledoc """ @moduledoc """
Represents an event Represents an event.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.{Event, Participant, Tag, Session, Track}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Media.Picture
alias Mobilizon.Addresses.Address 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 schema "events" do
field(:url, :string) field(:url, :string)
field(:local, :boolean, default: true) field(:local, :boolean, default: true)
@ -46,96 +91,59 @@ defmodule Mobilizon.Events.Event do
field(:description, :string) field(:description, :string)
field(:ends_on, :utc_datetime) field(:ends_on, :utc_datetime)
field(:title, :string) field(:title, :string)
field(:status, Mobilizon.Events.EventStatusEnum, default: :confirmed) field(:status, EventStatus, default: :confirmed)
field(:visibility, Mobilizon.Events.EventVisibilityEnum, default: :public) field(:visibility, EventVisibility, default: :public)
field(:join_options, Mobilizon.Events.JoinOptionsEnum, default: :free) field(:join_options, JoinOptions, default: :free)
field(:publish_at, :utc_datetime) field(:publish_at, :utc_datetime)
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate()) field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
field(:online_address, :string) field(:online_address, :string)
field(:phone_address, :string) field(:phone_address, :string)
field(:category, :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(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_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(:physical_address, Address)
belongs_to(:picture, Picture) 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) timestamps(type: :utc_datetime)
end end
@doc false @doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Event{} = event, attrs) do def changeset(%Event{} = event, attrs) do
event event
|> Ecto.Changeset.cast(attrs, [ |> cast(attrs, @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_embed(:options) |> cast_embed(:options)
|> validate_required([ |> validate_required(@required_attrs)
:title,
:begins_on,
:organizer_actor_id,
:url,
:uuid
])
end end
@doc false @doc false
@spec update_changeset(t, map) :: Ecto.Changeset.t()
def update_changeset(%Event{} = event, attrs) do def update_changeset(%Event{} = event, attrs) do
event event
|> Ecto.Changeset.cast(attrs, [ |> Ecto.Changeset.cast(attrs, @update_attrs)
:title,
:slug,
:description,
:begins_on,
:ends_on,
:category,
:status,
:visibility,
:publish_at,
:online_address,
:phone_address,
:picture_id,
:physical_address_id
])
|> cast_embed(:options) |> cast_embed(:options)
|> put_tags(attrs) |> put_tags(attrs)
|> validate_required([ |> validate_required(@update_required_attrs)
:title,
:begins_on,
:organizer_actor_id,
:url,
:uuid
])
end end
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags) @doc """
defp put_tags(changeset, _), do: changeset Checks whether an event can be managed.
"""
def can_event_be_managed_by(%Event{organizer_actor_id: organizer_actor_id}, actor_id) @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 when organizer_actor_id == actor_id do
{:event_can_be_managed, true} {:event_can_be_managed, true}
end end
def can_event_be_managed_by(_event, _actor) do def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
{:event_can_be_managed, false}
end @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 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 defmodule Mobilizon.Events.EventOptions do
@moduledoc """ @moduledoc """
Represents an event options Represents an event options.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.{ alias Mobilizon.Events.{
EventOptions,
EventOffer, EventOffer,
EventOptions,
EventParticipationCondition, EventParticipationCondition,
CommentModeration CommentModeration
} }
@primary_key false @type t :: %__MODULE__{
embedded_schema do maximum_attendee_capacity: integer,
field(:maximum_attendee_capacity, :integer) remaining_attendee_capacity: integer,
field(:remaining_attendee_capacity, :integer) show_remaining_attendee_capacity: boolean,
field(:show_remaining_attendee_capacity, :boolean) attendees: [String.t()],
embeds_many(:offers, EventOffer) program: String.t(),
embeds_many(:participation_condition, EventParticipationCondition) comment_moderation: CommentModeration.t(),
field(:attendees, {:array, :string}) show_participation_price: boolean,
field(:program, :string) offers: [EventOffer.t()],
field(:comment_moderation, CommentModeration) participation_condition: [EventParticipationCondition.t()]
field(:show_participation_price, :boolean) }
end
def changeset(%EventOptions{} = event_options, attrs) do @attrs [
event_options
|> Ecto.Changeset.cast(attrs, [
:maximum_attendee_capacity, :maximum_attendee_capacity,
:remaining_attendee_capacity, :remaining_attendee_capacity,
:show_remaining_attendee_capacity, :show_remaining_attendee_capacity,
@ -64,6 +34,25 @@ defmodule Mobilizon.Events.EventOptions do
:program, :program,
:comment_moderation, :comment_moderation,
:show_participation_price :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
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 Ecto.Query
import EctoEnum
import Mobilizon.Storage.Ecto import Mobilizon.Storage.Ecto
@ -13,13 +14,62 @@ defmodule Mobilizon.Events do
alias Mobilizon.Storage.{Page, Repo} alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.User alias Mobilizon.Users.User
def data() do defenum(EventVisibility, :event_visibility, [
Dataloader.Ecto.new(Repo, query: &query/2) :public,
end :unlisted,
:restricted,
:private
])
def query(queryable, _params) do defenum(JoinOptions, :join_options, [
queryable :free,
end :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 def get_public_events_for_actor(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
query = query =
@ -537,6 +587,18 @@ defmodule Mobilizon.Events do
def get_tag(id), do: Repo.get(Tag, id) 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 """ @doc """
Get an existing tag or create one Get an existing tag or create one
""" """
@ -698,6 +760,9 @@ defmodule Mobilizon.Events do
Repo.all(final_query) Repo.all(final_query)
end end
alias Mobilizon.Events.Participant alias Mobilizon.Events.Participant
@doc """ @doc """

View file

@ -1,16 +1,30 @@
defmodule Mobilizon.Events.FeedToken do defmodule Mobilizon.Events.FeedToken do
@moduledoc """ @moduledoc """
Represents a Token for a Feed of events Represents a token for a feed of events.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.FeedToken
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events.FeedToken
alias Mobilizon.Users.User 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 @primary_key false
schema "feed_tokens" do schema "feed_tokens" do
field(:token, Ecto.UUID, primary_key: true) field(:token, Ecto.UUID, primary_key: true)
belongs_to(:actor, Actor) belongs_to(:actor, Actor)
belongs_to(:user, User) belongs_to(:user, User)
@ -18,9 +32,10 @@ defmodule Mobilizon.Events.FeedToken do
end end
@doc false @doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%FeedToken{} = feed_token, attrs) do def changeset(%FeedToken{} = feed_token, attrs) do
feed_token feed_token
|> Ecto.Changeset.cast(attrs, [:token, :actor_id, :user_id]) |> cast(attrs, @attrs)
|> validate_required([:token, :user_id]) |> validate_required(@required_attrs)
end end
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 defmodule Mobilizon.Events.Participant do
@moduledoc """ @moduledoc """
Represents a participant, an actor participating to an event Represents a participant, an actor participating to an event.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.{Participant, Event}
alias Mobilizon.Actors.Actor 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} @primary_key {:id, :binary_id, autogenerate: true}
schema "participants" do schema "participants" do
field(:role, Mobilizon.Events.ParticipantRoleEnum, default: :participant) field(:role, ParticipantRole, default: :participant)
field(:url, :string) field(:url, :string)
belongs_to(:event, Event, primary_key: true) belongs_to(:event, Event, primary_key: true)
belongs_to(:actor, Actor, primary_key: true) belongs_to(:actor, Actor, primary_key: true)
timestamps() timestamps()
end 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 """ @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 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() @spec is_not_only_organizer(integer | String.t(), integer | String.t()) :: boolean
def check_that_participant_is_not_only_organizer(event_id, actor_id) do def is_not_only_organizer(event_id, actor_id) do
case Mobilizon.Events.list_organizers_participants_for_event(event_id) do case Events.list_organizers_participants_for_event(event_id) do
[%Participant{actor: %Actor{id: participant_actor_id}}] -> [%Participant{actor: %Actor{id: participant_actor_id}}] ->
participant_actor_id == actor_id participant_actor_id == actor_id
@ -75,4 +48,39 @@ defmodule Mobilizon.Events.Participant do
false false
end end
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 end

View file

@ -1,10 +1,41 @@
defmodule Mobilizon.Events.Session do defmodule Mobilizon.Events.Session do
@moduledoc """ @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 use Ecto.Schema
import Ecto.Changeset 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 schema "sessions" do
field(:audios_urls, :string) field(:audios_urls, :string)
@ -17,6 +48,7 @@ defmodule Mobilizon.Events.Session do
field(:videos_urls, :string) field(:videos_urls, :string)
field(:begins_on, :utc_datetime) field(:begins_on, :utc_datetime)
field(:ends_on, :utc_datetime) field(:ends_on, :utc_datetime)
belongs_to(:event, Event) belongs_to(:event, Event)
belongs_to(:track, Track) belongs_to(:track, Track)
@ -24,29 +56,10 @@ defmodule Mobilizon.Events.Session do
end end
@doc false @doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Session{} = session, attrs) do def changeset(%Session{} = session, attrs) do
session session
|> cast(attrs, [ |> cast(attrs, @attrs)
:title, |> validate_required(@required_attrs)
: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
])
end end
end end

View file

@ -1,40 +1,40 @@
defmodule Mobilizon.Events.Tag do defmodule Mobilizon.Events.Tag do
@moduledoc """ @moduledoc """
Represents a tag for events Represents a tag for events.
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.Tag
alias Mobilizon.Events.{Tag, TagRelation}
alias Mobilizon.Events.Tag.TitleSlug 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 schema "tags" do
field(:title, :string) field(:title, :string)
field(:slug, TitleSlug.Type) field(:slug, TitleSlug.Type)
many_to_many(:related_tags, Tag, join_through: TagRelation) many_to_many(:related_tags, Tag, join_through: TagRelation)
timestamps() timestamps()
end end
@doc false @doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Tag{} = tag, attrs) do def changeset(%Tag{} = tag, attrs) do
tag tag
|> cast(attrs, [:title]) |> cast(attrs, @attrs)
|> TitleSlug.maybe_generate_slug() |> TitleSlug.maybe_generate_slug()
|> validate_required([:title, :slug]) |> validate_required(@required_attrs)
|> TitleSlug.unique_constraint() |> TitleSlug.unique_constraint()
end 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 end

View file

@ -1,33 +1,53 @@
defmodule Mobilizon.Events.Tag.TitleSlug do defmodule Mobilizon.Events.Tag.TitleSlug do
@moduledoc """ @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 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 def build_slug(sources, changeset) do
slug = super(sources, changeset) slug = super(sources, changeset)
build_unique_slug(slug, changeset) build_unique_slug(slug, changeset)
end end
@spec build_unique_slug(String.t(), Ecto.Changeset.t()) :: String.t()
defp build_unique_slug(slug, changeset) do defp build_unique_slug(slug, changeset) do
query = case Events.get_tag_by_slug(slug) do
from(
t in Tag,
where: t.slug == ^slug
)
case Repo.one(query) do
nil -> nil ->
slug slug
_tag -> _tag ->
slug slug
|> Tag.increment_slug() |> increment_slug()
|> build_unique_slug(changeset) |> build_unique_slug(changeset)
end end
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 end

View file

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

View file

@ -1,15 +1,31 @@
defmodule Mobilizon.Events.Track do defmodule Mobilizon.Events.Track do
@moduledoc """ @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 use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.{Track, Event, Session} 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 schema "tracks" do
field(:color, :string) field(:color, :string)
field(:description, :string) field(:description, :string)
field(:name, :string) field(:name, :string)
belongs_to(:event, Event) belongs_to(:event, Event)
has_many(:sessions, Session) has_many(:sessions, Session)
@ -17,9 +33,10 @@ defmodule Mobilizon.Events.Track do
end end
@doc false @doc false
@spec changeset(t, map) :: Ecto.Changeset.t()
def changeset(%Track{} = track, attrs) do def changeset(%Track{} = track, attrs) do
track track
|> cast(attrs, [:name, :description, :color, :event_id]) |> cast(attrs, @attrs)
|> validate_required([:name, :description, :color]) |> validate_required(@required_attrs)
end end
end end

View file

@ -274,7 +274,7 @@ defmodule MobilizonWeb.Resolvers.Event do
) do ) do
with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id), with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_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 event <- Mobilizon.Events.delete_event!(event) do
{:ok, %{id: event.id}} {:ok, %{id: event.id}}
else else

View file

@ -461,8 +461,7 @@ defmodule Mobilizon.Service.ActivityPub do
local local
) do ) do
with {:only_organizer, false} <- with {:only_organizer, false} <-
{:only_organizer, {:only_organizer, Participant.is_not_only_organizer(event_id, actor_id)},
Participant.check_that_participant_is_not_only_organizer(event_id, actor_id)},
{:ok, %Participant{} = participant} <- {:ok, %Participant{} = participant} <-
Mobilizon.Events.get_participant(event_id, actor_id), Mobilizon.Events.get_participant(event_id, actor_id),
{:ok, %Participant{} = participant} <- Mobilizon.Events.delete_participant(participant), {: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.Tag,
Mobilizon.Events.TagRelations, Mobilizon.Events.TagRelations,
Mobilizon.Events.Track, Mobilizon.Events.Track,
Mobilizon.Event.EventCategoryEnum, Mobilizon.Event.EventCategory,
Mobilizon.Events.CommentVisibilityEnum, Mobilizon.Events.CommentVisibility,
Mobilizon.Events.EventStatusEnum, Mobilizon.Events.EventStatus,
Mobilizon.Events.EventVisibilityEnum, Mobilizon.Events.EventVisibility,
Mobilizon.Events.JoinOptionsEnum, Mobilizon.Events.JoinOptions,
Mobilizon.Events.ParticipantRoleEnum, Mobilizon.Events.ParticipantRole,
Mobilizon.Events.Tag.TitleSlug, Mobilizon.Events.Tag.TitleSlug,
Mobilizon.Events.Tag.TitleSlug.Type, Mobilizon.Events.Tag.TitleSlug.Type,
Mobilizon.Events.TagRelation, Mobilizon.Events.TagRelation,

View file

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

View file

@ -1,32 +1,32 @@
defmodule Mobilizon.Repo.Migrations.SplitEventVisibilityAndJoinOptions do defmodule Mobilizon.Repo.Migrations.SplitEventVisibilityAndJoinOptions do
use Ecto.Migration use Ecto.Migration
alias Mobilizon.Events.EventVisibilityEnum alias Mobilizon.Events.EventVisibility
alias Mobilizon.Events.JoinOptionsEnum alias Mobilizon.Events.JoinOptions
@doc """ @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 Visibility allowed nullable values previously
""" """
def up do def up do
execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text") execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text")
EventVisibilityEnum.drop_type() EventVisibility.drop_type()
EventVisibilityEnum.create_type() EventVisibility.create_type()
execute( 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 alter table(:events) do
add(:join_options, JoinOptionsEnum.type(), null: false, default: "free") add(:join_options, JoinOptions.type(), null: false, default: "free")
end end
execute("UPDATE events SET visibility = 'public' WHERE visibility IS NULL") execute("UPDATE events SET visibility = 'public' WHERE visibility IS NULL")
alter table(:events) do alter table(:events) do
modify(:visibility, EventVisibilityEnum.type(), null: false, default: "public") modify(:visibility, EventVisibility.type(), null: false, default: "public")
end end
end end
@ -35,14 +35,14 @@ defmodule Mobilizon.Repo.Migrations.SplitEventVisibilityAndJoinOptions do
remove(:join_options) remove(:join_options)
end end
JoinOptionsEnum.drop_type() JoinOptions.drop_type()
execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text") execute("ALTER TABLE events ALTER COLUMN visibility TYPE VARCHAR USING visibility::text")
EventVisibilityEnum.drop_type() EventVisibility.drop_type()
EventVisibilityEnum.create_type() EventVisibility.create_type()
execute( 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
end end

View file

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