Allow to accept / reject participants
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
ffa4ec9209
commit
abf3a58657
|
@ -82,6 +82,7 @@ export default class App extends Vue {
|
|||
@import "~bulma/sass/components/dropdown.sass";
|
||||
@import "~bulma/sass/components/breadcrumb.sass";
|
||||
@import "~bulma/sass/components/list.sass";
|
||||
@import "~bulma/sass/components/tabs";
|
||||
@import "~bulma/sass/elements/box.sass";
|
||||
@import "~bulma/sass/elements/button.sass";
|
||||
@import "~bulma/sass/elements/container.sass";
|
||||
|
@ -112,6 +113,7 @@ export default class App extends Vue {
|
|||
@import "~buefy/src/scss/components/radio";
|
||||
@import "~buefy/src/scss/components/switch";
|
||||
@import "~buefy/src/scss/components/table";
|
||||
@import "~buefy/src/scss/components/tabs";
|
||||
|
||||
.router-enter-active,
|
||||
.router-leave-active {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<span>
|
||||
<router-link v-if="actor.domain === null"
|
||||
<span v-if="actor.domain === null"
|
||||
:to="{name: 'Profile', params: { name: actor.preferredUsername } }"
|
||||
>
|
||||
<slot></slot>
|
||||
</router-link>
|
||||
</span>
|
||||
<a v-else :href="actor.url">
|
||||
<slot></slot>
|
||||
</a>
|
||||
|
|
48
js/src/components/Account/ParticipantCard.vue
Normal file
48
js/src/components/Account/ParticipantCard.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<article class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left" v-if="participant.actor.avatar">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="participant.actor.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span class="title" ref="title">{{ actorDisplayName }}</span><br>
|
||||
<small class="has-text-grey">@{{ participant.actor.preferredUsername }}</small>
|
||||
</div>
|
||||
</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="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>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IActor, IPerson, Person } from '@/types/actor';
|
||||
import { IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
|
||||
@Component
|
||||
export default class ActorCard extends Vue {
|
||||
@Prop({ required: true }) participant!: IParticipant;
|
||||
@Prop({ type: Function }) accept;
|
||||
@Prop({ type: Function }) reject;
|
||||
@Prop({ type: Function }) exclude;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
get actorDisplayName(): string {
|
||||
const actor = new Person(this.participant.actor);
|
||||
return actor.displayName();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -60,7 +60,6 @@ export interface IEventCardOptions {
|
|||
@Component({
|
||||
components: {
|
||||
DateCalendarIcon,
|
||||
EventCard,
|
||||
},
|
||||
mounted() {
|
||||
lineClamp(this.$refs.title, 3);
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
<span v-if="participation.event.physicalAddress && participation.event.physicalAddress.locality">{{ participation.event.physicalAddress.locality }} - </span>
|
||||
<span v-if="participation.actor.id === participation.event.organizerActor.id">{{ $t("You're organizing this event") }}</span>
|
||||
<span v-else>
|
||||
<span>{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span> |
|
||||
<span v-if="participation.event.beginsOn < new Date()">{{ $t('Organized by {name}', { name: participation.event.organizerActor.displayName() } ) }}</span>
|
||||
|
|
||||
<span>{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -50,9 +51,9 @@
|
|||
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
||||
</li>
|
||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
||||
<a @click="">
|
||||
<router-link :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } }">
|
||||
<b-icon icon="account-multiple-plus" /> {{ $t('Manage participations') }}
|
||||
</a>
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: EventRouteName.EVENT, params: { uuid: participation.event.uuid } }"><b-icon icon="view-compact" /> {{ $t('View event page') }}</router-link>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Join event {{ event.title }}</p>
|
||||
<p class="modal-card-title">{{ $t('Join event {title}', {title: event.title}) }}</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body is-flex">
|
||||
|
@ -14,14 +14,18 @@
|
|||
size="is-large"/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>Do you want to participate in {{ event.title }}?</p>
|
||||
<p>{{ $t('Do you want to participate in {title}?', {title: event.title}) }}?</p>
|
||||
|
||||
<b-field :label="$t('Identity')">
|
||||
<identity-picker v-model="identity"></identity-picker>
|
||||
</b-field>
|
||||
|
||||
<p v-if="event.joinOptions === EventJoinOptions.RESTRICTED">
|
||||
{{ $t('The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved')}}
|
||||
</p>
|
||||
|
||||
<p v-if="!event.local">
|
||||
The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.
|
||||
{{ $t('The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -32,13 +36,13 @@
|
|||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="close">
|
||||
Cancel
|
||||
{{ $t('Cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
ref="confirmButton"
|
||||
@click="confirm">
|
||||
Confirm my particpation
|
||||
{{ $t('Confirm my particpation') }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -46,7 +50,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { IEvent } from '@/types/event.model';
|
||||
import { IEvent, EventJoinOptions } from '@/types/event.model';
|
||||
import IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
|
@ -66,6 +70,8 @@ export default class ReportModal extends Vue {
|
|||
isActive: boolean = false;
|
||||
identity: IPerson = this.defaultIdentity;
|
||||
|
||||
EventJoinOptions = EventJoinOptions;
|
||||
|
||||
confirm() {
|
||||
this.onConfirm(this.identity);
|
||||
}
|
||||
|
|
|
@ -16,24 +16,23 @@
|
|||
size="is-large"/>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>The report will be sent to the moderators of your instance.
|
||||
You can explain why you report this content below.</p>
|
||||
<p>{{ $t('The report will be sent to the moderators of your instance. You can explain why you report this content below.') }}</p>
|
||||
|
||||
<div class="control">
|
||||
<b-input
|
||||
v-model="content"
|
||||
type="textarea"
|
||||
@keyup.enter="confirm"
|
||||
placeholder="Additional comments"
|
||||
:placeholder="$t('Additional comments')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="outsideDomain">
|
||||
The content came from another server. Transfer an anonymous copy of the report ?
|
||||
{{ $t('The content came from another server. Transfer an anonymous copy of the report?') }}
|
||||
</p>
|
||||
|
||||
<div class="control" v-if="outsideDomain">
|
||||
<b-switch v-model="forward">Transfer to {{ outsideDomain }}</b-switch>
|
||||
<b-switch v-model="forward">{{ $t('Transfer to {outsideDomain}', { outsideDomain }) }}</b-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,13 +43,13 @@
|
|||
class="button"
|
||||
ref="cancelButton"
|
||||
@click="close">
|
||||
{{ cancelText }}
|
||||
{{ translatedCancelText }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-primary"
|
||||
ref="confirmButton"
|
||||
@click="confirm">
|
||||
{{ confirmText }}
|
||||
{{ translatedConfirmText }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -69,13 +68,21 @@ export default class ReportModal extends Vue {
|
|||
@Prop({ type: Function, default: () => {} }) onConfirm;
|
||||
@Prop({ type: String }) title;
|
||||
@Prop({ type: String, default: '' }) outsideDomain;
|
||||
@Prop({ type: String, default: 'Cancel' }) cancelText;
|
||||
@Prop({ type: String, default: 'Send the report' }) confirmText;
|
||||
@Prop({ type: String }) cancelText;
|
||||
@Prop({ type: String }) confirmText;
|
||||
|
||||
isActive: boolean = false;
|
||||
content: string = '';
|
||||
forward: boolean = false;
|
||||
|
||||
get translatedCancelText() {
|
||||
return this.cancelText || this.$t('Cancel');
|
||||
}
|
||||
|
||||
get translatedConfirmText() {
|
||||
return this.confirmText || this.$t('Send the report');
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.onConfirm(this.content, this.forward);
|
||||
this.close();
|
||||
|
|
|
@ -91,6 +91,7 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
|||
remainingAttendeeCapacity
|
||||
}
|
||||
},
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id,
|
||||
|
|
|
@ -2,6 +2,7 @@ import gql from 'graphql-tag';
|
|||
|
||||
const participantQuery = `
|
||||
role,
|
||||
id,
|
||||
actor {
|
||||
preferredUsername,
|
||||
avatar {
|
||||
|
@ -50,7 +51,7 @@ const optionsQuery = `
|
|||
`;
|
||||
|
||||
export const FETCH_EVENT = gql`
|
||||
query($uuid:UUID!) {
|
||||
query($uuid:UUID!, $roles: String) {
|
||||
event(uuid: $uuid) {
|
||||
id,
|
||||
uuid,
|
||||
|
@ -63,6 +64,7 @@ export const FETCH_EVENT = gql`
|
|||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
@ -92,7 +94,7 @@ export const FETCH_EVENT = gql`
|
|||
# preferredUsername,
|
||||
# name,
|
||||
# },
|
||||
participants {
|
||||
participants (roles: $roles) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
|
@ -183,7 +185,8 @@ export const CREATE_EVENT = gql`
|
|||
$beginsOn: DateTime!,
|
||||
$endsOn: DateTime,
|
||||
$status: EventStatus,
|
||||
$visibility: EventVisibility
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
|
@ -200,6 +203,7 @@ export const CREATE_EVENT = gql`
|
|||
endsOn: $endsOn,
|
||||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
|
@ -216,6 +220,7 @@ export const CREATE_EVENT = gql`
|
|||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
@ -245,7 +250,8 @@ export const EDIT_EVENT = gql`
|
|||
$beginsOn: DateTime,
|
||||
$endsOn: DateTime,
|
||||
$status: EventStatus,
|
||||
$visibility: EventVisibility
|
||||
$visibility: EventVisibility,
|
||||
$joinOptions: EventJoinOptions,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$onlineAddress: String,
|
||||
|
@ -262,6 +268,7 @@ export const EDIT_EVENT = gql`
|
|||
endsOn: $endsOn,
|
||||
status: $status,
|
||||
visibility: $visibility,
|
||||
joinOptions: $joinOptions,
|
||||
tags: $tags,
|
||||
picture: $picture,
|
||||
onlineAddress: $onlineAddress,
|
||||
|
@ -278,6 +285,7 @@ export const EDIT_EVENT = gql`
|
|||
endsOn,
|
||||
status,
|
||||
visibility,
|
||||
joinOptions,
|
||||
picture {
|
||||
id
|
||||
url
|
||||
|
@ -323,6 +331,23 @@ export const LEAVE_EVENT = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const ACCEPT_PARTICIPANT = gql`
|
||||
mutation AcceptParticipant($id: ID!, $moderatorActorId: ID!) {
|
||||
acceptParticipation(id: $id, moderatorActorId: $moderatorActorId) {
|
||||
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(
|
||||
|
@ -333,3 +358,17 @@ export const DELETE_EVENT = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const PARTICIPANTS = gql`
|
||||
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String) {
|
||||
event(uuid: $uuid) {
|
||||
participants(page: $page, limit: $limit, roles: $roles) {
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -211,5 +211,29 @@
|
|||
"Load more": "Load more",
|
||||
"Past events": "Passed events",
|
||||
"View everything": "View everything",
|
||||
"Last week": "Last week"
|
||||
"Last week": "Last week",
|
||||
"Approve": "Approve",
|
||||
"Reject": "Reject",
|
||||
"Exclude": "Exclude",
|
||||
"Creator": "Creator",
|
||||
"Join event {title}": "Join event {title}",
|
||||
"Cancel": "Cancel",
|
||||
"Confirm my particpation": "Confirm my particpation",
|
||||
"Manage participants": "Manage participants",
|
||||
"No participants yet.": "No participants yet.",
|
||||
"Participants": "Participants",
|
||||
"Do you want to participate in {title}?": "Do you want to participate in {title}?",
|
||||
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved",
|
||||
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.",
|
||||
"Waiting list": "Waiting list",
|
||||
"Leaving event \"{title}\"": "Leaving event \"{title}\"",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Are you sure you want to cancel your participation at event \"{title}\"?",
|
||||
"Leave event": "Leave event",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
|
||||
"Additional comments": "Additional comments",
|
||||
"The content came from another server. Transfer an anonymous copy of the report?": "The content came from another server. Transfer an anonymous copy of the report ?",
|
||||
"Transfer to {outsideDomain}": "Transfer to {outsideDomain}",
|
||||
"Send the report": "Send the report",
|
||||
"Report this event": "Report this event"
|
||||
|
||||
}
|
|
@ -138,7 +138,7 @@
|
|||
"Register an account on Mobilizon!": "S'inscrire sur Mobilizon !",
|
||||
"Register": "S'inscrire",
|
||||
"Registration is currently closed.": "Les inscriptions sont actuellement fermées.",
|
||||
"Report": "Report",
|
||||
"Report": "Signaler",
|
||||
"Resend confirmation email": "Envoyer à nouveau l'email de confirmation",
|
||||
"Reset my password": "Réinitialiser mon mot de passe",
|
||||
"Save": "Enregistrer",
|
||||
|
@ -211,5 +211,28 @@
|
|||
"Load more": "Voir plus",
|
||||
"Past events": "Événements passés",
|
||||
"View everything": "Voir tout",
|
||||
"Last week": "La semaine dernière"
|
||||
"Last week": "La semaine dernière",
|
||||
"Approve": "Approuver",
|
||||
"Reject": "Rejetter",
|
||||
"Exclude": "Exclure",
|
||||
"Creator": "Créateur",
|
||||
"Join event {title}": "Rejoindre {title}",
|
||||
"Cancel": "Annuler",
|
||||
"Confirm my particpation": "Confirmer ma particpation",
|
||||
"Manage participants": "Gérer les participants",
|
||||
"No participants yet.": "Pas de participants pour le moment.",
|
||||
"Participants": "Participants",
|
||||
"Do you want to participate in {title}?": "Voulez-vous participer à {title} ?",
|
||||
"The event organizer has chosen to approve manually the participations to this event. You will receive a notification when your participation has been approved": "L'organisateur⋅ice de l'événement a choisi d'approuver manuellement les participations à cet événement. Vous recevrez une notification lorsque votre participation sera approuvée",
|
||||
"The event came from another instance. Your participation will be confirmed after we confirm it with the other instance.": "L'événement provient d'une autre instance. Votre participation sera confirmée après que nous ayons la confirmation de l'autre instance.",
|
||||
"Waiting list": "Liste d'attente",
|
||||
"Leaving event \"{title}\"": "Annuler ma participation à l'événement",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Êtes-vous certain⋅e de vouloir annuler votre participation à l'événement « {title} » ?",
|
||||
"Leave event": "Annuler ma participation à l'événement",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.",
|
||||
"Additional comments": "Commentaires additionnels",
|
||||
"The content came from another server. Transfer an anonymous copy of the report?": "Le contenu provient d'une autre instance. Transférer une copie anonyme du signalement ?",
|
||||
"Transfer to {outsideDomain}": "Transférer à {outsideDomain}",
|
||||
"Send the report": "Envoyer le signalement",
|
||||
"Report this event": "Signaler cet événement"
|
||||
}
|
|
@ -3,9 +3,10 @@ import Location from '@/views/Location.vue';
|
|||
import { RouteConfig } from 'vue-router';
|
||||
|
||||
// tslint:disable:space-in-parens
|
||||
const editEvent = () => import(/* webpackChunkName: "create-event" */ '@/views/Event/Edit.vue');
|
||||
const participations = () => import(/* webpackChunkName: "participations" */ '@/views/Event/Participants.vue');
|
||||
const editEvent = () => import(/* webpackChunkName: "edit-event" */ '@/views/Event/Edit.vue');
|
||||
const event = () => import(/* webpackChunkName: "event" */ '@/views/Event/Event.vue');
|
||||
const myEvents = () => import(/* webpackChunkName: "event" */ '@/views/Event/MyEvents.vue');
|
||||
const myEvents = () => import(/* webpackChunkName: "my-events" */ '@/views/Event/MyEvents.vue');
|
||||
// tslint:enable
|
||||
|
||||
export enum EventRouteName {
|
||||
|
@ -13,6 +14,7 @@ export enum EventRouteName {
|
|||
CREATE_EVENT = 'CreateEvent',
|
||||
MY_EVENTS = 'MyEvents',
|
||||
EDIT_EVENT = 'EditEvent',
|
||||
PARTICIPATIONS = 'Participations',
|
||||
EVENT = 'Event',
|
||||
LOCATION = 'Location',
|
||||
}
|
||||
|
@ -43,6 +45,13 @@ export const eventRoutes: RouteConfig[] = [
|
|||
meta: { requiredAuth: true },
|
||||
props: { isUpdate: true },
|
||||
},
|
||||
{
|
||||
path: '/events/participations/:eventId',
|
||||
name: EventRouteName.PARTICIPATIONS,
|
||||
component: participations,
|
||||
meta: { requiredAuth: true },
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: '/location/new',
|
||||
name: EventRouteName.LOCATION,
|
||||
|
|
|
@ -29,11 +29,11 @@ export enum EventVisibilityJoinOptions {
|
|||
}
|
||||
|
||||
export enum ParticipantRole {
|
||||
NOT_APPROVED = 'not_approved',
|
||||
PARTICIPANT = 'participant',
|
||||
MODERATOR = 'moderator',
|
||||
ADMINISTRATOR = 'administrator',
|
||||
CREATOR = 'creator',
|
||||
NOT_APPROVED = 'NOT_APPROVED',
|
||||
PARTICIPANT = 'PARTICIPANT',
|
||||
MODERATOR = 'MODERATOR',
|
||||
ADMINISTRATOR = 'ADMINISTRATOR',
|
||||
CREATOR = 'CREATOR',
|
||||
}
|
||||
|
||||
export enum Category {
|
||||
|
@ -45,12 +45,14 @@ export enum Category {
|
|||
}
|
||||
|
||||
export interface IParticipant {
|
||||
id?: string;
|
||||
role: ParticipantRole;
|
||||
actor: IActor;
|
||||
event: IEvent;
|
||||
}
|
||||
|
||||
export class Participant implements IParticipant {
|
||||
id?: string;
|
||||
event!: IEvent;
|
||||
actor!: IActor;
|
||||
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
|
||||
|
@ -58,6 +60,7 @@ export class Participant implements IParticipant {
|
|||
constructor(hash?: IParticipant) {
|
||||
if (!hash) return;
|
||||
|
||||
this.id = hash.id;
|
||||
this.event = new EventModel(hash.event);
|
||||
this.actor = new Actor(hash.actor);
|
||||
this.role = hash.role;
|
||||
|
@ -83,7 +86,7 @@ export enum CommentModeration {
|
|||
}
|
||||
|
||||
export interface IEvent {
|
||||
id?: number;
|
||||
id?: string;
|
||||
uuid: string;
|
||||
url: string;
|
||||
local: boolean;
|
||||
|
@ -147,7 +150,7 @@ export class EventOptions implements IEventOptions {
|
|||
}
|
||||
|
||||
export class EventModel implements IEvent {
|
||||
id?: number;
|
||||
id?: string;
|
||||
|
||||
beginsOn = new Date();
|
||||
endsOn: Date | null = new Date();
|
||||
|
@ -232,6 +235,7 @@ export class EventModel implements IEvent {
|
|||
endsOn: this.endsOn ? this.endsOn.toISOString() : null,
|
||||
status: this.status,
|
||||
visibility: this.visibility,
|
||||
joinOptions: this.joinOptions,
|
||||
tags: this.tags.map(t => t.title),
|
||||
picture: this.picture,
|
||||
onlineAddress: this.onlineAddress,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import {EventJoinOptions} from "@/types/event.model";
|
||||
<template>
|
||||
<section class="container">
|
||||
<h1 class="title" v-if="isUpdate === false">
|
||||
|
@ -187,11 +188,17 @@
|
|||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT, FETCH_EVENTS } from '@/graphql/event';
|
||||
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
|
||||
import { EventModel, EventStatus, EventVisibility, EventVisibilityJoinOptions, CommentModeration, IEvent } from '@/types/event.model';
|
||||
import {
|
||||
CommentModeration, EventJoinOptions,
|
||||
EventModel,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
EventVisibilityJoinOptions,
|
||||
} from '@/types/event.model';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson, Person } from '@/types/actor';
|
||||
import { Person } from '@/types/actor';
|
||||
import PictureUpload from '@/components/PictureUpload.vue';
|
||||
import Editor from '@/components/Editor.vue';
|
||||
import DateTimePicker from '@/components/Event/DateTimePicker.vue';
|
||||
|
@ -352,6 +359,15 @@ export default class EditEvent extends Vue {
|
|||
}
|
||||
}
|
||||
|
||||
@Watch('needsApproval')
|
||||
updateEventJoinOptions(needsApproval) {
|
||||
if (needsApproval === true) {
|
||||
this.event.joinOptions = EventJoinOptions.RESTRICTED;
|
||||
} else {
|
||||
this.event.joinOptions = EventJoinOptions.FREE;
|
||||
}
|
||||
}
|
||||
|
||||
// getAddressData(addressData) {
|
||||
// if (addressData !== null) {
|
||||
// this.event.address = {
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
</b-modal>
|
||||
</div>
|
||||
<div class="organizer">
|
||||
<actor-link :actor="event.organizerActor">
|
||||
<span>
|
||||
<span v-if="event.organizerActor">
|
||||
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
||||
</span>
|
||||
|
@ -113,31 +113,11 @@
|
|||
:src="event.organizerActor.avatar.url"
|
||||
:alt="event.organizerActor.avatar.alt" />
|
||||
</figure>
|
||||
</actor-link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- <p v-if="actorIsOrganizer()">-->
|
||||
<!-- <translate>You are an organizer.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <div v-else>-->
|
||||
<!-- <p v-if="actorIsParticipant()">-->
|
||||
<!-- <translate>You announced that you're going to this event.</translate>-->
|
||||
<!-- </p>-->
|
||||
<!-- <p v-else>-->
|
||||
<!-- <translate>Are you going to this event?</translate><br />-->
|
||||
<!-- <span>-->
|
||||
<!-- <translate-->
|
||||
<!-- :translate-n="event.participants.length"-->
|
||||
<!-- translate-plural="{event.participants.length} persons are going"-->
|
||||
<!-- >-->
|
||||
<!-- One person is going.-->
|
||||
<!-- </translate>-->
|
||||
<!-- </span>-->
|
||||
<!-- </p>-->
|
||||
<!-- </div>-->
|
||||
<div class="description">
|
||||
<div class="description-container container">
|
||||
<h3 class="title">
|
||||
|
@ -147,63 +127,31 @@
|
|||
{{ $t("The event organizer didn't add any description.") }}
|
||||
</p>
|
||||
<div class="columns" v-else>
|
||||
<div class="column is-half">
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Suspendisse vehicula ex dapibus augue volutpat, ultrices cursus mi rutrum.
|
||||
Nunc ante nunc, facilisis a tellus quis, tempor mollis diam. Aenean consectetur quis est a ultrices.
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</p>
|
||||
<p><a href="https://framasoft.org">https://framasoft.org</a>
|
||||
<p>
|
||||
Nam sit amet est eget velit tristique commodo. Etiam sollicitudin dignissim diam, ut ultricies tortor.
|
||||
Sed quis blandit diam, a tincidunt nunc. Donec tincidunt tristique neque at rhoncus. Ut eget vulputate felis.
|
||||
Pellentesque nibh purus, viverra ac augue sed, iaculis feugiat velit. Nulla ut hendrerit elit.
|
||||
Etiam at justo eu nunc tempus sagittis. Sed ac tincidunt tellus, sit amet luctus velit.
|
||||
Nam ullamcorper eros eleifend, eleifend diam vitae, lobortis risus.
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
Curabitur rhoncus sapien tortor, vitae imperdiet massa scelerisque non.
|
||||
Aliquam eu augue mi. Donec hendrerit lorem orci.
|
||||
</em>
|
||||
</p>
|
||||
<p>
|
||||
Donec volutpat, enim eu laoreet dictum, urna quam varius enim, eu convallis urna est vitae massa.
|
||||
Morbi porttitor lacus a sem efficitur blandit. Mauris in est in quam tincidunt iaculis non vitae ipsum.
|
||||
Phasellus eget velit tellus. Curabitur ac neque pharetra velit viverra mollis.
|
||||
</p>
|
||||
<img src="https://framasoft.org/img/biglogo-notxt.png" alt="logo Framasoft"/>
|
||||
<p>Aenean gravida, ante vitae aliquet aliquet, elit quam tristique orci, sit amet dictum lorem ipsum nec tortor.
|
||||
Vestibulum est eros, faucibus et semper vel, dapibus ac est. Suspendisse potenti. Suspendisse potenti.
|
||||
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
Nulla molestie nisi ac risus hendrerit, dapibus mattis sapien scelerisque.
|
||||
</p>
|
||||
<p>Maecenas id pretium justo, nec dignissim sapien. Mauris in venenatis odio, in congue augue. </p>
|
||||
<div class="column is-half" v-html="event.description">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <section class="container">-->
|
||||
<!-- <h2 class="title">Participants</h2>-->
|
||||
<!-- <span v-if="event.participants.length === 0">No participants yet.</span>-->
|
||||
<!-- <div class="columns">-->
|
||||
<!-- <router-link-->
|
||||
<!-- class="column"-->
|
||||
<!-- v-for="participant in event.participants"-->
|
||||
<!-- :key="participant.preferredUsername"-->
|
||||
<!-- :to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"-->
|
||||
<!-- >-->
|
||||
<!-- <div>-->
|
||||
<!-- <figure>-->
|
||||
<!-- <img v-if="!participant.actor.avatar.url" src="https://picsum.photos/125/125/">-->
|
||||
<!-- <img v-else :src="participant.actor.avatar.url">-->
|
||||
<!-- </figure>-->
|
||||
<!-- <span>{{ participant.actor.preferredUsername }}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </router-link>-->
|
||||
<!-- </div>-->
|
||||
<!-- </section>-->
|
||||
<section class="container">
|
||||
<h3 class="title">{{ $t('Participants') }}</h3>
|
||||
<router-link v-if="currentActor.id === event.organizerActor.id" :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: event.uuid } }">
|
||||
{{ $t('Manage participants') }}
|
||||
</router-link>
|
||||
<span v-if="event.participants.length === 0">{{ $t('No participants yet.') }}</span>
|
||||
<div class="columns">
|
||||
<div
|
||||
class="column"
|
||||
v-for="participant in event.participants"
|
||||
:key="participant.id"
|
||||
>
|
||||
<figure class="image is-48x48">
|
||||
<img v-if="!participant.actor.avatar.url" src="https://picsum.photos/48/48/" class="is-rounded">
|
||||
<img v-else :src="participant.actor.avatar.url" class="is-rounded">
|
||||
</figure>
|
||||
<span>{{ participant.actor.preferredUsername }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="share">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
|
@ -236,7 +184,7 @@
|
|||
</div>
|
||||
</section>
|
||||
<b-modal :active.sync="isReportModalActive" has-modal-card ref="reportModal">
|
||||
<report-modal :on-confirm="reportEvent" title="Report this event" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
|
||||
<report-modal :on-confirm="reportEvent" :title="$t('Report this event')" :outside-domain="event.organizerActor.domain" @close="$refs.reportModal.close()" />
|
||||
</b-modal>
|
||||
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
|
||||
<participation-modal :on-confirm="joinEvent" :event="event" :defaultIdentity="currentActor" @close="$refs.participationModal.close()" />
|
||||
|
@ -249,7 +197,7 @@
|
|||
import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { EventVisibility, IEvent, IParticipant } from '@/types/event.model';
|
||||
import { EventVisibility, IEvent, IParticipant, ParticipantRole } from '@/types/event.model';
|
||||
import { IPerson } from '@/types/actor';
|
||||
import { RouteName } from '@/router';
|
||||
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
|
||||
|
@ -263,6 +211,7 @@ import ParticipationModal from '@/components/Event/ParticipationModal.vue';
|
|||
import { IReport } from '@/types/report.model';
|
||||
import { CREATE_REPORT } from '@/graphql/report';
|
||||
import EventMixin from '@/mixins/event';
|
||||
import { EventRouteName } from '@/router/event';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -283,6 +232,7 @@ import EventMixin from '@/mixins/event';
|
|||
variables() {
|
||||
return {
|
||||
uuid: this.uuid,
|
||||
roles: [ParticipantRole.CREATOR, ParticipantRole.MODERATOR, ParticipantRole.MODERATOR, ParticipantRole.PARTICIPANT].join(),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -302,6 +252,7 @@ export default class Event extends EventMixin {
|
|||
isJoinModalActive: boolean = false;
|
||||
|
||||
EventVisibility = EventVisibility;
|
||||
EventRouteName = EventRouteName;
|
||||
|
||||
/**
|
||||
* Delete the event, then redirect to home.
|
||||
|
@ -367,9 +318,10 @@ export default class Event extends EventMixin {
|
|||
|
||||
confirmLeave() {
|
||||
this.$buefy.dialog.confirm({
|
||||
title: `Leaving event « ${this.event.title} »`,
|
||||
message: `Are you sure you want to leave event « ${this.event.title} »`,
|
||||
confirmText: 'Leave event',
|
||||
title: this.$t('Leaving event "{title}"', { title: this.event.title }) as string,
|
||||
message: this.$t('Are you sure you want to cancel your participation at event "{title}"?', { title: this.event.title }) as string,
|
||||
confirmText: this.$t('Leave event') as string,
|
||||
cancelText: this.$t('Cancel') as string,
|
||||
type: 'is-danger',
|
||||
hasIcon: true,
|
||||
onConfirm: () => this.leaveEvent(),
|
||||
|
|
197
js/src/views/Event/Participants.vue
Normal file
197
js/src/views/Event/Participants.vue
Normal file
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<main class="container">
|
||||
<b-tabs type="is-boxed" v-if="event">
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="information-outline"></b-icon>
|
||||
<span> Participants <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
|
||||
</template>
|
||||
<section v-if="participantsAndCreators.length > 0">
|
||||
<h2 class="title">{{ $t('Participants') }}</h2>
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter-desktop" v-for="participant in participantsAndCreators" :key="participant.actor.id">
|
||||
<participant-card
|
||||
:participant="participant"
|
||||
:accept="acceptParticipant"
|
||||
:reject="refuseParticipant"
|
||||
:exclude="refuseParticipant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</b-tab-item>
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="source-pull"></b-icon>
|
||||
<span> Demandes <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
|
||||
</template>
|
||||
<section v-if="queue.length > 0">
|
||||
<h2 class="title">{{ $t('Waiting list') }}</h2>
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter-desktop" v-for="participant in queue" :key="participant.actor.id">
|
||||
<participant-card
|
||||
:participant="participant"
|
||||
:accept="acceptParticipant"
|
||||
:reject="refuseParticipant"
|
||||
:exclude="refuseParticipant"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</b-tab-item>
|
||||
</b-tabs>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<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 ParticipantCard from '@/components/Account/ParticipantCard.vue';
|
||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||
import { IPerson } from '@/types/actor';
|
||||
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ParticipantCard,
|
||||
},
|
||||
apollo: {
|
||||
currentActor: {
|
||||
query: CURRENT_ACTOR_CLIENT,
|
||||
},
|
||||
event: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 10,
|
||||
roles: [ParticipantRole.PARTICIPANT].join(),
|
||||
};
|
||||
},
|
||||
},
|
||||
organizers: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.CREATOR].join(),
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
},
|
||||
queue: {
|
||||
query: PARTICIPANTS,
|
||||
variables() {
|
||||
return {
|
||||
uuid: this.eventId,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
roles: [ParticipantRole.NOT_APPROVED].join(),
|
||||
};
|
||||
},
|
||||
update: data => data.event.participants.map(participation => new Participant(participation)),
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Participants extends Vue {
|
||||
@Prop({ required: true }) eventId!: string;
|
||||
page: number = 1;
|
||||
limit: number = 10;
|
||||
|
||||
// participants: IParticipant[] = [];
|
||||
organizers: IParticipant[] = [];
|
||||
queue: IParticipant[] = [];
|
||||
event!: IEvent;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
currentActor!: IPerson;
|
||||
|
||||
hasMoreParticipants: boolean = false;
|
||||
|
||||
get participants(): IParticipant[] {
|
||||
return this.event.participants.map(participant => new Participant(participant));
|
||||
}
|
||||
|
||||
get participantStats(): Object {
|
||||
return this.event.participantStats;
|
||||
}
|
||||
|
||||
get participantsAndCreators(): IParticipant[] {
|
||||
if (this.event) {
|
||||
return [...this.organizers, ...this.participants];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
loadMoreParticipants() {
|
||||
this.page += 1;
|
||||
this.$apollo.queries.participants.fetchMore({
|
||||
// New variables
|
||||
variables: {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
},
|
||||
// Transform the previous result with new data
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
const newParticipations = fetchMoreResult.event.participants;
|
||||
this.hasMoreParticipants = newParticipations.length === this.limit;
|
||||
|
||||
return {
|
||||
loggedUser: {
|
||||
__typename: previousResult.event.__typename,
|
||||
participations: [...previousResult.event.participants, ...newParticipations],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async acceptParticipant(participant: IParticipant) {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: ACCEPT_PARTICIPANT,
|
||||
variables: {
|
||||
id: participant.id,
|
||||
moderatorActorId: this.currentActor.id,
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
console.log('accept', data);
|
||||
this.queue.filter(participant => participant !== data.acceptParticipation.id);
|
||||
this.participants.push(participant);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async refuseParticipant(participant: IParticipant) {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: REJECT_PARTICIPANT,
|
||||
variables: {
|
||||
id: participant.id,
|
||||
moderatorActorId: this.currentActor.id,
|
||||
},
|
||||
});
|
||||
if (data) {
|
||||
this.participants.filter(participant => participant !== data.rejectParticipation.id);
|
||||
this.queue.filter(participant => participant !== data.rejectParticipation.id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
</style>
|
|
@ -107,7 +107,7 @@ export default class Group extends Vue {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
section.container {
|
||||
min-height: 30em;
|
||||
}
|
||||
|
|
|
@ -32,12 +32,13 @@
|
|||
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t('Group') }}</router-link>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
<section v-if="currentActor" class="container">
|
||||
<section v-if="currentActor && goingToEvents.size > 0" class="container">
|
||||
<h3 class="title">
|
||||
{{ $t("Upcoming") }}
|
||||
</h3>
|
||||
<pre>{{ Array.from(goingToEvents.entries()) }}</pre>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<div v-if="goingToEvents.size > 0" v-for="row in goingToEvents" class="upcoming-events">
|
||||
<div v-for="row in goingToEvents" class="upcoming-events">
|
||||
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
||||
<date-component :date="row[0]"></date-component>
|
||||
<h3 class="subtitle"
|
||||
|
@ -63,9 +64,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<b-message v-else type="is-danger">
|
||||
{{ $t("You're not going to any event yet") }}
|
||||
</b-message>
|
||||
<span class="view-all">
|
||||
<router-link :to=" { name: EventRouteName.MY_EVENTS }">{{ $t('View everything')}} >></router-link>
|
||||
</span>
|
||||
|
@ -78,9 +76,10 @@
|
|||
<div class="level">
|
||||
<EventListCard
|
||||
v-for="participation in lastWeekEvents"
|
||||
:key="participation.event.uuid"
|
||||
:key="participation.id"
|
||||
:participation="participation"
|
||||
class="level-item"
|
||||
:options="{ hideDate: false }"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -190,6 +189,10 @@ export default class Home extends Vue {
|
|||
return this.calculateDiffDays(date) < nbDays;
|
||||
}
|
||||
|
||||
isAfter(date: string, nbDays: number) :boolean {
|
||||
return this.calculateDiffDays(date) >= nbDays;
|
||||
}
|
||||
|
||||
isInLessThanSevenDays(date: string): boolean {
|
||||
return this.isBefore(date, 7);
|
||||
}
|
||||
|
@ -200,7 +203,7 @@ export default class Home extends Vue {
|
|||
|
||||
get goingToEvents(): Map<string, Map<string, IParticipant>> {
|
||||
const res = this.currentUserParticipations.filter(({ event }) => {
|
||||
return event.beginsOn != null && !this.isBefore(event.beginsOn.toDateString(), 0);
|
||||
return event.beginsOn != null && this.isAfter(event.beginsOn.toDateString(), 0) && this.isBefore(event.beginsOn.toDateString(), 7);
|
||||
});
|
||||
res.sort(
|
||||
(a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
|
||||
|
@ -208,7 +211,7 @@ export default class Home extends Vue {
|
|||
return res.reduce((acc: Map<string, Map<string, IParticipant>>, participation: IParticipant) => {
|
||||
const day = (new Date(participation.event.beginsOn)).toDateString();
|
||||
const participations: Map<string, IParticipant> = acc.get(day) || new Map();
|
||||
participations.set(participation.event.uuid, participation);
|
||||
participations.set(`${participation.event.uuid}${participation.actor.id}`, participation);
|
||||
acc.set(day, participations);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
@ -273,7 +276,7 @@ export default class Home extends Vue {
|
|||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.search-autocomplete {
|
||||
border: 1px solid #dbdbdb;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
|
|
|
@ -67,6 +67,7 @@ defmodule Mobilizon.Events.Event do
|
|||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@update_required_attrs @required_attrs
|
||||
|
||||
@update_optional_attrs [
|
||||
:slug,
|
||||
:description,
|
||||
|
@ -74,6 +75,7 @@ defmodule Mobilizon.Events.Event do
|
|||
:category,
|
||||
:status,
|
||||
:visibility,
|
||||
:join_options,
|
||||
:publish_at,
|
||||
:online_address,
|
||||
:phone_address,
|
||||
|
|
|
@ -522,6 +522,26 @@ defmodule Mobilizon.Events do
|
|||
|
||||
@doc """
|
||||
Gets a single participant.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_participant(123)
|
||||
%Participant{}
|
||||
|
||||
iex> get_participant(456)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec get_participant(integer) :: Participant.t()
|
||||
def get_participant(participant_id) do
|
||||
Participant
|
||||
|> where([p], p.id == ^participant_id)
|
||||
|> preload([p], [:event, :actor])
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single participation for an event and actor.
|
||||
"""
|
||||
@spec get_participant(integer | String.t(), integer | String.t()) ::
|
||||
{:ok, Participant.t()} | {:error, :participant_not_found}
|
||||
|
@ -536,8 +556,18 @@ defmodule Mobilizon.Events do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Gets a single participant.
|
||||
Raises `Ecto.NoResultsError` if the participant does not exist.
|
||||
Gets a single participation for an event and actor.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Participant does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_participant!(123, 19)
|
||||
%Participant{}
|
||||
|
||||
iex> get_participant!(456, 5)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
@spec get_participant!(integer | String.t(), integer | String.t()) :: Participant.t()
|
||||
def get_participant!(event_id, actor_id) do
|
||||
|
@ -554,35 +584,20 @@ defmodule Mobilizon.Events do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the default participant role depending on the event join options.
|
||||
"""
|
||||
@spec get_default_participant_role(Event.t()) :: :participant | :not_approved
|
||||
def get_default_participant_role(%Event{join_options: :free}), do: :participant
|
||||
def get_default_participant_role(%Event{join_options: _}), do: :not_approved
|
||||
@default_participant_roles [:participant, :moderator, :administrator, :creator]
|
||||
|
||||
@doc """
|
||||
Creates a participant.
|
||||
Returns the list of participants for an event.
|
||||
Default behaviour is to not return :not_approved participants
|
||||
"""
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_participant(attrs \\ %{}) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
%Participant{}
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a participant.
|
||||
"""
|
||||
@spec update_participant(Participant.t(), map) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_participant(%Participant{} = participant, attrs) do
|
||||
participant
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.update()
|
||||
@spec list_participants_for_event(String.t(), list(atom()), integer | nil, integer | nil) ::
|
||||
[Participant.t()]
|
||||
def list_participants_for_event(uuid, roles \\ @default_participant_roles, page, limit) do
|
||||
uuid
|
||||
|> list_participants_for_event_query()
|
||||
|> filter_role(roles)
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -596,84 +611,43 @@ defmodule Mobilizon.Events do
|
|||
[%Participant{}, ...]
|
||||
|
||||
"""
|
||||
def list_participations_for_user(
|
||||
user_id,
|
||||
after_datetime \\ nil,
|
||||
before_datetime \\ nil,
|
||||
page \\ nil,
|
||||
limit \\ nil
|
||||
)
|
||||
|
||||
def list_participations_for_user(user_id, %DateTime{} = after_datetime, nil, page, limit) do
|
||||
@spec list_participations_for_user(
|
||||
integer,
|
||||
DateTime.t() | nil,
|
||||
DateTime.t() | nil,
|
||||
integer | nil,
|
||||
integer | nil
|
||||
) :: list(Participant.t())
|
||||
def list_participations_for_user(user_id, after_datetime, before_datetime, page, limit) do
|
||||
user_id
|
||||
|> do_list_participations_for_user(page, limit)
|
||||
|> where([_p, e, _a], e.begins_on > ^after_datetime)
|
||||
|> order_by([_p, e, _a], asc: e.begins_on)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def list_participations_for_user(user_id, nil, %DateTime{} = before_datetime, page, limit) do
|
||||
user_id
|
||||
|> do_list_participations_for_user(page, limit)
|
||||
|> where([_p, e, _a], e.begins_on < ^before_datetime)
|
||||
|> order_by([_p, e, _a], desc: e.begins_on)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def list_participations_for_user(user_id, nil, nil, page, limit) do
|
||||
user_id
|
||||
|> do_list_participations_for_user(page, limit)
|
||||
|> order_by([_p, e, _a], desc: e.begins_on)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
defp do_list_participations_for_user(user_id, page, limit) do
|
||||
from(
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
join: a in Actor,
|
||||
on: p.actor_id == a.id,
|
||||
on: p.event_id == e.id,
|
||||
where: a.user_id == ^user_id and p.role != ^:not_approved,
|
||||
preload: [:event, :actor]
|
||||
)
|
||||
|> Page.paginate(page, limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a participant.
|
||||
"""
|
||||
@spec delete_participant(Participant.t()) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
|
||||
|
||||
@doc """
|
||||
Returns the list of participants.
|
||||
"""
|
||||
@spec list_participants :: [Participant.t()]
|
||||
def list_participants, do: Repo.all(Participant)
|
||||
|
||||
@doc """
|
||||
Returns the list of participants for an event.
|
||||
Default behaviour is to not return :not_approved participants
|
||||
"""
|
||||
@spec list_participants_for_event(String.t(), integer | nil, integer | nil, boolean) ::
|
||||
[Participant.t()]
|
||||
def list_participants_for_event(
|
||||
event_uuid,
|
||||
page \\ nil,
|
||||
limit \\ nil,
|
||||
include_not_improved \\ false
|
||||
)
|
||||
|
||||
def list_participants_for_event(event_uuid, page, limit, include_not_improved) do
|
||||
event_uuid
|
||||
|> participants_for_event()
|
||||
|> filter_role(include_not_improved)
|
||||
|> list_participations_for_user_query()
|
||||
|> participation_filter_begins_on(after_datetime, before_datetime)
|
||||
|> Page.paginate(page, limit)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of moderator participants for an event.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> moderator_for_event?(5, 3)
|
||||
true
|
||||
|
||||
"""
|
||||
@spec moderator_for_event?(integer, integer) :: boolean
|
||||
def moderator_for_event?(event_id, actor_id) do
|
||||
!(Repo.one(
|
||||
from(
|
||||
p in Participant,
|
||||
where:
|
||||
p.event_id == ^event_id and
|
||||
p.actor_id ==
|
||||
^actor_id and p.role in ^[:moderator, :administrator, :creator]
|
||||
)
|
||||
) == nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of organizers participants for an event.
|
||||
|
||||
|
@ -739,6 +713,44 @@ defmodule Mobilizon.Events do
|
|||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the default participant role depending on the event join options.
|
||||
"""
|
||||
@spec get_default_participant_role(Event.t()) :: :participant | :not_approved
|
||||
def get_default_participant_role(%Event{join_options: :free}), do: :participant
|
||||
def get_default_participant_role(%Event{join_options: _}), do: :not_approved
|
||||
|
||||
@doc """
|
||||
Creates a participant.
|
||||
"""
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_participant(attrs \\ %{}) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
%Participant{}
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a participant.
|
||||
"""
|
||||
@spec update_participant(Participant.t(), map) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_participant(%Participant{} = participant, attrs) do
|
||||
participant
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a participant.
|
||||
"""
|
||||
@spec delete_participant(Participant.t()) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
|
||||
|
||||
@doc """
|
||||
Gets a single session.
|
||||
Raises `Ecto.NoResultsError` if the session does not exist.
|
||||
|
@ -1203,17 +1215,6 @@ defmodule Mobilizon.Events do
|
|||
)
|
||||
end
|
||||
|
||||
@spec participants_for_event(String.t()) :: Ecto.Query.t()
|
||||
defp participants_for_event(event_uuid) do
|
||||
from(
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
on: p.event_id == e.id,
|
||||
where: e.uuid == ^event_uuid,
|
||||
preload: [:actor]
|
||||
)
|
||||
end
|
||||
|
||||
defp organizers_participants_for_event(event_id) do
|
||||
from(
|
||||
p in Participant,
|
||||
|
@ -1274,6 +1275,30 @@ defmodule Mobilizon.Events do
|
|||
)
|
||||
end
|
||||
|
||||
@spec list_participants_for_event_query(String.t()) :: Ecto.Query.t()
|
||||
defp list_participants_for_event_query(event_uuid) do
|
||||
from(
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
on: p.event_id == e.id,
|
||||
where: e.uuid == ^event_uuid,
|
||||
preload: [:actor]
|
||||
)
|
||||
end
|
||||
|
||||
@spec list_participations_for_user_query(integer()) :: Ecto.Query.t()
|
||||
defp list_participations_for_user_query(user_id) do
|
||||
from(
|
||||
p in Participant,
|
||||
join: e in Event,
|
||||
join: a in Actor,
|
||||
on: p.actor_id == a.id,
|
||||
on: p.event_id == e.id,
|
||||
where: a.user_id == ^user_id and p.role != ^:not_approved,
|
||||
preload: [:event, :actor]
|
||||
)
|
||||
end
|
||||
|
||||
@spec count_comments_query(integer) :: Ecto.Query.t()
|
||||
defp count_comments_query(actor_id) do
|
||||
from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id)
|
||||
|
@ -1341,9 +1366,33 @@ defmodule Mobilizon.Events do
|
|||
from(p in query, where: p.role == ^:not_approved)
|
||||
end
|
||||
|
||||
@spec filter_role(Ecto.Query.t(), boolean) :: Ecto.Query.t()
|
||||
defp filter_role(query, false), do: filter_approved_role(query)
|
||||
defp filter_role(query, true), do: query
|
||||
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
|
||||
defp filter_role(query, []), do: query
|
||||
|
||||
defp filter_role(query, roles) do
|
||||
where(query, [p], p.role in ^roles)
|
||||
end
|
||||
|
||||
defp participation_filter_begins_on(query, nil, nil),
|
||||
do: participation_order_begins_on_desc(query)
|
||||
|
||||
defp participation_filter_begins_on(query, %DateTime{} = after_datetime, nil) do
|
||||
query
|
||||
|> where([_p, e, _a], e.begins_on > ^after_datetime)
|
||||
|> participation_order_begins_on_asc()
|
||||
end
|
||||
|
||||
defp participation_filter_begins_on(query, nil, %DateTime{} = before_datetime) do
|
||||
query
|
||||
|> where([_p, e, _a], e.begins_on < ^before_datetime)
|
||||
|> participation_order_begins_on_desc()
|
||||
end
|
||||
|
||||
defp participation_order_begins_on_asc(query),
|
||||
do: order_by(query, [_p, e, _a], asc: e.begins_on)
|
||||
|
||||
defp participation_order_begins_on_desc(query),
|
||||
do: order_by(query, [_p, e, _a], desc: e.begins_on)
|
||||
|
||||
@spec preload_for_event(Ecto.Query.t()) :: Ecto.Query.t()
|
||||
defp preload_for_event(query), do: preload(query, ^@event_preloads)
|
||||
|
|
|
@ -24,6 +24,7 @@ defmodule MobilizonWeb.API.Events do
|
|||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
category: category,
|
||||
join_options: join_options,
|
||||
options: options
|
||||
} <- prepare_args(args),
|
||||
event <-
|
||||
|
@ -39,7 +40,8 @@ defmodule MobilizonWeb.API.Events do
|
|||
ends_on: ends_on,
|
||||
physical_address: physical_address,
|
||||
category: category,
|
||||
options: options
|
||||
options: options,
|
||||
join_options: join_options
|
||||
}
|
||||
) do
|
||||
ActivityPub.create(%{
|
||||
|
@ -73,6 +75,7 @@ defmodule MobilizonWeb.API.Events do
|
|||
begins_on: begins_on,
|
||||
ends_on: ends_on,
|
||||
category: category,
|
||||
join_options: join_options,
|
||||
options: options
|
||||
} <-
|
||||
prepare_args(Map.merge(event, args)),
|
||||
|
@ -89,6 +92,7 @@ defmodule MobilizonWeb.API.Events do
|
|||
ends_on: ends_on,
|
||||
physical_address: physical_address,
|
||||
category: category,
|
||||
join_options: join_options,
|
||||
options: options
|
||||
},
|
||||
event.uuid,
|
||||
|
@ -112,7 +116,8 @@ defmodule MobilizonWeb.API.Events do
|
|||
options: options,
|
||||
tags: tags,
|
||||
begins_on: begins_on,
|
||||
category: category
|
||||
category: category,
|
||||
join_options: join_options
|
||||
} = args
|
||||
) do
|
||||
with physical_address <- Map.get(args, :physical_address, nil),
|
||||
|
@ -132,6 +137,7 @@ defmodule MobilizonWeb.API.Events do
|
|||
begins_on: begins_on,
|
||||
ends_on: Map.get(args, :ends_on, nil),
|
||||
category: category,
|
||||
join_options: join_options,
|
||||
options: options
|
||||
}
|
||||
end
|
||||
|
|
|
@ -4,9 +4,9 @@ defmodule MobilizonWeb.API.Participations do
|
|||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Service.ActivityPub
|
||||
require Logger
|
||||
|
||||
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
|
||||
def join(%Event{id: event_id} = event, %Actor{id: actor_id} = actor) do
|
||||
|
@ -21,4 +21,42 @@ defmodule MobilizonWeb.API.Participations do
|
|||
{:ok, activity, participant}
|
||||
end
|
||||
end
|
||||
|
||||
def accept(
|
||||
%Participant{} = participation,
|
||||
%Actor{} = moderator
|
||||
) do
|
||||
with {:ok, activity, _} <-
|
||||
ActivityPub.accept(
|
||||
%{
|
||||
to: [participation.actor.url],
|
||||
actor: moderator.url,
|
||||
object: participation.url
|
||||
},
|
||||
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participation.id}"
|
||||
),
|
||||
{:ok, %Participant{role: :participant} = participation} <-
|
||||
Events.update_participant(participation, %{"role" => :participant}) do
|
||||
{:ok, activity, participation}
|
||||
end
|
||||
end
|
||||
|
||||
def reject(
|
||||
%Participant{} = participation,
|
||||
%Actor{} = moderator
|
||||
) do
|
||||
with {:ok, activity, _} <-
|
||||
ActivityPub.reject(
|
||||
%{
|
||||
to: [participation.actor.url],
|
||||
actor: moderator.url,
|
||||
object: participation.url
|
||||
},
|
||||
"#{MobilizonWeb.Endpoint.url()}/reject/join/#{participation.id}"
|
||||
),
|
||||
{:ok, %Participant{} = participation} <-
|
||||
Events.delete_participant(participation) do
|
||||
{:ok, activity, participation}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,14 +42,30 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||
List participant for event (separate request)
|
||||
"""
|
||||
def list_participants_for_event(_parent, %{uuid: uuid, page: page, limit: limit}, _resolution) do
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, page, limit)}
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, [], page, limit)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
List participants for event (through an event request)
|
||||
"""
|
||||
def list_participants_for_event(%Event{uuid: uuid}, _args, _resolution) do
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)}
|
||||
def list_participants_for_event(
|
||||
%Event{uuid: uuid},
|
||||
%{page: page, limit: limit, roles: roles},
|
||||
_resolution
|
||||
) do
|
||||
roles =
|
||||
case roles do
|
||||
"" ->
|
||||
[]
|
||||
|
||||
roles ->
|
||||
roles
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.downcase/1)
|
||||
|> Enum.map(&String.to_existing_atom/1)
|
||||
end
|
||||
|
||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, roles, page, limit)}
|
||||
end
|
||||
|
||||
def stats_participants_for_event(%Event{id: id}, _args, _resolution) do
|
||||
|
@ -175,6 +191,87 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||
{:error, "You need to be logged-in to leave an event"}
|
||||
end
|
||||
|
||||
def accept_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, true, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
|
||||
# Check that participation already exists
|
||||
{:has_participation, %Participant{role: :not_approved} = 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.accept(participation, moderator_actor) do
|
||||
{:ok, participation}
|
||||
else
|
||||
{:is_owned, false} ->
|
||||
{:error, "Moderator Actor ID is not owned by authenticated user"}
|
||||
|
||||
{:has_participation, %Participant{role: role, id: id}} ->
|
||||
{:error,
|
||||
"Participant #{id} can't be approved since it's already a participant (with role #{role})"}
|
||||
|
||||
{:actor_approve_permission, _} ->
|
||||
{:error, "Provided moderator actor ID doesn't have permission on this event"}
|
||||
|
||||
{: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, true, 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, false} ->
|
||||
{: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
|
||||
"""
|
||||
|
|
|
@ -23,7 +23,8 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
field(:begins_on, :datetime, description: "Datetime for when the event begins")
|
||||
field(:ends_on, :datetime, description: "Datetime for when the event ends")
|
||||
field(:status, :event_status, description: "Status of the event")
|
||||
field(:visibility, :event_visibility, description: "The event's visibility")
|
||||
field(:visibility, :event_visibility, description: "The event's visibility")
|
||||
field(:join_options, :event_join_options, description: "The event's visibility")
|
||||
|
||||
field(:picture, :picture,
|
||||
description: "The event's picture",
|
||||
|
@ -56,10 +57,12 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
|
||||
field(:participant_stats, :participant_stats, resolve: &Event.stats_participants_for_event/3)
|
||||
|
||||
field(:participants, list_of(:participant),
|
||||
resolve: &Event.list_participants_for_event/3,
|
||||
description: "The event's participants"
|
||||
)
|
||||
field(:participants, list_of(:participant), description: "The event's participants") do
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
arg(:roles, :string, default_value: "")
|
||||
resolve(&Event.list_participants_for_event/3)
|
||||
end
|
||||
|
||||
field(:related_events, list_of(:event),
|
||||
resolve: &Event.list_related_events/3,
|
||||
|
@ -78,13 +81,18 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
enum :event_visibility do
|
||||
value(:public, description: "Publicly listed and federated. Can be shared.")
|
||||
value(:unlisted, description: "Visible only to people with the link - or invited")
|
||||
value(:restricted, description: "Visible only after a moderator accepted")
|
||||
|
||||
value(:private,
|
||||
description: "Visible only to people members of the group or followers of the person"
|
||||
)
|
||||
end
|
||||
|
||||
value(:moderated, description: "Visible only after a moderator accepted")
|
||||
value(:invite, description: "visible only to people invited")
|
||||
@desc "The list of join options for an event"
|
||||
enum :event_join_options do
|
||||
value(:free, description: "Anyone can join and is automatically accepted")
|
||||
value(:restricted, description: "Manual acceptation")
|
||||
value(:invite, description: "Participants must be invited")
|
||||
end
|
||||
|
||||
@desc "The list of possible options for the event's status"
|
||||
|
@ -218,6 +226,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
arg(:ends_on, :datetime)
|
||||
arg(:status, :event_status)
|
||||
arg(:visibility, :event_visibility, default_value: :private)
|
||||
arg(:join_options, :event_join_options, default_value: :free)
|
||||
|
||||
arg(:tags, list_of(:string),
|
||||
default_value: [],
|
||||
|
@ -250,6 +259,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||
arg(:ends_on, :datetime)
|
||||
arg(:status, :event_status)
|
||||
arg(:visibility, :event_visibility)
|
||||
arg(:join_options, :event_join_options)
|
||||
|
||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
|||
|
||||
@desc "Represents a participant to an event"
|
||||
object :participant do
|
||||
field(:id, :id, description: "The participation ID")
|
||||
|
||||
field(
|
||||
:event,
|
||||
:event,
|
||||
|
@ -24,11 +26,20 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
|||
description: "The actor that participates to the event"
|
||||
)
|
||||
|
||||
field(:role, :integer, description: "The role of this actor at this event")
|
||||
field(:role, :participant_role_enum, description: "The role of this actor at this event")
|
||||
end
|
||||
|
||||
enum :participant_role_enum do
|
||||
value(:not_approved)
|
||||
value(:participant)
|
||||
value(:moderator)
|
||||
value(:administrator)
|
||||
value(:creator)
|
||||
end
|
||||
|
||||
@desc "Represents a deleted participant"
|
||||
object :deleted_participant do
|
||||
field(:id, :id)
|
||||
field(:event, :deleted_object)
|
||||
field(:actor, :deleted_object)
|
||||
end
|
||||
|
@ -59,5 +70,21 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
|||
|
||||
resolve(&Resolvers.Event.actor_leave_event/3)
|
||||
end
|
||||
|
||||
@desc "Accept a participation"
|
||||
field :accept_participation, :participant do
|
||||
arg(:id, non_null(:id))
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,6 +59,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
|
|||
"begins_on" => object["startTime"],
|
||||
"ends_on" => object["endTime"],
|
||||
"category" => object["category"],
|
||||
"join_options" => object["joinOptions"],
|
||||
"url" => object["id"],
|
||||
"uuid" => object["uuid"],
|
||||
"tags" => tags,
|
||||
|
|
|
@ -328,6 +328,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
|||
"category" => metadata.category,
|
||||
"actor" => actor,
|
||||
"id" => url || Routes.page_url(Endpoint, :event, uuid),
|
||||
"joinOptions" => metadata.join_options,
|
||||
"uuid" => uuid,
|
||||
"tag" =>
|
||||
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# source: http://localhost:4000/api
|
||||
# timestamp: Wed Sep 18 2019 17:12:13 GMT+0200 (GMT+02:00)
|
||||
# timestamp: Fri Sep 20 2019 16:55:10 GMT+0200 (GMT+02:00)
|
||||
|
||||
schema {
|
||||
query: RootQueryType
|
||||
|
@ -244,6 +244,7 @@ type DeletedObject {
|
|||
type DeletedParticipant {
|
||||
actor: DeletedObject
|
||||
event: DeletedObject
|
||||
id: ID
|
||||
}
|
||||
|
||||
"""An event"""
|
||||
|
@ -269,6 +270,9 @@ type Event implements ActionLogObject {
|
|||
"""Internal ID for this event"""
|
||||
id: ID
|
||||
|
||||
"""The event's visibility"""
|
||||
joinOptions: EventJoinOptions
|
||||
|
||||
"""Whether the event is local or not"""
|
||||
local: Boolean
|
||||
|
||||
|
@ -283,7 +287,7 @@ type Event implements ActionLogObject {
|
|||
participantStats: ParticipantStats
|
||||
|
||||
"""The event's participants"""
|
||||
participants: [Participant]
|
||||
participants(limit: Int = 10, page: Int = 1, roles: String = ""): [Participant]
|
||||
|
||||
"""Phone address for the event"""
|
||||
phoneAddress: String
|
||||
|
@ -321,7 +325,7 @@ type Event implements ActionLogObject {
|
|||
"""The Event UUID"""
|
||||
uuid: UUID
|
||||
|
||||
"""The event's visibility"""
|
||||
"""The event's visibility"""
|
||||
visibility: EventVisibility
|
||||
}
|
||||
|
||||
|
@ -337,6 +341,18 @@ enum EventCommentModeration {
|
|||
MODERATED
|
||||
}
|
||||
|
||||
"""The list of join options for an event"""
|
||||
enum EventJoinOptions {
|
||||
"""Anyone can join and is automatically accepted"""
|
||||
FREE
|
||||
|
||||
"""Participants must be invited"""
|
||||
INVITE
|
||||
|
||||
"""Manual acceptation"""
|
||||
RESTRICTED
|
||||
}
|
||||
|
||||
type EventOffer {
|
||||
"""The price amount for this offer"""
|
||||
price: Float
|
||||
|
@ -462,18 +478,15 @@ enum EventStatus {
|
|||
|
||||
"""The list of visibility options for an event"""
|
||||
enum EventVisibility {
|
||||
"""visible only to people invited"""
|
||||
INVITE
|
||||
|
||||
"""Visible only after a moderator accepted"""
|
||||
MODERATED
|
||||
|
||||
"""Visible only to people members of the group or followers of the person"""
|
||||
PRIVATE
|
||||
|
||||
"""Publicly listed and federated. Can be shared."""
|
||||
PUBLIC
|
||||
|
||||
"""Visible only after a moderator accepted"""
|
||||
RESTRICTED
|
||||
|
||||
"""Visible only to people with the link - or invited"""
|
||||
UNLISTED
|
||||
}
|
||||
|
@ -645,8 +658,19 @@ type Participant {
|
|||
"""The event which the actor participates in"""
|
||||
event: Event
|
||||
|
||||
"""The participation ID"""
|
||||
id: ID
|
||||
|
||||
"""The role of this actor at this event"""
|
||||
role: Int
|
||||
role: ParticipantRoleEnum
|
||||
}
|
||||
|
||||
enum ParticipantRoleEnum {
|
||||
ADMINISTRATOR
|
||||
CREATOR
|
||||
MODERATOR
|
||||
NOT_APPROVED
|
||||
PARTICIPANT
|
||||
}
|
||||
|
||||
type ParticipantStats {
|
||||
|
@ -855,6 +879,9 @@ enum ReportStatus {
|
|||
}
|
||||
|
||||
type RootMutationType {
|
||||
"""Accept a participation"""
|
||||
acceptParticipation(id: ID!, moderatorActorId: ID!): Participant
|
||||
|
||||
"""Change default actor for user"""
|
||||
changeDefaultActor(preferredUsername: String!): User
|
||||
|
||||
|
@ -867,6 +894,7 @@ type RootMutationType {
|
|||
category: String = "meeting"
|
||||
description: String!
|
||||
endsOn: DateTime
|
||||
joinOptions: EventJoinOptions = FREE
|
||||
onlineAddress: String
|
||||
options: EventOptionsInput
|
||||
organizerActorId: ID!
|
||||
|
@ -997,6 +1025,9 @@ type RootMutationType {
|
|||
summary: String = ""
|
||||
): Person
|
||||
|
||||
"""Reject a participation"""
|
||||
rejectParticipation(id: ID!, moderatorActorId: ID!): DeletedParticipant
|
||||
|
||||
"""Resend registration confirmation token"""
|
||||
resendConfirmationEmail(email: String!, locale: String = "en"): String
|
||||
|
||||
|
@ -1013,6 +1044,7 @@ type RootMutationType {
|
|||
description: String
|
||||
endsOn: DateTime
|
||||
eventId: ID!
|
||||
joinOptions: EventJoinOptions
|
||||
onlineAddress: String
|
||||
options: EventOptionsInput
|
||||
phoneAddress: String
|
||||
|
|
|
@ -316,13 +316,6 @@ defmodule Mobilizon.EventsTest do
|
|||
{:ok, participant: participant, event: event, actor: actor}
|
||||
end
|
||||
|
||||
test "list_participants/0 returns all participants", %{
|
||||
participant: %Participant{event_id: participant_event_id, actor_id: participant_actor_id}
|
||||
} do
|
||||
assert [participant_event_id] == Events.list_participants() |> Enum.map(& &1.event_id)
|
||||
assert [participant_actor_id] == Events.list_participants() |> Enum.map(& &1.actor_id)
|
||||
end
|
||||
|
||||
test "get_participant!/1 returns the participant for a given event and given actor", %{
|
||||
event: %Event{id: event_id},
|
||||
actor: %Actor{id: actor_id}
|
||||
|
|
|
@ -784,7 +784,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||
assert :error == Transmogrifier.handle_incoming(reject_data)
|
||||
|
||||
# Organiser is not present since we use factories directly
|
||||
assert Events.list_participants_for_event(event.uuid, 1, 10, true) |> Enum.map(& &1.id) ==
|
||||
assert Events.list_participants_for_event(event.uuid) |> Enum.map(& &1.id) ==
|
||||
[]
|
||||
end
|
||||
|
||||
|
@ -812,7 +812,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||
assert activity.data["actor"] == participant_url
|
||||
|
||||
# The only participant left is the organizer
|
||||
assert Events.list_participants_for_event(event.uuid, 1, 10, true) |> Enum.map(& &1.id) == [
|
||||
assert Events.list_participants_for_event(event.uuid) |> Enum.map(& &1.id) == [
|
||||
organizer_participation.id
|
||||
]
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "participant"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "PARTICIPANT"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||
|
||||
|
@ -161,10 +161,12 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
|
||||
query = """
|
||||
{
|
||||
participants(uuid: "#{event.uuid}") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,12 +176,12 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
assert json_response(res, 200)["data"]["participants"] == [
|
||||
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => participant2.actor.preferred_username
|
||||
},
|
||||
"role" => "creator"
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
@ -331,10 +333,12 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
|
||||
query = """
|
||||
{
|
||||
participants(uuid: "#{event.uuid}") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(roles: "participant,moderator,administrator,creator") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,12 +348,12 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
context.conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
assert json_response(res, 200)["data"]["participants"] == [
|
||||
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => context.actor.preferred_username
|
||||
},
|
||||
"role" => "creator"
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -361,12 +365,59 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
# This one will (as a participant)
|
||||
participant2 = insert(:participant, event: event, actor: actor3, role: :participant)
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(page: 1, limit: 1, roles: "participant,moderator,administrator,creator") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
sorted_participants =
|
||||
json_response(res, 200)["data"]["participants"]
|
||||
json_response(res, 200)["data"]["event"]["participants"]
|
||||
|> Enum.sort_by(
|
||||
&(&1
|
||||
|> Map.get("actor")
|
||||
|> Map.get("preferredUsername"))
|
||||
)
|
||||
|
||||
assert sorted_participants == [
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => participant2.actor.preferred_username
|
||||
},
|
||||
"role" => "PARTICIPANT"
|
||||
}
|
||||
]
|
||||
|
||||
query = """
|
||||
{
|
||||
event(uuid: "#{event.uuid}") {
|
||||
participants(page: 2, limit: 1, roles: "participant,moderator,administrator,creator") {
|
||||
role,
|
||||
actor {
|
||||
preferredUsername
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
context.conn
|
||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||
|
||||
sorted_participants =
|
||||
json_response(res, 200)["data"]["event"]["participants"]
|
||||
|> Enum.sort_by(
|
||||
&(&1
|
||||
|> Map.get("actor")
|
||||
|
@ -378,13 +429,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
"actor" => %{
|
||||
"preferredUsername" => context.actor.preferred_username
|
||||
},
|
||||
"role" => "creator"
|
||||
},
|
||||
%{
|
||||
"actor" => %{
|
||||
"preferredUsername" => participant2.actor.preferred_username
|
||||
},
|
||||
"role" => "participant"
|
||||
"role" => "CREATOR"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
@ -456,4 +501,281 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||
assert json_response(res, 200)["data"]["event"]["participantStats"]["unapproved"] == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "Participant approval" do
|
||||
test "accept_participation/3", %{conn: conn, actor: actor, user: user} do
|
||||
user_creator = insert(:user)
|
||||
actor_creator = insert(:actor, user: user_creator)
|
||||
event = insert(:event, join_options: :restricted, organizer_actor: actor_creator)
|
||||
insert(:participant, event: event, actor: actor_creator, role: :creator)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
joinEvent(
|
||||
actor_id: #{actor.id},
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "NOT_APPROVED"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||
participation_id = json_response(res, 200)["data"]["joinEvent"]["id"]
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
acceptParticipation(
|
||||
id: "#{participation_id}",
|
||||
moderator_actor_id: #{actor_creator.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["acceptParticipation"]["role"] == "PARTICIPANT"
|
||||
|
||||
assert json_response(res, 200)["data"]["acceptParticipation"]["event"]["id"] ==
|
||||
to_string(event.id)
|
||||
|
||||
assert json_response(res, 200)["data"]["acceptParticipation"]["actor"]["id"] ==
|
||||
to_string(actor.id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] =~
|
||||
" can't be approved since it's already a participant (with role participant)"
|
||||
end
|
||||
|
||||
test "accept_participation/3 with bad parameters", %{conn: conn, actor: actor, user: user} do
|
||||
user_creator = insert(:user)
|
||||
actor_creator = insert(:actor, user: user_creator)
|
||||
event = insert(:event, join_options: :restricted)
|
||||
insert(:participant, event: event, role: :creator)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
joinEvent(
|
||||
actor_id: #{actor.id},
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "NOT_APPROVED"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||
participation_id = json_response(res, 200)["data"]["joinEvent"]["id"]
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
acceptParticipation(
|
||||
id: "#{participation_id}",
|
||||
moderator_actor_id: #{actor_creator.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
"Provided moderator actor ID doesn't have permission on this event"
|
||||
end
|
||||
end
|
||||
|
||||
describe "reject participation" do
|
||||
test "reject_participation/3", %{conn: conn, actor: actor, user: user} do
|
||||
user_creator = insert(:user)
|
||||
actor_creator = insert(:actor, user: user_creator)
|
||||
event = insert(:event, join_options: :restricted, organizer_actor: actor_creator)
|
||||
insert(:participant, event: event, actor: actor_creator, role: :creator)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
joinEvent(
|
||||
actor_id: #{actor.id},
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "NOT_APPROVED"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||
participation_id = json_response(res, 200)["data"]["joinEvent"]["id"]
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
rejectParticipation(
|
||||
id: "#{participation_id}",
|
||||
moderator_actor_id: #{actor_creator.id}
|
||||
) {
|
||||
id,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["rejectParticipation"]["id"] == participation_id
|
||||
|
||||
assert json_response(res, 200)["data"]["rejectParticipation"]["event"]["id"] ==
|
||||
to_string(event.id)
|
||||
|
||||
assert json_response(res, 200)["data"]["rejectParticipation"]["actor"]["id"] ==
|
||||
to_string(actor.id)
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] == "Participant not found"
|
||||
end
|
||||
|
||||
test "reject_participation/3 with bad parameters", %{conn: conn, actor: actor, user: user} do
|
||||
user_creator = insert(:user)
|
||||
actor_creator = insert(:actor, user: user_creator)
|
||||
event = insert(:event, join_options: :restricted)
|
||||
insert(:participant, event: event, role: :creator)
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
joinEvent(
|
||||
actor_id: #{actor.id},
|
||||
event_id: #{event.id}
|
||||
) {
|
||||
id,
|
||||
role,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert json_response(res, 200)["errors"] == nil
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "NOT_APPROVED"
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||
participation_id = json_response(res, 200)["data"]["joinEvent"]["id"]
|
||||
|
||||
mutation = """
|
||||
mutation {
|
||||
rejectParticipation(
|
||||
id: "#{participation_id}",
|
||||
moderator_actor_id: #{actor_creator.id}
|
||||
) {
|
||||
id,
|
||||
actor {
|
||||
id
|
||||
},
|
||||
event {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
res =
|
||||
conn
|
||||
|> auth_conn(user_creator)
|
||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||
|
||||
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||
"Provided moderator actor ID doesn't have permission on this event"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue