Correctly handle event update

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-09-04 18:24:31 +02:00
parent 6845825db2
commit f5c3dbf128
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
27 changed files with 493 additions and 161 deletions

View File

@ -81,11 +81,12 @@ import { Modal } from 'buefy/dist/components/dialog';
export default class AddressAutoComplete extends Vue { export default class AddressAutoComplete extends Vue {
@Prop({ required: false, default: () => [] }) initialData!: IAddress[]; @Prop({ required: false, default: () => [] }) initialData!: IAddress[];
@Prop({ required: false }) value!: IAddress;
data: IAddress[] = this.initialData; data: IAddress[] = this.initialData;
selected: IAddress|null = new Address(); selected: IAddress|null = new Address();
isFetching: boolean = false; isFetching: boolean = false;
queryText: string = ''; queryText: string = this.value && this.value.description || '';
addressModalActive: boolean = false; addressModalActive: boolean = false;
async getAsyncData(query) { async getAsyncData(query) {

View File

@ -1,33 +1,51 @@
<template> <template>
<b-field label="Enter some tags"> <b-field label="Enter some tags">
<b-taginput <b-taginput
v-model="tags" v-model="tagsStrings"
:data="filteredTags" :data="filteredTags"
autocomplete autocomplete
:allow-new="true" :allow-new="true"
:field="path" :field="path"
icon="label" icon="label"
placeholder="Add a tag" placeholder="Add a tag"
@typing="getFilteredTags"> @typing="getFilteredTags"
>
</b-taginput> </b-taginput>
</b-field> </b-field>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { get } from 'lodash'; import { get, differenceBy } from 'lodash';
import { ITag } from '@/types/tag.model'; import { ITag } from '@/types/tag.model';
@Component
@Component({
computed: {
tagsStrings: {
get() {
return this.$props.data.map((tag: ITag) => tag.title);
},
set(tagStrings) {
const tagEntities = tagStrings.map((tag) => {
if (TagInput.isTag(tag)) {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
this.$emit('input', tagEntities);
},
},
},
})
export default class TagInput extends Vue { export default class TagInput extends Vue {
@Prop({ required: false, default: () => [] }) data!: object[]; @Prop({ required: false, default: () => [] }) data!: ITag[];
@Prop({ required: true, default: 'value' }) path!: string; @Prop({ required: true, default: 'value' }) path!: string;
@Prop({ required: true }) value!: string; @Prop({ required: true }) value!: ITag[];
filteredTags: object[] = []; filteredTags: ITag[] = [];
tags: object[] = [];
getFilteredTags(text) { getFilteredTags(text) {
this.filteredTags = this.data.filter((option) => { this.filteredTags = differenceBy(this.data, this.value, 'id').filter((option) => {
return get(option, this.path) return get(option, this.path)
.toString() .toString()
.toLowerCase() .toLowerCase()
@ -35,18 +53,6 @@ export default class TagInput extends Vue {
}); });
} }
@Watch('tags')
onTagsChanged (tags) {
const tagEntities = tags.map((tag) => {
if (TagInput.isTag(tag)) {
return tag;
}
return { title: tag, slug: tag } as ITag;
});
console.log('tags changed', tagEntities);
this.$emit('input', tagEntities);
}
static isTag(x: any): x is ITag { static isTag(x: any): x is ITag {
return x.slug !== undefined; return x.slug !== undefined;
} }

View File

@ -52,6 +52,7 @@ export const FETCH_EVENT = gql`
domain, domain,
name, name,
url, url,
id,
}, },
# attributedTo { # attributedTo {
# avatar { # avatar {
@ -64,6 +65,7 @@ export const FETCH_EVENT = gql`
${participantQuery} ${participantQuery}
}, },
tags { tags {
id,
slug, slug,
title title
}, },
@ -82,6 +84,25 @@ export const FETCH_EVENT = gql`
domain, domain,
name, name,
} }
},
options {
maximumAttendeeCapacity,
remainingAttendeeCapacity,
showRemainingAttendeeCapacity,
offers {
price,
priceCurrency,
url
},
participationConditions {
title,
content,
url
},
attendees,
program,
commentModeration,
showParticipationPrice
} }
} }
} }
@ -144,6 +165,7 @@ export const CREATE_EVENT = gql`
$organizerActorId: ID!, $organizerActorId: ID!,
$category: String, $category: String,
$beginsOn: DateTime!, $beginsOn: DateTime!,
$endsOn: DateTime,
$picture: PictureInput, $picture: PictureInput,
$tags: [String], $tags: [String],
$options: EventOptionsInput, $options: EventOptionsInput,
@ -154,6 +176,7 @@ export const CREATE_EVENT = gql`
title: $title, title: $title,
description: $description, description: $description,
beginsOn: $beginsOn, beginsOn: $beginsOn,
endsOn: $endsOn,
organizerActorId: $organizerActorId, organizerActorId: $organizerActorId,
category: $category, category: $category,
options: $options, options: $options,
@ -173,13 +196,32 @@ export const CREATE_EVENT = gql`
`; `;
export const EDIT_EVENT = gql` export const EDIT_EVENT = gql`
mutation EditEvent( mutation updateEvent(
$id: ID!,
$title: String!, $title: String!,
$description: String!, $description: String!,
$organizerActorId: Int!, $organizerActorId: ID!,
$category: String $category: String,
$beginsOn: DateTime!,
$endsOn: DateTime,
$picture: PictureInput,
$tags: [String],
$options: EventOptionsInput,
$physicalAddress: AddressInput,
$visibility: EventVisibility
) { ) {
EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, category: $category) { updateEvent(eventId: $id,
title: $title,
description: $description,
beginsOn: $beginsOn,
endsOn: $endsOn,
organizerActorId: $organizerActorId,
category: $category,
options: $options,
picture: $picture,
tags: $tags,
physicalAddress: $physicalAddress,
visibility: $visibility) {
uuid uuid
} }
} }

View File

@ -4,9 +4,9 @@ import { ITag } from '@/types/tag.model';
import { IPicture } from '@/types/picture.model'; import { IPicture } from '@/types/picture.model';
export enum EventStatus { export enum EventStatus {
TENTATIVE, TENTATIVE = 'TENTATIVE',
CONFIRMED, CONFIRMED = 'CONFIRMED',
CANCELLED, CANCELLED = 'CANCELLED',
} }
export enum EventVisibility { export enum EventVisibility {
@ -17,9 +17,9 @@ export enum EventVisibility {
} }
export enum EventJoinOptions { export enum EventJoinOptions {
FREE, FREE = 'FREE',
RESTRICTED, RESTRICTED = 'RESTRICTED',
INVITE, INVITE = 'INVITE',
} }
export enum EventVisibilityJoinOptions { export enum EventVisibilityJoinOptions {

View File

@ -177,7 +177,7 @@
<script lang="ts"> <script lang="ts">
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event'; import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { EventModel, EventStatus, EventVisibility, EventVisibilityJoinOptions, IEvent, CommentModeration } from '@/types/event.model'; import { EventModel, EventStatus, EventVisibility, EventVisibilityJoinOptions, CommentModeration } from '@/types/event.model';
import { LOGGED_PERSON } from '@/graphql/actor'; import { LOGGED_PERSON } from '@/graphql/actor';
import { IPerson, Person } from '@/types/actor'; import { IPerson, Person } from '@/types/actor';
import PictureUpload from '@/components/PictureUpload.vue'; import PictureUpload from '@/components/PictureUpload.vue';
@ -207,6 +207,7 @@ export default class EditEvent extends Vue {
eventId!: string | undefined; eventId!: string | undefined;
loggedPerson = new Person(); loggedPerson = new Person();
tags: ITag[] = [];
event = new EventModel(); event = new EventModel();
pictureFile: File | null = null; pictureFile: File | null = null;
@ -223,7 +224,7 @@ export default class EditEvent extends Vue {
@Watch('$route.params.eventId', { immediate: true }) @Watch('$route.params.eventId', { immediate: true })
async onEventIdParamChanged (val: string) { async onEventIdParamChanged (val: string) {
if (this.isUpdate !== true) return; if (!this.isUpdate) return;
this.eventId = val; this.eventId = val;
@ -231,6 +232,7 @@ export default class EditEvent extends Vue {
this.event = await this.getEvent(); this.event = await this.getEvent();
this.pictureFile = await buildFileFromIPicture(this.event.picture); this.pictureFile = await buildFileFromIPicture(this.event.picture);
this.limitedPlaces = this.event.options.maximumAttendeeCapacity != null;
} }
} }
@ -241,7 +243,6 @@ export default class EditEvent extends Vue {
this.event.beginsOn = now; this.event.beginsOn = now;
this.event.endsOn = end; this.event.endsOn = end;
console.log('eventvisibilityjoinoptions', this.eventVisibilityJoinOptions);
} }
createOrUpdate(e: Event) { createOrUpdate(e: Event) {
@ -261,7 +262,7 @@ export default class EditEvent extends Vue {
console.log('Event created', data); console.log('Event created', data);
this.$router.push({ await this.$router.push({
name: 'Event', name: 'Event',
params: { uuid: data.createEvent.uuid }, params: { uuid: data.createEvent.uuid },
}); });
@ -277,7 +278,7 @@ export default class EditEvent extends Vue {
variables: this.buildVariables(), variables: this.buildVariables(),
}); });
this.$router.push({ await this.$router.push({
name: 'Event', name: 'Event',
params: { uuid: this.eventId as string }, params: { uuid: this.eventId as string },
}); });
@ -297,6 +298,8 @@ export default class EditEvent extends Vue {
}; };
const res = Object.assign({}, this.event, obj); const res = Object.assign({}, this.event, obj);
delete this.event.options['__typename'];
if (this.event.physicalAddress) { if (this.event.physicalAddress) {
delete this.event.physicalAddress['__typename']; delete this.event.physicalAddress['__typename'];
} }

View File

@ -57,7 +57,7 @@
<p class="control"> <p class="control">
<router-link <router-link
class="button" class="button"
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}" :to="{ name: 'EditEvent', params: {eventId: event.uuid}}"
> >
<translate>Edit</translate> <translate>Edit</translate>
</router-link> </router-link>

View File

@ -110,6 +110,24 @@ defmodule Mobilizon.Actors.Actor do
|> unique_constraint(:url, name: :actors_url_index) |> unique_constraint(:url, name: :actors_url_index)
end end
@doc false
def update_changeset(%Actor{} = actor, attrs) do
actor
|> Ecto.Changeset.cast(attrs, [
:name,
:summary,
:keys,
:manually_approves_followers,
:suspended,
:user_id
])
|> cast_embed(:avatar)
|> cast_embed(:banner)
|> validate_required([:preferred_username, :keys, :suspended, :url])
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|> unique_constraint(:url, name: :actors_url_index)
end
@doc """ @doc """
Changeset for person registration Changeset for person registration
""" """

View File

@ -124,7 +124,7 @@ defmodule Mobilizon.Actors do
""" """
def update_actor(%Actor{} = actor, attrs) do def update_actor(%Actor{} = actor, attrs) do
actor actor
|> Actor.changeset(attrs) |> Actor.update_changeset(attrs)
|> delete_files_if_media_changed() |> delete_files_if_media_changed()
|> Repo.update() |> Repo.update()
end end

View File

@ -54,10 +54,10 @@ defmodule Mobilizon.Events.Event do
field(:online_address, :string) field(:online_address, :string)
field(:phone_address, :string) field(:phone_address, :string)
field(:category, :string) field(:category, :string)
embeds_one(:options, Mobilizon.Events.EventOptions) embeds_one(:options, Mobilizon.Events.EventOptions, on_replace: :update)
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id) belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id) belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
many_to_many(:tags, Tag, join_through: "events_tags") many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
many_to_many(:participants, Actor, join_through: Participant) many_to_many(:participants, Actor, join_through: Participant)
has_many(:tracks, Track) has_many(:tracks, Track)
has_many(:sessions, Session) has_many(:sessions, Session)
@ -98,6 +98,38 @@ defmodule Mobilizon.Events.Event do
]) ])
end end
@doc false
def update_changeset(%Event{} = event, attrs) do
event
|> Ecto.Changeset.cast(attrs, [
:title,
:slug,
:description,
:begins_on,
:ends_on,
:category,
:status,
:visibility,
:publish_at,
:online_address,
:phone_address,
:picture_id,
:physical_address_id
])
|> cast_embed(:options)
|> put_tags(attrs)
|> validate_required([
:title,
:begins_on,
:organizer_actor_id,
:url,
:uuid
])
end
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags)
defp put_tags(changeset, _), do: changeset
def can_event_be_managed_by(%Event{organizer_actor_id: organizer_actor_id}, actor_id) def can_event_be_managed_by(%Event{organizer_actor_id: organizer_actor_id}, actor_id)
when organizer_actor_id == actor_id do when organizer_actor_id == actor_id do
{:event_can_be_managed, true} {:event_can_be_managed, true}

View File

@ -227,11 +227,12 @@ defmodule Mobilizon.Events do
:tracks, :tracks,
:tags, :tags,
:participants, :participants,
:physical_address :physical_address,
:picture
])} ])}
err -> _err ->
{:error, err} {:error, :event_not_found}
end end
end end
@ -435,7 +436,8 @@ defmodule Mobilizon.Events do
""" """
def update_event(%Event{} = event, attrs) do def update_event(%Event{} = event, attrs) do
event event
|> Event.changeset(attrs) |> Repo.preload(:tags)
|> Event.update_changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View File

