From 757d2cabec8625ff472f3181f9a9137633b6da1e 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 e165fdbd..38e2d4c5 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 2a0b91f4..40d2d070 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 0ad1dda2..00000000 --- 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 e9bccbda..6d53b896 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 45d66a81..2f42b26a 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 b1093854..96b018aa 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 3a3abcee..f346b68a 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 89a182ab..126b8a77 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 00000000..028c2980 --- /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 b1145407..07236d15 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 3ed81b53..fd5651c7 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 34bb6fd2..a1f4b93a 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";