forked from potsda.mn/mobilizon
Refactor Core things, including Ecto handling, ActivityPub & Transmogrifier modules
* Data doesn't need anymore to be converted to ActivityStream format to be saved (this was taken from Pleroma and not at all a good idea here) * Everything saved when creating an event is inserted into PostgreSQL in a single transaction
This commit is contained in:
parent
814cfbc8eb
commit
cc820d6b63
|
@ -14,7 +14,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
participation: {
|
participation: {
|
||||||
event: {
|
event: {
|
||||||
title: 'Vue Styleguidist first meetup: learn the basics!',
|
title: 'Vue Styleguidist first meetup: learn the basics!',
|
||||||
id: 5,
|
id: 5,
|
||||||
uuid: 'some uuid',
|
uuid: 'some uuid',
|
||||||
|
@ -31,7 +31,7 @@ export default {
|
||||||
},
|
},
|
||||||
participantStats: {
|
participantStats: {
|
||||||
approved: 1,
|
approved: 1,
|
||||||
unapproved: 2
|
notApproved: 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actor: {
|
actor: {
|
||||||
|
@ -75,20 +75,20 @@ export default {
|
||||||
</span>
|
</span>
|
||||||
<span class="column is-narrow participant-stats">
|
<span class="column is-narrow participant-stats">
|
||||||
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
|
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
|
||||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.participants, total: participation.event.options.maximumAttendeeCapacity }) }}
|
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.participant, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||||
<!-- <b-progress-->
|
<!-- <b-progress-->
|
||||||
<!-- v-if="participation.event.options.maximumAttendeeCapacity > 0"-->
|
<!-- v-if="participation.event.options.maximumAttendeeCapacity > 0"-->
|
||||||
<!-- size="is-medium"-->
|
<!-- size="is-medium"-->
|
||||||
<!-- :value="participation.event.participantStats.participants * 100 / participation.event.options.maximumAttendeeCapacity">-->
|
<!-- :value="participation.event.participantStats.participant * 100 / participation.event.options.maximumAttendeeCapacity">-->
|
||||||
<!-- </b-progress>-->
|
<!-- </b-progress>-->
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ $tc('{count} participants', participation.event.participantStats.participants, { count: participation.event.participantStats.participants })}}
|
{{ $tc('{count} participants', participation.event.participantStats.participant, { count: participation.event.participantStats.participant })}}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="participation.event.participantStats.unapproved > 0">
|
v-if="participation.event.participantStats.notApproved > 0">
|
||||||
<b-button type="is-text" @click="gotToWithCheck(participation, { name: RouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } })">
|
<b-button type="is-text" @click="gotToWithCheck(participation, { name: RouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } })">
|
||||||
{{ $tc('{count} requests waiting', participation.event.participantStats.unapproved, { count: participation.event.participantStats.unapproved })}}
|
{{ $tc('{count} requests waiting', participation.event.participantStats.notApproved, { count: participation.event.participantStats.notApproved })}}
|
||||||
</b-button>
|
</b-button>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -113,9 +113,8 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
notApproved
|
||||||
unapproved,
|
participant
|
||||||
participants
|
|
||||||
},
|
},
|
||||||
options {
|
options {
|
||||||
maximumAttendeeCapacity
|
maximumAttendeeCapacity
|
||||||
|
@ -161,8 +160,8 @@ export const LOGGED_USER_DRAFTS = gql`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved
|
notApproved
|
||||||
},
|
},
|
||||||
options {
|
options {
|
||||||
maximumAttendeeCapacity
|
maximumAttendeeCapacity
|
||||||
|
|
|
@ -102,9 +102,9 @@ export const FETCH_EVENT = gql`
|
||||||
# name,
|
# name,
|
||||||
# },
|
# },
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
participants
|
participant
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
|
@ -259,9 +259,9 @@ export const CREATE_EVENT = gql`
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
participants
|
participant
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
|
@ -344,9 +344,9 @@ export const EDIT_EVENT = gql`
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
participants
|
participant
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
|
@ -410,10 +410,10 @@ export const PARTICIPANTS = gql`
|
||||||
${participantQuery}
|
${participantQuery}
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
rejected,
|
rejected,
|
||||||
participants
|
participant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ import { IPerson } from '@/types/actor';
|
||||||
@Component
|
@Component
|
||||||
export default class EventMixin extends mixins(Vue) {
|
export default class EventMixin extends mixins(Vue) {
|
||||||
async openDeleteEventModal (event: IEvent, currentActor: IPerson) {
|
async openDeleteEventModal (event: IEvent, currentActor: IPerson) {
|
||||||
const participantsLength = event.participantStats.approved;
|
const participantsLength = event.participantStats.participant;
|
||||||
const prefix = participantsLength
|
const prefix = participantsLength
|
||||||
? this.$tc('There are {participants} participants.', event.participantStats.approved, {
|
? this.$tc('There are {participants} participants.', event.participantStats.participant, {
|
||||||
participants: event.participantStats.approved,
|
participants: event.participantStats.participant,
|
||||||
})
|
})
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
|
|
@ -94,10 +94,13 @@ export enum CommentModeration {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEventParticipantStats {
|
export interface IEventParticipantStats {
|
||||||
approved: number;
|
notApproved: number;
|
||||||
unapproved: number;
|
|
||||||
rejected: number;
|
rejected: number;
|
||||||
participants: number;
|
participant: number;
|
||||||
|
creator: number;
|
||||||
|
moderator: number;
|
||||||
|
administrator: number;
|
||||||
|
going: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEvent {
|
export interface IEvent {
|
||||||
|
@ -192,7 +195,7 @@ export class EventModel implements IEvent {
|
||||||
|
|
||||||
publishAt = new Date();
|
publishAt = new Date();
|
||||||
|
|
||||||
participantStats = { approved: 0, unapproved: 0, rejected: 0, participants: 0 };
|
participantStats = { notApproved: 0, rejected: 0, participant: 0, moderator: 0, administrator: 0, creator: 0, going: 0 };
|
||||||
participants: IParticipant[] = [];
|
participants: IParticipant[] = [];
|
||||||
|
|
||||||
relatedEvents: IEvent[] = [];
|
relatedEvents: IEvent[] = [];
|
||||||
|
|
|
@ -16,18 +16,18 @@ import {ParticipantRole} from "@/types/event.model";
|
||||||
<h1 class="title">{{ event.title }}</h1>
|
<h1 class="title">{{ event.title }}</h1>
|
||||||
<span>
|
<span>
|
||||||
<router-link v-if="actorIsOrganizer" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}">
|
<router-link v-if="actorIsOrganizer" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}">
|
||||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
<small v-if="event.participantStats.going > 0 && !actorIsParticipant">
|
||||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
{{ $tc('One person is going', event.participantStats.going, {approved: event.participantStats.going}) }}
|
||||||
</small>
|
</small>
|
||||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
|
<small v-else-if="event.participantStats.going > 0 && actorIsParticipant">
|
||||||
{{ $tc('You and one other person are going to this event', event.participantStats.participants, { approved: event.participantStats.participants }) }}
|
{{ $tc('You and one other person are going to this event', event.participantStats.participant, { approved: event.participantStats.participant }) }}
|
||||||
</small>
|
</small>
|
||||||
</router-link>
|
</router-link>
|
||||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant && !actorIsOrganizer">
|
<small v-if="event.participantStats.going > 0 && !actorIsParticipant && !actorIsOrganizer">
|
||||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
{{ $tc('One person is going', event.participantStats.going, {approved: event.participantStats.going}) }}
|
||||||
</small>
|
</small>
|
||||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant && !actorIsOrganizer">
|
<small v-else-if="event.participantStats.going > 0 && actorIsParticipant && !actorIsOrganizer">
|
||||||
{{ $tc('You and one other person are going to this event', event.participantStats.participants, { approved: event.participantStats.participants }) }}
|
{{ $tc('You and one other person are going to this event', event.participantStats.participant, { approved: event.participantStats.participant }) }}
|
||||||
</small>
|
</small>
|
||||||
<small v-if="event.options.maximumAttendeeCapacity">
|
<small v-if="event.options.maximumAttendeeCapacity">
|
||||||
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
|
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
|
||||||
|
@ -443,10 +443,10 @@ export default class Event extends EventMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.joinEvent.role === ParticipantRole.NOT_APPROVED) {
|
if (data.joinEvent.role === ParticipantRole.NOT_APPROVED) {
|
||||||
event.participantStats.unapproved = event.participantStats.unapproved + 1;
|
event.participantStats.notApproved = event.participantStats.notApproved + 1;
|
||||||
} else {
|
} else {
|
||||||
event.participantStats.approved = event.participantStats.approved + 1;
|
event.participantStats.going = event.participantStats.going + 1;
|
||||||
event.participantStats.participants = event.participantStats.participants + 1;
|
event.participantStats.participant = event.participantStats.participant + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||||
|
@ -514,10 +514,10 @@ export default class Event extends EventMixin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (participation.role === ParticipantRole.NOT_APPROVED) {
|
if (participation.role === ParticipantRole.NOT_APPROVED) {
|
||||||
event.participantStats.unapproved = event.participantStats.unapproved - 1;
|
event.participantStats.notApproved = event.participantStats.notApproved - 1;
|
||||||
} else {
|
} else {
|
||||||
event.participantStats.approved = event.participantStats.approved - 1;
|
event.participantStats.going = event.participantStats.going - 1;
|
||||||
event.participantStats.participants = event.participantStats.participants - 1;
|
event.participantStats.participant = event.participantStats.participant - 1;
|
||||||
}
|
}
|
||||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||||
},
|
},
|
||||||
|
@ -591,11 +591,11 @@ export default class Event extends EventMixin {
|
||||||
|
|
||||||
get eventCapacityOK(): boolean {
|
get eventCapacityOK(): boolean {
|
||||||
if (!this.event.options.maximumAttendeeCapacity) return true;
|
if (!this.event.options.maximumAttendeeCapacity) return true;
|
||||||
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participants;
|
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participant;
|
||||||
}
|
}
|
||||||
|
|
||||||
get numberOfPlacesStillAvailable(): number {
|
get numberOfPlacesStillAvailable(): number {
|
||||||
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participants;
|
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participant;
|
||||||
}
|
}
|
||||||
|
|
||||||
urlToHostname(url: string): string|null {
|
urlToHostname(url: string): string|null {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<b-tab-item>
|
<b-tab-item>
|
||||||
<template slot="header">
|
<template slot="header">
|
||||||
<b-icon icon="account-multiple"></b-icon>
|
<b-icon icon="account-multiple"></b-icon>
|
||||||
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
|
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.going }} </b-tag> </span>
|
||||||
</template>
|
</template>
|
||||||
<template>
|
<template>
|
||||||
<section v-if="participantsAndCreators.length > 0">
|
<section v-if="participantsAndCreators.length > 0">
|
||||||
|
@ -22,10 +22,10 @@
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</b-tab-item>
|
</b-tab-item>
|
||||||
<b-tab-item :disabled="participantStats.unapproved === 0">
|
<b-tab-item :disabled="participantStats.notApproved === 0">
|
||||||
<template slot="header">
|
<template slot="header">
|
||||||
<b-icon icon="account-multiple-plus"></b-icon>
|
<b-icon icon="account-multiple-plus"></b-icon>
|
||||||
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
|
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.notApproved }} </b-tag> </span>
|
||||||
</template>
|
</template>
|
||||||
<template>
|
<template>
|
||||||
<section v-if="queue.length > 0">
|
<section v-if="queue.length > 0">
|
||||||
|
@ -182,7 +182,7 @@ export default class Participants extends Vue {
|
||||||
@Watch('participantStats', { deep: true })
|
@Watch('participantStats', { deep: true })
|
||||||
watchParticipantStats(stats: IEventParticipantStats) {
|
watchParticipantStats(stats: IEventParticipantStats) {
|
||||||
if (!stats) return;
|
if (!stats) return;
|
||||||
if ((stats.unapproved === 0 && this.activeTab === 1) || stats.rejected === 0 && this.activeTab === 2 ) {
|
if ((stats.notApproved === 0 && this.activeTab === 1) || stats.rejected === 0 && this.activeTab === 2 ) {
|
||||||
this.activeTab = 0;
|
this.activeTab = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,9 @@ export default class Participants extends Vue {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
||||||
this.rejected = this.rejected.filter(participant => participant.id !== data.updateParticipation.id);
|
this.rejected = this.rejected.filter(participant => participant.id !== data.updateParticipation.id);
|
||||||
this.event.participantStats.approved += 1;
|
this.event.participantStats.going += 1;
|
||||||
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
||||||
this.event.participantStats.unapproved -= 1;
|
this.event.participantStats.notApproved -= 1;
|
||||||
}
|
}
|
||||||
if (participant.role === ParticipantRole.REJECTED) {
|
if (participant.role === ParticipantRole.REJECTED) {
|
||||||
this.event.participantStats.rejected -= 1;
|
this.event.participantStats.rejected -= 1;
|
||||||
|
@ -253,11 +253,11 @@ export default class Participants extends Vue {
|
||||||
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
||||||
this.event.participantStats.rejected += 1;
|
this.event.participantStats.rejected += 1;
|
||||||
if (participant.role === ParticipantRole.PARTICIPANT) {
|
if (participant.role === ParticipantRole.PARTICIPANT) {
|
||||||
this.event.participantStats.participants -= 1;
|
this.event.participantStats.participant -= 1;
|
||||||
this.event.participantStats.approved -= 1;
|
this.event.participantStats.going -= 1;
|
||||||
}
|
}
|
||||||
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
||||||
this.event.participantStats.unapproved -= 1;
|
this.event.participantStats.notApproved -= 1;
|
||||||
}
|
}
|
||||||
participant.role = ParticipantRole.REJECTED;
|
participant.role = ParticipantRole.REJECTED;
|
||||||
this.rejected = this.rejected.filter(participantIn => participantIn.id !== participant.id);
|
this.rejected = this.rejected.filter(participantIn => participantIn.id !== participant.id);
|
||||||
|
|
67
lib/mix/tasks/mobilizon/move_participant_stats.ex
Normal file
67
lib/mix/tasks/mobilizon/move_participant_stats.ex
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
defmodule Mix.Tasks.Mobilizon.MoveParticipantStats do
|
||||||
|
@moduledoc """
|
||||||
|
Temporary task to move participant stats in the events table
|
||||||
|
|
||||||
|
This task will be removed in version 1.0.0-beta.3
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
alias Mobilizon.Events
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
alias Mobilizon.Events.ParticipantRole
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@shortdoc "Move participant stats to events table"
|
||||||
|
def run([]) do
|
||||||
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
|
events =
|
||||||
|
Event
|
||||||
|
|> preload([e], :tags)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
nb_events = length(events)
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"\nStarting inserting participants stats into #{nb_events} events, this can take a while…\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
insert_participants_stats_into_events(events, nb_events)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp insert_participants_stats_into_events([%Event{url: url} = event | events], nb_events) do
|
||||||
|
with roles <- ParticipantRole.__enum_map__(),
|
||||||
|
counts <-
|
||||||
|
Enum.reduce(roles, %{}, fn role, acc ->
|
||||||
|
Map.put(acc, role, count_participants(event, role))
|
||||||
|
end),
|
||||||
|
{:ok, _} <-
|
||||||
|
Events.update_event(event, %{
|
||||||
|
participant_stats: counts
|
||||||
|
}) do
|
||||||
|
Logger.debug("Added participants stats to event #{url}")
|
||||||
|
else
|
||||||
|
{:error, res} ->
|
||||||
|
Logger.error("Error while adding participants stats to event #{url} : #{inspect(res)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
ProgressBar.render(nb_events - length(events), nb_events)
|
||||||
|
|
||||||
|
insert_participants_stats_into_events(events, nb_events)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp insert_participants_stats_into_events([], nb_events) do
|
||||||
|
IO.puts("\nFinished inserting participant stats for #{nb_events} events!\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp count_participants(%Event{id: event_id}, role) when is_atom(role) do
|
||||||
|
event_id
|
||||||
|
|> Events.count_participants_query()
|
||||||
|
|> Events.filter_role(role)
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
end
|
49
lib/mix/tasks/mobilizon/setup_search.ex
Normal file
49
lib/mix/tasks/mobilizon/setup_search.ex
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule Mix.Tasks.Mobilizon.SetupSearch do
|
||||||
|
@moduledoc """
|
||||||
|
Temporary task to insert search data from existing events
|
||||||
|
|
||||||
|
This task will be removed in version 1.0.0-beta.3
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Mobilizon.Service.Search
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@shortdoc "Insert search data"
|
||||||
|
def run([]) do
|
||||||
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
|
events =
|
||||||
|
Event
|
||||||
|
|> preload([e], :tags)
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
nb_events = length(events)
|
||||||
|
|
||||||
|
IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n")
|
||||||
|
insert_search_event(events, nb_events)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp insert_search_event([%Event{url: url} = event | events], nb_events) do
|
||||||
|
case Search.insert_search_event(event) do
|
||||||
|
{:ok, _} ->
|
||||||
|
Logger.debug("Added event #{url} to the search")
|
||||||
|
|
||||||
|
{:error, res} ->
|
||||||
|
Logger.error("Error while adding event #{url} to the search: #{inspect(res)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
ProgressBar.render(nb_events - length(events), nb_events)
|
||||||
|
|
||||||
|
insert_search_event(events, nb_events)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp insert_search_event([], nb_events) do
|
||||||
|
IO.puts("\nFinished setting up search for #{nb_events} events!\n")
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,18 +5,20 @@ defmodule Mix.Tasks.Mobilizon.Toot do
|
||||||
|
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
|
|
||||||
alias MobilizonWeb.API
|
alias MobilizonWeb.API.Comments
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@shortdoc "Toot to an user"
|
@shortdoc "Toot to an user"
|
||||||
def run([from, content]) do
|
def run([from, text]) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
case API.Comments.create_comment(from, content) do
|
with {:local_actor, %Actor{} = actor} <- {:local_actor, Actors.get_local_actor_by_name(from)},
|
||||||
{:ok, _, _} ->
|
{:ok, _, _} <- Comments.create_comment(%{actor: actor, text: text}) do
|
||||||
Mix.shell().info("Tooted")
|
Mix.shell().info("Tooted")
|
||||||
|
else
|
||||||
{:local_actor, _, _} ->
|
{:local_actor, _, _} ->
|
||||||
Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
|
Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
alias Mobilizon.Media.File
|
alias Mobilizon.Media.File
|
||||||
alias Mobilizon.Reports.{Note, Report}
|
alias Mobilizon.Reports.{Note, Report}
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
alias Mobilizon.Mention
|
||||||
|
|
||||||
alias MobilizonWeb.Endpoint
|
alias MobilizonWeb.Endpoint
|
||||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
@ -46,6 +47,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
created_reports: [Report.t()],
|
created_reports: [Report.t()],
|
||||||
subject_reports: [Report.t()],
|
subject_reports: [Report.t()],
|
||||||
report_notes: [Note.t()],
|
report_notes: [Note.t()],
|
||||||
|
mentions: [Mention.t()],
|
||||||
memberships: [t]
|
memberships: [t]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +141,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
||||||
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
||||||
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
||||||
|
has_many(:mentions, Mention)
|
||||||
many_to_many(:memberships, __MODULE__, join_through: Member)
|
many_to_many(:memberships, __MODULE__, join_through: Member)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
|
|
@ -88,7 +88,10 @@ defmodule Mobilizon.Actors do
|
||||||
"""
|
"""
|
||||||
@spec get_actor_by_url(String.t(), boolean) ::
|
@spec get_actor_by_url(String.t(), boolean) ::
|
||||||
{:ok, Actor.t()} | {:error, :actor_not_found}
|
{:ok, Actor.t()} | {:error, :actor_not_found}
|
||||||
def get_actor_by_url(url, preload \\ false) do
|
def get_actor_by_url(url, preload \\ false)
|
||||||
|
def get_actor_by_url(nil, _preload), do: {:error, :actor_not_found}
|
||||||
|
|
||||||
|
def get_actor_by_url(url, preload) do
|
||||||
case Repo.get_by(Actor, url: url) do
|
case Repo.get_by(Actor, url: url) do
|
||||||
nil ->
|
nil ->
|
||||||
{:error, :actor_not_found}
|
{:error, :actor_not_found}
|
||||||
|
|
|
@ -65,8 +65,7 @@ defmodule Mobilizon.Addresses.Address do
|
||||||
|
|
||||||
@spec set_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
@spec set_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||||
defp set_url(%Ecto.Changeset{changes: changes} = changeset) do
|
defp set_url(%Ecto.Changeset{changes: changes} = changeset) do
|
||||||
uuid = Ecto.UUID.generate()
|
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{Ecto.UUID.generate()}")
|
||||||
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{uuid}")
|
|
||||||
|
|
||||||
put_change(changeset, :url, url)
|
put_change(changeset, :url, url)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,8 @@ defmodule Mobilizon.Events.Comment do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.{Comment, CommentVisibility, Event}
|
alias Mobilizon.Events.{Comment, CommentVisibility, Event, Tag}
|
||||||
|
alias Mobilizon.Mention
|
||||||
|
|
||||||
alias MobilizonWeb.Endpoint
|
alias MobilizonWeb.Endpoint
|
||||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
@ -22,6 +23,8 @@ defmodule Mobilizon.Events.Comment do
|
||||||
actor: Actor.t(),
|
actor: Actor.t(),
|
||||||
attributed_to: Actor.t(),
|
attributed_to: Actor.t(),
|
||||||
event: Event.t(),
|
event: Event.t(),
|
||||||
|
tags: [Tag.t()],
|
||||||
|
mentions: [Mention.t()],
|
||||||
in_reply_to_comment: t,
|
in_reply_to_comment: t,
|
||||||
origin_comment: t
|
origin_comment: t
|
||||||
}
|
}
|
||||||
|
@ -42,6 +45,8 @@ defmodule Mobilizon.Events.Comment do
|
||||||
belongs_to(:event, Event, foreign_key: :event_id)
|
belongs_to(:event, Event, foreign_key: :event_id)
|
||||||
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
||||||
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
||||||
|
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||||
|
has_many(:mentions, Mention)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
@ -57,16 +62,45 @@ defmodule Mobilizon.Events.Comment do
|
||||||
@doc false
|
@doc false
|
||||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||||
def changeset(%__MODULE__{} = comment, attrs) do
|
def changeset(%__MODULE__{} = comment, attrs) do
|
||||||
uuid = attrs["uuid"] || Ecto.UUID.generate()
|
uuid = Map.get(attrs, :uuid) || Ecto.UUID.generate()
|
||||||
url = attrs["url"] || generate_url(uuid)
|
url = Map.get(attrs, :url) || generate_url(uuid)
|
||||||
|
|
||||||
comment
|
comment
|
||||||
|> cast(attrs, @attrs)
|
|> cast(attrs, @attrs)
|
||||||
|> put_change(:uuid, uuid)
|
|> put_change(:uuid, uuid)
|
||||||
|> put_change(:url, url)
|
|> put_change(:url, url)
|
||||||
|
|> put_tags(attrs)
|
||||||
|
|> put_mentions(attrs)
|
||||||
|> validate_required(@required_attrs)
|
|> validate_required(@required_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec generate_url(String.t()) :: String.t()
|
@spec generate_url(String.t()) :: String.t()
|
||||||
defp generate_url(uuid), do: Routes.page_url(Endpoint, :comment, uuid)
|
defp generate_url(uuid), do: Routes.page_url(Endpoint, :comment, uuid)
|
||||||
|
|
||||||
|
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
defp put_tags(changeset, %{"tags" => tags}),
|
||||||
|
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||||
|
|
||||||
|
defp put_tags(changeset, %{tags: tags}),
|
||||||
|
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||||
|
|
||||||
|
defp put_tags(changeset, _), do: changeset
|
||||||
|
|
||||||
|
@spec put_mentions(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
defp put_mentions(changeset, %{"mentions" => mentions}),
|
||||||
|
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
|
||||||
|
|
||||||
|
defp put_mentions(changeset, %{mentions: mentions}),
|
||||||
|
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
|
||||||
|
|
||||||
|
defp put_mentions(changeset, _), do: changeset
|
||||||
|
|
||||||
|
# We need a changeset instead of a raw struct because of slug which is generated in changeset
|
||||||
|
defp process_tag(tag) do
|
||||||
|
Tag.changeset(%Tag{}, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp process_mention(tag) do
|
||||||
|
Mention.changeset(%Mention{}, tag)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,22 +6,31 @@ defmodule Mobilizon.Events.Event do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias Ecto.Changeset
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
|
|
||||||
|
alias Mobilizon.Addresses
|
||||||
|
|
||||||
alias Mobilizon.Events.{
|
alias Mobilizon.Events.{
|
||||||
EventOptions,
|
EventOptions,
|
||||||
EventStatus,
|
EventStatus,
|
||||||
EventVisibility,
|
EventVisibility,
|
||||||
JoinOptions,
|
JoinOptions,
|
||||||
|
EventParticipantStats,
|
||||||
Participant,
|
Participant,
|
||||||
Session,
|
Session,
|
||||||
Tag,
|
Tag,
|
||||||
Track
|
Track
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alias Mobilizon.Media
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Media.Picture
|
||||||
|
alias Mobilizon.Mention
|
||||||
|
|
||||||
|
alias MobilizonWeb.Endpoint
|
||||||
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
url: String.t(),
|
url: String.t(),
|
||||||
|
@ -47,30 +56,15 @@ defmodule Mobilizon.Events.Event do
|
||||||
picture: Picture.t(),
|
picture: Picture.t(),
|
||||||
tracks: [Track.t()],
|
tracks: [Track.t()],
|
||||||
sessions: [Session.t()],
|
sessions: [Session.t()],
|
||||||
|
mentions: [Mention.t()],
|
||||||
tags: [Tag.t()],
|
tags: [Tag.t()],
|
||||||
participants: [Actor.t()]
|
participants: [Actor.t()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@required_attrs [:title, :begins_on, :organizer_actor_id, :url, :uuid]
|
@update_required_attrs [:title, :begins_on, :organizer_actor_id]
|
||||||
|
@required_attrs @update_required_attrs ++ [:url, :uuid]
|
||||||
|
|
||||||
@optional_attrs [
|
@optional_attrs [
|
||||||
:slug,
|
|
||||||
:description,
|
|
||||||
:ends_on,
|
|
||||||
:category,
|
|
||||||
:status,
|
|
||||||
:draft,
|
|
||||||
:visibility,
|
|
||||||
:publish_at,
|
|
||||||
:online_address,
|
|
||||||
:phone_address,
|
|
||||||
:picture_id,
|
|
||||||
:physical_address_id
|
|
||||||
]
|
|
||||||
@attrs @required_attrs ++ @optional_attrs
|
|
||||||
|
|
||||||
@update_required_attrs @required_attrs
|
|
||||||
|
|
||||||
@update_optional_attrs [
|
|
||||||
:slug,
|
:slug,
|
||||||
:description,
|
:description,
|
||||||
:ends_on,
|
:ends_on,
|
||||||
|
@ -85,7 +79,9 @@ defmodule Mobilizon.Events.Event do
|
||||||
:picture_id,
|
:picture_id,
|
||||||
:physical_address_id
|
:physical_address_id
|
||||||
]
|
]
|
||||||
@update_attrs @update_required_attrs ++ @update_optional_attrs
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
|
@update_attrs @update_required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
schema "events" do
|
schema "events" do
|
||||||
field(:url, :string)
|
field(:url, :string)
|
||||||
|
@ -105,13 +101,15 @@ defmodule Mobilizon.Events.Event do
|
||||||
field(:phone_address, :string)
|
field(:phone_address, :string)
|
||||||
field(:category, :string)
|
field(:category, :string)
|
||||||
|
|
||||||
embeds_one(:options, EventOptions, on_replace: :update)
|
embeds_one(:options, EventOptions, on_replace: :delete)
|
||||||
|
embeds_one(:participant_stats, EventParticipantStats, on_replace: :update)
|
||||||
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
|
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
|
||||||
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
|
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
|
||||||
belongs_to(:physical_address, Address)
|
belongs_to(:physical_address, Address, on_replace: :update)
|
||||||
belongs_to(:picture, Picture)
|
belongs_to(:picture, Picture, on_replace: :update)
|
||||||
has_many(:tracks, Track)
|
has_many(:tracks, Track)
|
||||||
has_many(:sessions, Session)
|
has_many(:sessions, Session)
|
||||||
|
has_many(:mentions, Mention)
|
||||||
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
||||||
many_to_many(:participants, Actor, join_through: Participant)
|
many_to_many(:participants, Actor, join_through: Participant)
|
||||||
|
|
||||||
|
@ -119,28 +117,41 @@ defmodule Mobilizon.Events.Event do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
@spec changeset(t, map) :: Changeset.t()
|
||||||
def changeset(%__MODULE__{} = event, attrs) do
|
def changeset(%__MODULE__{} = event, attrs) do
|
||||||
|
attrs = Map.update(attrs, :uuid, Ecto.UUID.generate(), & &1)
|
||||||
|
attrs = Map.update(attrs, :url, Routes.page_url(Endpoint, :event, attrs.uuid), & &1)
|
||||||
|
|
||||||
event
|
event
|
||||||
|> cast(attrs, @attrs)
|
|> cast(attrs, @attrs)
|
||||||
|> cast_embed(:options)
|
|> common_changeset(attrs)
|
||||||
|
|> put_creator_if_published(:create)
|
||||||
|> validate_required(@required_attrs)
|
|> validate_required(@required_attrs)
|
||||||
|> validate_lengths()
|
|> validate_lengths()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec update_changeset(t, map) :: Ecto.Changeset.t()
|
@spec update_changeset(t, map) :: Changeset.t()
|
||||||
def update_changeset(%__MODULE__{} = event, attrs) do
|
def update_changeset(%__MODULE__{} = event, attrs) do
|
||||||
event
|
event
|
||||||
|> Ecto.Changeset.cast(attrs, @update_attrs)
|
|> cast(attrs, @update_attrs)
|
||||||
|> cast_embed(:options)
|
|> common_changeset(attrs)
|
||||||
|> put_tags(attrs)
|
|> put_creator_if_published(:update)
|
||||||
|> validate_required(@update_required_attrs)
|
|> validate_required(@update_required_attrs)
|
||||||
|> validate_lengths()
|
|> validate_lengths()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec validate_lengths(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
@spec common_changeset(Changeset.t(), map) :: Changeset.t()
|
||||||
defp validate_lengths(%Ecto.Changeset{} = changeset) do
|
defp common_changeset(%Changeset{} = changeset, attrs) do
|
||||||
|
changeset
|
||||||
|
|> cast_embed(:options)
|
||||||
|
|> put_tags(attrs)
|
||||||
|
|> put_address(attrs)
|
||||||
|
|> put_picture(attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec validate_lengths(Changeset.t()) :: Changeset.t()
|
||||||
|
defp validate_lengths(%Changeset{} = changeset) do
|
||||||
changeset
|
changeset
|
||||||
|> validate_length(:title, min: 3, max: 200)
|
|> validate_length(:title, min: 3, max: 200)
|
||||||
|> validate_length(:online_address, min: 3, max: 2000)
|
|> validate_length(:online_address, min: 3, max: 2000)
|
||||||
|
@ -161,7 +172,80 @@ defmodule Mobilizon.Events.Event do
|
||||||
|
|
||||||
def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
|
def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
|
||||||
|
|
||||||
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
@spec put_tags(Changeset.t(), map) :: Changeset.t()
|
||||||
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags)
|
defp put_tags(%Changeset{} = changeset, %{tags: tags}),
|
||||||
defp put_tags(changeset, _), do: changeset
|
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||||
|
|
||||||
|
defp put_tags(%Changeset{} = changeset, _), do: changeset
|
||||||
|
|
||||||
|
# We need a changeset instead of a raw struct because of slug which is generated in changeset
|
||||||
|
defp process_tag(tag) do
|
||||||
|
Tag.changeset(%Tag{}, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case the provided addresses is an existing one
|
||||||
|
@spec put_address(Changeset.t(), map) :: Changeset.t()
|
||||||
|
defp put_address(%Changeset{} = changeset, %{physical_address: %{id: id} = _physical_address}) do
|
||||||
|
case Addresses.get_address!(id) do
|
||||||
|
%Address{} = address ->
|
||||||
|
put_assoc(changeset, :physical_address, address)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case it's a new address
|
||||||
|
defp put_address(%Changeset{} = changeset, _attrs) do
|
||||||
|
cast_assoc(changeset, :physical_address)
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case the provided picture is an existing one
|
||||||
|
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
||||||
|
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
|
||||||
|
case Media.get_picture!(id) do
|
||||||
|
%Picture{} = picture ->
|
||||||
|
put_assoc(changeset, :picture, picture)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# In case it's a new picture
|
||||||
|
defp put_picture(%Changeset{} = changeset, _attrs) do
|
||||||
|
cast_assoc(changeset, :picture)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Created or updated with draft parameter: don't publish
|
||||||
|
defp put_creator_if_published(
|
||||||
|
%Changeset{changes: %{draft: true}} = changeset,
|
||||||
|
_action
|
||||||
|
) do
|
||||||
|
cast_embed(changeset, :participant_stats)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Created with any other value: publish
|
||||||
|
defp put_creator_if_published(
|
||||||
|
%Changeset{} = changeset,
|
||||||
|
:create
|
||||||
|
) do
|
||||||
|
changeset
|
||||||
|
|> put_embed(:participant_stats, %{creator: 1})
|
||||||
|
end
|
||||||
|
|
||||||
|
# Updated from draft false to true: publish
|
||||||
|
defp put_creator_if_published(
|
||||||
|
%Changeset{
|
||||||
|
data: %{draft: false},
|
||||||
|
changes: %{draft: true}
|
||||||
|
} = changeset,
|
||||||
|
:update
|
||||||
|
) do
|
||||||
|
changeset
|
||||||
|
|> put_embed(:participant_stats, %{creator: 1})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_creator_if_published(%Changeset{} = changeset, _),
|
||||||
|
do: cast_embed(changeset, :participant_stats)
|
||||||
end
|
end
|
||||||
|
|
44
lib/mobilizon/events/event_participant_stats.ex
Normal file
44
lib/mobilizon/events/event_participant_stats.ex
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule Mobilizon.Events.EventParticipantStats do
|
||||||
|
@moduledoc """
|
||||||
|
Participation stats on event
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
not_approved: integer(),
|
||||||
|
rejected: integer(),
|
||||||
|
participant: integer(),
|
||||||
|
moderator: integer(),
|
||||||
|
administrator: integer(),
|
||||||
|
creator: integer()
|
||||||
|
}
|
||||||
|
|
||||||
|
@attrs [
|
||||||
|
:not_approved,
|
||||||
|
:rejected,
|
||||||
|
:participant,
|
||||||
|
:moderator,
|
||||||
|
:administrator,
|
||||||
|
:moderator,
|
||||||
|
:creator
|
||||||
|
]
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
@derive Jason.Encoder
|
||||||
|
embedded_schema do
|
||||||
|
field(:not_approved, :integer, default: 0)
|
||||||
|
field(:rejected, :integer, default: 0)
|
||||||
|
field(:participant, :integer, default: 0)
|
||||||
|
field(:moderator, :integer, default: 0)
|
||||||
|
field(:administrator, :integer, default: 0)
|
||||||
|
field(:creator, :integer, default: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||||
|
def changeset(%__MODULE__{} = event_options, attrs) do
|
||||||
|
cast(event_options, attrs, @attrs)
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,6 +7,7 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
import EctoEnum
|
import EctoEnum
|
||||||
|
alias Ecto.{Multi, Changeset}
|
||||||
|
|
||||||
import Mobilizon.Storage.Ecto
|
import Mobilizon.Storage.Ecto
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ defmodule Mobilizon.Events do
|
||||||
alias Mobilizon.Events.{
|
alias Mobilizon.Events.{
|
||||||
Comment,
|
Comment,
|
||||||
Event,
|
Event,
|
||||||
|
EventParticipantStats,
|
||||||
FeedToken,
|
FeedToken,
|
||||||
Participant,
|
Participant,
|
||||||
Session,
|
Session,
|
||||||
|
@ -82,6 +84,8 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
@event_preloads [
|
@event_preloads [
|
||||||
:organizer_actor,
|
:organizer_actor,
|
||||||
|
:attributed_to,
|
||||||
|
:mentions,
|
||||||
:sessions,
|
:sessions,
|
||||||
:tracks,
|
:tracks,
|
||||||
:tags,
|
:tags,
|
||||||
|
@ -90,7 +94,7 @@ defmodule Mobilizon.Events do
|
||||||
:picture
|
:picture
|
||||||
]
|
]
|
||||||
|
|
||||||
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment]
|
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment, :tags, :mentions]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single event.
|
Gets a single event.
|
||||||
|
@ -235,81 +239,103 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_or_create_event(%{"url" => url} = attrs) do
|
||||||
|
case Repo.get_by(Event, url: url) do
|
||||||
|
%Event{} = event -> {:ok, Repo.preload(event, @event_preloads)}
|
||||||
|
nil -> create_event(attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates an event.
|
Creates an event.
|
||||||
"""
|
"""
|
||||||
@spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||||
def create_event(attrs \\ %{}) do
|
def create_event(attrs \\ %{}) do
|
||||||
with {:ok, %Event{draft: false} = event} <- do_create_event(attrs),
|
with {:ok, %{insert: %Event{} = event}} <- do_create_event(attrs),
|
||||||
{:ok, %Participant{} = _participant} <-
|
%Event{} = event <- Repo.preload(event, @event_preloads) do
|
||||||
create_participant(%{
|
|
||||||
actor_id: event.organizer_actor_id,
|
|
||||||
role: :creator,
|
|
||||||
event_id: event.id
|
|
||||||
}) do
|
|
||||||
Task.start(fn -> Search.insert_search_event(event) end)
|
Task.start(fn -> Search.insert_search_event(event) end)
|
||||||
{:ok, event}
|
{:ok, event}
|
||||||
else
|
else
|
||||||
# We don't create a creator participant if the event is a draft
|
|
||||||
{:ok, %Event{draft: true} = event} -> {:ok, event}
|
|
||||||
err -> err
|
err -> err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
# We start by inserting the event and then insert a first participant if the event is not a draft
|
||||||
|
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||||
defp do_create_event(attrs) do
|
defp do_create_event(attrs) do
|
||||||
with {:ok, %Event{} = event} <-
|
Multi.new()
|
||||||
%Event{}
|
|> Multi.insert(:insert, Event.changeset(%Event{}, attrs))
|
||||||
|> Event.changeset(attrs)
|
|> Multi.run(:write, fn _repo, %{insert: %Event{draft: draft} = event} ->
|
||||||
|> Ecto.Changeset.put_assoc(:tags, Map.get(attrs, "tags", []))
|
with {:is_draft, false} <- {:is_draft, draft},
|
||||||
|> Repo.insert(),
|
{:ok, %Participant{} = participant} <-
|
||||||
%Event{} = event <-
|
create_participant(
|
||||||
Repo.preload(event, [:tags, :organizer_actor, :physical_address, :picture]) do
|
%{
|
||||||
{:ok, event}
|
event_id: event.id,
|
||||||
end
|
role: :creator,
|
||||||
|
actor_id: event.organizer_actor_id
|
||||||
|
},
|
||||||
|
false
|
||||||
|
) do
|
||||||
|
{:ok, participant}
|
||||||
|
else
|
||||||
|
{:is_draft, true} -> {:ok, nil}
|
||||||
|
err -> err
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates an event.
|
Updates an event.
|
||||||
|
|
||||||
|
We start by updating the event and then insert a first participant if the event is not a draft anymore
|
||||||
"""
|
"""
|
||||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||||
def update_event(
|
def update_event(%Event{} = old_event, attrs) do
|
||||||
%Event{draft: old_draft_status, id: event_id, organizer_actor_id: organizer_actor_id} =
|
with %Changeset{changes: changes} = changeset <-
|
||||||
old_event,
|
Event.update_changeset(Repo.preload(old_event, :tags), attrs),
|
||||||
attrs
|
{:ok, %{update: %Event{} = new_event}} <-
|
||||||
) do
|
Multi.new()
|
||||||
with %Ecto.Changeset{changes: changes} = changeset <-
|
|> Multi.update(
|
||||||
old_event |> Repo.preload(:tags) |> Event.update_changeset(attrs) do
|
:update,
|
||||||
with {:ok, %Event{draft: new_draft_status} = new_event} <- Repo.update(changeset) do
|
changeset
|
||||||
# If the event is no longer a draft
|
)
|
||||||
if old_draft_status == true && new_draft_status == false do
|
|> Multi.run(:write, fn _repo, %{update: %Event{draft: draft} = event} ->
|
||||||
{:ok, %Participant{} = _participant} =
|
with {:is_draft, false} <- {:is_draft, draft},
|
||||||
create_participant(%{
|
{:ok, %Participant{} = participant} <-
|
||||||
event_id: event_id,
|
create_participant(
|
||||||
role: :creator,
|
%{
|
||||||
actor_id: organizer_actor_id
|
event_id: event.id,
|
||||||
})
|
role: :creator,
|
||||||
end
|
actor_id: event.organizer_actor_id
|
||||||
|
},
|
||||||
|
false
|
||||||
|
) do
|
||||||
|
{:ok, participant}
|
||||||
|
else
|
||||||
|
{:is_draft, true} -> {:ok, nil}
|
||||||
|
err -> err
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Repo.transaction() do
|
||||||
|
Cachex.del(:ics, "event_#{new_event.uuid}")
|
||||||
|
|
||||||
Cachex.del(:ics, "event_#{new_event.uuid}")
|
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
|
||||||
|
old_event,
|
||||||
|
new_event,
|
||||||
|
changes
|
||||||
|
)
|
||||||
|
|
||||||
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
|
Task.start(fn -> Search.update_search_event(new_event) end)
|
||||||
old_event,
|
|
||||||
new_event,
|
|
||||||
changes
|
|
||||||
)
|
|
||||||
|
|
||||||
Task.start(fn -> Search.update_search_event(new_event) end)
|
{:ok, Repo.preload(new_event, @event_preloads)}
|
||||||
|
|
||||||
{:ok, new_event}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes an event.
|
Deletes an event.
|
||||||
"""
|
"""
|
||||||
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||||
def delete_event(%Event{} = event), do: Repo.delete(event)
|
def delete_event(%Event{} = event), do: Repo.delete(event)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -450,7 +476,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Gets an existing tag or creates the new one.
|
Gets an existing tag or creates the new one.
|
||||||
"""
|
"""
|
||||||
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||||
def get_or_create_tag(%{"name" => "#" <> title}) do
|
def get_or_create_tag(%{"name" => "#" <> title}) do
|
||||||
case Repo.get_by(Tag, title: title) do
|
case Repo.get_by(Tag, title: title) do
|
||||||
%Tag{} = tag ->
|
%Tag{} = tag ->
|
||||||
|
@ -461,10 +487,24 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets an existing tag or creates the new one.
|
||||||
|
"""
|
||||||
|
@spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||||
|
def get_or_create_tag(title) do
|
||||||
|
case Repo.get_by(Tag, title: title) do
|
||||||
|
%Tag{} = tag ->
|
||||||
|
{:ok, tag}
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
create_tag(%{"title" => title})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a tag.
|
Creates a tag.
|
||||||
"""
|
"""
|
||||||
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||||
def create_tag(attrs \\ %{}) do
|
def create_tag(attrs \\ %{}) do
|
||||||
%Tag{}
|
%Tag{}
|
||||||
|> Tag.changeset(attrs)
|
|> Tag.changeset(attrs)
|
||||||
|
@ -474,7 +514,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Updates a tag.
|
Updates a tag.
|
||||||
"""
|
"""
|
||||||
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||||
def update_tag(%Tag{} = tag, attrs) do
|
def update_tag(%Tag{} = tag, attrs) do
|
||||||
tag
|
tag
|
||||||
|> Tag.changeset(attrs)
|
|> Tag.changeset(attrs)
|
||||||
|
@ -484,7 +524,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a tag.
|
Deletes a tag.
|
||||||
"""
|
"""
|
||||||
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||||
def delete_tag(%Tag{} = tag), do: Repo.delete(tag)
|
def delete_tag(%Tag{} = tag), do: Repo.delete(tag)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -524,7 +564,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Creates a relation between two tags.
|
Creates a relation between two tags.
|
||||||
"""
|
"""
|
||||||
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Changeset.t()}
|
||||||
def create_tag_relation(attrs \\ {}) do
|
def create_tag_relation(attrs \\ {}) do
|
||||||
%TagRelation{}
|
%TagRelation{}
|
||||||
|> TagRelation.changeset(attrs)
|
|> TagRelation.changeset(attrs)
|
||||||
|
@ -538,7 +578,7 @@ defmodule Mobilizon.Events do
|
||||||
Removes a tag relation.
|
Removes a tag relation.
|
||||||
"""
|
"""
|
||||||
@spec delete_tag_relation(TagRelation.t()) ::
|
@spec delete_tag_relation(TagRelation.t()) ::
|
||||||
{:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, TagRelation.t()} | {:error, Changeset.t()}
|
||||||
def delete_tag_relation(%TagRelation{} = tag_relation) do
|
def delete_tag_relation(%TagRelation{} = tag_relation) do
|
||||||
Repo.delete(tag_relation)
|
Repo.delete(tag_relation)
|
||||||
end
|
end
|
||||||
|
@ -763,7 +803,7 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Counts participant participants.
|
Counts participant participants (participants with no extra role)
|
||||||
"""
|
"""
|
||||||
@spec count_participant_participants(integer | String.t()) :: integer
|
@spec count_participant_participants(integer | String.t()) :: integer
|
||||||
def count_participant_participants(event_id) do
|
def count_participant_participants(event_id) do
|
||||||
|
@ -773,17 +813,6 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Counts unapproved participants.
|
|
||||||
"""
|
|
||||||
@spec count_unapproved_participants(integer | String.t()) :: integer
|
|
||||||
def count_unapproved_participants(event_id) do
|
|
||||||
event_id
|
|
||||||
|> count_participants_query()
|
|
||||||
|> filter_unapproved_role()
|
|
||||||
|> Repo.aggregate(:count, :id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Counts rejected participants.
|
Counts rejected participants.
|
||||||
"""
|
"""
|
||||||
|
@ -805,12 +834,40 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Creates a participant.
|
Creates a participant.
|
||||||
"""
|
"""
|
||||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Changeset.t()}
|
||||||
def create_participant(attrs \\ %{}) do
|
def create_participant(attrs \\ %{}, update_event_participation_stats \\ true) do
|
||||||
with {:ok, %Participant{} = participant} <-
|
with {:ok, %{participant: %Participant{} = participant}} <-
|
||||||
%Participant{}
|
Multi.new()
|
||||||
|> Participant.changeset(attrs)
|
|> Multi.insert(:participant, Participant.changeset(%Participant{}, attrs))
|
||||||
|> Repo.insert() do
|
|> Multi.run(:update_event_participation_stats, fn _repo,
|
||||||
|
%{
|
||||||
|
participant:
|
||||||
|
%Participant{
|
||||||
|
role: role,
|
||||||
|
event_id: event_id
|
||||||
|
} = _participant
|
||||||
|
} ->
|
||||||
|
with {:update_event_participation_stats, true} <-
|
||||||
|
{:update_event_participation_stats, update_event_participation_stats},
|
||||||
|
{:ok, %Event{} = event} <- get_event(event_id),
|
||||||
|
%EventParticipantStats{} = participant_stats <-
|
||||||
|
Map.get(event, :participant_stats),
|
||||||
|
%EventParticipantStats{} = participant_stats <-
|
||||||
|
Map.update(participant_stats, role, 0, &(&1 + 1)),
|
||||||
|
{:ok, %Event{} = event} <-
|
||||||
|
event
|
||||||
|
|> Event.update_changeset(%{
|
||||||
|
participant_stats: Map.from_struct(participant_stats)
|
||||||
|
})
|
||||||
|
|> Repo.update() do
|
||||||
|
{:ok, event}
|
||||||
|
else
|
||||||
|
{:update_event_participation_stats, false} -> {:ok, nil}
|
||||||
|
{:error, :event_not_found} -> {:error, :event_not_found}
|
||||||
|
err -> {:error, err}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Repo.transaction() do
|
||||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -819,7 +876,7 @@ defmodule Mobilizon.Events do
|
||||||
Updates a participant.
|
Updates a participant.
|
||||||
"""
|
"""
|
||||||
@spec update_participant(Participant.t(), map) ::
|
@spec update_participant(Participant.t(), map) ::
|
||||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Participant.t()} | {:error, Changeset.t()}
|
||||||
def update_participant(%Participant{} = participant, attrs) do
|
def update_participant(%Participant{} = participant, attrs) do
|
||||||
participant
|
participant
|
||||||
|> Participant.changeset(attrs)
|
|> Participant.changeset(attrs)
|
||||||
|
@ -830,7 +887,7 @@ defmodule Mobilizon.Events do
|
||||||
Deletes a participant.
|
Deletes a participant.
|
||||||
"""
|
"""
|
||||||
@spec delete_participant(Participant.t()) ::
|
@spec delete_participant(Participant.t()) ::
|
||||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Participant.t()} | {:error, Changeset.t()}
|
||||||
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
|
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -843,7 +900,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Creates a session.
|
Creates a session.
|
||||||
"""
|
"""
|
||||||
@spec create_session(map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_session(map) :: {:ok, Session.t()} | {:error, Changeset.t()}
|
||||||
def create_session(attrs \\ %{}) do
|
def create_session(attrs \\ %{}) do
|
||||||
%Session{}
|
%Session{}
|
||||||
|> Session.changeset(attrs)
|
|> Session.changeset(attrs)
|
||||||
|
@ -853,7 +910,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Updates a session.
|
Updates a session.
|
||||||
"""
|
"""
|
||||||
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Changeset.t()}
|
||||||
def update_session(%Session{} = session, attrs) do
|
def update_session(%Session{} = session, attrs) do
|
||||||
session
|
session
|
||||||
|> Session.changeset(attrs)
|
|> Session.changeset(attrs)
|
||||||
|
@ -863,7 +920,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a session.
|
Deletes a session.
|
||||||
"""
|
"""
|
||||||
@spec delete_session(Session.t()) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_session(Session.t()) :: {:ok, Session.t()} | {:error, Changeset.t()}
|
||||||
def delete_session(%Session{} = session), do: Repo.delete(session)
|
def delete_session(%Session{} = session), do: Repo.delete(session)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -892,7 +949,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Creates a track.
|
Creates a track.
|
||||||
"""
|
"""
|
||||||
@spec create_track(map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_track(map) :: {:ok, Track.t()} | {:error, Changeset.t()}
|
||||||
def create_track(attrs \\ %{}) do
|
def create_track(attrs \\ %{}) do
|
||||||
%Track{}
|
%Track{}
|
||||||
|> Track.changeset(attrs)
|
|> Track.changeset(attrs)
|
||||||
|
@ -902,7 +959,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Updates a track.
|
Updates a track.
|
||||||
"""
|
"""
|
||||||
@spec update_track(Track.t(), map) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_track(Track.t(), map) :: {:ok, Track.t()} | {:error, Changeset.t()}
|
||||||
def update_track(%Track{} = track, attrs) do
|
def update_track(%Track{} = track, attrs) do
|
||||||
track
|
track
|
||||||
|> Track.changeset(attrs)
|
|> Track.changeset(attrs)
|
||||||
|
@ -912,7 +969,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a track.
|
Deletes a track.
|
||||||
"""
|
"""
|
||||||
@spec delete_track(Track.t()) :: {:ok, Track.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_track(Track.t()) :: {:ok, Track.t()} | {:error, Changeset.t()}
|
||||||
def delete_track(%Track{} = track), do: Repo.delete(track)
|
def delete_track(%Track{} = track), do: Repo.delete(track)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -931,6 +988,13 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single comment.
|
||||||
|
"""
|
||||||
|
@spec get_comment(integer | String.t()) :: Comment.t()
|
||||||
|
def get_comment(nil), do: nil
|
||||||
|
def get_comment(id), do: Repo.get(Comment, id)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single comment.
|
Gets a single comment.
|
||||||
Raises `Ecto.NoResultsError` if the comment does not exist.
|
Raises `Ecto.NoResultsError` if the comment does not exist.
|
||||||
|
@ -994,10 +1058,17 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.preload(@comment_preloads)
|
|> Repo.preload(@comment_preloads)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_or_create_comment(%{"url" => url} = attrs) do
|
||||||
|
case Repo.get_by(Comment, url: url) do
|
||||||
|
%Comment{} = comment -> {:ok, Repo.preload(comment, @comment_preloads)}
|
||||||
|
nil -> create_comment(attrs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a comment.
|
Creates a comment.
|
||||||
"""
|
"""
|
||||||
@spec create_comment(map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_comment(map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||||
def create_comment(attrs \\ %{}) do
|
def create_comment(attrs \\ %{}) do
|
||||||
with {:ok, %Comment{} = comment} <-
|
with {:ok, %Comment{} = comment} <-
|
||||||
%Comment{}
|
%Comment{}
|
||||||
|
@ -1011,7 +1082,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Updates a comment.
|
Updates a comment.
|
||||||
"""
|
"""
|
||||||
@spec update_comment(Comment.t(), map) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_comment(Comment.t(), map) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||||
def update_comment(%Comment{} = comment, attrs) do
|
def update_comment(%Comment{} = comment, attrs) do
|
||||||
comment
|
comment
|
||||||
|> Comment.changeset(attrs)
|
|> Comment.changeset(attrs)
|
||||||
|
@ -1021,7 +1092,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a comment.
|
Deletes a comment.
|
||||||
"""
|
"""
|
||||||
@spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_comment(Comment.t()) :: {:ok, Comment.t()} | {:error, Changeset.t()}
|
||||||
def delete_comment(%Comment{} = comment), do: Repo.delete(comment)
|
def delete_comment(%Comment{} = comment), do: Repo.delete(comment)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -1097,7 +1168,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Creates a feed token.
|
Creates a feed token.
|
||||||
"""
|
"""
|
||||||
@spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
|
@spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Changeset.t()}
|
||||||
def create_feed_token(attrs \\ %{}) do
|
def create_feed_token(attrs \\ %{}) do
|
||||||
attrs = Map.put(attrs, "token", Ecto.UUID.generate())
|
attrs = Map.put(attrs, "token", Ecto.UUID.generate())
|
||||||
|
|
||||||
|
@ -1110,7 +1181,7 @@ defmodule Mobilizon.Events do
|
||||||
Updates a feed token.
|
Updates a feed token.
|
||||||
"""
|
"""
|
||||||
@spec update_feed_token(FeedToken.t(), map) ::
|
@spec update_feed_token(FeedToken.t(), map) ::
|
||||||
{:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, FeedToken.t()} | {:error, Changeset.t()}
|
||||||
def update_feed_token(%FeedToken{} = feed_token, attrs) do
|
def update_feed_token(%FeedToken{} = feed_token, attrs) do
|
||||||
feed_token
|
feed_token
|
||||||
|> FeedToken.changeset(attrs)
|
|> FeedToken.changeset(attrs)
|
||||||
|
@ -1120,7 +1191,7 @@ defmodule Mobilizon.Events do
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a feed token.
|
Deletes a feed token.
|
||||||
"""
|
"""
|
||||||
@spec delete_feed_token(FeedToken.t()) :: {:ok, FeedToken.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_feed_token(FeedToken.t()) :: {:ok, FeedToken.t()} | {:error, Changeset.t()}
|
||||||
def delete_feed_token(%FeedToken{} = feed_token), do: Repo.delete(feed_token)
|
def delete_feed_token(%FeedToken{} = feed_token), do: Repo.delete(feed_token)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -1354,7 +1425,7 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec count_participants_query(integer) :: Ecto.Query.t()
|
@spec count_participants_query(integer) :: Ecto.Query.t()
|
||||||
defp count_participants_query(event_id) do
|
def count_participants_query(event_id) do
|
||||||
from(p in Participant, where: p.event_id == ^event_id)
|
from(p in Participant, where: p.event_id == ^event_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1385,17 +1456,10 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp public_comments_for_actor_query(actor_id) do
|
defp public_comments_for_actor_query(actor_id) do
|
||||||
from(
|
Comment
|
||||||
c in Comment,
|
|> where([c], c.actor_id == ^actor_id and c.visibility in ^@public_visibility)
|
||||||
where: c.actor_id == ^actor_id and c.visibility in ^@public_visibility,
|
|> order_by([c], desc: :id)
|
||||||
order_by: [desc: :id],
|
|> preload_for_comment()
|
||||||
preload: [
|
|
||||||
:actor,
|
|
||||||
:in_reply_to_comment,
|
|
||||||
:origin_comment,
|
|
||||||
:event
|
|
||||||
]
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
|
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
|
||||||
|
@ -1498,31 +1562,30 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
@spec filter_approved_role(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec filter_approved_role(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
defp filter_approved_role(query) do
|
defp filter_approved_role(query) do
|
||||||
from(p in query, where: p.role not in ^[:not_approved, :rejected])
|
filter_role(query, [:not_approved, :rejected])
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec filter_participant_role(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec filter_participant_role(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
defp filter_participant_role(query) do
|
defp filter_participant_role(query) do
|
||||||
from(p in query, where: p.role == ^:participant)
|
filter_role(query, :participant)
|
||||||
end
|
|
||||||
|
|
||||||
@spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t()
|
|
||||||
defp filter_unapproved_role(query) do
|
|
||||||
from(p in query, where: p.role == ^:not_approved)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec filter_rejected_role(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec filter_rejected_role(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
defp filter_rejected_role(query) do
|
defp filter_rejected_role(query) do
|
||||||
from(p in query, where: p.role == ^:rejected)
|
filter_role(query, :rejected)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
|
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
|
||||||
defp filter_role(query, []), do: query
|
def filter_role(query, []), do: query
|
||||||
|
|
||||||
defp filter_role(query, roles) do
|
def filter_role(query, roles) when is_list(roles) do
|
||||||
where(query, [p], p.role in ^roles)
|
where(query, [p], p.role in ^roles)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filter_role(query, role) when is_atom(role) do
|
||||||
|
from(p in query, where: p.role == ^role)
|
||||||
|
end
|
||||||
|
|
||||||
defp participation_filter_begins_on(query, nil, nil),
|
defp participation_filter_begins_on(query, nil, nil),
|
||||||
do: participation_order_begins_on_desc(query)
|
do: participation_order_begins_on_desc(query)
|
||||||
|
|
||||||
|
|
53
lib/mobilizon/mentions/mention.ex
Normal file
53
lib/mobilizon/mentions/mention.ex
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule Mobilizon.Mention do
|
||||||
|
@moduledoc """
|
||||||
|
The Mentions context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
alias Mobilizon.Events.Comment
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
silent: boolean,
|
||||||
|
actor: Actor.t(),
|
||||||
|
event: Event.t(),
|
||||||
|
comment: Comment.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
@required_attrs [:actor_id]
|
||||||
|
@optional_attrs [:silent, :event_id, :comment_id]
|
||||||
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
|
schema "mentions" do
|
||||||
|
field(:silent, :boolean, default: false)
|
||||||
|
belongs_to(:actor, Actor)
|
||||||
|
belongs_to(:event, Event)
|
||||||
|
belongs_to(:comment, Comment)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(event, attrs) do
|
||||||
|
event
|
||||||
|
|> cast(attrs, @attrs)
|
||||||
|
# TODO: Enforce having either event_id or comment_id
|
||||||
|
|> validate_required(@required_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a new mention
|
||||||
|
"""
|
||||||
|
@spec create_mention(map()) :: {:ok, t} | {:error, Ecto.Changeset.t()}
|
||||||
|
def create_mention(args) do
|
||||||
|
with {:ok, %__MODULE__{} = mention} <-
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(args)
|
||||||
|
|> Repo.insert() do
|
||||||
|
{:ok, Repo.preload(mention, [:actor, :event, :comment])}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,55 +2,18 @@ defmodule MobilizonWeb.API.Comments do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
API for Comments.
|
API for Comments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.Actor
|
|
||||||
alias Mobilizon.Events.Comment
|
alias Mobilizon.Events.Comment
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
alias Mobilizon.Service.ActivityPub.Activity
|
||||||
|
|
||||||
alias MobilizonWeb.API.Utils
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create a comment
|
Create a comment
|
||||||
|
|
||||||
Creates a comment from an actor and a status
|
Creates a comment from an actor and a status
|
||||||
"""
|
"""
|
||||||
@spec create_comment(String.t(), String.t(), String.t()) ::
|
@spec create_comment(map()) ::
|
||||||
{:ok, Activity.t(), Comment.t()} | any()
|
{:ok, Activity.t(), Comment.t()} | any()
|
||||||
def create_comment(
|
def create_comment(args) do
|
||||||
from_username,
|
ActivityPub.create(:comment, args, true)
|
||||||
status,
|
|
||||||
visibility \\ :public,
|
|
||||||
in_reply_to_comment_URL \\ nil
|
|
||||||
) do
|
|
||||||
with {:local_actor, %Actor{url: url} = actor} <-
|
|
||||||
{:local_actor, Actors.get_local_actor_by_name(from_username)},
|
|
||||||
in_reply_to_comment <- get_in_reply_to_comment(in_reply_to_comment_URL),
|
|
||||||
{content_html, tags, to, cc} <-
|
|
||||||
Utils.prepare_content(actor, status, visibility, [], in_reply_to_comment),
|
|
||||||
comment <-
|
|
||||||
ActivityPubUtils.make_comment_data(
|
|
||||||
url,
|
|
||||||
to,
|
|
||||||
content_html,
|
|
||||||
in_reply_to_comment,
|
|
||||||
tags,
|
|
||||||
cc
|
|
||||||
) do
|
|
||||||
ActivityPub.create(%{
|
|
||||||
to: to,
|
|
||||||
actor: actor,
|
|
||||||
object: comment,
|
|
||||||
local: true
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_in_reply_to_comment(nil) :: nil
|
|
||||||
defp get_in_reply_to_comment(nil), do: nil
|
|
||||||
@spec get_in_reply_to_comment(String.t()) :: Comment.t()
|
|
||||||
defp get_in_reply_to_comment(in_reply_to_comment_url) do
|
|
||||||
ActivityPub.fetch_object_from_url(in_reply_to_comment_url)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,37 +3,24 @@ defmodule MobilizonWeb.API.Events do
|
||||||
API for Events.
|
API for Events.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Events.Event
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Activity
|
alias Mobilizon.Service.ActivityPub.Activity
|
||||||
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
|
|
||||||
alias MobilizonWeb.API.Utils
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
"""
|
"""
|
||||||
@spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
|
@spec create_event(map()) :: {:ok, Activity.t(), Event.t()} | any()
|
||||||
def create_event(%{organizer_actor: organizer_actor} = args) do
|
def create_event(args) do
|
||||||
with args <- prepare_args(args),
|
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||||
event <-
|
args <-
|
||||||
ActivityPubUtils.make_event_data(
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
args.organizer_actor.url,
|
process_picture(picture, organizer_actor)
|
||||||
%{to: args.to, cc: args.cc},
|
end) do
|
||||||
args.title,
|
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
||||||
args.content_html,
|
ActivityPub.create(:event, args, args.draft == false)
|
||||||
args.picture,
|
|
||||||
args.tags,
|
|
||||||
args.metadata
|
|
||||||
) do
|
|
||||||
ActivityPub.create(%{
|
|
||||||
to: args.to,
|
|
||||||
actor: organizer_actor,
|
|
||||||
object: event,
|
|
||||||
# For now we don't federate drafts but it will be needed if we want to edit them as groups
|
|
||||||
local: args.metadata.draft == false
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,65 +28,13 @@ defmodule MobilizonWeb.API.Events do
|
||||||
Update an event
|
Update an event
|
||||||
"""
|
"""
|
||||||
@spec update_event(map(), Event.t()) :: {:ok, Activity.t(), Event.t()} | any()
|
@spec update_event(map(), Event.t()) :: {:ok, Activity.t(), Event.t()} | any()
|
||||||
def update_event(
|
def update_event(args, %Event{} = event) do
|
||||||
%{
|
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||||
organizer_actor: organizer_actor
|
args <-
|
||||||
} = args,
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
%Event{} = event
|
process_picture(picture, organizer_actor)
|
||||||
) do
|
end) do
|
||||||
with args <- Map.put(args, :tags, Map.get(args, :tags, [])),
|
ActivityPub.update(:event, event, args, Map.get(args, :draft, false) == false)
|
||||||
args <- prepare_args(Map.merge(event, args)),
|
|
||||||
event <-
|
|
||||||
ActivityPubUtils.make_event_data(
|
|
||||||
args.organizer_actor.url,
|
|
||||||
%{to: args.to, cc: args.cc},
|
|
||||||
args.title,
|
|
||||||
args.content_html,
|
|
||||||
args.picture,
|
|
||||||
args.tags,
|
|
||||||
args.metadata,
|
|
||||||
event.uuid,
|
|
||||||
event.url
|
|
||||||
) do
|
|
||||||
ActivityPub.update(%{
|
|
||||||
to: args.to,
|
|
||||||
actor: organizer_actor.url,
|
|
||||||
cc: [],
|
|
||||||
object: event,
|
|
||||||
local: args.metadata.draft == false
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp prepare_args(args) do
|
|
||||||
with %Actor{} = organizer_actor <- Map.get(args, :organizer_actor),
|
|
||||||
title <- args |> Map.get(:title, "") |> HtmlSanitizeEx.strip_tags() |> String.trim(),
|
|
||||||
visibility <- Map.get(args, :visibility, :public),
|
|
||||||
description <- Map.get(args, :description),
|
|
||||||
tags <- Map.get(args, :tags),
|
|
||||||
{content_html, tags, to, cc} <-
|
|
||||||
Utils.prepare_content(organizer_actor, description, visibility, tags, nil) do
|
|
||||||
%{
|
|
||||||
title: title,
|
|
||||||
content_html: content_html,
|
|
||||||
picture: Map.get(args, :picture),
|
|
||||||
tags: tags,
|
|
||||||
organizer_actor: organizer_actor,
|
|
||||||
to: to,
|
|
||||||
cc: cc,
|
|
||||||
metadata: %{
|
|
||||||
begins_on: Map.get(args, :begins_on),
|
|
||||||
ends_on: Map.get(args, :ends_on),
|
|
||||||
physical_address: Map.get(args, :physical_address),
|
|
||||||
category: Map.get(args, :category),
|
|
||||||
options: Map.get(args, :options),
|
|
||||||
join_options: Map.get(args, :join_options),
|
|
||||||
status: Map.get(args, :status),
|
|
||||||
online_address: Map.get(args, :online_address),
|
|
||||||
phone_address: Map.get(args, :phone_address),
|
|
||||||
draft: Map.get(args, :draft)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -111,4 +46,15 @@ defmodule MobilizonWeb.API.Events do
|
||||||
def delete_event(%Event{} = event, federate \\ true) do
|
def delete_event(%Event{} = event, federate \\ true) do
|
||||||
ActivityPub.delete(event, federate)
|
ActivityPub.delete(event, federate)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp process_picture(nil, _), do: nil
|
||||||
|
defp process_picture(%{picture_id: _picture_id} = args, _), do: args
|
||||||
|
|
||||||
|
defp process_picture(%{picture: picture}, %Actor{id: actor_id}) do
|
||||||
|
%{
|
||||||
|
file:
|
||||||
|
picture |> Map.get(:file) |> Utils.make_picture_data(description: Map.get(picture, :name)),
|
||||||
|
actor_id: actor_id
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule MobilizonWeb.API.Follows do
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.{Actor, Follower}
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
alias Mobilizon.Service.ActivityPub.Activity
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -32,17 +33,14 @@ defmodule MobilizonWeb.API.Follows do
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(%Actor{} = follower, %Actor{} = followed) do
|
def accept(%Actor{} = follower, %Actor{} = followed) do
|
||||||
with %Follower{approved: false, id: follow_id, url: follow_url} = follow <-
|
with %Follower{approved: false} = follow <-
|
||||||
Actors.is_following(follower, followed),
|
Actors.is_following(follower, followed),
|
||||||
activity_follow_url <- "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}",
|
{:ok, %Activity{} = activity, %Follower{approved: true}} <-
|
||||||
data <-
|
|
||||||
ActivityPub.Utils.make_follow_data(followed, follower, follow_url),
|
|
||||||
{:ok, activity, _} <-
|
|
||||||
ActivityPub.accept(
|
ActivityPub.accept(
|
||||||
%{to: [follower.url], actor: followed.url, object: data},
|
:follow,
|
||||||
activity_follow_url
|
follow,
|
||||||
),
|
%{approved: true}
|
||||||
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
|
) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
%Follower{approved: true} ->
|
%Follower{approved: true} ->
|
||||||
|
|
|
@ -6,38 +6,19 @@ defmodule MobilizonWeb.API.Groups do
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
|
alias Mobilizon.Service.ActivityPub.Activity
|
||||||
alias Mobilizon.Users.User
|
|
||||||
|
|
||||||
alias MobilizonWeb.API.Utils
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create a group
|
Create a group
|
||||||
"""
|
"""
|
||||||
@spec create_group(User.t(), map()) :: {:ok, Activity.t(), Group.t()} | any()
|
@spec create_group(map()) :: {:ok, Activity.t(), Actor.t()} | any()
|
||||||
def create_group(
|
def create_group(args) do
|
||||||
user,
|
with preferred_username <-
|
||||||
%{
|
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
|
||||||
preferred_username: title,
|
{:existing_group, nil} <-
|
||||||
summary: summary,
|
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
|
||||||
creator_actor_id: creator_actor_id,
|
{:ok, %Activity{} = activity, %Actor{} = group} <- ActivityPub.create(:group, args, true) do
|
||||||
avatar: _avatar,
|
{:ok, activity, group}
|
||||||
banner: _banner
|
|
||||||
} = args
|
|
||||||
) do
|
|
||||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, creator_actor_id),
|
|
||||||
title <- String.trim(title),
|
|
||||||
{:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
|
|
||||||
visibility <- Map.get(args, :visibility, :public),
|
|
||||||
{content_html, tags, to, cc} <-
|
|
||||||
Utils.prepare_content(actor, summary, visibility, [], nil),
|
|
||||||
group <- ActivityPubUtils.make_group_data(actor.url, to, title, content_html, tags, cc) do
|
|
||||||
ActivityPub.create(%{
|
|
||||||
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
||||||
actor: actor,
|
|
||||||
object: group,
|
|
||||||
local: true
|
|
||||||
})
|
|
||||||
else
|
else
|
||||||
{:existing_group, _} ->
|
{:existing_group, _} ->
|
||||||
{:error, "A group with this name already exists"}
|
{:error, "A group with this name already exists"}
|
||||||
|
|
|
@ -38,12 +38,11 @@ defmodule MobilizonWeb.API.Participations do
|
||||||
) do
|
) do
|
||||||
with {:ok, activity, _} <-
|
with {:ok, activity, _} <-
|
||||||
ActivityPub.accept(
|
ActivityPub.accept(
|
||||||
%{
|
:join,
|
||||||
to: [participation.actor.url],
|
participation,
|
||||||
actor: moderator.url,
|
%{role: :participant},
|
||||||
object: participation.url
|
true,
|
||||||
},
|
%{"to" => [moderator.url]}
|
||||||
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participation.id}"
|
|
||||||
),
|
),
|
||||||
{:ok, %Participant{role: :participant} = participation} <-
|
{:ok, %Participant{role: :participant} = participation} <-
|
||||||
Events.update_participant(participation, %{"role" => :participant}),
|
Events.update_participant(participation, %{"role" => :participant}),
|
||||||
|
|
|
@ -3,89 +3,9 @@ defmodule MobilizonWeb.API.Utils do
|
||||||
Utils for API.
|
Utils for API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
|
||||||
alias Mobilizon.Config
|
alias Mobilizon.Config
|
||||||
alias Mobilizon.Service.Formatter
|
alias Mobilizon.Service.Formatter
|
||||||
|
|
||||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Determines the full audience based on mentions for a public audience
|
|
||||||
|
|
||||||
Audience is:
|
|
||||||
* `to` : the mentioned actors, the eventual actor we're replying to and the public
|
|
||||||
* `cc` : the actor's followers
|
|
||||||
"""
|
|
||||||
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
|
||||||
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :public) do
|
|
||||||
to = [@ap_public | mentions]
|
|
||||||
cc = [actor.followers_url]
|
|
||||||
|
|
||||||
if inReplyTo do
|
|
||||||
{Enum.uniq([inReplyTo.actor | to]), cc}
|
|
||||||
else
|
|
||||||
{to, cc}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Determines the full audience based on mentions based on a unlisted audience
|
|
||||||
|
|
||||||
Audience is:
|
|
||||||
* `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
|
|
||||||
* `cc` : public
|
|
||||||
"""
|
|
||||||
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
|
||||||
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :unlisted) do
|
|
||||||
to = [actor.followers_url | mentions]
|
|
||||||
cc = [@ap_public]
|
|
||||||
|
|
||||||
if inReplyTo do
|
|
||||||
{Enum.uniq([inReplyTo.actor | to]), cc}
|
|
||||||
else
|
|
||||||
{to, cc}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Determines the full audience based on mentions based on a private audience
|
|
||||||
|
|
||||||
Audience is:
|
|
||||||
* `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
|
|
||||||
* `cc` : none
|
|
||||||
"""
|
|
||||||
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
|
||||||
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :private) do
|
|
||||||
{to, cc} = get_to_and_cc(actor, mentions, inReplyTo, :direct)
|
|
||||||
{[actor.followers_url | to], cc}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Determines the full audience based on mentions based on a direct audience
|
|
||||||
|
|
||||||
Audience is:
|
|
||||||
* `to` : the mentioned actors and the eventual actor we're replying to
|
|
||||||
* `cc` : none
|
|
||||||
"""
|
|
||||||
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
|
||||||
def get_to_and_cc(_actor, mentions, inReplyTo, :direct) do
|
|
||||||
if inReplyTo do
|
|
||||||
{Enum.uniq([inReplyTo.actor | mentions]), []}
|
|
||||||
else
|
|
||||||
{mentions, []}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_to_and_cc(_actor, mentions, _inReplyTo, {:list, _}) do
|
|
||||||
{mentions, []}
|
|
||||||
end
|
|
||||||
|
|
||||||
# def get_addressed_users(_, to) when is_list(to) do
|
|
||||||
# Actors.get(to)
|
|
||||||
# end
|
|
||||||
|
|
||||||
def get_addressed_users(mentioned_users, _), do: mentioned_users
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates HTML content from text and mentions
|
Creates HTML content from text and mentions
|
||||||
"""
|
"""
|
||||||
|
@ -126,19 +46,4 @@ defmodule MobilizonWeb.API.Utils do
|
||||||
{:error, "Comment must be up to #{max_size} characters"}
|
{:error, "Comment must be up to #{max_size} characters"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_content(actor, content, visibility, tags, in_reply_to) do
|
|
||||||
with content <- String.trim(content || ""),
|
|
||||||
{content_html, mentions, tags} <-
|
|
||||||
make_content_html(
|
|
||||||
content,
|
|
||||||
tags,
|
|
||||||
"text/html"
|
|
||||||
),
|
|
||||||
mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.url),
|
|
||||||
addressed_users <- get_addressed_users(mentioned_users, nil),
|
|
||||||
{to, cc} <- get_to_and_cc(actor, addressed_users, in_reply_to, visibility) do
|
|
||||||
{content_html, tags, to, cc}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,19 +4,19 @@ defmodule MobilizonWeb.Resolvers.Comment do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Events.Comment
|
alias Mobilizon.Events.Comment
|
||||||
alias Mobilizon.Service.ActivityPub.Activity
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
alias MobilizonWeb.API.Comments
|
alias MobilizonWeb.API.Comments
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def create_comment(_parent, %{text: comment, actor_username: username}, %{
|
def create_comment(_parent, %{text: text, actor_id: actor_id}, %{
|
||||||
context: %{current_user: %User{} = _user}
|
context: %{current_user: %User{} = user}
|
||||||
}) do
|
}) do
|
||||||
with {:ok, %Activity{data: %{"object" => %{"type" => "Note"} = _object}},
|
with {:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
|
||||||
%Comment{} = comment} <-
|
{:ok, _, %Comment{} = comment} <-
|
||||||
Comments.create_comment(username, comment) do
|
Comments.create_comment(%{actor_id: actor_id, text: text}) do
|
||||||
{:ok, comment}
|
{:ok, comment}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,11 +7,8 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Addresses
|
|
||||||
alias Mobilizon.Addresses.Address
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant, EventParticipantStats}
|
||||||
alias Mobilizon.Media.Picture
|
|
||||||
alias Mobilizon.Service.ActivityPub.Activity
|
alias Mobilizon.Service.ActivityPub.Activity
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
@ -96,14 +93,8 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:ok, []}
|
{:ok, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def stats_participants_for_event(%Event{id: id}, _args, _resolution) do
|
def stats_participants_going(%EventParticipantStats{} = stats, _args, _resolution) do
|
||||||
{:ok,
|
{:ok, stats.participant + stats.moderator + stats.administrator + stats.creator}
|
||||||
%{
|
|
||||||
approved: Mobilizon.Events.count_approved_participants(id),
|
|
||||||
unapproved: Mobilizon.Events.count_unapproved_participants(id),
|
|
||||||
rejected: Mobilizon.Events.count_rejected_participants(id),
|
|
||||||
participants: Mobilizon.Events.count_participant_participants(id)
|
|
||||||
}}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -277,8 +268,6 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
with args <- Map.put(args, :options, args[:options] || %{}),
|
with args <- Map.put(args, :options, args[:options] || %{}),
|
||||||
{:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
{:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
|
||||||
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
|
||||||
{:ok, args_with_organizer} <- save_attached_picture(args_with_organizer),
|
|
||||||
{:ok, args_with_organizer} <- save_physical_address(args_with_organizer),
|
|
||||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||||
MobilizonWeb.API.Events.create_event(args_with_organizer) do
|
MobilizonWeb.API.Events.create_event(args_with_organizer) do
|
||||||
{:ok, event}
|
{:ok, event}
|
||||||
|
@ -309,8 +298,6 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:is_owned, %Actor{} = organizer_actor} <-
|
{:is_owned, %Actor{} = organizer_actor} <-
|
||||||
User.owns_actor(user, event.organizer_actor_id),
|
User.owns_actor(user, event.organizer_actor_id),
|
||||||
args <- Map.put(args, :organizer_actor, organizer_actor),
|
args <- Map.put(args, :organizer_actor, organizer_actor),
|
||||||
{:ok, args} <- save_attached_picture(args),
|
|
||||||
{:ok, args} <- save_physical_address(args),
|
|
||||||
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
|
||||||
MobilizonWeb.API.Events.update_event(args, event) do
|
MobilizonWeb.API.Events.update_event(args, event) do
|
||||||
{:ok, event}
|
{:ok, event}
|
||||||
|
@ -327,47 +314,6 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:error, "You need to be logged-in to update an event"}
|
{:error, "You need to be logged-in to update an event"}
|
||||||
end
|
end
|
||||||
|
|
||||||
# If we have an attached picture, just transmit it. It will be handled by
|
|
||||||
# Mobilizon.Service.ActivityPub.Utils.make_picture_data/1
|
|
||||||
# However, we need to pass its actor ID
|
|
||||||
@spec save_attached_picture(map()) :: {:ok, map()}
|
|
||||||
defp save_attached_picture(
|
|
||||||
%{picture: %{picture: %{file: %Plug.Upload{} = _picture} = all_pic}} = args
|
|
||||||
) do
|
|
||||||
{:ok, Map.put(args, :picture, Map.put(all_pic, :actor_id, args.organizer_actor.id))}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Otherwise if we use a previously uploaded picture we need to fetch it from database
|
|
||||||
@spec save_attached_picture(map()) :: {:ok, map()}
|
|
||||||
defp save_attached_picture(%{picture: %{picture_id: picture_id}} = args) do
|
|
||||||
with %Picture{} = picture <- Mobilizon.Media.get_picture(picture_id) do
|
|
||||||
{:ok, Map.put(args, :picture, picture)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec save_attached_picture(map()) :: {:ok, map()}
|
|
||||||
defp save_attached_picture(args), do: {:ok, args}
|
|
||||||
|
|
||||||
@spec save_physical_address(map()) :: {:ok, map()}
|
|
||||||
defp save_physical_address(%{physical_address: %{url: physical_address_url}} = args)
|
|
||||||
when not is_nil(physical_address_url) do
|
|
||||||
with %Address{} = address <- Addresses.get_address_by_url(physical_address_url),
|
|
||||||
args <- Map.put(args, :physical_address, address.url) do
|
|
||||||
{:ok, args}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec save_physical_address(map()) :: {:ok, map()}
|
|
||||||
defp save_physical_address(%{physical_address: address} = args) when address != nil do
|
|
||||||
with {:ok, %Address{} = address} <- Addresses.create_address(address),
|
|
||||||
args <- Map.put(args, :physical_address, address.url) do
|
|
||||||
{:ok, args}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec save_physical_address(map()) :: {:ok, map()}
|
|
||||||
defp save_physical_address(args), do: {:ok, args}
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Delete an event
|
Delete an event
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule MobilizonWeb.Resolvers.Group do
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Member}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Activity
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
alias MobilizonWeb.API
|
alias MobilizonWeb.API
|
||||||
|
@ -47,23 +46,18 @@ defmodule MobilizonWeb.Resolvers.Group do
|
||||||
args,
|
args,
|
||||||
%{context: %{current_user: user}}
|
%{context: %{current_user: user}}
|
||||||
) do
|
) do
|
||||||
with {
|
with creator_actor_id <- Map.get(args, :creator_actor_id),
|
||||||
:ok,
|
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, creator_actor_id),
|
||||||
%Activity{data: %{"object" => %{"type" => "Group"} = _object}},
|
args <- Map.put(args, :creator_actor, actor),
|
||||||
%Actor{} = group
|
{:ok, _activity, %Actor{type: :Group} = group} <-
|
||||||
} <-
|
API.Groups.create_group(args) do
|
||||||
API.Groups.create_group(
|
|
||||||
user,
|
|
||||||
%{
|
|
||||||
preferred_username: args.preferred_username,
|
|
||||||
creator_actor_id: args.creator_actor_id,
|
|
||||||
name: Map.get(args, "name", args.preferred_username),
|
|
||||||
summary: args.summary,
|
|
||||||
avatar: Map.get(args, "avatar"),
|
|
||||||
banner: Map.get(args, "banner")
|
|
||||||
}
|
|
||||||
) do
|
|
||||||
{:ok, group}
|
{:ok, group}
|
||||||
|
else
|
||||||
|
{:error, err} when is_bitstring(err) ->
|
||||||
|
{:error, err}
|
||||||
|
|
||||||
|
{:is_owned, nil} ->
|
||||||
|
{:error, "Creator actor id is not owned by the current user"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ defmodule MobilizonWeb.Resolvers.Person do
|
||||||
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
|
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
|
||||||
pic = args[key][:picture]
|
pic = args[key][:picture]
|
||||||
|
|
||||||
with {:ok, %{"name" => name, "url" => [%{"href" => url, "mediaType" => content_type}]}} <-
|
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
||||||
MobilizonWeb.Upload.store(pic.file, type: key, description: pic.alt) do
|
MobilizonWeb.Upload.store(pic.file, type: key, description: pic.alt) do
|
||||||
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,7 +51,7 @@ defmodule MobilizonWeb.Resolvers.Picture do
|
||||||
%{context: %{current_user: user}}
|
%{context: %{current_user: user}}
|
||||||
) do
|
) do
|
||||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %{"url" => [%{"href" => url, "mediaType" => content_type}], "size" => size}} <-
|
{:ok, %{name: _name, url: url, content_type: content_type, size: size}} <-
|
||||||
MobilizonWeb.Upload.store(file),
|
MobilizonWeb.Upload.store(file),
|
||||||
args <-
|
args <-
|
||||||
args
|
args
|
||||||
|
|
|
@ -35,7 +35,7 @@ defmodule MobilizonWeb.Schema.CommentType do
|
||||||
@desc "Create a comment"
|
@desc "Create a comment"
|
||||||
field :create_comment, type: :comment do
|
field :create_comment, type: :comment do
|
||||||
arg(:text, non_null(:string))
|
arg(:text, non_null(:string))
|
||||||
arg(:actor_username, non_null(:string))
|
arg(:actor_id, non_null(:id))
|
||||||
|
|
||||||
resolve(&Comment.create_comment/3)
|
resolve(&Comment.create_comment/3)
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,7 +63,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
|
|
||||||
field(:draft, :boolean, description: "Whether or not the event is a draft")
|
field(:draft, :boolean, description: "Whether or not the event is a draft")
|
||||||
|
|
||||||
field(:participant_stats, :participant_stats, resolve: &Event.stats_participants_for_event/3)
|
field(:participant_stats, :participant_stats)
|
||||||
|
|
||||||
field(:participants, list_of(:participant), description: "The event's participants") do
|
field(:participants, list_of(:participant), description: "The event's participants") do
|
||||||
arg(:page, :integer, default_value: 1)
|
arg(:page, :integer, default_value: 1)
|
||||||
|
@ -112,13 +112,21 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
end
|
end
|
||||||
|
|
||||||
object :participant_stats do
|
object :participant_stats do
|
||||||
field(:approved, :integer, description: "The number of approved participants")
|
field(:going, :integer,
|
||||||
field(:unapproved, :integer, description: "The number of unapproved participants")
|
description: "The number of approved participants",
|
||||||
|
resolve: &Event.stats_participants_going/3
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:not_approved, :integer, description: "The number of not approved participants")
|
||||||
field(:rejected, :integer, description: "The number of rejected participants")
|
field(:rejected, :integer, description: "The number of rejected participants")
|
||||||
|
|
||||||
field(:participants, :integer,
|
field(:participant, :integer,
|
||||||
description: "The number of simple participants (excluding creators)"
|
description: "The number of simple participants (excluding creators)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
field(:moderator, :integer, description: "The number of moderators")
|
||||||
|
field(:administrator, :integer, description: "The number of administrators")
|
||||||
|
field(:creator, :integer, description: "The number of creators")
|
||||||
end
|
end
|
||||||
|
|
||||||
object :event_offer do
|
object :event_offer do
|
||||||
|
|
|
@ -73,16 +73,10 @@ defmodule MobilizonWeb.Upload do
|
||||||
{:ok, url_spec} <- Uploaders.Uploader.put_file(opts.uploader, upload) do
|
{:ok, url_spec} <- Uploaders.Uploader.put_file(opts.uploader, upload) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
"type" => opts.activity_type || get_type(upload.content_type),
|
name: Map.get(opts, :description) || upload.name,
|
||||||
"url" => [
|
url: url_from_spec(upload, opts.base_url, url_spec),
|
||||||
%{
|
content_type: upload.content_type,
|
||||||
"type" => "Link",
|
size: upload.size
|
||||||
"mediaType" => upload.content_type,
|
|
||||||
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"size" => upload.size,
|
|
||||||
"name" => Map.get(opts, :description) || upload.name
|
|
||||||
}}
|
}}
|
||||||
else
|
else
|
||||||
{:error, error} ->
|
{:error, error} ->
|
||||||
|
@ -166,16 +160,6 @@ defmodule MobilizonWeb.Upload do
|
||||||
|
|
||||||
defp check_file_size(_, _), do: :ok
|
defp check_file_size(_, _), do: :ok
|
||||||
|
|
||||||
@picture_content_types ["image/gif", "image/png", "image/jpg", "image/jpeg", "image/webp"]
|
|
||||||
# Return whether the upload is a picture or not
|
|
||||||
defp get_type(content_type) do
|
|
||||||
if content_type in @picture_content_types do
|
|
||||||
"Image"
|
|
||||||
else
|
|
||||||
"Document"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
||||||
path =
|
path =
|
||||||
URI.encode(path, &char_unescaped?/1) <>
|
URI.encode(path, &char_unescaped?/1) <>
|
||||||
|
|
|
@ -8,7 +8,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
# ActivityPub context.
|
# ActivityPub context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import Mobilizon.Service.ActivityPub.{Utils, Visibility}
|
import Mobilizon.Service.ActivityPub.Utils
|
||||||
|
import Mobilizon.Service.ActivityPub.Visibility
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Config, Events}
|
alias Mobilizon.{Actors, Config, Events}
|
||||||
alias Mobilizon.Actors.{Actor, Follower}
|
alias Mobilizon.Actors.{Actor, Follower}
|
||||||
|
@ -16,17 +17,12 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Relay, Transmogrifier}
|
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Relay, Transmogrifier}
|
||||||
alias Mobilizon.Service.{Federator, WebFinger}
|
alias Mobilizon.Service.{Federator, WebFinger}
|
||||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||||
|
alias MobilizonWeb.API.Utils, as: APIUtils
|
||||||
|
alias Mobilizon.Service.ActivityPub.Audience
|
||||||
|
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get recipients for an activity or object
|
|
||||||
"""
|
|
||||||
@spec get_recipients(map()) :: list()
|
|
||||||
def get_recipients(data) do
|
|
||||||
(data["to"] || []) ++ (data["cc"] || [])
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Wraps an object into an activity
|
Wraps an object into an activity
|
||||||
"""
|
"""
|
||||||
|
@ -106,8 +102,8 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
@doc """
|
@doc """
|
||||||
Getting an actor from url, eventually creating it
|
Getting an actor from url, eventually creating it
|
||||||
"""
|
"""
|
||||||
@spec get_or_fetch_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
|
@spec get_or_fetch_actor_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
|
||||||
def get_or_fetch_by_url(url, preload \\ false) do
|
def get_or_fetch_actor_by_url(url, preload \\ false) do
|
||||||
case Actors.get_actor_by_url(url, preload) do
|
case Actors.get_actor_by_url(url, preload) do
|
||||||
{:ok, %Actor{} = actor} ->
|
{:ok, %Actor{} = actor} ->
|
||||||
{:ok, actor}
|
{:ok, actor}
|
||||||
|
@ -126,27 +122,29 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an activity of type "Create"
|
Create an activity of type `Create`
|
||||||
"""
|
|
||||||
def create(%{to: to, actor: actor, object: object} = params) do
|
|
||||||
Logger.debug("creating an activity")
|
|
||||||
Logger.debug(inspect(params))
|
|
||||||
Logger.debug(inspect(object))
|
|
||||||
additional = params[:additional] || %{}
|
|
||||||
# only accept false as false value
|
|
||||||
local = !(params[:local] == false)
|
|
||||||
published = params[:published]
|
|
||||||
|
|
||||||
with create_data <-
|
* Creates the object, which returns AS data
|
||||||
make_create_data(
|
* Wraps ActivityStreams data into a `Create` activity
|
||||||
%{to: to, actor: actor, published: published, object: object},
|
* Creates an `Mobilizon.Service.ActivityPub.Activity` from this
|
||||||
additional
|
* Federates (asynchronously) the activity
|
||||||
),
|
* Returns the activity
|
||||||
{:ok, activity} <- create_activity(create_data, local),
|
"""
|
||||||
{:ok, object} <- insert_full_object(create_data),
|
@spec create(atom(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
|
||||||
|
def create(type, args, local \\ false, additional \\ %{}) do
|
||||||
|
Logger.debug("creating an activity")
|
||||||
|
Logger.debug(inspect(args))
|
||||||
|
|
||||||
|
{:ok, entity, create_data} =
|
||||||
|
case type do
|
||||||
|
:event -> create_event(args, additional)
|
||||||
|
:comment -> create_comment(args, additional)
|
||||||
|
:group -> create_group(args, additional)
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, activity} <- create_activity(create_data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
# {:ok, actor} <- Actors.increase_event_count(actor) do
|
{:ok, activity, entity}
|
||||||
{:ok, activity, object}
|
|
||||||
else
|
else
|
||||||
err ->
|
err ->
|
||||||
Logger.error("Something went wrong while creating an activity")
|
Logger.error("Something went wrong while creating an activity")
|
||||||
|
@ -155,21 +153,52 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do
|
@doc """
|
||||||
# only accept false as false value
|
Create an activity of type `Update`
|
||||||
local = !(params[:local] == false)
|
|
||||||
|
|
||||||
with data <- %{
|
* Updates the object, which returns AS data
|
||||||
"to" => to,
|
* Wraps ActivityStreams data into a `Update` activity
|
||||||
"type" => "Accept",
|
* Creates an `Mobilizon.Service.ActivityPub.Activity` from this
|
||||||
"actor" => actor,
|
* Federates (asynchronously) the activity
|
||||||
"object" => object,
|
* Returns the activity
|
||||||
"id" => activity_wrapper_id || get_url(object) <> "/activity"
|
"""
|
||||||
},
|
@spec update(atom(), struct(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
|
||||||
{:ok, activity} <- create_activity(data, local),
|
def update(type, old_entity, args, local \\ false, additional \\ %{}) do
|
||||||
{:ok, object} <- insert_full_object(data),
|
Logger.debug("updating an activity")
|
||||||
|
Logger.debug(inspect(args))
|
||||||
|
|
||||||
|
{:ok, entity, update_data} =
|
||||||
|
case type do
|
||||||
|
:event -> update_event(old_entity, args, additional)
|
||||||
|
:actor -> update_actor(old_entity, args, additional)
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, activity} <- create_activity(update_data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, entity}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.error("Something went wrong while creating an activity")
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def accept(type, entity, args, local \\ false, additional \\ %{}) do
|
||||||
|
{:ok, entity, update_data} =
|
||||||
|
case type do
|
||||||
|
:join -> accept_join(entity, args, additional)
|
||||||
|
:follow -> accept_follow(entity, args, additional)
|
||||||
|
end
|
||||||
|
|
||||||
|
with {:ok, activity} <- create_activity(update_data, local),
|
||||||
|
:ok <- maybe_federate(activity) do
|
||||||
|
{:ok, activity, entity}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.error("Something went wrong while creating an activity")
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -191,25 +220,6 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
|
||||||
# only accept false as false value
|
|
||||||
local = !(params[:local] == false)
|
|
||||||
|
|
||||||
with data <- %{
|
|
||||||
"to" => to,
|
|
||||||
"cc" => cc,
|
|
||||||
"id" => object["url"],
|
|
||||||
"type" => "Update",
|
|
||||||
"actor" => actor,
|
|
||||||
"object" => object
|
|
||||||
},
|
|
||||||
{:ok, activity} <- create_activity(data, local),
|
|
||||||
{:ok, object} <- update_object(object["id"], data),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity, object}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
|
||||||
# def like(
|
# def like(
|
||||||
# %Actor{url: url} = actor,
|
# %Actor{url: url} = actor,
|
||||||
|
@ -290,15 +300,12 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
Make an actor follow another
|
Make an actor follow another
|
||||||
"""
|
"""
|
||||||
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||||
with {:ok, %Follower{url: follow_url}} <-
|
with {:ok, %Follower{} = follower} <-
|
||||||
Actors.follow(followed, follower, activity_id, false),
|
Actors.follow(followed, follower, activity_id, false),
|
||||||
activity_follow_id <-
|
follower_as_data <- Convertible.model_to_as(follower),
|
||||||
activity_id || follow_url,
|
{:ok, activity} <- create_activity(follower_as_data, local),
|
||||||
data <- make_follow_data(followed, follower, activity_follow_id),
|
|
||||||
{:ok, activity} <- create_activity(data, local),
|
|
||||||
{:ok, object} <- insert_full_object(data),
|
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, follower}
|
||||||
else
|
else
|
||||||
{:error, err, msg} when err in [:already_following, :suspended] ->
|
{:error, err, msg} when err in [:already_following, :suspended] ->
|
||||||
{:error, msg}
|
{:error, msg}
|
||||||
|
@ -310,16 +317,11 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"""
|
"""
|
||||||
@spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
|
@spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
|
||||||
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||||
with {:ok, %Follower{id: follow_id}} <- Actors.unfollow(followed, follower),
|
with {:ok, %Follower{id: follow_id} = follow} <- Actors.unfollow(followed, follower),
|
||||||
# We recreate the follow activity
|
# We recreate the follow activity
|
||||||
data <-
|
follow_as_data <-
|
||||||
make_follow_data(
|
Convertible.model_to_as(%{follow | actor: follower, target_actor: followed}),
|
||||||
followed,
|
{:ok, follow_activity} <- create_activity(follow_as_data, local),
|
||||||
follower,
|
|
||||||
"#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity"
|
|
||||||
),
|
|
||||||
{:ok, follow_activity} <- create_activity(data, local),
|
|
||||||
{:ok, _object} <- insert_full_object(data),
|
|
||||||
activity_unfollow_id <-
|
activity_unfollow_id <-
|
||||||
activity_id || "#{MobilizonWeb.Endpoint.url()}/unfollow/#{follow_id}/activity",
|
activity_id || "#{MobilizonWeb.Endpoint.url()}/unfollow/#{follow_id}/activity",
|
||||||
unfollow_data <-
|
unfollow_data <-
|
||||||
|
@ -346,7 +348,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"id" => url <> "/delete"
|
"id" => url <> "/delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
with {:ok, _} <- Events.delete_event(event),
|
with {:ok, %Event{} = event} <- Events.delete_event(event),
|
||||||
{:ok, activity} <- create_activity(data, local),
|
{:ok, activity} <- create_activity(data, local),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, event}
|
{:ok, activity, event}
|
||||||
|
@ -362,11 +364,10 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
|
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
|
||||||
}
|
}
|
||||||
|
|
||||||
with {:ok, _} <- Events.delete_comment(comment),
|
with {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
|
||||||
{:ok, activity} <- create_activity(data, local),
|
{:ok, activity} <- create_activity(data, local),
|
||||||
{:ok, object} <- insert_full_object(data),
|
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, comment}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -379,11 +380,10 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
"to" => [url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
|
"to" => [url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
|
||||||
}
|
}
|
||||||
|
|
||||||
with {:ok, _} <- Actors.delete_actor(actor),
|
with {:ok, %Actor{} = actor} <- Actors.delete_actor(actor),
|
||||||
{:ok, activity} <- create_activity(data, local),
|
{:ok, activity} <- create_activity(data, local),
|
||||||
{:ok, object} <- insert_full_object(data),
|
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, actor}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -434,9 +434,9 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
{:ok, _object} <- insert_full_object(join_data),
|
{:ok, _object} <- insert_full_object(join_data),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
if role === :participant do
|
if role === :participant do
|
||||||
accept(
|
accept_join(
|
||||||
%{to: [actor.url], actor: event.organizer_actor.url, object: join_data["id"]},
|
participant,
|
||||||
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participant.id}"
|
%{}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -720,9 +720,233 @@ defmodule Mobilizon.Service.ActivityPub do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# # Whether the Public audience is in the activity's audience
|
# Get recipients for an activity or object
|
||||||
# defp is_public?(activity) do
|
@spec get_recipients(map()) :: list()
|
||||||
# "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
defp get_recipients(data) do
|
||||||
# (activity.data["cc"] || []))
|
(data["to"] || []) ++ (data["cc"] || [])
|
||||||
# end
|
end
|
||||||
|
|
||||||
|
@spec create_event(map(), map()) :: {:ok, map()}
|
||||||
|
defp create_event(args, additional) do
|
||||||
|
with args <- prepare_args_for_event(args),
|
||||||
|
{:ok, %Event{} = event} <- Events.create_event(args),
|
||||||
|
event_as_data <- Convertible.model_to_as(event),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(
|
||||||
|
event.organizer_actor,
|
||||||
|
args.mentions,
|
||||||
|
nil,
|
||||||
|
event.visibility
|
||||||
|
),
|
||||||
|
create_data <-
|
||||||
|
make_create_data(event_as_data, Map.merge(audience, additional)) do
|
||||||
|
{:ok, event, create_data}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_comment(map(), map()) :: {:ok, map()}
|
||||||
|
defp create_comment(args, additional) do
|
||||||
|
with args <- prepare_args_for_comment(args),
|
||||||
|
{:ok, %Comment{} = comment} <- Events.create_comment(args),
|
||||||
|
comment_as_data <- Convertible.model_to_as(comment),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(
|
||||||
|
comment.actor,
|
||||||
|
args.mentions,
|
||||||
|
args.in_reply_to_comment,
|
||||||
|
comment.visibility
|
||||||
|
),
|
||||||
|
create_data <-
|
||||||
|
make_create_data(comment_as_data, Map.merge(audience, additional)) do
|
||||||
|
{:ok, comment, create_data}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_group(map(), map()) :: {:ok, map()}
|
||||||
|
defp create_group(args, additional) do
|
||||||
|
with args <- prepare_args_for_group(args),
|
||||||
|
{:ok, %Actor{type: :Group} = group} <- Actors.create_group(args),
|
||||||
|
group_as_data <- Convertible.model_to_as(group),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(
|
||||||
|
args.creator_actor,
|
||||||
|
[],
|
||||||
|
nil,
|
||||||
|
:public
|
||||||
|
),
|
||||||
|
create_data <-
|
||||||
|
make_create_data(group_as_data, Map.merge(audience, additional)) do
|
||||||
|
{:ok, group, create_data}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update_event(Event.t(), map(), map()) ::
|
||||||
|
{:ok, Event.t(), Activity.t()} | any()
|
||||||
|
defp update_event(
|
||||||
|
%Event{} = old_event,
|
||||||
|
args,
|
||||||
|
additional
|
||||||
|
) do
|
||||||
|
with args <- prepare_args_for_event(args),
|
||||||
|
{:ok, %Event{} = new_event} <- Events.update_event(old_event, args),
|
||||||
|
event_as_data <- Convertible.model_to_as(new_event),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(
|
||||||
|
new_event.organizer_actor,
|
||||||
|
Map.get(args, :mentions, []),
|
||||||
|
nil,
|
||||||
|
new_event.visibility
|
||||||
|
),
|
||||||
|
update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do
|
||||||
|
{:ok, new_event, update_data}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.error("Something went wrong while creating an update activity")
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec update_actor(Actor.t(), map(), map()) ::
|
||||||
|
{:ok, Actor.t(), Activity.t()} | any()
|
||||||
|
defp update_actor(%Actor{} = old_actor, args, additional) do
|
||||||
|
with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args),
|
||||||
|
actor_as_data <- Convertible.model_to_as(new_actor),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(
|
||||||
|
new_actor,
|
||||||
|
[],
|
||||||
|
nil,
|
||||||
|
:public
|
||||||
|
),
|
||||||
|
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}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec accept_follow(Follower.t(), map(), map()) ::
|
||||||
|
{:ok, Follower.t(), Activity.t()} | any()
|
||||||
|
defp accept_follow(
|
||||||
|
%Follower{} = follower,
|
||||||
|
args,
|
||||||
|
additional
|
||||||
|
) do
|
||||||
|
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, args),
|
||||||
|
follower_as_data <- Convertible.model_to_as(follower),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(follower.target_actor),
|
||||||
|
update_data <-
|
||||||
|
make_update_data(
|
||||||
|
follower_as_data,
|
||||||
|
Map.merge(Map.merge(audience, additional), %{
|
||||||
|
"id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}"
|
||||||
|
})
|
||||||
|
) do
|
||||||
|
{:ok, follower, update_data}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.error("Something went wrong while creating an update activity")
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec accept_join(Participant.t(), map(), map()) ::
|
||||||
|
{:ok, Participant.t(), Activity.t()} | any()
|
||||||
|
defp accept_join(
|
||||||
|
%Participant{} = participant,
|
||||||
|
args,
|
||||||
|
additional \\ %{}
|
||||||
|
) do
|
||||||
|
with {:ok, %Participant{} = participant} <- Events.update_participant(participant, args),
|
||||||
|
participant_as_data <- Convertible.model_to_as(participant),
|
||||||
|
audience <-
|
||||||
|
Audience.calculate_to_and_cc_from_mentions(participant.actor),
|
||||||
|
update_data <-
|
||||||
|
make_accept_join_data(
|
||||||
|
participant_as_data,
|
||||||
|
Map.merge(Map.merge(audience, additional), %{
|
||||||
|
"id" => "#{MobilizonWeb.Endpoint.url()}/accept/join/#{participant.id}"
|
||||||
|
})
|
||||||
|
) do
|
||||||
|
{:ok, participant, update_data}
|
||||||
|
else
|
||||||
|
err ->
|
||||||
|
Logger.error("Something went wrong while creating an update activity")
|
||||||
|
Logger.debug(inspect(err))
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prepare and sanitize arguments for events
|
||||||
|
defp prepare_args_for_event(args) do
|
||||||
|
# If title is not set: we are not updating it
|
||||||
|
args =
|
||||||
|
if Map.has_key?(args, :title) && !is_nil(args.title),
|
||||||
|
do: Map.update(args, :title, "", &String.trim(HtmlSanitizeEx.strip_tags(&1))),
|
||||||
|
else: args
|
||||||
|
|
||||||
|
# If we've been given a description (we might not get one if updating)
|
||||||
|
# sanitize it, HTML it, and extract tags & mentions from it
|
||||||
|
args =
|
||||||
|
if Map.has_key?(args, :description) && !is_nil(args.description) do
|
||||||
|
{description, mentions, tags} =
|
||||||
|
APIUtils.make_content_html(
|
||||||
|
String.trim(args.description),
|
||||||
|
Map.get(args, :tags, []),
|
||||||
|
"text/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
mentions = ConverterUtils.fetch_mentions(Map.get(args, :mentions, []) ++ mentions)
|
||||||
|
|
||||||
|
Map.merge(args, %{
|
||||||
|
description: description,
|
||||||
|
mentions: mentions,
|
||||||
|
tags: tags
|
||||||
|
})
|
||||||
|
else
|
||||||
|
args
|
||||||
|
end
|
||||||
|
|
||||||
|
Map.update(args, :tags, [], &ConverterUtils.fetch_tags/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prepare and sanitize arguments for comments
|
||||||
|
defp prepare_args_for_comment(args) do
|
||||||
|
with in_reply_to_comment <-
|
||||||
|
args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment(),
|
||||||
|
args <- Map.update(args, :visibility, :public, & &1),
|
||||||
|
{text, mentions, tags} <-
|
||||||
|
APIUtils.make_content_html(
|
||||||
|
args |> Map.get(:text, "") |> String.trim(),
|
||||||
|
# Can't put additional tags on a comment
|
||||||
|
[],
|
||||||
|
"text/html"
|
||||||
|
),
|
||||||
|
tags <- ConverterUtils.fetch_tags(tags),
|
||||||
|
mentions <- Map.get(args, :mentions, []) ++ ConverterUtils.fetch_mentions(mentions),
|
||||||
|
args <-
|
||||||
|
Map.merge(args, %{
|
||||||
|
actor_id: Map.get(args, :actor_id),
|
||||||
|
text: text,
|
||||||
|
mentions: mentions,
|
||||||
|
tags: tags,
|
||||||
|
in_reply_to_comment: in_reply_to_comment,
|
||||||
|
in_reply_to_comment_id:
|
||||||
|
if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id))
|
||||||
|
}) do
|
||||||
|
args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_args_for_group(args) do
|
||||||
|
with preferred_username <-
|
||||||
|
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
|
||||||
|
summary <- args |> Map.get(:summary, "") |> String.trim(),
|
||||||
|
{summary, _mentions, _tags} <-
|
||||||
|
summary |> String.trim() |> APIUtils.make_content_html([], "text/html") do
|
||||||
|
%{args | preferred_username: preferred_username, summary: summary}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
98
lib/service/activity_pub/audience.ex
Normal file
98
lib/service/activity_pub/audience.ex
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
defmodule Mobilizon.Service.ActivityPub.Audience do
|
||||||
|
@moduledoc """
|
||||||
|
Tools for calculating content audience
|
||||||
|
"""
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
|
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determines the full audience based on mentions for a public audience
|
||||||
|
|
||||||
|
Audience is:
|
||||||
|
* `to` : the mentioned actors, the eventual actor we're replying to and the public
|
||||||
|
* `cc` : the actor's followers
|
||||||
|
"""
|
||||||
|
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
||||||
|
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :public) do
|
||||||
|
to = [@ap_public | mentions]
|
||||||
|
cc = [actor.followers_url]
|
||||||
|
|
||||||
|
if in_reply_to do
|
||||||
|
{Enum.uniq([in_reply_to.actor | to]), cc}
|
||||||
|
else
|
||||||
|
{to, cc}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determines the full audience based on mentions based on a unlisted audience
|
||||||
|
|
||||||
|
Audience is:
|
||||||
|
* `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
|
||||||
|
* `cc` : public
|
||||||
|
"""
|
||||||
|
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
||||||
|
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :unlisted) do
|
||||||
|
to = [actor.followers_url | mentions]
|
||||||
|
cc = [@ap_public]
|
||||||
|
|
||||||
|
if in_reply_to do
|
||||||
|
{Enum.uniq([in_reply_to.actor | to]), cc}
|
||||||
|
else
|
||||||
|
{to, cc}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determines the full audience based on mentions based on a private audience
|
||||||
|
|
||||||
|
Audience is:
|
||||||
|
* `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
|
||||||
|
* `cc` : none
|
||||||
|
"""
|
||||||
|
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
||||||
|
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :private) do
|
||||||
|
{to, cc} = get_to_and_cc(actor, mentions, in_reply_to, :direct)
|
||||||
|
{[actor.followers_url | to], cc}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Determines the full audience based on mentions based on a direct audience
|
||||||
|
|
||||||
|
Audience is:
|
||||||
|
* `to` : the mentioned actors and the eventual actor we're replying to
|
||||||
|
* `cc` : none
|
||||||
|
"""
|
||||||
|
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
|
||||||
|
def get_to_and_cc(_actor, mentions, in_reply_to, :direct) do
|
||||||
|
if in_reply_to do
|
||||||
|
{Enum.uniq([in_reply_to.actor | mentions]), []}
|
||||||
|
else
|
||||||
|
{mentions, []}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_to_and_cc(_actor, mentions, _in_reply_to, {:list, _}) do
|
||||||
|
{mentions, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
# def get_addressed_actors(_, to) when is_list(to) do
|
||||||
|
# Actors.get(to)
|
||||||
|
# end
|
||||||
|
|
||||||
|
def get_addressed_actors(mentioned_users, _), do: mentioned_users
|
||||||
|
|
||||||
|
def calculate_to_and_cc_from_mentions(
|
||||||
|
actor,
|
||||||
|
mentions \\ [],
|
||||||
|
in_reply_to \\ nil,
|
||||||
|
visibility \\ :public
|
||||||
|
) do
|
||||||
|
with mentioned_actors <- for({_, mentioned_actor} <- mentions, do: mentioned_actor.url),
|
||||||
|
addressed_actors <- get_addressed_actors(mentioned_actors, nil),
|
||||||
|
{to, cc} <- get_to_and_cc(actor, addressed_actors, in_reply_to, visibility) do
|
||||||
|
%{"to" => to, "cc" => cc}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -37,17 +37,18 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
|
||||||
"url" => object["image"]["url"]
|
"url" => object["image"]["url"]
|
||||||
}
|
}
|
||||||
|
|
||||||
%{
|
{:ok,
|
||||||
"type" => String.to_existing_atom(object["type"]),
|
%{
|
||||||
"preferred_username" => object["preferredUsername"],
|
"type" => String.to_existing_atom(object["type"]),
|
||||||
"summary" => object["summary"],
|
"preferred_username" => object["preferredUsername"],
|
||||||
"url" => object["id"],
|
"summary" => object["summary"],
|
||||||
"name" => object["name"],
|
"url" => object["id"],
|
||||||
"avatar" => avatar,
|
"name" => object["name"],
|
||||||
"banner" => banner,
|
"avatar" => avatar,
|
||||||
"keys" => object["publicKey"]["publicKeyPem"],
|
"banner" => banner,
|
||||||
"manually_approves_followers" => object["manuallyApprovesFollowers"]
|
"keys" => object["publicKey"]["publicKeyPem"],
|
||||||
}
|
"manually_approves_followers" => object["manuallyApprovesFollowers"]
|
||||||
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
|
alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
|
||||||
|
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -26,57 +27,67 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||||
Converts an AP object data to our internal data structure.
|
Converts an AP object data to our internal data structure.
|
||||||
"""
|
"""
|
||||||
@impl Converter
|
@impl Converter
|
||||||
@spec as_to_model_data(map) :: map
|
@spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
|
||||||
def as_to_model_data(object) do
|
def as_to_model_data(object) do
|
||||||
{:ok, %Actor{id: actor_id}} = ActivityPub.get_or_fetch_by_url(object["actor"])
|
Logger.debug("We're converting raw ActivityStream data to a comment entity")
|
||||||
|
|
||||||
Logger.debug("Inserting full comment")
|
|
||||||
Logger.debug(inspect(object))
|
Logger.debug(inspect(object))
|
||||||
|
|
||||||
data = %{
|
with {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]),
|
||||||
"text" => object["content"],
|
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
|
||||||
"url" => object["id"],
|
{:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])} do
|
||||||
"actor_id" => actor_id,
|
Logger.debug("Inserting full comment")
|
||||||
"in_reply_to_comment_id" => nil,
|
Logger.debug(inspect(object))
|
||||||
"event_id" => nil,
|
|
||||||
"uuid" => object["uuid"]
|
|
||||||
}
|
|
||||||
|
|
||||||
# We fetch the parent object
|
data = %{
|
||||||
Logger.debug("We're fetching the parent object")
|
text: object["content"],
|
||||||
|
url: object["id"],
|
||||||
|
actor_id: actor_id,
|
||||||
|
in_reply_to_comment_id: nil,
|
||||||
|
event_id: nil,
|
||||||
|
uuid: object["uuid"],
|
||||||
|
tags: tags,
|
||||||
|
mentions: mentions
|
||||||
|
}
|
||||||
|
|
||||||
data =
|
# We fetch the parent object
|
||||||
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
|
Logger.debug("We're fetching the parent object")
|
||||||
object["inReplyTo"] != "" do
|
|
||||||
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
|
|
||||||
|
|
||||||
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
data =
|
||||||
# Reply to an event (Event)
|
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
|
||||||
{:ok, %Event{id: id}} ->
|
object["inReplyTo"] != "" do
|
||||||
Logger.debug("Parent object is an event")
|
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
|
||||||
data |> Map.put("event_id", id)
|
|
||||||
|
|
||||||
# Reply to a comment (Comment)
|
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
||||||
{:ok, %CommentModel{id: id} = comment} ->
|
# Reply to an event (Event)
|
||||||
Logger.debug("Parent object is another comment")
|
{:ok, %Event{id: id}} ->
|
||||||
|
Logger.debug("Parent object is an event")
|
||||||
|
data |> Map.put(:event_id, id)
|
||||||
|
|
||||||
data
|
# Reply to a comment (Comment)
|
||||||
|> Map.put("in_reply_to_comment_id", id)
|
{:ok, %CommentModel{id: id} = comment} ->
|
||||||
|> Map.put("origin_comment_id", comment |> CommentModel.get_thread_id())
|
Logger.debug("Parent object is another comment")
|
||||||
|
|
||||||
# Anything else is kind of a MP
|
data
|
||||||
{:error, parent} ->
|
|> Map.put(:in_reply_to_comment_id, id)
|
||||||
Logger.debug("Parent object is something we don't handle")
|
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
|
||||||
Logger.debug(inspect(parent))
|
|
||||||
data
|
# Anything else is kind of a MP
|
||||||
|
{:error, parent} ->
|
||||||
|
Logger.debug("Parent object is something we don't handle")
|
||||||
|
Logger.debug(inspect(parent))
|
||||||
|
data
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Logger.debug("No parent object for this comment")
|
||||||
|
|
||||||
|
data
|
||||||
end
|
end
|
||||||
else
|
|
||||||
Logger.debug("No parent object for this comment")
|
|
||||||
|
|
||||||
data
|
{:ok, data}
|
||||||
end
|
else
|
||||||
|
err ->
|
||||||
data
|
{:error, err}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -85,14 +96,22 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
|
||||||
@impl Converter
|
@impl Converter
|
||||||
@spec model_to_as(CommentModel.t()) :: map
|
@spec model_to_as(CommentModel.t()) :: map
|
||||||
def model_to_as(%CommentModel{} = comment) do
|
def model_to_as(%CommentModel{} = comment) do
|
||||||
|
to =
|
||||||
|
if comment.visibility == :public,
|
||||||
|
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
else: [comment.actor.followers_url]
|
||||||
|
|
||||||
object = %{
|
object = %{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
"to" => to,
|
||||||
|
"cc" => [],
|
||||||
"content" => comment.text,
|
"content" => comment.text,
|
||||||
"actor" => comment.actor.url,
|
"actor" => comment.actor.url,
|
||||||
"attributedTo" => comment.actor.url,
|
"attributedTo" => comment.actor.url,
|
||||||
"uuid" => comment.uuid,
|
"uuid" => comment.uuid,
|
||||||
"id" => comment.url
|
"id" => comment.url,
|
||||||
|
"tag" =>
|
||||||
|
ConverterUtils.build_mentions(comment.mentions) ++ ConverterUtils.build_tags(comment.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
if comment.in_reply_to_comment do
|
if comment.in_reply_to_comment do
|
||||||
|
|
|
@ -6,15 +6,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||||
internal one, and back.
|
internal one, and back.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Addresses, Events, Media}
|
alias Mobilizon.{Addresses, Media}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Events.Event, as: EventModel
|
alias Mobilizon.Events.Event, as: EventModel
|
||||||
alias Mobilizon.Events.{EventOptions, Tag}
|
alias Mobilizon.Events.EventOptions
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Media.Picture
|
||||||
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
|
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
|
||||||
alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter
|
alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter
|
||||||
alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter
|
alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter
|
||||||
|
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -30,16 +32,16 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||||
Converts an AP object data to our internal data structure.
|
Converts an AP object data to our internal data structure.
|
||||||
"""
|
"""
|
||||||
@impl Converter
|
@impl Converter
|
||||||
@spec as_to_model_data(map) :: map
|
@spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
|
||||||
def as_to_model_data(object) do
|
def as_to_model_data(object) do
|
||||||
Logger.debug("event as_to_model_data")
|
Logger.debug("event as_to_model_data")
|
||||||
Logger.debug(inspect(object))
|
Logger.debug(inspect(object))
|
||||||
|
|
||||||
with {:actor, {:ok, %Actor{id: actor_id}}} <-
|
with {:actor, {:ok, %Actor{id: actor_id}}} <-
|
||||||
{:actor, Actors.get_actor_by_url(object["actor"])},
|
{:actor, ActivityPub.get_or_fetch_actor_by_url(object["actor"])},
|
||||||
{:address, address_id} <-
|
{:address, address_id} <-
|
||||||
{:address, get_address(object["location"])},
|
{:address, get_address(object["location"])},
|
||||||
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
|
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
|
||||||
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
||||||
{:options, options} <- {:options, get_options(object)} do
|
{:options, options} <- {:options, get_options(object)} do
|
||||||
picture_id =
|
picture_id =
|
||||||
|
@ -58,26 +60,27 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||||
end
|
end
|
||||||
|
|
||||||
entity = %{
|
entity = %{
|
||||||
"title" => object["name"],
|
title: object["name"],
|
||||||
"description" => object["content"],
|
description: object["content"],
|
||||||
"organizer_actor_id" => actor_id,
|
organizer_actor_id: actor_id,
|
||||||
"picture_id" => picture_id,
|
picture_id: picture_id,
|
||||||
"begins_on" => object["startTime"],
|
begins_on: object["startTime"],
|
||||||
"ends_on" => object["endTime"],
|
ends_on: object["endTime"],
|
||||||
"category" => object["category"],
|
category: object["category"],
|
||||||
"visibility" => visibility,
|
visibility: visibility,
|
||||||
"join_options" => object["joinOptions"],
|
join_options: Map.get(object, "joinOptions", "free"),
|
||||||
"status" => object["status"],
|
options: options,
|
||||||
"online_address" => object["onlineAddress"],
|
status: object["status"],
|
||||||
"phone_address" => object["phoneAddress"],
|
online_address: object["onlineAddress"],
|
||||||
"draft" => object["draft"] || false,
|
phone_address: object["phoneAddress"],
|
||||||
"url" => object["id"],
|
draft: object["draft"] || false,
|
||||||
"uuid" => object["uuid"],
|
url: object["id"],
|
||||||
"tags" => tags,
|
uuid: object["uuid"],
|
||||||
"physical_address_id" => address_id
|
tags: tags,
|
||||||
|
physical_address_id: address_id
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, Map.put(entity, "options", options)}
|
{:ok, entity}
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
{:error, error}
|
{:error, error}
|
||||||
|
@ -111,7 +114,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||||
"startTime" => event.begins_on |> date_to_string(),
|
"startTime" => event.begins_on |> date_to_string(),
|
||||||
"joinOptions" => to_string(event.join_options),
|
"joinOptions" => to_string(event.join_options),
|
||||||
"endTime" => event.ends_on |> date_to_string(),
|
"endTime" => event.ends_on |> date_to_string(),
|
||||||
"tag" => event.tags |> build_tags(),
|
"tag" => event.tags |> ConverterUtils.build_tags(),
|
||||||
"draft" => event.draft,
|
"draft" => event.draft,
|
||||||
"id" => event.url,
|
"id" => event.url,
|
||||||
"url" => event.url
|
"url" => event.url
|
||||||
|
@ -181,32 +184,6 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec fetch_tags([String.t()]) :: [String.t()]
|
|
||||||
defp fetch_tags(tags) do
|
|
||||||
Logger.debug("fetching tags")
|
|
||||||
|
|
||||||
Enum.reduce(tags, [], fn tag, acc ->
|
|
||||||
with true <- tag["type"] == "Hashtag",
|
|
||||||
{:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do
|
|
||||||
acc ++ [tag]
|
|
||||||
else
|
|
||||||
_err ->
|
|
||||||
acc
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec build_tags([String.t()]) :: String.t()
|
|
||||||
defp build_tags(tags) do
|
|
||||||
Enum.map(tags, fn %Tag{} = tag ->
|
|
||||||
%{
|
|
||||||
"href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}",
|
|
||||||
"name" => "##{tag.title}",
|
|
||||||
"type" => "Hashtag"
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
defp get_visibility(object) do
|
defp get_visibility(object) do
|
||||||
|
|
36
lib/service/activity_pub/converter/follower.ex
Normal file
36
lib/service/activity_pub/converter/follower.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
defmodule Mobilizon.Service.ActivityPub.Converter.Follower do
|
||||||
|
@moduledoc """
|
||||||
|
Participant converter.
|
||||||
|
|
||||||
|
This module allows to convert followers from ActivityStream format to our own
|
||||||
|
internal one, and back.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Follower, as: FollowerModel
|
||||||
|
|
||||||
|
alias Mobilizon.Service.ActivityPub.Convertible
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
|
defimpl Convertible, for: FollowerModel do
|
||||||
|
alias Mobilizon.Service.ActivityPub.Converter.Follower, as: FollowerConverter
|
||||||
|
|
||||||
|
defdelegate model_to_as(follower), to: FollowerConverter
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Convert an follow struct to an ActivityStream representation.
|
||||||
|
"""
|
||||||
|
@spec model_to_as(FollowerModel.t()) :: map
|
||||||
|
def model_to_as(
|
||||||
|
%FollowerModel{actor: %Actor{} = actor, target_actor: %Actor{} = target_actor} = follower
|
||||||
|
) do
|
||||||
|
%{
|
||||||
|
"type" => "Follow",
|
||||||
|
"actor" => actor.url,
|
||||||
|
"to" => [target_actor.url],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"object" => target_actor.url,
|
||||||
|
"id" => follower.url
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
100
lib/service/activity_pub/converter/utils.ex
Normal file
100
lib/service/activity_pub/converter/utils.ex
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
defmodule Mobilizon.Service.ActivityPub.Converter.Utils do
|
||||||
|
@moduledoc """
|
||||||
|
Various utils for converters
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events.Tag
|
||||||
|
alias Mobilizon.Mention
|
||||||
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@spec fetch_tags([String.t()]) :: [Tag.t()]
|
||||||
|
def fetch_tags(tags) when is_list(tags) do
|
||||||
|
Logger.debug("fetching tags")
|
||||||
|
|
||||||
|
Enum.reduce(tags, [], &fetch_tag/2)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec fetch_mentions([map()]) :: [map()]
|
||||||
|
def fetch_mentions(mentions) when is_list(mentions) do
|
||||||
|
Logger.debug("fetching mentions")
|
||||||
|
|
||||||
|
Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_address(%{id: id}) do
|
||||||
|
with {id, ""} <- Integer.parse(id) do
|
||||||
|
%{id: id}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_address(address) when is_map(address) do
|
||||||
|
address
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec build_tags([Tag.t()]) :: [Map.t()]
|
||||||
|
def build_tags(tags) do
|
||||||
|
Enum.map(tags, fn %Tag{} = tag ->
|
||||||
|
%{
|
||||||
|
"href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}",
|
||||||
|
"name" => "##{tag.title}",
|
||||||
|
"type" => "Hashtag"
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_mentions(mentions) do
|
||||||
|
Enum.map(mentions, fn %Mention{} = mention ->
|
||||||
|
if Ecto.assoc_loaded?(mention.actor) do
|
||||||
|
build_mention(mention.actor)
|
||||||
|
else
|
||||||
|
build_mention(Repo.preload(mention, [:actor]).actor)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_mention(%Actor{} = actor) do
|
||||||
|
%{
|
||||||
|
"href" => actor.url,
|
||||||
|
"name" => "@#{Mobilizon.Actors.Actor.preferred_username_and_domain(actor)}",
|
||||||
|
"type" => "Mention"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_tag(tag, acc) when is_map(tag) do
|
||||||
|
case tag["type"] do
|
||||||
|
"Hashtag" ->
|
||||||
|
acc ++ [%{title: tag}]
|
||||||
|
|
||||||
|
_err ->
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fetch_tag(tag, acc) when is_bitstring(tag) do
|
||||||
|
acc ++ [%{title: tag}]
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_mention(map(), list()) :: list()
|
||||||
|
defp create_mention(%Actor{id: actor_id} = _mention, acc) do
|
||||||
|
acc ++ [%{actor_id: actor_id}]
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_mention(map(), list()) :: list()
|
||||||
|
defp create_mention(mention, acc) when is_map(mention) do
|
||||||
|
with true <- mention["type"] == "Mention",
|
||||||
|
{:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(mention["href"]) do
|
||||||
|
acc ++ [%{actor_id: actor_id}]
|
||||||
|
else
|
||||||
|
_err ->
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec create_mention({String.t(), map()}, list()) :: list()
|
||||||
|
defp create_mention({_, mention}, acc) when is_map(mention) do
|
||||||
|
create_mention(mention, acc)
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,7 +26,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
||||||
|
|
||||||
def follow(target_instance) do
|
def follow(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.follow(local_actor, target_actor) do
|
{:ok, activity} <- Follows.follow(local_actor, target_actor) do
|
||||||
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -39,7 +39,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
||||||
|
|
||||||
def unfollow(target_instance) do
|
def unfollow(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
|
{:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
|
||||||
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
|
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -52,7 +52,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
||||||
|
|
||||||
def accept(target_instance) do
|
def accept(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.accept(target_actor, local_actor) do
|
{:ok, activity} <- Follows.accept(target_actor, local_actor) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
@ -60,7 +60,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
||||||
|
|
||||||
# def reject(target_instance) do
|
# def reject(target_instance) do
|
||||||
# with %Actor{} = local_actor <- get_actor(),
|
# with %Actor{} = local_actor <- get_actor(),
|
||||||
# {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_by_url(target_instance),
|
# {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_actor_by_url(target_instance),
|
||||||
# {:ok, activity} <- Follows.reject(target_actor, local_actor) do
|
# {:ok, activity} <- Follows.reject(target_actor, local_actor) do
|
||||||
# {:ok, activity}
|
# {:ok, activity}
|
||||||
# end
|
# end
|
||||||
|
|
|
@ -13,9 +13,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Comment, Event, Participant}
|
alias Mobilizon.Events.{Comment, Event, Participant}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils, Visibility}
|
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Utils}
|
||||||
alias MobilizonWeb.Email.Participation
|
alias MobilizonWeb.Email.Participation
|
||||||
|
|
||||||
|
import Mobilizon.Service.ActivityPub.Utils
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def get_actor(%{"actor" => actor}) when is_binary(actor) do
|
def get_actor(%{"actor" => actor}) when is_binary(actor) do
|
||||||
|
@ -138,59 +140,65 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
@doc """
|
||||||
|
Handles a `Create` activity for `Note` (comments) objects
|
||||||
|
|
||||||
|
The following actions are performed
|
||||||
|
* Fetch the author of the activity
|
||||||
|
* Convert the ActivityStream data to the comment model format (it also finds and inserts tags)
|
||||||
|
* Get (by it's URL) or create the comment with this data
|
||||||
|
* Insert eventual mentions in the database
|
||||||
|
* Convert the comment back in ActivityStreams data
|
||||||
|
* Wrap this data back into a `Create` activity
|
||||||
|
* Return the activity and the comment object
|
||||||
|
"""
|
||||||
|
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object}) do
|
||||||
Logger.info("Handle incoming to create notes")
|
Logger.info("Handle incoming to create notes")
|
||||||
|
|
||||||
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
|
with {:ok, object_data} <-
|
||||||
Logger.debug("found actor")
|
object |> fix_object() |> Converter.Comment.as_to_model_data(),
|
||||||
Logger.debug(inspect(actor))
|
{:existing_comment, {:error, :comment_not_found}} <-
|
||||||
|
{:existing_comment, Events.get_comment_from_url_with_preload(object_data.url)},
|
||||||
params = %{
|
{:ok, %Activity{} = activity, %Comment{} = comment} <-
|
||||||
to: data["to"],
|
ActivityPub.create(:comment, object_data, false) do
|
||||||
object: object |> fix_object,
|
{:ok, activity, comment}
|
||||||
actor: actor,
|
else
|
||||||
local: false,
|
{:existing_comment, {:ok, %Comment{} = comment}} ->
|
||||||
published: data["published"],
|
{:ok, nil, comment}
|
||||||
additional:
|
|
||||||
Map.take(data, [
|
|
||||||
"cc",
|
|
||||||
"id"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityPub.create(params)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do
|
@doc """
|
||||||
|
Handles a `Create` activity for `Event` objects
|
||||||
|
|
||||||
|
The following actions are performed
|
||||||
|
* Fetch the author of the activity
|
||||||
|
* Convert the ActivityStream data to the event model format (it also finds and inserts tags)
|
||||||
|
* Get (by it's URL) or create the event with this data
|
||||||
|
* Insert eventual mentions in the database
|
||||||
|
* Convert the event back in ActivityStreams data
|
||||||
|
* Wrap this data back into a `Create` activity
|
||||||
|
* Return the activity and the event object
|
||||||
|
"""
|
||||||
|
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object}) do
|
||||||
Logger.info("Handle incoming to create event")
|
Logger.info("Handle incoming to create event")
|
||||||
|
|
||||||
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
|
with {:ok, object_data} <-
|
||||||
Logger.debug("found actor")
|
object |> fix_object() |> Converter.Event.as_to_model_data(),
|
||||||
Logger.debug(inspect(actor))
|
{:existing_event, nil} <- {:existing_event, Events.get_event_by_url(object_data.url)},
|
||||||
|
{:ok, %Activity{} = activity, %Event{} = event} <-
|
||||||
params = %{
|
ActivityPub.create(:event, object_data, false) do
|
||||||
to: data["to"],
|
{:ok, activity, event}
|
||||||
object: object |> fix_object,
|
else
|
||||||
actor: actor,
|
{:existing_event, %Event{} = event} -> {:ok, nil, event}
|
||||||
local: false,
|
|
||||||
published: data["published"],
|
|
||||||
additional:
|
|
||||||
Map.take(data, [
|
|
||||||
"cc",
|
|
||||||
"id"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityPub.create(params)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
|
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
|
||||||
) do
|
) do
|
||||||
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_by_url(followed, true),
|
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_actor_by_url(followed, true),
|
||||||
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_by_url(follower),
|
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_actor_by_url(follower),
|
||||||
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
|
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
else
|
else
|
||||||
|
@ -209,8 +217,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
with actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
|
||||||
{:object_not_found, {:ok, activity, object}} <-
|
{:object_not_found, {:ok, %Activity{} = activity, object}} <-
|
||||||
{:object_not_found,
|
{:object_not_found,
|
||||||
do_handle_incoming_accept_following(accepted_object, actor) ||
|
do_handle_incoming_accept_following(accepted_object, actor) ||
|
||||||
do_handle_incoming_accept_join(accepted_object, actor)} do
|
do_handle_incoming_accept_join(accepted_object, actor)} do
|
||||||
|
@ -238,7 +246,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
|
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
|
||||||
) do
|
) do
|
||||||
with actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
|
||||||
{:object_not_found, {:ok, activity, object}} <-
|
{:object_not_found, {:ok, activity, object}} <-
|
||||||
{:object_not_found,
|
{:object_not_found,
|
||||||
do_handle_incoming_reject_following(rejected_object, actor) ||
|
do_handle_incoming_reject_following(rejected_object, actor) ||
|
||||||
|
@ -278,13 +286,20 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
# end
|
# end
|
||||||
# #
|
# #
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
|
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => _id} = data
|
||||||
) do
|
) do
|
||||||
with actor <- get_actor(data),
|
with actor <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
|
# TODO: Is the following line useful?
|
||||||
|
{:ok, %Actor{} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||||
|
:ok <- Logger.debug("Fetching contained object"),
|
||||||
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
||||||
public <- Visibility.is_public?(data),
|
:ok <- Logger.debug("Handling contained object"),
|
||||||
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false, public) do
|
create_data <-
|
||||||
|
make_create_data(object),
|
||||||
|
:ok <- Logger.debug(inspect(object)),
|
||||||
|
{:ok, _activity, object} <- handle_incoming(create_data),
|
||||||
|
:ok <- Logger.debug("Finished processing contained object"),
|
||||||
|
{:ok, activity} <- ActivityPub.create_activity(data, false) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
|
@ -293,21 +308,19 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(%{
|
||||||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} =
|
"type" => "Update",
|
||||||
data
|
"object" => %{"type" => object_type} = object,
|
||||||
)
|
"actor" => _actor_id
|
||||||
|
})
|
||||||
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
|
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
|
||||||
case Actors.get_actor_by_url(object["id"]) do
|
with {:ok, %Actor{} = old_actor} <- Actors.get_actor_by_url(object["id"]),
|
||||||
{:ok, %Actor{url: actor_url}} ->
|
{:ok, object_data} <-
|
||||||
ActivityPub.update(%{
|
object |> fix_object() |> Converter.Actor.as_to_model_data(),
|
||||||
local: false,
|
{:ok, %Activity{} = activity, %Actor{} = new_actor} <-
|
||||||
to: data["to"] || [],
|
ActivityPub.update(:actor, old_actor, object_data, false) do
|
||||||
cc: data["cc"] || [],
|
{:ok, activity, new_actor}
|
||||||
object: object,
|
else
|
||||||
actor: actor_url
|
|
||||||
})
|
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
Logger.debug(inspect(e))
|
Logger.debug(inspect(e))
|
||||||
:error
|
:error
|
||||||
|
@ -315,24 +328,19 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => actor} =
|
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => _actor} =
|
||||||
_update
|
_update
|
||||||
) do
|
) do
|
||||||
with {:ok, %{"actor" => existing_organizer_actor_url} = existing_event_data} <-
|
with %Event{} = old_event <-
|
||||||
fetch_obj_helper_as_activity_streams(object),
|
Events.get_event_by_url(object["id"]),
|
||||||
object <- Map.merge(existing_event_data, object),
|
{:ok, object_data} <-
|
||||||
{:ok, %Actor{url: actor_url}} <- actor |> Utils.get_url() |> Actors.get_actor_by_url(),
|
object |> fix_object() |> Converter.Event.as_to_model_data(),
|
||||||
true <- Utils.get_url(existing_organizer_actor_url) == actor_url do
|
{:ok, %Activity{} = activity, %Event{} = new_event} <-
|
||||||
ActivityPub.update(%{
|
ActivityPub.update(:event, old_event, object_data, false) do
|
||||||
local: false,
|
{:ok, activity, new_event}
|
||||||
to: object["to"] || [],
|
|
||||||
cc: object["cc"] || [],
|
|
||||||
object: object,
|
|
||||||
actor: actor_url
|
|
||||||
})
|
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
Logger.debug(inspect(e))
|
Logger.error(inspect(e))
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -350,7 +358,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
with actor <- get_actor(data),
|
with actor <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||||
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
||||||
{:ok, activity, object} <-
|
{:ok, activity, object} <-
|
||||||
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
|
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
|
||||||
|
@ -454,7 +462,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
# } = data
|
# } = data
|
||||||
# ) do
|
# ) do
|
||||||
# with actor <- get_actor(data),
|
# with actor <- get_actor(data),
|
||||||
# %Actor{} = actor <- ActivityPub.get_or_fetch_by_url(actor),
|
# %Actor{} = actor <- ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||||
# {:ok, object} <- fetch_obj_helper(object_id) || fetch_obj_helper(object_id),
|
# {:ok, object} <- fetch_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||||
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||||
# {:ok, activity}
|
# {:ok, activity}
|
||||||
|
@ -472,23 +480,16 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
Handle incoming `Accept` activities wrapping a `Follow` activity
|
Handle incoming `Accept` activities wrapping a `Follow` activity
|
||||||
"""
|
"""
|
||||||
def do_handle_incoming_accept_following(follow_object, %Actor{} = actor) do
|
def do_handle_incoming_accept_following(follow_object, %Actor{} = actor) do
|
||||||
with {:follow,
|
with {:follow, {:ok, %Follower{approved: false, target_actor: followed} = follow}} <-
|
||||||
{:ok,
|
|
||||||
%Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} =
|
|
||||||
follow}} <-
|
|
||||||
{:follow, get_follow(follow_object)},
|
{:follow, get_follow(follow_object)},
|
||||||
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
|
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
|
||||||
{:ok, activity, _} <-
|
{:ok, %Activity{} = activity, %Follower{approved: true} = follow} <-
|
||||||
ActivityPub.accept(
|
ActivityPub.accept(
|
||||||
%{
|
:follow,
|
||||||
to: [follower.url],
|
follow,
|
||||||
actor: actor.url,
|
%{approved: true},
|
||||||
object: follow_object,
|
false
|
||||||
local: false
|
) do
|
||||||
},
|
|
||||||
"#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}"
|
|
||||||
),
|
|
||||||
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
|
|
||||||
{:ok, activity, follow}
|
{:ok, activity, follow}
|
||||||
else
|
else
|
||||||
{:follow, _} ->
|
{:follow, _} ->
|
||||||
|
@ -546,26 +547,18 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
|
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
|
||||||
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
|
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
|
||||||
with {:join_event,
|
with {:join_event, {:ok, %Participant{role: :not_approved, event: event} = participant}} <-
|
||||||
{:ok,
|
|
||||||
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
|
|
||||||
participant}} <-
|
|
||||||
{:join_event, get_participant(join_object)},
|
{:join_event, get_participant(join_object)},
|
||||||
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
|
# 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
|
# 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},
|
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
|
||||||
{:ok, activity, _} <-
|
{:ok, %Activity{} = activity, %Participant{role: :participant} = participant} <-
|
||||||
ActivityPub.accept(
|
ActivityPub.accept(
|
||||||
%{
|
:join,
|
||||||
to: [actor.url],
|
participant,
|
||||||
actor: actor_accepting.url,
|
%{role: :participant},
|
||||||
object: join_object,
|
false
|
||||||
local: false
|
|
||||||
},
|
|
||||||
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{join_id}"
|
|
||||||
),
|
),
|
||||||
{:ok, %Participant{role: :participant}} <-
|
|
||||||
Events.update_participant(participant, %{"role" => :participant}),
|
|
||||||
:ok <-
|
:ok <-
|
||||||
Participation.send_emails_to_local_user(participant) do
|
Participation.send_emails_to_local_user(participant) do
|
||||||
{:ok, activity, participant}
|
{:ok, activity, participant}
|
||||||
|
@ -684,7 +677,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
def prepare_object(object) do
|
def prepare_object(object) do
|
||||||
object
|
object
|
||||||
# |> set_sensitive
|
# |> set_sensitive
|
||||||
|> add_hashtags
|
# |> add_hashtags
|
||||||
|> add_mention_tags
|
|> add_mention_tags
|
||||||
# |> add_emoji_tags
|
# |> add_emoji_tags
|
||||||
|> add_attributed_to
|
|> add_attributed_to
|
||||||
|
@ -781,6 +774,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_mention_tags(object) do
|
def add_mention_tags(object) do
|
||||||
|
Logger.debug("add mention tags")
|
||||||
|
Logger.debug(inspect(object))
|
||||||
|
|
||||||
recipients =
|
recipients =
|
||||||
(object["to"] ++ (object["cc"] || [])) -- ["https://www.w3.org/ns/activitystreams#Public"]
|
(object["to"] ++ (object["cc"] || [])) -- ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
|
@ -795,7 +791,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end)
|
end)
|
||||||
|> Enum.filter(& &1)
|
|> Enum.filter(& &1)
|
||||||
|> Enum.map(fn actor ->
|
|> Enum.map(fn actor ->
|
||||||
%{"type" => "Mention", "href" => actor.url, "name" => "@#{actor.preferred_username}"}
|
%{
|
||||||
|
"type" => "Mention",
|
||||||
|
"href" => actor.url,
|
||||||
|
"name" => "@#{Actor.preferred_username_and_domain(actor)}"
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
tags = object["tag"] || []
|
tags = object["tag"] || []
|
||||||
|
@ -854,6 +854,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
|
|
||||||
@spec fetch_obj_helper(map() | String.t()) :: Event.t() | Comment.t() | Actor.t() | any()
|
@spec fetch_obj_helper(map() | String.t()) :: Event.t() | Comment.t() | Actor.t() | any()
|
||||||
def fetch_obj_helper(object) do
|
def fetch_obj_helper(object) do
|
||||||
|
Logger.debug("fetch_obj_helper")
|
||||||
Logger.debug("Fetching object #{inspect(object)}")
|
Logger.debug("Fetching object #{inspect(object)}")
|
||||||
|
|
||||||
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
|
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
|
||||||
|
@ -867,6 +868,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_obj_helper_as_activity_streams(object) do
|
def fetch_obj_helper_as_activity_streams(object) do
|
||||||
|
Logger.debug("fetch_obj_helper_as_activity_streams")
|
||||||
|
|
||||||
with {:ok, object} <- fetch_obj_helper(object) do
|
with {:ok, object} <- fetch_obj_helper(object) do
|
||||||
{:ok, Convertible.model_to_as(object)}
|
{:ok, Convertible.model_to_as(object)}
|
||||||
end
|
end
|
||||||
|
|
|
@ -238,8 +238,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
@doc """
|
@doc """
|
||||||
Save picture data from %Plug.Upload{} and return AS Link data.
|
Save picture data from %Plug.Upload{} and return AS Link data.
|
||||||
"""
|
"""
|
||||||
def make_picture_data(%Plug.Upload{} = picture) do
|
def make_picture_data(%Plug.Upload{} = picture, opts) do
|
||||||
case MobilizonWeb.Upload.store(picture) do
|
case MobilizonWeb.Upload.store(picture, opts) do
|
||||||
{:ok, picture} ->
|
{:ok, picture} ->
|
||||||
picture
|
picture
|
||||||
|
|
||||||
|
@ -636,18 +636,39 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
Make create activity data
|
Make create activity data
|
||||||
"""
|
"""
|
||||||
@spec make_create_data(map(), map()) :: map()
|
@spec make_create_data(map(), map()) :: map()
|
||||||
def make_create_data(params, additional \\ %{}) do
|
def make_create_data(object, additional \\ %{}) do
|
||||||
Logger.debug("Making create data")
|
Logger.debug("Making create data")
|
||||||
Logger.debug(inspect(params))
|
Logger.debug(inspect(object))
|
||||||
published = params.published || make_date()
|
Logger.debug(inspect(additional))
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"type" => "Create",
|
"type" => "Create",
|
||||||
"to" => params.to |> Enum.uniq(),
|
"to" => object["to"],
|
||||||
"actor" => params.actor.url,
|
"cc" => object["cc"],
|
||||||
"object" => params.object,
|
"actor" => object["actor"],
|
||||||
"published" => published,
|
"object" => object,
|
||||||
"id" => params.object["id"] <> "/activity"
|
"published" => make_date(),
|
||||||
|
"id" => object["id"] <> "/activity"
|
||||||
|
}
|
||||||
|
|> Map.merge(additional)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make update activity data
|
||||||
|
"""
|
||||||
|
@spec make_update_data(map(), map()) :: map()
|
||||||
|
def make_update_data(object, additional \\ %{}) do
|
||||||
|
Logger.debug("Making update data")
|
||||||
|
Logger.debug(inspect(object))
|
||||||
|
Logger.debug(inspect(additional))
|
||||||
|
|
||||||
|
%{
|
||||||
|
"type" => "Update",
|
||||||
|
"to" => object["to"],
|
||||||
|
"cc" => object["cc"],
|
||||||
|
"actor" => object["actor"],
|
||||||
|
"object" => object,
|
||||||
|
"id" => object["id"] <> "/activity"
|
||||||
}
|
}
|
||||||
|> Map.merge(additional)
|
|> Map.merge(additional)
|
||||||
end
|
end
|
||||||
|
@ -688,6 +709,22 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make accept join activity data
|
||||||
|
"""
|
||||||
|
@spec make_accept_join_data(map(), map()) :: map()
|
||||||
|
def make_accept_join_data(object, additional \\ %{}) do
|
||||||
|
%{
|
||||||
|
"type" => "Accept",
|
||||||
|
"to" => object["to"],
|
||||||
|
"cc" => object["cc"],
|
||||||
|
"actor" => object["actor"],
|
||||||
|
"object" => object,
|
||||||
|
"id" => object["id"] <> "/activity"
|
||||||
|
}
|
||||||
|
|> Map.merge(additional)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converts PEM encoded keys to a public key representation
|
Converts PEM encoded keys to a public key representation
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -53,7 +53,7 @@ defmodule Mobilizon.Service.Federator do
|
||||||
Logger.debug(inspect(params))
|
Logger.debug(inspect(params))
|
||||||
|
|
||||||
case Transmogrifier.handle_incoming(params) do
|
case Transmogrifier.handle_incoming(params) do
|
||||||
{:ok, activity, _} ->
|
{:ok, activity, _data} ->
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
|
||||||
%Activity{} ->
|
%Activity{} ->
|
||||||
|
|
|
@ -52,7 +52,7 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
|
||||||
@spec get_public_key_for_url(String.t()) ::
|
@spec get_public_key_for_url(String.t()) ::
|
||||||
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error}
|
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error}
|
||||||
def get_public_key_for_url(url) do
|
def get_public_key_for_url(url) do
|
||||||
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_by_url(url),
|
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url),
|
||||||
{:ok, public_key} <- prepare_public_key(keys) do
|
{:ok, public_key} <- prepare_public_key(keys) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
defmodule Mobilizon.Storage.Repo.Migrations.AddEagerMaterializedViewForSearchingEvents do
|
defmodule Mobilizon.Storage.Repo.Migrations.AddEagerMaterializedViewForSearchingEvents do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Mobilizon.Storage.Repo
|
|
||||||
alias Mobilizon.Service.Search
|
|
||||||
alias Mobilizon.Events.Event
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
create table(:event_search, primary_key: false) do
|
create table(:event_search, primary_key: false) do
|
||||||
|
@ -28,35 +21,6 @@ defmodule Mobilizon.Storage.Repo.Migrations.AddEagerMaterializedViewForSearching
|
||||||
|
|
||||||
# to support updating CONCURRENTLY
|
# to support updating CONCURRENTLY
|
||||||
create(unique_index("event_search", [:id]))
|
create(unique_index("event_search", [:id]))
|
||||||
|
|
||||||
flush()
|
|
||||||
|
|
||||||
events =
|
|
||||||
Event
|
|
||||||
|> preload([e], :tags)
|
|
||||||
|> Repo.all()
|
|
||||||
|
|
||||||
nb_events = length(events)
|
|
||||||
|
|
||||||
IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n")
|
|
||||||
insert_search_event(events, nb_events)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp insert_search_event([%Event{url: url} = event | events], nb_events) do
|
|
||||||
with {:ok, _} <- Search.insert_search_event(event) do
|
|
||||||
Logger.debug("Added event #{url} to the search")
|
|
||||||
else
|
|
||||||
{:error, res} ->
|
|
||||||
Logger.error("Error while adding event #{url} to the search: #{inspect(res)}")
|
|
||||||
end
|
|
||||||
|
|
||||||
ProgressBar.render(nb_events - length(events), nb_events)
|
|
||||||
|
|
||||||
insert_search_event(events, nb_events)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp insert_search_event([], nb_events) do
|
|
||||||
IO.puts("\nFinished setting up search for #{nb_events} events!\n")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def down do
|
def down do
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.MoveParticipantsStatsToEvent do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:events) do
|
||||||
|
add(:participant_stats, :map)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:events) do
|
||||||
|
remove(:participant_stats)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
14
priv/repo/migrations/20191024204726_add_tags_to_comments.exs
Normal file
14
priv/repo/migrations/20191024204726_add_tags_to_comments.exs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddTagsToComments do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
create table(:comments_tags, primary_key: false) do
|
||||||
|
add(:comment_id, references(:comments, on_delete: :delete_all), primary_key: true)
|
||||||
|
add(:tag_id, references(:tags, on_delete: :nilify_all), primary_key: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
drop(table(:comments_tags))
|
||||||
|
end
|
||||||
|
end
|
16
priv/repo/migrations/20191025083642_add_mention_tables.exs
Normal file
16
priv/repo/migrations/20191025083642_add_mention_tables.exs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddMentionTables do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:mentions) do
|
||||||
|
add(:silent, :boolean, default: false, null: false)
|
||||||
|
add(:actor_id, references(:actors, on_delete: :delete_all), null: false)
|
||||||
|
add(:event_id, references(:events, on_delete: :delete_all), null: true)
|
||||||
|
add(:comment_id, references(:comments, on_delete: :delete_all), null: true)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:mentions, [:actor_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddUniqueIndexOnURLs do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create(unique_index(:events, [:url]))
|
||||||
|
create(unique_index(:comments, [:url]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,5 @@
|
||||||
# source: http://localhost:4000/api
|
# source: http://localhost:4000/api
|
||||||
# timestamp: Mon Oct 14 2019 19:26:36 GMT+0200 (Central European Summer Time)
|
# timestamp: Wed Oct 30 2019 17:12:28 GMT+0100 (Central European Standard Time)
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: RootQueryType
|
query: RootQueryType
|
||||||
|
@ -690,17 +690,26 @@ enum ParticipantRoleEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParticipantStats {
|
type ParticipantStats {
|
||||||
|
"""The number of administrators"""
|
||||||
|
administrator: Int
|
||||||
|
|
||||||
|
"""The number of creators"""
|
||||||
|
creator: Int
|
||||||
|
|
||||||
"""The number of approved participants"""
|
"""The number of approved participants"""
|
||||||
approved: Int
|
going: Int
|
||||||
|
|
||||||
|
"""The number of moderators"""
|
||||||
|
moderator: Int
|
||||||
|
|
||||||
|
"""The number of not approved participants"""
|
||||||
|
notApproved: Int
|
||||||
|
|
||||||
"""The number of simple participants (excluding creators)"""
|
"""The number of simple participants (excluding creators)"""
|
||||||
participants: Int
|
participant: Int
|
||||||
|
|
||||||
"""The number of rejected participants"""
|
"""The number of rejected participants"""
|
||||||
rejected: Int
|
rejected: Int
|
||||||
|
|
||||||
"""The number of unapproved participants"""
|
|
||||||
unapproved: Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -908,7 +917,7 @@ type RootMutationType {
|
||||||
changePassword(newPassword: String!, oldPassword: String!): User
|
changePassword(newPassword: String!, oldPassword: String!): User
|
||||||
|
|
||||||
"""Create a comment"""
|
"""Create a comment"""
|
||||||
createComment(actorUsername: String!, text: String!): Comment
|
createComment(actorId: ID!, text: String!): Comment
|
||||||
|
|
||||||
"""Create an event"""
|
"""Create an event"""
|
||||||
createEvent(
|
createEvent(
|
||||||
|
|
|
@ -28,9 +28,9 @@
|
||||||
"attributedTo": "https://framapiaf.org/users/admin",
|
"attributedTo": "https://framapiaf.org/users/admin",
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
"https://framapiaf.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"https://framapiaf.org/users/tcit"
|
||||||
],
|
],
|
||||||
"content": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span> #moo</p>",
|
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span> #moo</p>",
|
||||||
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
|
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
|
||||||
"id": "https://framapiaf.org/users/admin/statuses/99512778738411822",
|
"id": "https://framapiaf.org/users/admin/statuses/99512778738411822",
|
||||||
"inReplyTo": null,
|
"inReplyTo": null,
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
"summary": "cw",
|
"summary": "cw",
|
||||||
"tag": [
|
"tag": [
|
||||||
{
|
{
|
||||||
"href": "http://localtesting.pleroma.lol/users/lain",
|
"href": "https://framapiaf.org/users/tcit",
|
||||||
"name": "@lain@localtesting.pleroma.lol",
|
"name": "@tcit@framapiaf.org",
|
||||||
"type": "Mention"
|
"type": "Mention"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
8
test/fixtures/mastodon-post-activity.json
vendored
8
test/fixtures/mastodon-post-activity.json
vendored
|
@ -28,9 +28,9 @@
|
||||||
"attributedTo": "https://framapiaf.org/users/admin",
|
"attributedTo": "https://framapiaf.org/users/admin",
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
"https://framapiaf.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"https://framapiaf.org/users/tcit"
|
||||||
],
|
],
|
||||||
"content": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>",
|
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
|
||||||
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
|
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
|
||||||
"id": "https://framapiaf.org/users/admin/statuses/99512778738411822",
|
"id": "https://framapiaf.org/users/admin/statuses/99512778738411822",
|
||||||
"inReplyTo": null,
|
"inReplyTo": null,
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
"summary": "cw",
|
"summary": "cw",
|
||||||
"tag": [
|
"tag": [
|
||||||
{
|
{
|
||||||
"href": "http://localtesting.pleroma.lol/users/lain",
|
"href": "https://framapiaf.org/users/tcit",
|
||||||
"name": "@lain@localtesting.pleroma.lol",
|
"name": "@tcit@framapiaf.org",
|
||||||
"type": "Mention"
|
"type": "Mention"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
10
test/fixtures/mobilizon-post-activity.json
vendored
10
test/fixtures/mobilizon-post-activity.json
vendored
|
@ -14,7 +14,7 @@
|
||||||
"actor": "https://event1.tcit.fr/@tcit",
|
"actor": "https://event1.tcit.fr/@tcit",
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
"https://framapiaf.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"https://framapiaf.org/users/tcit"
|
||||||
],
|
],
|
||||||
"id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c/activity",
|
"id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c/activity",
|
||||||
"object": {
|
"object": {
|
||||||
|
@ -23,9 +23,9 @@
|
||||||
"startTime": "2018-02-12T14:08:20Z",
|
"startTime": "2018-02-12T14:08:20Z",
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
"https://framapiaf.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
"https://framapiaf.org/users/tcit"
|
||||||
],
|
],
|
||||||
"content": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>",
|
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
|
||||||
"category": "TODO remove me",
|
"category": "TODO remove me",
|
||||||
"id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c",
|
"id": "https://event1.tcit.fr/@tcit/events/109ccdfd-ee3e-46e1-a877-6c228763df0c",
|
||||||
"inReplyTo": null,
|
"inReplyTo": null,
|
||||||
|
@ -46,8 +46,8 @@
|
||||||
"published": "2018-02-12T14:08:20Z",
|
"published": "2018-02-12T14:08:20Z",
|
||||||
"tag": [
|
"tag": [
|
||||||
{
|
{
|
||||||
"href": "http://localtesting.pleroma.lol/users/lain",
|
"href": "https://framapiaf.org/users/tcit",
|
||||||
"name": "@lain@localtesting.pleroma.lol",
|
"name": "@tcit@framapiaf.org",
|
||||||
"type": "Mention"
|
"type": "Mention"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -99,7 +99,7 @@ defmodule Mobilizon.ActorsTest do
|
||||||
preferred_username: preferred_username,
|
preferred_username: preferred_username,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
avatar: %FileModel{name: picture_name} = _picture
|
avatar: %FileModel{name: picture_name} = _picture
|
||||||
} = _actor} = ActivityPub.get_or_fetch_by_url(@remote_account_url)
|
} = _actor} = ActivityPub.get_or_fetch_actor_by_url(@remote_account_url)
|
||||||
|
|
||||||
assert picture_name == "avatar"
|
assert picture_name == "avatar"
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ defmodule Mobilizon.ActorsTest do
|
||||||
|
|
||||||
test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do
|
test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||||
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(@remote_account_url) do
|
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do
|
||||||
assert Actors.get_actor_by_name_with_preload(
|
assert Actors.get_actor_by_name_with_preload(
|
||||||
"#{actor.preferred_username}@#{actor.domain}"
|
"#{actor.preferred_username}@#{actor.domain}"
|
||||||
).organized_events == []
|
).organized_events == []
|
||||||
|
@ -178,7 +178,8 @@ defmodule Mobilizon.ActorsTest do
|
||||||
test "test build_actors_by_username_or_name_page/4 returns actors with similar usernames",
|
test "test build_actors_by_username_or_name_page/4 returns actors with similar usernames",
|
||||||
%{actor: %Actor{id: actor_id}} do
|
%{actor: %Actor{id: actor_id}} do
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||||
with {:ok, %Actor{id: actor2_id}} <- ActivityPub.get_or_fetch_by_url(@remote_account_url) do
|
with {:ok, %Actor{id: actor2_id}} <-
|
||||||
|
ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do
|
||||||
%Page{total: 2, elements: actors} =
|
%Page{total: 2, elements: actors} =
|
||||||
Actors.build_actors_by_username_or_name_page("tcit", [:Person])
|
Actors.build_actors_by_username_or_name_page("tcit", [:Person])
|
||||||
|
|
||||||
|
@ -253,12 +254,11 @@ defmodule Mobilizon.ActorsTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = MobilizonWeb.Upload.store(file)
|
{:ok, data} = MobilizonWeb.Upload.store(file)
|
||||||
url = hd(data["url"])["href"]
|
|
||||||
|
|
||||||
assert {:ok, actor} =
|
assert {:ok, actor} =
|
||||||
Actors.update_actor(
|
Actors.update_actor(
|
||||||
actor,
|
actor,
|
||||||
Map.put(@update_attrs, :avatar, %{name: file.filename, url: url})
|
Map.put(@update_attrs, :avatar, %{name: file.filename, url: data.url})
|
||||||
)
|
)
|
||||||
|
|
||||||
assert %Actor{} = actor
|
assert %Actor{} = actor
|
||||||
|
|
|
@ -115,7 +115,7 @@ defmodule Mobilizon.EventsTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_event/1 with invalid data returns error changeset" do
|
test "create_event/1 with invalid data returns error changeset" do
|
||||||
assert {:error, %Ecto.Changeset{}} = Events.create_event(@invalid_attrs)
|
assert {:error, :insert, %Ecto.Changeset{}, _} = Events.create_event(@invalid_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_event/2 with valid data updates the event", %{event: event} do
|
test "update_event/2 with valid data updates the event", %{event: event} do
|
||||||
|
@ -128,7 +128,7 @@ defmodule Mobilizon.EventsTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_event/2 with invalid data returns error changeset", %{event: event} do
|
test "update_event/2 with invalid data returns error changeset", %{event: event} do
|
||||||
assert {:error, %Ecto.Changeset{}} = Events.update_event(event, @invalid_attrs)
|
assert {:error, :update, %Ecto.Changeset{}, _} = Events.update_event(event, @invalid_attrs)
|
||||||
assert event.title == Events.get_event!(event.id).title
|
assert event.title == Events.get_event!(event.id).title
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -345,7 +345,8 @@ defmodule Mobilizon.EventsTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_participant/1 with invalid data returns error changeset" do
|
test "create_participant/1 with invalid data returns error changeset" do
|
||||||
assert {:error, %Ecto.Changeset{}} = Events.create_participant(@invalid_attrs)
|
assert {:error, :participant, %Ecto.Changeset{}, _} =
|
||||||
|
Events.create_participant(@invalid_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_participant/2 with valid data updates the participant", %{
|
test "update_participant/2 with valid data updates the participant", %{
|
||||||
|
|
|
@ -14,12 +14,10 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.Event
|
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Converter
|
|
||||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||||
|
|
||||||
alias MobilizonWeb.ActivityPub.ActorView
|
@activity_pub_public_audience "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
HTTPoison.start()
|
HTTPoison.start()
|
||||||
|
@ -53,7 +51,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||||
test "returns an actor from url" do
|
test "returns an actor from url" do
|
||||||
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
|
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
|
||||||
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
|
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
|
||||||
ActivityPub.get_or_fetch_by_url("https://framapiaf.org/users/tcit")
|
ActivityPub.get_or_fetch_actor_by_url("https://framapiaf.org/users/tcit")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -165,28 +163,15 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||||
|
|
||||||
test "it creates an update activity with the new actor data" do
|
test "it creates an update activity with the new actor data" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
actor_data = ActorView.render("actor.json", %{actor: actor})
|
actor_data = %{summary: @updated_actor_summary}
|
||||||
actor_data = Map.put(actor_data, "summary", @updated_actor_summary)
|
|
||||||
|
|
||||||
{:ok, update, updated_actor} =
|
{:ok, update, _} = ActivityPub.update(:actor, actor, actor_data, false)
|
||||||
ActivityPub.update(%{
|
|
||||||
actor: actor_data["url"],
|
|
||||||
to: [actor.url <> "/followers"],
|
|
||||||
cc: [],
|
|
||||||
object: actor_data
|
|
||||||
})
|
|
||||||
|
|
||||||
assert update.data["actor"] == actor.url
|
assert update.data["actor"] == actor.url
|
||||||
assert update.data["to"] == [actor.url <> "/followers"]
|
assert update.data["to"] == [@activity_pub_public_audience]
|
||||||
assert update.data["object"]["id"] == actor_data["id"]
|
assert update.data["object"]["id"] == actor.url
|
||||||
assert update.data["object"]["type"] == actor_data["type"]
|
assert update.data["object"]["type"] == "Person"
|
||||||
assert update.data["object"]["summary"] == @updated_actor_summary
|
assert update.data["object"]["summary"] == @updated_actor_summary
|
||||||
|
|
||||||
refute updated_actor.summary == actor.summary
|
|
||||||
|
|
||||||
{:ok, %Actor{} = database_actor} = Mobilizon.Actors.get_actor_by_url(actor.url)
|
|
||||||
assert database_actor.summary == @updated_actor_summary
|
|
||||||
assert database_actor.preferred_username == actor.preferred_username
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@updated_start_time DateTime.utc_now() |> DateTime.truncate(:second)
|
@updated_start_time DateTime.utc_now() |> DateTime.truncate(:second)
|
||||||
|
@ -194,28 +179,15 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
||||||
test "it creates an update activity with the new event data" do
|
test "it creates an update activity with the new event data" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
event_data = Converter.Event.model_to_as(event)
|
event_data = %{begins_on: @updated_start_time}
|
||||||
event_data = Map.put(event_data, "startTime", @updated_start_time)
|
|
||||||
|
|
||||||
{:ok, update, updated_event} =
|
{:ok, update, _} = ActivityPub.update(:event, event, event_data)
|
||||||
ActivityPub.update(%{
|
|
||||||
actor: actor.url,
|
|
||||||
to: [actor.url <> "/followers"],
|
|
||||||
cc: [],
|
|
||||||
object: event_data
|
|
||||||
})
|
|
||||||
|
|
||||||
assert update.data["actor"] == actor.url
|
assert update.data["actor"] == actor.url
|
||||||
assert update.data["to"] == [actor.url <> "/followers"]
|
assert update.data["to"] == [@activity_pub_public_audience]
|
||||||
assert update.data["object"]["id"] == event_data["id"]
|
assert update.data["object"]["id"] == event.url
|
||||||
assert update.data["object"]["type"] == event_data["type"]
|
assert update.data["object"]["type"] == "Event"
|
||||||
assert update.data["object"]["startTime"] == @updated_start_time
|
assert update.data["object"]["startTime"] == DateTime.to_iso8601(@updated_start_time)
|
||||||
|
|
||||||
refute updated_event.begins_on == event.begins_on
|
|
||||||
|
|
||||||
%Event{} = database_event = Mobilizon.Events.get_event_by_url(event.url)
|
|
||||||
assert database_event.begins_on == @updated_start_time
|
|
||||||
assert database_event.title == event.title
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.ActorTest do
|
||||||
|
|
||||||
describe "AS to Actor" do
|
describe "AS to Actor" do
|
||||||
test "valid as data to model" do
|
test "valid as data to model" do
|
||||||
actor =
|
{:ok, actor} =
|
||||||
ActorConverter.as_to_model_data(%{
|
ActorConverter.as_to_model_data(%{
|
||||||
"type" => "Person",
|
"type" => "Person",
|
||||||
"preferredUsername" => "test_account"
|
"preferredUsername" => "test_account"
|
||||||
|
|
|
@ -124,10 +124,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|
|
||||||
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
assert data["cc"] == [
|
# assert data["cc"] == [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
# "https://framapiaf.org/users/admin/followers",
|
||||||
"http://mobilizon.com/@tcit"
|
# "http://mobilizon.com/@tcit"
|
||||||
]
|
# ]
|
||||||
|
|
||||||
assert data["actor"] == "https://framapiaf.org/users/admin"
|
assert data["actor"] == "https://framapiaf.org/users/admin"
|
||||||
|
|
||||||
|
@ -136,16 +136,14 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|
|
||||||
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
assert object["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
|
||||||
assert object["cc"] == [
|
# assert object["cc"] == [
|
||||||
"https://framapiaf.org/users/admin/followers",
|
# "https://framapiaf.org/users/admin/followers",
|
||||||
"http://localtesting.pleroma.lol/users/lain"
|
# "http://localtesting.pleroma.lol/users/lain"
|
||||||
]
|
# ]
|
||||||
|
|
||||||
assert object["actor"] == "https://framapiaf.org/users/admin"
|
assert object["actor"] == "https://framapiaf.org/users/admin"
|
||||||
assert object["attributedTo"] == "https://framapiaf.org/users/admin"
|
assert object["attributedTo"] == "https://framapiaf.org/users/admin"
|
||||||
|
|
||||||
assert object["sensitive"] == true
|
|
||||||
|
|
||||||
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
|
{:ok, %Actor{}} = Actors.get_actor_by_url(object["actor"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -153,6 +151,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
|
data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Jason.decode!()
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
|
||||||
|
assert Enum.at(data["object"]["tag"], 0)["name"] == "@tcit@framapiaf.org"
|
||||||
assert Enum.at(data["object"]["tag"], 1)["name"] == "#moo"
|
assert Enum.at(data["object"]["tag"], 1)["name"] == "#moo"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -347,9 +346,9 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|> Map.put("actor", data["actor"])
|
|> Map.put("actor", data["actor"])
|
||||||
|> Map.put("object", object)
|
|> Map.put("object", object)
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
|
{:ok, %Activity{data: _data, local: false}, _} = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(data["actor"])
|
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"])
|
||||||
assert actor.name == "nextsoft"
|
assert actor.name == "nextsoft"
|
||||||
|
|
||||||
assert actor.summary == "<p>Some bio</p>"
|
assert actor.summary == "<p>Some bio</p>"
|
||||||
|
@ -406,7 +405,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|
|
||||||
test "it works for incoming deletes" do
|
test "it works for incoming deletes" do
|
||||||
%Actor{url: actor_url} = actor = insert(:actor)
|
%Actor{url: actor_url} = actor = insert(:actor)
|
||||||
%Comment{url: comment_url} = insert(:comment, actor: actor)
|
%Comment{url: comment_url} = insert(:comment, actor: nil, actor_id: actor.id)
|
||||||
|
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-delete.json")
|
File.read!("test/fixtures/mastodon-delete.json")
|
||||||
|
@ -622,8 +621,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|> Map.put("object", follow_activity.data["id"])
|
|> Map.put("object", follow_activity.data["id"])
|
||||||
|
|
||||||
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, activity, _} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"]["id"] == follow_activity.data["id"]
|
||||||
assert activity.data["object"] =~ "/follow/"
|
assert activity.data["object"]["id"] =~ "/follow/"
|
||||||
assert activity.data["id"] =~ "/accept/follow/"
|
assert activity.data["id"] =~ "/accept/follow/"
|
||||||
|
|
||||||
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
||||||
|
@ -756,8 +755,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
|> Map.put("object", participation.url)
|
|> Map.put("object", participation.url)
|
||||||
|
|
||||||
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, accept_activity, _} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert accept_activity.data["object"] == join_activity.data["id"]
|
assert accept_activity.data["object"]["id"] == join_activity.data["id"]
|
||||||
assert accept_activity.data["object"] =~ "/join/"
|
assert accept_activity.data["object"]["id"] =~ "/join/"
|
||||||
assert accept_activity.data["id"] =~ "/accept/join/"
|
assert accept_activity.data["id"] =~ "/accept/join/"
|
||||||
|
|
||||||
# We don't accept already accepted Accept activities
|
# We don't accept already accepted Accept activities
|
||||||
|
@ -847,10 +846,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
other_actor = insert(:actor)
|
other_actor = insert(:actor)
|
||||||
|
|
||||||
{:ok, activity, _} =
|
{:ok, activity, _} =
|
||||||
API.Comments.create_comment(
|
API.Comments.create_comment(%{
|
||||||
actor.preferred_username,
|
actor_id: actor.id,
|
||||||
"hey, @#{other_actor.preferred_username}, how are ya? #2hu"
|
text: "hey, @#{other_actor.preferred_username}, how are ya? #2hu"
|
||||||
)
|
})
|
||||||
|
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
object = modified["object"]
|
object = modified["object"]
|
||||||
|
@ -883,7 +882,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
test "it adds the json-ld context and the conversation property" do
|
test "it adds the json-ld context and the conversation property" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
|
||||||
{:ok, activity, _} = API.Comments.create_comment(actor.preferred_username, "hey")
|
{:ok, activity, _} = API.Comments.create_comment(%{actor_id: actor.id, text: "hey"})
|
||||||
|
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
@ -893,7 +892,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
|
test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
|
||||||
{:ok, activity, _} = API.Comments.create_comment(actor.preferred_username, "hey")
|
{:ok, activity, _} = API.Comments.create_comment(%{actor_id: actor.id, text: "hey"})
|
||||||
|
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
@ -903,7 +902,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
test "it strips internal hashtag data" do
|
test "it strips internal hashtag data" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
|
||||||
{:ok, activity, _} = API.Comments.create_comment(actor.preferred_username, "#2hu")
|
{:ok, activity, _} = API.Comments.create_comment(%{actor_id: actor.id, text: "#2hu"})
|
||||||
|
|
||||||
expected_tag = %{
|
expected_tag = %{
|
||||||
"href" => MobilizonWeb.Endpoint.url() <> "/tags/2hu",
|
"href" => MobilizonWeb.Endpoint.url() <> "/tags/2hu",
|
||||||
|
@ -919,7 +918,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
test "it strips internal fields" do
|
test "it strips internal fields" do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
|
|
||||||
{:ok, activity, _} = API.Comments.create_comment(actor.preferred_username, "#2hu")
|
{:ok, activity, _} = API.Comments.create_comment(%{actor_id: actor.id, text: "#2hu"})
|
||||||
|
|
||||||
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,21 @@ defmodule Mobilizon.Service.ActivityPub.UtilsTest do
|
||||||
describe "make" do
|
describe "make" do
|
||||||
test "comment data from struct" do
|
test "comment data from struct" do
|
||||||
comment = insert(:comment)
|
comment = insert(:comment)
|
||||||
reply = insert(:comment, in_reply_to_comment: comment)
|
tag = insert(:tag, title: "MyTag")
|
||||||
|
reply = insert(:comment, in_reply_to_comment: comment, tags: [tag])
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
"type" => "Note",
|
"type" => "Note",
|
||||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"content" => reply.text,
|
"cc" => [],
|
||||||
|
"tag" => [
|
||||||
|
%{
|
||||||
|
"href" => "http://mobilizon.test/tags/#{tag.slug}",
|
||||||
|
"name" => "#MyTag",
|
||||||
|
"type" => "Hashtag"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content" => "My Comment",
|
||||||
"actor" => reply.actor.url,
|
"actor" => reply.actor.url,
|
||||||
"uuid" => reply.uuid,
|
"uuid" => reply.uuid,
|
||||||
"id" => Routes.page_url(Endpoint, :comment, reply.uuid),
|
"id" => Routes.page_url(Endpoint, :comment, reply.uuid),
|
||||||
|
|
|
@ -18,8 +18,7 @@ defmodule MobilizonWeb.Plugs.UploadedMediaPlugTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
[%{"href" => attachment_url} | _] = data["url"]
|
[attachment_url: data.url]
|
||||||
[attachment_url: attachment_url]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
setup_all :upload_file
|
setup_all :upload_file
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule MobilizonWeb.Resolvers.CommentResolverTest do
|
||||||
mutation {
|
mutation {
|
||||||
createComment(
|
createComment(
|
||||||
text: "#{@comment.text}",
|
text: "#{@comment.text}",
|
||||||
actor_username: "#{actor.preferred_username}"
|
actor_id: "#{actor.id}"
|
||||||
) {
|
) {
|
||||||
text,
|
text,
|
||||||
uuid
|
uuid
|
||||||
|
|
|
@ -363,7 +363,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
actor: actor,
|
actor: actor,
|
||||||
user: user
|
user: user
|
||||||
} do
|
} do
|
||||||
address = insert(:address)
|
address = %{street: "I am a street, please believe me", locality: "Where ever"}
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
|
@ -383,6 +383,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
title,
|
title,
|
||||||
uuid,
|
uuid,
|
||||||
physicalAddress {
|
physicalAddress {
|
||||||
|
id,
|
||||||
url,
|
url,
|
||||||
geom,
|
geom,
|
||||||
street
|
street
|
||||||
|
@ -403,8 +404,8 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] ==
|
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] ==
|
||||||
address.street
|
address.street
|
||||||
|
|
||||||
refute json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"] ==
|
address_url = json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"]
|
||||||
address.url
|
address_id = json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["id"]
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
|
@ -417,12 +418,13 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
organizer_actor_id: "#{actor.id}",
|
organizer_actor_id: "#{actor.id}",
|
||||||
category: "birthday",
|
category: "birthday",
|
||||||
physical_address: {
|
physical_address: {
|
||||||
url: "#{address.url}"
|
id: "#{address_id}"
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
title,
|
title,
|
||||||
uuid,
|
uuid,
|
||||||
physicalAddress {
|
physicalAddress {
|
||||||
|
id,
|
||||||
url,
|
url,
|
||||||
geom,
|
geom,
|
||||||
street
|
street
|
||||||
|
@ -443,8 +445,11 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] ==
|
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["street"] ==
|
||||||
address.street
|
address.street
|
||||||
|
|
||||||
|
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["id"] ==
|
||||||
|
address_id
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"] ==
|
assert json_response(res, 200)["data"]["createEvent"]["physicalAddress"]["url"] ==
|
||||||
address.url
|
address_url
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_event/3 creates an event with an attached picture", %{
|
test "create_event/3 creates an event with an attached picture", %{
|
||||||
|
@ -501,7 +506,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
"picture for my event"
|
"picture for my event"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_event/3 creates an event with an picture URL", %{
|
test "create_event/3 creates an event with an picture ID", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
actor: actor,
|
actor: actor,
|
||||||
user: user
|
user: user
|
||||||
|
|
|
@ -2,7 +2,6 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
|
||||||
use MobilizonWeb.ConnCase
|
use MobilizonWeb.ConnCase
|
||||||
alias MobilizonWeb.AbsintheHelpers
|
alias MobilizonWeb.AbsintheHelpers
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
require Logger
|
|
||||||
|
|
||||||
@non_existent_username "nonexistent"
|
@non_existent_username "nonexistent"
|
||||||
@new_group_params %{groupname: "new group"}
|
@new_group_params %{groupname: "new group"}
|
||||||
|
@ -36,7 +35,7 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
|
||||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||||
"Actor id is not owned by authenticated user"
|
"Creator actor id is not owned by the current user"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_group/3 creates a group and check a group with this name does not already exist",
|
test "create_group/3 creates a group and check a group with this name does not already exist",
|
||||||
|
|
|
@ -561,8 +561,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
event(uuid: "#{event.uuid}") {
|
event(uuid: "#{event.uuid}") {
|
||||||
uuid,
|
uuid,
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
rejected
|
rejected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,8 +574,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
|
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["approved"] == 1
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["going"] == 1
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["unapproved"] == 0
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["notApproved"] == 0
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 0
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 0
|
||||||
|
|
||||||
moderator = insert(:actor)
|
moderator = insert(:actor)
|
||||||
|
@ -586,18 +586,18 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
actor_id: moderator.id
|
actor_id: moderator.id
|
||||||
})
|
})
|
||||||
|
|
||||||
unapproved = insert(:actor)
|
not_approved = insert(:actor)
|
||||||
|
|
||||||
Events.create_participant(%{
|
Events.create_participant(%{
|
||||||
role: :not_approved,
|
role: :not_approved,
|
||||||
event_id: event.id,
|
event_id: event.id,
|
||||||
actor_id: unapproved.id
|
actor_id: not_approved.id
|
||||||
})
|
})
|
||||||
|
|
||||||
Events.create_participant(%{
|
Events.create_participant(%{
|
||||||
role: :rejected,
|
role: :rejected,
|
||||||
event_id: event.id,
|
event_id: event.id,
|
||||||
actor_id: unapproved.id
|
actor_id: not_approved.id
|
||||||
})
|
})
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
|
@ -605,8 +605,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
event(uuid: "#{event.uuid}") {
|
event(uuid: "#{event.uuid}") {
|
||||||
uuid,
|
uuid,
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
going,
|
||||||
unapproved,
|
notApproved,
|
||||||
rejected
|
rejected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,8 +618,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
|
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["approved"] == 2
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["going"] == 2
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["unapproved"] == 1
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["notApproved"] == 1
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 1
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,9 +25,9 @@ defmodule Mobilizon.UploadTest do
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
"url" => [%{"href" => url, "mediaType" => "image/jpeg"}],
|
url: url,
|
||||||
"size" => 13_227,
|
content_type: "image/jpeg",
|
||||||
"type" => "Image"
|
size: 13_227
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
assert String.starts_with?(url, MobilizonWeb.Endpoint.url() <> "/media/")
|
assert String.starts_with?(url, MobilizonWeb.Endpoint.url() <> "/media/")
|
||||||
|
@ -46,9 +46,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file, base_url: base_url)
|
{:ok, data} = Upload.store(file, base_url: base_url)
|
||||||
|
|
||||||
assert %{"url" => [%{"href" => url}]} = data
|
assert String.starts_with?(data.url, base_url <> "/media/")
|
||||||
|
|
||||||
assert String.starts_with?(url, base_url <> "/media/")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "copies the file to the configured folder with deduping" do
|
test "copies the file to the configured folder with deduping" do
|
||||||
|
@ -62,7 +60,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.Dedupe])
|
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.Dedupe])
|
||||||
|
|
||||||
assert List.first(data["url"])["href"] ==
|
assert data.url ==
|
||||||
MobilizonWeb.Endpoint.url() <>
|
MobilizonWeb.Endpoint.url() <>
|
||||||
"/media/590523d60d3831ec92d05cdd871078409d5780903910efec5cd35ab1b0f19d11.jpg"
|
"/media/590523d60d3831ec92d05cdd871078409d5780903910efec5cd35ab1b0f19d11.jpg"
|
||||||
end
|
end
|
||||||
|
@ -77,7 +75,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
assert data["name"] == "an [image.jpg"
|
assert data.name == "an [image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fixes incorrect content type" do
|
test "fixes incorrect content type" do
|
||||||
|
@ -90,7 +88,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.Dedupe])
|
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.Dedupe])
|
||||||
assert hd(data["url"])["mediaType"] == "image/jpeg"
|
assert data.content_type == "image/jpeg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "adds missing extension" do
|
test "adds missing extension" do
|
||||||
|
@ -103,7 +101,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
assert data["name"] == "an [image.jpg"
|
assert data.name == "an [image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fixes incorrect file extension" do
|
test "fixes incorrect file extension" do
|
||||||
|
@ -116,7 +114,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
assert data["name"] == "an [image.jpg"
|
assert data.name == "an [image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "don't modify filename of an unknown type" do
|
test "don't modify filename of an unknown type" do
|
||||||
|
@ -129,7 +127,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
assert data["name"] == "test.txt"
|
assert data.name == "test.txt"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "copies the file to the configured folder with anonymizing filename" do
|
test "copies the file to the configured folder with anonymizing filename" do
|
||||||
|
@ -143,7 +141,7 @@ defmodule Mobilizon.UploadTest do
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.AnonymizeFilename])
|
{:ok, data} = Upload.store(file, filters: [MobilizonWeb.Upload.Filter.AnonymizeFilename])
|
||||||
|
|
||||||
refute data["name"] == "an [image.jpg"
|
refute data.name == "an [image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "escapes invalid characters in url" do
|
test "escapes invalid characters in url" do
|
||||||
|
@ -156,9 +154,8 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
[attachment_url | _] = data["url"]
|
|
||||||
|
|
||||||
assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"
|
assert Path.basename(data.url) == "an%E2%80%A6%20image.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "escapes reserved uri characters" do
|
test "escapes reserved uri characters" do
|
||||||
|
@ -171,9 +168,8 @@ defmodule Mobilizon.UploadTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
[attachment_url | _] = data["url"]
|
|
||||||
|
|
||||||
assert Path.basename(attachment_url["href"]) ==
|
assert Path.basename(data.url) ==
|
||||||
"%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg"
|
"%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -210,9 +206,9 @@ defmodule Mobilizon.UploadTest do
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
"url" => [%{"href" => url, "mediaType" => "image/jpeg"}],
|
url: url,
|
||||||
"size" => 13_227,
|
size: 13_227,
|
||||||
"type" => "Image"
|
content_type: "image/jpeg"
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
assert String.starts_with?(url, MobilizonWeb.Endpoint.url() <> "/media/")
|
assert String.starts_with?(url, MobilizonWeb.Endpoint.url() <> "/media/")
|
||||||
|
|
|
@ -40,7 +40,7 @@ defmodule Mobilizon.Factory do
|
||||||
following_url: Actor.build_url(preferred_username, :following),
|
following_url: Actor.build_url(preferred_username, :following),
|
||||||
inbox_url: Actor.build_url(preferred_username, :inbox),
|
inbox_url: Actor.build_url(preferred_username, :inbox),
|
||||||
outbox_url: Actor.build_url(preferred_username, :outbox),
|
outbox_url: Actor.build_url(preferred_username, :outbox),
|
||||||
user: nil
|
user: build(:user)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -100,6 +100,8 @@ defmodule Mobilizon.Factory do
|
||||||
actor: build(:actor),
|
actor: build(:actor),
|
||||||
event: build(:event),
|
event: build(:event),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
|
mentions: [],
|
||||||
|
tags: build_list(3, :tag),
|
||||||
in_reply_to_comment: nil,
|
in_reply_to_comment: nil,
|
||||||
url: Routes.page_url(Endpoint, :comment, uuid)
|
url: Routes.page_url(Endpoint, :comment, uuid)
|
||||||
}
|
}
|
||||||
|
@ -121,11 +123,13 @@ defmodule Mobilizon.Factory do
|
||||||
physical_address: build(:address),
|
physical_address: build(:address),
|
||||||
visibility: :public,
|
visibility: :public,
|
||||||
tags: build_list(3, :tag),
|
tags: build_list(3, :tag),
|
||||||
|
mentions: [],
|
||||||
url: Routes.page_url(Endpoint, :event, uuid),
|
url: Routes.page_url(Endpoint, :event, uuid),
|
||||||
picture: insert(:picture),
|
picture: insert(:picture),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
join_options: :free,
|
join_options: :free,
|
||||||
options: %{}
|
options: %{},
|
||||||
|
participant_stats: %{}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -195,9 +199,10 @@ defmodule Mobilizon.Factory do
|
||||||
{:ok, data} = Upload.store(file)
|
{:ok, data} = Upload.store(file)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"url" => [%{"href" => url, "mediaType" => "image/jpeg"}],
|
content_type: "image/jpeg",
|
||||||
"size" => 13_227,
|
name: "image.jpg",
|
||||||
"type" => "Image"
|
url: url,
|
||||||
|
size: 13_227
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
%Mobilizon.Media.File{
|
%Mobilizon.Media.File{
|
||||||
|
|
Loading…
Reference in a new issue