From 8570e14bb34308af59a13b223881426a32f220f8 Mon Sep 17 00:00:00 2001
From: Thomas Citharel
Date: Wed, 18 Sep 2019 17:32:37 +0200
Subject: [PATCH 1/5] Work on dashboard
Signed-off-by: Thomas Citharel
---
js/public/index.html | 2 +-
js/src/App.vue | 34 +--
js/src/components/Event/DateCalendarIcon.vue | 4 +-
js/src/components/Event/DateTimePicker.vue | 17 +-
js/src/components/Event/EventListCard.vue | 185 ++++++++++++++++
js/src/components/NavBar.vue | 14 +-
js/src/graphql/actor.ts | 62 ++++--
js/src/i18n/en_US.json | 17 +-
js/src/i18n/fr_FR.json | 17 +-
js/src/mixins/actor.ts | 12 ++
js/src/mixins/event.ts | 61 ++++++
js/src/router/event.ts | 8 +
js/src/types/actor/actor.model.ts | 2 +
js/src/types/current-user.model.ts | 3 +
js/src/types/event.model.ts | 15 ++
js/src/utils/auth.ts | 40 +++-
.../views/Account/children/EditIdentity.vue | 1 +
js/src/views/Event/Event.vue | 63 +-----
js/src/views/Event/MyEvents.vue | 201 ++++++++++++++++++
js/src/views/Home.vue | 159 ++++++++++----
js/src/views/User/Login.vue | 3 +-
js/src/views/User/PasswordReset.vue | 4 +-
js/src/views/User/Register.vue | 4 +-
js/src/vue-apollo.ts | 14 +-
lib/mobilizon/events/events.ex | 60 ++++++
lib/mobilizon_web/resolvers/event.ex | 10 +-
lib/mobilizon_web/resolvers/user.ex | 20 +-
lib/mobilizon_web/schema/user.ex | 10 +
lib/mobilizon_web/views/error_view.ex | 23 +-
schema.graphql | 5 +-
test/mobilizon/events/events_test.exs | 16 +-
.../resolvers/event_resolver_test.exs | 46 ++--
test/mobilizon_web/views/error_view_test.exs | 3 +-
33 files changed, 931 insertions(+), 204 deletions(-)
create mode 100644 js/src/components/Event/EventListCard.vue
create mode 100644 js/src/mixins/actor.ts
create mode 100644 js/src/mixins/event.ts
create mode 100644 js/src/views/Event/MyEvents.vue
diff --git a/js/public/index.html b/js/public/index.html
index b7f91bf39..08101498c 100644
--- a/js/public/index.html
+++ b/js/public/index.html
@@ -6,7 +6,7 @@
-
+
mobilizon
diff --git a/js/src/App.vue b/js/src/App.vue
index c9fbf8be8..81b2635fa 100644
--- a/js/src/App.vue
+++ b/js/src/App.vue
@@ -24,7 +24,7 @@ import Footer from '@/components/Footer.vue';
import Logo from '@/components/Logo.vue';
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
import { IPerson } from '@/types/actor';
-import { changeIdentity, saveActorData } from '@/utils/auth';
+import { changeIdentity, initializeCurrentActor, saveActorData } from '@/utils/auth';
@Component({
apollo: {
@@ -40,18 +40,19 @@ import { changeIdentity, saveActorData } from '@/utils/auth';
})
export default class App extends Vue {
async created() {
- await this.initializeCurrentUser();
- await this.initializeCurrentActor();
+ if (await this.initializeCurrentUser()) {
+ await initializeCurrentActor(this.$apollo.provider.defaultClient);
+ }
}
- private initializeCurrentUser() {
+ private async initializeCurrentUser() {
const userId = localStorage.getItem(AUTH_USER_ID);
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
const role = localStorage.getItem(AUTH_USER_ROLE);
if (userId && userEmail && accessToken && role) {
- return this.$apollo.mutate({
+ return await this.$apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,
variables: {
id: userId,
@@ -61,26 +62,7 @@ export default class App extends Vue {
},
});
}
- }
-
- /**
- * We fetch from localStorage the latest actor ID used,
- * then fetch the current identities to set in cache
- * the current identity used
- */
- private async initializeCurrentActor() {
- const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
-
- const result = await this.$apollo.query({
- query: IDENTITIES,
- });
- const identities = result.data.identities;
- if (identities.length < 1) return;
- const activeIdentity = identities.find(identity => identity.id === actorId) || identities[0] as IPerson;
-
- if (activeIdentity) {
- return await changeIdentity(this.$apollo.provider.defaultClient, activeIdentity);
- }
+ return false;
}
}
@@ -107,6 +89,7 @@ export default class App extends Vue {
@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";
@@ -122,6 +105,7 @@ export default class App extends Vue {
@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";
diff --git a/js/src/components/Event/DateCalendarIcon.vue b/js/src/components/Event/DateCalendarIcon.vue
index 72baa994d..62f5b5cb5 100644
--- a/js/src/components/Event/DateCalendarIcon.vue
+++ b/js/src/components/Event/DateCalendarIcon.vue
@@ -1,5 +1,5 @@
-
+
{{ month }}
{{ day }}
@@ -26,7 +26,7 @@ export default class DateCalendarIcon extends Vue {
diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue
index fb8d6b14b..c10e350d4 100644
--- a/js/src/components/NavBar.vue
+++ b/js/src/components/NavBar.vue
@@ -108,7 +108,7 @@ import { RouteName } from '@/router';
},
identities: {
query: IDENTITIES,
- update: ({ identities }) => identities.map(identity => new Person(identity)),
+ update: ({ identities }) => identities ? identities.map(identity => new Person(identity)) : [],
},
config: {
query: CONFIG,
@@ -128,12 +128,22 @@ export default class NavBar extends Vue {
config!: IConfig;
currentUser!: ICurrentUser;
ICurrentUserRole = ICurrentUserRole;
- identities!: IPerson[];
+ identities: IPerson[] = [];
showNavbar: boolean = false;
ActorRouteName = ActorRouteName;
AdminRouteName = AdminRouteName;
+ @Watch('currentActor')
+ async initializeListOfIdentities() {
+ const { data } = await this.$apollo.query<{ identities: IPerson[] }>({
+ query: IDENTITIES,
+ });
+ if (data) {
+ this.identities = data.identities.map(identity => new Person(identity));
+ }
+ }
+
// @Watch('currentUser')
// async onCurrentUserChanged() {
// // Refresh logged person object
diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts
index 2231c9d46..7dc3878a7 100644
--- a/js/src/graphql/actor.ts
+++ b/js/src/graphql/actor.ts
@@ -59,25 +59,49 @@ export const UPDATE_CURRENT_ACTOR_CLIENT = gql`
}
`;
-export const LOGGED_PERSON_WITH_GOING_TO_EVENTS = gql`
-query {
- loggedPerson {
- id,
- avatar {
- url
- },
- preferredUsername,
- goingToEvents {
- uuid,
- title,
- beginsOn,
- participants {
- actor {
- id,
- preferredUsername
- }
- }
- },
+export const LOGGED_USER_PARTICIPATIONS = gql`
+query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTime $page: Int, $limit: Int) {
+ loggedUser {
+ participations(afterDatetime: $afterDateTime, beforeDatetime: $beforeDateTime, page: $page, limit: $limit) {
+ event {
+ id,
+ uuid,
+ title,
+ picture {
+ url,
+ alt
+ },
+ beginsOn,
+ visibility,
+ organizerActor {
+ id,
+ preferredUsername,
+ name,
+ domain,
+ avatar {
+ url
+ }
+ },
+ participantStats {
+ approved,
+ unapproved
+ },
+ options {
+ maximumAttendeeCapacity
+ remainingAttendeeCapacity
+ }
+ },
+ role,
+ actor {
+ id,
+ preferredUsername,
+ name,
+ domain,
+ avatar {
+ url
+ }
+ }
+ }
}
}`;
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 6cadb7099..c502c6c94 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -65,6 +65,7 @@
"Forgot your password ?": "Forgot your password ?",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "From the {startDate} at {startTime} to the {endDate} at {endTime}",
"General information": "General information",
+ "Going as {name}": "Going as {name}",
"Group List": "Group List",
"Group full name": "Group full name",
"Group name": "Group name",
@@ -108,6 +109,7 @@
"Only accessible through link and search (private)": "Only accessible through link and search (private)",
"Opened reports": "Opened reports",
"Organized": "Organized",
+ "Organized by {name}": "Organized by {name}",
"Organizer": "Organizer",
"Other stuff…": "Other stuff…",
"Otherwise this identity will just be removed from the group administrators.": "Otherwise this identity will just be removed from the group administrators.",
@@ -115,6 +117,7 @@
"Participation approval": "Participation approval",
"Password reset": "Password reset",
"Password": "Password",
+ "Password (confirmation)": "Password (confirmation)",
"Pick an identity": "Pick an identity",
"Please be nice to each other": "Please be nice to each other",
"Please check you spam folder if you didn't receive the email.": "Please check you spam folder if you didn't receive the email.",
@@ -196,5 +199,17 @@
"meditate a bit": "meditate a bit",
"public event": "public event",
"{actor}'s avatar": "{actor}'s avatar",
- "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
+ "{count} participants": "{count} participants",
+ "{count} requests waiting": "{count} requests waiting",
+ "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
+ "You're organizing this event": "You're organizing this event",
+ "View event page": "View event page",
+ "Manage participations": "Manage participations",
+ "Upcoming": "Upcoming",
+ "{approved} / {total} seats": "{approved} / {total} seats",
+ "My events": "My events",
+ "Load more": "Load more",
+ "Past events": "Passed events",
+ "View everything": "View everything",
+ "Last week": "Last week"
}
\ No newline at end of file
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index 92db9529a..f14a42ff9 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -65,6 +65,7 @@
"Forgot your password ?": "Mot de passe oublié ?",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Du {startDate} à {startTime} au {endDate} à {endTime}",
"General information": "Information générales",
+ "Going as {name}": "En tant que {name}",
"Group List": "Liste de groupes",
"Group full name": "Nom complet du groupe",
"Group name": "Nom du groupe",
@@ -108,6 +109,7 @@
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)",
"Opened reports": "Signalements ouverts",
"Organized": "Organisés",
+ "Organized by {name}": "Organisé par {name}",
"Organizer": "Organisateur",
"Other stuff…": "Autres trucs…",
"Otherwise this identity will just be removed from the group administrators.": "Sinon cette identité sera juste supprimée des administrateurs du groupe.",
@@ -115,6 +117,7 @@
"Participation approval": "Validation des participations",
"Password reset": "Réinitialisation du mot de passe",
"Password": "Mot de passe",
+ "Password (confirmation)": "Mot de passe (confirmation)",
"Pick an identity": "Choisissez une identité",
"Please be nice to each other": "Soyez sympas entre vous",
"Please check you spam folder if you didn't receive the email.": "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email.",
@@ -196,5 +199,17 @@
"meditate a bit": "méditez un peu",
"public event": "événement public",
"{actor}'s avatar": "Avatar de {actor}",
- "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines"
+ "{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
+ "{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
+ "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
+ "You're organizing this event": "Vous organisez cet événement",
+ "View event page": "Voir la page de l'événement",
+ "Manage participations": "Gérer les participations",
+ "Upcoming": "À venir",
+ "{approved} / {total} seats": "{approved} / {total} places",
+ "My events": "Mes événements",
+ "Load more": "Voir plus",
+ "Past events": "Événements passés",
+ "View everything": "Voir tout",
+ "Last week": "La semaine dernière"
}
\ No newline at end of file
diff --git a/js/src/mixins/actor.ts b/js/src/mixins/actor.ts
new file mode 100644
index 000000000..f2d4af0c7
--- /dev/null
+++ b/js/src/mixins/actor.ts
@@ -0,0 +1,12 @@
+import { IActor } from '@/types/actor';
+import { IEvent } from '@/types/event.model';
+import { Component, Vue } from 'vue-property-decorator';
+
+@Component
+export default class ActorMixin extends Vue {
+ actorIsOrganizer(actor: IActor, event: IEvent) {
+ console.log('actorIsOrganizer actor', actor.id);
+ console.log('actorIsOrganizer event', event);
+ return event.organizerActor && actor.id === event.organizerActor.id;
+ }
+}
diff --git a/js/src/mixins/event.ts b/js/src/mixins/event.ts
new file mode 100644
index 000000000..05af58fb0
--- /dev/null
+++ b/js/src/mixins/event.ts
@@ -0,0 +1,61 @@
+import { mixins } from 'vue-class-component';
+import { Component, Vue } from 'vue-property-decorator';
+import { IEvent, IParticipant } from '@/types/event.model';
+import { DELETE_EVENT } from '@/graphql/event';
+import { RouteName } from '@/router';
+import { IPerson } from '@/types/actor';
+
+@Component
+export default class EventMixin extends mixins(Vue) {
+ async openDeleteEventModal (event: IEvent, currentActor: IPerson) {
+ const participantsLength = event.participantStats.approved;
+ const prefix = participantsLength
+ ? this.$tc('There are {participants} participants.', event.participantStats.approved, {
+ participants: event.participantStats.approved,
+ })
+ : '';
+
+ this.$buefy.dialog.prompt({
+ type: 'is-danger',
+ title: this.$t('Delete event') as string,
+ message: `${prefix}
+ ${this.$t('Are you sure you want to delete this event? This action cannot be reverted.')}
+
+ ${this.$t('To confirm, type your event title "{eventTitle}"', { eventTitle: event.title })}`,
+ confirmText: this.$t(
+ 'Delete {eventTitle}',
+ { eventTitle: event.title },
+ ) as string,
+ inputAttrs: {
+ placeholder: event.title,
+ pattern: event.title,
+ },
+ onConfirm: () => this.deleteEvent(event, currentActor),
+ });
+ }
+
+ private async deleteEvent(event: IEvent, currentActor: IPerson) {
+ const router = this.$router;
+ const eventTitle = event.title;
+
+ try {
+ await this.$apollo.mutate({
+ mutation: DELETE_EVENT,
+ variables: {
+ eventId: event.id,
+ actorId: currentActor.id,
+ },
+ });
+ this.$emit('eventDeleted', event.id);
+
+ this.$buefy.notification.open({
+ message: this.$t('Event {eventTitle} deleted', { eventTitle }) as string,
+ type: 'is-success',
+ position: 'is-bottom-right',
+ duration: 5000,
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ }
+}
diff --git a/js/src/router/event.ts b/js/src/router/event.ts
index 7b6f75741..be566bf45 100644
--- a/js/src/router/event.ts
+++ b/js/src/router/event.ts
@@ -5,11 +5,13 @@ import { RouteConfig } from 'vue-router';
// tslint:disable:space-in-parens
const editEvent = () => import(/* webpackChunkName: "create-event" */ '@/views/Event/Edit.vue');
const event = () => import(/* webpackChunkName: "event" */ '@/views/Event/Event.vue');
+const myEvents = () => import(/* webpackChunkName: "event" */ '@/views/Event/MyEvents.vue');
// tslint:enable
export enum EventRouteName {
EVENT_LIST = 'EventList',
CREATE_EVENT = 'CreateEvent',
+ MY_EVENTS = 'MyEvents',
EDIT_EVENT = 'EditEvent',
EVENT = 'Event',
LOCATION = 'Location',
@@ -28,6 +30,12 @@ export const eventRoutes: RouteConfig[] = [
component: editEvent,
meta: { requiredAuth: true },
},
+ {
+ path: '/events/me',
+ name: EventRouteName.MY_EVENTS,
+ component: myEvents,
+ meta: { requiredAuth: true },
+ },
{
path: '/events/edit/:eventId',
name: EventRouteName.EDIT_EVENT,
diff --git a/js/src/types/actor/actor.model.ts b/js/src/types/actor/actor.model.ts
index b83503bba..ac827642a 100644
--- a/js/src/types/actor/actor.model.ts
+++ b/js/src/types/actor/actor.model.ts
@@ -10,6 +10,8 @@ export interface IActor {
suspended: boolean;
avatar: IPicture | null;
banner: IPicture | null;
+
+ displayName();
}
export class Actor implements IActor {
diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts
index 0bafaac51..257dbc76a 100644
--- a/js/src/types/current-user.model.ts
+++ b/js/src/types/current-user.model.ts
@@ -1,3 +1,5 @@
+import { IParticipant } from '@/types/event.model';
+
export enum ICurrentUserRole {
USER = 'USER',
MODERATOR = 'MODERATOR',
@@ -9,4 +11,5 @@ export interface ICurrentUser {
email: string;
isLoggedIn: boolean;
role: ICurrentUserRole;
+ participations: IParticipant[];
}
diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts
index 79b3621d0..6e02490f2 100644
--- a/js/src/types/event.model.ts
+++ b/js/src/types/event.model.ts
@@ -50,6 +50,20 @@ export interface IParticipant {
event: IEvent;
}
+export class Participant implements IParticipant {
+ event!: IEvent;
+ actor!: IActor;
+ role: ParticipantRole = ParticipantRole.NOT_APPROVED;
+
+ constructor(hash?: IParticipant) {
+ if (!hash) return;
+
+ this.event = new EventModel(hash.event);
+ this.actor = new Actor(hash.actor);
+ this.role = hash.role;
+ }
+}
+
export interface IOffer {
price: number;
priceCurrency: string;
@@ -203,6 +217,7 @@ export class EventModel implements IEvent {
this.onlineAddress = hash.onlineAddress;
this.phoneAddress = hash.phoneAddress;
this.physicalAddress = hash.physicalAddress;
+ this.participantStats = hash.participantStats;
this.tags = hash.tags;
if (hash.options) this.options = hash.options;
diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts
index 146b6bc80..75f01c598 100644
--- a/js/src/utils/auth.ts
+++ b/js/src/utils/auth.ts
@@ -12,7 +12,7 @@ import { onLogout } from '@/vue-apollo';
import ApolloClient from 'apollo-client';
import { ICurrentUserRole } from '@/types/current-user.model';
import { IPerson } from '@/types/actor';
-import { UPDATE_CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
+import { IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from '@/graphql/actor';
export function saveUserData(obj: ILogin) {
localStorage.setItem(AUTH_USER_ID, `${obj.user.id}`);
@@ -32,11 +32,31 @@ export function saveTokenData(obj: IToken) {
}
export function deleteUserData() {
- for (const key of [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE, AUTH_USER_ACTOR_ID]) {
+ for (const key of [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE]) {
localStorage.removeItem(key);
}
}
+/**
+ * We fetch from localStorage the latest actor ID used,
+ * then fetch the current identities to set in cache
+ * the current identity used
+ */
+export async function initializeCurrentActor(apollo: ApolloClient) {
+ const actorId = localStorage.getItem(AUTH_USER_ACTOR_ID);
+
+ const result = await apollo.query({
+ query: IDENTITIES,
+ });
+ const identities = result.data.identities;
+ if (identities.length < 1) return;
+ const activeIdentity = identities.find(identity => identity.id === actorId) || identities[0] as IPerson;
+
+ if (activeIdentity) {
+ return await changeIdentity(apollo, activeIdentity);
+ }
+}
+
export async function changeIdentity(apollo: ApolloClient, identity: IPerson) {
await apollo.mutate({
mutation: UPDATE_CURRENT_ACTOR_CLIENT,
@@ -45,8 +65,8 @@ export async function changeIdentity(apollo: ApolloClient, identity: IPerso
saveActorData(identity);
}
-export function logout(apollo: ApolloClient) {
- apollo.mutate({
+export async function logout(apollo: ApolloClient) {
+ await apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,
variables: {
id: null,
@@ -56,7 +76,17 @@ export function logout(apollo: ApolloClient) {
},
});
+ await apollo.mutate({
+ mutation: UPDATE_CURRENT_ACTOR_CLIENT,
+ variables: {
+ id: null,
+ avatar: null,
+ preferredUsername: null,
+ name: null,
+ },
+ });
+
deleteUserData();
- onLogout();
+ await onLogout();
}
diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue
index 0df8bc988..f541e83ac 100644
--- a/js/src/views/Account/children/EditIdentity.vue
+++ b/js/src/views/Account/children/EditIdentity.vue
@@ -30,6 +30,7 @@
has-icon
aria-close-label="Close notification"
role="alert"
+ :key="error"
v-for="error in errors"
>
{{ error }}
diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue
index 4c36f0251..ef4d95205 100644
--- a/js/src/views/Event/Event.vue
+++ b/js/src/views/Event/Event.vue
@@ -69,7 +69,7 @@
-
+
{{ $t('Delete') }}
@@ -111,7 +111,7 @@
+ :alt="event.organizerActor.avatar.alt" />
@@ -262,6 +262,7 @@ import ReportModal from '@/components/Report/ReportModal.vue';
import ParticipationModal from '@/components/Event/ParticipationModal.vue';
import { IReport } from '@/types/report.model';
import { CREATE_REPORT } from '@/graphql/report';
+import EventMixin from '@/mixins/event';
@Component({
components: {
@@ -290,7 +291,7 @@ import { CREATE_REPORT } from '@/graphql/report';
},
},
})
-export default class Event extends Vue {
+export default class Event extends EventMixin {
@Prop({ type: String, required: true }) uuid!: string;
event!: IEvent;
@@ -302,31 +303,12 @@ export default class Event extends Vue {
EventVisibility = EventVisibility;
- async openDeleteEventModal () {
- const participantsLength = this.event.participants.length;
- const prefix = participantsLength
- ? this.$tc('There are {participants} participants.', this.event.participants.length, {
- participants: this.event.participants.length,
- })
- : '';
-
- this.$buefy.dialog.prompt({
- type: 'is-danger',
- title: this.$t('Delete event') as string,
- message: `${prefix}
- ${this.$t('Are you sure you want to delete this event? This action cannot be reverted.')}
-
- ${this.$t('To confirm, type your event title "{eventTitle}"', { eventTitle: this.event.title })}`,
- confirmText: this.$t(
- 'Delete {eventTitle}',
- { eventTitle: this.event.title },
- ) as string,
- inputAttrs: {
- placeholder: this.event.title,
- pattern: this.event.title,
- },
- onConfirm: () => this.deleteEvent(),
- });
+ /**
+ * Delete the event, then redirect to home.
+ */
+ async openDeleteEventModalWrapper() {
+ await this.openDeleteEventModal(this.event, this.currentActor);
+ await this.$router.push({ name: RouteName.HOME });
}
async reportEvent(content: string, forward: boolean) {
@@ -464,31 +446,6 @@ export default class Event extends Vue {
return `mailto:?to=&body=${this.event.url}${encodeURIComponent('\n\n')}${this.event.description}&subject=${this.event.title}`;
}
- private async deleteEvent() {
- const router = this.$router;
- const eventTitle = this.event.title;
-
- try {
- await this.$apollo.mutate({
- mutation: DELETE_EVENT,
- variables: {
- eventId: this.event.id,
- actorId: this.currentActor.id,
- },
- });
-
- await router.push({ name: RouteName.HOME });
- this.$buefy.notification.open({
- message: this.$t('Event {eventTitle} deleted', { eventTitle }) as string,
- type: 'is-success',
- position: 'is-bottom-right',
- duration: 5000,
- });
- } catch (error) {
- console.error(error);
- }
- }
-
}
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index ac9a8db84..e38b06ad3 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -1,8 +1,8 @@
-
+
-
+
{{ config.name }}
{{ config.description }}
@@ -16,7 +16,7 @@
- {{ $t('Welcome back {username}', {username: loggedPerson.preferredUsername}) }}
+ {{ $t('Welcome back {username}', {username: `@${currentActor.preferredUsername}`}) }}
@@ -24,7 +24,7 @@
{{ $t('Create') }}
-
+.organizerActor.id
{{ $t('Event') }}
@@ -32,14 +32,14 @@
{{ $t('Group') }}
-
-
- {{ $t("Events you're going at") }}
-
+
+
+ {{ $t("Upcoming") }}
+
-
-
-
+
+
+
{{ $tc('You have one event today.', row[1].length, {count: row[1].length}) }}
@@ -49,24 +49,42 @@
{{ $tc('You have one event tomorrow.', row[1].length, {count: row[1].length}) }}
+ v-else-if="isInLessThanSevenDays(row[0])">
{{ $tc('You have one event in {days} days.', row[1].length, {count: row[1].length, days: calculateDiffDays(row[0])}) }}
-
{{ $t("You're not going to any event yet") }}
+
+ {{ $t('View everything')}} >>
+
-
+
+
+ {{ $t("Last week") }}
+
+
+
+
+
+
+
{{ $t('Events nearby you') }}
@@ -87,16 +105,18 @@
import ngeohash from 'ngeohash';
import { FETCH_EVENTS } from '@/graphql/event';
import { Component, Vue } from 'vue-property-decorator';
+import EventListCard from '@/components/Event/EventListCard.vue';
import EventCard from '@/components/Event/EventCard.vue';
-import { LOGGED_PERSON_WITH_GOING_TO_EVENTS } from '@/graphql/actor';
+import { CURRENT_ACTOR_CLIENT, LOGGED_USER_PARTICIPATIONS } from '@/graphql/actor';
import { IPerson, Person } from '@/types/actor';
import { ICurrentUser } from '@/types/current-user.model';
import { CURRENT_USER_CLIENT } from '@/graphql/user';
import { RouteName } from '@/router';
-import { IEvent } from '@/types/event.model';
+import { EventModel, IEvent, IParticipant, Participant } from '@/types/event.model';
import DateComponent from '@/components/Event/DateCalendarIcon.vue';
import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model';
+import { EventRouteName } from '@/router/event';
@Component({
apollo: {
@@ -104,8 +124,8 @@ import { IConfig } from '@/types/config.model';
query: FETCH_EVENTS,
fetchPolicy: 'no-cache', // Debug me: https://github.com/apollographql/apollo-client/issues/3030
},
- loggedPerson: {
- query: LOGGED_PERSON_WITH_GOING_TO_EVENTS,
+ currentActor: {
+ query: CURRENT_ACTOR_CLIENT,
},
currentUser: {
query: CURRENT_USER_CLIENT,
@@ -116,6 +136,7 @@ import { IConfig } from '@/types/config.model';
},
components: {
DateComponent,
+ EventListCard,
EventCard,
},
})
@@ -124,10 +145,12 @@ export default class Home extends Vue {
locations = [];
city = { name: null };
country = { name: null };
- loggedPerson: IPerson = new Person();
+ currentUserParticipations: IParticipant[] = [];
currentUser!: ICurrentUser;
+ currentActor!: IPerson;
config: IConfig = { description: '', name: '', registrationsOpen: false };
RouteName = RouteName;
+ EventRouteName = EventRouteName;
// get displayed_name() {
// return this.loggedPerson && this.loggedPerson.name === null
@@ -135,7 +158,23 @@ export default class Home extends Vue {
// : this.loggedPerson.name;
// }
- isToday(date: string) {
+ async mounted() {
+ const lastWeek = new Date();
+ lastWeek.setDate(new Date().getDate() - 7);
+
+ const { data } = await this.$apollo.query({
+ query: LOGGED_USER_PARTICIPATIONS,
+ variables: {
+ afterDateTime: lastWeek.toISOString(),
+ },
+ });
+
+ if (data) {
+ this.currentUserParticipations = data.loggedUser.participations.map(participation => new Participant(participation));
+ }
+ }
+
+ isToday(date: Date) {
return (new Date(date)).toDateString() === (new Date()).toDateString();
}
@@ -148,35 +187,43 @@ export default class Home extends Vue {
}
isBefore(date: string, nbDays: number) :boolean {
- return this.calculateDiffDays(date) > nbDays;
+ return this.calculateDiffDays(date) < nbDays;
}
- // FIXME: Use me
isInLessThanSevenDays(date: string): boolean {
- return this.isInDays(date, 7);
+ return this.isBefore(date, 7);
}
calculateDiffDays(date: string): number {
- const dateObj = new Date(date);
- return Math.ceil((dateObj.getTime() - (new Date()).getTime()) / 1000 / 60 / 60 / 24);
+ return Math.ceil(((new Date(date)).getTime() - (new Date()).getTime()) / 1000 / 60 / 60 / 24);
}
- get goingToEvents(): Map
{
- const res = this.$data.loggedPerson.goingToEvents.filter((event) => {
- return event.beginsOn != null && this.isBefore(event.beginsOn, 0);
+ get goingToEvents(): Map> {
+ const res = this.currentUserParticipations.filter(({ event }) => {
+ return event.beginsOn != null && !this.isBefore(event.beginsOn.toDateString(), 0);
});
res.sort(
- (a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn),
+ (a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
);
- return res.reduce((acc: Map, event: IEvent) => {
- const day = (new Date(event.beginsOn)).toDateString();
- const events: IEvent[] = acc.get(day) || [];
- events.push(event);
- acc.set(day, events);
+ return res.reduce((acc: Map>, participation: IParticipant) => {
+ const day = (new Date(participation.event.beginsOn)).toDateString();
+ const participations: Map = acc.get(day) || new Map();
+ participations.set(participation.event.uuid, participation);
+ acc.set(day, participations);
return acc;
}, new Map());
}
+ get lastWeekEvents() {
+ const res = this.currentUserParticipations.filter(({ event }) => {
+ return event.beginsOn != null && this.isBefore(event.beginsOn.toDateString(), 0);
+ });
+ res.sort(
+ (a: IParticipant, b: IParticipant) => a.event.beginsOn.getTime() - b.event.beginsOn.getTime(),
+ );
+ return res;
+ }
+
geoLocalize() {
const router = this.$router;
const sessionCity = sessionStorage.getItem('City');
@@ -226,7 +273,7 @@ export default class Home extends Vue {
-
diff --git a/js/src/views/User/Login.vue b/js/src/views/User/Login.vue
index a062c6d72..3b1cbadee 100644
--- a/js/src/views/User/Login.vue
+++ b/js/src/views/User/Login.vue
@@ -65,7 +65,7 @@
import { Component, Prop, Vue } from 'vue-property-decorator';
import { LOGIN } from '@/graphql/auth';
import { validateEmailField, validateRequiredField } from '@/utils/validators';
-import { saveUserData } from '@/utils/auth';
+import { initializeCurrentActor, saveUserData } from '@/utils/auth';
import { ILogin } from '@/types/login.model';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogin } from '@/vue-apollo';
@@ -146,6 +146,7 @@ export default class Login extends Vue {
role: data.login.user.role,
},
});
+ await initializeCurrentActor(this.$apollo.provider.defaultClient);
onLogin(this.$apollo);
diff --git a/js/src/views/User/PasswordReset.vue b/js/src/views/User/PasswordReset.vue
index f3f61d1e2..6ab6a4c78 100644
--- a/js/src/views/User/PasswordReset.vue
+++ b/js/src/views/User/PasswordReset.vue
@@ -6,7 +6,7 @@
{{ error }}
@@ -50,9 +51,9 @@
{{ $t('Delete') }}
-
+
{{ $t('Manage participations') }}
-
+
{{ $t('View event page') }}
diff --git a/js/src/components/Event/ParticipationModal.vue b/js/src/components/Event/ParticipationModal.vue
index 4175918ed..0ad1dda27 100644
--- a/js/src/components/Event/ParticipationModal.vue
+++ b/js/src/components/Event/ParticipationModal.vue
@@ -1,7 +1,7 @@
- Join event {{ event.title }}
+ {{ $t('Join event {title}', {title: event.title}) }}
@@ -14,14 +14,18 @@
size="is-large"/>
-
Do you want to participate in {{ event.title }}?
+
{{ $t('Do you want to participate in {title}?', {title: event.title}) }}?
+
+ {{ $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')}}
+
+
- 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.') }}
@@ -32,13 +36,13 @@
class="button"
ref="cancelButton"
@click="close">
- Cancel
+ {{ $t('Cancel') }}
- Confirm my particpation
+ {{ $t('Confirm my particpation') }}
@@ -46,7 +50,7 @@
+
+
+
diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue
index d3d2af6b0..dc86ef296 100644
--- a/js/src/views/Group/Group.vue
+++ b/js/src/views/Group/Group.vue
@@ -107,7 +107,7 @@ export default class Group extends Vue {
}
}
-
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index 5e10d1595..880ba76c0 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -24,7 +24,6 @@
{{ $t('Create') }}
-.organizerActor.id
{{ $t('Event') }}
diff --git a/js/yarn.lock b/js/yarn.lock
index afb21515f..5a3bff89e 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -2193,9 +2193,9 @@ browserslist@^4.0.0, browserslist@^4.3.4, browserslist@^4.5.4, browserslist@^4.6
node-releases "^1.1.29"
buefy@^0.8.2:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.8.2.tgz#26bfc931c8c7fbe5a90d4b814a8205501eee816a"
- integrity sha512-fS4sXYE0ge7fN5tP9k67j1fSCS/yxbTrnEhJ5MBt89gcbmVe5x8/SAXdADjx5W4SdERtjKjE9mzoIoRb+ZC29Q==
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.8.4.tgz#0c62d559e63aee8a18876ff90056f9a8b90f686f"
+ integrity sha512-hDUUKbKxQmtYlo/IPH9H+ewEN6KulpDxfNFIPvO5z3ukYqEG29psW6oFbJGisZDEIYGxqE2jMPcBOOjm8LxJVQ==
dependencies:
bulma "0.7.5"
From 4d9f3c724788d6d410efd3583d9f6da1a13ad3c5 Mon Sep 17 00:00:00 2001
From: Thomas Citharel
Date: Fri, 20 Sep 2019 19:43:29 +0200
Subject: [PATCH 4/5] Couple of fixes, and introducing Explore section
Signed-off-by: Thomas Citharel
---
js/src/App.vue | 2 +-
js/src/components/Event/EventCard.vue | 6 +-
js/src/components/Map.vue | 4 +-
js/src/components/NavBar.vue | 4 +-
js/src/graphql/event.ts | 21 +++---
js/src/i18n/en_US.json | 77 ++++++++++----------
js/src/i18n/fr_FR.json | 76 +++++++++----------
js/src/router/event.ts | 8 ++
js/src/types/actor/actor.model.ts | 2 -
js/src/views/Event/Edit.vue | 49 +++----------
js/src/views/Event/Event.vue | 11 ++-
js/src/views/Event/Explore.vue | 42 +++++++++++
js/src/views/Event/MyEvents.vue | 4 +-
lib/mobilizon_web/api/utils.ex | 10 ++-
lib/service/activity_pub/converters/event.ex | 12 +++
15 files changed, 185 insertions(+), 143 deletions(-)
create mode 100644 js/src/views/Event/Explore.vue
diff --git a/js/src/App.vue b/js/src/App.vue
index 6ee556880..e165fdbd5 100644
--- a/js/src/App.vue
+++ b/js/src/App.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/js/src/components/Event/EventCard.vue b/js/src/components/Event/EventCard.vue
index 4122af60d..d1771e1b1 100644
--- a/js/src/components/Event/EventCard.vue
+++ b/js/src/components/Event/EventCard.vue
@@ -16,8 +16,10 @@
{{ event.title }}
- {{ event.physicalAddress.locality }} -
- {{ actorDisplayName }}
+ {{ $t('By {name}', { name: actorDisplayName }) }}
+
+ - {{ event.physicalAddress.locality || event.physicalAddress.description }}
+
diff --git a/js/src/components/Map.vue b/js/src/components/Map.vue
index 0a8bedda5..d1c11d57e 100644
--- a/js/src/components/Map.vue
+++ b/js/src/components/Map.vue
@@ -55,8 +55,8 @@ export default class Map extends Vue {
return { ...this.defaultOptions, ...this.options };
}
- get lat() { return this.$props.coords.split(';')[0]; }
- get lon() { return this.$props.coords.split(';')[1]; }
+ get lat() { return this.$props.coords.split(';')[1]; }
+ get lon() { return this.$props.coords.split(';')[0]; }
}
diff --git a/js/src/views/Event/MyEvents.vue b/js/src/views/Event/MyEvents.vue
index 6b5fcbe73..b06f4f244 100644
--- a/js/src/views/Event/MyEvents.vue
+++ b/js/src/views/Event/MyEvents.vue
@@ -1,5 +1,5 @@
-
+
{{ $t('My events') }}
@@ -87,8 +87,6 @@ import EventListCard from '@/components/Event/EventListCard.vue';
},
})
export default class MyEvents extends Vue {
- @Prop(String) location!: string;
-
futurePage: number = 1;
pastPage: number = 1;
limit: number = 10;
diff --git a/lib/mobilizon_web/api/utils.ex b/lib/mobilizon_web/api/utils.ex
index b37d49610..a6b55b25d 100644
--- a/lib/mobilizon_web/api/utils.ex
+++ b/lib/mobilizon_web/api/utils.ex
@@ -7,6 +7,8 @@ defmodule MobilizonWeb.API.Utils do
alias Mobilizon.Config
alias Mobilizon.Service.Formatter
+ @ap_public "https://www.w3.org/ns/activitystreams#Public"
+
@doc """
Determines the full audience based on mentions for a public audience
@@ -16,7 +18,7 @@ defmodule MobilizonWeb.API.Utils do
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :public) do
- to = ["https://www.w3.org/ns/activitystreams#Public" | mentions]
+ to = [@ap_public | mentions]
cc = [actor.followers_url]
if inReplyTo do
@@ -36,7 +38,7 @@ defmodule MobilizonWeb.API.Utils do
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, inReplyTo, :unlisted) do
to = [actor.followers_url | mentions]
- cc = ["https://www.w3.org/ns/activitystreams#Public"]
+ cc = [@ap_public]
if inReplyTo do
{Enum.uniq([inReplyTo.actor | to]), cc}
@@ -49,7 +51,7 @@ defmodule MobilizonWeb.API.Utils do
Determines the full audience based on mentions based on a private audience
Audience is:
- * `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
+ * `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
@@ -62,7 +64,7 @@ defmodule MobilizonWeb.API.Utils do
Determines the full audience based on mentions based on a direct audience
Audience is:
- * `to` : the mentionned actors and the eventual actor we're replying to
+ * `to` : the mentioned actors and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
diff --git a/lib/service/activity_pub/converters/event.ex b/lib/service/activity_pub/converters/event.ex
index fb76bfe4e..ae19c2831 100644
--- a/lib/service/activity_pub/converters/event.ex
+++ b/lib/service/activity_pub/converters/event.ex
@@ -35,6 +35,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
{:address, address_id} <-
{:address, get_address(object["location"])},
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
+ {:visibility, visibility} <- {:visibility, get_visibility(object)},
{:options, options} <- {:options, get_options(object)} do
picture_id =
with true <- Map.has_key?(object, "attachment") && length(object["attachment"]) > 0,
@@ -59,6 +60,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
"begins_on" => object["startTime"],
"ends_on" => object["endTime"],
"category" => object["category"],
+ "visibility" => visibility,
"join_options" => object["joinOptions"],
"url" => object["id"],
"uuid" => object["uuid"],
@@ -148,6 +150,16 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
end)
end
+ @ap_public "https://www.w3.org/ns/activitystreams#Public"
+
+ defp get_visibility(object) do
+ cond do
+ @ap_public in object["to"] -> :public
+ @ap_public in object["cc"] -> :unlisted
+ true -> :private
+ end
+ end
+
@doc """
Convert an event struct to an ActivityStream representation
"""
From d80237f84fca30ffe1c7b3c0d7d2cb7a9667c5c1 Mon Sep 17 00:00:00 2001
From: Thomas Citharel
Date: Sun, 22 Sep 2019 11:22:16 +0200
Subject: [PATCH 5/5] Fixes
Signed-off-by: Thomas Citharel
---
lib/mobilizon/events/events.ex | 7 ++++++-
lib/mobilizon_web/resolvers/event.ex | 8 ++++----
lib/mobilizon_web/schema/event.ex | 6 +++---
lib/service/activity_pub/activity_pub.ex | 3 ++-
lib/service/activity_pub/converters/event.ex | 1 +
lib/service/activity_pub/transmogrifier.ex | 3 ++-
6 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index 7eb44363f..c6c47b72a 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -592,7 +592,12 @@ defmodule Mobilizon.Events do
"""
@spec list_participants_for_event(String.t(), list(atom()), integer | nil, integer | nil) ::
[Participant.t()]
- def list_participants_for_event(uuid, roles \\ @default_participant_roles, page, limit) do
+ def list_participants_for_event(
+ uuid,
+ roles \\ @default_participant_roles,
+ page \\ nil,
+ limit \\ nil
+ ) do
uuid
|> list_participants_for_event_query()
|> filter_role(roles)
diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex
index b1b14341b..96783f3c2 100644
--- a/lib/mobilizon_web/resolvers/event.ex
+++ b/lib/mobilizon_web/resolvers/event.ex
@@ -201,7 +201,7 @@ defmodule MobilizonWeb.Resolvers.Event do
}
) do
# Check that moderator provided is rightly authenticated
- with {:is_owned, true, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
+ with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation already exists
{:has_participation, %Participant{role: :not_approved} = participation} <-
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
@@ -213,7 +213,7 @@ defmodule MobilizonWeb.Resolvers.Event do
MobilizonWeb.API.Participations.accept(participation, moderator_actor) do
{:ok, participation}
else
- {:is_owned, false} ->
+ {:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
{:has_participation, %Participant{role: role, id: id}} ->
@@ -238,7 +238,7 @@ defmodule MobilizonWeb.Resolvers.Event do
}
) do
# Check that moderator provided is rightly authenticated
- with {:is_owned, true, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
+ with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
# Check that participation really exists
{:has_participation, %Participant{} = participation} <-
{:has_participation, Mobilizon.Events.get_participant(participation_id)},
@@ -261,7 +261,7 @@ defmodule MobilizonWeb.Resolvers.Event do
}
}
else
- {:is_owned, false} ->
+ {:is_owned, nil} ->
{:error, "Moderator Actor ID is not owned by authenticated user"}
{:actor_approve_permission, _} ->
diff --git a/lib/mobilizon_web/schema/event.ex b/lib/mobilizon_web/schema/event.ex
index 2ccd44405..5584a9d35 100644
--- a/lib/mobilizon_web/schema/event.ex
+++ b/lib/mobilizon_web/schema/event.ex
@@ -225,7 +225,7 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:begins_on, non_null(:datetime))
arg(:ends_on, :datetime)
arg(:status, :event_status)
- arg(:visibility, :event_visibility, default_value: :private)
+ arg(:visibility, :event_visibility, default_value: :public)
arg(:join_options, :event_join_options, default_value: :free)
arg(:tags, list_of(:string),
@@ -258,8 +258,8 @@ defmodule MobilizonWeb.Schema.EventType do
arg(:begins_on, :datetime)
arg(:ends_on, :datetime)
arg(:status, :event_status)
- arg(:visibility, :event_visibility)
- arg(:join_options, :event_join_options)
+ arg(:visibility, :event_visibility, default_value: :public)
+ arg(:join_options, :event_join_options, default_value: :free)
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
index 5dab5b12e..604a60165 100644
--- a/lib/service/activity_pub/activity_pub.ex
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -25,7 +25,8 @@ defmodule Mobilizon.Service.ActivityPub do
alias Mobilizon.Service.ActivityPub.{Activity, Convertible}
require Logger
- import Mobilizon.Service.ActivityPub.{Utils, Visibility}
+ import Mobilizon.Service.ActivityPub.Utils
+ import Mobilizon.Service.ActivityPub.Visibility
@doc """
Get recipients for an activity or object
diff --git a/lib/service/activity_pub/converters/event.ex b/lib/service/activity_pub/converters/event.ex
index ae19c2831..bba57d009 100644
--- a/lib/service/activity_pub/converters/event.ex
+++ b/lib/service/activity_pub/converters/event.ex
@@ -186,6 +186,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Event do
"mediaType" => "text/html",
"startTime" => event.begins_on |> date_to_string(),
"endTime" => event.ends_on |> date_to_string(),
+ "joinOptions" => to_string(event.join_options),
"tag" => event.tags |> build_tags(),
"id" => event.url,
"url" => event.url
diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex
index ef34e1477..fedd980ee 100644
--- a/lib/service/activity_pub/transmogrifier.ex
+++ b/lib/service/activity_pub/transmogrifier.ex
@@ -315,8 +315,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => actor} =
_update
) do
- with {:ok, %{"actor" => existing_organizer_actor_url} = _existing_event_data} <-
+ with {:ok, %{"actor" => existing_organizer_actor_url} = existing_event_data} <-
fetch_obj_helper_as_activity_streams(object),
+ object <- Map.merge(existing_event_data, object),
{:ok, %Actor{url: actor_url}} <- actor |> Utils.get_url() |> Actors.get_actor_by_url(),
true <- Utils.get_url(existing_organizer_actor_url) == actor_url do
ActivityPub.update(%{