Merge branch 'feature/cleanup-event' into 'master'

Add leave/join/delete event logic

See merge request framasoft/mobilizon!74
This commit is contained in:
Thomas Citharel 2019-02-22 13:54:50 +01:00
commit 24dfa3b2d1
5 changed files with 327 additions and 261 deletions

View File

@ -1,134 +1,157 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
const participantQuery = `
role,
actor {
preferredUsername,
avatarUrl,
name
}
`;
export const FETCH_EVENT = gql` export const FETCH_EVENT = gql`
query($uuid:UUID!) { query($uuid:UUID!) {
event(uuid: $uuid) { event(uuid: $uuid) {
id, id,
uuid, uuid,
url, url,
local, local,
title, title,
description, description,
beginsOn, beginsOn,
endsOn, endsOn,
status, status,
visibility, visibility,
thumbnail, thumbnail,
large_image, large_image,
publish_at, publish_at,
# online_address, # online_address,
# phone_address, # phone_address,
organizerActor { organizerActor {
avatarUrl, avatarUrl,
preferredUsername, preferredUsername,
name, name,
}, },
# attributedTo { # attributedTo {
# # avatarUrl, # # avatarUrl,
# preferredUsername, # preferredUsername,
# name, # name,
# }, # },
participants { participants {
actor { ${participantQuery}
avatarUrl, },
preferredUsername, category {
name, title,
}, },
role, }
}, }
category {
title,
},
}
}
`; `;
export const FETCH_EVENTS = gql` export const FETCH_EVENTS = gql`
query { query {
events { events {
id, id,
uuid, uuid,
url, url,
local, 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, title,
description, },
beginsOn, participants {
endsOn, ${participantQuery}
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
}
}
} }
} }
`; `;
export const CREATE_EVENT = gql` export const CREATE_EVENT = gql`
mutation CreateEvent( mutation CreateEvent(
$title: String!, $title: String!,
$description: String!, $description: String!,
$organizerActorId: String!, $organizerActorId: String!,
$category: String!, $category: String!,
$beginsOn: DateTime! $beginsOn: DateTime!
) {
createEvent(
title: $title,
description: $description,
beginsOn: $beginsOn,
organizerActorId: $organizerActorId,
category: $category
) { ) {
createEvent( id,
title: $title, uuid,
description: $description, title
beginsOn: $beginsOn, }
organizerActorId: $organizerActorId, }
category: $category
) {
id,
uuid,
title
}
}
`; `;
export const EDIT_EVENT = gql` export const EDIT_EVENT = gql`
mutation EditEvent( mutation EditEvent(
$title: String!, $title: String!,
$description: String!, $description: String!,
$organizerActorId: Int!, $organizerActorId: Int!,
$categoryId: Int! $categoryId: Int!
) { ) {
EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) { EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) {
uuid uuid
} }
} }
`; `;
export const JOIN_EVENT = gql` export const JOIN_EVENT = gql`
mutation JoinEvent( mutation JoinEvent($id: Int!, $actorId: Int!) {
$uuid: String!, joinEvent(
$username: String! id: $id,
actorId: $actorId
) { ) {
joinEvent( actor {
uuid: $uuid, ${participantQuery}
username: $username },
) 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
)
}
`; `;

View File

@ -7,54 +7,54 @@ import SendPasswordReset from '@/views/User/SendPasswordReset.vue';
import PasswordReset from '@/views/User/PasswordReset.vue'; import PasswordReset from '@/views/User/PasswordReset.vue';
export default [ export default [
{ {
path: '/register/user', path: '/register/user',
name: 'Register', name: 'Register',
component: RegisterUser, component: RegisterUser,
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: '/register/profile', path: '/register/profile',
name: 'RegisterProfile', name: 'RegisterProfile',
component: RegisterProfile, component: RegisterProfile,
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{ {
path: '/resend-instructions', path: '/resend-instructions',
name: 'ResendConfirmation', name: 'ResendConfirmation',
component: ResendConfirmation, component: ResendConfirmation,
props: true, props: true,
meta: { requiresAuth: false }, meta: { requiresAuth: false },
}, },
{ {
path: '/password-reset/send', path: '/password-reset/send',
name: 'SendPasswordReset', name: 'SendPasswordReset',
component: SendPasswordReset, component: SendPasswordReset,
props: true, props: true,
meta: { requiresAuth: false }, meta: { requiresAuth: false },
}, },
{ {
path: '/password-reset/:token', path: '/password-reset/:token',
name: 'PasswordReset', name: 'PasswordReset',
component: PasswordReset, component: PasswordReset,
meta: { requiresAuth: false }, meta: { requiresAuth: false },
props: true, props: true,
}, },
{ {
path: '/validate/:token', path: '/validate/:token',
name: 'Validate', name: 'Validate',
component: Validate, component: Validate,
// We can only pass string values through params, therefore // We can only pass string values through params, therefore
props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true'}), props: (route) => ({ email: route.params.email, userAlreadyActivated: route.params.userAlreadyActivated === 'true'}),
meta: { requiresAuth: false }, meta: { requiresAuth: false },
}, },
{ {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
component: Login, component: Login,
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
]; ];

View File

@ -1,13 +1,13 @@
export interface IActor { export interface IActor {
id: string; id: string;
url: string; url: string;
name: string; name: string;
domain: string|null; domain: string|null;
summary: string; summary: string;
preferredUsername: string; preferredUsername: string;
suspended: boolean; suspended: boolean;
avatarUrl: string; avatarUrl: string;
bannerUrl: string; bannerUrl: string;
} }
export interface IPerson extends IActor { export interface IPerson extends IActor {
@ -15,15 +15,18 @@ export interface IPerson extends IActor {
} }
export interface IGroup extends IActor { export interface IGroup extends IActor {
members: IMember[]; members: IMember[];
} }
export enum MemberRole { export enum MemberRole {
PENDING, MEMBER, MODERATOR, ADMIN PENDING,
MEMBER,
MODERATOR,
ADMIN,
} }
export interface IMember { export interface IMember {
role: MemberRole; role: MemberRole;
parent: IGroup; parent: IGroup;
actor: IActor; actor: IActor;
} }

View File

@ -1,62 +1,70 @@
import { IActor } from "./actor.model"; import { IActor } from './actor.model';
export enum EventStatus { export enum EventStatus {
TENTATIVE, TENTATIVE,
CONFIRMED, CONFIRMED,
CANCELLED CANCELLED,
} }
export enum EventVisibility { export enum EventVisibility {
PUBLIC, PUBLIC,
UNLISTED, UNLISTED,
RESTRICTED, RESTRICTED,
PRIVATE PRIVATE,
} }
export enum EventJoinOptions { export enum EventJoinOptions {
FREE, FREE,
RESTRICTED, RESTRICTED,
INVITE INVITE,
} }
export enum ParticipantRole { export enum ParticipantRole {
NOT_APPROVED = 'not_approved', NOT_APPROVED = 'not_approved',
PARTICIPANT = 'participant', PARTICIPANT = 'participant',
MODERATOR = 'moderator', MODERATOR = 'moderator',
ADMINSTRATOR = 'administrator', ADMINISTRATOR = 'administrator',
CREATOR = 'creator' CREATOR = 'creator',
} }
export interface ICategory { export interface ICategory {
title: string; title: string;
description: string; description: string;
picture: string; picture: string;
} }
export interface IParticipant { export interface IParticipant {
role: ParticipantRole, role: ParticipantRole;
actor: IActor, actor: IActor;
event: IEvent event: IEvent;
} }
export interface IEvent { export interface IEvent {
uuid: string; id?: number;
url: string; uuid: string;
local: boolean; url: string;
title: string; local: boolean;
description: string;
begins_on: Date; title: string;
ends_on: Date; description: string;
status: EventStatus; category: ICategory;
visibility: EventVisibility;
join_options: EventJoinOptions; begins_on: Date;
thumbnail: string; ends_on: Date;
large_image: string; publish_at: Date;
publish_at: Date;
// online_address: Adress; status: EventStatus;
// phone_address: string; visibility: EventVisibility;
organizerActor: IActor;
attributedTo: IActor; join_options: EventJoinOptions;
participants: IParticipant[];
category: ICategory; thumbnail: string;
} large_image: string;
organizerActor: IActor;
attributedTo: IActor;
participants: IParticipant[];
// online_address: Address;
// phone_address: string;
}

View File

@ -93,13 +93,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { FETCH_EVENT } from "@/graphql/event"; import { DELETE_EVENT, FETCH_EVENT, LEAVE_EVENT } from '@/graphql/event';
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from 'vue-property-decorator';
import VueMarkdown from "vue-markdown"; import { LOGGED_PERSON } from '@/graphql/actor';
import { LOGGED_PERSON } from "../../graphql/actor"; import { IEvent, IParticipant } from '@/types/event.model';
import { IEvent } from "@/types/event.model"; import { JOIN_EVENT } from '@/graphql/event';
import { JOIN_EVENT } from "../../graphql/event"; import { IPerson } from '@/types/actor.model';
import { IPerson } from "@/types/actor.model";
// No typings for this component, so we use require
const VueMarkdown = require('vue-markdown');
@Component({ @Component({
components: { components: {
@ -126,31 +128,73 @@ export default class Event extends Vue {
loggedPerson!: IPerson; loggedPerson!: IPerson;
validationSent: boolean = false; validationSent: boolean = false;
deleteEvent() { async deleteEvent() {
const router = this.$router; const router = this.$router;
// FIXME: remove eventFetch
// eventFetch(`/events/${this.uuid}`, this.$store, { method: 'DELETE' }) try {
// .then(() => router.push({ name: 'EventList' })); 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() { async joinEvent() {
try { try {
this.validationSent = true; await this.$apollo.mutate<IParticipant>({
await this.$apollo.mutate({ mutation: JOIN_EVENT,
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) { } catch (error) {
console.error(error); console.error(error);
} }
} }
leaveEvent() { async leaveEvent() {
// FIXME: remove eventFetch try {
// eventFetch(`/events/${this.uuid}/leave`, this.$store) await this.$apollo.mutate<IParticipant>({
// .then(response => response.json()) mutation: LEAVE_EVENT,
// .then((data) => { variables: {
// console.log(data); 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() { downloadIcsEvent() {
@ -169,28 +213,16 @@ export default class Event extends Vue {
} }
actorIsParticipant() { actorIsParticipant() {
return ( if (this.actorIsOrganizer()) return true;
(this.loggedPerson &&
this.event.participants return this.loggedPerson &&
.map(participant => participant.actor.preferredUsername) this.event.participants
.includes(this.loggedPerson.preferredUsername)) || .some(participant => participant.actor.id === this.loggedPerson.id);
this.actorIsOrganizer()
);
} }
//
actorIsOrganizer() { actorIsOrganizer() {
return ( return this.loggedPerson &&
this.loggedPerson && this.loggedPerson.id === this.event.organizerActor.id;
this.loggedPerson.preferredUsername ===
this.event.organizerActor.preferredUsername
);
} }
} }
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
.v-card__media__background {
filter: contrast(0.4);
}
</style>