@ -2,8 +2,7 @@ defmodule MobilizonWeb.API.Events do
@moduledoc """ @moduledoc """
API for Events API for Events
""" """
alias Mobilizon.Actors alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor
alias Mobilizon.Service.ActivityPub alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
alias MobilizonWeb.API.Utils alias MobilizonWeb.API.Utils
@ -16,13 +15,13 @@ defmodule MobilizonWeb.API.Events do
with %{ with %{
title: title, title: title,
physical_address: physical_address, physical_address: physical_address,
visibility: visibility,
picture: picture, picture: picture,
content_html: content_html, content_html: content_html,
tags: tags, tags: tags,
to: to, to: to,
cc: cc, cc: cc,
begins_on: begins_on, begins_on: begins_on,
ends_on: ends_on,
category: category, category: category,
options: options options: options
} <- prepare_args(args), } <- prepare_args(args),
@ -34,7 +33,13 @@ defmodule MobilizonWeb.API.Events do
content_html, content_html,
picture, picture,
tags, tags,
%{begins_on: begins_on, physical_address: physical_address, category: category, options: options} %{
begins_on: begins_on,
ends_on: ends_on,
physical_address: physical_address,
category: category,
options: options
}
) do ) do
ActivityPub.create(%{ ActivityPub.create(%{
to: ["https://www.w3.org/ns/activitystreams#Public"], to: ["https://www.w3.org/ns/activitystreams#Public"],
@ -48,30 +53,28 @@ defmodule MobilizonWeb.API.Events do
@doc """ @doc """
Update an event Update an event
""" """
@spec update_event(map()) :: {:ok, Activity.t(), Event.t()} | any() @spec update_event(map(), Event.t()) :: {:ok, Activity.t(), Event.t()} | any()
def update_event( def update_event(
%{ %{
organizer_actor: organizer_actor, organizer_actor: organizer_actor
event: event } = args,
} = args %Event{} = event
) do ) do
with %{ with args <- Map.put(args, :tags, Map.get(args, :tags, [])),
%{
title: title, title: title,
physical_address: physical_address, physical_address: physical_address,
visibility: visibility,
picture: picture, picture: picture,
content_html: content_html, content_html: content_html,
tags: tags, tags: tags,
to: to, to: to,
cc: cc, cc: cc,
begins_on: begins_on, begins_on: begins_on,
ends_on: ends_on,
category: category, category: category,
options: options options: options
} <- } <-
prepare_args( prepare_args(Map.merge(event, args)),
args
|> update_args(event)
),
event <- event <-
ActivityPubUtils.make_event_data( ActivityPubUtils.make_event_data(
organizer_actor.url, organizer_actor.url,
@ -82,34 +85,24 @@ defmodule MobilizonWeb.API.Events do
tags, tags,
%{ %{
begins_on: begins_on, begins_on: begins_on,
ends_on: ends_on,
physical_address: physical_address, physical_address: physical_address,
category: Map.get(args, :category), category: category,
options: options options: options
} },
event.uuid,
event.url
) do ) do
ActivityPub.update(%{ ActivityPub.update(%{
to: ["https://www.w3.org/ns/activitystreams#Public"], to: ["https://www.w3.org/ns/activitystreams#Public"],
actor: organizer_actor, actor: organizer_actor.url,
cc: [],
object: event, object: event,
local: true local: true
}) })
end end
end end
defp update_args(args, event) do
%{
title: Map.get(args, :title, event.title),
description: Map.get(args, :description, event.description),
tags: Map.get(args, :tags, event.tags),
physical_address: Map.get(args, :physical_address, event.physical_address),
visibility: Map.get(args, :visibility, event.visibility),
physical_address: Map.get(args, :physical_address, event.physical_address),
begins_on: Map.get(args, :begins_on, event.begins_on),
category: Map.get(args, :category, event.category),
options: Map.get(args, :options, event.options)
}
end
defp prepare_args( defp prepare_args(
%{ %{
organizer_actor: organizer_actor, organizer_actor: organizer_actor,
@ -118,8 +111,7 @@ defmodule MobilizonWeb.API.Events do
options: options, options: options,
tags: tags, tags: tags,
begins_on: begins_on, begins_on: begins_on,
category: category, category: category
options: options
} = args } = args
) do ) do
with physical_address <- Map.get(args, :physical_address, nil), with physical_address <- Map.get(args, :physical_address, nil),
@ -131,13 +123,13 @@ defmodule MobilizonWeb.API.Events do
%{ %{
title: title, title: title,
physical_address: physical_address, physical_address: physical_address,
visibility: visibility,
picture: picture, picture: picture,
content_html: content_html, content_html: content_html,
tags: tags, tags: tags,
to: to, to: to,
cc: cc, cc: cc,
begins_on: begins_on, begins_on: begins_on,
ends_on: Map.get(args, :ends_on, nil),
category: category, category: category,
options: options options: options
} }

View File

@ -18,13 +18,13 @@ defmodule MobilizonWeb.API.Groups do
preferred_username: title, preferred_username: title,
summary: summary, summary: summary,
creator_actor_id: creator_actor_id, creator_actor_id: creator_actor_id,
avatar: avatar, avatar: _avatar,
banner: banner banner: _banner
} = args } = args
) do ) do
with {:is_owned, true, actor} <- User.owns_actor(user, creator_actor_id), with {:is_owned, true, actor} <- User.owns_actor(user, creator_actor_id),
{:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
title <- String.trim(title), title <- String.trim(title),
{:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
visibility <- Map.get(args, :visibility, :public), visibility <- Map.get(args, :visibility, :public),
{content_html, tags, to, cc} <- {content_html, tags, to, cc} <-
Utils.prepare_content(actor, summary, visibility, [], nil), Utils.prepare_content(actor, summary, visibility, [], nil),

View File

@ -193,7 +193,9 @@ defmodule MobilizonWeb.Resolvers.Event do
} }
} = _resolution } = _resolution
) do ) do
with {:is_owned, true, organizer_actor} <- User.owns_actor(user, organizer_actor_id), # See https://github.com/absinthe-graphql/absinthe/issues/490
with args <- Map.put(args, :options, args[:options] || %{}),
{:is_owned, true, organizer_actor} <- User.owns_actor(user, organizer_actor_id),
{:ok, args} <- save_attached_picture(args), {:ok, args} <- save_attached_picture(args),
{:ok, args} <- save_physical_address(args), {:ok, args} <- save_physical_address(args),
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor), args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
@ -230,10 +232,13 @@ defmodule MobilizonWeb.Resolvers.Event do
} }
} = _resolution } = _resolution
) do ) do
with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id), # See https://github.com/absinthe-graphql/absinthe/issues/490
with args <- Map.put(args, :options, args[:options] || %{}),
{:ok, %Event{} = event} <- Mobilizon.Events.get_event_full(event_id),
{:is_owned, true, organizer_actor} <- User.owns_actor(user, event.organizer_actor_id), {:is_owned, true, organizer_actor} <- User.owns_actor(user, event.organizer_actor_id),
{:ok, args} <- save_attached_picture(args), {:ok, args} <- save_attached_picture(args),
{:ok, args} <- save_physical_address(args), {:ok, args} <- save_physical_address(args),
args <- Map.put(args, :organizer_actor, organizer_actor),
{ {
:ok, :ok,
%Activity{ %Activity{
@ -243,11 +248,14 @@ defmodule MobilizonWeb.Resolvers.Event do
}, },
%Event{} = event %Event{} = event
} <- } <-
MobilizonWeb.API.Events.update_event(args) do MobilizonWeb.API.Events.update_event(args, event) do
{:ok, event} {:ok, event}
else else
{:error, :event_not_found} -> {:error, :event_not_found} ->
{:error, "Event not found"} {:error, "Event not found"}
{:is_owned, _} ->
{:error, "User doesn't own actor"}
end end
end end

