From b69daa7b54f36c167531b065bd5dfac32405c0c7 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Mon, 21 Dec 2020 15:47:26 +0100
Subject: [PATCH] Attach event ics files to event-related emails

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/service/export/icalendar.ex        | 13 +++++++++++--
 lib/web/email/email.ex                 | 18 +++++++++++++++++-
 lib/web/email/event.ex                 |  1 +
 lib/web/email/notification.ex          |  1 +
 lib/web/email/participation.ex         |  1 +
 mix.exs                                |  1 +
 test/service/export/icalendar_test.exs |  2 +-
 7 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/lib/service/export/icalendar.ex b/lib/service/export/icalendar.ex
index a9d06e94a..902712334 100644
--- a/lib/service/export/icalendar.ex
+++ b/lib/service/export/icalendar.ex
@@ -3,7 +3,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
   Export an event to iCalendar format.
   """
 
-  alias Mobilizon.{Actors, Events, Users}
+  alias Mobilizon.{Actors, Config, Events, Users}
   alias Mobilizon.Actors.Actor
   alias Mobilizon.Addresses.Address
   alias Mobilizon.Events.{Event, FeedToken}
@@ -11,6 +11,8 @@ defmodule Mobilizon.Service.Export.ICalendar do
   alias Mobilizon.Storage.Page
   alias Mobilizon.Users.User
 
+  @vendor "Mobilizon #{Config.instance_version()}"
+
   @doc """
   Export a public event to iCalendar format.
 
@@ -19,12 +21,19 @@ defmodule Mobilizon.Service.Export.ICalendar do
   @spec export_public_event(Event.t()) :: {:ok, String.t()}
   def export_public_event(%Event{visibility: visibility} = event)
       when visibility in [:public, :unlisted] do
-    {:ok, %ICalendar{events: [do_export_event(event)]} |> ICalendar.to_ics(vendor: "Mobilizon")}
+    export_event(event)
   end
 
   @spec export_public_event(Event.t()) :: {:error, :event_not_public}
   def export_public_event(%Event{}), do: {:error, :event_not_public}
 
+  @doc """
+  Export an event to iCalendar format
+  """
+  def export_event(%Event{} = event) do
+    {:ok, %ICalendar{events: [do_export_event(event)]} |> ICalendar.to_ics(vendor: @vendor)}
+  end
+
   @spec do_export_event(Event.t()) :: ICalendar.Event.t()
   defp do_export_event(%Event{} = event) do
     %ICalendar.Event{
diff --git a/lib/web/email/email.ex b/lib/web/email/email.ex
index ac5a31e44..936503f88 100644
--- a/lib/web/email/email.ex
+++ b/lib/web/email/email.ex
@@ -6,7 +6,9 @@ defmodule Mobilizon.Web.Email do
   use Bamboo.Phoenix, view: Mobilizon.Web.EmailView
 
   alias Ecto.UUID
-  alias Mobilizon.Config
+  alias Mobilizon.{Config, Events}
+  alias Mobilizon.Events.Event
+  alias Mobilizon.Service.Export.ICalendar
   alias Mobilizon.Web.EmailView
 
   @spec base_email(keyword()) :: Bamboo.Email.t()
@@ -40,4 +42,18 @@ defmodule Mobilizon.Web.Email do
       Timex.format!(DateTime.utc_now(), "{WDshort}, {D} {Mshort} {YYYY} {h24}:{m}:{s} {Z}")
     end
   end
+
+  def add_event_attachment(%Bamboo.Email{} = email, %Event{id: event_id}) do
+    with {:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
+         {:ok, event_ics_data} <- ICalendar.export_event(event) do
+      put_attachment(email, %Bamboo.Attachment{
+        filename: Slugger.slugify_downcase(event.title),
+        content_type: "text/calendar",
+        data: event_ics_data
+      })
+    else
+      _ ->
+        email
+    end
+  end
 end
diff --git a/lib/web/email/event.ex b/lib/web/email/event.ex
index b7b36e481..2a34fb347 100644
--- a/lib/web/email/event.ex
+++ b/lib/web/email/event.ex
@@ -46,6 +46,7 @@ defmodule Mobilizon.Web.Email.Event do
     |> assign(:changes, changes)
     |> assign(:subject, subject)
     |> assign(:timezone, timezone)
+    |> Email.add_event_attachment(event)
     |> render(:event_updated)
   end
 
diff --git a/lib/web/email/notification.ex b/lib/web/email/notification.ex
index 76fd83ff0..e27b13efb 100644
--- a/lib/web/email/notification.ex
+++ b/lib/web/email/notification.ex
@@ -30,6 +30,7 @@ defmodule Mobilizon.Web.Email.Notification do
     |> assign(:locale, locale)
     |> assign(:participant, participant)
     |> assign(:subject, subject)
+    |> Email.add_event_attachment(event)
     |> render(:before_event_notification)
   end
 
diff --git a/lib/web/email/participation.ex b/lib/web/email/participation.ex
index d3cc31ac6..132c0d8a2 100644
--- a/lib/web/email/participation.ex
+++ b/lib/web/email/participation.ex
@@ -118,6 +118,7 @@ defmodule Mobilizon.Web.Email.Participation do
     |> assign(:locale, locale)
     |> assign(:event, event)
     |> assign(:subject, subject)
+    |> Email.add_event_attachment(event)
     |> render(:event_participation_approved)
   end
 
diff --git a/mix.exs b/mix.exs
index bdd09654a..5294e8c90 100644
--- a/mix.exs
+++ b/mix.exs
@@ -133,6 +133,7 @@ defmodule Mobilizon.Mixfile do
       {:xml_builder, "~> 2.1.1", override: true},
       {:remote_ip, "~> 0.2.0"},
       {:ex_cldr_languages, "~> 0.2.1"},
+      {:slugger, "~> 0.3"},
       # Dev and test dependencies
       {:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
       {:ex_machina, "~> 2.3", only: [:dev, :test]},
diff --git a/test/service/export/icalendar_test.exs b/test/service/export/icalendar_test.exs
index 94c170654..9d5329f0a 100644
--- a/test/service/export/icalendar_test.exs
+++ b/test/service/export/icalendar_test.exs
@@ -17,7 +17,7 @@ defmodule Mobilizon.Service.ICalendarTest do
       BEGIN:VCALENDAR
       CALSCALE:GREGORIAN
       VERSION:2.0
-      PRODID:-//ICalendar//Mobilizon//EN
+      PRODID:-//ICalendar//Mobilizon #{Mobilizon.Config.instance_version()}//EN
       BEGIN:VEVENT
       CATEGORIES:#{event.tags |> Enum.map(& &1.title) |> Enum.join(",")}
       DESCRIPTION:Ceci est une description avec une première phrase assez longue\\,\\n      puis sur une seconde ligne