Merge branch 'feature/edit-event' into 'master'
Improve create event and prepare update event See merge request framasoft/mobilizon!176
This commit is contained in:
commit
ba6d684786
@ -81,11 +81,12 @@ import { Modal } from 'buefy/dist/components/dialog';
|
||||
export default class AddressAutoComplete extends Vue {
|
||||
|
||||
@Prop({ required: false, default: () => [] }) initialData!: IAddress[];
|
||||
@Prop({ required: false }) value!: IAddress;
|
||||
|
||||
data: IAddress[] = this.initialData;
|
||||
selected: IAddress|null = new Address();
|
||||
isFetching: boolean = false;
|
||||
queryText: string = '';
|
||||
queryText: string = this.value && this.value.description || '';
|
||||
addressModalActive: boolean = false;
|
||||
|
||||
async getAsyncData(query) {
|
||||
|
@ -1,33 +1,51 @@
|
||||
<template>
|
||||
<b-field label="Enter some tags">
|
||||
<b-taginput
|
||||
v-model="tags"
|
||||
v-model="tagsStrings"
|
||||
:data="filteredTags"
|
||||
autocomplete
|
||||
:allow-new="true"
|
||||
:field="path"
|
||||
icon="label"
|
||||
placeholder="Add a tag"
|
||||
@typing="getFilteredTags">
|
||||
@typing="getFilteredTags"
|
||||
>
|
||||
</b-taginput>
|
||||
</b-field>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { get } from 'lodash';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { get, differenceBy } from 'lodash';
|
||||
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 {
|
||||
|
||||
@Prop({ required: false, default: () => [] }) data!: object[];
|
||||
@Prop({ required: false, default: () => [] }) data!: ITag[];
|
||||
@Prop({ required: true, default: 'value' }) path!: string;
|
||||
@Prop({ required: true }) value!: string;
|
||||
@Prop({ required: true }) value!: ITag[];
|
||||
|
||||
filteredTags: object[] = [];
|
||||
tags: object[] = [];
|
||||
filteredTags: ITag[] = [];
|
||||
|
||||
getFilteredTags(text) {
|
||||
this.filteredTags = this.data.filter((option) => {
|
||||
this.filteredTags = differenceBy(this.data, this.value, 'id').filter((option) => {
|
||||
return get(option, this.path)
|
||||
.toString()
|
||||
.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 {
|
||||
return x.slug !== undefined;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ export const FETCH_EVENT = gql`
|
||||
domain,
|
||||
name,
|
||||
url,
|
||||
id,
|
||||
},
|
||||
# attributedTo {
|
||||
# avatar {
|
||||
@ -64,6 +65,7 @@ export const FETCH_EVENT = gql`
|
||||
${participantQuery}
|
||||
},
|
||||
tags {
|
||||
id,
|
||||
slug,
|
||||
title
|
||||
},
|
||||
@ -82,6 +84,25 @@ export const FETCH_EVENT = gql`
|
||||
domain,
|
||||
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!,
|
||||
$category: String,
|
||||
$beginsOn: DateTime!,
|
||||
$endsOn: DateTime,
|
||||
$picture: PictureInput,
|
||||
$tags: [String],
|
||||
$options: EventOptionsInput,
|
||||
@ -154,6 +176,7 @@ export const CREATE_EVENT = gql`
|
||||
title: $title,
|
||||
description: $description,
|
||||
beginsOn: $beginsOn,
|
||||
endsOn: $endsOn,
|
||||
organizerActorId: $organizerActorId,
|
||||
category: $category,
|
||||
options: $options,
|
||||
@ -173,13 +196,32 @@ export const CREATE_EVENT = gql`
|
||||
`;
|
||||
|
||||
export const EDIT_EVENT = gql`
|
||||
mutation EditEvent(
|
||||
mutation updateEvent(
|
||||
$id: ID!,
|
||||
$title: String!,
|
||||
$description: String!,
|
||||
$organizerActorId: Int!,
|
||||
$category: String
|
||||
$organizerActorId: ID!,
|
||||
$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
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import { ITag } from '@/types/tag.model';
|
||||
import { IPicture } from '@/types/picture.model';
|
||||
|
||||
export enum EventStatus {
|
||||
TENTATIVE,
|
||||
CONFIRMED,
|
||||
CANCELLED,
|
||||
TENTATIVE = 'TENTATIVE',
|
||||
CONFIRMED = 'CONFIRMED',
|
||||
CANCELLED = 'CANCELLED',
|
||||
}
|
||||
|
||||
export enum EventVisibility {
|
||||
@ -17,9 +17,9 @@ export enum EventVisibility {
|
||||
}
|
||||
|
||||
export enum EventJoinOptions {
|
||||
FREE,
|
||||
RESTRICTED,
|
||||
INVITE,
|
||||
FREE = 'FREE',
|
||||
RESTRICTED = 'RESTRICTED',
|
||||
INVITE = 'INVITE',
|
||||
}
|
||||
|
||||
export enum EventVisibilityJoinOptions {
|
||||
|
@ -177,7 +177,7 @@
|
||||
<script lang="ts">
|
||||
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event';
|
||||
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 { IPerson, Person } from '@/types/actor';
|
||||
import PictureUpload from '@/components/PictureUpload.vue';
|
||||
@ -207,6 +207,7 @@ export default class EditEvent extends Vue {
|
||||
eventId!: string | undefined;
|
||||
|
||||
loggedPerson = new Person();
|
||||
tags: ITag[] = [];
|
||||
event = new EventModel();
|
||||
pictureFile: File | null = null;
|
||||
|
||||
@ -223,7 +224,7 @@ export default class EditEvent extends Vue {
|
||||
|
||||
@Watch('$route.params.eventId', { immediate: true })
|
||||
async onEventIdParamChanged (val: string) {
|
||||
if (this.isUpdate !== true) return;
|
||||
if (!this.isUpdate) return;
|
||||
|
||||
this.eventId = val;
|
||||
|
||||
@ -231,6 +232,7 @@ export default class EditEvent extends Vue {
|
||||
this.event = await this.getEvent();
|
||||
|
||||
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.endsOn = end;
|
||||
console.log('eventvisibilityjoinoptions', this.eventVisibilityJoinOptions);
|
||||
}
|
||||
|
||||
createOrUpdate(e: Event) {
|
||||
@ -261,7 +262,7 @@ export default class EditEvent extends Vue {
|
||||
|
||||
console.log('Event created', data);
|
||||
|
||||
this.$router.push({
|
||||
await this.$router.push({
|
||||
name: 'Event',
|
||||
params: { uuid: data.createEvent.uuid },
|
||||
});
|
||||
@ -277,7 +278,7 @@ export default class EditEvent extends Vue {
|
||||
variables: this.buildVariables(),
|
||||
});
|
||||
|
||||
this.$router.push({
|
||||
await this.$router.push({
|
||||
name: 'Event',
|
||||
params: { uuid: this.eventId as string },
|
||||
});
|
||||
@ -297,6 +298,8 @@ export default class EditEvent extends Vue {
|
||||
};
|
||||
const res = Object.assign({}, this.event, obj);
|
||||
|
||||
delete this.event.options['__typename'];
|
||||
|
||||
if (this.event.physicalAddress) {
|
||||
delete this.event.physicalAddress['__typename'];
|
||||
}
|
||||
|
@ -57,7 +57,7 @@
|
||||
<p class="control">
|
||||
<router-link
|
||||
class="button"
|
||||
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
|
||||
:to="{ name: 'EditEvent', params: {eventId: event.uuid}}"
|
||||
>
|
||||
<translate>Edit</translate>
|
||||
</router-link>
|
||||
|
@ -110,6 +110,24 @@ defmodule Mobilizon.Actors.Actor do
|
||||
|> unique_constraint(:url, name: :actors_url_index)
|
||||
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 """
|
||||
Changeset for person registration
|
||||
"""
|
||||
|
@ -124,7 +124,7 @@ defmodule Mobilizon.Actors do
|
||||
"""
|
||||
def update_actor(%Actor{} = actor, attrs) do
|
||||
actor
|
||||
|> Actor.changeset(attrs)
|
||||
|> Actor.update_changeset(attrs)
|
||||
|> delete_files_if_media_changed()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
@ -54,10 +54,10 @@ defmodule Mobilizon.Events.Event do
|
||||
field(:online_address, :string)
|
||||
field(:phone_address, :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(: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)
|
||||
has_many(:tracks, Track)
|
||||
has_many(:sessions, Session)
|
||||
@ -98,6 +98,38 @@ defmodule Mobilizon.Events.Event do
|
||||
])
|
||||
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)
|
||||
when organizer_actor_id == actor_id do
|
||||
{:event_can_be_managed, true}
|
||||
|
@ -227,11 +227,12 @@ defmodule Mobilizon.Events do
|
||||
:tracks,
|
||||
:tags,
|
||||
:participants,
|
||||
:physical_address
|
||||
:physical_address,
|
||||
:picture
|
||||
])}
|
||||
|
||||
err ->
|
||||
{:error, err}
|
||||
_err ->
|
||||
{:error, :event_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@ -435,7 +436,8 @@ defmodule Mobilizon.Events do
|
||||
"""
|
||||
def update_event(%Event{} = event, attrs) do
|
||||
event
|
||||
|> Event.changeset(attrs)
|
||||
|> Repo.preload(:tags)
|
||||
|> Event.update_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
|
@ -2,8 +2,7 @@ defmodule MobilizonWeb.API.Events do
|
||||
@moduledoc """
|
||||
API for Events
|
||||
"""
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
||||
alias MobilizonWeb.API.Utils
|
||||
@ -12,28 +11,23 @@ defmodule MobilizonWeb.API.Events do
|
||||
Create an event
|
||||
"""
|
||||
@spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
|
||||
def create_event(
|
||||
%{
|
||||
begins_on: begins_on,
|
||||
description: description,
|
||||
options: options,
|
||||
organizer_actor_id: organizer_actor_id,
|
||||
def create_event(%{organizer_actor: organizer_actor} = args) do
|
||||
with %{
|
||||
title: title,
|
||||
physical_address: physical_address,
|
||||
picture: picture,
|
||||
content_html: content_html,
|
||||
tags: tags,
|
||||
title: title
|
||||
} = args
|
||||
)
|
||||
when is_map(options) do
|
||||
with %Actor{url: url} = actor <-
|
||||
Actors.get_local_actor_with_everything(organizer_actor_id),
|
||||
physical_address <- Map.get(args, :physical_address, nil),
|
||||
title <- String.trim(title),
|
||||
visibility <- Map.get(args, :visibility, :public),
|
||||
picture <- Map.get(args, :picture, nil),
|
||||
{content_html, tags, to, cc} <-
|
||||
Utils.prepare_content(actor, description, visibility, tags, nil),
|
||||
to: to,
|
||||
cc: cc,
|
||||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
category: category,
|
||||
options: options
|
||||
} <- prepare_args(args),
|
||||
event <-
|
||||
ActivityPubUtils.make_event_data(
|
||||
url,
|
||||
organizer_actor.url,
|
||||
%{to: to, cc: cc},
|
||||
title,
|
||||
content_html,
|
||||
@ -41,17 +35,104 @@ defmodule MobilizonWeb.API.Events do
|
||||
tags,
|
||||
%{
|
||||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
physical_address: physical_address,
|
||||
category: Map.get(args, :category),
|
||||
category: category,
|
||||
options: options
|
||||
}
|
||||
) do
|
||||
ActivityPub.create(%{
|
||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
actor: actor,
|
||||
actor: organizer_actor,
|
||||
object: event,
|
||||
local: true
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update an event
|
||||
"""
|
||||
@spec update_event(map(), Event.t()) :: {:ok, Activity.t(), Event.t()} | any()
|
||||
def update_event(
|
||||
%{
|
||||
organizer_actor: organizer_actor
|
||||
} = args,
|
||||
%Event{} = event
|
||||
) do
|
||||
with args <- Map.put(args, :tags, Map.get(args, :tags, [])),
|
||||
%{
|
||||
title: title,
|
||||
physical_address: physical_address,
|
||||
picture: picture,
|
||||
content_html: content_html,
|
||||
tags: tags,
|
||||
to: to,
|
||||
cc: cc,
|
||||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
category: category,
|
||||
options: options
|
||||
} <-
|
||||
prepare_args(Map.merge(event, args)),
|
||||
event <-
|
||||
ActivityPubUtils.make_event_data(
|
||||
organizer_actor.url,
|
||||
%{to: to, cc: cc},
|
||||
title,
|
||||
content_html,
|
||||
picture,
|
||||
tags,
|
||||
%{
|
||||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
physical_address: physical_address,
|
||||
category: category,
|
||||
options: options
|
||||
},
|
||||
event.uuid,
|
||||
event.url
|
||||
) do
|
||||
ActivityPub.update(%{
|
||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
actor: organizer_actor.url,
|
||||
cc: [],
|
||||
object: event,
|
||||
local: true
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_args(
|
||||
%{
|
||||
organizer_actor: organizer_actor,
|
||||
title: title,
|
||||
description: description,
|
||||
options: options,
|
||||
tags: tags,
|
||||
begins_on: begins_on,
|
||||
category: category
|
||||
} = args
|
||||
) do
|
||||
with physical_address <- Map.get(args, :physical_address, nil),
|
||||
title <- String.trim(title),
|
||||
visibility <- Map.get(args, :visibility, :public),
|
||||
picture <- Map.get(args, :picture, nil),
|
||||
{content_html, tags, to, cc} <-
|
||||
Utils.prepare_content(organizer_actor, description, visibility, tags, nil) do
|
||||
%{
|
||||
title: title,
|
||||
physical_address: physical_address,
|
||||
picture: picture,
|
||||
content_html: content_html,
|
||||
tags: tags,
|
||||
to: to,
|
||||
cc: cc,
|
||||
begins_on: begins_on,
|
||||
ends_on: Map.get(args, :ends_on, nil),
|
||||
category: category,
|
||||
options: options
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -18,13 +18,13 @@ defmodule MobilizonWeb.API.Groups do
|
||||
preferred_username: title,
|
||||
summary: summary,
|
||||
creator_actor_id: creator_actor_id,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
avatar: _avatar,
|
||||
banner: _banner
|
||||
} = args
|
||||
) do
|
||||
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),
|
||||
{:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
|
||||
visibility <- Map.get(args, :visibility, :public),
|
||||
{content_html, tags, to, cc} <-
|
||||
Utils.prepare_content(actor, summary, visibility, [], nil),
|
||||
|
@ -58,7 +58,8 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
) do
|
||||
# We get the organizer's next public event
|
||||
events =
|
||||
[Events.get_actor_upcoming_public_event(organizer_actor, uuid)] |> Enum.filter(&is_map/1)
|
||||
[Events.get_actor_upcoming_public_event(organizer_actor, uuid)]
|
||||
|> Enum.filter(&is_map/1)
|
||||
|
||||
# We find similar events with the same tags
|
||||
# uniq_by : It's possible event_from_same_actor is inside events_from_tags
|
||||
@ -150,7 +151,17 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
{:has_event, {:ok, %Event{} = event}} <-
|
||||
{:has_event, Mobilizon.Events.get_event_full(event_id)},
|
||||
{:ok, _activity, _participant} <- MobilizonWeb.API.Participations.leave(event, actor) do
|
||||
{:ok, %{event: %{id: event_id}, actor: %{id: actor_id}}}
|
||||
{
|
||||
:ok,
|
||||
%{
|
||||
event: %{
|
||||
id: event_id
|
||||
},
|
||||
actor: %{
|
||||
id: actor_id
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{:has_event, _} ->
|
||||
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||
@ -173,12 +184,35 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
@doc """
|
||||
Create an event
|
||||
"""
|
||||
def create_event(_parent, args, %{context: %{current_user: _user}} = _resolution) do
|
||||
with {:ok, args} <- save_attached_picture(args),
|
||||
def create_event(
|
||||
_parent,
|
||||
%{organizer_actor_id: organizer_actor_id} = args,
|
||||
%{
|
||||
context: %{
|
||||
current_user: user
|
||||
}
|
||||
} = _resolution
|
||||
) do
|
||||
# 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_physical_address(args),
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"} = _object}}, %Event{} = event} <-
|
||||
MobilizonWeb.API.Events.create_event(args) do
|
||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{
|
||||
:ok,
|
||||
%Activity{
|
||||
data: %{
|
||||
"object" => %{"type" => "Event"} = _object
|
||||
}
|
||||
},
|
||||
%Event{} = event
|
||||
} <-
|
||||
MobilizonWeb.API.Events.create_event(args_with_organizer) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:is_owned, false} ->
|
||||
{:error, "Organizer actor id is not owned by the user"}
|
||||
end
|
||||
end
|
||||
|
||||
@ -186,19 +220,72 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
{:error, "You need to be logged-in to create events"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Update an event
|
||||
"""
|
||||
def update_event(
|
||||
_parent,
|
||||
%{event_id: event_id} = args,
|
||||
%{
|
||||
context: %{
|
||||
current_user: user
|
||||
}
|
||||
} = _resolution
|
||||
) do
|
||||
# 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),
|
||||
{:ok, args} <- save_attached_picture(args),
|
||||
{:ok, args} <- save_physical_address(args),
|
||||
args <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{
|
||||
:ok,
|
||||
%Activity{
|
||||
data: %{
|
||||
"object" => %{"type" => "Event"} = _object
|
||||
}
|
||||
},
|
||||
%Event{} = event
|
||||
} <-
|
||||
MobilizonWeb.API.Events.update_event(args, event) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:error, :event_not_found} ->
|
||||
{:error, "Event not found"}
|
||||
|
||||
{:is_owned, _} ->
|
||||
{:error, "User doesn't own actor"}
|
||||
end
|
||||
end
|
||||
|
||||
def update_event(_parent, _args, _resolution) do
|
||||
{:error, "You need to be logged-in to update an event"}
|
||||
end
|
||||
|
||||
# If we have an attached picture, just transmit it. It will be handled by
|
||||
# Mobilizon.Service.ActivityPub.Utils.make_picture_data/1
|
||||
# However, we need to pass it's actor ID
|
||||
@spec save_attached_picture(map()) :: {:ok, map()}
|
||||
defp save_attached_picture(
|
||||
%{picture: %{picture: %{file: %Plug.Upload{} = _picture} = all_pic}} = args
|
||||
%{
|
||||
picture: %{
|
||||
picture: %{file: %Plug.Upload{} = _picture} = all_pic
|
||||
}
|
||||
} = args
|
||||
) do
|
||||
{:ok, Map.put(args, :picture, Map.put(all_pic, :actor_id, args.organizer_actor_id))}
|
||||
end
|
||||
|
||||
# Otherwise if we use a previously uploaded picture we need to fetch it from database
|
||||
@spec save_attached_picture(map()) :: {:ok, map()}
|
||||
defp save_attached_picture(%{picture: %{picture_id: picture_id}} = args) do
|
||||
defp save_attached_picture(
|
||||
%{
|
||||
picture: %{
|
||||
picture_id: picture_id
|
||||
}
|
||||
} = args
|
||||
) do
|
||||
with %Picture{} = picture <- Mobilizon.Media.get_picture(picture_id) do
|
||||
{:ok, Map.put(args, :picture, picture)}
|
||||
end
|
||||
@ -208,7 +295,13 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
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)
|
||||
defp save_physical_address(
|
||||
%{
|
||||
physical_address: %{
|
||||
url: physical_address_url
|
||||
}
|
||||
} = args
|
||||
)
|
||||
when not is_nil(physical_address_url) do
|
||||
with %Address{} = address <- Addresses.get_address_by_url(physical_address_url),
|
||||
args <- Map.put(args, :physical_address, address.url) do
|
||||
@ -230,9 +323,15 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||
@doc """
|
||||
Delete an event
|
||||
"""
|
||||
def delete_event(_parent, %{event_id: event_id, actor_id: actor_id}, %{
|
||||
context: %{current_user: user}
|
||||
}) do
|
||||
def delete_event(
|
||||
_parent,
|
||||
%{event_id: event_id, actor_id: actor_id},
|
||||
%{
|
||||
context: %{
|
||||
current_user: user
|
||||
}
|
||||
}
|
||||
) do
|
||||
with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
|
||||
{:is_owned, true, _} <- User.owns_actor(user, actor_id),
|
||||
{:event_can_be_managed, true} <- Event.can_event_be_managed_by(event, actor_id),
|
||||
|
@ -229,11 +229,42 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||
arg(:organizer_actor_id, non_null(:id))
|
||||
arg(:category, :string, default_value: "meeting")
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input, default_value: %{})
|
||||
arg(:options, :event_options_input)
|
||||
|
||||
resolve(&Event.create_event/3)
|
||||
end
|
||||
|
||||
@desc "Update an event"
|
||||
field :update_event, type: :event do
|
||||
arg(:event_id, non_null(:id))
|
||||
|
||||
arg(:title, :string)
|
||||
arg(:description, :string)
|
||||
arg(:begins_on, :datetime)
|
||||
arg(:ends_on, :datetime)
|
||||
arg(:state, :integer)
|
||||
arg(:status, :integer)
|
||||
arg(:public, :boolean)
|
||||
arg(:visibility, :event_visibility)
|
||||
arg(:organizer_actor_id, :id)
|
||||
|
||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
||||
|
||||
arg(:picture, :picture_input,
|
||||
description:
|
||||
"The picture for the event, either as an object or directly the ID of an existing Picture"
|
||||
)
|
||||
|
||||
arg(:publish_at, :datetime)
|
||||
arg(:online_address, :string)
|
||||
arg(:phone_address, :string)
|
||||
arg(:category, :string)
|
||||
arg(:physical_address, :address_input)
|
||||
arg(:options, :event_options_input)
|
||||
|
||||
resolve(&Event.update_event/3)
|
||||
end
|
||||
|
||||
@desc "Delete an event"
|
||||
field :delete_event, :deleted_object do
|
||||
arg(:event_id, non_null(:integer))
|
||||
|
@ -39,24 +39,16 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
@doc """
|
||||
Wraps an object into an activity
|
||||
"""
|
||||
# TODO: Rename me
|
||||
@spec insert(map(), boolean()) :: {:ok, %Activity{}} | {:error, any()}
|
||||
def insert(map, local \\ true) when is_map(map) do
|
||||
with map <- lazy_put_activity_defaults(map),
|
||||
{:ok, object} <- insert_full_object(map) do
|
||||
activity = %Activity{
|
||||
@spec create_activity(map(), boolean()) :: {:ok, %Activity{}}
|
||||
def create_activity(map, local \\ true) when is_map(map) do
|
||||
with map <- lazy_put_activity_defaults(map) do
|
||||
{:ok,
|
||||
%Activity{
|
||||
data: map,
|
||||
local: local,
|
||||
actor: map["actor"],
|
||||
recipients: get_recipients(map)
|
||||
}
|
||||
|
||||
# Notification.create_notifications(activity)
|
||||
# stream_out(activity)
|
||||
{:ok, activity, object}
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
error -> {:error, error}
|
||||
}}
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,7 +129,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
%{to: to, actor: actor, published: published, object: object},
|
||||
additional
|
||||
),
|
||||
{:ok, activity, object} <- insert(create_data, local),
|
||||
{:ok, activity} <- create_activity(create_data, local),
|
||||
{:ok, object} <- insert_full_object(create_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
# {:ok, actor} <- Actors.increase_event_count(actor) do
|
||||
{:ok, activity, object}
|
||||
@ -160,7 +153,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
"object" => object,
|
||||
"id" => activity_wrapper_id || get_url(object) <> "/activity"
|
||||
},
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -177,7 +171,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
"object" => object,
|
||||
"id" => activity_wrapper_id || get_url(object) <> "/activity"
|
||||
},
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -195,7 +190,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
"actor" => actor,
|
||||
"object" => object
|
||||
},
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- update_object(object["id"], data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -210,7 +206,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# with nil <- get_existing_like(url, object),
|
||||
# like_data <- make_like_data(user, object, activity_id),
|
||||
# {:ok, activity, object} <- insert(like_data, local),
|
||||
# {:ok, activity} <- create_activity(like_data, local),
|
||||
# {:ok, object} <- insert_full_object(data),
|
||||
# {:ok, object} <- add_like_to_object(activity, object),
|
||||
# :ok <- maybe_federate(activity) do
|
||||
# {:ok, activity, object}
|
||||
@ -228,7 +225,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
# ) do
|
||||
# with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
|
||||
# unlike_data <- make_unlike_data(actor, like_activity, activity_id),
|
||||
# {:ok, unlike_activity, _object} <- insert(unlike_data, local),
|
||||
# {:ok, unlike_activity} <- create_activity(unlike_data, local),
|
||||
# {:ok, _object} <- insert_full_object(data),
|
||||
# {:ok, _activity} <- Repo.delete(like_activity),
|
||||
# {:ok, object} <- remove_like_from_object(like_activity, object),
|
||||
# :ok <- maybe_federate(unlike_activity) do
|
||||
@ -247,7 +245,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
) do
|
||||
with true <- is_public?(object),
|
||||
announce_data <- make_announce_data(actor, object, activity_id, public),
|
||||
{:ok, activity, object} <- insert(announce_data, local),
|
||||
{:ok, activity} <- create_activity(announce_data, local),
|
||||
{:ok, object} <- insert_full_object(announce_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
@ -265,7 +264,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
) do
|
||||
with announce_activity <- make_announce_data(actor, object, cancelled_activity_id),
|
||||
unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
|
||||
{:ok, unannounce_activity, _object} <- insert(unannounce_data, local),
|
||||
{:ok, unannounce_activity} <- create_activity(unannounce_data, local),
|
||||
{:ok, object} <- insert_full_object(unannounce_data),
|
||||
:ok <- maybe_federate(unannounce_activity) do
|
||||
{:ok, unannounce_activity, object}
|
||||
else
|
||||
@ -282,7 +282,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
activity_follow_id <-
|
||||
activity_id || follow_url,
|
||||
data <- make_follow_data(followed, follower, activity_follow_id),
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
@ -304,12 +305,14 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
follower,
|
||||
"#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity"
|
||||
),
|
||||
{:ok, follow_activity, _object} <- insert(data, local),
|
||||
{:ok, follow_activity} <- create_activity(data, local),
|
||||
{:ok, _object} <- insert_full_object(data),
|
||||
activity_unfollow_id <-
|
||||
activity_id || "#{MobilizonWeb.Endpoint.url()}/unfollow/#{follow_id}/activity",
|
||||
unfollow_data <-
|
||||
make_unfollow_data(follower, followed, follow_activity, activity_unfollow_id),
|
||||
{:ok, activity, object} <- insert(unfollow_data, local),
|
||||
{:ok, activity} <- create_activity(unfollow_data, local),
|
||||
{:ok, object} <- insert_full_object(unfollow_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
@ -331,7 +334,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with {:ok, _} <- Events.delete_event(event),
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -347,7 +351,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with {:ok, _} <- Events.delete_comment(comment),
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -363,7 +368,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
}
|
||||
|
||||
with {:ok, _} <- Actors.delete_actor(actor),
|
||||
{:ok, activity, object} <- insert(data, local),
|
||||
{:ok, activity} <- create_activity(data, local),
|
||||
{:ok, object} <- insert_full_object(data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, object}
|
||||
end
|
||||
@ -384,9 +390,10 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
end
|
||||
|
||||
with flag_data <- make_flag_data(params, additional),
|
||||
{:ok, activity, report} <- insert(flag_data, local),
|
||||
{:ok, activity} <- create_activity(flag_data, local),
|
||||
{:ok, object} <- insert_full_object(flag_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, report}
|
||||
{:ok, activity, object}
|
||||
end
|
||||
end
|
||||
|
||||
@ -403,7 +410,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
join_data <- Convertible.model_to_as(participant),
|
||||
join_data <- Map.put(join_data, "to", [event.organizer_actor.url]),
|
||||
join_data <- Map.put(join_data, "cc", []),
|
||||
{:ok, activity, _} <- insert(join_data, local),
|
||||
{:ok, activity} <- create_activity(join_data, local),
|
||||
{:ok, _object} <- insert_full_object(join_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
if role === :participant do
|
||||
accept(
|
||||
@ -443,7 +451,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||
"to" => [event.organizer_actor.url],
|
||||
"cc" => []
|
||||
},
|
||||
{:ok, activity, _} <- insert(leave_data, local),
|
||||
{:ok, activity} <- create_activity(leave_data, local),
|
||||
{:ok, _object} <- insert_full_object(leave_data),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, participant}
|
||||
end
|
||||
|
@ -15,12 +15,30 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Actor do
|
||||
@impl Converter
|
||||
@spec as_to_model_data(map()) :: map()
|
||||
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"]),
|
||||
"preferred_username" => object["preferred_username"],
|
||||
"preferred_username" => object["preferredUsername"],
|
||||
"summary" => object["summary"],
|
||||
"url" => object["url"],
|
||||
"name" => object["name"]
|
||||
"name" => object["name"],
|
||||
"avatar" => avatar,
|
||||
"banner" => banner,
|
||||
"keys" => object["publicKey"]["publicKeyPem"],
|
||||
"manually_approves_followers" => object["manuallyApprovesFollowers"]
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -57,6 +57,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
|
||||
"organizer_actor_id" => actor_id,
|
||||
"picture_id" => picture_id,
|
||||
"begins_on" => object["startTime"],
|
||||
"ends_on" => object["endTime"],
|
||||
"category" => object["category"],
|
||||
"url" => object["id"],
|
||||
"uuid" => object["uuid"],
|
||||
@ -173,7 +174,8 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
|
||||
"startTime" => event.begins_on |> date_to_string(),
|
||||
"endTime" => event.ends_on |> date_to_string(),
|
||||
"tag" => event.tags |> build_tags(),
|
||||
"id" => event.url
|
||||
"id" => event.url,
|
||||
"url" => event.url
|
||||
}
|
||||
|
||||
res =
|
||||
|
@ -295,19 +295,15 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} =
|
||||
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
|
||||
{:ok, %Actor{url: url}} ->
|
||||
{:ok, new_actor_data} = ActivityPub.actor_data_from_actor_object(object)
|
||||
|
||||
Actors.insert_or_update_actor(new_actor_data)
|
||||
|
||||
{:ok, %Actor{url: actor_url}} ->
|
||||
ActivityPub.update(%{
|
||||
local: false,
|
||||
to: data["to"] || [],
|
||||
cc: data["cc"] || [],
|
||||
object: object,
|
||||
actor: url
|
||||
actor: actor_url
|
||||
})
|
||||
|
||||
e ->
|
||||
@ -316,6 +312,28 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||
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(
|
||||
%{
|
||||
"type" => "Undo",
|
||||
|
@ -29,6 +29,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
alias MobilizonWeb.Endpoint
|
||||
|
||||
@actor_types ["Group", "Person", "Application"]
|
||||
|
||||
# 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.
|
||||
def get_url(%{"id" => id}), do: id
|
||||
@ -119,7 +121,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
@doc """
|
||||
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
|
||||
with {:ok, object_data} <-
|
||||
Converters.Event.as_to_model_data(object_data),
|
||||
@ -128,7 +130,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
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
|
||||
with object_data <-
|
||||
Map.put(object_data, "preferred_username", object_data["preferredUsername"]),
|
||||
@ -140,7 +142,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
@doc """
|
||||
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
|
||||
with data <- Converters.Comment.as_to_model_data(object_data),
|
||||
{: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}
|
||||
|
||||
@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
|
||||
|
||||
# @doc """
|
||||
@ -264,7 +299,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
String.t(),
|
||||
map(),
|
||||
list(),
|
||||
map()
|
||||
map(),
|
||||
String.t()
|
||||
) :: map()
|
||||
def make_event_data(
|
||||
actor,
|
||||
@ -273,10 +309,12 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
content_html,
|
||||
picture \\ nil,
|
||||
tags \\ [],
|
||||
metadata \\ %{}
|
||||
metadata \\ %{},
|
||||
uuid \\ nil,
|
||||
url \\ nil
|
||||
) do
|
||||
Logger.debug("Making event data")
|
||||
uuid = Ecto.UUID.generate()
|
||||
uuid = uuid || Ecto.UUID.generate()
|
||||
|
||||
res = %{
|
||||
"type" => "Event",
|
||||
@ -285,9 +323,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
"content" => content_html,
|
||||
"name" => title,
|
||||
"startTime" => metadata.begins_on,
|
||||
"endTime" => metadata.ends_on,
|
||||
"category" => metadata.category,
|
||||
"actor" => actor,
|
||||
"id" => Routes.page_url(Endpoint, :event, uuid),
|
||||
"id" => url || Routes.page_url(Endpoint, :event, uuid),
|
||||
"uuid" => uuid,
|
||||
"tag" =>
|
||||
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
|
||||
@ -505,7 +544,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||
activity_id,
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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 {
|
||||
query: RootQueryType
|
||||
@ -440,7 +440,7 @@ enum EventVisibility {
|
||||
"""Visible only to people members of the group or followers of the person"""
|
||||
PRIVATE
|
||||
|
||||
"""Publically listed and federated. Can be shared."""
|
||||
"""Publicly listed and federated. Can be shared."""
|
||||
PUBLIC
|
||||
|
||||
"""Visible only to people with the link - or invited"""
|
||||
@ -823,7 +823,7 @@ type RootMutationType {
|
||||
"""Create an event"""
|
||||
createEvent(
|
||||
beginsOn: DateTime!
|
||||
category: String
|
||||
category: String = "meeting"
|
||||
description: String!
|
||||
endsOn: DateTime
|
||||
onlineAddress: String
|
||||
@ -852,11 +852,6 @@ type RootMutationType {
|
||||
|
||||
"""Create a group"""
|
||||
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
|
||||
"""
|
||||
@ -867,14 +862,17 @@ type RootMutationType {
|
||||
"""
|
||||
banner: PictureInput
|
||||
|
||||
"""The summary for the group"""
|
||||
description: String = ""
|
||||
"""The identity that creates the group"""
|
||||
creatorActorId: Int!
|
||||
|
||||
"""The displayed name for the group"""
|
||||
name: String
|
||||
|
||||
"""The name for the group"""
|
||||
preferredUsername: String!
|
||||
|
||||
"""The summary for the group"""
|
||||
summary: String = ""
|
||||
): Group
|
||||
|
||||
"""Create a new person for user"""
|
||||
@ -969,6 +967,34 @@ type RootMutationType {
|
||||
"""Send a link through email to reset user password"""
|
||||
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"""
|
||||
updatePerson(
|
||||
"""
|
||||
|
26
test/fixtures/mastodon-update.json
vendored
26
test/fixtures/mastodon-update.json
vendored
@ -1,24 +1,24 @@
|
||||
{
|
||||
"type": "Update",
|
||||
"object": {
|
||||
"url": "http://mastodon.example.org/@gargron",
|
||||
"url": "https://framapiaf.org/@framasoft",
|
||||
"type": "Person",
|
||||
"summary": "<p>Some bio</p>",
|
||||
"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",
|
||||
"owner": "http://mastodon.example.org/users/gargron",
|
||||
"id": "http://mastodon.example.org/users/gargron#main-key"
|
||||
"owner": "https://framapiaf.org/users/framasoft",
|
||||
"id": "https://framapiaf.org/users/framasoft#main-key"
|
||||
},
|
||||
"preferredUsername": "gargron",
|
||||
"outbox": "http://mastodon.example.org/users/gargron/outbox",
|
||||
"name": "gargle",
|
||||
"preferredUsername": "framasoft",
|
||||
"outbox": "https://framapiaf.org/users/framasoft/outbox",
|
||||
"name": "nextsoft",
|
||||
"manuallyApprovesFollowers": false,
|
||||
"inbox": "http://mastodon.example.org/users/gargron/inbox",
|
||||
"id": "http://mastodon.example.org/users/gargron",
|
||||
"following": "http://mastodon.example.org/users/gargron/following",
|
||||
"followers": "http://mastodon.example.org/users/gargron/followers",
|
||||
"inbox": "https://framapiaf.org/users/framasoft/inbox",
|
||||
"id": "https://framapiaf.org/users/framasoft",
|
||||
"following": "https://framapiaf.org/users/framasoft/following",
|
||||
"followers": "https://framapiaf.org/users/framasoft/followers",
|
||||
"endpoints": {
|
||||
"sharedInbox": "http://mastodon.example.org/inbox"
|
||||
"sharedInbox": "https://framapiaf.org/inbox"
|
||||
},
|
||||
"icon":{
|
||||
"type":"Image",
|
||||
@ -31,8 +31,8 @@
|
||||
"url":"https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png"
|
||||
}
|
||||
},
|
||||
"id": "http://mastodon.example.org/users/gargron#updates/1519563538",
|
||||
"actor": "http://mastodon.example.org/users/gargron",
|
||||
"id": "https://framapiaf.org/users/gargron#updates/1519563538",
|
||||
"actor": "https://framapiaf.org/users/framasoft",
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
|
@ -270,10 +270,8 @@ defmodule Mobilizon.ActorsTest do
|
||||
assert %Actor{} = actor
|
||||
assert actor.summary == "some updated description"
|
||||
assert actor.name == "some updated name"
|
||||
assert actor.domain == "some updated domain"
|
||||
assert actor.keys == "some updated keys"
|
||||
refute actor.suspended
|
||||
assert actor.preferred_username == "some updated username"
|
||||
end
|
||||
|
||||
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.summary == "some updated description"
|
||||
assert actor.name == "some updated name"
|
||||
assert actor.domain == "some updated domain"
|
||||
assert actor.keys == "some updated keys"
|
||||
refute actor.suspended
|
||||
assert actor.preferred_username == "some updated username"
|
||||
|
||||
refute File.exists?(
|
||||
Mobilizon.CommonConfig.get!([MobilizonWeb.Uploaders.Local, :uploads]) <>
|
||||
|
@ -9,6 +9,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||
@ -137,11 +138,14 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||
end
|
||||
|
||||
describe "update" do
|
||||
@updated_actor_summary "This is an updated actor"
|
||||
|
||||
test "it creates an update activity with the new actor data" do
|
||||
actor = insert(: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(%{
|
||||
actor: actor_data["url"],
|
||||
to: [actor.url <> "/followers"],
|
||||
@ -153,6 +157,42 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||
assert update.data["to"] == [actor.url <> "/followers"]
|
||||
assert update.data["object"]["id"] == actor_data["id"]
|
||||
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
|
||||
|
@ -17,7 +17,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.ActorTest do
|
||||
actor =
|
||||
ActorConverter.as_to_model_data(%{
|
||||
"type" => "Person",
|
||||
"preferred_username" => "test_account"
|
||||
"preferredUsername" => "test_account"
|
||||
})
|
||||
|
||||
assert actor["type"] == :Person
|
||||
|
@ -330,7 +330,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||
assert data["object"] == comment.url
|
||||
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!()
|
||||
|
||||
{: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, %Actor{} = actor} = Actors.get_actor_by_url(data["actor"])
|
||||
assert actor.name == "gargle"
|
||||
assert actor.name == "nextsoft"
|
||||
|
||||
assert actor.summary == "<p>Some bio</p>"
|
||||
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
|
||||
# data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
|
@ -58,6 +58,40 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||
json_response(res, 200)["errors"]
|
||||
end
|
||||
|
||||
test "create_event/3 should check the organizer_actor_id is owned by the user", %{
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
another_actor = insert(:actor)
|
||||
|
||||
begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
createEvent(
|
||||
title: "come to my event",
|
||||
description: "it will be fine",
|
||||
begins_on: "#{begins_on}",
|
||||
organizer_actor_id: "#{another_actor.id}",
|
||||
category: "birthday"
|
||||
) {
|
||||
title,
|
||||
uuid
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["createEvent"] == nil
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
"Organizer actor id is not owned by the user"
|
||||
end
|
||||
|
||||
test "create_event/3 creates an event", %{conn: conn, actor: actor, user: user} do
|
||||
mutation = """
|
||||
mutation {
|
||||
@ -384,6 +418,166 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||
assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"]
|
||||
end
|
||||
|
||||
test "update_event/3 should check the event exists", %{conn: conn, actor: _actor, user: user} do
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: 45,
|
||||
title: "my event updated"
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "Event not found"
|
||||
end
|
||||
|
||||
test "update_event/3 should check the user is an administrator", %{
|
||||
conn: conn,
|
||||
actor: _actor,
|
||||
user: user
|
||||
} do
|
||||
event = insert(:event)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
title: "my event updated",
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "User doesn't own actor"
|
||||
end
|
||||
|
||||
test "update_event/3 updates an event", %{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",
|
||||
tags: ["tag1_updated", "tag2_updated"]
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
url,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
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"]["tags"] == [
|
||||
%{"slug" => "tag1-updated", "title" => "tag1_updated"},
|
||||
%{"slug" => "tag2-updated", "title" => "tag2_updated"}
|
||||
]
|
||||
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
|
||||
event = insert(:event)
|
||||
|
||||
|
@ -67,8 +67,8 @@ defmodule Mobilizon.Factory do
|
||||
|
||||
def tag_factory do
|
||||
%Mobilizon.Events.Tag{
|
||||
title: "MyTag",
|
||||
slug: sequence("MyTag")
|
||||
title: sequence("MyTag"),
|
||||
slug: sequence("my-tag")
|
||||
}
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user