View File

@ -229,14 +229,14 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:organizer_actor_id, non_null(:id)) arg(:organizer_actor_id, non_null(:id))
arg(:category, :string, default_value: "meeting") arg(:category, :string, default_value: "meeting")
arg(:physical_address, :address_input) arg(:physical_address, :address_input)
arg(:options, :event_options_input, default_value: %{}) arg(:options, :event_options_input)
resolve(&Event.create_event/3) resolve(&Event.create_event/3)
end end
@desc "Update an event" @desc "Update an event"
field :update_event, type: :event do field :update_event, type: :event do
arg(:event_id, non_null(:integer)) arg(:event_id, non_null(:id))
arg(:title, :string) arg(:title, :string)
arg(:description, :string) arg(:description, :string)
@ -246,6 +246,7 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:status, :integer) arg(:status, :integer)
arg(:public, :boolean) arg(:public, :boolean)
arg(:visibility, :event_visibility) arg(:visibility, :event_visibility)
arg(:organizer_actor_id, :id)
arg(:tags, list_of(:string), description: "The list of tags associated to the event") arg(:tags, list_of(:string), description: "The list of tags associated to the event")
@ -259,6 +260,7 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:phone_address, :string) arg(:phone_address, :string)
arg(:category, :string) arg(:category, :string)
arg(:physical_address, :address_input) arg(:physical_address, :address_input)
arg(:options, :event_options_input)
resolve(&Event.update_event/3) resolve(&Event.update_event/3)
end end

