From 5fbaf42cad28f26a582fb4c179dd40689b191daf Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 30 Jul 2019 10:35:29 +0200 Subject: [PATCH] Add address input and refactor federation stuff Signed-off-by: Thomas Citharel --- config/dev.exs | 2 + .../components/Event/AddressAutoComplete.vue | 53 +++++++++++ js/src/graphql/address.ts | 18 ++++ js/src/graphql/event.ts | 6 +- js/src/types/address.model.ts | 1 + js/src/views/Event/Create.vue | 13 ++- lib/mix/tasks/mobilizon/toot.ex | 4 +- lib/mobilizon/addresses/address.ex | 38 +++++--- lib/mobilizon/addresses/addresses.ex | 19 +++- lib/mobilizon/events/event.ex | 4 +- lib/mobilizon/events/events.ex | 19 ++-- lib/mobilizon_web/api/comments.ex | 3 +- lib/mobilizon_web/api/events.ex | 23 +++-- lib/mobilizon_web/api/groups.ex | 2 +- lib/mobilizon_web/resolvers/address.ex | 2 +- lib/mobilizon_web/resolvers/comment.ex | 10 +- lib/mobilizon_web/resolvers/event.ex | 42 ++++---- lib/mobilizon_web/resolvers/group.ex | 13 +-- lib/mobilizon_web/schema/address.ex | 16 ++++ lib/mobilizon_web/schema/event.ex | 3 +- lib/service/activity_pub/activity_pub.ex | 68 +++++++------ .../activity_pub/converters/address.ex | 58 +++++++++++ lib/service/activity_pub/converters/event.ex | 84 ++++++++++++---- lib/service/activity_pub/transmogrifier.ex | 41 ++++++-- lib/service/activity_pub/utils.ex | 63 +++++++++--- lib/service/federator.ex | 2 +- .../20190729125532_add-url-to-addresses.exs | 9 ++ schema.graphql | 26 ++++- test/fixtures/mobilizon-post-activity.json | 66 +++++++++++++ test/mobilizon/addresses/addresses_test.exs | 20 ++-- .../activity_pub/activity_pub_test.exs | 8 +- .../activity_pub/transmogrifier_test.exs | 95 ++++++++++++++----- .../resolvers/event_resolver_test.exs | 89 +++++++++++++++++ test/support/factory.ex | 1 + 34 files changed, 729 insertions(+), 192 deletions(-) create mode 100644 js/src/components/Event/AddressAutoComplete.vue create mode 100644 js/src/graphql/address.ts create mode 100644 lib/service/activity_pub/converters/address.ex create mode 100644 priv/repo/migrations/20190729125532_add-url-to-addresses.exs create mode 100644 test/fixtures/mobilizon-post-activity.json diff --git a/config/dev.exs b/config/dev.exs index 41fb0f730..b438a25b3 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -50,6 +50,8 @@ config :mobilizon, MobilizonWeb.Endpoint, # Do not include metadata nor timestamps in development logs config :logger, :console, format: "[$level] $message\n", level: :debug +config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim + # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. config :phoenix, :stacktrace_depth, 20 diff --git a/js/src/components/Event/AddressAutoComplete.vue b/js/src/components/Event/AddressAutoComplete.vue new file mode 100644 index 000000000..023ef7963 --- /dev/null +++ b/js/src/components/Event/AddressAutoComplete.vue @@ -0,0 +1,53 @@ + + diff --git a/js/src/graphql/address.ts b/js/src/graphql/address.ts new file mode 100644 index 000000000..0a16a98de --- /dev/null +++ b/js/src/graphql/address.ts @@ -0,0 +1,18 @@ +import gql from 'graphql-tag'; + +export const ADDRESS = gql` + query($query:String!) { + searchAddress( + query: $query + ) { + description, + geom, + floor, + street, + locality, + postalCode, + region, + country + } + } +`; diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts index a7174dcb5..48ad5381f 100644 --- a/js/src/graphql/event.ts +++ b/js/src/graphql/event.ts @@ -144,7 +144,8 @@ export const CREATE_EVENT = gql` $category: String!, $beginsOn: DateTime!, $picture: PictureInput, - $tags: [String] + $tags: [String], + $physicalAddress: AddressInput! ) { createEvent( title: $title, @@ -153,7 +154,8 @@ export const CREATE_EVENT = gql` organizerActorId: $organizerActorId, category: $category, picture: $picture, - tags: $tags + tags: $tags, + physicalAddress: $physicalAddress ) { id, uuid, diff --git a/js/src/types/address.model.ts b/js/src/types/address.model.ts index 663ec425f..fddab2695 100644 --- a/js/src/types/address.model.ts +++ b/js/src/types/address.model.ts @@ -1,4 +1,5 @@ export interface IAddress { + id: number; description: string; floor: string; street: string; diff --git a/js/src/views/Event/Create.vue b/js/src/views/Event/Create.vue index 4c0ce83e5..856432c05 100644 --- a/js/src/views/Event/Create.vue +++ b/js/src/views/Event/Create.vue @@ -14,6 +14,8 @@ + + @@ -57,9 +59,10 @@ import DateTimePicker from '@/components/Event/DateTimePicker.vue'; import TagInput from '@/components/Event/TagInput.vue'; import { TAGS } from '@/graphql/tags'; import { ITag } from '@/types/tag.model'; +import AddressAutoComplete from '@/components/Event/AddressAutoComplete.vue'; @Component({ - components: { TagInput, DateTimePicker, PictureUpload, Editor }, + components: { AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor }, apollo: { loggedPerson: { query: LOGGED_PERSON, @@ -134,9 +137,13 @@ export default class CreateEvent extends Vue { const obj = { organizerActorId: this.loggedPerson.id, beginsOn: this.event.beginsOn.toISOString(), - tags: this.event.tags.map((tag: ITag) => tag.title), + tags: this.event.tags.map((tag: ITag) => tag.title) }; - const res = Object.assign({}, this.event, obj); + let res = Object.assign({}, this.event, obj); + + if (this.event.physicalAddress) { + delete this.event.physicalAddress['__typename']; + } /** * Transform picture files diff --git a/lib/mix/tasks/mobilizon/toot.ex b/lib/mix/tasks/mobilizon/toot.ex index e3ae95747..3f9371eb0 100644 --- a/lib/mix/tasks/mobilizon/toot.ex +++ b/lib/mix/tasks/mobilizon/toot.ex @@ -11,10 +11,10 @@ defmodule Mix.Tasks.Mobilizon.Toot do Mix.Task.run("app.start") case MobilizonWeb.API.Comments.create_comment(from, content) do - {:ok, _} -> + {:ok, _, _} -> Mix.shell().info("Tooted") - {:local_actor, _} -> + {:local_actor, _, _} -> Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist") _ -> diff --git a/lib/mobilizon/addresses/address.ex b/lib/mobilizon/addresses/address.ex index 405a0af92..6dfcb434a 100644 --- a/lib/mobilizon/addresses/address.ex +++ b/lib/mobilizon/addresses/address.ex @@ -6,6 +6,20 @@ defmodule Mobilizon.Addresses.Address do alias Mobilizon.Addresses.Address alias Mobilizon.Events.Event # alias Mobilizon.Actors.Actor + @attrs [ + :description, + :floor, + :geom, + :country, + :locality, + :region, + :postal_code, + :street, + :url + ] + @required [ + :url + ] schema "addresses" do field(:country, :string) @@ -16,8 +30,8 @@ defmodule Mobilizon.Addresses.Address do field(:geom, Geo.PostGIS.Geometry) field(:postal_code, :string) field(:street, :string) - has_one(:event, Event, foreign_key: :physical_address_id) - # has_one(:group, Actor) + field(:url, :string) + has_many(:event, Event, foreign_key: :physical_address_id) timestamps() end @@ -25,15 +39,15 @@ defmodule Mobilizon.Addresses.Address do @doc false def changeset(%Address{} = address, attrs) do address - |> cast(attrs, [ - :description, - :floor, - :geom, - :country, - :locality, - :region, - :postal_code, - :street - ]) + |> cast(attrs, @attrs) + |> set_url() + |> validate_required(@required) + end + + defp set_url(%Ecto.Changeset{changes: changes} = changeset) do + url = + Map.get(changes, :url, MobilizonWeb.Endpoint.url() <> "/address/#{Ecto.UUID.generate()}") + + put_change(changeset, :url, url) end end diff --git a/lib/mobilizon/addresses/addresses.ex b/lib/mobilizon/addresses/addresses.ex index a0c8c0022..0c2304f41 100644 --- a/lib/mobilizon/addresses/addresses.ex +++ b/lib/mobilizon/addresses/addresses.ex @@ -50,6 +50,21 @@ defmodule Mobilizon.Addresses do """ def get_address!(id), do: Repo.get!(Address, id) + @doc """ + Gets a single address by it's url + + ## Examples + + iex> get_address_by_url("https://mobilizon.social/addresses/4572") + %Address{} + + iex> get_address_by_url("https://mobilizon.social/addresses/099") + nil + """ + def get_address_by_url(url) do + Repo.get_by(Address, url: url) + end + @doc """ Creates a address. @@ -163,7 +178,7 @@ defmodule Mobilizon.Addresses do We only look at the description for now, and eventually order by object distance """ @spec search_addresses(String.t(), list()) :: list(Address.t()) - def search_addresses(search, options) do + def search_addresses(search, options \\ []) do limit = Keyword.get(options, :limit, 5) query = from(a in Address, where: ilike(a.description, ^"%#{search}%"), limit: ^limit) @@ -181,7 +196,7 @@ defmodule Mobilizon.Addresses do do: from(a in query, where: ilike(a.country, ^"%#{country}%")), else: query - Repo.all(query) + if Keyword.get(options, :single, false) == true, do: Repo.one(query), else: Repo.all(query) end @doc """ diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index 95005bb72..fe100b3ea 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -84,9 +84,9 @@ defmodule Mobilizon.Events.Event do :online_address, :phone_address, :uuid, - :picture_id + :picture_id, + :physical_address_id ]) - |> cast_assoc(:physical_address) |> validate_required([ :title, :begins_on, diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 31908182d..7194dc89e 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -382,7 +382,8 @@ defmodule Mobilizon.Events do defp do_create_event(attrs) do with {:ok, %Event{} = event} <- %Event{} |> Event.changeset(attrs) |> Repo.insert(), - %Event{} = event <- event |> Repo.preload([:tags, :organizer_actor]), + %Event{} = event <- + event |> Repo.preload([:tags, :organizer_actor, :physical_address, :picture]), {:has_tags, true, _} <- {:has_tags, Map.has_key?(attrs, "tags"), event} do event |> Ecto.Changeset.change() @@ -513,8 +514,10 @@ defmodule Mobilizon.Events do @doc """ Get an existing tag or create one """ - @spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, any()} - def get_or_create_tag(title) do + @spec get_or_create_tag(map()) :: {:ok, Tag.t()} | {:error, any()} + def get_or_create_tag(tag) do + "#" <> title = tag["name"] + case Repo.get_by(Tag, title: title) do %Tag{} = tag -> {:ok, tag} @@ -1223,9 +1226,13 @@ defmodule Mobilizon.Events do """ def create_comment(attrs \\ %{}) do - %Comment{} - |> Comment.changeset(attrs) - |> Repo.insert() + with {:ok, %Comment{} = comment} <- + %Comment{} + |> Comment.changeset(attrs) + |> Repo.insert(), + %Comment{} = comment <- Repo.preload(comment, [:actor, :in_reply_to_comment]) do + {:ok, comment} + end end @doc """ diff --git a/lib/mobilizon_web/api/comments.ex b/lib/mobilizon_web/api/comments.ex index 6680bd420..fbef71418 100644 --- a/lib/mobilizon_web/api/comments.ex +++ b/lib/mobilizon_web/api/comments.ex @@ -15,7 +15,8 @@ defmodule MobilizonWeb.API.Comments do Creates a comment from an actor and a status """ - @spec create_comment(String.t(), String.t(), String.t()) :: {:ok, Activity.t()} | any() + @spec create_comment(String.t(), String.t(), String.t()) :: + {:ok, Activity.t(), Comment.t()} | any() def create_comment( from_username, status, diff --git a/lib/mobilizon_web/api/events.ex b/lib/mobilizon_web/api/events.ex index aface5cf2..4207dcd4a 100644 --- a/lib/mobilizon_web/api/events.ex +++ b/lib/mobilizon_web/api/events.ex @@ -2,6 +2,7 @@ defmodule MobilizonWeb.API.Events do @moduledoc """ API for Events """ + alias Mobilizon.Addresses alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Service.ActivityPub @@ -11,7 +12,7 @@ defmodule MobilizonWeb.API.Events do @doc """ Create an event """ - @spec create_event(map()) :: {:ok, Activity.t()} | any() + @spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any() def create_event( %{ title: title, @@ -22,10 +23,9 @@ defmodule MobilizonWeb.API.Events do tags: tags } = args ) do - require Logger - with %Actor{url: url} = actor <- Actors.get_local_actor_with_everything(organizer_actor_id), + physical_address <- Map.get(args, :physical_address, nil), title <- String.trim(title), visibility <- Map.get(args, :visibility, :public), picture <- Map.get(args, :picture, nil), @@ -34,14 +34,12 @@ defmodule MobilizonWeb.API.Events do event <- ActivityPubUtils.make_event_data( url, - to, + %{to: to, cc: cc}, title, content_html, picture, tags, - cc, - %{begins_on: begins_on}, - category + %{begins_on: begins_on, physical_address: physical_address, category: category} ) do ActivityPub.create(%{ to: ["https://www.w3.org/ns/activitystreams#Public"], @@ -51,4 +49,15 @@ defmodule MobilizonWeb.API.Events do }) end end + + defp get_physical_address(address_id) when is_number(address_id), + do: Addresses.get_address!(address_id) + + defp get_physical_address(address_id) when is_binary(address_id) do + with {address_id, ""} <- Integer.parse(address_id) do + get_physical_address(address_id) + end + end + + defp get_physical_address(nil), do: nil end diff --git a/lib/mobilizon_web/api/groups.ex b/lib/mobilizon_web/api/groups.ex index 7daf2aa7b..8c6e8f64b 100644 --- a/lib/mobilizon_web/api/groups.ex +++ b/lib/mobilizon_web/api/groups.ex @@ -11,7 +11,7 @@ defmodule MobilizonWeb.API.Groups do @doc """ Create a group """ - @spec create_group(map()) :: {:ok, Activity.t()} | any() + @spec create_group(map()) :: {:ok, Activity.t(), Group.t()} | any() def create_group( %{ preferred_username: title, diff --git a/lib/mobilizon_web/resolvers/address.ex b/lib/mobilizon_web/resolvers/address.ex index 3dabf4c9a..372cc03b9 100644 --- a/lib/mobilizon_web/resolvers/address.ex +++ b/lib/mobilizon_web/resolvers/address.ex @@ -11,7 +11,7 @@ defmodule MobilizonWeb.Resolvers.Address do Search an address """ @spec search(map(), map(), map()) :: {:ok, list(Address.t())} - def search(_parent, %{query: query}, %{context: %{ip: ip}}) do + def search(_parent, %{query: query, page: _page, limit: _limit}, %{context: %{ip: ip}}) do country = Geolix.lookup(ip) |> Map.get(:country, nil) local_addresses = Task.async(fn -> Addresses.search_addresses(query, country: country) end) diff --git a/lib/mobilizon_web/resolvers/comment.ex b/lib/mobilizon_web/resolvers/comment.ex index 09d866bf3..c167b898f 100644 --- a/lib/mobilizon_web/resolvers/comment.ex +++ b/lib/mobilizon_web/resolvers/comment.ex @@ -11,14 +11,10 @@ defmodule MobilizonWeb.Resolvers.Comment do def create_comment(_parent, %{text: comment, actor_username: username}, %{ context: %{current_user: %User{} = _user} }) do - with {:ok, %Activity{data: %{"object" => %{"type" => "Note"} = object}}} <- + with {:ok, %Activity{data: %{"object" => %{"type" => "Note"} = _object}}, + %Comment{} = comment} <- Comments.create_comment(username, comment) do - {:ok, - %Comment{ - text: object["content"], - url: object["id"], - uuid: object["uuid"] - }} + {:ok, comment} end end diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex index 339fbbdbc..d3155bed5 100644 --- a/lib/mobilizon_web/resolvers/event.ex +++ b/lib/mobilizon_web/resolvers/event.ex @@ -3,6 +3,8 @@ defmodule MobilizonWeb.Resolvers.Event do Handles the event-related GraphQL calls """ alias Mobilizon.Activity + alias Mobilizon.Addresses + alias Mobilizon.Addresses.Address alias Mobilizon.Events alias Mobilizon.Events.{Event, Participant} alias Mobilizon.Media.Picture @@ -190,25 +192,10 @@ defmodule MobilizonWeb.Resolvers.Event do """ def create_event(_parent, args, %{context: %{current_user: _user}} = _resolution) do with {:ok, args} <- save_attached_picture(args), - {:ok, %Activity{data: %{"object" => %{"type" => "Event"} = object}}} <- + {:ok, args} <- save_physical_address(args), + {:ok, %Activity{data: %{"object" => %{"type" => "Event"} = _object}}, %Event{} = event} <- MobilizonWeb.API.Events.create_event(args) do - res = %{ - title: object["name"], - description: object["content"], - uuid: object["uuid"], - url: object["id"] - } - - res = - if Map.has_key?(object, "attachment"), - do: - Map.put(res, :picture, %{ - name: object["attachment"] |> hd() |> Map.get("name"), - url: object["attachment"] |> hd() |> Map.get("url") |> hd() |> Map.get("href") - }), - else: res - - {:ok, res} + {:ok, event} end end @@ -237,6 +224,25 @@ defmodule MobilizonWeb.Resolvers.Event do @spec save_attached_picture(map()) :: {:ok, map()} defp save_attached_picture(args), do: {:ok, args} + @spec save_physical_address(map()) :: {:ok, map()} + defp save_physical_address(%{physical_address: %{url: physical_address_url}} = args) do + with %Address{} = address <- Addresses.get_address_by_url(physical_address_url), + args <- Map.put(args, :physical_address, address) do + {:ok, args} + end + end + + # @spec save_physical_address(map()) :: {:ok, map()} + # defp save_physical_address(%{physical_address: address} = args) do + # with {:ok, %Address{} = address} <- Addresses.create_address(address), + # args <- Map.put(args, :physical_address, address) do + # {:ok, args} + # end + # end + + @spec save_physical_address(map()) :: {:ok, map()} + defp save_physical_address(args), do: {:ok, args} + @doc """ Delete an event """ diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex index e24f4ae35..96b4214eb 100644 --- a/lib/mobilizon_web/resolvers/group.ex +++ b/lib/mobilizon_web/resolvers/group.ex @@ -47,20 +47,15 @@ defmodule MobilizonWeb.Resolvers.Group do :ok, %Activity{ data: %{ - "object" => %{"type" => "Group"} = object + "object" => %{"type" => "Group"} = _object } - } + }, + %Actor{} = group } <- MobilizonWeb.API.Groups.create_group(args) do { :ok, - %Actor{ - preferred_username: object["preferredUsername"], - summary: object["summary"], - type: :Group, - # uuid: object["uuid"], - url: object["id"] - } + group } end diff --git a/lib/mobilizon_web/schema/address.ex b/lib/mobilizon_web/schema/address.ex index acbdeed76..2bbbb0bec 100644 --- a/lib/mobilizon_web/schema/address.ex +++ b/lib/mobilizon_web/schema/address.ex @@ -14,6 +14,7 @@ defmodule MobilizonWeb.Schema.AddressType do field(:region, :string) field(:country, :string) field(:description, :string) + field(:url, :string) end object :phone_address do @@ -26,10 +27,25 @@ defmodule MobilizonWeb.Schema.AddressType do field(:info, :string) end + input_object :address_input do + # Either a full picture object + field(:geom, :point, description: "The geocoordinates for the point where this address is") + field(:floor, :string, description: "The floor this event is at") + field(:street, :string, description: "The address's street name (with number)") + field(:locality, :string, description: "The address's locality") + field(:postal_code, :string) + field(:region, :string) + field(:country, :string) + field(:description, :string) + field(:url, :string) + end + object :address_queries do @desc "Search for an address" field :search_address, type: list_of(:address) do arg(:query, non_null(:string)) + arg(:page, :integer, default_value: 1) + arg(:limit, :integer, default_value: 10) resolve(&Resolvers.Address.search/3) end diff --git a/lib/mobilizon_web/schema/event.ex b/lib/mobilizon_web/schema/event.ex index 12285a7a4..aff8bc835 100644 --- a/lib/mobilizon_web/schema/event.ex +++ b/lib/mobilizon_web/schema/event.ex @@ -22,7 +22,7 @@ defmodule MobilizonWeb.Schema.EventType do field(:begins_on, :datetime, description: "Datetime for when the event begins") field(:ends_on, :datetime, description: "Datetime for when the event ends") field(:status, :event_status, description: "Status of the event") - field(:visibility, :event_visibility, description: "The event's visibility") + field(:visibility, :event_visibility, description: "The event's visibility") field(:picture, :picture, description: "The event's picture", @@ -132,6 +132,7 @@ defmodule MobilizonWeb.Schema.EventType do arg(:phone_address, :string) arg(:organizer_actor_id, non_null(:id)) arg(:category, non_null(:string)) + arg(:physical_address, :address_input) resolve(&Event.create_event/3) end diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 0efdc5dcf..483a7a14f 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -70,10 +70,11 @@ defmodule Mobilizon.Service.ActivityPub do def fetch_object_from_url(url) do Logger.info("Fetching object from url #{url}") - with true <- String.starts_with?(url, "http"), - nil <- Events.get_event_by_url(url), - nil <- Events.get_comment_from_url(url), - {:error, :actor_not_found} <- Actors.get_actor_by_url(url), + with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")}, + {:existing_event, nil} <- {:existing_event, Events.get_event_by_url(url)}, + {:existing_comment, nil} <- {:existing_comment, Events.get_comment_from_url(url)}, + {:existing_actor, {:error, :actor_not_found}} <- + {:existing_actor, Actors.get_actor_by_url(url)}, {:ok, %{body: body, status_code: code}} when code in 200..299 <- HTTPoison.get( url, @@ -90,25 +91,32 @@ defmodule Mobilizon.Service.ActivityPub do "actor" => data["attributedTo"], "object" => data }, - {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, _activity, %{url: object_url} = _object} <- Transmogrifier.handle_incoming(params) do case data["type"] do "Event" -> - {:ok, Events.get_event_by_url!(activity.data["object"]["id"])} + {:ok, Events.get_event_by_url!(object_url)} "Note" -> - {:ok, Events.get_comment_full_from_url!(activity.data["object"]["id"])} + {:ok, Events.get_comment_full_from_url!(object_url)} "Actor" -> - {:ok, Actors.get_actor_by_url!(activity.data["object"]["id"], true)} + {:ok, Actors.get_actor_by_url!(object_url, true)} other -> {:error, other} end else - %Event{url: event_url} -> {:ok, Events.get_event_by_url!(event_url)} - %Comment{url: comment_url} -> {:ok, Events.get_comment_full_from_url!(comment_url)} - %Actor{url: actor_url} -> {:ok, Actors.get_actor_by_url!(actor_url, true)} - e -> {:error, e} + {:existing_event, %Event{url: event_url}} -> + {:ok, Events.get_event_by_url!(event_url)} + + {:existing_comment, %Comment{url: comment_url}} -> + {:ok, Events.get_comment_full_from_url!(comment_url)} + + {:existing_actor, %Actor{url: actor_url}} -> + {:ok, Actors.get_actor_by_url!(actor_url, true)} + + e -> + {:error, e} end end @@ -130,10 +138,10 @@ defmodule Mobilizon.Service.ActivityPub do additional ), :ok <- Logger.debug(inspect(create_data)), - {:ok, activity, _object} <- insert(create_data, local), + {:ok, activity, object} <- insert(create_data, local), :ok <- maybe_federate(activity) do # {:ok, actor} <- Actors.increase_event_count(actor) do - {:ok, activity} + {:ok, activity, object} else err -> Logger.error("Something went wrong") @@ -147,9 +155,9 @@ defmodule Mobilizon.Service.ActivityPub do local = !(params[:local] == false) with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} end end @@ -164,9 +172,9 @@ defmodule Mobilizon.Service.ActivityPub do "actor" => actor, "object" => object }, - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} end end @@ -179,7 +187,7 @@ defmodule Mobilizon.Service.ActivityPub do # ) do # with nil <- get_existing_like(url, object), # like_data <- make_like_data(user, object, activity_id), - # {:ok, activity, _object} <- insert(like_data, local), + # {:ok, activity, object} <- insert(like_data, local), # {:ok, object} <- add_like_to_object(activity, object), # :ok <- maybe_federate(activity) do # {:ok, activity, object} @@ -215,7 +223,7 @@ defmodule Mobilizon.Service.ActivityPub do # ) do # #with true <- is_public?(object), # with announce_data <- make_announce_data(actor, object, activity_id), - # {:ok, activity, _object} <- insert(announce_data, local), + # {:ok, activity, object} <- insert(announce_data, local), # # {:ok, object} <- add_announce_to_object(activity, object), # :ok <- maybe_federate(activity) do # {:ok, activity, object} @@ -250,9 +258,9 @@ defmodule Mobilizon.Service.ActivityPub do activity_follow_id <- activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity", data <- make_follow_data(followed, follower, activity_follow_id), - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} else {err, _} when err in [:already_following, :suspended] -> {:error, err} @@ -269,9 +277,9 @@ defmodule Mobilizon.Service.ActivityPub do data <- make_follow_data(followed, follower, follow_id), {:ok, follow_activity, _object} <- insert(data, local), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), - {:ok, activity, _object} <- insert(unfollow_data, local), + {:ok, activity, object} <- insert(unfollow_data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} else err -> Logger.error(inspect(err)) @@ -290,9 +298,9 @@ defmodule Mobilizon.Service.ActivityPub do } with {:ok, _} <- Events.delete_event(event), - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} end end @@ -305,9 +313,9 @@ defmodule Mobilizon.Service.ActivityPub do } with {:ok, _} <- Events.delete_comment(comment), - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} end end @@ -320,9 +328,9 @@ defmodule Mobilizon.Service.ActivityPub do } with {:ok, _} <- Actors.delete_actor(actor), - {:ok, activity, _object} <- insert(data, local), + {:ok, activity, object} <- insert(data, local), :ok <- maybe_federate(activity) do - {:ok, activity} + {:ok, activity, object} end end diff --git a/lib/service/activity_pub/converters/address.ex b/lib/service/activity_pub/converters/address.ex new file mode 100644 index 000000000..d6d0cc1c3 --- /dev/null +++ b/lib/service/activity_pub/converters/address.ex @@ -0,0 +1,58 @@ +defmodule Mobilizon.Service.ActivityPub.Converters.Address do + @moduledoc """ + Flag converter + + This module allows to convert reports from ActivityStream format to our own internal one, and back. + + Note: Reports are named Flag in AS. + """ + alias Mobilizon.Addresses.Address, as: AddressModel + alias Mobilizon.Service.ActivityPub.Converter + + @behaviour Converter + + @doc """ + Converts an AP object data to our internal data structure + """ + @impl Converter + @spec as_to_model_data(map()) :: map() + def as_to_model_data(object) do + res = %{ + "description" => object["name"], + "url" => object["url"] + } + + res = + if is_nil(object["address"]) do + res + else + Map.merge(res, %{ + "country" => object["address"]["addressCountry"], + "postal_code" => object["address"]["postalCode"], + "region" => object["address"]["addressRegion"], + "street" => object["address"]["streetAddress"], + "locality" => object["address"]["addressLocality"] + }) + end + + if is_nil(object["geo"]) do + res + else + geo = %Geo.Point{ + coordinates: {object["geo"]["latitude"], object["geo"]["longitude"]}, + srid: 4326 + } + + Map.put(res, "geom", geo) + end + end + + @doc """ + Convert an event struct to an ActivityStream representation + """ + @impl Converter + @spec model_to_as(AddressModel.t()) :: map() + def model_to_as(%AddressModel{} = _address) do + nil + end +end diff --git a/lib/service/activity_pub/converters/event.ex b/lib/service/activity_pub/converters/event.ex index 64021611a..1c6a64500 100644 --- a/lib/service/activity_pub/converters/event.ex +++ b/lib/service/activity_pub/converters/event.ex @@ -12,19 +12,28 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do alias Mobilizon.Service.ActivityPub.Converter alias Mobilizon.Events alias Mobilizon.Events.Tag + alias Mobilizon.Addresses + alias Mobilizon.Addresses.Address @behaviour Converter + require Logger + @doc """ Converts an AP object data to our internal data structure """ @impl Converter @spec as_to_model_data(map()) :: map() def as_to_model_data(object) do - with {:ok, %Actor{id: actor_id}} <- Actors.get_actor_by_url(object["actor"]), - tags <- fetch_tags(object["tag"]) do + Logger.debug("event as_to_model_data") + + with {:actor, {:ok, %Actor{id: actor_id}}} <- + {:actor, Actors.get_actor_by_url(object["actor"])}, + {:address, address_id} <- + {:address, get_address(object["location"])}, + {:tags, tags} <- {:tags, fetch_tags(object["tag"])} do picture_id = - with true <- Map.has_key?(object, "attachment"), + with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0, %Picture{id: picture_id} <- Media.get_picture_by_url( object["attachment"] @@ -38,27 +47,64 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do _ -> nil end - %{ - "title" => object["name"], - "description" => object["content"], - "organizer_actor_id" => actor_id, - "picture_id" => picture_id, - "begins_on" => object["begins_on"], - "category" => object["category"], - "url" => object["id"], - "uuid" => object["uuid"], - "tags" => tags - } + {: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 + }} + else + err -> + {:error, err} + end + end + + defp get_address(%{"id" => url} = map) when is_map(map) and is_binary(url) do + Logger.debug("Address with an URL, let's check against our own database") + + case Addresses.get_address_by_url(url) do + %Address{id: address_id} -> + address_id + + _ -> + Logger.debug("not in our database, let's try to create it") + map = Map.put(map, "url", map["id"]) + do_get_address(map) + end + end + + defp get_address(map) when is_map(map) do + do_get_address(map) + end + + defp get_address(nil), do: nil + + defp do_get_address(map) do + map = Mobilizon.Service.ActivityPub.Converters.Address.as_to_model_data(map) + + case Addresses.create_address(map) do + {:ok, %Address{id: address_id}} -> + address_id + + _ -> + nil end end defp fetch_tags(tags) do Enum.reduce(tags, [], fn tag, acc -> - case Events.get_or_create_tag(tag) do - {:ok, %Tag{} = tag} -> - acc ++ [tag] - - _ -> + with true <- tag["type"] == "Hashtag", + {:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do + acc ++ [tag] + else + _err -> acc end end) diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex index 5f4658b37..d2022b78f 100644 --- a/lib/service/activity_pub/transmogrifier.ex +++ b/lib/service/activity_pub/transmogrifier.ex @@ -132,9 +132,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do end end - # TODO: validate those with a Ecto scheme - # - tags - # - emoji def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do Logger.info("Handle incoming to create notes") @@ -159,15 +156,39 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do end end + def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do + Logger.info("Handle incoming to create event") + + with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do + Logger.debug("found actor") + Logger.debug(inspect(actor)) + + params = %{ + to: data["to"], + object: object |> fix_object, + actor: actor, + local: false, + published: data["published"], + additional: + Map.take(data, [ + "cc", + "id" + ]) + } + + ActivityPub.create(params) + end + end + def handle_incoming( %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data ) do with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true), {:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower), - {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do + {:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true}) - {:ok, activity} + {:ok, activity, object} else e -> Logger.error("Unable to handle Follow activity") @@ -257,9 +278,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do ) do with {:ok, %Actor{domain: nil} = followed} <- Actors.get_actor_by_url(followed), {:ok, %Actor{} = follower} <- Actors.get_actor_by_url(follower), - {:ok, activity} <- ActivityPub.unfollow(followed, follower, id, false) do + {:ok, activity, object} <- ActivityPub.unfollow(followed, follower, id, false) do Actor.unfollow(follower, followed) - {:ok, activity} + {:ok, activity, object} else e -> Logger.error(inspect(e)) @@ -282,11 +303,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), # TODO : Validate that DELETE comes indeed form right domain (see above) # :ok <- contain_origin(actor_url, object.data), - {:ok, activity} <- ActivityPub.delete(object, false) do - {:ok, activity} + {:ok, activity, object} <- ActivityPub.delete(object, false) do + {:ok, activity, object} else e -> - Logger.debug(inspect(e)) + Logger.error(inspect(e)) :error end end diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex index 8f1b9b5ca..59c0b1c45 100644 --- a/lib/service/activity_pub/utils.ex +++ b/lib/service/activity_pub/utils.ex @@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do """ alias Mobilizon.Repo + alias Mobilizon.Addresses.Address alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Events.Event @@ -122,7 +123,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do """ def insert_full_object(%{"object" => %{"type" => "Event"} = object_data}) when is_map(object_data) do - with object_data <- + with {:ok, object_data} <- Converters.Event.as_to_model_data(object_data), {:ok, %Event{} = event} <- Events.create_event(object_data) do {:ok, event} @@ -260,26 +261,21 @@ defmodule Mobilizon.Service.ActivityPub.Utils do """ @spec make_event_data( String.t(), - String.t(), + map(), String.t(), String.t(), map(), list(), - list(), - map(), - String.t() + map() ) :: map() def make_event_data( actor, - to, + %{to: to, cc: cc} = _audience, title, content_html, picture \\ nil, tags \\ [], - # _cw \\ nil, - cc \\ [], - metadata \\ %{}, - category \\ "" + metadata \\ %{} ) do Logger.debug("Making event data") uuid = Ecto.UUID.generate() @@ -287,21 +283,58 @@ defmodule Mobilizon.Service.ActivityPub.Utils do res = %{ "type" => "Event", "to" => to, - "cc" => cc, + "cc" => cc || [], "content" => content_html, "name" => title, - # "summary" => cw, - "begins_on" => metadata.begins_on, - "category" => category, + "startTime" => metadata.begins_on, + "category" => metadata.category, "actor" => actor, "id" => Routes.page_url(Endpoint, :event, uuid), "uuid" => uuid, - "tag" => tags |> Enum.uniq() + "tag" => + tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end) } + res = + if is_nil(metadata.physical_address), + 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)]) end + def make_address_data(%Address{} = address) do + res = %{ + "type" => "Place", + "name" => address.description, + "id" => address.url, + "address" => %{ + "type" => "PostalAddress", + "streetAddress" => address.street, + "postalCode" => address.postal_code, + "addressLocality" => address.locality, + "addressRegion" => address.region, + "addressCountry" => address.country + } + } + + if is_nil(address.geom) do + res + else + Map.put(res, "geo", %{ + "type" => "GeoCoordinates", + "latitude" => address.geom.coordinates |> elem(0), + "longitude" => address.geom.coordinates |> elem(1) + }) + end + end + + def make_address_data(address) do + Address + |> struct(address) + |> make_address_data() + end + @doc """ Make an AP comment object from an set of values """ diff --git a/lib/service/federator.ex b/lib/service/federator.ex index 6be044bcd..da9d54205 100644 --- a/lib/service/federator.ex +++ b/lib/service/federator.ex @@ -52,7 +52,7 @@ defmodule Mobilizon.Service.Federator do Logger.debug(inspect(params)) case Transmogrifier.handle_incoming(params) do - {:ok, activity} -> + {:ok, activity, _} -> {:ok, activity} %Activity{} -> diff --git a/priv/repo/migrations/20190729125532_add-url-to-addresses.exs b/priv/repo/migrations/20190729125532_add-url-to-addresses.exs new file mode 100644 index 000000000..41ed31bd6 --- /dev/null +++ b/priv/repo/migrations/20190729125532_add-url-to-addresses.exs @@ -0,0 +1,9 @@ +defmodule :"Elixir.Mobilizon.Repo.Migrations.Add-url-to-addresses" do + use Ecto.Migration + + def change do + alter table(:addresses) do + add(:url, :string, null: false) + end + end +end diff --git a/schema.graphql b/schema.graphql index 57cf761a6..e69b2c165 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,5 +1,5 @@ # source: http://localhost:4000/api -# timestamp: Fri Jul 26 2019 11:28:32 GMT+0200 (GMT+02:00) +# timestamp: Mon Jul 29 2019 15:24:10 GMT+0200 (GMT+02:00) schema { query: RootQueryType @@ -121,6 +121,25 @@ type Address { street: String } +input AddressInput { + country: String + description: String + + """The floor this event is at""" + floor: String + + """The geocoordinates for the point where this address is""" + geom: Point + + """The address's locality""" + locality: String + postalCode: String + region: String + + """The address's street name (with number)""" + street: String +} + """A comment""" type Comment { """Internal ID for this comment""" @@ -262,7 +281,7 @@ type Event { """The Event UUID""" uuid: UUID - """The event's visibility""" + """The event's visibility""" visibility: EventVisibility } @@ -675,6 +694,7 @@ type RootMutationType { onlineAddress: String organizerActorId: ID! phoneAddress: String + physicalAddress: AddressInput """ The picture for the event, either as an object or directly the ID of an existing Picture @@ -891,7 +911,7 @@ type RootQueryType { reverseGeocode(latitude: Float!, longitude: Float!): [Address] """Search for an address""" - searchAddress(query: String!): [Address] + searchAddress(limit: Int = 10, page: Int = 1, query: String!): [Address] """Search events""" searchEvents(limit: Int = 10, page: Int = 1, search: String!): Events diff --git a/test/fixtures/mobilizon-post-activity.json b/test/fixtures/mobilizon-post-activity.json new file mode 100644 index 000000000..b21f4bfa3 --- /dev/null +++ b/test/fixtures/mobilizon-post-activity.json @@ -0,0 +1,66 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "mblzn": "https://joinmobilizon.org/ns#", + "Hashtag": "as:Hashtag", + "sc": "http://schema.org#", + "Place": "sc:Place", + "PostalAddress": "sc:PostalAddress", + "uuid": "sc:identifier" + } + ], + "actor": "https://event1.tcit.fr/@tcit", + "cc": [ + "https://framapiaf.org/users/admin/followers", + "http://localtesting.pleroma.lol/users/lain" + ], + "id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c/activity", + "object": { + "attachment": [], + "attributedTo": "https://event1.tcit.fr/@tcit", + "startTime": "2018-02-12T14:08:20Z", + "cc": [ + "https://framapiaf.org/users/admin/followers", + "http://localtesting.pleroma.lol/users/lain" + ], + "content": "

