diff --git a/lib/mobilizon/events/comment.ex b/lib/mobilizon/events/comment.ex index 5b2c038ee..4caf24219 100644 --- a/lib/mobilizon/events/comment.ex +++ b/lib/mobilizon/events/comment.ex @@ -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 diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index 52d1c765d..fa5f5ad61 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -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 diff --git a/lib/mobilizon/events/event_offer.ex b/lib/mobilizon/events/event_offer.ex new file mode 100644 index 000000000..c30f67b3e --- /dev/null +++ b/lib/mobilizon/events/event_offer.ex @@ -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 diff --git a/lib/mobilizon/events/event_options.ex b/lib/mobilizon/events/event_options.ex index c6a02fafd..4764dde97 100644 --- a/lib/mobilizon/events/event_options.ex +++ b/lib/mobilizon/events/event_options.ex @@ -1,69 +1,58 @@ -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 } + @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()] + } + + @attrs [ + :maximum_attendee_capacity, + :remaining_attendee_capacity, + :show_remaining_attendee_capacity, + :attendees, + :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) - 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) + + 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 - event_options - |> Ecto.Changeset.cast(attrs, [ - :maximum_attendee_capacity, - :remaining_attendee_capacity, - :show_remaining_attendee_capacity, - :attendees, - :program, - :comment_moderation, - :show_participation_price - ]) + cast(event_options, attrs, @attrs) end end diff --git a/lib/mobilizon/events/event_participation_condition.ex b/lib/mobilizon/events/event_participation_condition.ex new file mode 100644 index 000000000..9fd4bff1d --- /dev/null +++ b/lib/mobilizon/events/event_participation_condition.ex @@ -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 diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index a6256b294..29f483aae 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -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 """ diff --git a/lib/mobilizon/events/feed_token.ex b/lib/mobilizon/events/feed_token.ex index f4b55e477..25d12a980 100644 --- a/lib/mobilizon/events/feed_token.ex +++ b/lib/mobilizon/events/feed_token.ex @@ -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 diff --git a/lib/mobilizon/events/participant.ex b/lib/mobilizon/events/participant.ex index 144dc7b38..5fc33bdf1 100644 --- a/lib/mobilizon/events/participant.ex +++ b/lib/mobilizon/events/participant.ex @@ -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 diff --git a/lib/mobilizon/events/session.ex b/lib/mobilizon/events/session.ex index 5f03ec00e..5d1510425 100644 --- a/lib/mobilizon/events/session.ex +++ b/lib/mobilizon/events/session.ex @@ -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 diff --git a/lib/mobilizon/events/tag.ex b/lib/mobilizon/events/tag.ex index 5d38dec3e..a342f1c68 100644 --- a/lib/mobilizon/events/tag.ex +++ b/lib/mobilizon/events/tag.ex @@ -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 diff --git a/lib/mobilizon/events/tag/title_slug.ex b/lib/mobilizon/events/tag/title_slug.ex index 9c64841d0..5e3fcf3b1 100644 --- a/lib/mobilizon/events/tag/title_slug.ex +++ b/lib/mobilizon/events/tag/title_slug.ex @@ -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 diff --git a/lib/mobilizon/events/tag_relations.ex b/lib/mobilizon/events/tag_relations.ex index 65618cb19..7122a6876 100644 --- a/lib/mobilizon/events/tag_relations.ex +++ b/lib/mobilizon/events/tag_relations.ex @@ -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 = - changeset - |> put_change(:tag_id, min(changes.tag_id, changes.link_id)) - |> put_change(:link_id, max(changes.tag_id, changes.link_id)) - + 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)) |> unique_constraint(:tag_id, name: :tag_relations_pkey) |> check_constraint(:tag_id, name: :no_self_loops_check, diff --git a/lib/mobilizon/events/track.ex b/lib/mobilizon/events/track.ex index a3dab3709..a006fd2d6 100644 --- a/lib/mobilizon/events/track.ex +++ b/lib/mobilizon/events/track.ex @@ -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 diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index fee135bf5..339bbdcba 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -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 diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 507768dbb..7818cbfd8 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -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), diff --git a/mix.exs b/mix.exs index aa18d53c0..67ad05c5f 100644 --- a/mix.exs +++ b/mix.exs @@ -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, diff --git a/priv/repo/migrations/20190103150805_fix_event_visibility.exs b/priv/repo/migrations/20190103150805_fix_event_visibility.exs index 0d1fd294b..e5eecdbf9 100644 --- a/priv/repo/migrations/20190103150805_fix_event_visibility.exs +++ b/priv/repo/migrations/20190103150805_fix_event_visibility.exs @@ -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 diff --git a/priv/repo/migrations/20190130151607_split_event_visibility_and_join_options.exs b/priv/repo/migrations/20190130151607_split_event_visibility_and_join_options.exs index a991ef11b..ecf464a9c 100644 --- a/priv/repo/migrations/20190130151607_split_event_visibility_and_join_options.exs +++ b/priv/repo/migrations/20190130151607_split_event_visibility_and_join_options.exs @@ -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 diff --git a/priv/repo/migrations/20190207134142_move_participant_role_to_enum.exs b/priv/repo/migrations/20190207134142_move_participant_role_to_enum.exs index 68e8a9713..065ff0991 100644 --- a/priv/repo/migrations/20190207134142_move_participant_role_to_enum.exs +++ b/priv/repo/migrations/20190207134142_move_participant_role_to_enum.exs @@ -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