Send email notifications when a participation is approved/rejected

Also handles participant status :rejected instead of deleting the
participation

Closes #164

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-09-30 13:48:47 +02:00
parent d30b2fa147
commit 5b4f1c271a
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
47 changed files with 3092 additions and 484 deletions

View File

@ -14,7 +14,7 @@
</div>
</div>
<footer class="card-footer">
<b-button v-if="participant.role === ParticipantRole.NOT_APPROVED" @click="accept(participant)" type="is-success" class="card-footer-item">{{ $t('Approve') }}</b-button>
<b-button v-if="[ParticipantRole.NOT_APPROVED, ParticipantRole.REJECTED].includes(participant.role)" @click="accept(participant)" type="is-success" class="card-footer-item">{{ $t('Approve') }}</b-button>
<b-button v-if="participant.role === ParticipantRole.NOT_APPROVED" @click="reject(participant)" type="is-danger" class="card-footer-item">{{ $t('Reject')}} </b-button>
<b-button v-if="participant.role === ParticipantRole.PARTICIPANT" @click="exclude(participant)" type="is-danger" class="card-footer-item">{{ $t('Exclude')}} </b-button>
<span v-if="participant.role === ParticipantRole.CREATOR" class="card-footer-item">{{ $t('Creator')}} </span>

View File

@ -2,6 +2,7 @@
<div class="participation-button">
<b-dropdown aria-role="list" position="is-bottom-left" v-if="participation && participation.role === ParticipantRole.PARTICIPANT">
<button class="button is-success" type="button" slot="trigger">
<b-icon icon="check"></b-icon>
<template>
<span>{{ $t('I participate') }}</span>
</template>
@ -12,7 +13,7 @@
<!-- {{ $t('Change my identity…')}}-->
<!-- </b-dropdown-item>-->
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave">
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave" class="has-text-danger">
{{ $t('Cancel my participation…')}}
</b-dropdown-item>
</b-dropdown>
@ -20,6 +21,7 @@
<div v-else-if="participation && participation.role === ParticipantRole.NOT_APPROVED">
<b-dropdown aria-role="list" position="is-bottom-left" class="dropdown-disabled">
<button class="button is-success" type="button" slot="trigger">
<b-icon icon="timer-sand-empty"></b-icon>
<template>
<span>{{ $t('I participate') }}</span>
</template>
@ -30,7 +32,7 @@
<!-- {{ $t('Change my identity…')}}-->
<!-- </b-dropdown-item>-->
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave">
<b-dropdown-item :value="false" aria-role="listitem" @click="confirmLeave" class="has-text-danger">
{{ $t('Cancel my participation request…')}}
</b-dropdown-item>
</b-dropdown>
@ -38,6 +40,10 @@
<small>{{ $t('Waiting for organization team approval.')}}</small>
</div>
<div v-else-if="participation && participation.role === ParticipantRole.REJECTED">
<span>{{ $t('Unfortunately, your participation request was rejected by the organizers.')}}</span>
</div>
<b-dropdown aria-role="list" position="is-bottom-left" v-if="!participation">
<button class="button is-primary" type="button" slot="trigger">
<template>

View File