View File

@ -39,21 +39,16 @@ defmodule Mobilizon.Service.ActivityPub do
@doc """ @doc """
Wraps an object into an activity Wraps an object into an activity
""" """
@spec create_activity(map(), boolean()) :: {:ok, %Activity{}} | {:error, any()} @spec create_activity(map(), boolean()) :: {:ok, %Activity{}}
def create_activity(map, local \\ true) when is_map(map) do def create_activity(map, local \\ true) when is_map(map) do
with map <- lazy_put_activity_defaults(map) do with map <- lazy_put_activity_defaults(map) do
activity = %Activity{ {:ok,
data: map, %Activity{
local: local, data: map,
actor: map["actor"], local: local,
recipients: get_recipients(map) actor: map["actor"],
} recipients: get_recipients(map)
}}
# Notification.create_notifications(activity)
# stream_out(activity)
{:ok, activity}
else
error -> {:error, error}
end end
end end
@ -196,7 +191,7 @@ defmodule Mobilizon.Service.ActivityPub do
"object" => object "object" => object
}, },
{:ok, activity} <- create_activity(data, local), {:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data), {:ok, object} <- update_object(object["id"], data),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity, object} {:ok, activity, object}
end end

View File

@ -15,12 +15,30 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Actor do
@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
avatar =
object["icon"]["url"] &&
%{
"name" => object["icon"]["name"] || "avatar",
"url" => object["icon"]["url"]
}
banner =
object["image"]["url"] &&
%{
"name" => object["image"]["name"] || "banner",
"url" => object["image"]["url"]
}
%{ %{
"type" => String.to_existing_atom(object["type"]), "type" => String.to_existing_atom(object["type"]),
"preferred_username" => object["preferred_username"], "preferred_username" => object["preferredUsername"],
"summary" => object["summary"], "summary" => object["summary"],
"url" => object["url"], "url" => object["url"],
"name" => object["name"] "name" => object["name"],
"avatar" => avatar,
"banner" => banner,
"keys" => object["publicKey"]["publicKeyPem"],
"manually_approves_followers" => object["manuallyApprovesFollowers"]
} }
end end

