From 14176ccc893bb1b85fe1f99380086f373f383700 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Mon, 16 Dec 2019 11:46:19 +0100
Subject: [PATCH 1/3] Support pt:CommentsEnabled in addition to
 mz:repliesModeration

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 docs/contribute/activity_pub.md             | 11 ++++++++++-
 lib/service/activity_pub/converter/event.ex |  8 +++++++-
 lib/service/activity_pub/utils.ex           |  5 +++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/docs/contribute/activity_pub.md b/docs/contribute/activity_pub.md
index 47986abe9..27017c1d6 100644
--- a/docs/contribute/activity_pub.md
+++ b/docs/contribute/activity_pub.md
@@ -10,7 +10,7 @@ To match usernames to actors, Mobilizon uses [WebFinger](https://tools.ietf.org/
 
 ## Instance subscriptions
 
-Instances subscribe to each other through an internal actor named `relay@instance.tld` that publishes (through `Announce`) every created content to it's followers. Each content creation share is saved so that updates and deletes are correctly sent to every
+Instances subscribe to each other through an internal actor named `relay@instance.tld` that publishes (through `Announce`) every created content to it's followers. Each content creation share is saved so that updates and deletes are correctly sent to every relay subscriber.
 
 ## Activities
 
@@ -46,6 +46,9 @@ See [the corresponding issue](https://framagit.org/framasoft/mobilizon/issues/32
 
 Accepted values: `allow_all`, `closed`, `moderated` (not used at the moment)
 
+!!! info
+    We also support PeerTube's `commentEnabled` property as a fallback. It is set to `true` only when `repliesModeration` is equal to `allow_all`.
+
 Example:
 ```json
 {
@@ -53,6 +56,7 @@ Example:
     "...",
     {
       "mz": "https://joinmobilizon.org/ns#",
+      "pt": "https://joinpeertube.org/ns#",
       "repliesModerationOption": {
         "@id": "mz:repliesModerationOption",
         "@type": "mz:repliesModerationOptionType"
@@ -60,11 +64,16 @@ Example:
       "repliesModerationOptionType": {
         "@id": "mz:repliesModerationOptionType",
         "@type": "rdfs:Class"
+      },
+      "commentsEnabled": {
+        "@id": "pt:commentsEnabled",
+        "@type": "sc:Boolean"
       }
     }
   ],
   "...": "...",
   "repliesModerationOption": "allow_all",
+  "commentsEnabled": true,
   "type": "Event",
   "url": "http://mobilizon1.com/events/8cf76e9f-c426-4912-9cd6-c7030b969611"
 }
diff --git a/lib/service/activity_pub/converter/event.ex b/lib/service/activity_pub/converter/event.ex
index a273b87f1..d02065fe8 100644
--- a/lib/service/activity_pub/converter/event.ex
+++ b/lib/service/activity_pub/converter/event.ex
@@ -119,6 +119,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
       "tag" => event.tags |> ConverterUtils.build_tags(),
       "maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
       "repliesModerationOption" => event.options.comment_moderation,
+      "commentsEnabled" => event.options.comment_moderation == :allow_all,
       # "draft" => event.draft,
       "ical:status" => event.status |> to_string |> String.upcase(),
       "id" => event.url,
@@ -140,7 +141,12 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
   defp get_options(object) do
     %{
       maximum_attendee_capacity: object["maximumAttendeeCapacity"],
-      comment_moderation: object["repliesModerationOption"]
+      comment_moderation:
+        Map.get(
+          object,
+          "repliesModerationOption",
+          if(Map.get(object, "commentsEnabled", true), do: :allow_all, else: :closed)
+        )
     }
   end
 
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
index 94c06a16a..3e99e7bd6 100644
--- a/lib/service/activity_pub/utils.ex
+++ b/lib/service/activity_pub/utils.ex
@@ -32,6 +32,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
         %{
           "sc" => "http://schema.org#",
           "ical" => "http://www.w3.org/2002/12/cal/ical#",
+          "pt" => "https://joinpeertube.org/ns#",
           "Hashtag" => "as:Hashtag",
           "category" => "sc:category",
           "uuid" => "sc:identifier",
@@ -45,6 +46,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
             "@id" => "mz:repliesModerationOption",
             "@type" => "mz:repliesModerationOptionType"
           },
+          "commentsEnabled" => %{
+            "@type" => "sc:Boolean",
+            "@id" => "pt:commentsEnabled"
+          },
           "joinModeType" => %{
             "@id" => "mz:joinModeType",
             "@type" => "rdfs:Class"

From 039846d46513b22a4b8618b9ed589b5b503df236 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Mon, 16 Dec 2019 11:47:31 +0100
Subject: [PATCH 2/3] Clear AP cache when content is updated or deleted

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/mobilizon/actors/actors.ex           | 1 +
 lib/service/activity_pub/activity_pub.ex | 4 ++++
 2 files changed, 5 insertions(+)

diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index 05fc1c3de..a92710859 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -249,6 +249,7 @@ defmodule Mobilizon.Actors do
 
     case transaction do
       {:ok, %{actor: %Actor{} = actor}} ->
+        {:ok, true} = Cachex.del(:activity_pub, "actor_#{actor.preferred_username}")
         {:ok, actor}
 
       {:error, remove, error, _} when remove in [:remove_banner, :remove_avatar] ->
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
index 4c612c803..298ba8690 100644
--- a/lib/service/activity_pub/activity_pub.ex
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -328,6 +328,7 @@ defmodule Mobilizon.Service.ActivityPub do
     with audience <-
            Audience.calculate_to_and_cc_from_mentions(event),
          {:ok, %Event{} = event} <- Events.delete_event(event),
+         {:ok, true} <- Cachex.del(:activity_pub, "event_#{event.uuid}"),
          {:ok, %Tombstone{} = _tombstone} <-
            Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}),
          Share.delete_all_by_uri(event.url),
@@ -350,6 +351,7 @@ defmodule Mobilizon.Service.ActivityPub do
     with audience <-
            Audience.calculate_to_and_cc_from_mentions(comment),
          {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
+         {:ok, true} <- Cachex.del(:activity_pub, "comment_#{comment.uuid}"),
          {:ok, %Tombstone{} = _tombstone} <-
            Tombstone.create_tombstone(%{uri: comment.url, actor_id: actor.id}),
          Share.delete_all_by_uri(comment.url),
@@ -730,6 +732,7 @@ defmodule Mobilizon.Service.ActivityPub do
        ) do
     with args <- prepare_args_for_event(args),
          {:ok, %Event{} = new_event} <- Events.update_event(old_event, args),
+         {:ok, true} <- Cachex.del(:activity_pub, "event_#{new_event.uuid}"),
          event_as_data <- Convertible.model_to_as(new_event),
          audience <-
            Audience.calculate_to_and_cc_from_mentions(new_event),
@@ -748,6 +751,7 @@ defmodule Mobilizon.Service.ActivityPub do
   defp update_actor(%Actor{} = old_actor, args, additional) do
     with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args),
          actor_as_data <- Convertible.model_to_as(new_actor),
+         {:ok, true} <- Cachex.del(:activity_pub, "actor_#{new_actor.preferred_username}"),
          audience <-
            Audience.calculate_to_and_cc_from_mentions(new_actor),
          additional <- Map.merge(additional, %{"actor" => old_actor.url}),

From 39b7a5b94857bea6f5bb7b16913478a2bca55467 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Mon, 16 Dec 2019 11:47:57 +0100
Subject: [PATCH 3/3] Improve JSON-LD representation for Address and add it to
 the docs

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 docs/contribute/activity_pub.md   | 58 +++++++++++++++++++++++++++++++
 lib/service/activity_pub/utils.ex | 20 +++++++++++
 2 files changed, 78 insertions(+)

diff --git a/docs/contribute/activity_pub.md b/docs/contribute/activity_pub.md
index 27017c1d6..88f383e06 100644
--- a/docs/contribute/activity_pub.md
+++ b/docs/contribute/activity_pub.md
@@ -111,3 +111,61 @@ Example:
   "url": "http://mobilizon1.com/events/8cf76e9f-c426-4912-9cd6-c7030b969611"
 }
 ```
+
+#### location
+
+We use Schema.org's `location` property on `Event`.
+[The ActivityStream vocabulary to represent places](https://www.w3.org/TR/activitystreams-vocabulary/#places) is quite limited so instead of using `Place` from ActivityStreams we use `Place` from Schema.org.
+
+A [Schema.org `Place` type](https://schema.org/Place) has [an `address` property](https://schema.org/address), which we assume to be [of `PostalAddress` type](https://schema.org/PostalAddress) and [a `geo` property](https://schema.org/geo), which is assumed to be of [`GeoCoordinates` type](https://schema.org/GeoCoordinates).
+
+```json
+{
+  "@context": [
+    "...",
+    {
+      "GeoCoordinates": "sc:GeoCoordinates",
+      "Place": "sc:Place",
+      "PostalAddress": "sc:PostalAddress",
+      "address": {
+        "@id": "sc:address",
+        "@type": "sc:PostalAddress"
+      },
+      "addressCountry": "sc:addressCountry",
+      "addressLocality": "sc:addressLocality",
+      "addressRegion": "sc:addressRegion",
+      "geo": {
+        "@id": "sc:geo",
+        "@type": "sc:GeoCoordinates"
+      },
+      "location": {
+        "@id": "sc:location",
+        "@type": "sc:Place"
+      },
+      "postalCode": "sc:postalCode",
+      "sc": "http://schema.org#",
+      "streetAddress": "sc:streetAddress",
+    }
+  ],
+  "id": "http://mobilizon2.com/events/945f350d-a3e6-4bcd-9bf2-0bd2e4d353c5",
+  "location": {
+      "address": {
+        "addressCountry": "France",
+        "addressLocality": "Lyon",
+        "addressRegion": "Auvergne-Rhône-Alpes",
+        "postalCode": "69007",
+        "streetAddress": "10 Rue Jangot",
+        "type": "PostalAddress"
+      },
+      "geo": {
+        "latitude": 4.8425657,
+        "longitude": 45.7517141,
+        "type": "GeoCoordinates"
+      },
+      "id": "http://mobilizon2.com/address/bdf7fb53-7177-46f3-8fb3-93c25a802522",
+      "name": "10 Rue Jangot",
+      "type": "Place"
+    },
+  "type": "Event"
+}
+```
\ No newline at end of file
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
index 3e99e7bd6..dc8e7eb95 100644
--- a/lib/service/activity_pub/utils.ex
+++ b/lib/service/activity_pub/utils.ex
@@ -37,6 +37,26 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
           "category" => "sc:category",
           "uuid" => "sc:identifier",
           "maximumAttendeeCapacity" => "sc:maximumAttendeeCapacity",
+          "location" => %{
+            "@id" => "sc:location",
+            "@type" => "sc:Place"
+          },
+          "Place" => "sc:Place",
+          "PostalAddress" => "sc:PostalAddress",
+          "GeoCoordinates" => "sc:GeoCoordinates",
+          "address" => %{
+            "@id" => "sc:address",
+            "@type" => "sc:PostalAddress"
+          },
+          "geo" => %{
+            "@id" => "sc:geo",
+            "@type" => "sc:GeoCoordinates"
+          },
+          "addressCountry" => "sc:addressCountry",
+          "addressRegion" => "sc:addressRegion",
+          "postalCode" => "sc:postalCode",
+          "addressLocality" => "sc:addressLocality",
+          "streetAddress" => "sc:streetAddress",
           "mz" => "https://joinmobilizon.org/ns#",
           "repliesModerationOptionType" => %{
             "@id" => "mz:repliesModerationOptionType",