@lain

", + "category": "TODO remove me", + "id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c", + "inReplyTo": null, + "location": { + "type": "Place", + "name": "Locaux de Framasoft", + "id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd", + "address": { + "type": "PostalAddress", + "streetAddress": "10 Rue Jangot", + "postalCode": "69007", + "addressLocality": "Lyon", + "addressRegion": "Auvergne Rhône Alpes", + "addressCountry": "France" + } + }, + "name": "My first event", + "published": "2018-02-12T14:08:20Z", + "tag": [ + { + "href": "http://localtesting.pleroma.lol/users/lain", + "name": "@lain@localtesting.pleroma.lol", + "type": "Mention" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Event", + "url": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c", + "uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c" + }, + "published": "2018-02-12T14:08:20Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Create" +} diff --git a/test/mobilizon/addresses/addresses_test.exs b/test/mobilizon/addresses/addresses_test.exs index 04b6f78b1..8f9e267fe 100644 --- a/test/mobilizon/addresses/addresses_test.exs +++ b/test/mobilizon/addresses/addresses_test.exs @@ -1,6 +1,7 @@ defmodule Mobilizon.AddressesTest do use Mobilizon.DataCase + import Mobilizon.Factory alias Mobilizon.Addresses describe "addresses" do @@ -37,22 +38,13 @@ defmodule Mobilizon.AddressesTest do # geom: nil # } - def address_fixture(attrs \\ %{}) do - {:ok, address} = - attrs - |> Enum.into(@valid_attrs) - |> Addresses.create_address() - - address - end - test "list_addresses/0 returns all addresses" do - address = address_fixture() + address = insert(:address) assert [address.id] == Addresses.list_addresses() |> Enum.map(& &1.id) end test "get_address!/1 returns the address with given id" do - address = address_fixture() + address = insert(:address) assert Addresses.get_address!(address.id).id == address.id end @@ -68,7 +60,7 @@ defmodule Mobilizon.AddressesTest do end test "update_address/2 with valid data updates the address" do - address = address_fixture() + address = insert(:address) assert {:ok, %Address{} = address} = Addresses.update_address(address, @update_attrs) assert address.country == "some updated addressCountry" assert address.locality == "some updated addressLocality" @@ -80,13 +72,13 @@ defmodule Mobilizon.AddressesTest do end test "delete_address/1 deletes the address" do - address = address_fixture() + address = insert(:address) assert {:ok, %Address{}} = Addresses.delete_address(address) assert_raise Ecto.NoResultsError, fn -> Addresses.get_address!(address.id) end end test "change_address/1 returns a address changeset" do - address = address_fixture() + address = insert(:address) assert %Ecto.Changeset{} = Addresses.change_address(address) end diff --git a/test/mobilizon/service/activity_pub/activity_pub_test.exs b/test/mobilizon/service/activity_pub/activity_pub_test.exs index c452af8d0..12aa5f373 100644 --- a/test/mobilizon/service/activity_pub/activity_pub_test.exs +++ b/test/mobilizon/service/activity_pub/activity_pub_test.exs @@ -56,7 +56,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do test "removes doubled 'to' recipients" do actor = insert(:actor) - {:ok, activity} = + {:ok, activity, _} = ActivityPub.create(%{ to: ["user1", "user1", "user2"], actor: actor, @@ -113,7 +113,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do test "it creates a delete activity and deletes the original event" do event = insert(:event) event = Events.get_event_full_by_url!(event.url) - {:ok, delete} = ActivityPub.delete(event) + {:ok, delete, _} = ActivityPub.delete(event) assert delete.data["type"] == "Delete" assert delete.data["actor"] == event.organizer_actor.url @@ -125,7 +125,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do test "it creates a delete activity and deletes the original comment" do comment = insert(:comment) comment = Events.get_comment_full_from_url!(comment.url) - {:ok, delete} = ActivityPub.delete(comment) + {:ok, delete, _} = ActivityPub.delete(comment) assert delete.data["type"] == "Delete" assert delete.data["actor"] == comment.actor.url @@ -140,7 +140,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do actor = insert(:actor) actor_data = MobilizonWeb.ActivityPub.ActorView.render("actor.json", %{actor: actor}) - {:ok, update} = + {:ok, update, _} = ActivityPub.update(%{ actor: actor_data["url"], to: [actor.url <> "/followers"], diff --git a/test/mobilizon/service/activity_pub/transmogrifier_test.exs b/test/mobilizon/service/activity_pub/transmogrifier_test.exs index ffd8b32cf..56e3e296f 100644 --- a/test/mobilizon/service/activity_pub/transmogrifier_test.exs +++ b/test/mobilizon/service/activity_pub/transmogrifier_test.exs @@ -12,7 +12,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do alias Mobilizon.Actors alias Mobilizon.Actors.Actor alias Mobilizon.Events - alias Mobilizon.Events.Comment + alias Mobilizon.Events.{Comment, Event} alias Mobilizon.Service.ActivityPub.Utils alias Mobilizon.Service.ActivityPub.Transmogrifier use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney @@ -21,7 +21,51 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do HTTPoison.start() end - describe "handle_incoming" do + describe "handle incoming events" do + test "it works for incoming events" do + data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!() + + {:ok, %Mobilizon.Activity{data: data, local: false}, %Event{} = event} = + Transmogrifier.handle_incoming(data) + + assert data["id"] == + "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c/activity" + + assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + # + # assert data["cc"] == [ + # "https://framapiaf.org/users/admin/followers", + # "http://localtesting.pleroma.lol/users/lain" + # ] + + assert data["actor"] == "https://event1.tcit.fr/@tcit" + + object = data["object"] + + assert object["id"] == + "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c" + + assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + + # assert object["cc"] == [ + # "https://framapiaf.org/users/admin/followers", + # "http://localtesting.pleroma.lol/users/lain" + # ] + + assert object["actor"] == "https://event1.tcit.fr/@tcit" + assert object["location"]["name"] == "Locaux de Framasoft" + assert object["attributedTo"] == "https://event1.tcit.fr/@tcit" + + assert event.physical_address.street == "10 Rue Jangot" + + assert event.physical_address.url == + "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd" + + {:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"]) + end + end + + describe "handle incoming notices" do # test "it ignores an incoming comment if we already have it" do # comment = insert(:comment) @@ -37,7 +81,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # |> Jason.decode!() # |> Map.put("object", activity["object"]) - # {:ok, returned_activity} = Transmogrifier.handle_incoming(data) + # {:ok, returned_activity, _} = Transmogrifier.handle_incoming(data) # assert activity == returned_activity.data # end @@ -55,7 +99,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # data # |> Map.put("object", object) - # {:ok, returned_activity} = Transmogrifier.handle_incoming(data) + # {:ok, returned_activity, _} = Transmogrifier.handle_incoming(data) # assert activity = # Activity.get_create_activity_by_object_ap_id( @@ -71,7 +115,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it works for incoming notices" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() - {:ok, %Mobilizon.Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Mobilizon.Activity{data: data, local: false}, _} = + Transmogrifier.handle_incoming(data) assert data["id"] == "https://framapiaf.org/users/admin/statuses/99512778738411822/activity" @@ -105,7 +150,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it works for incoming notices with hashtags" do data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!() - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) assert Enum.at(data["object"]["tag"], 2) == "moo" end @@ -113,7 +158,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # data = # File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!() - # {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + # {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) # assert data["object"]["content"] == # "

