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/dropdown.sass";
|
||||||
@import "~bulma/sass/components/breadcrumb.sass";
|
@import "~bulma/sass/components/breadcrumb.sass";
|
||||||
@import "~bulma/sass/components/list.sass";
|
@import "~bulma/sass/components/list.sass";
|
||||||
|
@import "~bulma/sass/components/tabs";
|
||||||
@import "~bulma/sass/elements/box.sass";
|
@import "~bulma/sass/elements/box.sass";
|
||||||
@import "~bulma/sass/elements/button.sass";
|
@import "~bulma/sass/elements/button.sass";
|
||||||
@import "~bulma/sass/elements/container.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/radio";
|
||||||
@import "~buefy/src/scss/components/switch";
|
@import "~buefy/src/scss/components/switch";
|
||||||
@import "~buefy/src/scss/components/table";
|
@import "~buefy/src/scss/components/table";
|
||||||
|
@import "~buefy/src/scss/components/tabs";
|
||||||
|
|
||||||
.router-enter-active,
|
.router-enter-active,
|
||||||
.router-leave-active {
|
.router-leave-active {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<router-link v-if="actor.domain === null"
|
<span v-if="actor.domain === null"
|
||||||
:to="{name: 'Profile', params: { name: actor.preferredUsername } }"
|
:to="{name: 'Profile', params: { name: actor.preferredUsername } }"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</router-link>
|
</span>
|
||||||
<a v-else :href="actor.url">
|
<a v-else :href="actor.url">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</a>
|
</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({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
DateCalendarIcon,
|
DateCalendarIcon,
|
||||||
EventCard,
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
lineClamp(this.$refs.title, 3);
|
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.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-if="participation.actor.id === participation.event.organizerActor.id">{{ $t("You're organizing this event") }}</span>
|
||||||
<span v-else>
|
<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>{{ $t('Going as {name}', { name: participation.actor.displayName() }) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,9 +51,9 @@
|
||||||
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
<a @click="openDeleteEventModalWrapper"><b-icon icon="delete" /> {{ $t('Delete') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!([ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(participation.role))">
|
<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') }}
|
<b-icon icon="account-multiple-plus" /> {{ $t('Manage participations') }}
|
||||||
</a>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link :to="{ name: EventRouteName.EVENT, params: { uuid: participation.event.uuid } }"><b-icon icon="view-compact" /> {{ $t('View event page') }}</router-link>
|
<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>
|
<template>
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<header class="modal-card-head">
|
<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>
|
</header>
|
||||||
|
|
||||||
<section class="modal-card-body is-flex">
|
<section class="modal-card-body is-flex">
|
||||||
|
@ -14,14 +14,18 @@
|
||||||
size="is-large"/>
|
size="is-large"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<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')">
|
<b-field :label="$t('Identity')">
|
||||||
<identity-picker v-model="identity"></identity-picker>
|
<identity-picker v-model="identity"></identity-picker>
|
||||||
</b-field>
|
</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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,13 +36,13 @@
|
||||||
class="button"
|
class="button"
|
||||||
ref="cancelButton"
|
ref="cancelButton"
|
||||||
@click="close">
|
@click="close">
|
||||||
Cancel
|
{{ $t('Cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
ref="confirmButton"
|
ref="confirmButton"
|
||||||
@click="confirm">
|
@click="confirm">
|
||||||
Confirm my particpation
|
{{ $t('Confirm my particpation') }}
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +50,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
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 IdentityPicker from '@/views/Account/IdentityPicker.vue';
|
||||||
import { IPerson } from '@/types/actor';
|
import { IPerson } from '@/types/actor';
|
||||||
|
|
||||||
|
@ -66,6 +70,8 @@ export default class ReportModal extends Vue {
|
||||||
isActive: boolean = false;
|
isActive: boolean = false;
|
||||||
identity: IPerson = this.defaultIdentity;
|
identity: IPerson = this.defaultIdentity;
|
||||||
|
|
||||||
|
EventJoinOptions = EventJoinOptions;
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
this.onConfirm(this.identity);
|
this.onConfirm(this.identity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,24 +16,23 @@
|
||||||
size="is-large"/>
|
size="is-large"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
<p>The report will be sent to the moderators of your instance.
|
<p>{{ $t('The report will be sent to the moderators of your instance. You can explain why you report this content below.') }}</p>
|
||||||
You can explain why you report this content below.</p>
|
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<b-input
|
<b-input
|
||||||
v-model="content"
|
v-model="content"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@keyup.enter="confirm"
|
@keyup.enter="confirm"
|
||||||
placeholder="Additional comments"
|
:placeholder="$t('Additional comments')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="outsideDomain">
|
<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>
|
</p>
|
||||||
|
|
||||||
<div class="control" v-if="outsideDomain">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,13 +43,13 @@
|
||||||
class="button"
|
class="button"
|
||||||
ref="cancelButton"
|
ref="cancelButton"
|
||||||
@click="close">
|
@click="close">
|
||||||
{{ cancelText }}
|
{{ translatedCancelText }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
ref="confirmButton"
|
ref="confirmButton"
|
||||||
@click="confirm">
|
@click="confirm">
|
||||||
{{ confirmText }}
|
{{ translatedConfirmText }}
|
||||||
</button>
|
</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,13 +68,21 @@ export default class ReportModal extends Vue {
|
||||||
@Prop({ type: Function, default: () => {} }) onConfirm;
|
@Prop({ type: Function, default: () => {} }) onConfirm;
|
||||||
@Prop({ type: String }) title;
|
@Prop({ type: String }) title;
|
||||||
@Prop({ type: String, default: '' }) outsideDomain;
|
@Prop({ type: String, default: '' }) outsideDomain;
|
||||||
@Prop({ type: String, default: 'Cancel' }) cancelText;
|
@Prop({ type: String }) cancelText;
|
||||||
@Prop({ type: String, default: 'Send the report' }) confirmText;
|
@Prop({ type: String }) confirmText;
|
||||||
|
|
||||||
isActive: boolean = false;
|
isActive: boolean = false;
|
||||||
content: string = '';
|
content: string = '';
|
||||||
forward: boolean = false;
|
forward: boolean = false;
|
||||||
|
|
||||||
|
get translatedCancelText() {
|
||||||
|
return this.cancelText || this.$t('Cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
get translatedConfirmText() {
|
||||||
|
return this.confirmText || this.$t('Send the report');
|
||||||
|
}
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
this.onConfirm(this.content, this.forward);
|
this.onConfirm(this.content, this.forward);
|
||||||
this.close();
|
this.close();
|
||||||
|
|
|
@ -91,6 +91,7 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
||||||
remainingAttendeeCapacity
|
remainingAttendeeCapacity
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
id,
|
||||||
role,
|
role,
|
||||||
actor {
|
actor {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import gql from 'graphql-tag';
|
||||||
|
|
||||||
const participantQuery = `
|
const participantQuery = `
|
||||||
role,
|
role,
|
||||||
|
id,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername,
|
preferredUsername,
|
||||||
avatar {
|
avatar {
|
||||||
|
@ -50,7 +51,7 @@ const optionsQuery = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FETCH_EVENT = gql`
|
export const FETCH_EVENT = gql`
|
||||||
query($uuid:UUID!) {
|
query($uuid:UUID!, $roles: String) {
|
||||||
event(uuid: $uuid) {
|
event(uuid: $uuid) {
|
||||||
id,
|
id,
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -63,6 +64,7 @@ export const FETCH_EVENT = gql`
|
||||||
endsOn,
|
endsOn,
|
||||||
status,
|
status,
|
||||||
visibility,
|
visibility,
|
||||||
|
joinOptions,
|
||||||
picture {
|
picture {
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
|
@ -92,7 +94,7 @@ export const FETCH_EVENT = gql`
|
||||||
# preferredUsername,
|
# preferredUsername,
|
||||||
# name,
|
# name,
|
||||||
# },
|
# },
|
||||||
participants {
|
participants (roles: $roles) {
|
||||||
${participantQuery}
|
${participantQuery}
|
||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
|
@ -183,7 +185,8 @@ export const CREATE_EVENT = gql`
|
||||||
$beginsOn: DateTime!,
|
$beginsOn: DateTime!,
|
||||||
$endsOn: DateTime,
|
$endsOn: DateTime,
|
||||||
$status: EventStatus,
|
$status: EventStatus,
|
||||||
$visibility: EventVisibility
|
$visibility: EventVisibility,
|
||||||
|
$joinOptions: EventJoinOptions,
|
||||||
$tags: [String],
|
$tags: [String],
|
||||||
$picture: PictureInput,
|
$picture: PictureInput,
|
||||||
$onlineAddress: String,
|
$onlineAddress: String,
|
||||||
|
@ -200,6 +203,7 @@ export const CREATE_EVENT = gql`
|
||||||
endsOn: $endsOn,
|
endsOn: $endsOn,
|
||||||
status: $status,
|
status: $status,
|
||||||
visibility: $visibility,
|
visibility: $visibility,
|
||||||
|
joinOptions: $joinOptions,
|
||||||
tags: $tags,
|
tags: $tags,
|
||||||
picture: $picture,
|
picture: $picture,
|
||||||
onlineAddress: $onlineAddress,
|
onlineAddress: $onlineAddress,
|
||||||
|
@ -216,6 +220,7 @@ export const CREATE_EVENT = gql`
|
||||||
endsOn,
|
endsOn,
|
||||||
status,
|
status,
|
||||||
visibility,
|
visibility,
|
||||||
|
joinOptions,
|
||||||
picture {
|
picture {
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
|
@ -245,7 +250,8 @@ export const EDIT_EVENT = gql`
|
||||||
$beginsOn: DateTime,
|
$beginsOn: DateTime,
|
||||||
$endsOn: DateTime,
|
$endsOn: DateTime,
|
||||||
$status: EventStatus,
|
$status: EventStatus,
|
||||||
$visibility: EventVisibility
|
$visibility: EventVisibility,
|
||||||
|
$joinOptions: EventJoinOptions,
|
||||||
$tags: [String],
|
$tags: [String],
|
||||||
$picture: PictureInput,
|
$picture: PictureInput,
|
||||||
$onlineAddress: String,
|
$onlineAddress: String,
|
||||||
|
@ -262,6 +268,7 @@ export const EDIT_EVENT = gql`
|
||||||
endsOn: $endsOn,
|
endsOn: $endsOn,
|
||||||
status: $status,
|
status: $status,
|
||||||
visibility: $visibility,
|
visibility: $visibility,
|
||||||
|
joinOptions: $joinOptions,
|
||||||
tags: $tags,
|
tags: $tags,
|
||||||
picture: $picture,
|
picture: $picture,
|
||||||
onlineAddress: $onlineAddress,
|
onlineAddress: $onlineAddress,
|
||||||
|
@ -278,6 +285,7 @@ export const EDIT_EVENT = gql`
|
||||||
endsOn,
|
endsOn,
|
||||||
status,
|
status,
|
||||||
visibility,
|
visibility,
|
||||||
|
joinOptions,
|
||||||
picture {
|
picture {
|
||||||
id
|
id
|
||||||
url
|
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`
|
export const DELETE_EVENT = gql`
|
||||||
mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
|
mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
|
||||||
deleteEvent(
|
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",
|
"Load more": "Load more",
|
||||||
"Past events": "Passed events",
|
"Past events": "Passed events",
|
||||||
"View everything": "View everything",
|
"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 an account on Mobilizon!": "S'inscrire sur Mobilizon !",
|
||||||
"Register": "S'inscrire",
|
"Register": "S'inscrire",
|
||||||
"Registration is currently closed.": "Les inscriptions sont actuellement fermées.",
|
"Registration is currently closed.": "Les inscriptions sont actuellement fermées.",
|
||||||
"Report": "Report",
|
"Report": "Signaler",
|
||||||
"Resend confirmation email": "Envoyer à nouveau l'email de confirmation",
|
"Resend confirmation email": "Envoyer à nouveau l'email de confirmation",
|
||||||
"Reset my password": "Réinitialiser mon mot de passe",
|
"Reset my password": "Réinitialiser mon mot de passe",
|
||||||
"Save": "Enregistrer",
|
"Save": "Enregistrer",
|
||||||
|
@ -211,5 +211,28 @@
|
||||||
"Load more": "Voir plus",
|
"Load more": "Voir plus",
|
||||||
"Past events": "Événements passés",
|
"Past events": "Événements passés",
|
||||||
"View everything": "Voir tout",
|
"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';
|
import { RouteConfig } from 'vue-router';
|
||||||
|
|
||||||
// tslint:disable:space-in-parens
|
// 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 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
|
// tslint:enable
|
||||||
|
|
||||||
export enum EventRouteName {
|
export enum EventRouteName {
|
||||||
|
@ -13,6 +14,7 @@ export enum EventRouteName {
|
||||||
CREATE_EVENT = 'CreateEvent',
|
CREATE_EVENT = 'CreateEvent',
|
||||||
MY_EVENTS = 'MyEvents',
|
MY_EVENTS = 'MyEvents',
|
||||||
EDIT_EVENT = 'EditEvent',
|
EDIT_EVENT = 'EditEvent',
|
||||||
|
PARTICIPATIONS = 'Participations',
|
||||||
EVENT = 'Event',
|
EVENT = 'Event',
|
||||||
LOCATION = 'Location',
|
LOCATION = 'Location',
|
||||||
}
|
}
|
||||||
|
@ -43,6 +45,13 @@ export const eventRoutes: RouteConfig[] = [
|
||||||
meta: { requiredAuth: true },
|
meta: { requiredAuth: true },
|
||||||
props: { isUpdate: true },
|
props: { isUpdate: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/events/participations/:eventId',
|
||||||
|
name: EventRouteName.PARTICIPATIONS,
|
||||||
|
component: participations,
|
||||||
|
meta: { requiredAuth: true },
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/location/new',
|
path: '/location/new',
|
||||||
name: EventRouteName.LOCATION,
|
name: EventRouteName.LOCATION,
|
||||||
|
|
|
@ -29,11 +29,11 @@ export enum EventVisibilityJoinOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ParticipantRole {
|
export enum ParticipantRole {
|
||||||
NOT_APPROVED = 'not_approved',
|
NOT_APPROVED = 'NOT_APPROVED',
|
||||||
PARTICIPANT = 'participant',
|
PARTICIPANT = 'PARTICIPANT',
|
||||||
MODERATOR = 'moderator',
|
MODERATOR = 'MODERATOR',
|
||||||
ADMINISTRATOR = 'administrator',
|
ADMINISTRATOR = 'ADMINISTRATOR',
|
||||||
CREATOR = 'creator',
|
CREATOR = 'CREATOR',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Category {
|
export enum Category {
|
||||||
|
@ -45,12 +45,14 @@ export enum Category {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IParticipant {
|
export interface IParticipant {
|
||||||
|
id?: string;
|
||||||
role: ParticipantRole;
|
role: ParticipantRole;
|
||||||
actor: IActor;
|
actor: IActor;
|
||||||
event: IEvent;
|
event: IEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Participant implements IParticipant {
|
export class Participant implements IParticipant {
|
||||||
|
id?: string;
|
||||||
event!: IEvent;
|
event!: IEvent;
|
||||||
actor!: IActor;
|
actor!: IActor;
|
||||||
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
|
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
|
||||||
|
@ -58,6 +60,7 @@ export class Participant implements IParticipant {
|
||||||
constructor(hash?: IParticipant) {
|
constructor(hash?: IParticipant) {
|
||||||
if (!hash) return;
|
if (!hash) return;
|
||||||
|
|
||||||
|
this.id = hash.id;
|
||||||
this.event = new EventModel(hash.event);
|
this.event = new EventModel(hash.event);
|
||||||
this.actor = new Actor(hash.actor);
|
this.actor = new Actor(hash.actor);
|
||||||
this.role = hash.role;
|
this.role = hash.role;
|
||||||
|
@ -83,7 +86,7 @@ export enum CommentModeration {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEvent {
|
export interface IEvent {
|
||||||
id?: number;
|
id?: string;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
url: string;
|
url: string;
|
||||||
local: boolean;
|
local: boolean;
|
||||||
|
@ -147,7 +150,7 @@ export class EventOptions implements IEventOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventModel implements IEvent {
|
export class EventModel implements IEvent {
|
||||||
id?: number;
|
id?: string;
|
||||||
|
|
||||||
beginsOn = new Date();
|
beginsOn = new Date();
|
||||||
endsOn: Date | null = new Date();
|
endsOn: Date | null = new Date();
|
||||||
|
@ -232,6 +235,7 @@ export class EventModel implements IEvent {
|
||||||
endsOn: this.endsOn ? this.endsOn.toISOString() : null,
|
endsOn: this.endsOn ? this.endsOn.toISOString() : null,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
visibility: this.visibility,
|
visibility: this.visibility,
|
||||||
|
joinOptions: this.joinOptions,
|
||||||
tags: this.tags.map(t => t.title),
|
tags: this.tags.map(t => t.title),
|
||||||
picture: this.picture,
|
picture: this.picture,
|
||||||
onlineAddress: this.onlineAddress,
|
onlineAddress: this.onlineAddress,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {EventJoinOptions} from "@/types/event.model";
|
||||||
<template>
|
<template>
|
||||||
<section class="container">
|
<section class="container">
|
||||||
<h1 class="title" v-if="isUpdate === false">
|
<h1 class="title" v-if="isUpdate === false">
|
||||||
|
@ -187,11 +188,17 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { 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 { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
||||||
import { IPerson, Person } from '@/types/actor';
|
import { Person } from '@/types/actor';
|
||||||
import PictureUpload from '@/components/PictureUpload.vue';
|
import PictureUpload from '@/components/PictureUpload.vue';
|
||||||
import Editor from '@/components/Editor.vue';
|
import Editor from '@/components/Editor.vue';
|
||||||
import DateTimePicker from '@/components/Event/DateTimePicker.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) {
|
// getAddressData(addressData) {
|
||||||
// if (addressData !== null) {
|
// if (addressData !== null) {
|
||||||
// this.event.address = {
|
// this.event.address = {
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</div>
|
</div>
|
||||||
<div class="organizer">
|
<div class="organizer">
|
||||||
<actor-link :actor="event.organizerActor">
|
<span>
|
||||||
<span v-if="event.organizerActor">
|
<span v-if="event.organizerActor">
|
||||||
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
{{ $t('By {name}', {name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}) }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -113,31 +113,11 @@
|
||||||
:src="event.organizerActor.avatar.url"
|
:src="event.organizerActor.avatar.url"
|
||||||
:alt="event.organizerActor.avatar.alt" />
|
:alt="event.organizerActor.avatar.alt" />
|
||||||
</figure>
|
</figure>
|
||||||
</actor-link>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
||||||
<div class="description-container container">
|
<div class="description-container container">
|
||||||
<h3 class="title">
|
<h3 class="title">
|
||||||
|
@ -147,63 +127,31 @@
|
||||||
{{ $t("The event organizer didn't add any description.") }}
|
{{ $t("The event organizer didn't add any description.") }}
|
||||||
</p>
|
</p>
|
||||||
<div class="columns" v-else>
|
<div class="columns" v-else>
|
||||||
<div class="column is-half">
|
<div class="column is-half" v-html="event.description">
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <section class="container">-->
|
<section class="container">
|
||||||
<!-- <h2 class="title">Participants</h2>-->
|
<h3 class="title">{{ $t('Participants') }}</h3>
|
||||||
<!-- <span v-if="event.participants.length === 0">No participants yet.</span>-->
|
<router-link v-if="currentActor.id === event.organizerActor.id" :to="{ name: EventRouteName.PARTICIPATIONS, params: { eventId: event.uuid } }">
|
||||||
<!-- <div class="columns">-->
|
{{ $t('Manage participants') }}
|
||||||
<!-- <router-link-->
|
</router-link>
|
||||||
<!-- class="column"-->
|
<span v-if="event.participants.length === 0">{{ $t('No participants yet.') }}</span>
|
||||||
<!-- v-for="participant in event.participants"-->
|
<div class="columns">
|
||||||
<!-- :key="participant.preferredUsername"-->
|
<div
|
||||||
<!-- :to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"-->
|
class="column"
|
||||||
<!-- >-->
|
v-for="participant in event.participants"
|
||||||
<!-- <div>-->
|
:key="participant.id"
|
||||||
<!-- <figure>-->
|
>
|
||||||
<!-- <img v-if="!participant.actor.avatar.url" src="https://picsum.photos/125/125/">-->
|
<figure class="image is-48x48">
|
||||||
<!-- <img v-else :src="participant.actor.avatar.url">-->
|
<img v-if="!participant.actor.avatar.url" src="https://picsum.photos/48/48/" class="is-rounded">
|
||||||
<!-- </figure>-->
|
<img v-else :src="participant.actor.avatar.url" class="is-rounded">
|
||||||
<!-- <span>{{ participant.actor.preferredUsername }}</span>-->
|
</figure>
|
||||||
<!-- </div>-->
|
<span>{{ participant.actor.preferredUsername }}</span>
|
||||||
<!-- </router-link>-->
|
</div>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
<!-- </section>-->
|
</section>
|
||||||
<section class="share">
|
<section class="share">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
@ -236,7 +184,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<b-modal :active.sync="isReportModalActive" has-modal-card ref="reportModal">
|
<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>
|
||||||
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
|
<b-modal :active.sync="isJoinModalActive" has-modal-card ref="participationModal">
|
||||||
<participation-modal :on-confirm="joinEvent" :event="event" :defaultIdentity="currentActor" @close="$refs.participationModal.close()" />
|
<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 { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
|
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 { IPerson } from '@/types/actor';
|
||||||
import { RouteName } from '@/router';
|
import { RouteName } from '@/router';
|
||||||
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
|
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 { IReport } from '@/types/report.model';
|
||||||
import { CREATE_REPORT } from '@/graphql/report';
|
import { CREATE_REPORT } from '@/graphql/report';
|
||||||
import EventMixin from '@/mixins/event';
|
import EventMixin from '@/mixins/event';
|
||||||
|
import { EventRouteName } from '@/router/event';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
@ -283,6 +232,7 @@ import EventMixin from '@/mixins/event';
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
uuid: this.uuid,
|
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;
|
isJoinModalActive: boolean = false;
|
||||||
|
|
||||||
EventVisibility = EventVisibility;
|
EventVisibility = EventVisibility;
|
||||||
|
EventRouteName = EventRouteName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the event, then redirect to home.
|
* Delete the event, then redirect to home.
|
||||||
|
@ -367,9 +318,10 @@ export default class Event extends EventMixin {
|
||||||
|
|
||||||
confirmLeave() {
|
confirmLeave() {
|
||||||
this.$buefy.dialog.confirm({
|
this.$buefy.dialog.confirm({
|
||||||
title: `Leaving event « ${this.event.title} »`,
|
title: this.$t('Leaving event "{title}"', { title: this.event.title }) as string,
|
||||||
message: `Are you sure you want to leave event « ${this.event.title} »`,
|
message: this.$t('Are you sure you want to cancel your participation at event "{title}"?', { title: this.event.title }) as string,
|
||||||
confirmText: 'Leave event',
|
confirmText: this.$t('Leave event') as string,
|
||||||
|
cancelText: this.$t('Cancel') as string,
|
||||||
type: 'is-danger',
|
type: 'is-danger',
|
||||||
hasIcon: true,
|
hasIcon: true,
|
||||||
onConfirm: () => this.leaveEvent(),
|
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>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
section.container {
|
section.container {
|
||||||
min-height: 30em;
|
min-height: 30em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,13 @@
|
||||||
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t('Group') }}</router-link>
|
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t('Group') }}</router-link>
|
||||||
</b-dropdown-item>
|
</b-dropdown-item>
|
||||||
</b-dropdown>
|
</b-dropdown>
|
||||||
<section v-if="currentActor" class="container">
|
<section v-if="currentActor && goingToEvents.size > 0" class="container">
|
||||||
<h3 class="title">
|
<h3 class="title">
|
||||||
{{ $t("Upcoming") }}
|
{{ $t("Upcoming") }}
|
||||||
</h3>
|
</h3>
|
||||||
|
<pre>{{ Array.from(goingToEvents.entries()) }}</pre>
|
||||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
<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])">
|
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
||||||
<date-component :date="row[0]"></date-component>
|
<date-component :date="row[0]"></date-component>
|
||||||
<h3 class="subtitle"
|
<h3 class="subtitle"
|
||||||
|
@ -63,9 +64,6 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<b-message v-else type="is-danger">
|
|
||||||
{{ $t("You're not going to any event yet") }}
|
|
||||||
</b-message>
|
|
||||||
<span class="view-all">
|
<span class="view-all">
|
||||||
<router-link :to=" { name: EventRouteName.MY_EVENTS }">{{ $t('View everything')}} >></router-link>
|
<router-link :to=" { name: EventRouteName.MY_EVENTS }">{{ $t('View everything')}} >></router-link>
|
||||||
</span>
|
</span>
|
||||||
|
@ -78,9 +76,10 @@
|
||||||
<div class="level">
|
<div class="level">
|
||||||
<EventListCard
|
<EventListCard
|
||||||
v-for="participation in lastWeekEvents"
|
v-for="participation in lastWeekEvents"
|
||||||
:key="participation.event.uuid"
|
:key="participation.id"
|
||||||
:participation="participation"
|
:participation="participation"
|
||||||
class="level-item"
|
class="level-item"
|
||||||
|
:options="{ hideDate: false }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -190,6 +189,10 @@ export default class Home extends Vue {
|
||||||
return this.calculateDiffDays(date) < nbDays;
|
return this.calculateDiffDays(date) < nbDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAfter(date: string, nbDays: number) :boolean {
|
||||||
|
return this.calculateDiffDays(date) >= nbDays;
|
||||||
|
}
|
||||||
|
|
||||||
isInLessThanSevenDays(date: string): boolean {
|
isInLessThanSevenDays(date: string): boolean {
|
||||||
return this.isBefore(date, 7);
|
return this.isBefore(date, 7);
|
||||||
}
|
}
|
||||||
|
@ -200,7 +203,7 @@ export default class Home extends Vue {
|
||||||
|
|
||||||
get goingToEvents(): Map<string, Map<string, IParticipant>> {
|
get goingToEvents(): Map<string, Map<string, IParticipant>> {
|
||||||
const res = this.currentUserParticipations.filter(({ event }) => {
|
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(
|
res.sort(
|
||||||
(a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
|
(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) => {
|
return res.reduce((acc: Map<string, Map<string, IParticipant>>, participation: IParticipant) => {
|
||||||
const day = (new Date(participation.event.beginsOn)).toDateString();
|
const day = (new Date(participation.event.beginsOn)).toDateString();
|
||||||
const participations: Map<string, IParticipant> = acc.get(day) || new Map();
|
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);
|
acc.set(day, participations);
|
||||||
return acc;
|
return acc;
|
||||||
}, new Map());
|
}, new Map());
|
||||||
|
@ -273,7 +276,7 @@ export default class Home extends Vue {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.search-autocomplete {
|
.search-autocomplete {
|
||||||
border: 1px solid #dbdbdb;
|
border: 1px solid #dbdbdb;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
|
|
@ -67,6 +67,7 @@ defmodule Mobilizon.Events.Event do
|
||||||
@attrs @required_attrs ++ @optional_attrs
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
@update_required_attrs @required_attrs
|
@update_required_attrs @required_attrs
|
||||||
|
|
||||||
@update_optional_attrs [
|
@update_optional_attrs [
|
||||||
:slug,
|
:slug,
|
||||||
:description,
|
:description,
|
||||||
|
@ -74,6 +75,7 @@ defmodule Mobilizon.Events.Event do
|
||||||
:category,
|
:category,
|
||||||
:status,
|
:status,
|
||||||
:visibility,
|
:visibility,
|
||||||
|
:join_options,
|
||||||
:publish_at,
|
:publish_at,
|
||||||
:online_address,
|
:online_address,
|
||||||
:phone_address,
|
:phone_address,
|
||||||
|
|
|
@ -522,6 +522,26 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single participant.
|
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()) ::
|
@spec get_participant(integer | String.t(), integer | String.t()) ::
|
||||||
{:ok, Participant.t()} | {:error, :participant_not_found}
|
{:ok, Participant.t()} | {:error, :participant_not_found}
|
||||||
|
@ -536,8 +556,18 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single participant.
|
Gets a single participation for an event and actor.
|
||||||
Raises `Ecto.NoResultsError` if the participant does not exist.
|
|
||||||
|
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()
|
@spec get_participant!(integer | String.t(), integer | String.t()) :: Participant.t()
|
||||||
def get_participant!(event_id, actor_id) do
|
def get_participant!(event_id, actor_id) do
|
||||||
|
@ -554,35 +584,20 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@default_participant_roles [:participant, :moderator, :administrator, :creator]
|
||||||
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 """
|
@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()}
|
@spec list_participants_for_event(String.t(), list(atom()), integer | nil, integer | nil) ::
|
||||||
def create_participant(attrs \\ %{}) do
|
[Participant.t()]
|
||||||
with {:ok, %Participant{} = participant} <-
|
def list_participants_for_event(uuid, roles \\ @default_participant_roles, page, limit) do
|
||||||
%Participant{}
|
uuid
|
||||||
|> Participant.changeset(attrs)
|
|> list_participants_for_event_query()
|
||||||
|> Repo.insert() do
|
|> filter_role(roles)
|
||||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
|> Page.paginate(page, limit)
|
||||||
end
|
|> Repo.all()
|
||||||
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
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -596,82 +611,41 @@ defmodule Mobilizon.Events do
|
||||||
[%Participant{}, ...]
|
[%Participant{}, ...]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def list_participations_for_user(
|
@spec list_participations_for_user(
|
||||||
user_id,
|
integer,
|
||||||
after_datetime \\ nil,
|
DateTime.t() | nil,
|
||||||
before_datetime \\ nil,
|
DateTime.t() | nil,
|
||||||
page \\ nil,
|
integer | nil,
|
||||||
limit \\ nil
|
integer | nil
|
||||||
)
|
) :: list(Participant.t())
|
||||||
|
def list_participations_for_user(user_id, after_datetime, before_datetime, page, limit) do
|
||||||
def list_participations_for_user(user_id, %DateTime{} = after_datetime, nil, page, limit) do
|
|
||||||
user_id
|
user_id
|
||||||
|> do_list_participations_for_user(page, limit)
|
|> list_participations_for_user_query()
|
||||||
|> where([_p, e, _a], e.begins_on > ^after_datetime)
|
|> participation_filter_begins_on(after_datetime, before_datetime)
|
||||||
|> order_by([_p, e, _a], asc: e.begins_on)
|
|> Page.paginate(page, limit)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_participations_for_user(user_id, nil, %DateTime{} = before_datetime, page, limit) do
|
@doc """
|
||||||
user_id
|
Returns the list of moderator participants for an event.
|
||||||
|> 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
|
## Examples
|
||||||
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
|
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(
|
from(
|
||||||
p in Participant,
|
p in Participant,
|
||||||
join: e in Event,
|
where:
|
||||||
join: a in Actor,
|
p.event_id == ^event_id and
|
||||||
on: p.actor_id == a.id,
|
p.actor_id ==
|
||||||
on: p.event_id == e.id,
|
^actor_id and p.role in ^[:moderator, :administrator, :creator]
|
||||||
where: a.user_id == ^user_id and p.role != ^:not_approved,
|
|
||||||
preload: [:event, :actor]
|
|
||||||
)
|
)
|
||||||
|> Page.paginate(page, limit)
|
) == nil)
|
||||||
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)
|
|
||||||
|> Page.paginate(page, limit)
|
|
||||||
|> Repo.all()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -739,6 +713,44 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
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 """
|
@doc """
|
||||||
Gets a single session.
|
Gets a single session.
|
||||||
Raises `Ecto.NoResultsError` if the session does not exist.
|
Raises `Ecto.NoResultsError` if the session does not exist.
|
||||||
|
@ -1203,17 +1215,6 @@ defmodule Mobilizon.Events do
|
||||||
)
|
)
|
||||||
end
|
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
|
defp organizers_participants_for_event(event_id) do
|
||||||
from(
|
from(
|
||||||
p in Participant,
|
p in Participant,
|
||||||
|
@ -1274,6 +1275,30 @@ defmodule Mobilizon.Events do
|
||||||
)
|
)
|
||||||
end
|
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()
|
@spec count_comments_query(integer) :: Ecto.Query.t()
|
||||||
defp count_comments_query(actor_id) do
|
defp count_comments_query(actor_id) do
|
||||||
from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id)
|
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)
|
from(p in query, where: p.role == ^:not_approved)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec filter_role(Ecto.Query.t(), boolean) :: Ecto.Query.t()
|
@spec filter_role(Ecto.Query.t(), list(atom())) :: Ecto.Query.t()
|
||||||
defp filter_role(query, false), do: filter_approved_role(query)
|
defp filter_role(query, []), do: query
|
||||||
defp filter_role(query, true), 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()
|
@spec preload_for_event(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
defp preload_for_event(query), do: preload(query, ^@event_preloads)
|
defp preload_for_event(query), do: preload(query, ^@event_preloads)
|
||||||
|
|
|
@ -24,6 +24,7 @@ defmodule MobilizonWeb.API.Events do
|
||||||
begins_on: begins_on,
|
begins_on: begins_on,
|
||||||
ends_on: ends_on,
|
ends_on: ends_on,
|
||||||
category: category,
|
category: category,
|
||||||
|
join_options: join_options,
|
||||||
options: options
|
options: options
|
||||||
} <- prepare_args(args),
|
} <- prepare_args(args),
|
||||||
event <-
|
event <-
|
||||||
|
@ -39,7 +40,8 @@ defmodule MobilizonWeb.API.Events do
|
||||||
ends_on: ends_on,
|
ends_on: ends_on,
|
||||||
physical_address: physical_address,
|
physical_address: physical_address,
|
||||||
category: category,
|
category: category,
|
||||||
options: options
|
options: options,
|
||||||
|
join_options: join_options
|
||||||
}
|
}
|
||||||
) do
|
) do
|
||||||
ActivityPub.create(%{
|
ActivityPub.create(%{
|
||||||
|
@ -73,6 +75,7 @@ defmodule MobilizonWeb.API.Events do
|
||||||
begins_on: begins_on,
|
begins_on: begins_on,
|
||||||
ends_on: ends_on,
|
ends_on: ends_on,
|
||||||
category: category,
|
category: category,
|
||||||
|
join_options: join_options,
|
||||||
options: options
|
options: options
|
||||||
} <-
|
} <-
|
||||||
prepare_args(Map.merge(event, args)),
|
prepare_args(Map.merge(event, args)),
|
||||||
|
@ -89,6 +92,7 @@ defmodule MobilizonWeb.API.Events do
|
||||||
ends_on: ends_on,
|
ends_on: ends_on,
|
||||||
physical_address: physical_address,
|
physical_address: physical_address,
|
||||||
category: category,
|
category: category,
|
||||||
|
join_options: join_options,
|
||||||
options: options
|
options: options
|
||||||
},
|
},
|
||||||
event.uuid,
|
event.uuid,
|
||||||
|
@ -112,7 +116,8 @@ defmodule MobilizonWeb.API.Events do
|
||||||
options: options,
|
options: options,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
begins_on: begins_on,
|
begins_on: begins_on,
|
||||||
category: category
|
category: category,
|
||||||
|
join_options: join_options
|
||||||
} = args
|
} = args
|
||||||
) do
|
) do
|
||||||
with physical_address <- Map.get(args, :physical_address, nil),
|
with physical_address <- Map.get(args, :physical_address, nil),
|
||||||
|
@ -132,6 +137,7 @@ defmodule MobilizonWeb.API.Events do
|
||||||
begins_on: begins_on,
|
begins_on: begins_on,
|
||||||
ends_on: Map.get(args, :ends_on, nil),
|
ends_on: Map.get(args, :ends_on, nil),
|
||||||
category: category,
|
category: category,
|
||||||
|
join_options: join_options,
|
||||||
options: options
|
options: options
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,9 @@ defmodule MobilizonWeb.API.Participations do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
require Logger
|
|
||||||
|
|
||||||
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
|
@spec join(Event.t(), Actor.t()) :: {:ok, Participant.t()}
|
||||||
def join(%Event{id: event_id} = event, %Actor{id: actor_id} = actor) do
|
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}
|
{:ok, activity, participant}
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -42,14 +42,30 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
List participant for event (separate request)
|
List participant for event (separate request)
|
||||||
"""
|
"""
|
||||||
def list_participants_for_event(_parent, %{uuid: uuid, page: page, limit: limit}, _resolution) do
|
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
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
List participants for event (through an event request)
|
List participants for event (through an event request)
|
||||||
"""
|
"""
|
||||||
def list_participants_for_event(%Event{uuid: uuid}, _args, _resolution) do
|
def list_participants_for_event(
|
||||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)}
|
%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
|
end
|
||||||
|
|
||||||
def stats_participants_for_event(%Event{id: id}, _args, _resolution) do
|
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"}
|
{:error, "You need to be logged-in to leave an event"}
|
||||||
end
|
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 """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -24,6 +24,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
field(:ends_on, :datetime, description: "Datetime for when the event ends")
|
field(:ends_on, :datetime, description: "Datetime for when the event ends")
|
||||||
field(:status, :event_status, description: "Status of the event")
|
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,
|
field(:picture, :picture,
|
||||||
description: "The event's 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(:participant_stats, :participant_stats, resolve: &Event.stats_participants_for_event/3)
|
||||||
|
|
||||||
field(:participants, list_of(:participant),
|
field(:participants, list_of(:participant), description: "The event's participants") do
|
||||||
resolve: &Event.list_participants_for_event/3,
|
arg(:page, :integer, default_value: 1)
|
||||||
description: "The event's participants"
|
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),
|
field(:related_events, list_of(:event),
|
||||||
resolve: &Event.list_related_events/3,
|
resolve: &Event.list_related_events/3,
|
||||||
|
@ -78,13 +81,18 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
enum :event_visibility do
|
enum :event_visibility do
|
||||||
value(:public, description: "Publicly listed and federated. Can be shared.")
|
value(:public, description: "Publicly listed and federated. Can be shared.")
|
||||||
value(:unlisted, description: "Visible only to people with the link - or invited")
|
value(:unlisted, description: "Visible only to people with the link - or invited")
|
||||||
|
value(:restricted, description: "Visible only after a moderator accepted")
|
||||||
|
|
||||||
value(:private,
|
value(:private,
|
||||||
description: "Visible only to people members of the group or followers of the person"
|
description: "Visible only to people members of the group or followers of the person"
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
value(:moderated, description: "Visible only after a moderator accepted")
|
@desc "The list of join options for an event"
|
||||||
value(:invite, description: "visible only to people invited")
|
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
|
end
|
||||||
|
|
||||||
@desc "The list of possible options for the event's status"
|
@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(:ends_on, :datetime)
|
||||||
arg(:status, :event_status)
|
arg(:status, :event_status)
|
||||||
arg(:visibility, :event_visibility, default_value: :private)
|
arg(:visibility, :event_visibility, default_value: :private)
|
||||||
|
arg(:join_options, :event_join_options, default_value: :free)
|
||||||
|
|
||||||
arg(:tags, list_of(:string),
|
arg(:tags, list_of(:string),
|
||||||
default_value: [],
|
default_value: [],
|
||||||
|
@ -250,6 +259,7 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
arg(:ends_on, :datetime)
|
arg(:ends_on, :datetime)
|
||||||
arg(:status, :event_status)
|
arg(:status, :event_status)
|
||||||
arg(:visibility, :event_visibility)
|
arg(:visibility, :event_visibility)
|
||||||
|
arg(:join_options, :event_join_options)
|
||||||
|
|
||||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
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"
|
@desc "Represents a participant to an event"
|
||||||
object :participant do
|
object :participant do
|
||||||
|
field(:id, :id, description: "The participation ID")
|
||||||
|
|
||||||
field(
|
field(
|
||||||
:event,
|
:event,
|
||||||
:event,
|
:event,
|
||||||
|
@ -24,11 +26,20 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
||||||
description: "The actor that participates to the event"
|
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
|
end
|
||||||
|
|
||||||
@desc "Represents a deleted participant"
|
@desc "Represents a deleted participant"
|
||||||
object :deleted_participant do
|
object :deleted_participant do
|
||||||
|
field(:id, :id)
|
||||||
field(:event, :deleted_object)
|
field(:event, :deleted_object)
|
||||||
field(:actor, :deleted_object)
|
field(:actor, :deleted_object)
|
||||||
end
|
end
|
||||||
|
@ -59,5 +70,21 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
|
||||||
|
|
||||||
resolve(&Resolvers.Event.actor_leave_event/3)
|
resolve(&Resolvers.Event.actor_leave_event/3)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,6 +59,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
|
||||||
"begins_on" => object["startTime"],
|
"begins_on" => object["startTime"],
|
||||||
"ends_on" => object["endTime"],
|
"ends_on" => object["endTime"],
|
||||||
"category" => object["category"],
|
"category" => object["category"],
|
||||||
|
"join_options" => object["joinOptions"],
|
||||||
"url" => object["id"],
|
"url" => object["id"],
|
||||||
"uuid" => object["uuid"],
|
"uuid" => object["uuid"],
|
||||||
"tags" => tags,
|
"tags" => tags,
|
||||||
|
|
|
@ -328,6 +328,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"category" => metadata.category,
|
"category" => metadata.category,
|
||||||
"actor" => actor,
|
"actor" => actor,
|
||||||
"id" => url || Routes.page_url(Endpoint, :event, uuid),
|
"id" => url || Routes.page_url(Endpoint, :event, uuid),
|
||||||
|
"joinOptions" => metadata.join_options,
|
||||||
"uuid" => uuid,
|
"uuid" => uuid,
|
||||||
"tag" =>
|
"tag" =>
|
||||||
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
|
tags |> Enum.uniq() |> Enum.map(fn tag -> %{"type" => "Hashtag", "name" => "##{tag}"} end)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# source: http://localhost:4000/api
|
# 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 {
|
schema {
|
||||||
query: RootQueryType
|
query: RootQueryType
|
||||||
|
@ -244,6 +244,7 @@ type DeletedObject {
|
||||||
type DeletedParticipant {
|
type DeletedParticipant {
|
||||||
actor: DeletedObject
|
actor: DeletedObject
|
||||||
event: DeletedObject
|
event: DeletedObject
|
||||||
|
id: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
"""An event"""
|
"""An event"""
|
||||||
|
@ -269,6 +270,9 @@ type Event implements ActionLogObject {
|
||||||
"""Internal ID for this event"""
|
"""Internal ID for this event"""
|
||||||
id: ID
|
id: ID
|
||||||
|
|
||||||
|
"""The event's visibility"""
|
||||||
|
joinOptions: EventJoinOptions
|
||||||
|
|
||||||
"""Whether the event is local or not"""
|
"""Whether the event is local or not"""
|
||||||
local: Boolean
|
local: Boolean
|
||||||
|
|
||||||
|
@ -283,7 +287,7 @@ type Event implements ActionLogObject {
|
||||||
participantStats: ParticipantStats
|
participantStats: ParticipantStats
|
||||||
|
|
||||||
"""The event's participants"""
|
"""The event's participants"""
|
||||||
participants: [Participant]
|
participants(limit: Int = 10, page: Int = 1, roles: String = ""): [Participant]
|
||||||
|
|
||||||
"""Phone address for the event"""
|
"""Phone address for the event"""
|
||||||
phoneAddress: String
|
phoneAddress: String
|
||||||
|
@ -337,6 +341,18 @@ enum EventCommentModeration {
|
||||||
MODERATED
|
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 {
|
type EventOffer {
|
||||||
"""The price amount for this offer"""
|
"""The price amount for this offer"""
|
||||||
price: Float
|
price: Float
|
||||||
|
@ -462,18 +478,15 @@ enum EventStatus {
|
||||||
|
|
||||||
"""The list of visibility options for an event"""
|
"""The list of visibility options for an event"""
|
||||||
enum EventVisibility {
|
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"""
|
"""Visible only to people members of the group or followers of the person"""
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
|
||||||
"""Publicly listed and federated. Can be shared."""
|
"""Publicly listed and federated. Can be shared."""
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
|
||||||
|
"""Visible only after a moderator accepted"""
|
||||||
|
RESTRICTED
|
||||||
|
|
||||||
"""Visible only to people with the link - or invited"""
|
"""Visible only to people with the link - or invited"""
|
||||||
UNLISTED
|
UNLISTED
|
||||||
}
|
}
|
||||||
|
@ -645,8 +658,19 @@ type Participant {
|
||||||
"""The event which the actor participates in"""
|
"""The event which the actor participates in"""
|
||||||
event: Event
|
event: Event
|
||||||
|
|
||||||
|
"""The participation ID"""
|
||||||
|
id: ID
|
||||||
|
|
||||||
"""The role of this actor at this event"""
|
"""The role of this actor at this event"""
|
||||||
role: Int
|
role: ParticipantRoleEnum
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ParticipantRoleEnum {
|
||||||
|
ADMINISTRATOR
|
||||||
|
CREATOR
|
||||||
|
MODERATOR
|
||||||
|
NOT_APPROVED
|
||||||
|
PARTICIPANT
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParticipantStats {
|
type ParticipantStats {
|
||||||
|
@ -855,6 +879,9 @@ enum ReportStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RootMutationType {
|
type RootMutationType {
|
||||||
|
"""Accept a participation"""
|
||||||
|
acceptParticipation(id: ID!, moderatorActorId: ID!): Participant
|
||||||
|
|
||||||
"""Change default actor for user"""
|
"""Change default actor for user"""
|
||||||
changeDefaultActor(preferredUsername: String!): User
|
changeDefaultActor(preferredUsername: String!): User
|
||||||
|
|
||||||
|
@ -867,6 +894,7 @@ type RootMutationType {
|
||||||
category: String = "meeting"
|
category: String = "meeting"
|
||||||
description: String!
|
description: String!
|
||||||
endsOn: DateTime
|
endsOn: DateTime
|
||||||
|
joinOptions: EventJoinOptions = FREE
|
||||||
onlineAddress: String
|
onlineAddress: String
|
||||||
options: EventOptionsInput
|
options: EventOptionsInput
|
||||||
organizerActorId: ID!
|
organizerActorId: ID!
|
||||||
|
@ -997,6 +1025,9 @@ type RootMutationType {
|
||||||
summary: String = ""
|
summary: String = ""
|
||||||
): Person
|
): Person
|
||||||
|
|
||||||
|
"""Reject a participation"""
|
||||||
|
rejectParticipation(id: ID!, moderatorActorId: ID!): DeletedParticipant
|
||||||
|
|
||||||
"""Resend registration confirmation token"""
|
"""Resend registration confirmation token"""
|
||||||
resendConfirmationEmail(email: String!, locale: String = "en"): String
|
resendConfirmationEmail(email: String!, locale: String = "en"): String
|
||||||
|
|
||||||
|
@ -1013,6 +1044,7 @@ type RootMutationType {
|
||||||
description: String
|
description: String
|
||||||
endsOn: DateTime
|
endsOn: DateTime
|
||||||
eventId: ID!
|
eventId: ID!
|
||||||
|
joinOptions: EventJoinOptions
|
||||||
onlineAddress: String
|
onlineAddress: String
|
||||||
options: EventOptionsInput
|
options: EventOptionsInput
|
||||||
phoneAddress: String
|
phoneAddress: String
|
||||||
|
|
|
@ -316,13 +316,6 @@ defmodule Mobilizon.EventsTest do
|
||||||
{:ok, participant: participant, event: event, actor: actor}
|
{:ok, participant: participant, event: event, actor: actor}
|
||||||
end
|
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", %{
|
test "get_participant!/1 returns the participant for a given event and given actor", %{
|
||||||
event: %Event{id: event_id},
|
event: %Event{id: event_id},
|
||||||
actor: %Actor{id: actor_id}
|
actor: %Actor{id: actor_id}
|
||||||
|
|
|
@ -784,7 +784,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
assert :error == Transmogrifier.handle_incoming(reject_data)
|
assert :error == Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
# Organiser is not present since we use factories directly
|
# 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
|
end
|
||||||
|
|
||||||
|
@ -812,7 +812,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
||||||
assert activity.data["actor"] == participant_url
|
assert activity.data["actor"] == participant_url
|
||||||
|
|
||||||
# The only participant left is the organizer
|
# 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
|
organizer_participation.id
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
|
@ -50,7 +50,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||||
|
|
||||||
assert json_response(res, 200)["errors"] == nil
|
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"]["event"]["id"] == to_string(event.id)
|
||||||
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
|
||||||
|
|
||||||
|
@ -161,25 +161,27 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
participants(uuid: "#{event.uuid}") {
|
event(uuid: "#{event.uuid}") {
|
||||||
|
participants {
|
||||||
role,
|
role,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername
|
preferredUsername
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["participants"] == [
|
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||||
%{
|
%{
|
||||||
"actor" => %{
|
"actor" => %{
|
||||||
"preferredUsername" => participant2.actor.preferred_username
|
"preferredUsername" => participant2.actor.preferred_username
|
||||||
},
|
},
|
||||||
"role" => "creator"
|
"role" => "CREATOR"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -331,25 +333,27 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
{
|
{
|
||||||
participants(uuid: "#{event.uuid}") {
|
event(uuid: "#{event.uuid}") {
|
||||||
|
participants(roles: "participant,moderator,administrator,creator") {
|
||||||
role,
|
role,
|
||||||
actor {
|
actor {
|
||||||
preferredUsername
|
preferredUsername
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res =
|
res =
|
||||||
context.conn
|
context.conn
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["participants"] == [
|
assert json_response(res, 200)["data"]["event"]["participants"] == [
|
||||||
%{
|
%{
|
||||||
"actor" => %{
|
"actor" => %{
|
||||||
"preferredUsername" => context.actor.preferred_username
|
"preferredUsername" => context.actor.preferred_username
|
||||||
},
|
},
|
||||||
"role" => "creator"
|
"role" => "CREATOR"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -361,12 +365,59 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
# This one will (as a participant)
|
# This one will (as a participant)
|
||||||
participant2 = insert(:participant, event: event, actor: actor3, role: :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 =
|
res =
|
||||||
context.conn
|
context.conn
|
||||||
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
|
||||||
|
|
||||||
sorted_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(
|
|> Enum.sort_by(
|
||||||
&(&1
|
&(&1
|
||||||
|> Map.get("actor")
|
|> Map.get("actor")
|
||||||
|
@ -378,13 +429,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
"actor" => %{
|
"actor" => %{
|
||||||
"preferredUsername" => context.actor.preferred_username
|
"preferredUsername" => context.actor.preferred_username
|
||||||
},
|
},
|
||||||
"role" => "creator"
|
"role" => "CREATOR"
|
||||||
},
|
|
||||||
%{
|
|
||||||
"actor" => %{
|
|
||||||
"preferredUsername" => participant2.actor.preferred_username
|
|
||||||
},
|
|
||||||
"role" => "participant"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -456,4 +501,281 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
||||||
assert json_response(res, 200)["data"]["event"]["participantStats"]["unapproved"] == 1
|
assert json_response(res, 200)["data"]["event"]["participantStats"]["unapproved"] == 1
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue