Merge branch 'event/create-with-address' into 'master'

Add address input and refactor federation stuff

See merge request framasoft/mobilizon!163
This commit is contained in:
Thomas Citharel 2019-07-30 13:15:11 +02:00
commit 3fa2bd35d8
34 changed files with 729 additions and 192 deletions

View file

@ -50,6 +50,8 @@ config :mobilizon, MobilizonWeb.Endpoint,
# Do not include metadata nor timestamps in development logs # Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n", level: :debug 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 # Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive. # in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20 config :phoenix, :stacktrace_depth, 20

View file

@ -0,0 +1,53 @@
<template>
<b-field label="Find an address">
<b-autocomplete
:data="data"
placeholder="e.g. 10 Rue Jangot"
field="description"
:loading="isFetching"
@typing="getAsyncData"
@select="option => selected = option">
<template slot-scope="{option}">
<b>{{ option.description }}</b>
<p>
<small>{{ option.street }}</small>,&#32;
<small>{{ option.locality }}</small>
</p>
</template>
</b-autocomplete>
</b-field>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { IAddress } from '@/types/address.model';
import { ADDRESS } from '@/graphql/address';
@Component
export default class AddressAutoComplete extends Vue {
@Prop({ required: false, default: () => [] }) initialData!: IAddress[];
data: IAddress[] = this.initialData;
selected: IAddress|null = null;
isFetching: boolean = false;
async getAsyncData(query) {
if (!query.length) {
this.data = [];
return;
}
this.isFetching = true;
const result = await this.$apollo.query({
query: ADDRESS,
variables: { query },
});
this.data = result.data.searchAddress as IAddress[];
}
@Watch("selected")
updateSelected() {
this.$emit('input', this.selected);
}
}
</script>

18
js/src/graphql/address.ts Normal file
View file