@ -334,23 +334,15 @@ export const LEAVE_EVENT = gql`
}
`;
export const ACCEPT_PARTICIPANT = gql`
mutation AcceptParticipant($id: ID!, $moderatorActorId: ID!) {
acceptParticipation(id: $id, moderatorActorId: $moderatorActorId) {
export const UPDATE_PARTICIPANT = gql`
mutation AcceptParticipant($id: ID!, $moderatorActorId: ID!, $role: ParticipantRoleEnum!) {
updateParticipation(id: $id, moderatorActorId: $moderatorActorId, role: $role) {
role,
id
}
}
`;
export const REJECT_PARTICIPANT = gql`
mutation RejectParticipant($id: ID!, $moderatorActorId: ID!) {
rejectParticipation(id: $id, moderatorActorId: $moderatorActorId) {
id
}
}
`;
export const DELETE_EVENT = gql`
mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
deleteEvent(
@ -371,7 +363,8 @@ export const PARTICIPANTS = gql`
},
participantStats {
approved,
unapproved
unapproved,
rejected
}
}
}

View File

@ -253,5 +253,9 @@
"{approved} / {total} seats": "{approved} / {total} seats",
"{count} participants": "{count} participants",
"{count} requests waiting": "{count} requests waiting",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
"Requests": "Requests",
"Rejected": "Rejected",
"Rejected participations": "Rejected participations",
"Unfortunately, your participation request was rejected by the organizers.": "Unfortunately, your participation request was rejected by the organizers."
}

View File

@ -253,5 +253,9 @@
"{approved} / {total} seats": "{approved} / {total} places",
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
"{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines"
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
"Requests": "Requêtes",
"Rejected": "Rejetés",
"Rejected participations": "Participations rejetées",
"Unfortunately, your participation request was rejected by the organizers.": "Malheureusement, votre demande de participation a été refusée par les organisateur⋅ices."
}

View File

@ -30,6 +30,7 @@ export enum EventVisibilityJoinOptions {
export enum ParticipantRole {
NOT_APPROVED = 'NOT_APPROVED',
REJECTED = 'REJECTED',
PARTICIPANT = 'PARTICIPANT',
MODERATOR = 'MODERATOR',
ADMINISTRATOR = 'ADMINISTRATOR',
@ -112,6 +113,7 @@ export interface IEvent {
participantStats: {
approved: number;
unapproved: number;
rejected: number;
};
participants: IParticipant[];
@ -175,7 +177,7 @@ export class EventModel implements IEvent {
publishAt = new Date();
participantStats = { approved: 0, unapproved: 0 };
participantStats = { approved: 0, unapproved: 0, rejected: 0 };
participants: IParticipant[] = [];
relatedEvents: IEvent[] = [];

View File

@ -4,7 +4,7 @@
<b-tab-item>
<template slot="header">
<b-icon icon="information-outline"></b-icon>
<span> Participants <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
</template>
<section v-if="participantsAndCreators.length > 0">
<h2 class="title">{{ $t('Participants') }}</h2>
@ -23,7 +23,7 @@
<b-tab-item>
<template slot="header">
<b-icon icon="source-pull"></b-icon>
<span> Demandes <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
</template>
<section v-if="queue.length > 0">
<h2 class="title">{{ $t('Waiting list') }}</h2>
@ -39,6 +39,25 @@
</div>
</section>
</b-tab-item>
<b-tab-item>
<template slot="header">
<b-icon icon="source-pull"></b-icon>
<span>{{ $t('Rejected')}} <b-tag rounded> {{ participantStats.rejected }} </b-tag> </span>
</template>
<section v-if="rejected.length > 0">
<h2 class="title">{{ $t('Rejected participations') }}</h2>
<div class="columns">
<div class="column is-one-quarter-desktop" v-for="participant in rejected" :key="participant.actor.id">
<participant-card
:participant="participant"
:accept="acceptParticipant"
:reject="refuseParticipant"
:exclude="refuseParticipant"
/>
</div>
</div>
</section>
</b-tab-item>
</b-tabs>
</main>
</template>
@ -46,7 +65,7 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { IEvent, IParticipant, Participant, ParticipantRole } from '@/types/event.model';
import { ACCEPT_PARTICIPANT, PARTICIPANTS, REJECT_PARTICIPANT } from '@/graphql/event';
import { UPDATE_PARTICIPANT, PARTICIPANTS } from '@/graphql/event';
import ParticipantCard from '@/components/Account/ParticipantCard.vue';
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
import { IPerson } from '@/types/actor';
@ -98,6 +117,19 @@ import { IPerson } from '@/types/actor';
},
update: data => data.event.participants.map(participation => new Participant(participation)),
},
rejected: {
query: PARTICIPANTS,
variables() {
return {
uuid: this.eventId,
page: 1,
limit: 20,
roles: [ParticipantRole.REJECTED].join(),
actorId: this.currentActor.id,
};
},
update: data => data.event.participants.map(participation => new Participant(participation)),
},
},
})
export default class Participants extends Vue {
@ -108,6 +140,7 @@ export default class Participants extends Vue {
// participants: IParticipant[] = [];
organizers: IParticipant[] = [];
queue: IParticipant[] = [];
rejected: IParticipant[] = [];
event!: IEvent;
ParticipantRole = ParticipantRole;
@ -156,15 +189,16 @@ export default class Participants extends Vue {
async acceptParticipant(participant: IParticipant) {
try {
const { data } = await this.$apollo.mutate({
mutation: ACCEPT_PARTICIPANT,
mutation: UPDATE_PARTICIPANT,
variables: {
id: participant.id,
moderatorActorId: this.currentActor.id,
role: ParticipantRole.PARTICIPANT,
},
});
if (data) {
console.log('accept', data);
this.queue.filter(participant => participant !== data.acceptParticipation.id);
this.queue.filter(participant => participant !== data.updateParticipation.id);
this.rejected.filter(participant => participant !== data.updateParticipation.id);
this.participants.push(participant);
}
} catch (e) {
@ -175,15 +209,17 @@ export default class Participants extends Vue {
async refuseParticipant(participant: IParticipant) {
try {
const { data } = await this.$apollo.mutate({
mutation: REJECT_PARTICIPANT,
mutation: UPDATE_PARTICIPANT,
variables: {
id: participant.id,
moderatorActorId: this.currentActor.id,
role: ParticipantRole.REJECTED,
},
});
if (data) {
this.participants.filter(participant => participant !== data.rejectParticipation.id);
this.queue.filter(participant => participant !== data.rejectParticipation.id);
this.participants.filter(participant => participant !== data.updateParticipation.id);
this.queue.filter(participant => participant !== data.updateParticipation.id);
this.rejected.push(participant);
}
} catch (e) {
console.error(e);

View File

@ -70,6 +70,7 @@ defmodule Mobilizon.Events do
defenum(ParticipantRole, :participant_role, [
:not_approved,
:rejected,
:participant,
:moderator,
:administrator,
@ -718,6 +719,17 @@ defmodule Mobilizon.Events do
|> Repo.aggregate(:count, :id)
end
@doc """
Counts rejected participants.
"""
@spec count_rejected_participants(integer | String.t()) :: integer
def count_rejected_participants(event_id) do
event_id
|> count_participants_query()
|> filter_rejected_role()
|> Repo.aggregate(:count, :id)
end
@doc """
Gets the default participant role depending on the event join options.
"""
@ -1361,7 +1373,7 @@ defmodule Mobilizon.Events do
@spec filter_approved_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_approved_role(query) do
from(p in query, where: p.role != ^:not_approved)
from(p in query, where: p.role not in ^[:not_approved, :rejected])
end
@spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t()
@ -1369,6 +1381,11 @@ defmodule Mobilizon.Events do
from(p in query, where: p.role == ^:not_approved)
end
@spec filter_rejected_role(Ecto.Query.t()) :: Ecto.Query.t()
defp filter_rejected_role(query) do
from(p in query, where: p.role == ^:rejected)
end
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
defp filter_role(query, []), do: query

View File

@ -7,6 +7,7 @@ defmodule MobilizonWeb.API.Participations do
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Service.ActivityPub
alias MobilizonWeb.Email.Participation
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
def join(%Event{id: event_id} = event, %Actor{id: actor_id} = actor) do
@ -22,10 +23,19 @@ defmodule MobilizonWeb.API.Participations do
end
end
def accept(
%Participant{} = participation,
%Actor{} = moderator
) do
@doc """
Update participation status
"""
def update(%Participant{} = participation, %Actor{} = moderator, :participant),
do: accept(participation, moderator)
def update(%Participant{} = participation, %Actor{} = moderator, :rejected),
do: reject(participation, moderator)
defp accept(
%Participant{} = participation,
%Actor{} = moderator
) do
with {:ok, activity, _} <-
ActivityPub.accept(
%{
@ -36,15 +46,16 @@ defmodule MobilizonWeb.API.Participations do
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participation.id}"
),
{:ok, %Participant{role: :participant} = participation} <-
Events.update_participant(participation, %{"role" => :participant}) do
Events.update_participant(participation, %{"role" => :participant}),
:ok <- Participation.send_emails_to_local_user(participation) do
{:ok, activity, participation}
end
end
def reject(
%Participant{} = participation,
%Actor{} = moderator
) do
defp reject(
%Participant{} = participation,
%Actor{} = moderator
) do
with {:ok, activity, _} <-
ActivityPub.reject(
%{
@ -54,8 +65,9 @@ defmodule MobilizonWeb.API.Participations do
},
"#{MobilizonWeb.Endpoint.url()}/reject/join/#{participation.id}"
),
{:ok, %Participant{} = participation} <-
Events.delete_participant(participation) do
{:ok, %Participant{role: :rejected} = participation} <-
Events.update_participant(participation, %{"role" => :rejected}),
:ok <- Participation.send_emails_to_local_user(participation) do
{:ok, activity, participation}
end
end

View File

@ -0,0 +1,84 @@
defmodule MobilizonWeb.Email.Participation do
@moduledoc """
Handles emails sent about participation.
"""
use Bamboo.Phoenix, view: MobilizonWeb.EmailView
import Bamboo.Phoenix
import MobilizonWeb.Gettext
alias Mobilizon.Users.User
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Participant
alias MobilizonWeb.Email
@doc """
Send emails to local user
"""
def send_emails_to_local_user(
%Participant{actor: %Actor{user_id: nil} = _actor} = _participation
),
do: :ok
@doc """
Send emails to local user
"""
def send_emails_to_local_user(
%Participant{actor: %Actor{user_id: user_id} = _actor} = participation
) do
with %User{} = user <- Mobilizon.Users.get_user!(user_id) do
user
|> participation_updated(participation)
|> Email.Mailer.deliver_later()
:ok
end
end
@spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(user, participant, locale \\ "en")
def participation_updated(
%User{email: email},
%Participant{event: event, role: :rejected},
locale
) do
Gettext.put_locale(locale)
subject =
gettext(
"Your participation to event %{title} has been rejected",
title: event.title
)
Email.base_email(to: email, subject: subject)
|> assign(:locale, locale)
|> assign(:event, event)
|> assign(:subject, subject)
|> render(:event_participation_rejected)
end
@spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
%User{email: email},
%Participant{event: event, role: :participant},
locale
) do
Gettext.put_locale(locale)
subject =
gettext(
"Your participation to event %{title} has been approved",
title: event.title
)
Email.base_email(to: email, subject: subject)
|> assign(:locale, locale)
|> assign(:event, event)
|> assign(:subject, subject)
|> render(:event_participation_approved)
end
end

View File

@ -83,7 +83,8 @@ defmodule MobilizonWeb.Resolvers.Event do
{:ok,
%{
approved: Mobilizon.Events.count_approved_participants(id),
unapproved: Mobilizon.Events.count_unapproved_participants(id)
unapproved: Mobilizon.Events.count_unapproved_participants(id),
rejected: Mobilizon.Events.count_rejected_participants(id)
}}
end
@ -202,9 +203,9 @@ defmodule MobilizonWeb.Resolvers.Event do
{:error, "You need to be logged-in to leave an event"}
end
def accept_participation(
def update_participation(
_parent,
%{id: participation_id, moderator_actor_id: moderator_actor_id},
%{id: participation_id, moderator_actor_id: moderator_actor_id, role: new_role},
%{
context: %{
current_user: user
@ -214,14 +215,15 @@ defmodule MobilizonWeb.Resolvers.Event do
# Check that moderator provided is rightly authenticated
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation already exists
{:has_participation, %Participant{role: :not_approved} = participation} <-
{:has_participation, %Participant{role: old_role} = participation} <-
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
{:same_role, false} <- {:same_role, new_role == old_role},
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission,
Mobilizon.Events.moderator_for_event?(participation.event.id, moderator_actor_id)},
{:ok, _activity, participation} <-
MobilizonWeb.API.Participations.accept(participation, moderator_actor) do
MobilizonWeb.API.Participations.update(participation, moderator_actor, new_role) do
{:ok, participation}
else
{:is_owned, nil} ->
@ -234,55 +236,14 @@ defmodule MobilizonWeb.Resolvers.Event do
{:actor_approve_permission, _} ->
{:error, "Provided moderator actor ID doesn't have permission on this event"}
{:same_role, true} ->
{:error, "Participant already has role #{new_role}"}
{:error, :participant_not_found} ->
{:error, "Participant not found"}
end
end
def reject_participation(
_parent,
%{id: participation_id, moderator_actor_id: moderator_actor_id},
%{
context: %{
current_user: user
}
}
) do
# Check that moderator provided is rightly authenticated
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation really exists
{:has_participation, %Participant{} = participation} <-
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission,
Mobilizon.Events.moderator_for_event?(participation.event.id, moderator_actor_id)},
{:ok, _activity, participation} <-
MobilizonWeb.API.Participations.reject(participation, moderator_actor) do
{
:ok,
%{
id: participation.id,
event: %{
id: participation.event.id
},
actor: %{
id: participation.actor.id
}
}
}
else
{:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
{:actor_approve_permission, _} ->
{:error, "Provided moderator actor ID doesn't have permission on this event"}
{:has_participation, nil} ->
{:error, "Participant not found"}
end
end
@doc """
Create an event
"""

View File

@ -111,6 +111,7 @@ defmodule MobilizonWeb.Schema.EventType do
object :participant_stats do
field(:approved, :integer, description: "The number of approved participants")
field(:unapproved, :integer, description: "The number of unapproved participants")
field(:rejected, :integer, description: "The number of rejected participants")
end
object :event_offer do

View File

@ -38,6 +38,7 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
value(:moderator)
value(:administrator)
value(:creator)
value(:rejected)
end
@desc "Represents a deleted participant"
@ -65,19 +66,12 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
end
@desc "Accept a participation"
field :accept_participation, :participant do
field :update_participation, :participant do
arg(:id, non_null(:id))
arg(:role, non_null(:participant_role_enum))
arg(:moderator_actor_id, non_null(:id))
resolve(&Resolvers.Event.accept_participation/3)
end
@desc "Reject a participation"
field :reject_participation, :deleted_participant do
arg(:id, non_null(:id))
arg(:moderator_actor_id, non_null(:id))
resolve(&Resolvers.Event.reject_participation/3)
resolve(&Resolvers.Event.update_participation/3)
end
end
end

View File

@ -0,0 +1,81 @@
<!-- HERO -->
<tr>
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "All good!" %>
</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<!-- COPY -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;">
<%= gettext "You requested to participate in event %{title}", title: @event.title %>
</p>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0">
<%= gettext "An organizer just approved your participation. You're now going to this event!" %>
</p>
</td>
</tr>
<!-- BULLETPROOF BUTTON -->
<tr>
<td bgcolor="#ffffff" align="left">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#424056"><a href="<%= page_url(MobilizonWeb.Endpoint, :event, @event.id) %>" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #424056; display: inline-block;">
<%= gettext "Go to event page" %>
</a></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 20px;" >
<p style="margin: 0">
<%= gettext "If you need to cancel your participation, just access the event page through link above and click on the participation button." %>
</p>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>

View File

@ -0,0 +1,11 @@
<%= gettext "Participation approved" %>
==
<%= gettext "You requested to participate in event %{title}.", title: @event.title %>
<%= gettext "An organizer just approved your participation. You're now going to this event!" %>
<%= page_url(MobilizonWeb.Endpoint, :event, @event.id) %>
<%= gettext "If you need to cancel your participation, just access the previous link and click on the participation button." %>

View File

@ -0,0 +1,56 @@
<!-- HERO -->
<tr>
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Sorry!" %>
</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<!-- COPY -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;">
<%= gettext "You requested to participate in event %{title}", title: @event.title %>
</p>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0">
<%= gettext "Unfortunately, the organizers rejected your participation." %>
</p>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>

View File

@ -0,0 +1,7 @@
<%= gettext "Participation rejected" %>
==
<%= gettext "You requested to participate in event %{title}.", title: @event.title %>
<%= gettext "Unfortunately, the organizers rejected your participation." %>

View File

@ -8,7 +8,6 @@ defmodule Mobilizon.Service.ActivityPub.Activity do
local: boolean,
actor: Actor.t(),
recipients: [String.t()]
# notifications: [???]
}
defstruct [
@ -16,6 +15,5 @@ defmodule Mobilizon.Service.ActivityPub.Activity do
:local,
:actor,
:recipients
# :notifications
]
end

View File

@ -454,7 +454,8 @@ defmodule Mobilizon.Service.ActivityPub do
{:only_organizer, Participant.is_not_only_organizer(event_id, actor_id)},
{:ok, %Participant{} = participant} <-
Mobilizon.Events.get_participant(event_id, actor_id),
{:ok, %Participant{} = participant} <- Mobilizon.Events.delete_participant(participant),
{:ok, %Participant{} = participant} <-
Events.delete_participant(participant),
leave_data <- %{
"type" => "Leave",
# If it's an exclusion it should be something else

View File

@ -14,6 +14,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
alias Mobilizon.Events.{Comment, Event, Participant}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils, Visibility}
alias MobilizonWeb.Email.Participation
require Logger
@ -543,10 +544,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
@doc """
Handle incoming `Accept` activities wrapping a `Join` activity on an event
"""
def do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
with {:join_event,
{:ok,
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
@ -566,7 +565,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{join_id}"
),
{:ok, %Participant{role: :participant}} <-
Events.update_participant(participant, %{"role" => :participant}) do
Events.update_participant(participant, %{"role" => :participant}),
:ok <-
Participation.send_emails_to_local_user(participant) do
{:ok, activity, participant}
else
{:join_event, {:ok, %Participant{role: :participant}}} ->
@ -591,10 +592,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
@doc """
Handle incoming `Reject` activities wrapping a `Join` activity on an event
"""
def do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
# Handle incoming `Reject` activities wrapping a `Join` activity on an event
defp do_handle_incoming_reject_join(join_object, %Actor{} = actor_accepting) do
with {:join_event,
{:ok,
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
@ -613,8 +612,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
},
"#{MobilizonWeb.Endpoint.url()}/reject/join/#{join_id}"
),
{:ok, %Participant{}} <-
Events.delete_participant(participant) do
{:ok, %Participant{role: :rejected} = participant} <-
Events.update_participant(participant, %{"role" => :rejected}),
:ok <- Participation.send_emails_to_local_user(participant) do
{:ok, activity, participant}
else
{:join_event, {:ok, %Participant{role: :participant}}} ->

View File

@ -12,129 +12,231 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 2.4.0\n"
#: lib/mobilizon_web/templates/email/email.text.eex:3
#, elixir-format
msgid "An email sent by Mobilizon on %{instance}."
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:48
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.text.eex:12
msgid "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one."
msgstr ""
#: lib/service/export/feed.ex:161
#, elixir-format
#: lib/service/export/feed.ex:169
msgid "Feed for %{email} on Mobilizon"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:122
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:122
#: lib/mobilizon_web/templates/email/email.text.eex:6
msgid "%{instance} is a Mobilizon server."
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:38
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:38
msgid "%{reporter_name} (%{reporter_username}) reported the following content."
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:48
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:48
msgid "%{title} by %{creator}"
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:58
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:58
msgid "Activate my account"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:91
#: lib/mobilizon_web/templates/email/password_reset.html.eex:94
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:91
msgid "Ask the community on Framacolibri"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:62
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:62
#: lib/mobilizon_web/templates/email/report.text.eex:11
msgid "Comments"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:46
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:46
#: lib/mobilizon_web/templates/email/report.text.eex:6
msgid "Event"
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:45
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:45
msgid "If you didn't request this, please ignore this email."
msgstr ""
#: lib/mobilizon_web/email/user.ex:46
#, elixir-format
#: lib/mobilizon_web/email/user.ex:45
msgid "Instructions to reset your password on %{instance}"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:123
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:123
msgid "Learn more about Mobilizon."
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:13
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:13
msgid "Nearly here!"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:88
#: lib/mobilizon_web/templates/email/password_reset.html.eex:91
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:88
msgid "Need some help? Something not working properly?"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:13
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:13
msgid "New report on %{instance}"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:80
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:80
#: lib/mobilizon_web/templates/email/report.text.eex:18
msgid "Reason"
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:61
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:61
msgid "Reset Password"
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:41
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:41
msgid "Resetting your password is easy. Just press the button below and follow the instructions. We'll have you up and running in no time."
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:13
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:13
msgid "Trouble signing in?"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:100
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:100
msgid "View the report"
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:38
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:38
msgid "You created an account on %{host} with this email address. You are one click away from activating it."
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:38
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:38
msgid "You requested a new password for your account on %{server}."
msgstr ""
#: lib/mobilizon_web/email/user.ex:25
#, elixir-format
#: lib/mobilizon_web/email/user.ex:25
msgid "Instructions to confirm your Mobilizon account on %{instance}"
msgstr ""
#: lib/mobilizon_web/email/admin.ex:23
#, elixir-format
#: lib/mobilizon_web/email/admin.ex:23
msgid "New report on Mobilizon instance %{instance}"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:1
msgid "Activate your account"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.html.eex:13
msgid "All good!"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.html.eex:45
#: lib/mobilizon_web/templates/email/event_participation_approved.text.eex:7
msgid "An organizer just approved your participation. You're now going to this event!"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.html.eex:58
msgid "Go to event page"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.html.eex:70
msgid "If you need to cancel your participation, just access the event page through link above and click on the participation button."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.text.eex:11
msgid "If you need to cancel your participation, just access the previous link and click on the participation button."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/email.text.eex:6
msgid "Learn more about Mobilizon:"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/report.text.eex:1
msgid "New report from %{reporter} on %{instance}"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.text.eex:1
msgid "Participation approved"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_rejected.text.eex:1
msgid "Participation rejected"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.text.eex:1
msgid "Password reset"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.text.eex:7
msgid "Resetting your password is easy. Just click the link below and follow the instructions. We'll have you up and running in no time."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_rejected.html.eex:13
msgid "Sorry!"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_rejected.html.eex:45
#: lib/mobilizon_web/templates/email/event_participation_rejected.text.eex:7
msgid "Unfortunately, the organizers rejected your participation."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:5
msgid "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.text.eex:5
msgid "You requested a new password for your account on %{host}."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.html.eex:38
#: lib/mobilizon_web/templates/email/event_participation_rejected.html.eex:38
msgid "You requested to participate in event %{title}"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/event_participation_approved.text.eex:5
#: lib/mobilizon_web/templates/email/event_participation_rejected.text.eex:5
msgid "You requested to participate in event %{title}."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/email/participation.ex:73
msgid "Your participation to event %{title} has been approved"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/email/participation.ex:52
msgid "Your participation to event %{title} has been rejected"
msgstr ""

View File

@ -0,0 +1,93 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: cs\n"
"Plural-Forms: nplurals=3\n"
msgid "can't be blank"
msgstr ""
msgid "has already been taken"
msgstr ""
msgid "is invalid"
msgstr ""
msgid "must be accepted"
msgstr ""
msgid "has invalid format"
msgstr ""
msgid "has an invalid entry"
msgstr ""
msgid "is reserved"
msgstr ""
msgid "does not match confirmation"
msgstr ""
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgstr[2] ""
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""

View File

@ -12,129 +12,231 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 2.4.0\n"
#: lib/mobilizon_web/templates/email/email.text.eex:3
#, elixir-format
msgid "An email sent by Mobilizon on %{instance}."
msgstr ""
#: lib/mobilizon_web/templates/email/password_reset.html.eex:48
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.text.eex:12
msgid "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one."
msgstr ""
#: lib/service/export/feed.ex:161
#, elixir-format
#: lib/service/export/feed.ex:169
msgid "Feed for %{email} on Mobilizon"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:122
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:122
#: lib/mobilizon_web/templates/email/email.text.eex:6
msgid "%{instance} is a Mobilizon server."
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:38
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:38
msgid "%{reporter_name} (%{reporter_username}) reported the following content."
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:48
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:48
msgid "%{title} by %{creator}"
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:58
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:58
msgid "Activate my account"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:91
#: lib/mobilizon_web/templates/email/password_reset.html.eex:94
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:91
msgid "Ask the community on Framacolibri"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:62
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:62
#: lib/mobilizon_web/templates/email/report.text.eex:11
msgid "Comments"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:46
#, elixir-format
#: lib/mobilizon_web/templates/email/report.html.eex:46
#: lib/mobilizon_web/templates/email/report.text.eex:6
msgid "Event"
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:45
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:45
msgid "If you didn't request this, please ignore this email."
msgstr ""
#: lib/mobilizon_web/email/user.ex:46
#, elixir-format
#: lib/mobilizon_web/email/user.ex:45
msgid "Instructions to reset your password on %{instance}"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:123
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:123
msgid "Learn more about Mobilizon."
msgstr ""
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:13
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:13
msgid "Nearly here!"
msgstr ""
#: lib/mobilizon_web/templates/email/email.html.eex:88
#: lib/mobilizon_web/templates/email/password_reset.html.eex:91
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:88
msgid "Need some help? Something not working properly?"
msgstr ""
#: lib/mobilizon_web/templates/email/report.html.eex:13
#, elixir-format