From fd669e90fa4e0b3e4b470c31251360a2dbf3886c Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 26 Sep 2019 16:38:58 +0200 Subject: [PATCH] Add a dropdown on participate menu, disallow listing participations Now requires quering the person endpoint to know if an actor participates in an event, organizers can make authenticated requests to event { participants { } } to see the pending / approved participants. Also closes #174 Signed-off-by: Thomas Citharel --- js/src/App.vue | 43 +--- js/src/components/Event/DateTimePicker.vue | 2 +- js/src/components/Event/EventListCard.vue | 118 +++++------ .../components/Event/ParticipationButton.vue | 111 ++++++++++ .../components/Event/ParticipationModal.vue | 92 --------- js/src/graphql/event.ts | 31 ++- js/src/i18n/en_US.json | 16 ++ js/src/i18n/fr_FR.json | 16 ++ js/src/types/actor/person.model.ts | 4 +- js/src/views/Account/IdentityPicker.vue | 50 ++--- .../views/Account/IdentityPickerWrapper.vue | 39 ++++ js/src/views/Account/Register.vue | 1 + js/src/views/Event/Edit.vue | 7 +- js/src/views/Event/Event.vue | 194 +++++++++++------- js/src/views/Event/Participants.vue | 3 + js/src/views/Home.vue | 13 +- js/src/views/Moderation/Report.vue | 4 +- lib/mobilizon/events/events.ex | 20 +- lib/mobilizon_web/resolvers/event.ex | 50 +++-- lib/mobilizon_web/resolvers/person.ex | 27 ++- lib/mobilizon_web/resolvers/user.ex | 7 +- lib/mobilizon_web/schema.ex | 1 - lib/mobilizon_web/schema/actors/person.ex | 7 +- lib/mobilizon_web/schema/event.ex | 1 + .../schema/events/participant.ex | 10 - lib/mobilizon_web/schema/user.ex | 2 +- lib/service/export/feed.ex | 16 +- lib/service/export/icalendar.ex | 17 +- schema.graphql | 15 +- test/mobilizon/events/events_test.exs | 4 +- .../activity_pub/transmogrifier_test.exs | 4 +- .../controllers/feed_controller_test.exs | 7 +- .../resolvers/participant_resolver_test.exs | 91 +++++--- .../resolvers/person_resolver_test.exs | 71 +++++-- 34 files changed, 655 insertions(+), 439 deletions(-) create mode 100644 js/src/components/Event/ParticipationButton.vue delete mode 100644 js/src/components/Event/ParticipationModal.vue create mode 100644 js/src/views/Account/IdentityPickerWrapper.vue diff --git a/js/src/App.vue b/js/src/App.vue index e165fdbd5..38e2d4c5c 100644 --- a/js/src/App.vue +++ b/js/src/App.vue @@ -71,49 +71,10 @@ export default class App extends Vue { @import "variables"; /* Bulma imports */ -@import "~bulma/sass/utilities/_all"; -@import "~bulma/sass/base/_all.sass"; -@import "~bulma/sass/components/card.sass"; -@import "~bulma/sass/components/media.sass"; -@import "~bulma/sass/components/message.sass"; -@import "~bulma/sass/components/modal.sass"; -@import "~bulma/sass/components/navbar.sass"; -@import "~bulma/sass/components/pagination.sass"; -@import "~bulma/sass/components/dropdown.sass"; -@import "~bulma/sass/components/breadcrumb.sass"; -@import "~bulma/sass/components/list.sass"; -@import "~bulma/sass/components/tabs"; -@import "~bulma/sass/elements/box.sass"; -@import "~bulma/sass/elements/button.sass"; -@import "~bulma/sass/elements/container.sass"; -@import "~bulma/sass/form/_all"; -@import "~bulma/sass/elements/icon.sass"; -@import "~bulma/sass/elements/image.sass"; -@import "~bulma/sass/elements/other.sass"; -@import "~bulma/sass/elements/progress.sass"; -@import "~bulma/sass/elements/tag.sass"; -@import "~bulma/sass/elements/title.sass"; -@import "~bulma/sass/elements/notification"; -@import "~bulma/sass/elements/table"; -@import "~bulma/sass/grid/_all.sass"; -@import "~bulma/sass/layout/_all.sass"; +@import "~bulma/bulma"; /* Buefy imports */ -@import "~buefy/src/scss/utils/_all"; -@import "~buefy/src/scss/components/datepicker"; -@import "~buefy/src/scss/components/notices"; -@import "~buefy/src/scss/components/dropdown"; -@import "~buefy/src/scss/components/autocomplete"; -@import "~buefy/src/scss/components/form"; -@import "~buefy/src/scss/components/modal"; -@import "~buefy/src/scss/components/progress"; -@import "~buefy/src/scss/components/tag"; -@import "~buefy/src/scss/components/taginput"; -@import "~buefy/src/scss/components/upload"; -@import "~buefy/src/scss/components/radio"; -@import "~buefy/src/scss/components/switch"; -@import "~buefy/src/scss/components/table"; -@import "~buefy/src/scss/components/tabs"; +@import "~buefy/src/scss/buefy"; .router-enter-active, .router-leave-active { diff --git a/js/src/components/Event/DateTimePicker.vue b/js/src/components/Event/DateTimePicker.vue index 2a0b91f4e..40d2d070a 100644 --- a/js/src/components/Event/DateTimePicker.vue +++ b/js/src/components/Event/DateTimePicker.vue @@ -6,7 +6,7 @@ + + \ No newline at end of file diff --git a/js/src/components/Event/ParticipationModal.vue b/js/src/components/Event/ParticipationModal.vue deleted file mode 100644 index 0ad1dda27..000000000 --- a/js/src/components/Event/ParticipationModal.vue +++ /dev/null @@ -1,92 +0,0 @@ - - - - \ No newline at end of file diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts index e9bccbda0..6d53b8964 100644 --- a/js/src/graphql/event.ts +++ b/js/src/graphql/event.ts @@ -10,6 +10,9 @@ const participantQuery = ` }, name, id + }, + event { + id } `; @@ -52,7 +55,7 @@ const optionsQuery = ` `; export const FETCH_EVENT = gql` - query($uuid:UUID!, $roles: String) { + query($uuid:UUID!) { event(uuid: $uuid) { id, uuid, @@ -95,9 +98,6 @@ export const FETCH_EVENT = gql` # preferredUsername, # name, # }, - participants (roles: $roles) { - ${participantQuery} - }, participantStats { approved, unapproved @@ -363,9 +363,10 @@ export const DELETE_EVENT = gql` `; export const PARTICIPANTS = gql` - query($uuid: UUID!, $page: Int, $limit: Int, $roles: String) { + query($uuid: UUID!, $page: Int, $limit: Int, $roles: String, $actorId: ID!) { event(uuid: $uuid) { - participants(page: $page, limit: $limit, roles: $roles) { + id, + participants(page: $page, limit: $limit, roles: $roles, actorId: $actorId) { ${participantQuery} }, participantStats { @@ -375,3 +376,21 @@ export const PARTICIPANTS = gql` } } `; + +export const EVENT_PERSON_PARTICIPATION = gql` + query($name: String!, $eventId: ID!) { + person(preferredUsername: $name) { + id, + participations(eventId: $eventId) { + id, + role, + actor { + id + }, + event { + id + } + } + } + } +`; diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 45d66a819..2f42b26ab 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -17,8 +17,13 @@ "Are you sure you want to delete this event? This action cannot be reverted.": "Are you sure you want to delete this event? This action cannot be reverted.", "Before you can login, you need to click on the link inside it to validate your account": "Before you can login, you need to click on the link inside it to validate your account", "By {name}": "By {name}", + "Cancel my participation request…": "Cancel my participation request…", + "Cancel my participation…": "Cancel my participation…", "Cancel": "Cancel", "Category": "Category", + "Change my identity…": "Change my identity…", + "Change my password": "Change my password", + "Change password": "Change password", "Change": "Change", "Clear": "Clear", "Click to select": "Click to select", @@ -82,6 +87,7 @@ "Group": "Group", "Groups": "Groups", "I create an identity": "I create an identity", + "I participate": "I participate", "I want to approve every participation request": "I want to approve every participation request", "Identities": "Identities", "Identity {displayName} created": "Identity {displayName} created", @@ -116,6 +122,7 @@ "My events": "My events", "My identities": "My identities", "Name": "Name", + "New password": "New password", "No address defined": "No address defined", "No events found": "No events found", "No group found": "No group found", @@ -123,6 +130,7 @@ "No participants yet.": "No participants yet.", "No results for \"{queryText}\"": "No results for \"{queryText}\"", "Number of places": "Number of places", + "Old password": "Old password", "One person is going": "No one is going | One person is going | {approved} persons are going", "Only accessible through link and search (private)": "Only accessible through link and search (private)", "Opened reports": "Opened reports", @@ -133,8 +141,11 @@ "Otherwise this identity will just be removed from the group administrators.": "Otherwise this identity will just be removed from the group administrators.", "Page limited to my group (asks for auth)": "Page limited to my group (asks for auth)", "Participants": "Participants", + "Participate": "Participate", "Participation approval": "Participation approval", + "Participation requested!": "Participation requested!", "Password (confirmation)": "Password (confirmation)", + "Password change": "Password change", "Password reset": "Password reset", "Password": "Password", "Past events": "Passed events", @@ -187,6 +198,7 @@ "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 title will be ellipsed.": "The event title will be ellipsed.", "The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.", + "The password was successfully changed": "The password was successfully changed", "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.", "The {date} at {time}": "The {date} at {time}", "The {date} from {startTime} to {endTime}": "The {date} from {startTime} to {endTime}", @@ -208,6 +220,7 @@ "View event page": "View event page", "View everything": "View everything", "Visible everywhere on the web (public)": "Visible everywhere on the web (public)", + "Waiting for organization team approval.": "Waiting for organization team approval.", "Waiting list": "Waiting list", "We just sent an email to {email}": "We just sent an email to {email}", "Website / URL": "Website / URL", @@ -220,6 +233,7 @@ "You announced that you're going to this event.": "You announced that you're going to this event.", "You are already logged-in.": "You are already logged-in.", "You are an organizer.": "You are an organizer.", + "You have been disconnected": "You have been disconnected", "You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days", "You have one event today.": "You have no events today | You have one event today. | You have {count} events today", "You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow", @@ -233,6 +247,8 @@ "e.g. 10 Rue Jangot": "e.g. 10 Rue Jangot", "iCal Feed": "iCal Feed", "meditate a bit": "meditate a bit", + "with another identity…": "with another identity…", + "with {identity}": "with {identity}", "{actor}'s avatar": "{actor}'s avatar", "{approved} / {total} seats": "{approved} / {total} seats", "{count} participants": "{count} participants", diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index b1093854b..96b018aa4 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -17,8 +17,13 @@ "Are you sure you want to delete this event? This action cannot be reverted.": "Êtes-vous certain⋅e de vouloir supprimer cet événement ? Cette action ne peut être annulée.", "Before you can login, you need to click on the link inside it to validate your account": "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte", "By {name}": "Par {name}", + "Cancel my participation request…": "Cancel my participation request…", + "Cancel my participation…": "Annuler ma participation…", "Cancel": "Annuler", "Category": "Catégorie", + "Change my identity…": "Changer mon identité…", + "Change my password": "Modifier mon mot de passe", + "Change password": "Modifier mot de passe", "Change": "Modifier", "Clear": "Effacer", "Click to select": "Cliquez pour sélectionner", @@ -82,6 +87,7 @@ "Group": "Groupe", "Groups": "Groupes", "I create an identity": "Je crée une identité", + "I participate": "Je participe", "I want to approve every participation request": "Je veux approuver chaque demande de participation", "Identities": "Identités", "Identity {displayName} created": "Identité {displayName} créée", @@ -116,6 +122,7 @@ "My events": "Mes événements", "My identities": "Mes identités", "Name": "Nom", + "New password": "Nouveau mot de passe", "No address defined": "Aucune adresse définie", "No events found": "Aucun événement trouvé", "No group found": "Aucun groupe trouvé", @@ -123,6 +130,7 @@ "No participants yet.": "Pas de participants pour le moment.", "No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »", "Number of places": "Nombre de places", + "Old password": "Ancien mot de passe", "One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont", "Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)", "Opened reports": "Signalements ouverts", @@ -133,8 +141,11 @@ "Otherwise this identity will just be removed from the group administrators.": "Sinon cette identité sera juste supprimée des administrateurs du groupe.", "Page limited to my group (asks for auth)": "Accès limité à mon groupe (demande authentification)", "Participants": "Participants", + "Participate": "Participer", "Participation approval": "Validation des participations", + "Participation requested!": "Participation demandée !", "Password (confirmation)": "Mot de passe (confirmation)", + "Password change": "Changement de mot de passe", "Password reset": "Réinitialisation du mot de passe", "Password": "Mot de passe", "Past events": "Événements passés", @@ -187,6 +198,7 @@ "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 title will be ellipsed.": "Le titre de l'événement sera ellipsé.", "The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.", + "The password was successfully changed": "Le mot de passe a été changé avec succès", "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.", "The {date} at {time}": "Le {date} à {time}", "The {date} from {startTime} to {endTime}": "Le {date} de {startTime} à {endTime}", @@ -208,6 +220,7 @@ "View event page": "Voir la page de l'événement", "View everything": "Voir tout", "Visible everywhere on the web (public)": "Visible partout sur le web (public)", + "Waiting for organization team approval.": "En attente d'approbation par l'organisation.", "Waiting list": "Liste d'attente", "We just sent an email to {email}": "Nous venons d'envoyer un email à {email}", "Website / URL": "Site web / URL", @@ -220,6 +233,7 @@ "You announced that you're going to this event.": "Vous avez annoncé vous rendre à cet événement.", "You are already logged-in.": "Vous êtes déjà connecté.", "You are an organizer.": "Vous êtes un organisateur.", + "You have been disconnected": "Vous avez été déconnecté⋅e", "You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours", "You have one event today.": "Vous n'avez pas d'évenement aujourd'hui | Vous avez un événement aujourd'hui. | Vous avez {count} événements aujourd'hui", "You have one event tomorrow.": "Vous n'avez pas d'événement demain | Vous avez un événement demain. | Vous avez {count} événements demain", @@ -233,6 +247,8 @@ "e.g. 10 Rue Jangot": "par exemple : 10 Rue Jangot", "iCal Feed": "Flux iCal", "meditate a bit": "méditez un peu", + "with another identity…": "avec une autre identité…", + "with {identity}": "avec {identity}", "{actor}'s avatar": "Avatar de {actor}", "{approved} / {total} seats": "{approved} / {total} places", "{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s", diff --git a/js/src/types/actor/person.model.ts b/js/src/types/actor/person.model.ts index 3a3abcee8..f346b68ab 100644 --- a/js/src/types/actor/person.model.ts +++ b/js/src/types/actor/person.model.ts @@ -1,5 +1,5 @@ import { ICurrentUser } from '@/types/current-user.model'; -import { IEvent } from '@/types/event.model'; +import { IEvent, IParticipant } from '@/types/event.model'; import { Actor, IActor } from '@/types/actor/actor.model'; export interface IFeedToken { @@ -11,11 +11,13 @@ export interface IFeedToken { export interface IPerson extends IActor { feedTokens: IFeedToken[]; goingToEvents: IEvent[]; + participations: IParticipant[]; } export class Person extends Actor implements IPerson { feedTokens: IFeedToken[] = []; goingToEvents: IEvent[] = []; + participations: IParticipant[] = []; constructor(hash: IPerson | {} = {}) { super(hash); diff --git a/js/src/views/Account/IdentityPicker.vue b/js/src/views/Account/IdentityPicker.vue index 89a182ab2..126b8a777 100644 --- a/js/src/views/Account/IdentityPicker.vue +++ b/js/src/views/Account/IdentityPicker.vue @@ -1,29 +1,22 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/js/src/views/Account/IdentityPickerWrapper.vue b/js/src/views/Account/IdentityPickerWrapper.vue new file mode 100644 index 000000000..028c29802 --- /dev/null +++ b/js/src/views/Account/IdentityPickerWrapper.vue @@ -0,0 +1,39 @@ + + + \ No newline at end of file diff --git a/js/src/views/Account/Register.vue b/js/src/views/Account/Register.vue index b11454078..07236d154 100644 --- a/js/src/views/Account/Register.vue +++ b/js/src/views/Account/Register.vue @@ -92,6 +92,7 @@ export default class Register extends Vue { domain: null, feedTokens: [], goingToEvents: [], + participations: [], }; errors: object = {}; validationSent: boolean = false; diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index 3ed81b536..fd5651c7c 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -29,7 +29,7 @@ import {EventJoinOptions} from "@/types/event.model"; - +
@@ -188,7 +188,6 @@ import { EventModel, EventStatus, EventVisibility, - EventVisibilityJoinOptions, } from '@/types/event.model'; import { CURRENT_ACTOR_CLIENT } from '@/graphql/actor'; import { Person } from '@/types/actor'; @@ -200,10 +199,10 @@ import { TAGS } from '@/graphql/tags'; import { ITag } from '@/types/tag.model'; import AddressAutoComplete from '@/components/Event/AddressAutoComplete.vue'; import { buildFileFromIPicture, buildFileVariable } from '@/utils/image'; -import IdentityPicker from '@/views/Account/IdentityPicker.vue'; +import IdentityPickerWrapper from '@/views/Account/IdentityPickerWrapper.vue'; @Component({ - components: { AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor, IdentityPicker }, + components: { IdentityPickerWrapper, AddressAutoComplete, TagInput, DateTimePicker, PictureUpload, Editor }, apollo: { currentActor: { query: CURRENT_ACTOR_CLIENT, diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue index 34bb6fd20..a1f4b93af 100644 --- a/js/src/views/Event/Event.vue +++ b/js/src/views/Event/Event.vue @@ -1,3 +1,6 @@ +import {ParticipantRole} from "@/types/event.model"; +import {ParticipantRole} from "@/types/event.model"; +import {ParticipantRole} from "@/types/event.model";