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:
Thomas Citharel 2019-09-05 14:09:09 +02:00
commit 060f6c8775
27 changed files with 856 additions and 173 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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'];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
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),
def create_event(%{organizer_actor: organizer_actor} = args) do
with %{
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(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

View File

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

View File

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

View File

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

View File

@ -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{
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}
@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)
}}
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

View File

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

View File

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

View File

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

View File

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