@ -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
}
}
`;

View file

@ -144,7 +144,8 @@ export const CREATE_EVENT = gql`
$category: String!, $category: String!,
$beginsOn: DateTime!, $beginsOn: DateTime!,
$picture: PictureInput, $picture: PictureInput,
$tags: [String] $tags: [String],
$physicalAddress: AddressInput!
) { ) {
createEvent( createEvent(
title: $title, title: $title,
@ -153,7 +154,8 @@ export const CREATE_EVENT = gql`
organizerActorId: $organizerActorId, organizerActorId: $organizerActorId,
category: $category, category: $category,
picture: $picture, picture: $picture,
tags: $tags tags: $tags,
physicalAddress: $physicalAddress
) { ) {
id, id,
uuid, uuid,

View file

@ -1,4 +1,5 @@
export interface IAddress { export interface IAddress {
id: number;
description: string; description: string;
floor: string; floor: string;
street: string; street: string;

View file

@ -14,6 +14,8 @@
<tag-input v-model="event.tags" :data="tags" path="title" /> <tag-input v-model="event.tags" :data="tags" path="title" />
<address-auto-complete v-model="event.physicalAddress" />
<date-time-picker v-model="event.beginsOn" :label="$gettext('Starts on…')" :step="15"/> <date-time-picker v-model="event.beginsOn" :label="$gettext('Starts on…')" :step="15"/>
<date-time-picker v-model="event.endsOn" :label="$gettext('Ends on…')" :step="15" /> <date-time-picker v-model="event.endsOn" :label="$gettext('Ends on…')" :step="15" />
@ -57,9 +59,10 @@ import DateTimePicker from '@/components/Event/DateTimePicker.vue';
import TagInput from '@/components/Event/TagInput.vue'; import TagInput from '@/components/Event/TagInput.vue';
import { TAGS } from '@/graphql/tags'; import { TAGS } from '@/graphql/tags';
import { ITag } from '@/types/tag.model'; import { ITag } from '@/types/tag.model';
import AddressAutoComplete from '@/components/Event/AddressAutoComplete.vue';
@Component({ @Component({
components: { TagInput, DateTimePicker, PictureUpload, Editor }, components: { AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor },
apollo: { apollo: {
loggedPerson: { loggedPerson: {
query: LOGGED_PERSON, query: LOGGED_PERSON,
@ -134,9 +137,13 @@ export default class CreateEvent extends Vue {
const obj = { const obj = {
organizerActorId: this.loggedPerson.id, organizerActorId: this.loggedPerson.id,
beginsOn: this.event.beginsOn.toISOString(), 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 * Transform picture files

View file

@ -11,10 +11,10 @@ defmodule Mix.Tasks.Mobilizon.Toot do
Mix.Task.run("app.start") Mix.Task.run("app.start")
case MobilizonWeb.API.Comments.create_comment(from, content) do case MobilizonWeb.API.Comments.create_comment(from, content) do
{:ok, _} -> {:ok, _, _} ->
Mix.shell().info("Tooted") Mix.shell().info("Tooted")
{:local_actor, _} -> {:local_actor, _, _} ->
Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist") Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
_ -> _ ->

View file

@ -6,6 +6,20 @@ defmodule Mobilizon.Addresses.Address do
alias Mobilizon.Addresses.Address alias Mobilizon.Addresses.Address
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
# alias Mobilizon.Actors.Actor # alias Mobilizon.Actors.Actor
@attrs [
:description,
:floor,
:geom,
:country,
:locality,
:region,
:postal_code,
:street,
:url
]
@required [
:url
]
schema "addresses" do schema "addresses" do
field(:country, :string) field(:country, :string)
@ -16,8 +30,8 @@ defmodule Mobilizon.Addresses.Address do
field(:geom, Geo.PostGIS.Geometry) field(:geom, Geo.PostGIS.Geometry)
field(:postal_code, :string) field(:postal_code, :string)
field(:street, :string) field(:street, :string)
has_one(:event, Event, foreign_key: :physical_address_id) field(:url, :string)
# has_one(:group, Actor) has_many(:event, Event, foreign_key: :physical_address_id)
timestamps() timestamps()
end end
@ -25,15 +39,15 @@ defmodule Mobilizon.Addresses.Address do
@doc false @doc false
def changeset(%Address{} = address, attrs) do def changeset(%Address{} = address, attrs) do
address address
|> cast(attrs, [ |> cast(attrs, @attrs)
:description, |> set_url()
:floor, |> validate_required(@required)
:geom, end
:country,
:locality, defp set_url(%Ecto.Changeset{changes: changes} = changeset) do
:region, url =
:postal_code, Map.get(changes, :url, MobilizonWeb.Endpoint.url() <> "/address/#{Ecto.UUID.generate()}")
:street
]) put_change(changeset, :url, url)
end end
end end

View file

@ -50,6 +50,21 @@ defmodule Mobilizon.Addresses do
""" """
def get_address!(id), do: Repo.get!(Address, id) 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 """ @doc """
Creates a address. 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 We only look at the description for now, and eventually order by object distance
""" """
@spec search_addresses(String.t(), list()) :: list(Address.t()) @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) limit = Keyword.get(options, :limit, 5)
query = from(a in Address, where: ilike(a.description, ^"%#{search}%"), limit: ^limit) 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}%")), do: from(a in query, where: ilike(a.country, ^"%#{country}%")),
else: query else: query
Repo.all(query) if Keyword.get(options, :single, false) == true, do: Repo.one(query), else: Repo.all(query)
end end
@doc """ @doc """

View file

@ -84,9 +84,9 @@ defmodule Mobilizon.Events.Event do
:online_address, :online_address,
:phone_address, :phone_address,
:uuid, :uuid,
:picture_id :picture_id,
:physical_address_id
]) ])
|> cast_assoc(:physical_address)
|> validate_required([ |> validate_required([
:title, :title,
:begins_on, :begins_on,

View file

@ -382,7 +382,8 @@ defmodule Mobilizon.Events do
defp do_create_event(attrs) do defp do_create_event(attrs) do
with {:ok, %Event{} = event} <- %Event{} |> Event.changeset(attrs) |> Repo.insert(), 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 {:has_tags, true, _} <- {:has_tags, Map.has_key?(attrs, "tags"), event} do
event event
|> Ecto.Changeset.change() |> Ecto.Changeset.change()
@ -513,8 +514,10 @@ defmodule Mobilizon.Events do
@doc """ @doc """
Get an existing tag or create one Get an existing tag or create one
""" """
@spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, any()} @spec get_or_create_tag(map()) :: {:ok, Tag.t()} | {:error, any()}
def get_or_create_tag(title) do def get_or_create_tag(tag) do
"#" <> title = tag["name"]
case Repo.get_by(Tag, title: title) do case Repo.get_by(Tag, title: title) do
%Tag{} = tag -> %Tag{} = tag ->
{:ok, tag} {:ok, tag}
@ -1223,9 +1226,13 @@ defmodule Mobilizon.Events do
""" """
def create_comment(attrs \\ %{}) do def create_comment(attrs \\ %{}) do
with {:ok, %Comment{} = comment} <-
%Comment{} %Comment{}
|> Comment.changeset(attrs) |> Comment.changeset(attrs)
|> Repo.insert() |> Repo.insert(),
%Comment{} = comment <- Repo.preload(comment, [:actor, :in_reply_to_comment]) do
{:ok, comment}
end
end end
@doc """ @doc """

View file

@ -15,7 +15,8 @@ defmodule MobilizonWeb.API.Comments do
Creates a comment from an actor and a status 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( def create_comment(
from_username, from_username,
status, status,

View file

@ -2,6 +2,7 @@ defmodule MobilizonWeb.API.Events do
@moduledoc """ @moduledoc """
API for Events API for Events
""" """
alias Mobilizon.Addresses
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub
@ -11,7 +12,7 @@ defmodule MobilizonWeb.API.Events do
@doc """ @doc """
Create an event Create an event
""" """
@spec create_event(map()) :: {:ok, Activity.t()} | any() @spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
def create_event( def create_event(
%{ %{
title: title, title: title,
@ -22,10 +23,9 @@ defmodule MobilizonWeb.API.Events do
tags: tags tags: tags
} = args } = args
) do ) do
require Logger
with %Actor{url: url} = actor <- with %Actor{url: url} = actor <-
Actors.get_local_actor_with_everything(organizer_actor_id), Actors.get_local_actor_with_everything(organizer_actor_id),
physical_address <- Map.get(args, :physical_address, nil),
title <- String.trim(title), title <- String.trim(title),
visibility <- Map.get(args, :visibility, :public), visibility <- Map.get(args, :visibility, :public),
picture <- Map.get(args, :picture, nil), picture <- Map.get(args, :picture, nil),
@ -34,14 +34,12 @@ defmodule MobilizonWeb.API.Events do
event <- event <-
ActivityPubUtils.make_event_data( ActivityPubUtils.make_event_data(
url, url,
to, %{to: to, cc: cc},
title, title,
content_html, content_html,
picture, picture,
tags, tags,
cc, %{begins_on: begins_on, physical_address: physical_address, category: category}
%{begins_on: begins_on},
category
) do ) do
ActivityPub.create(%{ ActivityPub.create(%{
to: ["https://www.w3.org/ns/activitystreams#Public"], to: ["https://www.w3.org/ns/activitystreams#Public"],
@ -51,4 +49,15 @@ defmodule MobilizonWeb.API.Events do
}) })
end end
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 end

View file

@ -11,7 +11,7 @@ defmodule MobilizonWeb.API.Groups do
@doc """ @doc """
Create a group Create a group
""" """
@spec create_group(map()) :: {:ok, Activity.t()} | any() @spec create_group(map()) :: {:ok, Activity.t(), Group.t()} | any()
def create_group( def create_group(
%{ %{
preferred_username: title, preferred_username: title,

View file

@ -11,7 +11,7 @@ defmodule MobilizonWeb.Resolvers.Address do
Search an address Search an address
""" """
@spec search(map(), map(), map()) :: {:ok, list(Address.t())} @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) country = Geolix.lookup(ip) |> Map.get(:country, nil)
local_addresses = Task.async(fn -> Addresses.search_addresses(query, country: country) end) local_addresses = Task.async(fn -> Addresses.search_addresses(query, country: country) end)

View file

@ -11,14 +11,10 @@ defmodule MobilizonWeb.Resolvers.Comment do
def create_comment(_parent, %{text: comment, actor_username: username}, %{ def create_comment(_parent, %{text: comment, actor_username: username}, %{
context: %{current_user: %User{} = _user} context: %{current_user: %User{} = _user}
}) do }) 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 Comments.create_comment(username, comment) do
{:ok, {:ok, comment}
%Comment{
text: object["content"],
url: object["id"],
uuid: object["uuid"]
}}
end end
end end

View file

@ -3,6 +3,8 @@ defmodule MobilizonWeb.Resolvers.Event do
Handles the event-related GraphQL calls Handles the event-related GraphQL calls
""" """
alias Mobilizon.Activity alias Mobilizon.Activity
alias Mobilizon.Addresses
alias Mobilizon.Addresses.Address
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant} alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Media.Picture alias Mobilizon.Media.Picture
@ -190,25 +192,10 @@ defmodule MobilizonWeb.Resolvers.Event do
""" """
def create_event(_parent, args, %{context: %{current_user: _user}} = _resolution) do def create_event(_parent, args, %{context: %{current_user: _user}} = _resolution) do
with {:ok, args} <- save_attached_picture(args), 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 MobilizonWeb.API.Events.create_event(args) do
res = %{ {:ok, event}
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}
end end
end end
@ -237,6 +224,25 @@ defmodule MobilizonWeb.Resolvers.Event do
@spec save_attached_picture(map()) :: {:ok, map()} @spec save_attached_picture(map()) :: {:ok, map()}
defp save_attached_picture(args), do: {:ok, args} 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 """ @doc """
Delete an event Delete an event
""" """

View file

@ -47,20 +47,15 @@ defmodule MobilizonWeb.Resolvers.Group do
:ok, :ok,
%Activity{ %Activity{
data: %{ data: %{
"object" => %{"type" => "Group"} = object "object" => %{"type" => "Group"} = _object
}
} }
},
%Actor{} = group
} <- } <-
MobilizonWeb.API.Groups.create_group(args) do MobilizonWeb.API.Groups.create_group(args) do
{ {
:ok, :ok,
%Actor{ group
preferred_username: object["preferredUsername"],
summary: object["summary"],
type: :Group,
# uuid: object["uuid"],
url: object["id"]
}
} }
end end

View file

@ -14,6 +14,7 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:region, :string) field(:region, :string)
field(:country, :string) field(:country, :string)
field(:description, :string) field(:description, :string)
field(:url, :string)
end end
object :phone_address do object :phone_address do
@ -26,10 +27,25 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:info, :string) field(:info, :string)
end 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 object :address_queries do
@desc "Search for an address" @desc "Search for an address"
field :search_address, type: list_of(:address) do field :search_address, type: list_of(:address) do
arg(:query, non_null(:string)) arg(:query, non_null(:string))
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Resolvers.Address.search/3) resolve(&Resolvers.Address.search/3)
end end

View file

@ -132,6 +132,7 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:phone_address, :string) arg(:phone_address, :string)
arg(:organizer_actor_id, non_null(:id)) arg(:organizer_actor_id, non_null(:id))
arg(:category, non_null(:string)) arg(:category, non_null(:string))
arg(:physical_address, :address_input)
resolve(&Event.create_event/3) resolve(&Event.create_event/3)
end end

View file

@ -70,10 +70,11 @@ defmodule Mobilizon.Service.ActivityPub do
def fetch_object_from_url(url) do def fetch_object_from_url(url) do
Logger.info("Fetching object from url #{url}") Logger.info("Fetching object from url #{url}")
with true <- String.starts_with?(url, "http"), with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")},
nil <- Events.get_event_by_url(url), {:existing_event, nil} <- {:existing_event, Events.get_event_by_url(url)},
nil <- Events.get_comment_from_url(url), {:existing_comment, nil} <- {:existing_comment, Events.get_comment_from_url(url)},
{:error, :actor_not_found} <- Actors.get_actor_by_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 <- {:ok, %{body: body, status_code: code}} when code in 200..299 <-
HTTPoison.get( HTTPoison.get(
url, url,
@ -90,25 +91,32 @@ defmodule Mobilizon.Service.ActivityPub do
"actor" => data["attributedTo"], "actor" => data["attributedTo"],
"object" => data "object" => data
}, },
{:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, _activity, %{url: object_url} = _object} <- Transmogrifier.handle_incoming(params) do
case data["type"] do case data["type"] do
"Event" -> "Event" ->
{:ok, Events.get_event_by_url!(activity.data["object"]["id"])} {:ok, Events.get_event_by_url!(object_url)}
"Note" -> "Note" ->
{:ok, Events.get_comment_full_from_url!(activity.data["object"]["id"])} {:ok, Events.get_comment_full_from_url!(object_url)}
"Actor" -> "Actor" ->
{:ok, Actors.get_actor_by_url!(activity.data["object"]["id"], true)} {:ok, Actors.get_actor_by_url!(object_url, true)}
other -> other ->
{:error, other} {:error, other}
end end
else else
%Event{url: event_url} -> {:ok, Events.get_event_by_url!(event_url)} {:existing_event, %Event{url: event_url}} ->
%Comment{url: comment_url} -> {:ok, Events.get_comment_full_from_url!(comment_url)} {:ok, Events.get_event_by_url!(event_url)}
%Actor{url: actor_url} -> {:ok, Actors.get_actor_by_url!(actor_url, true)}
e -> {:error, e} {: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
end end
@ -130,10 +138,10 @@ defmodule Mobilizon.Service.ActivityPub do
additional additional
), ),
:ok <- Logger.debug(inspect(create_data)), :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 <- maybe_federate(activity) do
# {:ok, actor} <- Actors.increase_event_count(actor) do # {:ok, actor} <- Actors.increase_event_count(actor) do
{:ok, activity} {:ok, activity, object}
else else
err -> err ->
Logger.error("Something went wrong") Logger.error("Something went wrong")
@ -147,9 +155,9 @@ defmodule Mobilizon.Service.ActivityPub do
local = !(params[:local] == false) local = !(params[:local] == false)
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, 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 <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
end end
end end
@ -164,9 +172,9 @@ defmodule Mobilizon.Service.ActivityPub do
"actor" => actor, "actor" => actor,
"object" => object "object" => object
}, },
{:ok, activity, _object} <- insert(data, local), {:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
end end
end end
@ -179,7 +187,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do # ) do
# with nil <- get_existing_like(url, object), # with nil <- get_existing_like(url, object),
# like_data <- make_like_data(user, object, activity_id), # 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, object} <- add_like_to_object(activity, object),
# :ok <- maybe_federate(activity) do # :ok <- maybe_federate(activity) do
# {:ok, activity, object} # {:ok, activity, object}
@ -215,7 +223,7 @@ defmodule Mobilizon.Service.ActivityPub do
# ) do # ) do
# #with true <- is_public?(object), # #with true <- is_public?(object),
# with announce_data <- make_announce_data(actor, object, activity_id), # 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, object} <- add_announce_to_object(activity, object),
# :ok <- maybe_federate(activity) do # :ok <- maybe_federate(activity) do
# {:ok, activity, object} # {:ok, activity, object}
@ -250,9 +258,9 @@ defmodule Mobilizon.Service.ActivityPub do
activity_follow_id <- activity_follow_id <-
activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity", activity_id || "#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity",
data <- make_follow_data(followed, follower, activity_follow_id), 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 <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
else else
{err, _} when err in [:already_following, :suspended] -> {err, _} when err in [:already_following, :suspended] ->
{:error, err} {:error, err}
@ -269,9 +277,9 @@ defmodule Mobilizon.Service.ActivityPub do
data <- make_follow_data(followed, follower, follow_id), data <- make_follow_data(followed, follower, follow_id),
{:ok, follow_activity, _object} <- insert(data, local), {:ok, follow_activity, _object} <- insert(data, local),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), 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 <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
else else
err -> err ->
Logger.error(inspect(err)) Logger.error(inspect(err))
@ -290,9 +298,9 @@ defmodule Mobilizon.Service.ActivityPub do
} }
with {:ok, _} <- Events.delete_event(event), with {:ok, _} <- Events.delete_event(event),
{:ok, activity, _object} <- insert(data, local), {:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
end end
end end
@ -305,9 +313,9 @@ defmodule Mobilizon.Service.ActivityPub do
} }
with {:ok, _} <- Events.delete_comment(comment), with {:ok, _} <- Events.delete_comment(comment),
{:ok, activity, _object} <- insert(data, local), {:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
end end
end end
@ -320,9 +328,9 @@ defmodule Mobilizon.Service.ActivityPub do
} }
with {:ok, _} <- Actors.delete_actor(actor), with {:ok, _} <- Actors.delete_actor(actor),
{:ok, activity, _object} <- insert(data, local), {:ok, activity, object} <- insert(data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity, object}
end end
end end

View file

@ -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

View file

@ -12,19 +12,28 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
alias Mobilizon.Service.ActivityPub.Converter alias Mobilizon.Service.ActivityPub.Converter
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.Tag alias Mobilizon.Events.Tag
alias Mobilizon.Addresses
alias Mobilizon.Addresses.Address
@behaviour Converter @behaviour Converter
require Logger
@doc """ @doc """
Converts an AP object data to our internal data structure Converts an AP object data to our internal data structure
""" """
@impl Converter @impl Converter
@spec as_to_model_data(map()) :: map() @spec as_to_model_data(map()) :: map()
def as_to_model_data(object) do def as_to_model_data(object) do
with {:ok, %Actor{id: actor_id}} <- Actors.get_actor_by_url(object["actor"]), Logger.debug("event as_to_model_data")
tags <- fetch_tags(object["tag"]) do
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 = picture_id =
with true <- Map.has_key?(object, "attachment"), with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0,
%Picture{id: picture_id} <- %Picture{id: picture_id} <-
Media.get_picture_by_url( Media.get_picture_by_url(
object["attachment"] object["attachment"]
@ -38,27 +47,64 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
_ -> nil _ -> nil
end end
{:ok,
%{ %{
"title" => object["name"], "title" => object["name"],
"description" => object["content"], "description" => object["content"],
"organizer_actor_id" => actor_id, "organizer_actor_id" => actor_id,
"picture_id" => picture_id, "picture_id" => picture_id,
"begins_on" => object["begins_on"], "begins_on" => object["startTime"],
"category" => object["category"], "category" => object["category"],
"url" => object["id"], "url" => object["id"],
"uuid" => object["uuid"], "uuid" => object["uuid"],
"tags" => tags "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
end end
defp fetch_tags(tags) do defp fetch_tags(tags) do
Enum.reduce(tags, [], fn tag, acc -> Enum.reduce(tags, [], fn tag, acc ->
case Events.get_or_create_tag(tag) do with true <- tag["type"] == "Hashtag",
{:ok, %Tag{} = tag} -> {:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do
acc ++ [tag] acc ++ [tag]
else
_ -> _err ->
acc acc
end end
end) end)

View file

@ -132,9 +132,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end end
end end
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
Logger.info("Handle incoming to create notes") Logger.info("Handle incoming to create notes")
@ -159,15 +156,39 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end end
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( def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
) do ) do
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true), with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower), {: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}) ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true})
{:ok, activity} {:ok, activity, object}
else else
e -> e ->
Logger.error("Unable to handle Follow activity") Logger.error("Unable to handle Follow activity")
@ -257,9 +278,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
) do ) do
with {:ok, %Actor{domain: nil} = followed} <- Actors.get_actor_by_url(followed), with {:ok, %Actor{domain: nil} = followed} <- Actors.get_actor_by_url(followed),
{:ok, %Actor{} = follower} <- Actors.get_actor_by_url(follower), {: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) Actor.unfollow(follower, followed)
{:ok, activity} {:ok, activity, object}
else else
e -> e ->
Logger.error(inspect(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), {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# TODO : Validate that DELETE comes indeed form right domain (see above) # TODO : Validate that DELETE comes indeed form right domain (see above)
# :ok <- contain_origin(actor_url, object.data), # :ok <- contain_origin(actor_url, object.data),
{:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity, object} <- ActivityPub.delete(object, false) do
{:ok, activity} {:ok, activity, object}
else else
e -> e ->
Logger.debug(inspect(e)) Logger.error(inspect(e))
:error :error
end end
end end

View file

@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
""" """
alias Mobilizon.Repo alias Mobilizon.Repo
alias Mobilizon.Addresses.Address
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
@ -122,7 +123,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
""" """
def insert_full_object(%{"object" => %{"type" => "Event"} = object_data}) def insert_full_object(%{"object" => %{"type" => "Event"} = object_data})
when is_map(object_data) do when is_map(object_data) do
with object_data <- with {:ok, object_data} <-
Converters.Event.as_to_model_data(object_data), Converters.Event.as_to_model_data(object_data),
{:ok, %Event{} = event} <- Events.create_event(object_data) do {:ok, %Event{} = event} <- Events.create_event(object_data) do
{:ok, event} {:ok, event}
@ -260,26 +261,21 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
""" """
@spec make_event_data( @spec make_event_data(
String.t(), String.t(),
String.t(), map(),
String.t(), String.t(),
String.t(), String.t(),
map(), map(),
list(), list(),
list(), map()
map(),
String.t()
) :: map() ) :: map()
def make_event_data( def make_event_data(
actor, actor,
to, %{to: to, cc: cc} = _audience,
title, title,
content_html, content_html,
picture \\ nil, picture \\ nil,
tags \\ [], tags \\ [],
# _cw \\ nil, metadata \\ %{}
cc \\ [],
metadata \\ %{},
category \\ ""
) do ) do
Logger.debug("Making event data") Logger.debug("Making event data")
uuid = Ecto.UUID.generate() uuid = Ecto.UUID.generate()
@ -287,21 +283,58 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
res = %{ res = %{
"type" => "Event", "type" => "Event",
"to" => to, "to" => to,
"cc" => cc, "cc" => cc || [],
"content" => content_html, "content" => content_html,
"name" => title, "name" => title,
# "summary" => cw, "startTime" => metadata.begins_on,
"begins_on" => metadata.begins_on, "category" => metadata.category,
"category" => category,
"actor" => actor, "actor" => actor,
"id" => Routes.page_url(Endpoint, :event, uuid), "id" => Routes.page_url(Endpoint, :event, uuid),
"uuid" => 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)]) if is_nil(picture), do: res, else: Map.put(res, "attachment", [make_picture_data(picture)])
end 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 """ @doc """
Make an AP comment object from an set of values Make an AP comment object from an set of values
""" """

View file

@ -52,7 +52,7 @@ defmodule Mobilizon.Service.Federator do
Logger.debug(inspect(params)) Logger.debug(inspect(params))
case Transmogrifier.handle_incoming(params) do case Transmogrifier.handle_incoming(params) do
{:ok, activity} -> {:ok, activity, _} ->
{:ok, activity} {:ok, activity}
%Activity{} -> %Activity{} ->

View file

@ -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

View file

@ -1,5 +1,5 @@
# source: http://localhost:4000/api # 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 { schema {
query: RootQueryType query: RootQueryType
@ -121,6 +121,25 @@ type Address {
street: String 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""" """A comment"""
type Comment { type Comment {
"""Internal ID for this comment""" """Internal ID for this comment"""
@ -675,6 +694,7 @@ type RootMutationType {
onlineAddress: String onlineAddress: String
organizerActorId: ID! organizerActorId: ID!
phoneAddress: String phoneAddress: String
physicalAddress: AddressInput
""" """
The picture for the event, either as an object or directly the ID of an existing Picture 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] reverseGeocode(latitude: Float!, longitude: Float!): [Address]
"""Search for an address""" """Search for an address"""
searchAddress(query: String!): [Address] searchAddress(limit: Int = 10, page: Int = 1, query: String!): [Address]
"""Search events""" """Search events"""
searchEvents(limit: Int = 10, page: Int = 1, search: String!): Events searchEvents(limit: Int = 10, page: Int = 1, search: String!): Events

View file

@ -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": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>",
"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"
}

View file

@ -1,6 +1,7 @@
defmodule Mobilizon.AddressesTest do defmodule Mobilizon.AddressesTest do
use Mobilizon.DataCase use Mobilizon.DataCase
import Mobilizon.Factory
alias Mobilizon.Addresses alias Mobilizon.Addresses
describe "addresses" do describe "addresses" do
@ -37,22 +38,13 @@ defmodule Mobilizon.AddressesTest do
# geom: nil # 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 test "list_addresses/0 returns all addresses" do
address = address_fixture() address = insert(:address)
assert [address.id] == Addresses.list_addresses() |> Enum.map(& &1.id) assert [address.id] == Addresses.list_addresses() |> Enum.map(& &1.id)
end end
test "get_address!/1 returns the address with given id" do 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 assert Addresses.get_address!(address.id).id == address.id
end end
@ -68,7 +60,7 @@ defmodule Mobilizon.AddressesTest do
end end
test "update_address/2 with valid data updates the address" do 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 {:ok, %Address{} = address} = Addresses.update_address(address, @update_attrs)
assert address.country == "some updated addressCountry" assert address.country == "some updated addressCountry"
assert address.locality == "some updated addressLocality" assert address.locality == "some updated addressLocality"
@ -80,13 +72,13 @@ defmodule Mobilizon.AddressesTest do
end end
test "delete_address/1 deletes the address" do test "delete_address/1 deletes the address" do
address = address_fixture() address = insert(:address)
assert {:ok, %Address{}} = Addresses.delete_address(address) assert {:ok, %Address{}} = Addresses.delete_address(address)
assert_raise Ecto.NoResultsError, fn -> Addresses.get_address!(address.id) end assert_raise Ecto.NoResultsError, fn -> Addresses.get_address!(address.id) end
end end
test "change_address/1 returns a address changeset" do test "change_address/1 returns a address changeset" do
address = address_fixture() address = insert(:address)
assert %Ecto.Changeset{} = Addresses.change_address(address) assert %Ecto.Changeset{} = Addresses.change_address(address)
end end

View file

@ -56,7 +56,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
test "removes doubled 'to' recipients" do test "removes doubled 'to' recipients" do
actor = insert(:actor) actor = insert(:actor)
{:ok, activity} = {:ok, activity, _} =
ActivityPub.create(%{ ActivityPub.create(%{
to: ["user1", "user1", "user2"], to: ["user1", "user1", "user2"],
actor: actor, actor: actor,
@ -113,7 +113,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
test "it creates a delete activity and deletes the original event" do test "it creates a delete activity and deletes the original event" do
event = insert(:event) event = insert(:event)
event = Events.get_event_full_by_url!(event.url) 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["type"] == "Delete"
assert delete.data["actor"] == event.organizer_actor.url 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 test "it creates a delete activity and deletes the original comment" do
comment = insert(:comment) comment = insert(:comment)
comment = Events.get_comment_full_from_url!(comment.url) 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["type"] == "Delete"
assert delete.data["actor"] == comment.actor.url assert delete.data["actor"] == comment.actor.url
@ -140,7 +140,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
actor = insert(:actor) actor = insert(:actor)
actor_data = MobilizonWeb.ActivityPub.ActorView.render("actor.json", %{actor: actor}) actor_data = MobilizonWeb.ActivityPub.ActorView.render("actor.json", %{actor: actor})
{:ok, update} = {:ok, update, _} =
ActivityPub.update(%{ ActivityPub.update(%{
actor: actor_data["url"], actor: actor_data["url"],
to: [actor.url <> "/followers"], to: [actor.url <> "/followers"],

View file

@ -12,7 +12,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.Comment alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Service.ActivityPub.Utils alias Mobilizon.Service.ActivityPub.Utils
alias Mobilizon.Service.ActivityPub.Transmogrifier alias Mobilizon.Service.ActivityPub.Transmogrifier
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
@ -21,7 +21,51 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
HTTPoison.start() HTTPoison.start()
end 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 # test "it ignores an incoming comment if we already have it" do
# comment = insert(:comment) # comment = insert(:comment)
@ -37,7 +81,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
# |> Jason.decode!() # |> Jason.decode!()
# |> Map.put("object", activity["object"]) # |> Map.put("object", activity["object"])
# {:ok, returned_activity} = Transmogrifier.handle_incoming(data) # {:ok, returned_activity, _} = Transmogrifier.handle_incoming(data)
# assert activity == returned_activity.data # assert activity == returned_activity.data
# end # end
@ -55,7 +99,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
# data # data
# |> Map.put("object", object) # |> Map.put("object", object)
# {:ok, returned_activity} = Transmogrifier.handle_incoming(data) # {:ok, returned_activity, _} = Transmogrifier.handle_incoming(data)
# assert activity = # assert activity =
# Activity.get_create_activity_by_object_ap_id( # 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 test "it works for incoming notices" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() 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" 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 test "it works for incoming notices with hashtags" do
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!() 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" assert Enum.at(data["object"]["tag"], 2) == "moo"
end end
@ -113,7 +158,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
# data = # data =
# File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Jason.decode!() # 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"] == # assert data["object"]["content"] ==
# "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>" # "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
@ -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 # 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!() # 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"] == # assert data["object"]["content"] ==
# "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>" # "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
@ -131,7 +176,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
# test "it works for incoming announces with actor being inlined (kroeg)" 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!() # 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/" # assert data["actor"] == "https://puckipedia.com/"
# end # 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 # 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!() # 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"] == %{ # assert data["object"]["emoji"] == %{
# "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png" # "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!() # 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"] # assert "test" in data["object"]["tag"]
# end # end
@ -170,7 +215,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|> Jason.decode!() |> Jason.decode!()
|> Map.put("object", actor.url) |> 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["actor"] == "https://social.tcit.fr/users/tcit"
assert data["type"] == "Follow" assert data["type"] == "Follow"
@ -289,7 +334,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
test "it works for incoming update activities" do test "it works for incoming update activities" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() 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!() update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
object = object =
@ -302,7 +347,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|> Map.put("actor", data["actor"]) |> Map.put("actor", data["actor"])
|> Map.put("object", object) |> 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"]) {:ok, %Actor{} = actor} = Actors.get_actor_by_url(data["actor"])
assert actor.name == "gargle" assert actor.name == "gargle"
@ -352,7 +397,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert Events.get_comment_from_url(comment_url) 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) refute Events.get_comment_from_url(comment_url)
end end
@ -413,14 +458,14 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|> Jason.decode!() |> Jason.decode!()
|> Map.put("object", actor.url) |> 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 = data =
File.read!("test/fixtures/mastodon-unfollow-activity.json") File.read!("test/fixtures/mastodon-unfollow-activity.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("object", follow_data) |> 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["type"] == "Undo"
assert data["object"]["type"] == "Follow" assert data["object"]["type"] == "Follow"
@ -706,7 +751,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
actor = insert(:actor) actor = insert(:actor)
other_actor = insert(:actor) other_actor = insert(:actor)
{:ok, activity} = {:ok, activity, _} =
MobilizonWeb.API.Comments.create_comment( MobilizonWeb.API.Comments.create_comment(
actor.preferred_username, actor.preferred_username,
"hey, @#{other_actor.preferred_username}, how are ya? #2hu" "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 test "it adds the json-ld context and the conversation property" do
actor = insert(:actor) 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) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["@context"] == Utils.make_json_ld_header()["@context"] 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 test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
actor = insert(:actor) 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) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["object"]["actor"] == modified["object"]["attributedTo"] assert modified["object"]["actor"] == modified["object"]["attributedTo"]
@ -761,7 +810,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
test "it strips internal hashtag data" do test "it strips internal hashtag data" do
actor = insert(:actor) 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 = %{ expected_tag = %{
"href" => MobilizonWeb.Endpoint.url() <> "/tags/2hu", "href" => MobilizonWeb.Endpoint.url() <> "/tags/2hu",
@ -777,7 +827,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
test "it strips internal fields" do test "it strips internal fields" do
actor = insert(:actor) 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) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)

View file

@ -122,6 +122,95 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
] ]
end 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", %{ test "create_event/3 creates an event with an attached picture", %{
conn: conn, conn: conn,
actor: actor, actor: actor,

View file

@ -78,6 +78,7 @@ defmodule Mobilizon.Factory do
%Mobilizon.Addresses.Address{ %Mobilizon.Addresses.Address{
description: sequence("MyAddress"), description: sequence("MyAddress"),
geom: %Geo.Point{coordinates: {45.75, 4.85}, srid: 4326}, geom: %Geo.Point{coordinates: {45.75, 4.85}, srid: 4326},
url: "http://mobilizon.test/address/#{Ecto.UUID.generate()}",
floor: "Myfloor", floor: "Myfloor",
country: "My Country", country: "My Country",
locality: "My Locality", locality: "My Locality",