Encore des commits degeu

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2018-06-06 17:42:53 +02:00
parent 4c60f0ec51
commit 164b6b014c
31 changed files with 3200 additions and 2937 deletions

5416
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
"test:e2e": "vue-cli-service test:e2e"
},
"dependencies": {
"material-design-icons-iconfont": "^3.0.3",
"material-design-icons": "^3.0.1",
"moment": "^2.22.1",
"ngeohash": "^0.6.0",
"register-service-worker": "^1.0.0",

View File

@ -23,7 +23,7 @@
<v-avatar size="125px">
<img v-if="!account.avatar_url"
class="img-circle elevation-7 mb-1"
src="http://lorempixel.com/125/125/"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
@ -84,7 +84,7 @@
<v-card-media
class="black--text"
height="200px"
src="http://lorempixel.com/400/200/"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
@ -125,7 +125,7 @@
<v-card-media
class="black--text"
height="200px"
src="http://lorempixel.com/400/200/"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>

View File

@ -28,54 +28,63 @@
</v-list>
</v-menu>
</v-card-title>
<v-spacer></v-spacer>
<div class="text-xs-center">
<v-card-title class="pl-5 pt-5">
<div class="display-1 pl-5 pt-5">{{ event.title }}</div>
</v-card-title>
<!--<p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey&#45;&#45;text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p>
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>-->
</div>
<v-container fluid grid-list-md>
<v-subheader>Membres</v-subheader>
<v-layout row>
<v-flex xs2>
<router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">
<v-avatar size="75px">
<img v-if="!event.organizer.avatar_url"
class="img-circle elevation-7 mb-1"
src="http://lorempixel.com/125/125/"
<v-container grid-list-md text-xs-center>
<v-card-media
src="https://picsum.photos/600/400/"
height="200px"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="event.organizer.avatar_url"
</v-card-media>
<v-layout row wrap>
<v-flex xs6>
<v-spacer></v-spacer>
<span class="subheading grey--text">{{ event.begins_on | formatDay }}</span>
<h1 class="display-2">{{ event.title }}</h1>
<div>
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
<v-avatar size="25px">
<img class="img-circle elevation-7 mb-1"
:src="event.organizer.avatar"
>
</v-avatar>
</router-link>
Organisateur <span>{{ event.organizer.username }}</span>
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name }}</span>
</div>
<p>
<vue-markdown :source="event.description" />
</p>
<!--<p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey&#45;&#45;text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p>
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>-->
</v-flex>
<v-flex xs2 v-for="actor in event.participants" :key="actor.id">
<router-link :to="{name: 'Account', params: {'id': actor.id}}">
<v-flex xs6>
<v-card-actions>
<v-btn color="success" v-if="!event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="joinEvent" class="btn btn-primary"><v-icon>check</v-icon> Join</v-btn>
<v-btn v-if="event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="leaveEvent" class="btn btn-primary">Leave</v-btn>
</v-card-actions>
</v-flex>
</v-layout>
</v-container>
<v-container fluid grid-list-md>
<v-subheader>Membres</v-subheader>
<v-layout row>
<v-flex xs2 v-for="actor in event.participants" :key="actor.uuid">
<router-link :to="{name: 'Account', params: { name: actor.username }}">
<v-avatar size="75px">
<img v-if="!account.avatar_url"
<img v-if="!actor.avatar"
class="img-circle elevation-7 mb-1"
src="http://lorempixel.com/125/125/"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="account.avatar_url"
:src="actor.avatar"
>
</v-avatar>
</router-link>
<span>{{ actor.username }}</span>
</v-flex>
<span v-if="event.participants.length === 0">No participants yet.</span>
</v-layout>
</v-container>
<v-card-actions>
<button v-if="!event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="joinEvent" class="btn btn-primary">Join</button>
<button v-if="event.participants.map(participant => participant.id).includes($store.state.user.actor.id)" @click="leaveEvent" class="btn btn-primary">Leave</button>
<button @click="deleteEvent" class="btn btn-danger">Delete</button>
</v-card-actions>
</v-layout>
</v-card>
</v-flex>
@ -97,9 +106,10 @@
loading: true,
error: false,
event: {
name: this.name,
slug: this.slug,
name: '',
slug: '',
title: '',
uuid: this.uuid,
description: '',
organizer: {
id: null,
@ -112,11 +122,11 @@
methods: {
deleteEvent() {
const router = this.$router;
eventFetch(`/events/${this.name}/${this.slug}`, this.$store, { method: 'DELETE' })
eventFetch(`/events/${this.uuid}`, this.$store, { method: 'DELETE' })
.then(() => router.push({'name': 'EventList'}));
},
fetchData() {
eventFetch(`/events/${this.name}/${this.slug}`, this.$store)
eventFetch(`/events/${this.uuid}`, this.$store)
.then(response => response.json())
.then((data) => {
this.loading = false;
@ -130,21 +140,21 @@
});
},
joinEvent() {
eventFetch(`/events/${this.name}/${this.slug}/join`, this.$store)
eventFetch(`/events/${this.uuid}/join`, this.$store)
.then(response => response.json())
.then((data) => {
console.log(data);
});
},
leaveEvent() {
eventFetch(`/events/${this.name}/${this.slug}/leave`, this.$store)
eventFetch(`/events/${this.uuid}/leave`, this.$store)
.then(response => response.json())
.then((data) => {
console.log(data);
});
},
downloadIcsEvent() {
eventFetch(`/events/${this.name}/${this.slug}/ics`, this.$store, {responseType: 'arraybuffer'})
eventFetch(`/events/${this.uuid}/ics`, this.$store, {responseType: 'arraybuffer'})
.then((response) => response.text())
.then(response => {
const blob = new Blob([response],{type: 'text/calendar'});
@ -158,14 +168,10 @@
},
},
props: {
name: {
uuid: {
type: String,
required: true,
},
slug: {
type: String,
required: true
},
},
created() {
this.fetchData();

View File

@ -1,35 +1,43 @@
<template>
<v-container>
<v-layout>
<v-flex xs12 sm8 offset-sm2>
<v-card>
<h1>{{ $t("event.list.title") }}</h1>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-chip close v-model="locationChip" label color="pink" text-color="white" v-if="$router.currentRoute.params.location">
<v-icon left>location_city</v-icon>{{ locationText }}
</v-chip>
<v-layout row wrap justify-space-around>
<v-flex xs12 md3 v-for="event in events" :key="event.id">
<v-container grid-list-sm fluid>
<v-layout row wrap>
<v-flex xs4 v-for="event in events" :key="event.id">
<v-card>
<v-card-media v-if="event.image"
<v-card-media v-if="!event.image"
class="white--text"
height="200px"
src="http://lorempixel.com/400/200/"
src="https://picsum.photos/g/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
<v-flex xs12 align-end flexbox>
<span class="headline">{{ event.title }}</span>
<span class="headline black--text">{{ event.title }}</span>
</v-flex>
</v-layout>
</v-container>
</v-card-media>
<v-card-title v-else primary-title>
<div class="headline">{{ event.title }}</div>
<v-card-title primary-title>
<div>
<span class="grey--text">{{ event.begins_on | formatDate }}</span><br>
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
<v-avatar size="25px">
<img class="img-circle elevation-7 mb-1"
:src="event.organizer.avatar"
>
</v-avatar>
</router-link>
<span v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'name': event.organizer.username}}">{{ event.organizer.username }}</router-link></span>
</div>
</v-card-title>
<v-container>
<!--<span class="grey&#45;&#45;text">{{ event.startDate | formatDate }} à <router-link :to="{name: 'EventList', params: {location: geocode(event.address.geo.latitude, event.address.geo.longitude, 10) }}">{{ event.address.addressLocality }}</router-link></span><br>-->
<p><vue-markdown>{{ event.description }}</vue-markdown></p>
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'name': event.organizer.username}}">{{ event.organizer.username }}</router-link></p>
</v-container>
<v-card-actions>
<v-btn flat color="orange" @click="downloadIcsEvent(event)">Share</v-btn>
<v-btn flat color="orange" @click="viewEvent(event)">Explore</v-btn>
@ -38,8 +46,11 @@
</v-card>
</v-flex>
</v-layout>
<router-link :to="{ name: 'CreateEvent' }" class="btn btn-default">Create</router-link>
</v-container>
<router-link :to="{ name: 'CreateEvent' }" class="btn btn-default">Create</router-link>
</v-card>
</v-flex>
</v-layout>
</template>
<script>
@ -96,18 +107,19 @@
.then((response) => {
this.loading = false;
this.events = response.data;
console.log(this.events);
});
},
deleteEvent(event) {
const router = this.$router;
eventFetch(`/events/${event.organizer.username}/${event.slug}`, this.$store, {'method': 'DELETE'})
eventFetch(`/events/${event.uuid}`, this.$store, {'method': 'DELETE'})
.then(() => router.push('/events'));
},
viewEvent(event) {
this.$router.push({ name: 'Event', params: { name: event.organizer.username, slug: event.slug } })
this.$router.push({ name: 'Event', params: { uuid: event.uuid } })
},
downloadIcsEvent(event) {
eventFetch(`/events/${event.organizer.username}/${event.slug}/export`, this.$store, {responseType: 'arraybuffer'})
eventFetch(`/events/${event.uuid}/ics`, this.$store, {responseType: 'arraybuffer'})
.then((response) => response.text())
.then(response => {
const blob = new Blob([response],{type: 'text/calendar'});

View File

@ -4,15 +4,16 @@
<v-flex xs12 sm6 offset-sm3>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-card v-if="!loading">
<v-card-media :src="group.banner" height="400px">
<v-layout column class="media">
<v-card-title>
<v-btn icon @click="$router.go(-1)">
<v-icon>chevron_left</v-icon>
</v-btn>
<v-spacer></v-spacer>
<v-btn icon class="mr-3" v-if="$store.state.user">
<v-icon>edit</v-icon>
</v-btn>
<!--<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.user.actor.id === actor.id">-->
<!--<v-icon>edit</v-icon>-->
<!--</v-btn>-->
<v-btn icon>
<v-icon>more_vert</v-icon>
</v-btn>
@ -20,21 +21,40 @@
<v-spacer></v-spacer>
<div class="text-xs-center">
<v-avatar size="125px">
<img v-if="!group.avatar_url"
<img v-if="!group.avatar"
class="img-circle elevation-7 mb-1"
src="http://lorempixel.com/125/125/"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="group.avatar_url"
:src="group.avatar"
>
</v-avatar>
<v-card-title class="pl-5 pt-5">
<div class="display-1 pl-5 pt-5">{{ group.title }}<span v-if="group.server">@{{ group.server.address }}</span></div>
</v-card-title>
<v-card-text v-html="group.description"></v-card-text>
</div>
<v-container fluid grid-list-lg>
<v-layout row>
<v-flex xs7>
<div class="headline">{{ group.display_name }}</div>
<div>
<span class="subheading">
~{{ group.username }}
<span v-if="group.domain">
@{{ group.domain }}
</span>
</span>
<v-chip color="indigo" text-color="white">
<v-avatar>
<v-icon>group</v-icon>
</v-avatar>
Group
</v-chip>
</div>
<v-card-text v-if="group.description" v-html="group.description"></v-card-text>
</v-flex>
</v-layout>
</v-container>
</v-layout>
</v-card-media>
<v-list three-line>
<v-list-tile>
<v-list-tile-action>
@ -59,48 +79,90 @@
</v-list-tile-content>
</v-list-tile>
<v-divider inset></v-divider>
<v-list-tile v-if="group.address">
<v-list-tile>
<v-list-tile-action>
<v-icon color="indigo">location_on</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ group.address.streetAddress }}</v-list-tile-title>
<v-list-tile-sub-title>{{ group.address.postalCode }} {{ group.address.locality }}</v-list-tile-sub-title>
<v-list-tile-title>1400 Main Street</v-list-tile-title>
<v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-container fluid grid-list-md v-if="group.members.length > 0">
<v-subheader>Membres</v-subheader>
<v-layout row>
<v-flex xs2 v-for="member in group.members" :key="member.id">
<router-link :to="{name: 'Account', params: {'id': member.actor.id}}">
<v-flex xs2 v-for="member in group.members" :key="member.actor.username">
<router-link :to="{name: 'Account', params: { name: member.actor.username } }">
<v-badge overlap>
<span slot="badge" v-if="member.role == 3"><v-icon>stars</v-icon></span>
<span slot="badge" v-if="member.role === 1"><v-icon>star_half</v-icon></span>
<span slot="badge" v-if="member.role === 2"><v-icon>star</v-icon></span>
<v-avatar size="75px">
<img v-if="!member.account.avatar_url"
<img v-if="!member.actor.avatar"
class="img-circle elevation-7 mb-1"
src="http://lorempixel.com/125/125/"
src="https://picsum.photos/125/125/"
>
<img v-else
class="img-circle elevation-7 mb-1"
:src="member.account.avatar_url"
:src="member.actor.avatar"
>
</v-avatar>
</v-badge>
</router-link>
<span>{{ groupAccount.actor.username }}</span>
<span>{{ member.actor.username }}</span>
</v-flex>
</v-layout>
</v-container>
<v-container fluid grid-list-md v-if="group.events.length > 0">
<v-container fluid grid-list-md v-if="group.participatingEvents && group.participatingEvents.length > 0">
<v-subheader>Participated at</v-subheader>
<v-layout row wrap>
<v-flex v-for="event in group.events" :key="event.id">
<v-flex v-for="event in group.participatingEvents" :key="event.id">
<v-card>
<v-card-media
class="black--text"
height="200px"
src="http://lorempixel.com/400/200/"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
<v-flex xs12 align-end flexbox>
<span class="headline">{{ event.title }}</span>
</v-flex>
</v-layout>
</v-container>
</v-card-media>
<v-card-title>
<div>
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
<p>{{ event.description }}</p>
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
</div>
</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>favorite</v-icon>
</v-btn>
<v-btn icon>
<v-icon>bookmark</v-icon>
</v-btn>
<v-btn icon>
<v-icon>share</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-container>
<v-container fluid grid-list-md v-if="group.organizingEvents && group.organizingEvents.length > 0">
<v-subheader>Organized events</v-subheader>
<v-layout row wrap>
<v-flex v-for="event in group.organizingEvents" :key="event.id">
<v-card>
<v-card-media
class="black--text"
height="200px"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
@ -140,40 +202,39 @@
</template>
<script>
import eventFetch from '@/api/eventFetch';
export default {
name: 'Group',
props: ['id'],
data() {
return {
group: {
id: this.id,
title: '',
description: '',
},
group: null,
loading: true,
}
},
methods: {
fetchData() {
eventFetch(`/groups/${this.id}`, this.$store)
.then(response => response.json())
.then((data) => {
this.loading = false;
this.group = data.data;
});
},
deleteGroup() {
const router = this.$router;
eventFetch(`/groups/${this.id}`, this.$store, { method: 'DELETE' })
.then(response => response.json())
.then(() => router.push('/groups'));
},
props: {
name: {
type: String,
required: true,
}
},
created() {
this.fetchData();
},
watch: {
// call again the method if the route changes
'$route': 'fetchData'
},
methods: {
fetchData() {
eventFetch(`/actors/${this.name}`, this.$store)
.then(response => response.json())
.then((response) => {
this.group = response.data;
this.loading = false;
console.log(this.group);
})
}
}
}
</script>

View File

@ -9,7 +9,7 @@
<v-card-media
class="black--text"
height="200px"
src="http://lorempixel.com/400/200/"
src="https://picsum.photos/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
@ -26,9 +26,9 @@
</div>
</v-card-title>
<v-card-actions>
<v-btn flat color="green" @click="joinGroup(group.id)"><v-icon v-if="group.locked">lock</v-icon>Join</v-btn>
<v-btn flat color="green" @click="joinGroup(group)"><v-icon v-if="group.locked">lock</v-icon>Join</v-btn>
<v-btn flat color="orange" @click="viewActor(group)">Explore</v-btn>
<v-btn flat color="red" @click="deleteEvent(group.id)">Delete</v-btn>
<v-btn flat color="red" @click="deleteGroup(group)">Delete</v-btn>
</v-card-actions>
</v-card>
</v-flex>
@ -64,20 +64,20 @@
this.groups = data.data;
});
},
deleteEvent(id) {
deleteGroup(group) {
const router = this.$router;
eventFetch('/groups/' + id, this.$store, {'method': 'DELETE'})
eventFetch(`/groups/${this.username_with_domain(group)}`, this.$store, {'method': 'DELETE'})
.then(response => response.json())
.then(() => router.push('/groups'));
},
viewActor(actor) {
this.$router.push({ name: 'Account', params: { name: this.username_with_domain(actor) } })
this.$router.push({ name: 'Group', params: { name: this.username_with_domain(actor) } })
},
joinGroup(id) {
joinGroup(group) {
const router = this.$router;
eventFetch('/groups/' + id + '/join', this.$store)
eventFetch(`/groups/${this.username_with_domain(group)}/join`, this.$store, { method: 'POST' })
.then(response => response.json())
.then(() => router.push('/group/' + id))
.then(() => router.push({ name: 'Group', params: { name: this.username_with_domain(group) } }));
}
},
};