@lain

" @@ -122,7 +167,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # test "it works for incoming notices with to/cc not being an array (kroeg)" do # data = File.read!("test/fixtures/kroeg-post-activity.json") |> Jason.decode!() - # {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + # {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) # assert data["object"]["content"] == # "

henlo from my Psion netBook

message sent from my Psion netBook

" @@ -131,7 +176,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # test "it works for incoming announces with actor being inlined (kroeg)" do # data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Jason.decode!() - # {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + # {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) # assert data["actor"] == "https://puckipedia.com/" # end @@ -139,7 +184,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # test "it works for incoming notices with tag not being an array (kroeg)" do # data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Jason.decode!() - # {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + # {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) # assert data["object"]["emoji"] == %{ # "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png" @@ -147,7 +192,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do # data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Jason.decode!() - # {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + # {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) # assert "test" in data["object"]["tag"] # end @@ -170,7 +215,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do |> Jason.decode!() |> Map.put("object", actor.url) - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) assert data["actor"] == "https://social.tcit.fr/users/tcit" assert data["type"] == "Follow" @@ -289,7 +334,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it works for incoming update activities" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!() object = @@ -302,7 +347,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do |> Map.put("actor", data["actor"]) |> Map.put("object", object) - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) + {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(update_data) {:ok, %Actor{} = actor} = Actors.get_actor_by_url(data["actor"]) assert actor.name == "gargle" @@ -352,7 +397,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do assert Events.get_comment_from_url(comment_url) - {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(data) refute Events.get_comment_from_url(comment_url) end @@ -413,14 +458,14 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do |> Jason.decode!() |> Map.put("object", actor.url) - {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data) + {:ok, %Activity{data: _, local: false}, _} = Transmogrifier.handle_incoming(follow_data) data = File.read!("test/fixtures/mastodon-unfollow-activity.json") |> Jason.decode!() |> Map.put("object", follow_data) - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + {:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data) assert data["type"] == "Undo" assert data["object"]["type"] == "Follow" @@ -706,7 +751,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do actor = insert(:actor) other_actor = insert(:actor) - {:ok, activity} = + {:ok, activity, _} = MobilizonWeb.API.Comments.create_comment( actor.preferred_username, "hey, @#{other_actor.preferred_username}, how are ya? #2hu" @@ -743,7 +788,9 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it adds the json-ld context and the conversation property" do actor = insert(:actor) - {:ok, activity} = MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "hey") + {:ok, activity, _} = + MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "hey") + {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["@context"] == Utils.make_json_ld_header()["@context"] @@ -752,7 +799,9 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do actor = insert(:actor) - {:ok, activity} = MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "hey") + {:ok, activity, _} = + MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "hey") + {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["object"]["actor"] == modified["object"]["attributedTo"] @@ -761,7 +810,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it strips internal hashtag data" do actor = insert(:actor) - {:ok, activity} = MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "#2hu") + {:ok, activity, _} = + MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "#2hu") expected_tag = %{ "href" => MobilizonWeb.Endpoint.url() <> "/tags/2hu", @@ -777,7 +827,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do test "it strips internal fields" do actor = insert(:actor) - {:ok, activity} = MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "#2hu") + {:ok, activity, _} = + MobilizonWeb.API.Comments.create_comment(actor.preferred_username, "#2hu") {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) diff --git a/test/mobilizon_web/resolvers/event_resolver_test.exs b/test/mobilizon_web/resolvers/event_resolver_test.exs index 3cce2fd09..f1d1e52ba 100644 --- a/test/mobilizon_web/resolvers/event_resolver_test.exs +++ b/test/mobilizon_web/resolvers/event_resolver_test.exs @@ -122,6 +122,95 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do ] end + test "create_event/3 creates an event with an address", %{ + conn: conn, + actor: actor, + user: user + } do + address = insert(:address) + + mutation = """ + mutation { + createEvent( + title: "my event is referenced", + description: "with tags!", + begins_on: "#{ + DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() + }", + organizer_actor_id: "#{actor.id}", + category: "birthday", + physical_address: { + street: "#{address.street}", + locality: "#{address.locality}" + } + ) { + title, + uuid, + physicalAddress { + url, + geom, + street + } + } + } + """ + + res = + conn + |> auth_conn(user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["errors"] == nil + + assert json_response(res, 200)["data"]["createEvent"]["title"] == "my event is referenced" + + assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] == + address.street + + refute json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"] == + address.url + + mutation = """ + mutation { + createEvent( + title: "my event is referenced", + description: "with tags!", + begins_on: "#{ + DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() + }", + organizer_actor_id: "#{actor.id}", + category: "birthday", + physical_address: { + url: "#{address.url}" + } + ) { + title, + uuid, + physicalAddress { + url, + geom, + street + } + } + } + """ + + res = + conn + |> auth_conn(user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["errors"] == nil + + assert json_response(res, 200)["data"]["createEvent"]["title"] == "my event is referenced" + + assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] == + address.street + + assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"] == + address.url + end + test "create_event/3 creates an event with an attached picture", %{ conn: conn, actor: actor, diff --git a/test/support/factory.ex b/test/support/factory.ex index 0d417cdda..b8ee13cf4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -78,6 +78,7 @@ defmodule Mobilizon.Factory do %Mobilizon.Addresses.Address{ description: sequence("MyAddress"), geom: %Geo.Point{coordinates: {45.75, 4.85}, srid: 4326}, + url: "http://mobilizon.test/address/#{Ecto.UUID.generate()}", floor: "Myfloor", country: "My Country", locality: "My Locality",