Improve event creation form by introducting EventOptions

It's a subentity that holds additional metadata in a map database type

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-08-28 11:28:27 +02:00
parent f928be3200
commit cb96b807a0
15 changed files with 631 additions and 50 deletions

View File

@@ -54,6 +54,7 @@ defmodule Mobilizon.Events.Event do
field(:online_address, :string)
field(:phone_address, :string)
field(:category, :string)
embeds_one(:options, Mobilizon.Events.EventOptions)
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")
@@ -87,6 +88,7 @@ defmodule Mobilizon.Events.Event do
:picture_id,
:physical_address_id
])
|> cast_embed(:options)
|> validate_required([
:title,
:begins_on,

View File

@@ -0,0 +1,69 @@
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
"""
use Ecto.Schema
alias Mobilizon.Events.{
EventOptions,
EventOffer,
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
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
])
end
end

View File

@@ -14,14 +14,15 @@ defmodule MobilizonWeb.API.Events do
@spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
def create_event(
%{
title: title,
description: description,
organizer_actor_id: organizer_actor_id,
begins_on: begins_on,
category: category,
tags: tags
description: description,
options: options,
organizer_actor_id: organizer_actor_id,
tags: tags,
title: title
} = args
) do
)
when is_map(options) do
with %Actor{url: url} = actor <-
Actors.get_local_actor_with_everything(organizer_actor_id),
physical_address <- Map.get(args, :physical_address, nil),
@@ -38,7 +39,12 @@ defmodule MobilizonWeb.API.Events do
content_html,
picture,
tags,
%{begins_on: begins_on, physical_address: physical_address, category: category}
%{
begins_on: begins_on,
physical_address: physical_address,
category: Map.get(args, :category),
options: options
}
) do
ActivityPub.create(%{
to: ["https://www.w3.org/ns/activitystreams#Public"],

View File

@@ -68,6 +68,7 @@ defmodule MobilizonWeb.Schema.EventType do
field(:updated_at, :datetime, description: "When the event was last updated")
field(:created_at, :datetime, description: "When the event was created")
field(:options, :event_options, description: "The event options")
end
@desc "The list of visibility options for an event"
@@ -90,6 +91,101 @@ defmodule MobilizonWeb.Schema.EventType do
value(:cancelled, description: "The event is cancelled")
end
object :event_offer do
field(:price, :float, description: "The price amount for this offer")
field(:price_currency, :string, description: "The currency for this price offer")
field(:url, :string, description: "The URL to access to this offer")
end
object :event_participation_condition do
field(:title, :string, description: "The title for this condition")
field(:content, :string, description: "The content for this condition")
field(:url, :string, description: "The URL to access this condition")
end
input_object :event_offer_input do
field(:price, :float, description: "The price amount for this offer")
field(:price_currency, :string, description: "The currency for this price offer")
field(:url, :string, description: "The URL to access to this offer")
end
input_object :event_participation_condition_input do
field(:title, :string, description: "The title for this condition")
field(:content, :string, description: "The content for this condition")
field(:url, :string, description: "The URL to access this condition")
end
@desc "The list of possible options for the event's status"
enum :event_comment_moderation do
value(:allow_all, description: "Anyone can comment under the event")
value(:moderated, description: "Every comment has to be moderated by the admin")
value(:closed, description: "No one can comment except for the admin")
end
object :event_options do
field(:maximum_attendee_capacity, :integer,
description: "The maximum attendee capacity for this event"
)
field(:remaining_attendee_capacity, :integer,
description: "The number of remaining seats for this event"
)
field(:show_remaining_attendee_capacity, :boolean,
description: "Whether or not to show the number of remaining seats for this event"
)
field(:offers, list_of(:event_offer), description: "The list of offers to show for this event")
field(:participation_conditions, list_of(:event_participation_condition),
description: "The list of participation conditions to accept to join this event"
)
field(:attendees, list_of(:string), description: "The list of special attendees")
field(:program, :string, description: "The list of the event")
field(:comment_moderation, :event_comment_moderation,
description: "The policy on public comment moderation under the event"
)
field(:show_participation_price, :boolean,
description: "Whether or not to show the participation price"
)
end
input_object :event_options_input do
field(:maximum_attendee_capacity, :integer,
description: "The maximum attendee capacity for this event"
)
field(:remaining_attendee_capacity, :integer,
description: "The number of remaining seats for this event"
)
field(:show_remaining_attendee_capacity, :boolean,
description: "Whether or not to show the number of remaining seats for this event"
)
field(:offers, list_of(:event_offer_input),
description: "The list of offers to show for this event"
)
field(:participation_conditions, list_of(:event_participation_condition_input),
description: "The list of participation conditions to accept to join this event"
)
field(:attendees, list_of(:string), description: "The list of special attendees")
field(:program, :string, description: "The list of the event")
field(:comment_moderation, :event_comment_moderation,
description: "The policy on public comment moderation under the event"
)
field(:show_participation_price, :boolean,
description: "Whether or not to show the participation price"
)
end
object :event_queries do
@desc "Get all events"
field :events, list_of(:event) do
@@ -131,8 +227,9 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:online_address, :string)
arg(:phone_address, :string)
arg(:organizer_actor_id, non_null(:id))
arg(:category, :string)
arg(:category, :string, default_value: "meeting")
arg(:physical_address, :address_input)
arg(:options, :event_options_input, default_value: %{})
resolve(&Event.create_event/3)
end

View File

@@ -34,7 +34,8 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
{:actor, Actors.get_actor_by_url(object["actor"])},
{:address, address_id} <-
{:address, get_address(object["location"])},
{:tags, tags} <- {:tags, fetch_tags(object["tag"])} do
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
{:options, options} <- {:options, get_options(object)} do
picture_id =
with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0,
%Picture{id: picture_id} <-
@@ -50,25 +51,41 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
_ -> nil
end
{:ok,
%{
"title" => object["name"],
"description" => object["content"],
"organizer_actor_id" => actor_id,
"picture_id" => picture_id,
"begins_on" => object["startTime"],
"category" => object["category"],
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags,
"physical_address_id" => address_id
}}
entity = %{
"title" => object["name"],
"description" => object["content"],
"organizer_actor_id" => actor_id,
"picture_id" => picture_id,
"begins_on" => object["startTime"],
"category" => object["category"],
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags,
"physical_address_id" => address_id
}
{:ok, Map.put(entity, "options", options)}
else
err ->
{:error, err}
end
end
# Get only elements that we have in EventOptions
defp get_options(object) do
keys =
Mobilizon.Events.EventOptions
|> struct
|> Map.keys()
|> List.delete(:__struct__)
|> Enum.map(&Utils.camelize/1)
Enum.reduce(object, %{}, fn {key, value}, acc ->
(value && key in keys && Map.put(acc, Utils.underscore(key), value)) ||
acc
end)
end
defp get_address(address_url) when is_bitstring(address_url) do
get_address(%{"id" => address_url})
end

View File

@@ -298,7 +298,19 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
do: res,
else: Map.put(res, "location", make_address_data(metadata.physical_address))
if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)])
res =
if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)])
if is_nil(metadata.options) do
res
else
options = struct(Mobilizon.Events.EventOptions, metadata.options) |> Map.from_struct()
Enum.reduce(options, res, fn {key, value}, acc ->
(value && Map.put(acc, camelize(key), value)) ||
acc
end)
end
end
def make_address_data(%Address{} = address) do
@@ -669,4 +681,21 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
:public_key.pem_encode([public_key])
end
def camelize(word) when is_atom(word) do
camelize(to_string(word))
end
def camelize(word) when is_bitstring(word) do
{first, rest} = String.split_at(Macro.camelize(word), 1)
String.downcase(first) <> rest
end
def underscore(word) when is_atom(word) do
underscore(to_string(word))
end
def underscore(word) when is_bitstring(word) do
Macro.underscore(word)
end
end