View File

@ -1,10 +1,59 @@
<template>
<v-container>
<h1 class="welcome" v-if="$store.state.user">{{ $t("home.welcome", { 'username': this.displayed_name }) }}</h1>
<h1 class="welcome" v-else>{{ $t("home.welcome_off", { 'username': $store.state.user.username}) }}</h1>
<router-link :to="{ name: 'EventList' }">{{ $t('home.events') }}</router-link>
<router-link v-if="$store.state.user === false" :to="{ name: 'Login' }">{{ $t('home.login') }}</router-link>
<router-link v-if="$store.state.user === false" :to="{ name: 'Register' }">{{ $t('home.register') }}</router-link>
<v-jumbotron
:gradient="gradient"
src="https://picsum.photos/1200/900"
dark
v-if="$store.state.user === false"
>
<v-container fill-height>
<v-layout align-center>
<v-flex text-xs-center>
<h1 class="display-3">Find events you like</h1>
<h2>Share it with Eventos</h2>
<v-btn>{{ $t("home.register") }}</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-jumbotron>
<v-layout>
<v-flex xs12 sm8 offset-sm2>
<v-card>
<v-layout row wrap>
<v-flex xs4 v-for="event in events" :key="event.uuid">
<v-card :to="{ name: 'Event', params:{ uuid: event.uuid } }">
<v-card-media v-if="!event.image"
class="white--text"
height="200px"
src="https://picsum.photos/g/400/200/"
>
<v-container fill-height fluid>
<v-layout fill-height>
<v-flex xs12 align-end flexbox>
<span class="headline black--text">{{ event.title }}</span>
</v-flex>
</v-layout>
</v-container>
</v-card-media>
<v-card-title primary-title>
<div>
<span class="grey--text">{{ event.begins_on | formatDate }}</span><br>
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
<v-avatar size="25px">
<img class="img-circle elevation-7 mb-1"
:src="event.organizer.avatar"
>
</v-avatar>
</router-link>
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name }}</span>
</div>
</v-card-title>
</v-card>
</v-flex>
</v-layout>
</v-card>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs6>
<v-btn large @click="geoLocalize"><v-icon>my_location</v-icon>Me géolocaliser</v-btn>
@ -36,6 +85,7 @@ export default {
name: 'Home',
data() {
return {
gradient: 'to top right, rgba(63,81,181, .7), rgba(25,32,72, .7)',
user: null,
searchTerm: null,
location_field: {
@ -43,10 +93,11 @@ export default {
search: null,
},
locations: [],
events: [],
};
},
mounted() {
// this.fetchLocations();
created() {
this.fetchData();
},
computed: {
displayed_name: function() {
@ -61,6 +112,14 @@ export default {
this.locations = response;
});
},
fetchData() {
eventFetch('/events', this.$store)
.then(response => response.json())
.then((response) => {
this.loading = false;
this.events = response.data;
});
},
geoLocalize() {
const router = this.$router;
if (sessionStorage.getItem('City')) {
@ -85,6 +144,9 @@ export default {
sessionStorage.setItem('City', geohash);
this.$router.push({name: 'EventList', params: {location: geohash}});
},
viewEvent(event) {
this.$router.push({ name: 'Event', params: { uuid: event.uuid } })
},
},
};
</script>

View File

@ -9,7 +9,7 @@
<v-toolbar-title style="width: 300px" class="ml-0 pl-3">
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
<router-link :to="{ name: 'Home' }">
Libre-Event
Eventos
</router-link>
</v-toolbar-title>
<v-select

View File

@ -1,7 +1,7 @@
export default {
home: {
welcome: 'Welcome on Libre-Event, {username}',
welcome_off: 'Welcome on Libre-Event',
welcome: 'Welcome on Eventos, {username}',
welcome_off: 'Welcome on Eventos',
events: 'Events',
groups: 'Groups',
login: 'Login',

View File

@ -1,7 +1,7 @@
export default {
home: {
welcome: 'Bienvenue sur Libre-Event, {username}!',
welcome_off: 'Bienvenue sur Libre-Event',
welcome: 'Bienvenue sur Eventos, {username}!',
welcome_off: 'Bienvenue sur Eventos',
events: 'Événements',
groups: 'Groupes',
login: 'Se connecter',

View File

@ -8,8 +8,8 @@ import Vuetify from 'vuetify';
import Vuex from 'vuex';
import moment from 'moment';
import VuexI18n from 'vuex-i18n';
import 'material-design-icons/iconfont/material-icons.css';
import 'vuetify/dist/vuetify.min.css';
import 'material-design-icons-iconfont/dist/material-design-icons.css';
import App from '@/App';
import router from '@/router';
import storeData from '@/store/index';
@ -37,6 +37,7 @@ let language = window.navigator.userLanguage || window.navigator.language;
moment.locale(language);
Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null));
Vue.filter('formatDay', value => (value ? moment(String(value)).format('LL') : null));
if (!(language in translations)) {
[language] = language.split('-', 1);

View File

@ -103,7 +103,7 @@ const router = new Router({
meta: { requiredAuth: false },
},
{
path: '/@:name/:slug',
path: '/events/:uuid',
name: 'Event',
component: Event,
props: true,

View File

@ -133,6 +133,15 @@ defmodule Eventos.Actors do
Repo.all(from a in Actor, where: a.type == "Group")
end
def get_group_by_name(name) do
actor = case String.split(name, "@") do
[name] ->
Repo.get_by(Actor, preferred_username: name, type: :Group)
[name, domain] ->
Repo.get_by(Actor, preferred_username: name, domain: domain, type: :Group)
end
end
@doc """
Creates a group.
@ -176,7 +185,7 @@ defmodule Eventos.Actors do
def insert_or_update_actor(data) do
cs = Actor.remote_actor_creation(data)
Repo.insert(cs, on_conflict: [set: [public_key: data.public_key, avatar_url: data.avatar_url, banner: data.banner_url, name: data.name]], conflict_target: [:preferred_username, :domain])
Repo.insert(cs, on_conflict: [set: [public_key: data.public_key, avatar_url: data.avatar_url, banner_url: data.banner_url, name: data.name]], conflict_target: [:preferred_username, :domain])
end
# def increase_event_count(%Actor{} = actor) do
@ -494,7 +503,8 @@ defmodule Eventos.Actors do
def create_member(attrs \\ %{}) do
%Member{}
|> Member.changeset(attrs)
|> Repo.insert()
|> Repo.insert!()
|> Repo.preload([:actor, :parent])
end
@doc """
@ -544,6 +554,22 @@ defmodule Eventos.Actors do
Member.changeset(member, %{})
end
def groups_for_actor(%Actor{id: id} = _actor) do
Repo.all(
from m in Member,
where: m.actor_id == ^id,
preload: [:parent]
)
end
def members_for_group(%Actor{type: :Group, id: id} = _group) do
Repo.all(
from m in Member,
where: m.parent_id == ^id,
preload: [:parent, :actor]
)
end
alias Eventos.Actors.Bot

View File

@ -7,10 +7,10 @@ defmodule Eventos.Actors.Member do
alias Eventos.Actors.Member
alias Eventos.Actors.Actor
@primary_key false
schema "members" do
field :approved, :boolean
field :role, :integer
field :approved, :boolean, default: true
field :role, :integer, default: 0 # 0 : Member, 1 : Moderator, 2 : Admin
belongs_to :parent, Actor
belongs_to :actor, Actor
@ -20,7 +20,7 @@ defmodule Eventos.Actors.Member do
@doc false
def changeset(%Member{} = member, attrs) do
member
|> cast(attrs, [:role])
|> validate_required([:role])
|> cast(attrs, [:role, :approved, :parent_id, :actor_id])
|> validate_required([:parent_id, :actor_id])
end
end

View File

@ -26,6 +26,5 @@ defmodule Eventos.Addresses.Address do
def changeset(%Address{} = address, attrs) do
address
|> cast(attrs, [:description, :floor, :geom, :addressCountry, :addressLocality, :addressRegion, :postalCode, :streetAddress])
|> validate_required([:geom])
end
end

View File

@ -51,6 +51,7 @@ defmodule Eventos.Events.Event do
field :thumbnail, :string
field :large_image, :string
field :publish_at, Timex.Ecto.DateTimeWithTimezone
field :uuid, Ecto.UUID, default: Ecto.UUID.generate()
belongs_to :organizer_actor, Actor, [foreign_key: :organizer_actor_id]
many_to_many :tags, Tag, join_through: "events_tags"
belongs_to :category, Category
@ -64,12 +65,17 @@ defmodule Eventos.Events.Event do
@doc false
def changeset(%Event{} = event, attrs) do
event
changeset = event
|> cast(attrs, [:title, :description, :url, :begins_on, :ends_on, :organizer_actor_id, :category_id, :state, :status, :public, :thumbnail, :large_image, :publish_at])
|> cast_assoc(:tags)
|> cast_assoc(:address)
|> validate_required([:title, :description, :begins_on, :ends_on, :organizer_actor_id, :category_id])
|> TitleSlug.maybe_generate_slug()
|> TitleSlug.unique_constraint()
|> put_change(:uuid, Ecto.UUID.generate())
import Logger
Logger.debug(inspect changeset)
changeset
end
end

View File

@ -77,6 +77,13 @@ defmodule Eventos.Events do
Repo.get_by(Event, url: url)
end
@doc """
Gets an event by it's UUID
"""
def get_event_by_uuid(uuid) do
Repo.get_by(Event, uuid: uuid)
end
@doc """
Gets a single event, with all associations loaded.
"""
@ -93,6 +100,14 @@ defmodule Eventos.Events do
Repo.preload(event, [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address])
end
@doc """
Gets a full event by it's UUID
"""
def get_event_full_by_uuid(uuid) do
event = Repo.get_by(Event, uuid: uuid)
Repo.preload(event, [:organizer_actor, :category, :sessions, :tracks, :tags, :participants, :address])
end
@spec get_event_full_by_name_and_slug!(String.t, String.t) :: Event.t
def get_event_full_by_name_and_slug!(name, slug) do
query = case String.split(name, "@") do
@ -139,7 +154,8 @@ defmodule Eventos.Events do
def create_event(attrs \\ %{}) do
%Event{}
|> Event.changeset(attrs)
|> Repo.insert()
|> Repo.insert!()
|> Repo.preload([:organizer_actor])
end
@doc """

View File

@ -9,7 +9,7 @@ defmodule Eventos.Events.Participant do
@primary_key false
schema "participants" do
field :role, :integer
field :role, :integer, default: 0 # 0 : participant, 1 : moderator, 2 : administrator, 3 : creator
field :approved, :boolean
belongs_to :event, Event, primary_key: true
belongs_to :actor, Actor, primary_key: true

View File

@ -11,7 +11,8 @@ defmodule Eventos.Export.ICalendar do
summary: event.title,
dtstart: event.begins_on,
dtend: event.ends_on,
description: event.description
description: event.description,
uid: event.uuid
}]
%ICalendar{events: events}
|> ICalendar.to_ics()

View File

@ -27,6 +27,27 @@ defmodule EventosWeb.AddressController do
end
end
def process_geom(%{"type" => type, "data" => data}) do
import Logger
Logger.debug("Process geom")
Logger.debug(inspect data)
Logger.debug(inspect type)
types = [:point]
unless is_atom(type) do
type = String.to_existing_atom(type)
end
case type do
:point ->
%Geo.Point{coordinates: {data["latitude"], data["longitude"]}, srid: 4326}
nil ->
nil
end
end
def process_geom(nil) do
nil
end
def show(conn, %{"id" => id}) do
address = Addresses.get_address!(id)
render(conn, "show.json", address: address)

View File

@ -17,18 +17,30 @@ defmodule EventosWeb.EventController do
end
def create(conn, %{"event" => event_params}) do
event_params = %{event_params | "address" => process_address(event_params["address"])}
address = process_address(event_params["address"])
event_params = if is_nil address do
event_params
else
%{event_params | "address" => address}
end
with {:ok, %Event{} = event} <- Events.create_event(event_params) do
conn
|> put_status(:created)
|> put_resp_header("location", event_path(conn, :show, event))
|> put_resp_header("location", event_path(conn, :show, event.uuid))
|> render("show_simple.json", event: event)
end
end
defp process_address(address) do
case Addresses.process_geom(address["geom"]) do
{:ok, geom} ->
import Logger
Logger.debug("process address")
Logger.debug(inspect address)
geom = EventosWeb.AddressController.process_geom(address["geom"])
Logger.debug(inspect geom)
case geom do
nil ->
address
_ ->
%{address | "geom" => geom}
_ ->
address
@ -40,27 +52,26 @@ defmodule EventosWeb.EventController do
render(conn, "index.json", events: events)
end
def show(conn, %{"username" => username, "slug" => slug}) do
event = Events.get_event_full_by_name_and_slug!(username, slug)
def show(conn, %{"uuid" => uuid}) do
event = Events.get_event_full_by_uuid(uuid)
render(conn, "show.json", event: event)
end
def export_to_ics(conn, %{"username" => username, "slug" => slug}) do
event = Events.get_event_full_by_name_and_slug!(username, slug)
|> ICalendar.export_event()
def export_to_ics(conn, %{"uuid" => uuid}) do
event = Events.get_event_full_by_uuid(uuid) |> ICalendar.export_event()
send_resp(conn, 200, event)
end
def update(conn, %{"username" => username, "slug" => slug, "event" => event_params}) do
event = Events.get_event_full_by_name_and_slug!(username, slug)
def update(conn, %{"uuid" => uuid, "event" => event_params}) do
event = Events.get_event_full_by_uuid(uuid)
with {:ok, %Event{} = event} <- Events.update_event(event, event_params) do
render(conn, "show_simple.json", event: event)
end
end
def delete(conn, %{"username" => username, "slug" => slug}) do
event = Events.get_event_full_by_name_and_slug!(username, slug)
def delete(conn, %{"uuid" => uuid}) do
event = Events.get_event_by_uuid(uuid)
with {:ok, %Event{}} <- Events.delete_event(event) do
send_resp(conn, :no_content, "")
end

View File

@ -5,7 +5,7 @@ defmodule EventosWeb.GroupController do
use EventosWeb, :controller
alias Eventos.Actors
alias Eventos.Actors.Actor
alias Eventos.Actors.{Actor, Member}
action_fallback EventosWeb.FallbackController
@ -23,4 +23,18 @@ defmodule EventosWeb.GroupController do
|> render(EventosWeb.ActorView, "acccount_basic.json", actor: group)
end
end
def join(conn, %{"name" => group_name}) do
with actor = Guardian.Plug.current_resource(conn).actor,
group <- Actors.get_group_by_name(group_name),
%Member{} = member <- Actors.create_member(%{"parent_id" => group.id, "actor_id" => actor.id}) do
conn
|> put_status(:created)
|> render(EventosWeb.MemberView, "member.json", member: member)
else
err ->
import Logger
Logger.debug(inspect err)
end
end
end

View File

@ -40,10 +40,10 @@ defmodule EventosWeb.Router do
get "/groups", GroupController, :index
get "/events", EventController, :index
get "/events/search/:name", EventController, :search
get "/events/:username/:slug", EventController, :show
get "/events/:username/:slug/ics", EventController, :export_to_ics
get "/events/:username/:slug/tracks", TrackController, :show_tracks_for_event
get "/events/:username/:slug/sessions", SessionController, :show_sessions_for_event
get "/events/:uuid/ics", EventController, :export_to_ics
get "/events/:uuid/tracks", TrackController, :show_tracks_for_event
get "/events/:uuid/sessions", SessionController, :show_sessions_for_event
get "/events/:uuid", EventController, :show
resources "/comments", CommentController, only: [:show]
get "/bots/:id", BotController, :view
@ -71,18 +71,17 @@ defmodule EventosWeb.Router do
resources "/users", UserController, except: [:new, :edit, :show]
patch "/actors/:name", ActorController, :update
post "/events", EventController, :create
patch "/events/:username/:slug", EventController, :update
put "/events/:username/:slug", EventController, :update
delete "/events/:username/:slug", EventController, :delete
patch "/events/:uuid", EventController, :update
put "/events/:uuid", EventController, :update
delete "/events/:uuid", EventController, :delete
resources "/comments", CommentController, except: [:new, :edit, :show]
#post "/events/:id/request", EventRequestController, :create_for_event
resources "/participant", ParticipantController
resources "/bots", BotController, except: [:new, :edit, :show]
#resources "/requests", EventRequestController
post "/groups", GroupController, :create
#post "/groups/:id/request", GroupRequestController, :create_for_group
post "/groups/:name/join", GroupController, :join
resources "/members", MemberController
#resources "/requests", GroupRequestController
resources "/sessions", SessionController, except: [:index, :show]
resources "/tracks", TrackController, except: [:index, :show]
get "/tracks/:id/sessions", SessionController, :show_sessions_for_track

View File

@ -3,7 +3,8 @@ defmodule EventosWeb.ActorView do
View for Actors
"""
use EventosWeb, :view
alias EventosWeb.{ActorView, EventView}
alias EventosWeb.{ActorView, EventView, MemberView}
alias Eventos.Actors
def render("index.json", %{actors: actors}) do
%{data: render_many(actors, ActorView, "acccount_basic.json")}
@ -32,7 +33,7 @@ defmodule EventosWeb.ActorView do
end
def render("actor.json", %{actor: actor}) do
%{id: actor.id,
output = %{id: actor.id,
username: actor.preferred_username,
domain: actor.domain,
display_name: actor.name,
@ -45,5 +46,14 @@ defmodule EventosWeb.ActorView do
banner: actor.banner_url,
organized_events: render_many(actor.organized_events, EventView, "event_for_actor.json")
}
import Logger
Logger.debug(inspect actor.type)
if actor.type == :Group do
Logger.debug("I'm a group !")
Map.put(output, :members, render_many(Actors.members_for_group(actor), MemberView, "member.json"))
else
Logger.debug("not a group")
output
end
end
end

View File

@ -20,7 +20,8 @@ defmodule EventosWeb.EventView do
def render("event_for_actor.json", %{event: event}) do
%{id: event.id,
title: event.title,
slug: event.slug
slug: event.slug,
uuid: event.uuid,
}
end
@ -31,8 +32,11 @@ defmodule EventosWeb.EventView do
description: event.description,
begins_on: event.begins_on,
ends_on: event.ends_on,
uuid: event.uuid,
organizer: %{
username: event.organizer_actor.preferred_username
username: event.organizer_actor.preferred_username,
display_name: event.organizer_actor.name,
avatar: event.organizer_actor.avatar_url,
},
type: "Event",
}
@ -44,6 +48,7 @@ defmodule EventosWeb.EventView do
description: event.description,
begins_on: event.begins_on,
ends_on: event.ends_on,
uuid: event.uuid,
organizer: render_one(event.organizer_actor, ActorView, "acccount_basic.json"),
participants: render_many(event.participants, ActorView, "show_basic.json"),
address: render_one(event.address, AddressView, "address.json"),

View File

@ -3,7 +3,7 @@ defmodule EventosWeb.MemberView do
View for Members
"""
use EventosWeb, :view
alias EventosWeb.MemberView
alias EventosWeb.{MemberView, ActorView}
def render("index.json", %{members: members}) do
%{data: render_many(members, MemberView, "member.json")}
@ -14,7 +14,10 @@ defmodule EventosWeb.MemberView do
end
def render("member.json", %{member: member}) do
%{id: member.id,
role: member.role}
%{
role: member.role,
actor: render_one(member.actor, ActorView, "acccount_basic.json"),
group: render_one(member.parent, ActorView, "acccount_basic.json")
}
end
end

View File

@ -1,20 +0,0 @@
defmodule EventosWeb.RequestView do
@moduledoc """
View for event request
"""
use EventosWeb, :view
alias EventosWeb.RequestView
def render("index.json", %{requests: requests}) do
%{data: render_many(requests, RequestView, "request.json")}
end
def render("show.json", %{request: request}) do
%{data: render_one(request, RequestView, "request.json")}
end
def render("request.json", %{request: request}) do
%{id: request.id,
state: request.state}
end
end

View File

@ -0,0 +1,15 @@
defmodule Eventos.Repo.Migrations.AddUUIDIdToEvents do
use Ecto.Migration
def up do
alter table(:events) do
add :uuid, :uuid
end
end
def down do
alter table(:events) do
remove :uuid
end
end
end

View File

@ -0,0 +1,15 @@
defmodule Eventos.Repo.Migrations.AddApprovedStatusToMember do
use Ecto.Migration
def up do
alter table(:members) do
add :approved, :boolean, default: true
end
end
def down do
alter table(:members) do
remove :approved
end
end
end