Merge branch 'allow-group-event-edition' into 'master'
Allow events & posts to be edited by group moderators Closes #807 et #517 See merge request framasoft/mobilizon!1003
This commit is contained in:
commit
a5a0c8c18a
|
@ -77,12 +77,12 @@
|
|||
</div>
|
||||
<div class="media-content" v-if="actor.name">
|
||||
<p class="is-4">{{ actor.name }}</p>
|
||||
<p class="is-6 has-text-grey">
|
||||
{{ `@${actor.preferredUsername}` }}
|
||||
<p class="is-6 has-text-grey-dark">
|
||||
{{ `@${usernameWithDomain(actor)}` }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="media-content" v-else>
|
||||
{{ `@${actor.preferredUsername}` }}
|
||||
{{ `@${usernameWithDomain(actor)}` }}
|
||||
</div>
|
||||
</div>
|
||||
</b-checkbox>
|
||||
|
@ -167,6 +167,8 @@ export default class OrganizerPickerWrapper extends Vue {
|
|||
|
||||
isComponentModalActive = false;
|
||||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
|
||||
@Prop({ type: Array, required: false, default: () => [] })
|
||||
contacts!: IActor[];
|
||||
members: Paginate<IMember> = { elements: [], total: 0 };
|
||||
|
|
|
@ -192,9 +192,6 @@ export default class ParticipationSection extends Vue {
|
|||
if (this.event.draft || this.event.status === EventStatus.CANCELLED)
|
||||
return false;
|
||||
|
||||
// Organizer can't participate
|
||||
if (this.actorIsOrganizer) return false;
|
||||
|
||||
// If capacity is OK
|
||||
if (this.eventCapacityOK) return true;
|
||||
|
||||
|
|
|
@ -21,19 +21,52 @@ function formatTimeString(value: string): string {
|
|||
});
|
||||
}
|
||||
|
||||
function formatDateTimeString(value: string, showTime = true): string {
|
||||
const options: DateTimeFormatOptions = {
|
||||
// TODO: These can be removed in favor of dateStyle/timeStyle when those two have sufficient support
|
||||
// https://caniuse.com/mdn-javascript_builtins_intl_datetimeformat_datetimeformat_datestyle
|
||||
const LONG_DATE_FORMAT_OPTIONS: DateTimeFormatOptions = {
|
||||
weekday: undefined,
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: undefined,
|
||||
minute: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const LONG_TIME_FORMAT_OPTIONS: DateTimeFormatOptions = {
|
||||
weekday: "long",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
};
|
||||
|
||||
const SHORT_DATE_FORMAT_OPTIONS: DateTimeFormatOptions = {
|
||||
weekday: undefined,
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: undefined,
|
||||
minute: undefined,
|
||||
};
|
||||
|
||||
const SHORT_TIME_FORMAT_OPTIONS: DateTimeFormatOptions = {
|
||||
weekday: "short",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
};
|
||||
|
||||
function formatDateTimeString(
|
||||
value: string,
|
||||
showTime = true,
|
||||
dateFormat = "long"
|
||||
): string {
|
||||
const isLongFormat = dateFormat === "long";
|
||||
let options = isLongFormat
|
||||
? LONG_DATE_FORMAT_OPTIONS
|
||||
: SHORT_DATE_FORMAT_OPTIONS;
|
||||
if (showTime) {
|
||||
options.weekday = "long";
|
||||
options.hour = "numeric";
|
||||
options.minute = "numeric";
|
||||
options = {
|
||||
...options,
|
||||
...(isLongFormat ? LONG_TIME_FORMAT_OPTIONS : SHORT_TIME_FORMAT_OPTIONS),
|
||||
};
|
||||
}
|
||||
const format = new Intl.DateTimeFormat(locale(), options);
|
||||
return format.format(parseDateTime(value));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<section>
|
||||
<div class="container" v-if="isCurrentActorOrganizer">
|
||||
<div class="container" v-if="hasCurrentActorPermissionsToEdit">
|
||||
<h1 class="title" v-if="isUpdate === true">
|
||||
{{ $t("Update event {name}", { name: event.title }) }}
|
||||
</h1>
|
||||
|
@ -269,6 +269,11 @@
|
|||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
<div class="container section" v-else>
|
||||
<b-message type="is-danger">
|
||||
{{ $t("Only group moderators can create, edit and delete events.") }}
|
||||
</b-message>
|
||||
</div>
|
||||
<b-modal v-model="dateSettingsIsOpen" has-modal-card trap-focus>
|
||||
<form action>
|
||||
<div class="modal-card" style="width: auto">
|
||||
|
@ -305,7 +310,7 @@
|
|||
aria-label="main navigation"
|
||||
class="navbar save__navbar"
|
||||
:class="{ 'is-fixed-bottom': showFixedNavbar }"
|
||||
v-if="isCurrentActorOrganizer"
|
||||
v-if="hasCurrentActorPermissionsToEdit"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="navbar-menu">
|
||||
|
@ -457,6 +462,7 @@ import {
|
|||
EventJoinOptions,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
MemberRole,
|
||||
ParticipantRole,
|
||||
} from "@/types/enums";
|
||||
import OrganizerPickerWrapper from "../../components/Event/OrganizerPickerWrapper.vue";
|
||||
|
@ -472,8 +478,15 @@ import {
|
|||
IDENTITIES,
|
||||
LOGGED_USER_DRAFTS,
|
||||
LOGGED_USER_PARTICIPATIONS,
|
||||
PERSON_MEMBERSHIP_GROUP,
|
||||
} from "../../graphql/actor";
|
||||
import { displayNameAndUsername, IActor, IGroup } from "../../types/actor";
|
||||
import {
|
||||
displayNameAndUsername,
|
||||
IActor,
|
||||
IGroup,
|
||||
IPerson,
|
||||
usernameWithDomain,
|
||||
} from "../../types/actor";
|
||||
import { TAGS } from "../../graphql/tags";
|
||||
import { ITag } from "../../types/tag.model";
|
||||
import {
|
||||
|
@ -519,6 +532,22 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
|
|||
return !this.eventId;
|
||||
},
|
||||
},
|
||||
person: {
|
||||
query: PERSON_MEMBERSHIP_GROUP,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
id: this.currentActor.id,
|
||||
group: usernameWithDomain(this.event?.attributedTo),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return (
|
||||
!this.event?.attributedTo ||
|
||||
!this.event?.attributedTo?.preferredUsername
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
|
@ -545,6 +574,8 @@ export default class EditEvent extends Vue {
|
|||
|
||||
identities: IActor[] = [];
|
||||
|
||||
person!: IPerson;
|
||||
|
||||
config!: IConfig;
|
||||
|
||||
pictureFile: File | null = null;
|
||||
|
@ -553,8 +584,6 @@ export default class EditEvent extends Vue {
|
|||
|
||||
EventVisibility = EventVisibility;
|
||||
|
||||
needsApproval = false;
|
||||
|
||||
canPromote = true;
|
||||
|
||||
limitedPlaces = false;
|
||||
|
@ -749,13 +778,23 @@ export default class EditEvent extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
get isCurrentActorOrganizer(): boolean {
|
||||
get hasCurrentActorPermissionsToEdit(): boolean {
|
||||
return !(
|
||||
this.eventId &&
|
||||
this.event.organizerActor?.id !== undefined &&
|
||||
!this.identities
|
||||
.map(({ id }) => id)
|
||||
.includes(this.event.organizerActor?.id)
|
||||
.includes(this.event.organizerActor?.id) &&
|
||||
!this.hasGroupPrivileges
|
||||
);
|
||||
}
|
||||
|
||||
get hasGroupPrivileges(): boolean {
|
||||
return (
|
||||
this.person?.memberships?.total > 0 &&
|
||||
[MemberRole.MODERATOR, MemberRole.ADMINISTRATOR].includes(
|
||||
this.person?.memberships?.elements[0].role
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -908,9 +947,12 @@ export default class EditEvent extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
@Watch("needsApproval")
|
||||
updateEventJoinOptions(needsApproval: boolean): void {
|
||||
if (needsApproval === true) {
|
||||
get needsApproval(): boolean {
|
||||
return this.event?.joinOptions == EventJoinOptions.RESTRICTED;
|
||||
}
|
||||
|
||||
set needsApproval(value: boolean) {
|
||||
if (value === true) {
|
||||
this.event.joinOptions = EventJoinOptions.RESTRICTED;
|
||||
} else {
|
||||
this.event.joinOptions = EventJoinOptions.FREE;
|
||||
|
|
|
@ -120,7 +120,7 @@
|
|||
<b-icon icon="link" />
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="!event.local && organizer">
|
||||
<template v-if="!event.local && organizer.domain">
|
||||
<a :href="event.url">
|
||||
<tag>{{ organizer.domain }}</tag>
|
||||
</a>
|
||||
|
@ -128,7 +128,7 @@
|
|||
<p>
|
||||
<router-link
|
||||
class="participations-link"
|
||||
v-if="actorIsOrganizer && event.draft === false"
|
||||
v-if="canManageEvent && event.draft === false"
|
||||
:to="{
|
||||
name: RouteName.PARTICIPATIONS,
|
||||
params: { eventId: event.uuid },
|
||||
|
@ -214,7 +214,7 @@
|
|||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
has-link
|
||||
v-if="actorIsOrganizer || event.draft"
|
||||
v-if="canManageEvent || event.draft"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
|
@ -229,7 +229,7 @@
|
|||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
has-link
|
||||
v-if="actorIsOrganizer || event.draft"
|
||||
v-if="canManageEvent || event.draft"
|
||||
>
|
||||
<router-link
|
||||
:to="{
|
||||
|
@ -243,7 +243,7 @@
|
|||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
v-if="actorIsOrganizer || event.draft"
|
||||
v-if="canManageEvent || event.draft"
|
||||
@click="openDeleteEventModalWrapper"
|
||||
>
|
||||
{{ $t("Delete") }}
|
||||
|
@ -253,7 +253,7 @@
|
|||
<hr
|
||||
class="dropdown-divider"
|
||||
aria-role="menuitem"
|
||||
v-if="actorIsOrganizer || event.draft"
|
||||
v-if="canManageEvent || event.draft"
|
||||
/>
|
||||
<b-dropdown-item
|
||||
aria-role="listitem"
|
||||
|
@ -623,6 +623,7 @@ import {
|
|||
EventJoinOptions,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
MemberRole,
|
||||
ParticipantRole,
|
||||
RoutingTransportationType,
|
||||
RoutingType,
|
||||
|
@ -633,7 +634,10 @@ import {
|
|||
FETCH_EVENT,
|
||||
JOIN_EVENT,
|
||||
} from "../../graphql/event";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
import {
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
PERSON_MEMBERSHIP_GROUP,
|
||||
} from "../../graphql/actor";
|
||||
import { EventModel, IEvent } from "../../types/event.model";
|
||||
import { IActor, IPerson, Person, usernameWithDomain } from "../../types/actor";
|
||||
import { GRAPHQL_API_ENDPOINT } from "../../api/_entrypoint";
|
||||
|
@ -738,6 +742,22 @@ import { ApolloCache, FetchResult } from "@apollo/client/core";
|
|||
);
|
||||
},
|
||||
},
|
||||
person: {
|
||||
query: PERSON_MEMBERSHIP_GROUP,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
id: this.currentActor.id,
|
||||
group: usernameWithDomain(this.event?.attributedTo),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return (
|
||||
!this.event?.attributedTo ||
|
||||
!this.event?.attributedTo?.preferredUsername
|
||||
);
|
||||
},
|
||||
},
|
||||
config: CONFIG,
|
||||
},
|
||||
metaInfo() {
|
||||
|
@ -764,6 +784,8 @@ export default class Event extends EventMixin {
|
|||
|
||||
config!: IConfig;
|
||||
|
||||
person!: IPerson;
|
||||
|
||||
participations: IParticipant[] = [];
|
||||
|
||||
oldParticipationRole!: string;
|
||||
|
@ -1211,6 +1233,19 @@ export default class Event extends EventMixin {
|
|||
);
|
||||
}
|
||||
|
||||
get hasGroupPrivileges(): boolean {
|
||||
return (
|
||||
this.person?.memberships?.total > 0 &&
|
||||
[MemberRole.MODERATOR, MemberRole.ADMINISTRATOR].includes(
|
||||
this.person?.memberships?.elements[0].role
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get canManageEvent(): boolean {
|
||||
return this.actorIsOrganizer || this.hasGroupPrivileges;
|
||||
}
|
||||
|
||||
get endDate(): Date {
|
||||
return this.event.endsOn !== null && this.event.endsOn > this.event.beginsOn
|
||||
? this.event.endsOn
|
||||
|
|
|
@ -207,7 +207,7 @@ import { FETCH_GROUP } from "@/graphql/group";
|
|||
variables() {
|
||||
return {
|
||||
id: this.currentActor.id,
|
||||
group: this.actualGroup.preferredUsername,
|
||||
group: usernameWithDomain(this.actualGroup),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
|
|
|
@ -24,6 +24,29 @@
|
|||
<b-icon icon="clock" size="is-small" />
|
||||
{{ post.publishAt | formatDateTimeString }}
|
||||
</span>
|
||||
<span
|
||||
class="published has-text-grey-dark"
|
||||
:title="
|
||||
$options.filters.formatDateTimeString(
|
||||
post.updatedAt,
|
||||
true,
|
||||
'short'
|
||||
)
|
||||
"
|
||||
v-else
|
||||
>
|
||||
<b-icon icon="clock" size="is-small" />
|
||||
{{
|
||||
$t("Edited {relative_time} ago", {
|
||||
relative_time: formatDistanceToNowStrict(
|
||||
new Date(post.updatedAt),
|
||||
{
|
||||
locale: $dateFnsLocale,
|
||||
}
|
||||
),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="post.visibility === PostVisibility.PRIVATE"
|
||||
class="has-text-grey-dark"
|
||||
|
@ -75,7 +98,11 @@ import { mixins } from "vue-class-component";
|
|||
import GroupMixin from "@/mixins/group";
|
||||
import { PostVisibility } from "@/types/enums";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
|
||||
import {
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
PERSON_MEMBERSHIPS,
|
||||
PERSON_MEMBERSHIP_GROUP,
|
||||
} from "../../graphql/actor";
|
||||
import { FETCH_POST } from "../../graphql/post";
|
||||
import { IPost } from "../../types/post.model";
|
||||
import { usernameWithDomain } from "../../types/actor";
|
||||
|
@ -83,6 +110,7 @@ import RouteName from "../../router/name";
|
|||
import Tag from "../../components/Tag.vue";
|
||||
import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue";
|
||||
import ActorInline from "../../components/Account/ActorInline.vue";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
|
@ -115,6 +143,23 @@ import ActorInline from "../../components/Account/ActorInline.vue";
|
|||
this.handleErrors(graphQLErrors);
|
||||
},
|
||||
},
|
||||
person: {
|
||||
query: PERSON_MEMBERSHIP_GROUP,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
id: this.currentActor.id,
|
||||
group: usernameWithDomain(this.post.attributedTo),
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return (
|
||||
!this.currentActor ||
|
||||
!this.currentActor.id ||
|
||||
!this.post?.attributedTo
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Tag,
|
||||
|
@ -144,6 +189,8 @@ export default class Post extends mixins(GroupMixin) {
|
|||
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
|
||||
formatDistanceToNowStrict = formatDistanceToNowStrict;
|
||||
|
||||
PostVisibility = PostVisibility;
|
||||
|
||||
handleErrors(errors: any[]): void {
|
||||
|
|
|
@ -426,7 +426,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
"id" => "#{Endpoint.url()}/leave/event/#{participant.id}"
|
||||
},
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(participant),
|
||||
Audience.get_audience(participant),
|
||||
{:ok, activity} <- create_activity(Map.merge(leave_data, audience), local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity, participant}
|
||||
|
@ -803,15 +803,15 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
Scheduler.trigger_notifications_for_participant(participant),
|
||||
participant_as_data <- Convertible.model_to_as(participant),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(participant),
|
||||
update_data <-
|
||||
Audience.get_audience(participant),
|
||||
accept_join_data <-
|
||||
make_accept_join_data(
|
||||
participant_as_data,
|
||||
Map.merge(Map.merge(audience, additional), %{
|
||||
"id" => "#{Endpoint.url()}/accept/join/#{participant.id}"
|
||||
})
|
||||
) do
|
||||
{:ok, participant, update_data}
|
||||
{:ok, participant, accept_join_data}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an update activity")
|
||||
|
@ -837,15 +837,15 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
),
|
||||
member_as_data <- Convertible.model_to_as(member),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(member),
|
||||
update_data <-
|
||||
Audience.get_audience(member),
|
||||
accept_join_data <-
|
||||
make_accept_join_data(
|
||||
member_as_data,
|
||||
Map.merge(Map.merge(audience, additional), %{
|
||||
"id" => "#{Endpoint.url()}/accept/join/#{member.id}"
|
||||
})
|
||||
) do
|
||||
{:ok, member, update_data}
|
||||
{:ok, member, accept_join_data}
|
||||
else
|
||||
err ->
|
||||
Logger.error("Something went wrong while creating an update activity")
|
||||
|
@ -899,7 +899,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
participant_as_data <- Convertible.model_to_as(participant),
|
||||
audience <-
|
||||
participant
|
||||
|> Audience.calculate_to_and_cc_from_mentions()
|
||||
|> Audience.get_audience()
|
||||
|> Map.merge(additional),
|
||||
reject_data <- %{
|
||||
"type" => "Reject",
|
||||
|
@ -925,7 +925,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
with {:ok, %Follower{} = follower} <- Actors.delete_follower(follower),
|
||||
follower_as_data <- Convertible.model_to_as(follower),
|
||||
audience <-
|
||||
follower.actor |> Audience.calculate_to_and_cc_from_mentions() |> Map.merge(additional),
|
||||
follower.actor |> Audience.get_audience() |> Map.merge(additional),
|
||||
reject_data <- %{
|
||||
"to" => [follower.actor.url],
|
||||
"type" => "Reject",
|
||||
|
|
|
@ -55,6 +55,10 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
|
|||
{:error, "Can't make a local actor from URL"}
|
||||
else
|
||||
case Fetcher.fetch_and_prepare_actor_from_url(url) do
|
||||
# Just in case
|
||||
{:ok, {:error, _e}} ->
|
||||
raise ArgumentError, message: "Failed to make actor from url #{url}"
|
||||
|
||||
{:ok, data} ->
|
||||
Actors.upsert_actor(data, preload)
|
||||
|
||||
|
@ -67,7 +71,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
|
|||
{:error, :http_error}
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Failed to make actor from url")
|
||||
Logger.warn("Failed to make actor from url #{url}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,18 +3,99 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
Tools for calculating content audience
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.{Actors, Events, Share}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Share
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
@type audience :: %{required(String.t()) => list(String.t())}
|
||||
|
||||
@doc """
|
||||
Get audience for an entity
|
||||
"""
|
||||
@spec get_audience(Entity.t()) :: audience()
|
||||
def get_audience(%Event{} = event) do
|
||||
extract_actors_from_event(event)
|
||||
end
|
||||
|
||||
def get_audience(%Post{draft: true} = post) do
|
||||
get_audience(%Post{post | visibility: :private, draft: false})
|
||||
end
|
||||
|
||||
def get_audience(%Post{attributed_to: %Actor{} = group, visibility: visibility}) do
|
||||
{to, cc} = get_to_and_cc(group, [], visibility)
|
||||
%{"to" => to, "cc" => cc}
|
||||
end
|
||||
|
||||
def get_audience(%Discussion{actor: actor}) do
|
||||
%{"to" => maybe_add_group_members([], actor), "cc" => []}
|
||||
end
|
||||
|
||||
def get_audience(%Comment{discussion: %Discussion{} = discussion}) do
|
||||
get_audience(discussion)
|
||||
end
|
||||
|
||||
def get_audience(%Comment{
|
||||
mentions: mentions,
|
||||
actor: %Actor{} = actor,
|
||||
visibility: visibility,
|
||||
in_reply_to_comment: in_reply_to_comment,
|
||||
event: event,
|
||||
origin_comment: origin_comment,
|
||||
url: url
|
||||
}) do
|
||||
with {to, cc} <-
|
||||
extract_actors_from_mentions(mentions, actor, visibility),
|
||||
{to, cc} <- {Enum.uniq(to ++ add_in_reply_to(in_reply_to_comment)), cc},
|
||||
{to, cc} <- {Enum.uniq(to ++ add_event_author(event)), cc},
|
||||
{to, cc} <-
|
||||
{to,
|
||||
Enum.uniq(
|
||||
cc ++
|
||||
add_comments_authors([origin_comment]) ++
|
||||
add_shares_actors_followers(url)
|
||||
)} do
|
||||
%{"to" => to, "cc" => cc}
|
||||
end
|
||||
end
|
||||
|
||||
def get_audience(%Participant{} = participant) do
|
||||
%Event{} = event = Events.get_event_with_preload!(participant.event_id)
|
||||
%Actor{} = organizer = group_or_organizer_event(event)
|
||||
|
||||
cc =
|
||||
event.id
|
||||
|> Mobilizon.Events.list_actors_participants_for_event()
|
||||
|> Enum.map(& &1.url)
|
||||
|> Enum.filter(&(&1 != participant.actor.url))
|
||||
|> maybe_add_group_members(organizer)
|
||||
|> maybe_add_followers(organizer)
|
||||
|
||||
%{
|
||||
"to" => [participant.actor.url, organizer.url],
|
||||
"cc" => cc
|
||||
}
|
||||
end
|
||||
|
||||
def get_audience(%Member{} = member) do
|
||||
%{"to" => [member.parent.url, member.parent.members_url], "cc" => []}
|
||||
end
|
||||
|
||||
def get_audience(%Actor{} = actor) do
|
||||
%{
|
||||
"to" => [@ap_public],
|
||||
"cc" =>
|
||||
maybe_add_group_members([actor.followers_url], actor) ++
|
||||
add_actors_that_had_our_content(actor.id)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines the full audience based on mentions for an audience
|
||||
|
||||
|
@ -39,6 +120,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
to = [@ap_public | mentions]
|
||||
cc = [actor.followers_url]
|
||||
|
||||
cc = maybe_add_group_members(cc, actor)
|
||||
|
||||
{to, cc}
|
||||
end
|
||||
|
||||
|
@ -47,13 +130,18 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
to = [actor.followers_url | mentions]
|
||||
cc = [@ap_public]
|
||||
|
||||
to = maybe_add_group_members(to, actor)
|
||||
|
||||
{to, cc}
|
||||
end
|
||||
|
||||
@spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
|
||||
def get_to_and_cc(%Actor{} = actor, mentions, :private) do
|
||||
{to, cc} = get_to_and_cc(actor, mentions, :direct)
|
||||
{[actor.followers_url | to], cc}
|
||||
|
||||
to = maybe_add_group_members(to, actor)
|
||||
|
||||
{to, cc}
|
||||
end
|
||||
|
||||
@spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
|
||||
|
@ -65,125 +153,31 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
{mentions, []}
|
||||
end
|
||||
|
||||
@spec maybe_add_group_members(List.t(), Actor.t()) :: List.t()
|
||||
defp maybe_add_group_members(collection, %Actor{type: :Group, members_url: members_url}) do
|
||||
[members_url | collection]
|
||||
end
|
||||
|
||||
defp maybe_add_group_members(collection, %Actor{type: _}), do: collection
|
||||
|
||||
@spec maybe_add_followers(List.t(), Actor.t()) :: List.t()
|
||||
defp maybe_add_followers(collection, %Actor{type: :Group, followers_url: followers_url}) do
|
||||
[followers_url | collection]
|
||||
end
|
||||
|
||||
defp maybe_add_followers(collection, %Actor{type: _}), do: collection
|
||||
|
||||
def get_addressed_actors(mentioned_users, _), do: mentioned_users
|
||||
|
||||
def calculate_to_and_cc_from_mentions(
|
||||
%Comment{discussion: %Discussion{actor_id: actor_id}} = _comment
|
||||
) do
|
||||
with %Actor{type: :Group, members_url: members_url} <- Actors.get_actor(actor_id) do
|
||||
%{"to" => [members_url], "cc" => []}
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Comment{} = comment) do
|
||||
with {to, cc} <-
|
||||
extract_actors_from_mentions(comment.mentions, comment.actor, comment.visibility),
|
||||
{to, cc} <- {Enum.uniq(to ++ add_in_reply_to(comment.in_reply_to_comment)), cc},
|
||||
{to, cc} <- {Enum.uniq(to ++ add_event_author(comment.event)), cc},
|
||||
{to, cc} <-
|
||||
{to,
|
||||
Enum.uniq(
|
||||
cc ++
|
||||
add_comments_authors([comment.origin_comment]) ++
|
||||
add_shares_actors_followers(comment.url)
|
||||
)} do
|
||||
%{"to" => to, "cc" => cc}
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Discussion{actor_id: actor_id}) do
|
||||
with %Actor{type: :Group, members_url: members_url} <- Actors.get_actor(actor_id) do
|
||||
%{"to" => [members_url], "cc" => []}
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(
|
||||
%Event{
|
||||
attributed_to: %Actor{members_url: members_url},
|
||||
visibility: visibility
|
||||
} = event
|
||||
) do
|
||||
%{"to" => to, "cc" => cc} = extract_actors_from_event(event)
|
||||
|
||||
case visibility do
|
||||
:public ->
|
||||
%{"to" => [@ap_public, members_url] ++ to, "cc" => [] ++ cc}
|
||||
|
||||
:unlisted ->
|
||||
%{"to" => [members_url] ++ to, "cc" => [@ap_public] ++ cc}
|
||||
|
||||
:private ->
|
||||
# Private is restricted to only the members
|
||||
%{"to" => [members_url], "cc" => []}
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Event{} = event) do
|
||||
extract_actors_from_event(event)
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Post{
|
||||
attributed_to: %Actor{members_url: members_url, followers_url: followers_url},
|
||||
visibility: visibility,
|
||||
draft: draft
|
||||
}) do
|
||||
cond do
|
||||
# If the post is draft we send it only to members
|
||||
draft == true ->
|
||||
%{"to" => [members_url], "cc" => []}
|
||||
|
||||
# If public everyone
|
||||
visibility == :public ->
|
||||
%{"to" => [@ap_public, members_url], "cc" => [followers_url]}
|
||||
|
||||
# Otherwise just followers
|
||||
visibility == :unlisted ->
|
||||
%{"to" => [followers_url, members_url], "cc" => [@ap_public]}
|
||||
|
||||
visibility == :private ->
|
||||
# Private is restricted to only the members
|
||||
%{"to" => [members_url], "cc" => []}
|
||||
|
||||
true ->
|
||||
%{"to" => [], "cc" => []}
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Participant{} = participant) do
|
||||
participant = Repo.preload(participant, [:actor, :event])
|
||||
|
||||
actor_participants_urls =
|
||||
participant.event.id
|
||||
|> Mobilizon.Events.list_actors_participants_for_event()
|
||||
|> Enum.map(& &1.url)
|
||||
|
||||
%{"to" => [participant.actor.url], "cc" => actor_participants_urls}
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Member{} = member) do
|
||||
member = Repo.preload(member, [:parent])
|
||||
|
||||
%{"to" => [member.parent.members_url], "cc" => []}
|
||||
end
|
||||
|
||||
def calculate_to_and_cc_from_mentions(%Actor{} = actor) do
|
||||
%{
|
||||
"to" => [@ap_public],
|
||||
"cc" => [actor.followers_url] ++ add_actors_that_had_our_content(actor.id)
|
||||
}
|
||||
end
|
||||
|
||||
defp add_in_reply_to(%Comment{actor: %Actor{url: url}} = _comment), do: [url]
|
||||
defp add_in_reply_to(%Event{organizer_actor: %Actor{url: url}} = _event), do: [url]
|
||||
defp add_in_reply_to(_), do: []
|
||||
|
||||
defp add_event_author(nil), do: []
|
||||
|
||||
defp add_event_author(%Event{} = event) do
|
||||
[Repo.preload(event, [:organizer_actor]).organizer_actor.url]
|
||||
end
|
||||
|
||||
defp add_comment_author(nil), do: nil
|
||||
defp add_event_author(_), do: []
|
||||
|
||||
defp add_comment_author(%Comment{} = comment) do
|
||||
case Repo.preload(comment, [:actor]) do
|
||||
|
@ -195,6 +189,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
end
|
||||
end
|
||||
|
||||
defp add_comment_author(_), do: nil
|
||||
|
||||
defp add_comments_authors(comments) do
|
||||
authors =
|
||||
comments
|
||||
|
@ -208,8 +204,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
defp add_shares_actors_followers(uri) do
|
||||
uri
|
||||
|> Share.get_actors_by_share_uri()
|
||||
|> Enum.map(&Actors.list_followers_actors_for_actor/1)
|
||||
|> List.flatten()
|
||||
|> Enum.map(& &1.url)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
@ -217,8 +211,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
defp add_actors_that_had_our_content(actor_id) do
|
||||
actor_id
|
||||
|> Share.get_actors_by_owner_actor_id()
|
||||
|> Enum.map(&Actors.list_followers_actors_for_actor/1)
|
||||
|> List.flatten()
|
||||
|> Enum.map(& &1.url)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
|
@ -241,7 +233,11 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
|
||||
defp extract_actors_from_event(%Event{} = event) do
|
||||
with {to, cc} <-
|
||||
extract_actors_from_mentions(event.mentions, event.organizer_actor, event.visibility),
|
||||
extract_actors_from_mentions(
|
||||
event.mentions,
|
||||
group_or_organizer_event(event),
|
||||
event.visibility
|
||||
),
|
||||
{to, cc} <-
|
||||
{to,
|
||||
Enum.uniq(
|
||||
|
@ -253,4 +249,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
%{"to" => [], "cc" => []}
|
||||
end
|
||||
end
|
||||
|
||||
@spec group_or_organizer_event(Event.t()) :: Actor.t()
|
||||
defp group_or_organizer_event(%Event{attributed_to: %Actor{} = group}), do: group
|
||||
defp group_or_organizer_event(%Event{organizer_actor: %Actor{} = actor}), do: actor
|
||||
end
|
||||
|
|
134
lib/federation/activity_pub/permission.ex
Normal file
134
lib/federation/activity_pub/permission.ex
Normal file
|
@ -0,0 +1,134 @@
|
|||
defmodule Mobilizon.Federation.ActivityPub.Permission do
|
||||
@moduledoc """
|
||||
Module to check group members permissions on objects
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Types.{Entity, Ownable}
|
||||
require Logger
|
||||
|
||||
use StructAccess
|
||||
defstruct [:access, :create, :update, :delete]
|
||||
|
||||
@member_roles [:member, :moderator, :administrator]
|
||||
|
||||
@doc """
|
||||
Check that actor can access the object
|
||||
"""
|
||||
@spec can_access_group_object?(Actor.t(), Entity.t()) :: boolean()
|
||||
def can_access_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:access, actor, object)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check that actor can create such an object
|
||||
"""
|
||||
@spec can_create_group_object?(String.t() | integer(), String.t() | integer(), Entity.t()) ::
|
||||
boolean()
|
||||
def can_create_group_object?(
|
||||
actor_id,
|
||||
group_id,
|
||||
object
|
||||
) do
|
||||
case object |> Ownable.permissions() |> get_in([:create]) do
|
||||
:member ->
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
|
||||
:moderator ->
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check that actor can update the object
|
||||
"""
|
||||
@spec can_update_group_object?(Actor.t(), Entity.t()) :: boolean()
|
||||
def can_update_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:update, actor, object)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Check that actor can delete the object
|
||||
"""
|
||||
@spec can_delete_group_object?(Actor.t(), Entity.t()) :: boolean()
|
||||
def can_delete_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:delete, actor, object)
|
||||
end
|
||||
|
||||
@type existing_object_permissions :: :access | :update | :delete
|
||||
|
||||
@spec can_manage_group_object?(
|
||||
existing_object_permissions(),
|
||||
Actor.t(),
|
||||
any()
|
||||
) :: boolean()
|
||||
defp can_manage_group_object?(permission, %Actor{url: actor_url} = actor, object) do
|
||||
if Ownable.group_actor(object) != nil do
|
||||
case object |> Ownable.permissions() |> get_in([permission]) do
|
||||
role when role in @member_roles ->
|
||||
activity_actor_is_group_member?(actor, object, role)
|
||||
|
||||
_ ->
|
||||
case permission do
|
||||
:access ->
|
||||
Logger.warn("Actor #{actor_url} can't access #{object.url}")
|
||||
|
||||
:update ->
|
||||
Logger.warn("Actor #{actor_url} can't update #{object.url}")
|
||||
|
||||
:delete ->
|
||||
Logger.warn("Actor #{actor_url} can't delete #{object.url}")
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@spec activity_actor_is_group_member?(Actor.t(), Entity.t(), atom()) :: boolean()
|
||||
defp activity_actor_is_group_member?(
|
||||
%Actor{id: actor_id, url: actor_url},
|
||||
object,
|
||||
role
|
||||
) do
|
||||
case Ownable.group_actor(object) do
|
||||
%Actor{type: :Group, id: group_id, url: group_url} ->
|
||||
Logger.debug("Group object url is #{group_url}")
|
||||
|
||||
case role do
|
||||
:moderator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
end
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,7 +14,7 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
|
|||
|
||||
@spec maybe_preload(struct()) :: {:ok, struct()} | {:error, struct()}
|
||||
def maybe_preload(%Event{url: url}),
|
||||
do: {:ok, Events.get_public_event_by_url_with_preload!(url)}
|
||||
do: {:ok, Events.get_event_by_url!(url)}
|
||||
|
||||
def maybe_preload(%Comment{url: url}),
|
||||
do: {:ok, Discussions.get_comment_from_url_with_preload!(url)}
|
||||
|
|
|
@ -17,7 +17,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
alias Mobilizon.Todos.{Todo, TodoList}
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Utils}
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Permission, Relay, Utils}
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Ownable
|
||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||
|
@ -409,7 +409,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data) ||
|
||||
Utils.can_update_group_object?(actor, old_event)},
|
||||
Permission.can_update_group_object?(actor, old_event)},
|
||||
{:ok, %Activity{} = activity, %Event{} = new_event} <-
|
||||
ActivityPub.update(old_event, object_data, false) do
|
||||
{:ok, activity, new_event}
|
||||
|
@ -454,7 +454,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data["object"]) ||
|
||||
Utils.can_update_group_object?(actor, old_post)},
|
||||
Permission.can_update_group_object?(actor, old_post)},
|
||||
{:ok, %Activity{} = activity, %Post{} = new_post} <-
|
||||
ActivityPub.update(old_post, object_data, false) do
|
||||
{:ok, activity, new_post}
|
||||
|
@ -482,7 +482,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data) ||
|
||||
Utils.can_update_group_object?(actor, old_resource)},
|
||||
Permission.can_update_group_object?(actor, old_resource)},
|
||||
{:ok, %Activity{} = activity, %Resource{} = new_resource} <-
|
||||
ActivityPub.update(old_resource, object_data, false) do
|
||||
{:ok, activity, new_resource}
|
||||
|
@ -585,7 +585,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Utils.can_delete_group_object?(actor, object)},
|
||||
Permission.can_delete_group_object?(actor, object)},
|
||||
{:ok, activity, object} <- ActivityPub.delete(object, actor, false) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
|
@ -629,7 +629,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, data) ||
|
||||
Utils.can_update_group_object?(actor, old_resource)},
|
||||
Permission.can_update_group_object?(actor, old_resource)},
|
||||
{:ok, activity, new_resource} <- ActivityPub.move(:resource, old_resource, object_data) do
|
||||
{:ok, activity, new_resource}
|
||||
else
|
||||
|
@ -837,7 +837,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
|
||||
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
|
||||
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
|
||||
case get_participant(join_object) do
|
||||
case get_participant(join_object, actor_accepting) do
|
||||
{:ok, participant} ->
|
||||
do_handle_incoming_accept_join_event(participant, actor_accepting)
|
||||
|
||||
|
@ -868,9 +868,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
%Actor{} = actor_accepting
|
||||
)
|
||||
when role in [:not_approved, :rejected] do
|
||||
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
||||
# Or maybe for groups it's the group that sends the Accept activity
|
||||
with {:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
|
||||
with %Event{} = event <- Events.get_event_with_preload!(event.id),
|
||||
{:can_accept_event_join, true} <-
|
||||
{:can_accept_event_join, can_accept_event_join?(actor_accepting, event)},
|
||||
{:ok, %Activity{} = activity, %Participant{role: :participant} = participant} <-
|
||||
ActivityPub.accept(
|
||||
:join,
|
||||
|
@ -881,8 +881,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
Participation.send_emails_to_local_user(participant) do
|
||||
{:ok, activity, participant}
|
||||
else
|
||||
{:same_actor} ->
|
||||
{:error, "Actor who accepted the join wasn't the event organizer. Quite odd."}
|
||||
{:can_accept_event_join, false} ->
|
||||
{:error, "Actor who accepted the join didn't have permission to do so."}
|
||||
|
||||
{:ok, %Participant{role: :participant} = _follow} ->
|
||||
{:error, "Participant"}
|
||||
|
@ -902,7 +902,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
type
|
||||
)
|
||||
when role in [:not_approved, :rejected, :invited] and type in [:join, :invite] do
|
||||
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
||||
# Or maybe for groups it's the group that sends the Accept activity
|
||||
with {:ok, %Activity{} = activity, %Member{role: :member} = member} <-
|
||||
ActivityPub.accept(
|
||||
|
@ -918,7 +917,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
defp do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
|
||||
with {:join_event, {:ok, %Participant{event: event, role: role} = participant}}
|
||||
when role != :rejected <-
|
||||
{:join_event, get_participant(join_object)},
|
||||
{:join_event, get_participant(join_object, actor_accepting)},
|
||||
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
||||
# Or maybe for groups it's the group that sends the Accept activity
|
||||
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
|
||||
|
@ -1026,14 +1025,22 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_participant(join_object) do
|
||||
defp get_participant(join_object, %Actor{} = actor_accepting, loop \\ 1) do
|
||||
with join_object_id when not is_nil(join_object_id) <- Utils.get_url(join_object),
|
||||
{:not_found, %Participant{} = participant} <-
|
||||
{:not_found, Events.get_participant_by_url(join_object_id)} do
|
||||
{:ok, participant}
|
||||
else
|
||||
{:not_found, _err} ->
|
||||
with true <- is_map(join_object),
|
||||
true <- loop < 2,
|
||||
true <- Utils.are_same_origin?(actor_accepting.url, join_object["id"]),
|
||||
{:ok, _activity, %Participant{url: participant_url}} <- handle_incoming(join_object) do
|
||||
get_participant(participant_url, actor_accepting, 2)
|
||||
else
|
||||
_ ->
|
||||
{:error, "Participant URL not found"}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, "ActivityPub ID not found in Accept Join object"}
|
||||
|
@ -1130,4 +1137,22 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
_ -> {:error, :remove_object_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
defp can_accept_event_join?(
|
||||
%Actor{url: actor_url} = actor,
|
||||
%Event{attributed_to: %Actor{type: :Group, url: group_url} = _group} = event
|
||||
) do
|
||||
actor_url == group_url || Permission.can_update_group_object?(actor, event)
|
||||
end
|
||||
|
||||
defp can_accept_event_join?(
|
||||
%Actor{id: actor_id},
|
||||
%Event{organizer_actor: %Actor{id: organizer_actor_id}}
|
||||
) do
|
||||
organizer_actor_id == actor_id
|
||||
end
|
||||
|
||||
defp can_accept_event_join?(_actor, _event) do
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Relay}
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission, Relay}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
|
||||
|
@ -47,7 +47,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
actor_as_data <- Convertible.model_to_as(new_actor),
|
||||
{:ok, true} <- Cachex.del(:activity_pub, "actor_#{new_actor.preferred_username}"),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(new_actor),
|
||||
Audience.get_audience(new_actor),
|
||||
additional <- Map.merge(additional, %{"actor" => old_actor.url}),
|
||||
update_data <- make_update_data(actor_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, new_actor, update_data}
|
||||
|
@ -104,8 +104,14 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
|
||||
def group_actor(%Actor{} = actor), do: actor
|
||||
|
||||
def role_needed_to_update(%Actor{} = _group), do: :administrator
|
||||
def role_needed_to_delete(%Actor{} = _group), do: :administrator
|
||||
def permissions(%Actor{} = _group) do
|
||||
%Permission{
|
||||
access: :member,
|
||||
create: nil,
|
||||
update: :administrator,
|
||||
delete: :administrator
|
||||
}
|
||||
end
|
||||
|
||||
@spec join(Actor.t(), Actor.t(), boolean(), map()) :: {:ok, map(), Member.t()}
|
||||
def join(%Actor{type: :Group} = group, %Actor{} = actor, _local, additional) do
|
||||
|
@ -136,7 +142,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
"object" => group.url
|
||||
},
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(member) do
|
||||
Audience.get_audience(member) do
|
||||
approve_if_default_role_is_member(
|
||||
group,
|
||||
actor,
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
|||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.{Event, EventOptions}
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
|
@ -32,7 +32,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
|||
:ok <- maybe_publish_graphql_subscription(discussion_id),
|
||||
comment_as_data <- Convertible.model_to_as(comment),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(comment),
|
||||
Audience.get_audience(comment),
|
||||
create_data <-
|
||||
make_create_data(comment_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, comment, create_data}
|
||||
|
@ -47,7 +47,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
|||
{:ok, true} <- Cachex.del(:activity_pub, "comment_#{new_comment.uuid}"),
|
||||
comment_as_data <- Convertible.model_to_as(new_comment),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(new_comment),
|
||||
Audience.get_audience(new_comment),
|
||||
update_data <- make_update_data(comment_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, new_comment, update_data}
|
||||
else
|
||||
|
@ -79,7 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
|||
force_deletion = Map.get(options, :force, false)
|
||||
|
||||
with audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(comment),
|
||||
Audience.get_audience(comment),
|
||||
{:ok, %Comment{} = updated_comment} <-
|
||||
Discussions.delete_comment(comment, force: force_deletion),
|
||||
{:ok, true} <- Cachex.del(:activity_pub, "comment_#{comment.uuid}"),
|
||||
|
@ -104,8 +104,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
|||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Comment{attributed_to: %Actor{} = _group}), do: :administrator
|
||||
def role_needed_to_delete(%Comment{attributed_to_id: _attributed_to_id}), do: :administrator
|
||||
def permissions(%Comment{}),
|
||||
do: %Permission{
|
||||
access: :member,
|
||||
create: :member,
|
||||
update: :administrator,
|
||||
delete: :administrator
|
||||
}
|
||||
|
||||
# Prepare and sanitize arguments for comments
|
||||
defp prepare_args_for_comment(args) do
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
|||
alias Mobilizon.{Actors, Discussions}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
|
||||
|
@ -31,7 +31,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
|||
:ok <- maybe_publish_graphql_subscription(discussion),
|
||||
comment_as_data <- Convertible.model_to_as(last_comment),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(discussion),
|
||||
Audience.get_audience(discussion),
|
||||
create_data <-
|
||||
make_create_data(comment_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, discussion, create_data}
|
||||
|
@ -48,7 +48,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
|||
DiscussionActivity.insert_activity(discussion, subject: "discussion_created"),
|
||||
discussion_as_data <- Convertible.model_to_as(discussion),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(discussion),
|
||||
Audience.get_audience(discussion),
|
||||
create_data <-
|
||||
make_create_data(discussion_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, discussion, create_data}
|
||||
|
@ -68,7 +68,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
|||
{:ok, true} <- Cachex.del(:activity_pub, "discussion_#{new_discussion.slug}"),
|
||||
discussion_as_data <- Convertible.model_to_as(new_discussion),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(new_discussion),
|
||||
Audience.get_audience(new_discussion),
|
||||
update_data <- make_update_data(discussion_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, new_discussion, update_data}
|
||||
else
|
||||
|
@ -110,8 +110,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
|||
|
||||
def group_actor(%Discussion{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%Discussion{}), do: :moderator
|
||||
def role_needed_to_delete(%Discussion{}), do: :moderator
|
||||
def permissions(%Discussion{}) do
|
||||
%Permission{access: :member, create: :member, update: :moderator, delete: :moderator}
|
||||
end
|
||||
|
||||
@spec maybe_publish_graphql_subscription(Discussion.t()) :: :ok
|
||||
defp maybe_publish_graphql_subscription(%Discussion{} = discussion) do
|
||||
|
|
|
@ -17,6 +17,7 @@ alias Mobilizon.Federation.ActivityPub.Types.{
|
|||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Todos.{Todo, TodoList}
|
||||
|
@ -67,11 +68,8 @@ defprotocol Mobilizon.Federation.ActivityPub.Types.Ownable do
|
|||
@doc "Returns the actor for the entity"
|
||||
def actor(entity)
|
||||
|
||||
@spec role_needed_to_update(Entity.t()) :: group_role()
|
||||
def role_needed_to_update(entity)
|
||||
|
||||
@spec role_needed_to_delete(Entity.t()) :: group_role()
|
||||
def role_needed_to_delete(entity)
|
||||
@spec permissions(Entity.t()) :: Permission.t()
|
||||
def permissions(entity)
|
||||
end
|
||||
|
||||
defimpl Managable, for: Event do
|
||||
|
@ -82,8 +80,7 @@ end
|
|||
defimpl Ownable, for: Event do
|
||||
defdelegate group_actor(entity), to: Events
|
||||
defdelegate actor(entity), to: Events
|
||||
defdelegate role_needed_to_update(entity), to: Events
|
||||
defdelegate role_needed_to_delete(entity), to: Events
|
||||
defdelegate permissions(entity), to: Events
|
||||
end
|
||||
|
||||
defimpl Managable, for: Comment do
|
||||
|
@ -94,8 +91,7 @@ end
|
|||
defimpl Ownable, for: Comment do
|
||||
defdelegate group_actor(entity), to: Comments
|
||||
defdelegate actor(entity), to: Comments
|
||||
defdelegate role_needed_to_update(entity), to: Comments
|
||||
defdelegate role_needed_to_delete(entity), to: Comments
|
||||
defdelegate permissions(entity), to: Comments
|
||||
end
|
||||
|
||||
defimpl Managable, for: Post do
|
||||
|
@ -106,8 +102,7 @@ end
|
|||
defimpl Ownable, for: Post do
|
||||
defdelegate group_actor(entity), to: Posts
|
||||
defdelegate actor(entity), to: Posts
|
||||
defdelegate role_needed_to_update(entity), to: Posts
|
||||
defdelegate role_needed_to_delete(entity), to: Posts
|
||||
defdelegate permissions(entity), to: Posts
|
||||
end
|
||||
|
||||
defimpl Managable, for: Actor do
|
||||
|
@ -118,8 +113,7 @@ end
|
|||
defimpl Ownable, for: Actor do
|
||||
defdelegate group_actor(entity), to: Actors
|
||||
defdelegate actor(entity), to: Actors
|
||||
defdelegate role_needed_to_update(entity), to: Actors
|
||||
defdelegate role_needed_to_delete(entity), to: Actors
|
||||
defdelegate permissions(entity), to: Actors
|
||||
end
|
||||
|
||||
defimpl Managable, for: TodoList do
|
||||
|
@ -130,8 +124,7 @@ end
|
|||
defimpl Ownable, for: TodoList do
|
||||
defdelegate group_actor(entity), to: TodoLists
|
||||
defdelegate actor(entity), to: TodoLists
|
||||
defdelegate role_needed_to_update(entity), to: TodoLists
|
||||
defdelegate role_needed_to_delete(entity), to: TodoLists
|
||||
defdelegate permissions(entity), to: TodoLists
|
||||
end
|
||||
|
||||
defimpl Managable, for: Todo do
|
||||
|
@ -142,8 +135,7 @@ end
|
|||
defimpl Ownable, for: Todo do
|
||||
defdelegate group_actor(entity), to: Todos
|
||||
defdelegate actor(entity), to: Todos
|
||||
defdelegate role_needed_to_update(entity), to: Todos
|
||||
defdelegate role_needed_to_delete(entity), to: Todos
|
||||
defdelegate permissions(entity), to: Todos
|
||||
end
|
||||
|
||||
defimpl Managable, for: Resource do
|
||||
|
@ -154,8 +146,7 @@ end
|
|||
defimpl Ownable, for: Resource do
|
||||
defdelegate group_actor(entity), to: Resources
|
||||
defdelegate actor(entity), to: Resources
|
||||
defdelegate role_needed_to_update(entity), to: Resources
|
||||
defdelegate role_needed_to_delete(entity), to: Resources
|
||||
defdelegate permissions(entity), to: Resources
|
||||
end
|
||||
|
||||
defimpl Managable, for: Discussion do
|
||||
|
@ -166,15 +157,13 @@ end
|
|||
defimpl Ownable, for: Discussion do
|
||||
defdelegate group_actor(entity), to: Discussions
|
||||
defdelegate actor(entity), to: Discussions
|
||||
defdelegate role_needed_to_update(entity), to: Discussions
|
||||
defdelegate role_needed_to_delete(entity), to: Discussions
|
||||
defdelegate permissions(entity), to: Discussions
|
||||
end
|
||||
|
||||
defimpl Ownable, for: Tombstone do
|
||||
defdelegate group_actor(entity), to: Tombstones
|
||||
defdelegate actor(entity), to: Tombstones
|
||||
defdelegate role_needed_to_update(entity), to: Tombstones
|
||||
defdelegate role_needed_to_delete(entity), to: Tombstones
|
||||
defdelegate permissions(entity), to: Tombstones
|
||||
end
|
||||
|
||||
defimpl Managable, for: Member do
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
alias Mobilizon.Events, as: EventsManager
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
|
@ -29,7 +29,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
EventActivity.insert_activity(event, subject: "event_created"),
|
||||
event_as_data <- Convertible.model_to_as(event),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(event),
|
||||
Audience.get_audience(event),
|
||||
create_data <-
|
||||
make_create_data(event_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, event, create_data}
|
||||
|
@ -46,7 +46,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
{:ok, true} <- Cachex.del(:activity_pub, "event_#{new_event.uuid}"),
|
||||
event_as_data <- Convertible.model_to_as(new_event),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(new_event),
|
||||
Audience.get_audience(new_event),
|
||||
update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do
|
||||
{:ok, new_event, update_data}
|
||||
else
|
||||
|
@ -69,7 +69,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
}
|
||||
|
||||
with audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(event),
|
||||
Audience.get_audience(event),
|
||||
{:ok, %Event{} = event} <- EventsManager.delete_event(event),
|
||||
{:ok, _} <-
|
||||
EventActivity.insert_activity(event, subject: "event_deleted"),
|
||||
|
@ -95,9 +95,14 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Event{attributed_to: %Actor{} = _group}), do: :moderator
|
||||
def role_needed_to_delete(%Event{attributed_to_id: _attributed_to_id}), do: :moderator
|
||||
def role_needed_to_delete(_), do: nil
|
||||
def permissions(%Event{draft: draft, attributed_to_id: _attributed_to_id}) do
|
||||
%Permission{
|
||||
access: if(draft, do: nil, else: :member),
|
||||
create: :moderator,
|
||||
update: :moderator,
|
||||
delete: :moderator
|
||||
}
|
||||
end
|
||||
|
||||
def join(%Event{} = event, %Actor{} = actor, _local, additional) do
|
||||
with {:maximum_attendee_capacity, true} <-
|
||||
|
@ -119,7 +124,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
}),
|
||||
join_data <- Convertible.model_to_as(participant),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(participant) do
|
||||
Audience.get_audience(participant) do
|
||||
approve_if_default_role_is_participant(
|
||||
event,
|
||||
Map.merge(join_data, audience),
|
||||
|
@ -142,7 +147,30 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
|
||||
# Set the participant to approved if the default role for new participants is :participant
|
||||
defp approve_if_default_role_is_participant(event, activity_data, participant, role) do
|
||||
if event.local do
|
||||
case event do
|
||||
%Event{attributed_to: %Actor{id: group_id, url: group_url}} ->
|
||||
case Actors.get_single_group_moderator_actor(group_id) do
|
||||
%Actor{} = actor ->
|
||||
do_approve(event, activity_data, participant, role, %{
|
||||
"actor" => actor.url,
|
||||
"attributedTo" => group_url
|
||||
})
|
||||
|
||||
_ ->
|
||||
{:ok, activity_data, participant}
|
||||
end
|
||||
|
||||
%Event{local: true} ->
|
||||
do_approve(event, activity_data, participant, role, %{
|
||||
"actor" => event.organizer_actor.url
|
||||
})
|
||||
|
||||
_ ->
|
||||
{:ok, activity_data, participant}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_approve(event, activity_data, participant, role, additionnal) do
|
||||
cond do
|
||||
Mobilizon.Events.get_default_participant_role(event) === :participant &&
|
||||
role == :participant ->
|
||||
|
@ -151,7 +179,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
:join,
|
||||
participant,
|
||||
true,
|
||||
%{"actor" => event.organizer_actor.url}
|
||||
additionnal
|
||||
)}
|
||||
|
||||
Mobilizon.Events.get_default_participant_role(event) === :not_approved &&
|
||||
|
@ -162,9 +190,6 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
true ->
|
||||
{:ok, activity_data, participant}
|
||||
end
|
||||
else
|
||||
{:ok, activity_data, participant}
|
||||
end
|
||||
end
|
||||
|
||||
# Prepare and sanitize arguments for events
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Posts, Tombstone}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
|
@ -47,7 +47,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
|||
post_as_data <-
|
||||
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
||||
audience <-
|
||||
Audience.calculate_to_and_cc_from_mentions(post) do
|
||||
Audience.get_audience(post) do
|
||||
update_data = make_update_data(post_as_data, Map.merge(audience, additional))
|
||||
|
||||
{:ok, post, update_data}
|
||||
|
@ -91,6 +91,12 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
|||
def group_actor(%Post{attributed_to_id: attributed_to_id}),
|
||||
do: Actors.get_actor(attributed_to_id)
|
||||
|
||||
def role_needed_to_update(%Post{}), do: :moderator
|
||||
def role_needed_to_delete(%Post{}), do: :moderator
|
||||
def permissions(%Post{}) do
|
||||
%Permission{
|
||||
access: :member,
|
||||
create: :moderator,
|
||||
update: :moderator,
|
||||
delete: :moderator
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Resources}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Resources.Resource
|
||||
|
@ -170,6 +171,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
|
||||
def group_actor(%Resource{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%Resource{}), do: :member
|
||||
def role_needed_to_delete(%Resource{}), do: :member
|
||||
def permissions(%Resource{}) do
|
||||
%Permission{access: :member, create: :member, update: :member, delete: :member}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Todos}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
|
@ -68,6 +69,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
|
|||
|
||||
def group_actor(%TodoList{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%TodoList{}), do: :member
|
||||
def role_needed_to_delete(%TodoList{}), do: :member
|
||||
def permissions(%TodoList{}) do
|
||||
%Permission{access: :member, create: :member, update: :member, delete: :member}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Todos}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Todos.{Todo, TodoList}
|
||||
|
@ -80,6 +81,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
|
|||
end
|
||||
end
|
||||
|
||||
def role_needed_to_update(%Todo{}), do: :member
|
||||
def role_needed_to_delete(%Todo{}), do: :member
|
||||
def permissions(%Todo{}) do
|
||||
%Permission{access: :member, create: :member, update: :member, delete: :member}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Tombstones do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Tombstone}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
|
||||
def actor(%Tombstone{actor: %Actor{id: actor_id}}), do: Actors.get_actor(actor_id)
|
||||
|
||||
|
@ -12,6 +13,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Tombstones do
|
|||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Actor{}), do: nil
|
||||
def role_needed_to_delete(%Actor{}), do: nil
|
||||
def permissions(_) do
|
||||
%Permission{access: nil, create: nil, update: nil, delete: nil}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,6 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay}
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Ownable
|
||||
alias Mobilizon.Federation.ActivityStream.Converter
|
||||
alias Mobilizon.Federation.HTTPSignatures
|
||||
alias Mobilizon.Web.Endpoint
|
||||
|
@ -291,43 +290,6 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
def origin_check_from_id?(id, %{"id" => other_id} = _params) when is_binary(other_id),
|
||||
do: origin_check_from_id?(id, other_id)
|
||||
|
||||
def activity_actor_is_group_member?(
|
||||
%Actor{id: actor_id, url: actor_url},
|
||||
object,
|
||||
role \\ :member
|
||||
) do
|
||||
case Ownable.group_actor(object) do
|
||||
%Actor{type: :Group, id: group_id, url: group_url} ->
|
||||
Logger.debug("Group object url is #{group_url}")
|
||||
|
||||
case role do
|
||||
:moderator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
end
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return AS Link data from
|
||||
|
||||
|
@ -514,6 +476,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
"type" => "Update",
|
||||
"to" => object["to"],
|
||||
"cc" => object["cc"],
|
||||
"attributedTo" => object["attributedTo"] || object["actor"],
|
||||
"actor" => object["actor"],
|
||||
"object" => object,
|
||||
"id" => object["id"] <> "/activity"
|
||||
|
@ -662,41 +625,6 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
:ok
|
||||
end
|
||||
|
||||
def can_update_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:role_needed_to_update, actor, object)
|
||||
end
|
||||
|
||||
def can_delete_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:role_needed_to_delete, actor, object)
|
||||
end
|
||||
|
||||
@spec can_manage_group_object?(
|
||||
:role_needed_to_update | :role_needed_to_delete,
|
||||
Actor.t(),
|
||||
any()
|
||||
) :: boolean()
|
||||
defp can_manage_group_object?(action_function, %Actor{url: actor_url} = actor, object) do
|
||||
if Ownable.group_actor(object) != nil do
|
||||
case apply(Ownable, action_function, [object]) do
|
||||
role when role in [:member, :moderator, :administrator] ->
|
||||
activity_actor_is_group_member?(actor, object, role)
|
||||
|
||||
_ ->
|
||||
case action_function do
|
||||
:role_needed_to_update ->
|
||||
Logger.warn("Actor #{actor_url} can't update #{object.url}")
|
||||
|
||||
:role_needed_to_delete ->
|
||||
Logger.warn("Actor #{actor_url} can't delete #{object.url}")
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@spec label_in_collection?(any(), any()) :: boolean()
|
||||
defp label_in_collection?(url, coll) when is_binary(coll), do: url == coll
|
||||
defp label_in_collection?(url, coll) when is_list(coll), do: url in coll
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
|
||||
alias Mobilizon.Web.Endpoint
|
||||
|
||||
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
||||
only: [
|
||||
|
@ -36,6 +37,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
|
||||
@online_address_name "Website"
|
||||
@banner_picture_name "Banner"
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
@doc """
|
||||
Converts an AP object data to our internal data structure.
|
||||
|
@ -43,7 +45,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
@impl Converter
|
||||
@spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
|
||||
def as_to_model_data(object) do
|
||||
with {%Actor{id: actor_id, domain: actor_domain}, attributed_to} <-
|
||||
with {%Actor{id: actor_id}, attributed_to} <-
|
||||
maybe_fetch_actor_and_attributed_to_id(object),
|
||||
{:address, address_id} <-
|
||||
{:address, get_address(object["location"])},
|
||||
|
@ -65,7 +67,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
category: object["category"],
|
||||
visibility: visibility,
|
||||
join_options: Map.get(object, "joinMode", "free"),
|
||||
local: is_nil(actor_domain),
|
||||
local: is_local(object["id"]),
|
||||
options: options,
|
||||
status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
|
||||
online_address: object |> Map.get("attachment", []) |> get_online_address(),
|
||||
|
@ -91,15 +93,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
@impl Converter
|
||||
@spec model_to_as(EventModel.t()) :: map
|
||||
def model_to_as(%EventModel{} = event) do
|
||||
to =
|
||||
{to, cc} =
|
||||
if event.visibility == :public,
|
||||
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
else: [attributed_to_or_default(event).followers_url]
|
||||
do: {[@ap_public], []},
|
||||
else: {[attributed_to_or_default(event).followers_url], [@ap_public]}
|
||||
|
||||
%{
|
||||
"type" => "Event",
|
||||
"to" => to,
|
||||
"cc" => [],
|
||||
"cc" => cc,
|
||||
"attributedTo" => attributed_to_or_default(event).url,
|
||||
"name" => event.title,
|
||||
"actor" =>
|
||||
|
@ -274,4 +276,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
&(&1 ++ medias)
|
||||
)
|
||||
end
|
||||
|
||||
defp is_local(url) do
|
||||
%URI{host: url_domain} = URI.parse(url)
|
||||
%URI{host: local_domain} = URI.parse(Endpoint.url())
|
||||
url_domain == local_domain
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
|||
}
|
||||
} = post
|
||||
) do
|
||||
audience = Audience.calculate_to_and_cc_from_mentions(post)
|
||||
audience = Audience.get_audience(post)
|
||||
|
||||
%{
|
||||
"type" => "Article",
|
||||
|
@ -65,10 +65,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
|||
@impl Converter
|
||||
@spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
|
||||
def as_to_model_data(
|
||||
%{"type" => "Article", "actor" => creator, "attributedTo" => group} = object
|
||||
%{"type" => "Article", "actor" => creator, "attributedTo" => group_uri} = object
|
||||
) do
|
||||
with {:ok, %Actor{id: attributed_to_id}} <- get_actor(group),
|
||||
with {:ok, %Actor{id: attributed_to_id} = group} <- get_actor(group_uri),
|
||||
{:ok, %Actor{id: author_id}} <- get_actor(creator),
|
||||
{:visibility, visibility} <- {:visibility, get_visibility(object, group)},
|
||||
[description: description, picture_id: picture_id, medias: medias] <-
|
||||
process_pictures(object, attributed_to_id) do
|
||||
%{
|
||||
|
@ -81,6 +82,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
|||
publish_at: object["published"],
|
||||
picture_id: picture_id,
|
||||
medias: medias,
|
||||
visibility: visibility,
|
||||
draft: object["draft"] == true
|
||||
}
|
||||
else
|
||||
|
@ -128,4 +130,17 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
|
|||
&(&1 ++ medias)
|
||||
)
|
||||
end
|
||||
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
defp get_visibility(%{"to" => to}, %Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
}) do
|
||||
cond do
|
||||
@ap_public in to -> :public
|
||||
followers_url in to -> :unlisted
|
||||
members_url in to -> :private
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,7 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||
process_picture(picture, organizer_actor)
|
||||
end) do
|
||||
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
||||
ActivityPub.create(:event, args, args.draft == false)
|
||||
ActivityPub.create(:event, args, should_federate(args))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -37,7 +37,7 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||
Map.update(args, :picture, nil, fn picture ->
|
||||
process_picture(picture, organizer_actor)
|
||||
end) do
|
||||
ActivityPub.update(event, args, Map.get(args, :draft, false) == false)
|
||||
ActivityPub.update(event, args, should_federate(args))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -74,4 +74,9 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||
end
|
||||
|
||||
defp extract_pictures_from_event_body(args, _), do: args
|
||||
|
||||
defp should_federate(%{attributed_to_id: attributed_to_id}) when not is_nil(attributed_to_id),
|
||||
do: true
|
||||
|
||||
defp should_federate(args), do: Map.get(args, :draft, false) == false
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
alias Mobilizon.GraphQL.API
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Permission
|
||||
import Mobilizon.Users.Guards, only: [is_moderator: 1]
|
||||
import Mobilizon.Web.Gettext
|
||||
|
||||
|
@ -75,13 +76,28 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
defp find_private_event(
|
||||
_parent,
|
||||
%{uuid: uuid},
|
||||
%{context: %{current_user: %User{id: user_id}}} = _resolution
|
||||
%{context: %{current_user: %User{} = user}} = _resolution
|
||||
) do
|
||||
case {:has_event, Events.get_own_event_by_uuid_with_preload(uuid, user_id)} do
|
||||
{:has_event, %Event{} = event} ->
|
||||
{:ok, event}
|
||||
%Actor{} = profile = Users.get_actor_for_user(user)
|
||||
|
||||
{:has_event, _} ->
|
||||
case Events.get_event_by_uuid_with_preload(uuid) do
|
||||
# Event attributed to group
|
||||
%Event{attributed_to: %Actor{}} = event ->
|
||||
if Permission.can_access_group_object?(profile, event) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:error, :event_not_found}
|
||||
end
|
||||
|
||||
# Own event
|
||||
%Event{organizer_actor: %Actor{id: actor_id}} = event ->
|
||||
if actor_id == profile.id do
|
||||
{:ok, event}
|
||||
else
|
||||
{:error, :event_not_found}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :event_not_found}
|
||||
end
|
||||
end
|
||||
|
@ -239,11 +255,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||
args <- Map.put(args, :options, args[:options] || %{}),
|
||||
{:group_check, true} <- {:group_check, is_organizer_group_member?(args)},
|
||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
API.Events.create_event(args_with_organizer) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:group_check, false} ->
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
)}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, dgettext("errors", "Organizer profile is not owned by the user")}
|
||||
|
||||
|
@ -270,16 +294,21 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
# See https://github.com/absinthe-graphql/absinthe/issues/490
|
||||
with args <- Map.put(args, :options, args[:options] || %{}),
|
||||
{:ok, %Event{} = event} <- Events.get_event_with_preload(event_id),
|
||||
{:old_actor, {:is_owned, %Actor{}}} <-
|
||||
{:old_actor, User.owns_actor(user, event.organizer_actor_id)},
|
||||
new_organizer_actor_id <- args |> Map.get(:organizer_actor_id, event.organizer_actor_id),
|
||||
{:new_actor, {:is_owned, %Actor{} = organizer_actor}} <-
|
||||
{:new_actor, User.owns_actor(user, new_organizer_actor_id)},
|
||||
args <- Map.put(args, :organizer_actor, organizer_actor),
|
||||
%Actor{} = actor <- Users.get_actor_for_user(user),
|
||||
{:ok, args} <- verify_profile_change(args, event, user, actor),
|
||||
{:event_can_be_managed, true} <-
|
||||
{:event_can_be_managed, can_event_be_updated_by?(event, actor)},
|
||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||
API.Events.update_event(args, event) do
|
||||
{:ok, event}
|
||||
else
|
||||
{:event_can_be_managed, false} ->
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"This profile doesn't have permission to update an event on behalf of this group"
|
||||
)}
|
||||
|
||||
{:error, :event_not_found} ->
|
||||
{:error, dgettext("errors", "Event not found")}
|
||||
|
||||
|
@ -309,7 +338,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
with {:ok, %Event{local: is_local} = event} <- Events.get_event_with_preload(event_id),
|
||||
%Actor{id: actor_id} = actor <- Users.get_actor_for_user(user) do
|
||||
cond do
|
||||
{:event_can_be_managed, true} == Event.can_be_managed_by(event, actor_id) ->
|
||||
{:event_can_be_managed, true} ==
|
||||
{:event_can_be_managed, can_event_be_deleted_by?(event, actor)} ->
|
||||
do_delete_event(event, actor)
|
||||
|
||||
role in [:moderator, :administrator] ->
|
||||
|
@ -339,4 +369,74 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
|
|||
{:ok, %{id: event.id}}
|
||||
end
|
||||
end
|
||||
|
||||
defp is_organizer_group_member?(%{
|
||||
attributed_to_id: attributed_to_id,
|
||||
organizer_actor_id: organizer_actor_id
|
||||
})
|
||||
when not is_nil(attributed_to_id) do
|
||||
Actors.is_member?(organizer_actor_id, attributed_to_id) &&
|
||||
Permission.can_create_group_object?(organizer_actor_id, attributed_to_id, %Event{})
|
||||
end
|
||||
|
||||
defp is_organizer_group_member?(_), do: true
|
||||
|
||||
defp verify_profile_change(
|
||||
args,
|
||||
%Event{attributed_to: %Actor{}},
|
||||
%User{} = _user,
|
||||
%Actor{} = current_profile
|
||||
) do
|
||||
# The organizer_actor has to be the current profile, because otherwise we're left with a possible remote organizer
|
||||
args =
|
||||
args
|
||||
|> Map.put(:organizer_actor, current_profile)
|
||||
|> Map.put(:organizer_actor_id, current_profile.id)
|
||||
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
defp verify_profile_change(
|
||||
args,
|
||||
%Event{organizer_actor: %Actor{id: organizer_actor_id}},
|
||||
%User{} = user,
|
||||
%Actor{} = _actor
|
||||
) do
|
||||
with {:old_actor, {:is_owned, %Actor{}}} <-
|
||||
{:old_actor, User.owns_actor(user, organizer_actor_id)},
|
||||
new_organizer_actor_id <- args |> Map.get(:organizer_actor_id, organizer_actor_id),
|
||||
{:new_actor, {:is_owned, %Actor{} = organizer_actor}} <-
|
||||
{:new_actor, User.owns_actor(user, new_organizer_actor_id)},
|
||||
args <-
|
||||
args
|
||||
|> Map.put(:organizer_actor, organizer_actor)
|
||||
|> Map.put(:organizer_actor_id, organizer_actor.id) do
|
||||
{:ok, args}
|
||||
end
|
||||
end
|
||||
|
||||
defp can_event_be_updated_by?(
|
||||
%Event{attributed_to: %Actor{type: :Group}} = event,
|
||||
%Actor{} = actor_member
|
||||
) do
|
||||
Permission.can_update_group_object?(actor_member, event)
|
||||
end
|
||||
|
||||
defp can_event_be_updated_by?(
|
||||
%Event{} = event,
|
||||
%Actor{id: actor_member_id}
|
||||
) do
|
||||
Event.can_be_managed_by?(event, actor_member_id)
|
||||
end
|
||||
|
||||
defp can_event_be_deleted_by?(
|
||||
%Event{attributed_to: %Actor{type: :Group}} = event,
|
||||
%Actor{} = actor_member
|
||||
) do
|
||||
Permission.can_delete_group_object?(actor_member, event)
|
||||
end
|
||||
|
||||
defp can_event_be_deleted_by?(%Event{} = event, %Actor{id: actor_member_id}) do
|
||||
Event.can_be_managed_by?(event, actor_member_id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
|||
alias Mobilizon.{Actors, Posts, Users}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Utils
|
||||
alias Mobilizon.Federation.ActivityPub.{Permission, Utils}
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Storage.Page
|
||||
alias Mobilizon.Users.User
|
||||
|
@ -69,11 +69,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
|||
}
|
||||
} = _resolution
|
||||
) do
|
||||
with {:current_actor, %Actor{id: actor_id}} <-
|
||||
with {:current_actor, %Actor{} = current_profile} <-
|
||||
{:current_actor, Users.get_actor_for_user(user)},
|
||||
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
|
||||
{:post, %Post{attributed_to: %Actor{}} = post} <-
|
||||
{:post, Posts.get_post_by_slug_with_preloads(slug)},
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
|
||||
{:member, true} <- {:member, Permission.can_access_group_object?(current_profile, post)} do
|
||||
{:ok, post}
|
||||
else
|
||||
{:member, false} -> get_post(parent, %{slug: slug}, nil)
|
||||
|
|
|
@ -794,11 +794,18 @@ defmodule Mobilizon.Actors do
|
|||
|
||||
@spec get_single_group_member_actor(integer() | String.t()) :: Actor.t() | nil
|
||||
def get_single_group_member_actor(group_id) do
|
||||
do_get_single_group_member_actor(group_id, [:member, :moderator, :administrator, :creator])
|
||||
end
|
||||
|
||||
@spec get_single_group_moderator_actor(integer() | String.t()) :: Actor.t() | nil
|
||||
def get_single_group_moderator_actor(group_id) do
|
||||
do_get_single_group_member_actor(group_id, [:moderator, :administrator, :creator])
|
||||
end
|
||||
|
||||
@spec do_get_single_group_member_actor(integer() | String.t(), list(atom())) :: Actor.t() | nil
|
||||
defp do_get_single_group_member_actor(group_id, roles) do
|
||||
Member
|
||||
|> where(
|
||||
[m],
|
||||
m.parent_id == ^group_id and m.role in [^:member, ^:moderator, ^:administrator, ^:creator]
|
||||
)
|
||||
|> where([m], m.parent_id == ^group_id and m.role in ^roles)
|
||||
|> join(:inner, [m], a in Actor, on: m.actor_id == a.id)
|
||||
|> where([_m, a], is_nil(a.domain))
|
||||
|> limit(1)
|
||||
|
|
|
@ -188,13 +188,11 @@ defmodule Mobilizon.Events.Event do
|
|||
@doc """
|
||||
Checks whether an event can be managed.
|
||||
"""
|
||||
@spec can_be_managed_by(t, integer | String.t()) :: boolean
|
||||
def can_be_managed_by(%__MODULE__{organizer_actor_id: organizer_actor_id}, actor_id)
|
||||
when organizer_actor_id == actor_id do
|
||||
{:event_can_be_managed, true}
|
||||
end
|
||||
@spec can_be_managed_by?(t, integer | String.t()) :: boolean
|
||||
def can_be_managed_by?(%__MODULE__{organizer_actor_id: organizer_actor_id}, actor_id),
|
||||
do: organizer_actor_id == actor_id
|
||||
|
||||
def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
|
||||
def can_be_managed_by?(_event, _actor), do: false
|
||||
|
||||
@spec put_tags(Changeset.t(), map) :: Changeset.t()
|
||||
defp put_tags(%Changeset{} = changeset, %{tags: tags}) do
|
||||
|
|
|
@ -88,6 +88,8 @@ defmodule Mobilizon.Events do
|
|||
:media
|
||||
]
|
||||
|
||||
@participant_preloads [:event, :actor]
|
||||
|
||||
@doc """
|
||||
Gets a single event.
|
||||
"""
|
||||
|
@ -153,6 +155,7 @@ defmodule Mobilizon.Events do
|
|||
def get_event_by_url!(url) do
|
||||
url
|
||||
|> event_by_url_query()
|
||||
|> preload_for_event()
|
||||
|> Repo.one!()
|
||||
end
|
||||
|
||||
|
@ -306,8 +309,9 @@ defmodule Mobilizon.Events do
|
|||
"""
|
||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||
def update_event(%Event{draft: old_draft} = old_event, attrs) do
|
||||
with %Changeset{changes: changes} = changeset <-
|
||||
Event.update_changeset(Repo.preload(old_event, [:tags, :media]), attrs),
|
||||
with %Event{} = old_event <- Repo.preload(old_event, @event_preloads),
|
||||
%Changeset{changes: changes} = changeset <-
|
||||
Event.update_changeset(old_event, attrs),
|
||||
{:ok, %{update: %Event{} = new_event}} <-
|
||||
Multi.new()
|
||||
|> Multi.update(:update, changeset)
|
||||
|
@ -328,7 +332,8 @@ defmodule Mobilizon.Events do
|
|||
err -> err
|
||||
end
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
|> Repo.transaction(),
|
||||
%Event{} = new_event <- Repo.preload(new_event, @event_preloads, force: true) do
|
||||
Cachex.del(:ics, "event_#{new_event.uuid}")
|
||||
|
||||
Email.Event.calculate_event_diff_and_send_notifications(
|
||||
|
@ -340,7 +345,7 @@ defmodule Mobilizon.Events do
|
|||
unless new_event.draft,
|
||||
do: Workers.BuildSearch.enqueue(:update_search_event, %{"event_id" => new_event.id})
|
||||
|
||||
{:ok, Repo.preload(new_event, @event_preloads)}
|
||||
{:ok, new_event}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -530,6 +535,7 @@ defmodule Mobilizon.Events do
|
|||
|> events_for_ends_on(args)
|
||||
|> events_for_tags(args)
|
||||
|> events_for_location(args)
|
||||
|> filter_draft()
|
||||
|> filter_local_or_from_followed_instances_events()
|
||||
|> filter_public_visibility()
|
||||
|> event_order_begins_on_asc()
|
||||
|
@ -726,7 +732,7 @@ defmodule Mobilizon.Events do
|
|||
def get_participant(participant_id) do
|
||||
Participant
|
||||
|> where([p], p.id == ^participant_id)
|
||||
|> preload([p], [:event, :actor])
|
||||
|> preload([p], ^@participant_preloads)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
|
@ -742,6 +748,7 @@ defmodule Mobilizon.Events do
|
|||
case Participant
|
||||
|> where([p], event_id: ^event_id, actor_id: ^actor_id)
|
||||
|> where([p], fragment("? ->>'email' = ?", p.metadata, ^email))
|
||||
|> preload([p], ^@participant_preloads)
|
||||
|> Repo.one() do
|
||||
%Participant{} = participant ->
|
||||
{:ok, participant}
|
||||
|
@ -756,6 +763,7 @@ defmodule Mobilizon.Events do
|
|||
case Participant
|
||||
|> where([p], event_id: ^event_id, actor_id: ^actor_id)
|
||||
|> where([p], fragment("? ->>'cancellation_token' = ?", p.metadata, ^cancellation_token))
|
||||
|> preload([p], ^@participant_preloads)
|
||||
|> Repo.one() do
|
||||
%Participant{} = participant ->
|
||||
{:ok, participant}
|
||||
|
@ -766,7 +774,9 @@ defmodule Mobilizon.Events do
|
|||
end
|
||||
|
||||
def get_participant(event_id, actor_id, %{}) do
|
||||
case Repo.get_by(Participant, event_id: event_id, actor_id: actor_id) do
|
||||
case Participant
|
||||
|> Repo.get_by(event_id: event_id, actor_id: actor_id)
|
||||
|> Repo.preload(@participant_preloads) do
|
||||
%Participant{} = participant ->
|
||||
{:ok, participant}
|
||||
|
||||
|
@ -779,7 +789,7 @@ defmodule Mobilizon.Events do
|
|||
def get_participant_by_confirmation_token(confirmation_token) do
|
||||
Participant
|
||||
|> where([p], fragment("? ->>'confirmation_token' = ?", p.metadata, ^confirmation_token))
|
||||
|> preload([p], [:actor, :event])
|
||||
|> preload([p], ^@participant_preloads)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -163,6 +163,7 @@ defmodule Mobilizon.Mixfile do
|
|||
{:web_push_encryption,
|
||||
git: "https://github.com/tcitworld/elixir-web-push-encryption", branch: "otp-24"},
|
||||
{:eblurhash, "~> 1.2"},
|
||||
{:struct_access, "~> 1.1.2"},
|
||||
# Dev and test dependencies
|
||||
{:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
|
||||
{:ex_machina, "~> 2.3", only: [:dev, :test]},
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -121,6 +121,7 @@
|
|||
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
|
||||
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"struct_access": {:hex, :struct_access, "1.1.2", "a42e6ceedd9b9ea090ee94a6da089d56e16f374dbbc010c3eebdf8be17df286f", [:mix], [], "hexpm", "e4c411dcc0226081b95709909551fc92b8feb1a3476108348ea7e3f6c12e586a"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
||||
"tesla": {:hex, :tesla, "1.4.1", "ff855f1cac121e0d16281b49e8f066c4a0d89965f98864515713878cca849ac8", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "95f5de35922c8c4b3945bee7406f66eb680b0955232f78f5fb7e853aa1ce201a"},
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Mobilizon.Storage.Repo.Migrations.PutUniqueIndexOnParticipantsUrls do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
create_if_not_exists(unique_index("participants", [:url]))
|
||||
end
|
||||
|
||||
def down do
|
||||
drop_if_exists(index("participants", [:url]))
|
||||
end
|
||||
end
|
319
test/federation/activity_pub/audience_test.exs
Normal file
319
test/federation/activity_pub/audience_test.exs
Normal file
|
@ -0,0 +1,319 @@
|
|||
defmodule Mobilizon.Federation.ActivityPub.AudienceTest do
|
||||
use Mobilizon.DataCase
|
||||
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Mention
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub.Audience
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
describe "get audience for an event created from a profile" do
|
||||
test "when the event is public" do
|
||||
%Event{} = event = insert(:event)
|
||||
event = Repo.preload(event, [:comments])
|
||||
|
||||
assert %{"cc" => [event.organizer_actor.followers_url], "to" => [@ap_public]} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
|
||||
test "when the event is unlisted" do
|
||||
%Event{} = event = insert(:event, visibility: :unlisted)
|
||||
event = Repo.preload(event, [:comments])
|
||||
|
||||
assert %{"cc" => [@ap_public], "to" => [event.organizer_actor.followers_url]} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
|
||||
test "when the event is unlisted and mentions some actors" do
|
||||
%Actor{id: mentionned_actor_id, url: mentionned_actor_url} =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Event{} = event = insert(:event, visibility: :unlisted)
|
||||
event = Repo.preload(event, [:comments])
|
||||
mentions = [%Mention{actor_id: mentionned_actor_id}]
|
||||
event = %Event{event | mentions: mentions}
|
||||
|
||||
assert %{
|
||||
"cc" => [@ap_public],
|
||||
"to" => [event.organizer_actor.followers_url, mentionned_actor_url]
|
||||
} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
|
||||
test "with interactions" do
|
||||
%Actor{} = interactor = insert(:actor)
|
||||
|
||||
%Event{} = event = insert(:event)
|
||||
|
||||
insert(:share, owner_actor: event.organizer_actor, actor: interactor, uri: event.url)
|
||||
|
||||
event = Repo.preload(event, [:comments])
|
||||
|
||||
assert %{
|
||||
"cc" => [event.organizer_actor.followers_url, interactor.url],
|
||||
"to" => [@ap_public]
|
||||
} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get audience for an event created from a group member" do
|
||||
test "when the event is public" do
|
||||
%Actor{} = actor = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Event{} = event = insert(:event, attributed_to: group, organizer_actor: actor)
|
||||
event = Repo.preload(event, [:comments])
|
||||
|
||||
assert %{
|
||||
"cc" => [members_url, followers_url],
|
||||
"to" => [@ap_public]
|
||||
} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
|
||||
test "when the event is unlisted" do
|
||||
%Actor{} = actor = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Event{} =
|
||||
event =
|
||||
insert(:event, visibility: :unlisted, attributed_to: group, organizer_actor: actor)
|
||||
|
||||
event = Repo.preload(event, [:comments])
|
||||
|
||||
assert %{
|
||||
"cc" => [@ap_public],
|
||||
"to" => [members_url, followers_url]
|
||||
} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
|
||||
test "when the event is unlisted and mentions some actors" do
|
||||
%Actor{id: mentionned_actor_id, url: mentionned_actor_url} =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Actor{} = actor = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@a_group")
|
||||
|
||||
%Event{} =
|
||||
event =
|
||||
insert(:event, visibility: :unlisted, attributed_to: group, organizer_actor: actor)
|
||||
|
||||
event = Repo.preload(event, [:comments])
|
||||
mentions = [%Mention{actor_id: mentionned_actor_id}]
|
||||
event = %Event{event | mentions: mentions}
|
||||
|
||||
assert %{
|
||||
"cc" => [@ap_public],
|
||||
"to" => [members_url, followers_url, mentionned_actor_url]
|
||||
} ==
|
||||
Audience.get_audience(event)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get audience for a post" do
|
||||
test "when it's public" do
|
||||
%Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
assert %{"to" => [@ap_public], "cc" => [members_url, followers_url]} ==
|
||||
Audience.get_audience(post)
|
||||
end
|
||||
|
||||
test "when it's unlisted" do
|
||||
%Actor{
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Post{} = post = insert(:post, attributed_to: group, visibility: :unlisted)
|
||||
|
||||
assert %{"to" => [members_url, followers_url], "cc" => [@ap_public]} ==
|
||||
Audience.get_audience(post)
|
||||
end
|
||||
|
||||
test "when it's private" do
|
||||
%Actor{
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Post{} = post = insert(:post, attributed_to: group, visibility: :private)
|
||||
|
||||
assert %{"to" => [members_url], "cc" => []} ==
|
||||
Audience.get_audience(post)
|
||||
end
|
||||
|
||||
test "when it's still a draft" do
|
||||
%Actor{
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Post{} = post = insert(:post, attributed_to: group, draft: true)
|
||||
|
||||
assert %{"to" => [members_url], "cc" => []} ==
|
||||
Audience.get_audience(post)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get audience for a discussion" do
|
||||
test "basic" do
|
||||
%Actor{
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Discussion{} = discussion = insert(:discussion, actor: group)
|
||||
|
||||
assert %{"to" => [members_url], "cc" => []} ==
|
||||
Audience.get_audience(discussion)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get audience for a comment" do
|
||||
test "basic" do
|
||||
%Actor{id: mentionned_actor_id, url: mentionned_actor_url} =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Comment{} = comment = insert(:comment)
|
||||
mentions = [%Mention{actor_id: mentionned_actor_id}]
|
||||
comment = %Comment{comment | mentions: mentions}
|
||||
|
||||
assert %{
|
||||
"cc" => [comment.actor.followers_url],
|
||||
"to" => [@ap_public, mentionned_actor_url, comment.event.organizer_actor.url]
|
||||
} ==
|
||||
Audience.get_audience(comment)
|
||||
end
|
||||
|
||||
test "in reply to other comments" do
|
||||
%Actor{id: mentionned_actor_id, url: mentionned_actor_url} =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Comment{} = original_comment = insert(:comment)
|
||||
|
||||
%Comment{} =
|
||||
reply_comment =
|
||||
insert(:comment, in_reply_to_comment: original_comment, origin_comment: original_comment)
|
||||
|
||||
%Comment{} =
|
||||
comment =
|
||||
insert(:comment, in_reply_to_comment: reply_comment, origin_comment: original_comment)
|
||||
|
||||
mentions = [%Mention{actor_id: mentionned_actor_id}]
|
||||
comment = %Comment{comment | mentions: mentions}
|
||||
|
||||
assert %{
|
||||
"cc" => [comment.actor.followers_url, original_comment.actor.url],
|
||||
"to" => [
|
||||
@ap_public,
|
||||
mentionned_actor_url,
|
||||
reply_comment.actor.url,
|
||||
comment.event.organizer_actor.url
|
||||
]
|
||||
} ==
|
||||
Audience.get_audience(comment)
|
||||
end
|
||||
|
||||
test "part of a discussion" do
|
||||
%Actor{
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Discussion{} = discussion = insert(:discussion, actor: group)
|
||||
%Comment{} = comment = insert(:comment, discussion: discussion)
|
||||
|
||||
assert %{"to" => [members_url], "cc" => []} ==
|
||||
Audience.get_audience(comment)
|
||||
end
|
||||
end
|
||||
|
||||
describe "participant" do
|
||||
test "basic" do
|
||||
%Event{} = event = insert(:event)
|
||||
%Participant{} = participant2 = insert(:participant, event: event)
|
||||
%Participant{} = participant = insert(:participant, event: event)
|
||||
|
||||
assert %{
|
||||
"to" => [participant.actor.url, participant.event.organizer_actor.url],
|
||||
"cc" => [participant2.actor.url]
|
||||
} == Audience.get_audience(participant)
|
||||
end
|
||||
|
||||
test "to a group event" do
|
||||
%Actor{} =
|
||||
group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
%Event{} = event = insert(:event, attributed_to: group)
|
||||
%Participant{} = participant2 = insert(:participant, event: event)
|
||||
%Participant{} = participant = insert(:participant, event: event)
|
||||
|
||||
assert %{
|
||||
"to" => [participant.actor.url, participant.event.attributed_to.url],
|
||||
"cc" => [group.followers_url, group.members_url, participant2.actor.url]
|
||||
} == Audience.get_audience(participant)
|
||||
end
|
||||
end
|
||||
|
||||
describe "member" do
|
||||
test "basic" do
|
||||
%Member{} = member = insert(:member)
|
||||
|
||||
assert %{"to" => [member.parent.url, member.parent.members_url], "cc" => []} ==
|
||||
Audience.get_audience(member)
|
||||
end
|
||||
end
|
||||
|
||||
describe "actor" do
|
||||
test "basic" do
|
||||
%Actor{followers_url: followers_url} = actor = insert(:actor)
|
||||
|
||||
assert %{"to" => [@ap_public], "cc" => [followers_url]} ==
|
||||
Audience.get_audience(actor)
|
||||
end
|
||||
|
||||
test "group" do
|
||||
%Actor{followers_url: followers_url, members_url: members_url, type: :Group} =
|
||||
group = insert(:group)
|
||||
|
||||
assert %{"to" => [@ap_public], "cc" => [members_url, followers_url]} ==
|
||||
Audience.get_audience(group)
|
||||
end
|
||||
|
||||
test "with interactions" do
|
||||
%Actor{followers_url: followers_url, members_url: members_url, type: :Group} =
|
||||
group = insert(:group)
|
||||
|
||||
%Actor{} = interactor = insert(:actor)
|
||||
|
||||
insert(:share, owner_actor: group, actor: interactor)
|
||||
|
||||
assert %{
|
||||
"to" => [@ap_public],
|
||||
"cc" => [members_url, followers_url, interactor.url]
|
||||
} ==
|
||||
Audience.get_audience(group)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,6 +64,46 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.JoinTest do
|
|||
# We don't accept already accepted Accept activities
|
||||
:error = Transmogrifier.handle_incoming(accept_data)
|
||||
end
|
||||
|
||||
test "it accepts Accept activities with an inline Join from same origin" do
|
||||
%Actor{} = organizer = insert(:actor)
|
||||
|
||||
%Actor{url: participant_actor_url} =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@participant")
|
||||
|
||||
%Actor{} =
|
||||
group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@group")
|
||||
|
||||
insert(:member, actor: organizer, parent: group, role: :moderator)
|
||||
|
||||
%Actor{} =
|
||||
actor_member_2 =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@member")
|
||||
|
||||
insert(:member, actor: actor_member_2, parent: group, role: :moderator)
|
||||
|
||||
%Event{url: event_url} =
|
||||
insert(:event, organizer_actor: organizer, join_options: :restricted, attributed_to: group)
|
||||
|
||||
join_data =
|
||||
File.read!("test/fixtures/mobilizon-join-activity.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("actor", participant_actor_url)
|
||||
|> Map.put("object", event_url)
|
||||
|> Map.put("participationMessage", @join_message)
|
||||
|> Map.put("id", "https://somewhere.else/@participant/join/event/1")
|
||||
|
||||
accept_data =
|
||||
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("actor", actor_member_2.url)
|
||||
|> Map.put("object", join_data)
|
||||
|
||||
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
|
||||
assert accept_activity.data["object"]["id"] == join_data["id"]
|
||||
assert accept_activity.data["object"]["id"] =~ "/join/"
|
||||
assert accept_activity.data["id"] =~ "/accept/join/"
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle incoming reject join activities" do
|
||||
|
|
440
test/federation/activity_pub/types/events_test.exs
Normal file
440
test/federation/activity_pub/types/events_test.exs
Normal file
|
@ -0,0 +1,440 @@
|
|||
defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
|
||||
use Mobilizon.DataCase
|
||||
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub.Activity
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Events
|
||||
|
||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
describe "test event creation" do
|
||||
@event_begins_on "2021-07-28T15:04:22Z"
|
||||
@event_title "hey"
|
||||
@event_data %{title: @event_title, begins_on: @event_begins_on}
|
||||
|
||||
test "from a simple profile" do
|
||||
%Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} =
|
||||
insert(:actor)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.create(
|
||||
Map.merge(@event_data, %{organizer_actor_id: organizer_actor_id}),
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_url,
|
||||
"attributedTo" => ^actor_url,
|
||||
"cc" => [^followers_url],
|
||||
"object" => %{
|
||||
"actor" => ^actor_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attachment" => [],
|
||||
"attributedTo" => ^actor_url,
|
||||
"category" => nil,
|
||||
"cc" => [],
|
||||
"commentsEnabled" => false,
|
||||
"content" => nil,
|
||||
"draft" => false,
|
||||
"endTime" => nil,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [@ap_public],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [@ap_public],
|
||||
"type" => "Create"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "an unlisted event" do
|
||||
%Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} =
|
||||
insert(:actor)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.create(
|
||||
Map.merge(@event_data, %{
|
||||
organizer_actor_id: organizer_actor_id,
|
||||
visibility: :unlisted
|
||||
}),
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_url,
|
||||
"attributedTo" => ^actor_url,
|
||||
"cc" => [@ap_public],
|
||||
"object" => %{
|
||||
"actor" => ^actor_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attachment" => [],
|
||||
"attributedTo" => ^actor_url,
|
||||
"category" => nil,
|
||||
"cc" => [@ap_public],
|
||||
"commentsEnabled" => false,
|
||||
"content" => nil,
|
||||
"draft" => false,
|
||||
"endTime" => nil,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [^followers_url],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [^followers_url],
|
||||
"type" => "Create"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "from a group member" do
|
||||
%Actor{id: organizer_actor_id, url: actor_url} = actor = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
id: attributed_to_id,
|
||||
url: group_url,
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
insert(:member, parent: group, actor: actor, role: :moderator)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.create(
|
||||
Map.merge(@event_data, %{
|
||||
organizer_actor_id: organizer_actor_id,
|
||||
attributed_to_id: attributed_to_id
|
||||
}),
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_url,
|
||||
"attributedTo" => ^group_url,
|
||||
"cc" => [^members_url, ^followers_url],
|
||||
"object" => %{
|
||||
"actor" => ^actor_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attachment" => [],
|
||||
"attributedTo" => ^group_url,
|
||||
"category" => nil,
|
||||
"cc" => [],
|
||||
"commentsEnabled" => false,
|
||||
"content" => nil,
|
||||
"draft" => false,
|
||||
"endTime" => nil,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [@ap_public],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [@ap_public],
|
||||
"type" => "Create"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@event_updated_title "my event updated"
|
||||
@event_update_data %{title: @event_updated_title}
|
||||
|
||||
describe "test event update" do
|
||||
test "from a simple profile" do
|
||||
%Actor{url: actor_url, followers_url: followers_url} = actor = insert(:actor)
|
||||
|
||||
{:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on)
|
||||
%Event{} = event = insert(:event, organizer_actor: actor, begins_on: begins_on)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.update(
|
||||
event,
|
||||
@event_update_data,
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_url,
|
||||
"attributedTo" => ^actor_url,
|
||||
"cc" => [^followers_url],
|
||||
"object" => %{
|
||||
"actor" => ^actor_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attributedTo" => ^actor_url,
|
||||
"cc" => [],
|
||||
"commentsEnabled" => false,
|
||||
"draft" => false,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_updated_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [@ap_public],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [@ap_public],
|
||||
"type" => "Update"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "from a group member" do
|
||||
%Actor{} = actor_1 = insert(:actor)
|
||||
%Actor{id: organizer_actor_2_id, url: actor_2_url} = actor_2 = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
url: group_url,
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
insert(:member, parent: group, actor: actor_1, role: :moderator)
|
||||
insert(:member, parent: group, actor: actor_2, role: :moderator)
|
||||
|
||||
{:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on)
|
||||
|
||||
%Event{} =
|
||||
event =
|
||||
insert(:event, organizer_actor: actor_1, begins_on: begins_on, attributed_to: group)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.update(
|
||||
event,
|
||||
Map.merge(@event_update_data, %{
|
||||
organizer_actor_id: organizer_actor_2_id
|
||||
}),
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_2_url,
|
||||
"attributedTo" => ^group_url,
|
||||
"cc" => [^members_url, ^followers_url],
|
||||
"object" => %{
|
||||
"actor" => ^actor_2_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attributedTo" => ^group_url,
|
||||
"cc" => [],
|
||||
"commentsEnabled" => false,
|
||||
"draft" => false,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_updated_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [@ap_public],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [@ap_public],
|
||||
"type" => "Update"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "from a remote group member" do
|
||||
%Actor{id: organizer_actor_1_id, url: actor_1_url} = actor_1 = insert(:actor)
|
||||
%Actor{} = actor_2 = insert(:actor)
|
||||
|
||||
%Actor{
|
||||
url: group_url,
|
||||
followers_url: followers_url,
|
||||
members_url: members_url
|
||||
} = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
insert(:member, parent: group, actor: actor_1, role: :moderator)
|
||||
insert(:member, parent: group, actor: actor_2, role: :moderator)
|
||||
|
||||
{:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on)
|
||||
|
||||
%Event{} =
|
||||
event =
|
||||
insert(:event, organizer_actor: actor_2, begins_on: begins_on, attributed_to: group)
|
||||
|
||||
assert {:ok, %Event{}, data} =
|
||||
Events.update(
|
||||
event,
|
||||
Map.merge(@event_update_data, %{
|
||||
organizer_actor_id: organizer_actor_1_id
|
||||
}),
|
||||
%{}
|
||||
)
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^actor_1_url,
|
||||
"attributedTo" => ^group_url,
|
||||
"cc" => [^members_url, ^followers_url],
|
||||
"object" => %{
|
||||
"actor" => ^actor_1_url,
|
||||
"anonymousParticipationEnabled" => false,
|
||||
"attributedTo" => ^group_url,
|
||||
"cc" => [],
|
||||
"commentsEnabled" => false,
|
||||
"draft" => false,
|
||||
"ical:status" => "CONFIRMED",
|
||||
"joinMode" => "free",
|
||||
"maximumAttendeeCapacity" => nil,
|
||||
"mediaType" => "text/html",
|
||||
"name" => @event_updated_title,
|
||||
"repliesModerationOption" => nil,
|
||||
"startTime" => @event_begins_on,
|
||||
"tag" => [],
|
||||
"to" => [@ap_public],
|
||||
"type" => "Event"
|
||||
},
|
||||
"to" => [@ap_public],
|
||||
"type" => "Update"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "join an event" do
|
||||
test "simple and remote" do
|
||||
%Actor{url: organizer_actor_url} =
|
||||
organizer_actor = insert(:actor, domain: "somewhere.else")
|
||||
|
||||
%Actor{url: participant_actor_url} = actor = insert(:actor, domain: nil)
|
||||
|
||||
%Event{url: event_url} =
|
||||
event = insert(:event, organizer_actor: organizer_actor, local: false)
|
||||
|
||||
assert {:ok, data, %Participant{}} = Events.join(event, actor, true, %{})
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^participant_actor_url,
|
||||
"cc" => [],
|
||||
"object" => ^event_url,
|
||||
"participationMessage" => nil,
|
||||
"to" => [^participant_actor_url, ^organizer_actor_url],
|
||||
"type" => "Join"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "simple and local" do
|
||||
%Actor{url: organizer_actor_url} = organizer_actor = insert(:actor, domain: nil)
|
||||
|
||||
%Actor{url: participant_actor_url} = actor = insert(:actor, domain: nil)
|
||||
|
||||
%Event{url: event_url} = event = insert(:event, organizer_actor: organizer_actor)
|
||||
|
||||
assert {:accept, {:ok, %Activity{data: data, local: true}, %Participant{}}} =
|
||||
Events.join(event, actor, true, %{})
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^organizer_actor_url,
|
||||
"cc" => [],
|
||||
"object" => %{
|
||||
"actor" => ^participant_actor_url,
|
||||
"object" => ^event_url,
|
||||
"participationMessage" => nil,
|
||||
"type" => "Join"
|
||||
},
|
||||
"to" => [^participant_actor_url, ^organizer_actor_url],
|
||||
"type" => "Accept"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "group event local" do
|
||||
%Actor{url: organizer_group_url, members_url: members_url, followers_url: followers_url} =
|
||||
organizer_group = insert(:group, domain: nil)
|
||||
|
||||
%Actor{url: participant_actor_url} = actor = insert(:actor, domain: nil)
|
||||
|
||||
%Event{url: event_url} = event = insert(:event, attributed_to: organizer_group)
|
||||
|
||||
assert {:ok, data, %Participant{}} = Events.join(event, actor, true, %{})
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^participant_actor_url,
|
||||
"cc" => [^followers_url, ^members_url],
|
||||
"object" => ^event_url,
|
||||
"participationMessage" => nil,
|
||||
"to" => [^participant_actor_url, ^organizer_group_url],
|
||||
"type" => "Join"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "group event with organizer remote" do
|
||||
%Actor{url: organizer_group_url, members_url: members_url, followers_url: followers_url} =
|
||||
organizer_group = insert(:group, domain: nil)
|
||||
|
||||
%Actor{url: organizer_actor_url} =
|
||||
organizer_actor =
|
||||
insert(:actor, domain: "somewhere.else", url: "https://somewhere.else/@someone")
|
||||
|
||||
insert(:member, parent: organizer_group, actor: organizer_actor, role: :moderator)
|
||||
|
||||
%Actor{url: participant_actor_url} = actor = insert(:actor, domain: nil)
|
||||
|
||||
%Event{url: event_url} =
|
||||
event =
|
||||
insert(:event,
|
||||
attributed_to: organizer_group,
|
||||
organizer_actor: organizer_actor,
|
||||
local: true
|
||||
)
|
||||
|
||||
assert {:ok, data, %Participant{}} = Events.join(event, actor, true, %{})
|
||||
|
||||
assert match?(
|
||||
%{
|
||||
"actor" => ^participant_actor_url,
|
||||
"cc" => [^followers_url, ^members_url],
|
||||
"object" => ^event_url,
|
||||
"participationMessage" => nil,
|
||||
"to" => [^participant_actor_url, ^organizer_group_url],
|
||||
"type" => "Join"
|
||||
},
|
||||
data
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,8 +5,11 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.{Events, Users}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Service.Workers
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.GraphQL.AbsintheHelpers
|
||||
|
||||
|
@ -21,14 +24,6 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
category: "meeting"
|
||||
}
|
||||
|
||||
setup %{conn: conn} do
|
||||
user = insert(:user)
|
||||
actor = insert(:actor, user: user, preferred_username: "test")
|
||||
|
||||
{:ok, conn: conn, actor: actor, user: user}
|
||||
end
|
||||
|
||||
describe "Event Resolver" do
|
||||
@find_event_query """
|
||||
query Event($uuid: UUID!) {
|
||||
event(uuid: $uuid) {
|
||||
|
@ -38,6 +33,14 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
}
|
||||
"""
|
||||
|
||||
setup %{conn: conn} do
|
||||
user = insert(:user)
|
||||
actor = insert(:actor, user: user, preferred_username: "test")
|
||||
|
||||
{:ok, conn: conn, actor: actor, user: user}
|
||||
end
|
||||
|
||||
describe "Find an event" do
|
||||
test "find_event/3 returns an event", context do
|
||||
event =
|
||||
@event
|
||||
|
@ -66,7 +69,9 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
|
||||
assert [%{"message" => "Event not found"}] = res["errors"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "create_event/3 for a regular profile" do
|
||||
@create_event_mutation """
|
||||
mutation CreateEvent(
|
||||
$title: String!,
|
||||
|
@ -76,6 +81,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
$status: EventStatus,
|
||||
$visibility: EventVisibility,
|
||||
$organizer_actor_id: ID!,
|
||||
$attributed_to_id: ID,
|
||||
$online_address: String,
|
||||
$options: EventOptionsInput,
|
||||
$draft: Boolean
|
||||
|
@ -88,6 +94,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
status: $status,
|
||||
visibility: $visibility,
|
||||
organizer_actor_id: $organizer_actor_id,
|
||||
attributed_to_id: $attributed_to_id,
|
||||
online_address: $online_address,
|
||||
options: $options,
|
||||
draft: $draft
|
||||
|
@ -629,30 +636,171 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
|
||||
assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"]
|
||||
end
|
||||
end
|
||||
|
||||
test "update_event/3 should check the event exists", %{conn: conn, actor: _actor, user: user} do
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: 45,
|
||||
title: "my event updated"
|
||||
describe "create_event/3 on behalf of a group" do
|
||||
@variables %{
|
||||
title: "come to my event",
|
||||
description: "it will be fine",
|
||||
begins_on: "2021-07-26T09:00:00Z"
|
||||
}
|
||||
|
||||
test "create_event/3 should check the member has permission to create a group event", %{
|
||||
conn: conn
|
||||
} do
|
||||
%User{} = user = insert(:user)
|
||||
%Actor{id: group_id} = group = insert(:group)
|
||||
|
||||
%Actor{id: member_not_approved_actor_id} =
|
||||
member_not_approved_actor = insert(:actor, user: user)
|
||||
|
||||
insert(:member, parent: group, actor: member_not_approved_actor)
|
||||
%Actor{id: member_actor_id} = member_actor = insert(:actor, user: user)
|
||||
insert(:member, parent: group, actor: member_actor, role: :member)
|
||||
%Actor{id: moderator_actor_id} = moderator_actor = insert(:actor, user: user)
|
||||
insert(:member, parent: group, actor: moderator_actor, role: :moderator)
|
||||
%Actor{id: not_member_actor_id} = insert(:actor, user: user)
|
||||
|
||||
variables = Map.put(@variables, :attributed_to_id, "#{group_id}")
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{member_not_approved_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["createEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{not_member_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["createEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{member_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["createEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"Organizer profile doesn't have permission to create an event on behalf of this group"
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @create_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{moderator_actor_id}")
|
||||
)
|
||||
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["createEvent"] != nil
|
||||
end
|
||||
end
|
||||
|
||||
@update_event_mutation """
|
||||
mutation updateEvent(
|
||||
$eventId: ID!
|
||||
$title: String
|
||||
$description: String
|
||||
$beginsOn: DateTime
|
||||
$endsOn: DateTime
|
||||
$status: EventStatus
|
||||
$visibility: EventVisibility
|
||||
$joinOptions: EventJoinOptions
|
||||
$draft: Boolean
|
||||
$tags: [String]
|
||||
$picture: MediaInput
|
||||
$onlineAddress: String
|
||||
$phoneAddress: String
|
||||
$organizerActorId: ID
|
||||
$attributedToId: ID
|
||||
$category: String
|
||||
$physicalAddress: AddressInput
|
||||
$options: EventOptionsInput
|
||||
$contacts: [Contact]
|
||||
) {
|
||||
title,
|
||||
updateEvent(
|
||||
eventId: $eventId
|
||||
title: $title
|
||||
description: $description
|
||||
beginsOn: $beginsOn
|
||||
endsOn: $endsOn
|
||||
status: $status
|
||||
visibility: $visibility
|
||||
joinOptions: $joinOptions
|
||||
draft: $draft
|
||||
tags: $tags
|
||||
picture: $picture
|
||||
onlineAddress: $onlineAddress
|
||||
phoneAddress: $phoneAddress
|
||||
organizerActorId: $organizerActorId
|
||||
attributedToId: $attributedToId
|
||||
category: $category
|
||||
physicalAddress: $physicalAddress
|
||||
options: $options
|
||||
contacts: $contacts
|
||||
) {
|
||||
id,
|
||||
uuid,
|
||||
url,
|
||||
title
|
||||
draft
|
||||
description
|
||||
beginsOn
|
||||
endsOn
|
||||
status
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
},
|
||||
online_address,
|
||||
phone_address,
|
||||
category,
|
||||
options {
|
||||
maximumAttendeeCapacity,
|
||||
showRemainingAttendeeCapacity
|
||||
},
|
||||
physicalAddress {
|
||||
url,
|
||||
geom,
|
||||
street
|
||||
}
|
||||
picture {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
describe "update_event/3" do
|
||||
test "update_event/3 should check the event exists", %{conn: conn, actor: _actor, user: user} do
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{eventId: 45, title: "my event updated"}
|
||||
)
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "Event not found"
|
||||
assert hd(res["errors"])["message"] == "Event not found"
|
||||
end
|
||||
|
||||
test "update_event/3 should check the user can change the organizer", %{
|
||||
|
@ -663,29 +811,15 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
event = insert(:event, organizer_actor: actor)
|
||||
actor2 = insert(:actor)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
title: "my event updated",
|
||||
event_id: #{event.id}
|
||||
organizer_actor_id: #{actor2.id}
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{eventId: event.id, title: "my event updated", organizerActorId: actor2.id}
|
||||
)
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"You can't attribute this event to this profile."
|
||||
end
|
||||
|
||||
|
@ -696,28 +830,15 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
} 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))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{eventId: event.id, title: "my event updated"}
|
||||
)
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "You can't edit this event."
|
||||
assert hd(res["errors"])["message"] == "You can't edit this event."
|
||||
end
|
||||
|
||||
test "update_event/3 should check the user is the organizer also when it's changed", %{
|
||||
|
@ -727,29 +848,15 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
} do
|
||||
event = insert(:event)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
title: "my event updated",
|
||||
event_id: #{event.id},
|
||||
organizer_actor_id: #{actor.id}
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{eventId: event.id, title: "my event updated", organizerActorId: actor.id}
|
||||
)
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "You can't edit this event."
|
||||
assert hd(res["errors"])["message"] == "You can't edit this event."
|
||||
end
|
||||
|
||||
test "update_event/3 should check end time is after the beginning time", %{
|
||||
|
@ -759,29 +866,19 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
} do
|
||||
event = insert(:event, organizer_actor: actor)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
title: "my event updated",
|
||||
ends_on: "#{Timex.shift(event.begins_on, hours: -2)}",
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
title: "my event updated",
|
||||
endsOn: event.begins_on |> DateTime.add(3600 * -2) |> DateTime.to_iso8601()
|
||||
}
|
||||
)
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
assert hd(res["errors"])["message"] ==
|
||||
["ends_on cannot be set before begins_on"]
|
||||
end
|
||||
|
||||
|
@ -799,69 +896,41 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
ends_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: #{event.id},
|
||||
title: "my event updated",
|
||||
description: "description updated",
|
||||
begins_on: "#{begins_on}",
|
||||
ends_on: "#{ends_on}",
|
||||
status: TENTATIVE,
|
||||
tags: ["tag1_updated", "tag2_updated"],
|
||||
online_address: "toto@example.com",
|
||||
phone_address: "0000000000",
|
||||
category: "birthday",
|
||||
options: {
|
||||
maximumAttendeeCapacity: 30,
|
||||
showRemainingAttendeeCapacity: true
|
||||
},
|
||||
physical_address: {
|
||||
street: "#{address.street}",
|
||||
locality: "#{address.locality}"
|
||||
}
|
||||
) {
|
||||
id,
|
||||
uuid,
|
||||
url,
|
||||
title,
|
||||
description,
|
||||
begins_on,
|
||||
ends_on,
|
||||
status,
|
||||
tags {
|
||||
title,
|
||||
slug
|
||||
},
|
||||
online_address,
|
||||
phone_address,
|
||||
category,
|
||||
options {
|
||||
maximumAttendeeCapacity,
|
||||
showRemainingAttendeeCapacity
|
||||
},
|
||||
physicalAddress {
|
||||
url,
|
||||
geom,
|
||||
street
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
title: "my event updated",
|
||||
description: "description updated",
|
||||
beginsOn: "#{begins_on}",
|
||||
endsOn: "#{ends_on}",
|
||||
status: "TENTATIVE",
|
||||
tags: ["tag1_updated", "tag2_updated"],
|
||||
onlineAddress: "toto@example.com",
|
||||
phoneAddress: "0000000000",
|
||||
category: "birthday",
|
||||
options: %{
|
||||
maximumAttendeeCapacity: 30,
|
||||
showRemainingAttendeeCapacity: true
|
||||
},
|
||||
physicalAddress: %{
|
||||
street: "#{address.street}",
|
||||
locality: "#{address.locality}"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert res["errors"] == nil
|
||||
|
||||
event_res = json_response(res, 200)["data"]["updateEvent"]
|
||||
event_res = res["data"]["updateEvent"]
|
||||
|
||||
assert event_res["title"] == "my event updated"
|
||||
assert event_res["description"] == "description updated"
|
||||
assert event_res["begins_on"] == "#{begins_on}"
|
||||
assert event_res["ends_on"] == "#{ends_on}"
|
||||
assert event_res["beginsOn"] == "#{begins_on}"
|
||||
assert event_res["endsOn"] == "#{ends_on}"
|
||||
assert event_res["status"] == "TENTATIVE"
|
||||
assert event_res["online_address"] == "toto@example.com"
|
||||
assert event_res["phone_address"] == "0000000000"
|
||||
|
@ -920,212 +989,166 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
|
||||
begins_on =
|
||||
event.begins_on
|
||||
|> Timex.shift(hours: 3)
|
||||
|> DateTime.add(3 * 3600)
|
||||
|> DateTime.truncate(:second)
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
title: "my event updated",
|
||||
description: "description updated",
|
||||
begins_on: "#{begins_on}",
|
||||
event_id: #{event.id},
|
||||
category: "birthday",
|
||||
picture: {
|
||||
media: {
|
||||
name: "picture for my event",
|
||||
alt: "A very sunny landscape",
|
||||
file: "event.jpg",
|
||||
actor_id: "#{actor.id}"
|
||||
}
|
||||
}
|
||||
) {
|
||||
title,
|
||||
uuid,
|
||||
url,
|
||||
beginsOn,
|
||||
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)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
title: "my event updated",
|
||||
description: "description updated",
|
||||
beginsOn: "#{begins_on}",
|
||||
category: "birthday",
|
||||
picture: %{
|
||||
media: %{
|
||||
name: "picture for my event",
|
||||
alt: "A very sunny landscape",
|
||||
file: "event.jpg",
|
||||
actorId: "#{actor.id}"
|
||||
}
|
||||
}
|
||||
},
|
||||
uploads: %{
|
||||
"event.jpg" => %Plug.Upload{
|
||||
path: "test/fixtures/picture.png",
|
||||
filename: "event.jpg"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
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 res["errors"] == nil
|
||||
assert res["data"]["updateEvent"]["title"] == "my event updated"
|
||||
assert res["data"]["updateEvent"]["uuid"] == event.uuid
|
||||
assert res["data"]["updateEvent"]["url"] == event.url
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["beginsOn"] ==
|
||||
DateTime.to_iso8601(event.begins_on |> Timex.shift(hours: 3))
|
||||
assert res["data"]["updateEvent"]["beginsOn"] ==
|
||||
event.begins_on |> DateTime.add(3 * 3600) |> DateTime.to_iso8601()
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["picture"]["name"] ==
|
||||
assert res["data"]["updateEvent"]["picture"]["name"] ==
|
||||
"picture for my event"
|
||||
end
|
||||
|
||||
test "update_event/3 respects the draft status", %{conn: conn, actor: actor, user: user} do
|
||||
@person_participations_query """
|
||||
query EventPersonParticipation($actorId: ID!, $eventId: ID!) {
|
||||
person(id: $actorId) {
|
||||
id
|
||||
participations(eventId: $eventId) {
|
||||
total
|
||||
elements {
|
||||
role
|
||||
actor {
|
||||
id
|
||||
}
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
test "respects the draft status", %{conn: conn, actor: actor, user: user} do
|
||||
event = insert(:event, organizer_actor: actor, draft: true)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: #{event.id},
|
||||
title: "my event updated but still draft"
|
||||
) {
|
||||
draft,
|
||||
title,
|
||||
uuid
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
title: "my event updated but still draft",
|
||||
draft: true
|
||||
}
|
||||
)
|
||||
|
||||
assert res["data"]["updateEvent"]["draft"] == true
|
||||
|
||||
res =
|
||||
conn
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @find_event_query,
|
||||
variables: %{
|
||||
uuid: event.uuid
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] =~ "not found"
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["draft"] == true
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @find_event_query,
|
||||
variables: %{
|
||||
uuid: event.uuid
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "not found"
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
}
|
||||
}
|
||||
"""
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["event"]["draft"] == true
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @person_participations_query,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
actorId: actor.id
|
||||
}
|
||||
)
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["event"]["draft"] == true
|
||||
|
||||
query = """
|
||||
{
|
||||
person(id: "#{actor.id}") {
|
||||
id,
|
||||
participations(eventId: #{event.id}) {
|
||||
elements {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["person"]["participations"]["elements"] == []
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["person"]["participations"]["elements"] == []
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
updateEvent(
|
||||
event_id: #{event.id},
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
title: "my event updated and no longer draft",
|
||||
draft: false
|
||||
) {
|
||||
draft,
|
||||
title,
|
||||
uuid
|
||||
}
|
||||
)
|
||||
|
||||
assert res["data"]["updateEvent"]["draft"] == false
|
||||
|
||||
res =
|
||||
conn
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @find_event_query,
|
||||
variables: %{
|
||||
uuid: event.uuid
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["event"]["draft"] == false
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["data"]["updateEvent"]["draft"] == false
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
uuid,
|
||||
draft
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @person_participations_query,
|
||||
variables: %{
|
||||
eventId: event.id,
|
||||
actorId: actor.id
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||
assert res["errors"] == nil
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["event"]["draft"] == false
|
||||
|
||||
query = """
|
||||
{
|
||||
person(id: "#{actor.id}") {
|
||||
id,
|
||||
participations(eventId: #{event.id}) {
|
||||
elements {
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
|
||||
assert json_response(res, 200)["data"]["person"]["participations"]["elements"] == [
|
||||
assert res["data"]["person"]["participations"]["elements"] == [
|
||||
%{
|
||||
"actor" => %{"id" => to_string(actor.id)},
|
||||
"event" => %{"id" => to_string(event.id)},
|
||||
|
@ -1133,7 +1156,98 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_event/3 on behalf of a group" do
|
||||
test "should check the member has permission to update a group event", %{
|
||||
conn: conn
|
||||
} do
|
||||
%User{} = user = insert(:user)
|
||||
%Actor{id: group_id} = group = insert(:group)
|
||||
|
||||
%Actor{id: member_not_approved_actor_id} =
|
||||
member_not_approved_actor = insert(:actor, user: user)
|
||||
|
||||
insert(:member, parent: group, actor: member_not_approved_actor)
|
||||
%Actor{id: member_actor_id} = member_actor = insert(:actor, user: user)
|
||||
insert(:member, parent: group, actor: member_actor, role: :member)
|
||||
%Actor{id: moderator_actor_id} = moderator_actor = insert(:actor, user: user)
|
||||
insert(:member, parent: group, actor: moderator_actor, role: :moderator)
|
||||
|
||||
%Actor{} = administrator_actor = insert(:actor, user: user)
|
||||
insert(:member, parent: group, actor: administrator_actor, role: :administrator)
|
||||
|
||||
%Actor{id: not_member_actor_id} = insert(:actor, user: user)
|
||||
|
||||
%Event{} =
|
||||
event = insert(:event, attributed_to: group, organizer_actor: administrator_actor)
|
||||
|
||||
variables =
|
||||
@variables
|
||||
|> Map.put(:attributed_to_id, "#{group_id}")
|
||||
|> Map.put(:eventId, to_string(event.id))
|
||||
|
||||
Users.update_user_default_actor(user.id, member_not_approved_actor_id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{member_not_approved_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["updateEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"This profile doesn't have permission to update an event on behalf of this group"
|
||||
|
||||
Users.update_user_default_actor(user.id, not_member_actor_id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{not_member_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["updateEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"This profile doesn't have permission to update an event on behalf of this group"
|
||||
|
||||
Users.update_user_default_actor(user.id, member_actor_id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{member_actor_id}")
|
||||
)
|
||||
|
||||
assert res["data"]["updateEvent"] == nil
|
||||
|
||||
assert hd(res["errors"])["message"] ==
|
||||
"This profile doesn't have permission to update an event on behalf of this group"
|
||||
|
||||
Users.update_user_default_actor(user.id, moderator_actor_id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> AbsintheHelpers.graphql_query(
|
||||
query: @update_event_mutation,
|
||||
variables: Map.put(variables, :organizer_actor_id, "#{moderator_actor_id}")
|
||||
)
|
||||
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["updateEvent"] != nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_events/3" do
|
||||
@fetch_events_query """
|
||||
query Events($page: Int, $limit: Int) {
|
||||
events(page: $page, limit: $limit) {
|
||||
|
@ -1250,7 +1364,9 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
# assert json_response(res, 200)["errors"] |> hd |> Map.get("message") ==
|
||||
# "Event with UUID #{event.uuid} not found"
|
||||
# end
|
||||
end
|
||||
|
||||
describe "delete_event/3" do
|
||||
test "delete_event/3 deletes an event", %{conn: conn, user: user, actor: actor} do
|
||||
event = insert(:event, organizer_actor: actor)
|
||||
|
||||
|
@ -1393,7 +1509,9 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||
"object" => %{"title" => event.title, "id" => to_string(event.id)}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_related_events/3" do
|
||||
test "list_related_events/3 should give related events", %{
|
||||
conn: conn,
|
||||
actor: actor
|
||||
|
|
|
@ -297,7 +297,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PostTest do
|
|||
}
|
||||
)
|
||||
|
||||
assert is_nil(res["errors"])
|
||||
assert res["errors"] == nil
|
||||
|
||||
assert res["data"]["post"]["title"] == post_draft.title
|
||||
assert res["data"]["post"]["draft"] == true
|
||||
|
|
|
@ -205,6 +205,19 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
|
|||
assert hd(res["data"]["searchEvents"]["elements"])["uuid"] ==
|
||||
event.uuid
|
||||
end
|
||||
|
||||
test "doesn't find drafts", %{conn: conn} do
|
||||
insert(:event, title: "A draft event", draft: true)
|
||||
|
||||
res =
|
||||
AbsintheHelpers.graphql_query(conn,
|
||||
query: @search_events_query,
|
||||
variables: %{term: "draft"}
|
||||
)
|
||||
|
||||
assert res["errors"] == nil
|
||||
assert res["data"]["searchEvents"]["total"] == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "search_persons/3" do
|
||||
|
|
|
@ -28,14 +28,24 @@ defmodule Mobilizon.GraphQL.AbsintheHelpers do
|
|||
@spec graphql_query(Conn.t(), Keyword.t()) :: map | no_return
|
||||
def graphql_query(conn, options) do
|
||||
conn
|
||||
|> post("/api", build_query(options[:query], Keyword.get(options, :variables, %{})))
|
||||
|> post(
|
||||
"/api",
|
||||
build_query(
|
||||
options[:query],
|
||||
Keyword.get(options, :variables, %{}),
|
||||
Keyword.get(options, :uploads, %{})
|
||||
)
|
||||
)
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
defp build_query(query, variables) do
|
||||
defp build_query(query, variables, uploads) do
|
||||
Map.merge(
|
||||
%{
|
||||
"query" => query,
|
||||
"variables" => variables
|
||||
}
|
||||
},
|
||||
uploads
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -158,6 +158,7 @@ defmodule Mobilizon.Factory do
|
|||
deleted_at: nil,
|
||||
tags: build_list(3, :tag),
|
||||
in_reply_to_comment: nil,
|
||||
origin_comment: nil,
|
||||
is_announcement: false,
|
||||
published_at: DateTime.utc_now(),
|
||||
url: Routes.page_url(Endpoint, :comment, uuid)
|
||||
|
@ -450,4 +451,12 @@ defmodule Mobilizon.Factory do
|
|||
user: build(:user)
|
||||
}
|
||||
end
|
||||
|
||||
def share_factory do
|
||||
%Mobilizon.Share{
|
||||
actor: build(:actor),
|
||||
owner_actor: build(:actor),
|
||||
uri: sequence("https://someshare.uri/p/12")
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue