Add draft feature

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2019-10-02 17:59:07 +02:00
parent b96f3bc3ad
commit 442a011490
22 changed files with 587 additions and 66 deletions

View File

@@ -32,6 +32,7 @@ defmodule Mobilizon.Events.Event do
ends_on: DateTime.t(),
title: String.t(),
status: EventStatus.t(),
draft: boolean,
visibility: EventVisibility.t(),
join_options: JoinOptions.t(),
publish_at: DateTime.t(),
@@ -57,6 +58,7 @@ defmodule Mobilizon.Events.Event do
:ends_on,
:category,
:status,
:draft,
:visibility,
:publish_at,
:online_address,
@@ -74,6 +76,7 @@ defmodule Mobilizon.Events.Event do
:ends_on,
:category,
:status,
:draft,
:visibility,
:join_options,
:publish_at,
@@ -93,6 +96,7 @@ defmodule Mobilizon.Events.Event do
field(:ends_on, :utc_datetime)
field(:title, :string)
field(:status, EventStatus, default: :confirmed)
field(:draft, :boolean, default: false)
field(:visibility, EventVisibility, default: :public)
field(:join_options, JoinOptions, default: :free)
field(:publish_at, :utc_datetime)

View File

@@ -169,6 +169,7 @@ defmodule Mobilizon.Events do
url
|> event_by_url_query()
|> filter_public_visibility()
|> filter_draft()
|> preload_for_event()
|> Repo.one()
@@ -190,18 +191,32 @@ defmodule Mobilizon.Events do
url
|> event_by_url_query()
|> filter_public_visibility()
|> filter_draft()
|> preload_for_event()
|> Repo.one!()
end
@doc """
Gets an event by its UUID, with all associations loaded.
Gets a public event by its UUID, with all associations loaded.
"""
@spec get_public_event_by_uuid_with_preload(String.t()) :: Event.t() | nil
def get_public_event_by_uuid_with_preload(uuid) do
uuid
|> event_by_uuid_query()
|> filter_public_visibility()
|> filter_draft()
|> preload_for_event()
|> Repo.one()
end
@doc """
Gets an event by its UUID, with all associations loaded.
"""
@spec get_own_event_by_uuid_with_preload(String.t(), integer()) :: Event.t() | nil
def get_own_event_by_uuid_with_preload(uuid, user_id) do
uuid
|> event_by_uuid_query()
|> user_events_query(user_id)
|> preload_for_event()
|> Repo.one()
end
@@ -215,6 +230,7 @@ defmodule Mobilizon.Events do
|> upcoming_public_event_for_actor_query()
|> filter_public_visibility()
|> filter_not_event_uuid(not_event_uuid)
|> filter_draft()
|> Repo.one()
end
@@ -223,16 +239,18 @@ defmodule Mobilizon.Events do
"""
@spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
def create_event(attrs \\ %{}) do
with {:ok, %Event{} = event} <- do_create_event(attrs),
with {:ok, %Event{draft: false} = event} <- do_create_event(attrs),
{:ok, %Participant{} = _participant} <-
%Participant{}
|> Participant.changeset(%{
create_participant(%{
actor_id: event.organizer_actor_id,
role: :creator,
event_id: event.id
})
|> Repo.insert() do
}) do
{:ok, event}
else
# We don't create a creator participant if the event is a draft
{:ok, %Event{draft: true} = event} -> {:ok, event}
err -> err
end
end
@@ -262,10 +280,24 @@ defmodule Mobilizon.Events do
Updates an event.
"""
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
def update_event(%Event{} = old_event, attrs) do
def update_event(
%Event{draft: old_draft_status, id: event_id, organizer_actor_id: organizer_actor_id} =
old_event,
attrs
) do
with %Ecto.Changeset{changes: changes} = changeset <-
old_event |> Repo.preload(:tags) |> Event.update_changeset(attrs) do
with {:ok, %Event{} = new_event} <- Repo.update(changeset) do
with {:ok, %Event{draft: new_draft_status} = new_event} <- Repo.update(changeset) do
# If the event is no longer a draft
if old_draft_status == true && new_draft_status == false do
{:ok, %Participant{} = _participant} =
create_participant(%{
event_id: event_id,
role: :creator,
actor_id: organizer_actor_id
})
end
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
old_event,
new_event,
@@ -309,6 +341,7 @@ defmodule Mobilizon.Events do
|> sort(sort, direction)
|> filter_future_events(is_future)
|> filter_unlisted(is_unlisted)
|> filter_draft()
|> Repo.all()
end
@@ -320,6 +353,7 @@ defmodule Mobilizon.Events do
tags
|> Enum.map(& &1.id)
|> events_by_tags_query(limit)
|> filter_draft()
|> Repo.all()
end
@@ -333,6 +367,7 @@ defmodule Mobilizon.Events do
actor_id
|> event_for_actor_query()
|> filter_public_visibility()
|> filter_draft()
|> preload_for_event()
|> Page.paginate(page, limit)
|> Repo.all()
@@ -345,6 +380,15 @@ defmodule Mobilizon.Events do
{:ok, events, events_count}
end
@spec list_drafts_for_user(integer, integer | nil, integer | nil) :: [Event.t()]
def list_drafts_for_user(user_id, page \\ nil, limit \\ nil) do
Event
|> user_events_query(user_id)
|> filter_draft(true)
|> Page.paginate(page, limit)
|> Repo.all()
end
@doc """
Finds close events to coordinates.
Radius is in meters and defaults to 50km.
@@ -354,6 +398,7 @@ defmodule Mobilizon.Events do
"SRID=#{srid};POINT(#{lon} #{lat})"
|> Geo.WKT.decode!()
|> close_events_query(radius)
|> filter_draft()
|> Repo.all()
end
@@ -364,6 +409,7 @@ defmodule Mobilizon.Events do
def count_local_events do
count_local_events_query()
|> filter_public_visibility()
|> filter_draft()
|> Repo.one()
end
@@ -1134,6 +1180,16 @@ defmodule Mobilizon.Events do
)
end
@spec user_events_query(Ecto.Query.t(), number()) :: Ecto.Query.t()
defp user_events_query(query, user_id) do
from(
e in query,
join: a in Actor,
on: a.id == e.organizer_actor_id,
where: a.user_id == ^user_id
)
end
@spec events_by_name_query(String.t()) :: Ecto.Query.t()
defp events_by_name_query(name) do
from(
@@ -1372,6 +1428,11 @@ defmodule Mobilizon.Events do
from(e in query, where: e.uuid != ^not_event_uuid)
end
@spec filter_draft(Ecto.Query.t(), boolean) :: Ecto.Query.t()
defp filter_draft(query, is_draft \\ false) do
from(e in query, where: e.draft == ^is_draft)
end
@spec filter_future_events(Ecto.Query.t(), boolean) :: Ecto.Query.t()
defp filter_future_events(query, true) do
from(q in query, where: q.begins_on > ^DateTime.utc_now())

View File

@@ -31,7 +31,8 @@ defmodule MobilizonWeb.API.Events do
to: args.to,
actor: organizer_actor,
object: event,
local: true
# For now we don't federate drafts but it will be needed if we want to edit them as groups
local: args.metadata.draft == false
})
end
end
@@ -65,7 +66,7 @@ defmodule MobilizonWeb.API.Events do
actor: organizer_actor.url,
cc: [],
object: event,
local: true
local: args.metadata.draft == false
})
end
end
@@ -95,7 +96,8 @@ defmodule MobilizonWeb.API.Events do
join_options: Map.get(args, :join_options),
status: Map.get(args, :status),
online_address: Map.get(args, :online_address),
phone_address: Map.get(args, :phone_address)
phone_address: Map.get(args, :phone_address),
draft: Map.get(args, :draft)
}
}
end

View File

@@ -31,6 +31,20 @@ defmodule MobilizonWeb.Resolvers.Event do
{:error, :events_max_limit_reached}
end
def find_event(
_parent,
%{uuid: uuid},
%{context: %{current_user: %User{id: user_id}}} = _resolution
) do
case {:has_event, Mobilizon.Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
{:has_event, %Event{} = event} ->
{:ok, Map.put(event, :organizer_actor, Person.proxify_pictures(event.organizer_actor))}
{:has_event, _} ->
{:error, "Event with UUID #{uuid} not found"}
end
end
def find_event(_parent, %{uuid: uuid}, _resolution) do
case {:has_event, Mobilizon.Events.get_public_event_by_uuid_with_preload(uuid)} do
{:has_event, %Event{} = event} ->
@@ -264,6 +278,9 @@ defmodule MobilizonWeb.Resolvers.Event do
else
{:is_owned, nil} ->
{:error, "Organizer actor id is not owned by the user"}
{:error, %Ecto.Changeset{} = error} ->
{:error, error}
end
end

View File

@@ -243,6 +243,19 @@ defmodule MobilizonWeb.Resolvers.User do
end
end
@doc """
Returns the list of draft events for the current user
"""
def user_drafted_events(%User{id: user_id}, args, %{
context: %{current_user: %User{id: logged_user_id}}
}) do
with {:same_user, true} <- {:same_user, user_id == logged_user_id},
events <-
Events.list_drafts_for_user(user_id, Map.get(args, :page), Map.get(args, :limit)) do
{:ok, events}
end
end
def change_password(_parent, %{old_password: old_password, new_password: new_password}, %{
context: %{current_user: %User{password_hash: old_password_hash} = user}
}) do

View File

@@ -6,6 +6,7 @@ defmodule MobilizonWeb.Schema.EventType do
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import MobilizonWeb.Schema.Utils
alias Mobilizon.{Actors, Addresses}
@@ -60,6 +61,8 @@ defmodule MobilizonWeb.Schema.EventType do
field(:category, :string, description: "The event's category")
field(:draft, :boolean, description: "Whether or not the event is a draft")
field(:participant_stats, :participant_stats, resolve: &Event.stats_participants_for_event/3)
field(:participants, list_of(:participant), description: "The event's participants") do
@@ -252,8 +255,9 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:category, :string, default_value: "meeting")
arg(:physical_address, :address_input)
arg(:options, :event_options_input)
arg(:draft, :boolean, default_value: false)
resolve(&Event.create_event/3)
resolve(handle_errors(&Event.create_event/3))
end
@desc "Update an event"
@@ -280,8 +284,9 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:category, :string)
arg(:physical_address, :address_input)
arg(:options, :event_options_input)
arg(:draft, :boolean)
resolve(&Event.update_event/3)
resolve(handle_errors(&Event.update_event/3))
end
@desc "Delete an event"

View File

@@ -49,7 +49,7 @@ defmodule MobilizonWeb.Schema.UserType do
field(:locale, :string, description: "The user's locale")
field(:participations, list_of(:participant),
description: "The list of events this user goes to"
description: "The list of participations this user has"
) do
arg(:after_datetime, :datetime)
arg(:before_datetime, :datetime)
@@ -57,6 +57,12 @@ defmodule MobilizonWeb.Schema.UserType do
arg(:limit, :integer, default_value: 10)
resolve(&User.user_participations/3)
end
field(:drafts, list_of(:event), description: "The list of draft events this user has created") do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&User.user_drafted_events/3)
end
end
enum :user_role do

View File

@@ -70,6 +70,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
"status" => object["status"],
"online_address" => object["onlineAddress"],
"phone_address" => object["phoneAddress"],
"draft" => object["draft"] || false,
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags,
@@ -111,6 +112,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
"joinOptions" => to_string(event.join_options),
"endTime" => event.ends_on |> date_to_string(),
"tag" => event.tags |> build_tags(),
"draft" => event.draft,
"id" => event.url,
"url" => event.url
}

View File

@@ -319,6 +319,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"status" => metadata.status,
"onlineAddress" => metadata.online_address,
"phoneAddress" => metadata.phone_address,
"draft" => metadata.draft,
"uuid" => uuid,
"tag" =>
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)