View File

@ -57,6 +57,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
"organizer_actor_id" => actor_id, "organizer_actor_id" => actor_id,
"picture_id" => picture_id, "picture_id" => picture_id,
"begins_on" => object["startTime"], "begins_on" => object["startTime"],
"ends_on" => object["endTime"],
"category" => object["category"], "category" => object["category"],
"url" => object["id"], "url" => object["id"],
"uuid" => object["uuid"], "uuid" => object["uuid"],
@ -173,7 +174,8 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
"startTime" => event.begins_on |> date_to_string(), "startTime" => event.begins_on |> date_to_string(),
"endTime" => event.ends_on |> date_to_string(), "endTime" => event.ends_on |> date_to_string(),
"tag" => event.tags |> build_tags(), "tag" => event.tags |> build_tags(),
"id" => event.url "id" => event.url,
"url" => event.url
} }
res = res =

View File

@ -295,19 +295,15 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} = %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} =
data data
) )
when object_type in ["Person", "Application", "Service", "Organization"] do when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
case Actors.get_actor_by_url(object["id"]) do case Actors.get_actor_by_url(object["id"]) do
{:ok, %Actor{url: url}} -> {:ok, %Actor{url: actor_url}} ->
{:ok, new_actor_data} = ActivityPub.actor_data_from_actor_object(object)
Actors.insert_or_update_actor(new_actor_data)
ActivityPub.update(%{ ActivityPub.update(%{
local: false, local: false,
to: data["to"] || [], to: data["to"] || [],
cc: data["cc"] || [], cc: data["cc"] || [],
object: object, object: object,
actor: url actor: actor_url
}) })
e -> e ->
@ -316,6 +312,28 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end end
end end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => actor} =
_update
) do
with {:ok, %{"actor" => existing_organizer_actor_url} = _existing_event_data} <-
fetch_obj_helper_as_activity_streams(object),
{:ok, %Actor{url: actor_url}} <- actor |> Utils.get_url() |> Actors.get_actor_by_url(),
true <- Utils.get_url(existing_organizer_actor_url) == actor_url do
ActivityPub.update(%{
local: false,
to: object["to"] || [],
cc: object["cc"] || [],
object: object,
actor: actor_url
})
else
e ->
Logger.debug(inspect(e))
:error
end
end
def handle_incoming( def handle_incoming(
%{ %{
"type" => "Undo", "type" => "Undo",

View File

@ -29,6 +29,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
alias MobilizonWeb.Router.Helpers, as: Routes alias MobilizonWeb.Router.Helpers, as: Routes
alias MobilizonWeb.Endpoint alias MobilizonWeb.Endpoint
@actor_types ["Group", "Person", "Application"]
# Some implementations send the actor URI as the actor field, others send the entire actor object, # Some implementations send the actor URI as the actor field, others send the entire actor object,
# so figure out what the actor's URI is based on what we have. # so figure out what the actor's URI is based on what we have.
def get_url(%{"id" => id}), do: id def get_url(%{"id" => id}), do: id
@ -119,7 +121,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
@doc """ @doc """
Inserts a full object if it is contained in an activity. Inserts a full object if it is contained in an activity.
""" """
def insert_full_object(%{"object" => %{"type" => "Event"} = object_data}) def insert_full_object(%{"object" => %{"type" => "Event"} = object_data, "type" => "Create"})
when is_map(object_data) do when is_map(object_data) do
with {:ok, object_data} <- with {:ok, object_data} <-
Converters.Event.as_to_model_data(object_data), Converters.Event.as_to_model_data(object_data),
@ -128,7 +130,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
end end
end end
def insert_full_object(%{"object" => %{"type" => "Group"} = object_data}) def insert_full_object(%{"object" => %{"type" => "Group"} = object_data, "type" => "Create"})
when is_map(object_data) do when is_map(object_data) do
with object_data <- with object_data <-
Map.put(object_data, "preferred_username", object_data["preferredUsername"]), Map.put(object_data, "preferred_username", object_data["preferredUsername"]),
@ -140,7 +142,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
@doc """ @doc """
Inserts a full object if it is contained in an activity. Inserts a full object if it is contained in an activity.
""" """
def insert_full_object(%{"object" => %{"type" => "Note"} = object_data}) def insert_full_object(%{"object" => %{"type" => "Note"} = object_data, "type" => "Create"})
when is_map(object_data) do when is_map(object_data) do
with data <- Converters.Comment.as_to_model_data(object_data), with data <- Converters.Comment.as_to_model_data(object_data),
{:ok, %Comment{} = comment} <- Events.create_comment(data) do {:ok, %Comment{} = comment} <- Events.create_comment(data) do
@ -177,6 +179,39 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
def insert_full_object(_), do: {:ok, nil} def insert_full_object(_), do: {:ok, nil}
@doc """
Update an object
"""
@spec update_object(struct(), map()) :: {:ok, struct()} | any()
def update_object(object, object_data)
def update_object(event_url, %{
"object" => %{"type" => "Event"} = object_data,
"type" => "Update"
})
when is_map(object_data) do
with {:event_not_found, %Event{} = event} <-
{:event_not_found, Events.get_event_by_url(event_url)},
{:ok, object_data} <- Converters.Event.as_to_model_data(object_data),
{:ok, %Event{} = event} <- Events.update_event(event, object_data) do
{:ok, event}
end
end
def update_object(actor_url, %{
"object" => %{"type" => type_actor} = object_data,
"type" => "Update"
})
when is_map(object_data) and type_actor in @actor_types do
with {:ok, %Actor{} = actor} <- Actors.get_actor_by_url(actor_url),
object_data <- Converters.Actor.as_to_model_data(object_data),
{:ok, %Actor{} = actor} <- Actors.update_actor(actor, object_data) do
{:ok, actor}
end
end
def update_object(_, _), do: {:ok, nil}
#### Like-related helpers #### Like-related helpers
# @doc """ # @doc """
@ -264,7 +299,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
String.t(), String.t(),
map(), map(),
list(), list(),
map() map(),
String.t()
) :: map() ) :: map()
def make_event_data( def make_event_data(
actor, actor,
@ -273,10 +309,12 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
content_html, content_html,
picture \\ nil, picture \\ nil,
tags \\ [], tags \\ [],
metadata \\ %{} metadata \\ %{},
uuid \\ nil,
url \\ nil
) do ) do
Logger.debug("Making event data") Logger.debug("Making event data")
uuid = Ecto.UUID.generate() uuid = uuid || Ecto.UUID.generate()
res = %{ res = %{
"type" => "Event", "type" => "Event",
@ -285,9 +323,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"content" => content_html, "content" => content_html,
"name" => title, "name" => title,
"startTime" => metadata.begins_on, "startTime" => metadata.begins_on,
"endTime" => metadata.ends_on,
"category" => metadata.category, "category" => metadata.category,
"actor" => actor, "actor" => actor,
"id" => Routes.page_url(Endpoint, :event, uuid), "id" => url || Routes.page_url(Endpoint, :event, uuid),
"uuid" => uuid, "uuid" => uuid,
"tag" => "tag" =>
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end) tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
@ -505,7 +544,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
activity_id, activity_id,
public public
) )
when type in ["Group", "Person", "Application"] do when type in @actor_types do
do_make_announce_data(actor_url, actor_followers_url, url, url, activity_id, public) do_make_announce_data(actor_url, actor_followers_url, url, url, activity_id, public)
end end

View File

@ -1,5 +1,5 @@
# source: http://localhost:4000/api # source: http://localhost:4000/api
# timestamp: Mon Sep 02 2019 16:41:17 GMT+0200 (GMT+02:00) # timestamp: Thu Sep 05 2019 13:00:10 GMT+0200 (GMT+02:00)
schema { schema {
query: RootQueryType query: RootQueryType
@ -440,7 +440,7 @@ enum EventVisibility {
"""Visible only to people members of the group or followers of the person""" """Visible only to people members of the group or followers of the person"""
PRIVATE PRIVATE
"""Publically listed and federated. Can be shared.""" """Publicly listed and federated. Can be shared."""
PUBLIC PUBLIC
"""Visible only to people with the link - or invited""" """Visible only to people with the link - or invited"""
@ -823,7 +823,7 @@ type RootMutationType {
"""Create an event""" """Create an event"""
createEvent( createEvent(
beginsOn: DateTime! beginsOn: DateTime!
category: String category: String = "meeting"
description: String! description: String!
endsOn: DateTime endsOn: DateTime
onlineAddress: String onlineAddress: String
@ -852,11 +852,6 @@ type RootMutationType {
"""Create a group""" """Create a group"""
createGroup( createGroup(
"""
The actor's username which will be the admin (otherwise user's default one)
"""
adminActorUsername: String
""" """
The avatar for the group, either as an object or directly the ID of an existing Picture The avatar for the group, either as an object or directly the ID of an existing Picture
""" """
@ -867,14 +862,17 @@ type RootMutationType {
""" """
banner: PictureInput banner: PictureInput
"""The summary for the group""" """The identity that creates the group"""
description: String = "" creatorActorId: Int!
"""The displayed name for the group""" """The displayed name for the group"""
name: String name: String
"""The name for the group""" """The name for the group"""
preferredUsername: String! preferredUsername: String!
"""The summary for the group"""
summary: String = ""
): Group ): Group
"""Create a new person for user""" """Create a new person for user"""
@ -969,6 +967,34 @@ type RootMutationType {
"""Send a link through email to reset user password""" """Send a link through email to reset user password"""
sendResetPassword(email: String!, locale: String = "en"): String sendResetPassword(email: String!, locale: String = "en"): String
"""Update an event"""
updateEvent(
beginsOn: DateTime
category: String
description: String
endsOn: DateTime
eventId: ID!
onlineAddress: String
options: EventOptionsInput
organizerActorId: ID
phoneAddress: String
physicalAddress: AddressInput
"""
The picture for the event, either as an object or directly the ID of an existing Picture
"""
picture: PictureInput
public: Boolean
publishAt: DateTime
state: Int
status: Int
"""The list of tags associated to the event"""
tags: [String]
title: String
visibility: EventVisibility
): Event
"""Update an identity""" """Update an identity"""
updatePerson( updatePerson(
""" """

View File

@ -1,24 +1,24 @@
{ {
"type": "Update", "type": "Update",
"object": { "object": {
"url": "http://mastodon.example.org/@gargron", "url": "https://framapiaf.org/@framasoft",
"type": "Person", "type": "Person",
"summary": "<p>Some bio</p>", "summary": "<p>Some bio</p>",
"publicKey": { "publicKey": {
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n",
"owner": "http://mastodon.example.org/users/gargron", "owner": "https://framapiaf.org/users/framasoft",
"id": "http://mastodon.example.org/users/gargron#main-key" "id": "https://framapiaf.org/users/framasoft#main-key"
}, },
"preferredUsername": "gargron", "preferredUsername": "framasoft",
"outbox": "http://mastodon.example.org/users/gargron/outbox", "outbox": "https://framapiaf.org/users/framasoft/outbox",
"name": "gargle", "name": "nextsoft",
"manuallyApprovesFollowers": false, "manuallyApprovesFollowers": false,
"inbox": "http://mastodon.example.org/users/gargron/inbox", "inbox": "https://framapiaf.org/users/framasoft/inbox",
"id": "http://mastodon.example.org/users/gargron", "id": "https://framapiaf.org/users/framasoft",
"following": "http://mastodon.example.org/users/gargron/following", "following": "https://framapiaf.org/users/framasoft/following",
"followers": "http://mastodon.example.org/users/gargron/followers", "followers": "https://framapiaf.org/users/framasoft/followers",
"endpoints": { "endpoints": {
"sharedInbox": "http://mastodon.example.org/inbox" "sharedInbox": "https://framapiaf.org/inbox"
}, },
"icon":{ "icon":{
"type":"Image", "type":"Image",
@ -31,8 +31,8 @@
"url":"https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png" "url":"https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png"
} }
}, },
"id": "http://mastodon.example.org/users/gargron#updates/1519563538", "id": "https://framapiaf.org/users/gargron#updates/1519563538",
"actor": "http://mastodon.example.org/users/gargron", "actor": "https://framapiaf.org/users/framasoft",
"@context": [ "@context": [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1", "https://w3id.org/security/v1",

View File

@ -270,10 +270,8 @@ defmodule Mobilizon.ActorsTest do
assert %Actor{} = actor assert %Actor{} = actor
assert actor.summary == "some updated description" assert actor.summary == "some updated description"
assert actor.name == "some updated name" assert actor.name == "some updated name"
assert actor.domain == "some updated domain"
assert actor.keys == "some updated keys" assert actor.keys == "some updated keys"
refute actor.suspended refute actor.suspended
assert actor.preferred_username == "some updated username"
end end
test "update_actor/2 with valid data updates the actor and it's media files", %{ test "update_actor/2 with valid data updates the actor and it's media files", %{
@ -310,10 +308,8 @@ defmodule Mobilizon.ActorsTest do
assert %Actor{} = actor assert %Actor{} = actor
assert actor.summary == "some updated description" assert actor.summary == "some updated description"
assert actor.name == "some updated name" assert actor.name == "some updated name"
assert actor.domain == "some updated domain"
assert actor.keys == "some updated keys" assert actor.keys == "some updated keys"
refute actor.suspended refute actor.suspended
assert actor.preferred_username == "some updated username"
refute File.exists?( refute File.exists?(
Mobilizon.CommonConfig.get!([MobilizonWeb.Uploaders.Local, :uploads]) <> Mobilizon.CommonConfig.get!([MobilizonWeb.Uploaders.Local, :uploads]) <>

View File

@ -9,6 +9,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
import Mobilizon.Factory import Mobilizon.Factory
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Service.HTTPSignatures.Signature alias Mobilizon.Service.HTTPSignatures.Signature
@ -137,11 +138,14 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
end end
describe "update" do describe "update" do
@updated_actor_summary "This is an updated actor"
test "it creates an update activity with the new actor data" do test "it creates an update activity with the new actor data" 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})
actor_data = Map.put(actor_data, "summary", @updated_actor_summary)
{:ok, update, _} = {:ok, update, updated_actor} =
ActivityPub.update(%{ ActivityPub.update(%{
actor: actor_data["url"], actor: actor_data["url"],
to: [actor.url <> "/followers"], to: [actor.url <> "/followers"],
@ -153,6 +157,42 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
assert update.data["to"] == [actor.url <> "/followers"] assert update.data["to"] == [actor.url <> "/followers"]
assert update.data["object"]["id"] == actor_data["id"] assert update.data["object"]["id"] == actor_data["id"]
assert update.data["object"]["type"] == actor_data["type"] assert update.data["object"]["type"] == actor_data["type"]
assert update.data["object"]["summary"] == @updated_actor_summary
refute updated_actor.summary == actor.summary
{:ok, %Actor{} = database_actor} = Mobilizon.Actors.get_actor_by_url(actor.url)
assert database_actor.summary == @updated_actor_summary
assert database_actor.preferred_username == actor.preferred_username
end
@updated_start_time DateTime.utc_now() |> DateTime.truncate(:second)
test "it creates an update activity with the new event data" do
actor = insert(:actor)
event = insert(:event, organizer_actor: actor)
event_data = Mobilizon.Service.ActivityPub.Converters.Event.model_to_as(event)
event_data = Map.put(event_data, "startTime", @updated_start_time)
{:ok, update, updated_event} =
ActivityPub.update(%{
actor: actor.url,
to: [actor.url <> "/followers"],
cc: [],
object: event_data
})
assert update.data["actor"] == actor.url
assert update.data["to"] == [actor.url <> "/followers"]
assert update.data["object"]["id"] == event_data["id"]
assert update.data["object"]["type"] == event_data["type"]
assert update.data["object"]["startTime"] == @updated_start_time
refute updated_event.begins_on == event.begins_on
%Event{} = database_event = Mobilizon.Events.get_event_by_url(event.url)
assert database_event.begins_on == @updated_start_time
assert database_event.title == event.title
end end
end end
end end

View File

@ -17,7 +17,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.ActorTest do
actor = actor =
ActorConverter.as_to_model_data(%{ ActorConverter.as_to_model_data(%{
"type" => "Person", "type" => "Person",
"preferred_username" => "test_account" "preferredUsername" => "test_account"
}) })
assert actor["type"] == :Person assert actor["type"] == :Person

View File

@ -330,7 +330,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
assert data["object"] == comment.url assert data["object"] == comment.url
end end
test "it works for incoming update activities" do test "it works for incoming update activities on actors" 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)
@ -349,11 +349,37 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
{: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 == "nextsoft"
assert actor.summary == "<p>Some bio</p>" assert actor.summary == "<p>Some bio</p>"
end end
test "it works for incoming update activities on events" do
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
object =
data["object"]
|> Map.put("actor", data["actor"])
|> Map.put("name", "My updated event")
|> Map.put("id", data["object"]["id"])
|> Map.put("type", "Event")
update_data =
update_data
|> Map.put("actor", data["actor"])
|> Map.put("object", object)
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
%Event{} = event = Events.get_event_by_url(data["object"]["id"])
assert event.title == "My updated event"
assert event.description == data["object"]["content"]
end
# test "it works for incoming update activities which lock the account" do # test "it works for incoming update activities which lock the account" do
# data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() # data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()

View File

@ -418,12 +418,12 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"] assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"]
end end
test "update_event/3 should check the event exists", %{conn: conn, actor: actor, user: user} do test "update_event/3 should check the event exists", %{conn: conn, actor: _actor, user: user} do
mutation = """ mutation = """
mutation { mutation {
updateEvent( updateEvent(
event_id: 45, event_id: 45,
title: "my event updated", title: "my event updated"
) { ) {
title, title,
uuid, uuid,
@ -445,7 +445,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
test "update_event/3 should check the user is an administrator", %{ test "update_event/3 should check the user is an administrator", %{
conn: conn, conn: conn,
actor: actor, actor: _actor,
user: user user: user
} do } do
event = insert(:event) event = insert(:event)
@ -454,6 +454,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
mutation { mutation {
updateEvent( updateEvent(
title: "my event updated", title: "my event updated",
event_id: #{event.id}
) { ) {
title, title,
uuid, uuid,
@ -470,11 +471,11 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|> auth_conn(user) |> auth_conn(user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil assert hd(json_response(res, 200)["errors"])["message"] == "User doesn't own actor"
end end
test "update_event/3 updates an event", %{conn: conn, actor: actor, user: user} do test "update_event/3 updates an event", %{conn: conn, actor: actor, user: user} do
event = insert(:event) event = insert(:event, organizer_actor: actor)
begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
@ -484,12 +485,14 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
title: "my event updated", title: "my event updated",
description: "description updated", description: "description updated",
begins_on: "#{begins_on}", begins_on: "#{begins_on}",
event_id: #{event.id},
organizer_actor_id: "#{actor.id}", organizer_actor_id: "#{actor.id}",
category: "birthday", category: "birthday",
tags: ["tag1_updated", "tag2_updated"] tags: ["tag1_updated", "tag2_updated"]
) { ) {
title, title,
uuid, uuid,
url,
tags { tags {
title, title,
slug slug
@ -505,13 +508,76 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["updateEvent"]["title"] == "my event updated" assert json_response(res, 200)["data"]["updateEvent"]["title"] == "my event updated"
assert json_response(res, 200)["data"]["updateEvent"]["uuid"] == event.uuid
assert json_response(res, 200)["data"]["updateEvent"]["url"] == event.url
assert json_response(res, 200)["data"]["createEvent"]["tags"] == [ assert json_response(res, 200)["data"]["updateEvent"]["tags"] == [
%{"slug" => "tag1_updated", "title" => "tag1_updated"}, %{"slug" => "tag1-updated", "title" => "tag1_updated"},
%{"slug" => "tag2_updated", "title" => "tag2_updated"} %{"slug" => "tag2-updated", "title" => "tag2_updated"}
] ]
end end
test "update_event/3 updates an event with a new picture", %{
conn: conn,
actor: actor,
user: user
} do
event = insert(:event, organizer_actor: actor)
begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
mutation = """
mutation {
updateEvent(
title: "my event updated",
description: "description updated",
begins_on: "#{begins_on}",
event_id: #{event.id},
organizer_actor_id: "#{actor.id}",
category: "birthday",
picture: {
picture: {
name: "picture for my event",
alt: "A very sunny landscape",
file: "event.jpg",
actor_id: "#{actor.id}"
}
}
) {
title,
uuid,
url,
picture {
name,
url
}
}
}
"""
map = %{
"query" => mutation,
"event.jpg" => %Plug.Upload{
path: "test/fixtures/picture.png",
filename: "event.jpg"
}
}
res =
conn
|> auth_conn(user)
|> put_req_header("content-type", "multipart/form-data")
|> post("/api", map)
assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["updateEvent"]["title"] == "my event updated"
assert json_response(res, 200)["data"]["updateEvent"]["uuid"] == event.uuid
assert json_response(res, 200)["data"]["updateEvent"]["url"] == event.url
assert json_response(res, 200)["data"]["updateEvent"]["picture"]["name"] ==
"picture for my event"
end
test "list_events/3 returns events", context do test "list_events/3 returns events", context do
event = insert(:event) event = insert(:event)

View File

@ -67,8 +67,8 @@ defmodule Mobilizon.Factory do
def tag_factory do def tag_factory do
%Mobilizon.Events.Tag{ %Mobilizon.Events.Tag{
title: "MyTag", title: sequence("MyTag"),
slug: sequence("MyTag") slug: sequence("my-tag")
} }
end end