diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts index a3be313ff..b5989eeb0 100644 --- a/js/src/graphql/event.ts +++ b/js/src/graphql/event.ts @@ -1,134 +1,157 @@ import gql from 'graphql-tag'; +const participantQuery = ` + role, + actor { + preferredUsername, + avatarUrl, + name + } +`; + export const FETCH_EVENT = gql` - query($uuid:UUID!) { - event(uuid: $uuid) { - id, - uuid, - url, - local, - title, - description, - beginsOn, - endsOn, - status, - visibility, - thumbnail, - large_image, - publish_at, - # online_address, - # phone_address, - organizerActor { - avatarUrl, - preferredUsername, - name, - }, - # attributedTo { - # # avatarUrl, - # preferredUsername, - # name, - # }, - participants { - actor { - avatarUrl, - preferredUsername, - name, - }, - role, - }, - category { - title, - }, - } - } + query($uuid:UUID!) { + event(uuid: $uuid) { + id, + uuid, + url, + local, + title, + description, + beginsOn, + endsOn, + status, + visibility, + thumbnail, + large_image, + publish_at, + # online_address, + # phone_address, + organizerActor { + avatarUrl, + preferredUsername, + name, + }, + # attributedTo { + # # avatarUrl, + # preferredUsername, + # name, + # }, + participants { + ${participantQuery} + }, + category { + title, + }, + } + } `; export const FETCH_EVENTS = gql` - query { - events { - id, - uuid, - url, - local, + query { + events { + id, + uuid, + url, + local, + title, + description, + beginsOn, + endsOn, + status, + visibility, + thumbnail, + large_image, + publish_at, + # online_address, + # phone_address, + organizerActor { + avatarUrl, + preferredUsername, + name, + }, + attributedTo { + avatarUrl, + preferredUsername, + name, + }, + category { title, - description, - beginsOn, - endsOn, - status, - visibility, - thumbnail, - large_image, - publish_at, - # online_address, - # phone_address, - organizerActor { - avatarUrl, - preferredUsername, - name, - }, - attributedTo { - avatarUrl, - preferredUsername, - name, - }, - category { - title, - }, - participants { - role, - actor { - preferredUsername, - avatarUrl, - name - } - } + }, + participants { + ${participantQuery} + } } -} + } `; export const CREATE_EVENT = gql` - mutation CreateEvent( - $title: String!, - $description: String!, - $organizerActorId: String!, - $category: String!, - $beginsOn: DateTime! + mutation CreateEvent( + $title: String!, + $description: String!, + $organizerActorId: String!, + $category: String!, + $beginsOn: DateTime! + ) { + createEvent( + title: $title, + description: $description, + beginsOn: $beginsOn, + organizerActorId: $organizerActorId, + category: $category ) { - createEvent( - title: $title, - description: $description, - beginsOn: $beginsOn, - organizerActorId: $organizerActorId, - category: $category - ) { - id, - uuid, - title - } -} + id, + uuid, + title + } + } `; export const EDIT_EVENT = gql` - mutation EditEvent( - $title: String!, - $description: String!, - $organizerActorId: Int!, - $categoryId: Int! - ) { + mutation EditEvent( + $title: String!, + $description: String!, + $organizerActorId: Int!, + $categoryId: Int! + ) { EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) { - uuid + uuid } -} + } `; export const JOIN_EVENT = gql` - mutation JoinEvent( - $uuid: String!, - $username: String! + mutation JoinEvent($id: Int!, $actorId: Int!) { + joinEvent( + id: $id, + actorId: $actorId ) { - joinEvent( - uuid: $uuid, - username: $username - ) -} + actor { + ${participantQuery} + }, + role + } + } +`; + +export const LEAVE_EVENT = gql` + mutation LeaveEvent($id: Int!, $actorId: Int!) { + leaveEvent( + id: $id, + actorId: $actorId + ) { + actor { + id + } + } + } +`; + +export const DELETE_EVENT = gql` + mutation DeleteEvent($id: Int!, $actorId: Int!) { + deleteEvent( + id: $id, + actorId: $actorId + ) + } `; diff --git a/js/src/router/user.ts b/js/src/router/user.ts index 811723730..24bb0b98e 100644 --- a/js/src/router/user.ts +++ b/js/src/router/user.ts @@ -7,54 +7,54 @@ import SendPasswordReset from '@/views/User/SendPasswordReset.vue'; import PasswordReset from '@/views/User/PasswordReset.vue'; export default [ - { - path: '/register/user', - name: 'Register', - component: RegisterUser, - props: true, - meta: { requiredAuth: false }, - }, - { - path: '/register/profile', - name: 'RegisterProfile', - component: RegisterProfile, - props: true, - meta: { requiredAuth: false }, - }, - { - path: '/resend-instructions', - name: 'ResendConfirmation', - component: ResendConfirmation, - props: true, - meta: { requiresAuth: false }, - }, - { - path: '/password-reset/send', - name: 'SendPasswordReset', - component: SendPasswordReset, - props: true, - meta: { requiresAuth: false }, - }, - { - path: '/password-reset/:token', - name: 'PasswordReset', - component: PasswordReset, - meta: { requiresAuth: false }, - props: true, - }, - { - path: '/validate/:token', - name: 'Validate', - component: Validate, - // We can only pass string values through params, therefore - props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true'}), - meta: { requiresAuth: false }, - }, - { - path: '/login', - name: 'Login', - component: Login, - props: true, - meta: { requiredAuth: false }, - }, -]; \ No newline at end of file + { + path: '/register/user', + name: 'Register', + component: RegisterUser, + props: true, + meta: { requiredAuth: false }, + }, + { + path: '/register/profile', + name: 'RegisterProfile', + component: RegisterProfile, + props: true, + meta: { requiredAuth: false }, + }, + { + path: '/resend-instructions', + name: 'ResendConfirmation', + component: ResendConfirmation, + props: true, + meta: { requiresAuth: false }, + }, + { + path: '/password-reset/send', + name: 'SendPasswordReset', + component: SendPasswordReset, + props: true, + meta: { requiresAuth: false }, + }, + { + path: '/password-reset/:token', + name: 'PasswordReset', + component: PasswordReset, + meta: { requiresAuth: false }, + props: true, + }, + { + path: '/validate/:token', + name: 'Validate', + component: Validate, + // We can only pass string values through params, therefore + props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true'}), + meta: { requiresAuth: false }, + }, + { + path: '/login', + name: 'Login', + component: Login, + props: true, + meta: { requiredAuth: false }, + }, +]; diff --git a/js/src/types/actor.model.ts b/js/src/types/actor.model.ts index a5803c0b2..215df38cb 100644 --- a/js/src/types/actor.model.ts +++ b/js/src/types/actor.model.ts @@ -1,13 +1,13 @@ export interface IActor { - id: string; - url: string; - name: string; - domain: string|null; - summary: string; - preferredUsername: string; - suspended: boolean; - avatarUrl: string; - bannerUrl: string; + id: string; + url: string; + name: string; + domain: string|null; + summary: string; + preferredUsername: string; + suspended: boolean; + avatarUrl: string; + bannerUrl: string; } export interface IPerson extends IActor { @@ -15,15 +15,18 @@ export interface IPerson extends IActor { } export interface IGroup extends IActor { - members: IMember[]; + members: IMember[]; } export enum MemberRole { - PENDING, MEMBER, MODERATOR, ADMIN + PENDING, + MEMBER, + MODERATOR, + ADMIN, } export interface IMember { - role: MemberRole; - parent: IGroup; - actor: IActor; -} \ No newline at end of file + role: MemberRole; + parent: IGroup; + actor: IActor; +} diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts index e985748f4..48c713d13 100644 --- a/js/src/types/event.model.ts +++ b/js/src/types/event.model.ts @@ -1,62 +1,70 @@ -import { IActor } from "./actor.model"; +import { IActor } from './actor.model'; export enum EventStatus { - TENTATIVE, - CONFIRMED, - CANCELLED + TENTATIVE, + CONFIRMED, + CANCELLED, } export enum EventVisibility { - PUBLIC, - UNLISTED, - RESTRICTED, - PRIVATE + PUBLIC, + UNLISTED, + RESTRICTED, + PRIVATE, } export enum EventJoinOptions { - FREE, - RESTRICTED, - INVITE + FREE, + RESTRICTED, + INVITE, } export enum ParticipantRole { - NOT_APPROVED = 'not_approved', - PARTICIPANT = 'participant', - MODERATOR = 'moderator', - ADMINSTRATOR = 'administrator', - CREATOR = 'creator' + NOT_APPROVED = 'not_approved', + PARTICIPANT = 'participant', + MODERATOR = 'moderator', + ADMINISTRATOR = 'administrator', + CREATOR = 'creator', } export interface ICategory { - title: string; - description: string; - picture: string; + title: string; + description: string; + picture: string; } export interface IParticipant { - role: ParticipantRole, - actor: IActor, - event: IEvent + role: ParticipantRole; + actor: IActor; + event: IEvent; } export interface IEvent { - uuid: string; - url: string; - local: boolean; - title: string; - description: string; - begins_on: Date; - ends_on: Date; - status: EventStatus; - visibility: EventVisibility; - join_options: EventJoinOptions; - thumbnail: string; - large_image: string; - publish_at: Date; - // online_address: Adress; - // phone_address: string; - organizerActor: IActor; - attributedTo: IActor; - participants: IParticipant[]; - category: ICategory; -} \ No newline at end of file + id?: number; + uuid: string; + url: string; + local: boolean; + + title: string; + description: string; + category: ICategory; + + begins_on: Date; + ends_on: Date; + publish_at: Date; + + status: EventStatus; + visibility: EventVisibility; + + join_options: EventJoinOptions; + + thumbnail: string; + large_image: string; + + organizerActor: IActor; + attributedTo: IActor; + participants: IParticipant[]; + + // online_address: Address; + // phone_address: string; +} diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue index d2854ae02..24a03639d 100644 --- a/js/src/views/Event/Event.vue +++ b/js/src/views/Event/Event.vue @@ -93,13 +93,15 @@ </template> <script lang="ts"> -import { FETCH_EVENT } from "@/graphql/event"; -import { Component, Prop, Vue } from "vue-property-decorator"; -import VueMarkdown from "vue-markdown"; -import { LOGGED_PERSON } from "../../graphql/actor"; -import { IEvent } from "@/types/event.model"; -import { JOIN_EVENT } from "../../graphql/event"; -import { IPerson } from "@/types/actor.model"; +import { DELETE_EVENT, FETCH_EVENT, LEAVE_EVENT } from '@/graphql/event'; +import { Component, Prop, Vue } from 'vue-property-decorator'; +import { LOGGED_PERSON } from '@/graphql/actor'; +import { IEvent, IParticipant } from '@/types/event.model'; +import { JOIN_EVENT } from '@/graphql/event'; +import { IPerson } from '@/types/actor.model'; + +// No typings for this component, so we use require +const VueMarkdown = require('vue-markdown'); @Component({ components: { @@ -126,31 +128,73 @@ export default class Event extends Vue { loggedPerson!: IPerson; validationSent: boolean = false; - deleteEvent() { + async deleteEvent() { const router = this.$router; - // FIXME: remove eventFetch - // eventFetch(`/events/${this.uuid}`, this.$store, { method: 'DELETE' }) - // .then(() => router.push({ name: 'EventList' })); + + try { + await this.$apollo.mutate<IParticipant>({ + mutation: DELETE_EVENT, + variables: { + id: this.event.id, + actorId: this.loggedPerson.id, + } + }); + + router.push({ name: 'EventList' }) + } catch (error) { + console.error(error); + } } async joinEvent() { try { - this.validationSent = true; - await this.$apollo.mutate({ - mutation: JOIN_EVENT + await this.$apollo.mutate<IParticipant>({ + mutation: JOIN_EVENT, + variables: { + id: this.event.id, + actorId: this.loggedPerson.id, + }, + update: (store, { data: { joinEvent } }) => { + const event = store.readQuery<IEvent>({ query: FETCH_EVENT }); + if (event === null) { + console.error('Cannot update event participant cache, because of null value.') + return + } + + event.participants = event.participants.concat([ joinEvent ]); + + store.writeQuery({ query: FETCH_EVENT, data: event }); + } }); } catch (error) { console.error(error); } } - leaveEvent() { - // FIXME: remove eventFetch - // eventFetch(`/events/${this.uuid}/leave`, this.$store) - // .then(response => response.json()) - // .then((data) => { - // console.log(data); - // }); + async leaveEvent() { + try { + await this.$apollo.mutate<IParticipant>({ + mutation: LEAVE_EVENT, + variables: { + id: this.event.id, + actorId: this.loggedPerson.id, + }, + update: (store, { data: { leaveEvent } }) => { + const event = store.readQuery<IEvent>({ query: FETCH_EVENT }); + if (event === null) { + console.error('Cannot update event participant cache, because of null value.'); + return + } + + event.participants = event.participants + .filter(p => p.actor.id !== leaveEvent.actor.id); + + store.writeQuery({ query: FETCH_EVENT, data: event }); + } + }); + } catch (error) { + console.error(error); + } } downloadIcsEvent() { @@ -169,28 +213,16 @@ export default class Event extends Vue { } actorIsParticipant() { - return ( - (this.loggedPerson && - this.event.participants - .map(participant => participant.actor.preferredUsername) - .includes(this.loggedPerson.preferredUsername)) || - this.actorIsOrganizer() - ); + if (this.actorIsOrganizer()) return true; + + return this.loggedPerson && + this.event.participants + .some(participant => participant.actor.id === this.loggedPerson.id); } - // + actorIsOrganizer() { - return ( - this.loggedPerson && - this.loggedPerson.preferredUsername === - this.event.organizerActor.preferredUsername - ); + return this.loggedPerson && + this.loggedPerson.id === this.event.organizerActor.id; } } </script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style> -.v-card__media__background { - filter: contrast(0.4); -} -</style>