This commit is contained in:
les
2019-09-11 19:12:24 +02:00
parent 93baf01a55
commit 2fe956d117
65 changed files with 762 additions and 721 deletions

View File

@@ -22,7 +22,7 @@ export default {
const month = moment().month() + 1 const month = moment().month() + 1
const year = moment().year() const year = moment().year()
return { return {
page: { month, year}, page: { month, year }
} }
}, },
watch: { watch: {
@@ -35,8 +35,8 @@ export default {
...mapActions(['updateEvents']), ...mapActions(['updateEvents']),
click (day) { click (day) {
const element = document.getElementById(day.day) const element = document.getElementById(day.day)
if (element) element.scrollIntoView(); //Even IE6 supports this if (element) { element.scrollIntoView() } // Even IE6 supports this
}, }
}, },
computed: { computed: {
...mapGetters(['filteredEventsWithPast']), ...mapGetters(['filteredEventsWithPast']),
@@ -53,9 +53,9 @@ export default {
function getColor (event) { function getColor (event) {
const color = { class: event.past && !that.filters.show_past_events ? 'past-event vc-rounded-full' : 'vc-rounded-full', color: 'blue' } const color = { class: event.past && !that.filters.show_past_events ? 'past-event vc-rounded-full' : 'vc-rounded-full', color: 'blue' }
const tag = get(event, 'tags[0]') const tag = get(event, 'tags[0]')
if (!tag) return color if (!tag) { return color }
const idx = tags.indexOf(tag) const idx = tags.indexOf(tag)
if (idx<0) return color if (idx < 0) { return color }
color.color = colors[idx] color.color = colors[idx]
return color return color
} }
@@ -68,11 +68,14 @@ export default {
key: e.id, key: e.id,
dot: color, dot: color,
dates: new Date(e.start_datetime * 1000) dates: new Date(e.start_datetime * 1000)
}})) }
}))
attributes = attributes.concat(this.filteredEventsWithPast attributes = attributes.concat(this.filteredEventsWithPast
.filter(e => e.multidate) .filter(e => e.multidate)
.map( e => ({ key: e.id, highlight: getColor(e), dates: { .map(e => ({ key: e.id,
highlight: getColor(e),
dates: {
start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) } }))) start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) } })))
return attributes return attributes

View File

@@ -32,7 +32,7 @@ export default {
showImage: { showImage: {
type: Boolean, type: Boolean,
default: true default: true
}, }
}, },
computed: { computed: {
date () { date () {

View File

@@ -42,10 +42,10 @@ export default {
] ]
} }
}, },
components: { Calendar, Event },
data () { data () {
return { } return { }
}, },
components: { Calendar, Event },
computed: { computed: {
...mapGetters(['filteredEvents']), ...mapGetters(['filteredEvents']),
...mapState(['events', 'settings']) ...mapState(['events', 'settings'])

View File

@@ -20,17 +20,6 @@ import { mapGetters } from 'vuex'
export default { export default {
name: 'List', name: 'List',
data () {
return { }
},
methods: {
link (event) {
if (event.recurrent) {
return `${event.id}_${event.start_datetime}`
}
return event.id
}
},
props: { props: {
title: { title: {
type: String, type: String,
@@ -52,17 +41,28 @@ export default {
}, },
showTags: { showTags: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showImage: { showImage: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
showDescription: { showDescription: {
type: Boolean, type: Boolean,
default: true default: true
} }
}, },
data () {
return { }
},
methods: {
link (event) {
if (event.recurrent) {
return `${event.id}_${event.start_datetime}`
}
return event.id
}
}
} }
</script> </script>
<style lang='less'> <style lang='less'>

View File

@@ -50,7 +50,7 @@ export default {
could_add () { could_add () {
return (this.$auth.loggedIn || this.settings.allow_anon_event) return (this.$auth.loggedIn || this.settings.allow_anon_event)
}, },
...mapState(['filters', 'settings']), ...mapState(['filters', 'settings'])
}, },
methods: { methods: {
logout () { logout () {

View File

@@ -33,16 +33,16 @@
<script> <script>
import { mapState, mapActions } from 'vuex' import { mapState, mapActions } from 'vuex'
export default { export default {
data () {
return {
onlyMine: false,
}
},
name: 'Search', name: 'Search',
props: { props: {
pastFilter: Boolean, pastFilter: Boolean,
recurrentFilter: Boolean recurrentFilter: Boolean
}, },
data () {
return {
onlyMine: false
}
},
methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents', 'showRecurrentEvents']), methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents', 'showRecurrentEvents']),
computed: { computed: {
...mapState(['tags', 'places', 'filters', 'settings']), ...mapState(['tags', 'places', 'filters', 'settings']),
@@ -70,7 +70,7 @@ export default {
get () { get () {
return this.filters.tags.map(t => 't' + t).concat(this.filters.places.map(p => 'p' + p)) return this.filters.tags.map(t => 't' + t).concat(this.filters.places.map(p => 'p' + p))
} }
}, }
} }
} }
</script> </script>

View File

@@ -36,7 +36,7 @@ export default {
paginatedPlaces () { paginatedPlaces () {
return this.places.slice((this.placePage - 1) * this.perPage, return this.places.slice((this.placePage - 1) * this.perPage,
this.placePage * this.perPage) this.placePage * this.perPage)
}, }
}, },
methods: { methods: {
placeSelected (items) { placeSelected (items) {
@@ -51,8 +51,7 @@ export default {
}, },
async savePlace () { async savePlace () {
const place = await this.$axios.$put('/place', this.place) const place = await this.$axios.$put('/place', this.place)
}, }
} }
} }
</script> </script>

View File

@@ -55,7 +55,7 @@ export default {
userPage: 1, userPage: 1,
new_user: { new_user: {
email: '', email: '',
is_admin: false, is_admin: false
}, },
users_: this.users users_: this.users
} }
@@ -64,7 +64,7 @@ export default {
paginatedUsers () { paginatedUsers () {
return this.users_.slice((this.userPage - 1) * this.perPage, return this.users_.slice((this.userPage - 1) * this.perPage,
this.userPage * this.perPage) this.userPage * this.perPage)
}, }
}, },
methods: { methods: {
async delete_user (user) { async delete_user (user) {
@@ -114,7 +114,7 @@ export default {
message: this.$t(e) message: this.$t(e)
}) })
} }
}, }
} }
} }
</script> </script>

View File

@@ -1,176 +1,175 @@
// Event handling // Event handling
function addEvent (el, type, handler) { function addEvent (el, type, handler) {
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler); if (el.attachEvent) { el.attachEvent('on' + type, handler) } else { el.addEventListener(type, handler) }
} }
function removeEvent (el, type, handler) { function removeEvent (el, type, handler) {
if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler); if (el.detachEvent) { el.detachEvent('on' + type, handler) } else { el.removeEventListener(type, handler) }
} }
// Show/hide mobile menu // Show/hide mobile menu
function toggleNav () { function toggleNav () {
const nav = document.querySelector('.js-main-nav'); const nav = document.querySelector('.js-main-nav')
const auxNav = document.querySelector('.js-aux-nav'); const auxNav = document.querySelector('.js-aux-nav')
const navTrigger = document.querySelector('.js-main-nav-trigger'); const navTrigger = document.querySelector('.js-main-nav-trigger')
const search = document.querySelector('.js-search'); const search = document.querySelector('.js-search')
addEvent(navTrigger, 'click', function () { addEvent(navTrigger, 'click', function () {
var text = navTrigger.innerText; const text = navTrigger.textContent
var textToggle = navTrigger.getAttribute('data-text-toggle'); let textToggle = navTrigger.getAttribute('data-text-toggle')
nav.classList.toggle('nav-open'); nav.classList.toggle('nav-open')
auxNav.classList.toggle('nav-open'); auxNav.classList.toggle('nav-open')
navTrigger.classList.toggle('nav-open'); navTrigger.classList.toggle('nav-open')
search.classList.toggle('nav-open'); search.classList.toggle('nav-open')
navTrigger.innerText = textToggle; navTrigger.textContent = textToggle
navTrigger.setAttribute('data-text-toggle', text); navTrigger.setAttribute('data-text-toggle', text)
textToggle = text; textToggle = text
}) })
} }
// Site search // Site search
function initSearch () { function initSearch () {
var index = lunr(function () { const index = lunr(function () {
this.ref('id'); this.ref('id')
this.field('title', { boost: 20 }); this.field('title', { boost: 20 })
this.field('content', { boost: 10 }); this.field('content', { boost: 10 })
this.field('url'); this.field('url')
}); })
// Get the generated search_data.json file so lunr.js can search it locally. // Get the generated search_data.json file so lunr.js can search it locally.
sc = document.getElementsByTagName("script"); sc = document.getElementsByTagName('script')
source = ''; source = ''
for(idx = 0; idx < sc.length; idx++) for (idx = 0; idx < sc.length; idx++) {
{ s = sc.item(idx)
s = sc.item(idx);
if(s.src && s.src.match(/just-the-docs\.js$/)) if (s.src && s.src.match(/just-the-docs\.js$/)) { source = s.src }
{ source = s.src; }
} }
jsPath = source.replace('just-the-docs.js', ''); jsPath = source.replace('just-the-docs.js', '')
jsonPath = jsPath + 'search-data.json'; jsonPath = jsPath + 'search-data.json'
var request = new XMLHttpRequest(); const request = new XMLHttpRequest()
request.open('GET', jsonPath, true); request.open('GET', jsonPath, true)
request.onload = function () { request.onload = function () {
if (request.status >= 200 && request.status < 400) { if (request.status >= 200 && request.status < 400) {
// Success! // Success!
var data = JSON.parse(request.responseText); const data = JSON.parse(request.responseText)
var keys = Object.keys(data); const keys = Object.keys(data)
for(var i in data) { for (const i in data) {
index.add({ index.add({
id: data[i].id, id: data[i].id,
title: data[i].title, title: data[i].title,
content: data[i].content, content: data[i].content,
url: data[i].url url: data[i].url
}); })
} }
searchResults(data); searchResults(data)
} else { } else {
// We reached our target server, but it returned an error // We reached our target server, but it returned an error
console.log('Error loading ajax request. Request status:' + request.status); console.log('Error loading ajax request. Request status:' + request.status)
}
} }
};
request.onerror = function () { request.onerror = function () {
// There was a connection error of some sort // There was a connection error of some sort
console.log('There was a connection error'); console.log('There was a connection error')
}; }
request.send(); request.send()
function searchResults (dataStore) { function searchResults (dataStore) {
var searchInput = document.querySelector('.js-search-input'); const searchInput = document.querySelector('.js-search-input')
var searchResults = document.querySelector('.js-search-results'); const searchResults = document.querySelector('.js-search-results')
var store = dataStore; const store = dataStore
function hideResults () { function hideResults () {
searchResults.innerHTML = ''; searchResults.innerHTML = ''
searchResults.classList.remove('active'); searchResults.classList.remove('active')
} }
addEvent(searchInput, 'keyup', function (e) { addEvent(searchInput, 'keyup', function (e) {
var query = this.value; const query = this.value
searchResults.innerHTML = ''; searchResults.innerHTML = ''
searchResults.classList.remove('active'); searchResults.classList.remove('active')
if (query === '') { if (query === '') {
hideResults(); hideResults()
} else { } else {
var results = index.search(query); const results = index.search(query)
if (results.length > 0) { if (results.length > 0) {
searchResults.classList.add('active'); searchResults.classList.add('active')
var resultsList = document.createElement('ul'); const resultsList = document.createElement('ul')
searchResults.appendChild(resultsList); searchResults.appendChild(resultsList)
for (var i in results) { for (const i in results) {
var resultsListItem = document.createElement('li'); const resultsListItem = document.createElement('li')
var resultsLink = document.createElement('a'); const resultsLink = document.createElement('a')
var resultsUrlDesc = document.createElement('span'); const resultsUrlDesc = document.createElement('span')
var resultsUrl = store[results[i].ref].url; const resultsUrl = store[results[i].ref].url
var resultsRelUrl = store[results[i].ref].relUrl; const resultsRelUrl = store[results[i].ref].relUrl
var resultsTitle = store[results[i].ref].title; const resultsTitle = store[results[i].ref].title
resultsLink.setAttribute('href', resultsUrl); resultsLink.setAttribute('href', resultsUrl)
resultsLink.innerText = resultsTitle; resultsLink.textContent = resultsTitle
resultsUrlDesc.innerText = resultsRelUrl; resultsUrlDesc.textContent = resultsRelUrl
resultsList.classList.add('search-results-list'); resultsList.classList.add('search-results-list')
resultsListItem.classList.add('search-results-list-item'); resultsListItem.classList.add('search-results-list-item')
resultsLink.classList.add('search-results-link'); resultsLink.classList.add('search-results-link')
resultsUrlDesc.classList.add('fs-2','text-grey-dk-000','d-block'); resultsUrlDesc.classList.add('fs-2', 'text-grey-dk-000', 'd-block')
resultsList.appendChild(resultsListItem); resultsList.appendChild(resultsListItem)
resultsListItem.appendChild(resultsLink); resultsListItem.appendChild(resultsLink)
resultsLink.appendChild(resultsUrlDesc); resultsLink.appendChild(resultsUrlDesc)
} }
} }
// When esc key is pressed, hide the results and clear the field // When esc key is pressed, hide the results and clear the field
if (e.keyCode == 27) { if (e.keyCode == 27) {
hideResults(); hideResults()
searchInput.value = ''; searchInput.value = ''
} }
} }
}); })
addEvent(searchInput, 'blur', function () { addEvent(searchInput, 'blur', function () {
setTimeout(function(){ hideResults() }, 300); setTimeout(function () { hideResults() }, 300)
}); })
} }
} }
function pageFocus () { function pageFocus () {
var mainContent = document.querySelector('.js-main-content'); const mainContent = document.querySelector('.js-main-content')
mainContent.focus(); mainContent.focus()
} }
// Document ready // Document ready
function ready () { function ready () {
toggleNav(); toggleNav()
pageFocus(); pageFocus()
if (typeof lunr !== 'undefined') { if (typeof lunr !== 'undefined') {
initSearch(); initSearch()
} }
} }
// in case the document is already rendered // in case the document is already rendered
if (document.readyState!='loading') ready(); if (document.readyState != 'loading') { ready() }
// modern browsers // modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', ready); else if (document.addEventListener) { document.addEventListener('DOMContentLoaded', ready) }
// IE <= 8 // IE <= 8
else document.attachEvent('onreadystatechange', function(){ else {
if (document.readyState=='complete') ready(); document.attachEvent('onreadystatechange', function () {
}); if (document.readyState == 'complete') { ready() }
})
}

View File

@@ -7,7 +7,7 @@
<script> <script>
export default { export default {
props: ['error'], props: ['error']
} }
</script> </script>
<style lang="less"> <style lang="less">

View File

@@ -157,7 +157,7 @@ Adding this link to your feed reader will keep you up to date.`,
password_updated: 'Password updated', password_updated: 'Password updated',
danger_section: 'Dangerous section', danger_section: 'Dangerous section',
remove_account: 'By pressing the following button your user will be deleted. The events you published instead no.', remove_account: 'By pressing the following button your user will be deleted. The events you published instead no.',
remove_account_confirm: 'You are about to permanently delete your account', remove_account_confirm: 'You are about to permanently delete your account'
}, },
error: { error: {
@@ -171,7 +171,7 @@ Adding this link to your feed reader will keep you up to date.`,
3: 'third', 3: 'third',
4: 'fourth', 4: 'fourth',
5: 'fifth', 5: 'fifth',
[-1]: 'last', [-1]: 'last'
}, },
about: ` about: `

View File

@@ -162,7 +162,7 @@ export default {
password_updated: 'Contraseña modificada', password_updated: 'Contraseña modificada',
danger_section: 'Sección peligrosa', danger_section: 'Sección peligrosa',
remove_account: 'Al presionar el siguiente botón, su usuario será eliminado. No serán eliminados los eventos que publicaste.', remove_account: 'Al presionar el siguiente botón, su usuario será eliminado. No serán eliminados los eventos que publicaste.',
remove_account_confirm: 'Estás por borrar definitivamente tu cuenta', remove_account_confirm: 'Estás por borrar definitivamente tu cuenta'
}, },
error: { error: {
@@ -176,7 +176,7 @@ export default {
3: 'tercero', 3: 'tercero',
4: 'cuarto', 4: 'cuarto',
5: 'quinto', 5: 'quinto',
[-1]: 'último', [-1]: 'último'
}, },
about: ` about: `

View File

@@ -164,7 +164,7 @@ export default {
password_updated: 'Password modificata', password_updated: 'Password modificata',
danger_section: 'Sezione pericolosa', danger_section: 'Sezione pericolosa',
remove_account: 'Premendo il seguente tasto il tuo utente verrà eliminato. Gli eventi da te pubblicati invece no.', remove_account: 'Premendo il seguente tasto il tuo utente verrà eliminato. Gli eventi da te pubblicati invece no.',
remove_account_confirm: 'Stai per eliminare definitivamente il tuo account', remove_account_confirm: 'Stai per eliminare definitivamente il tuo account'
}, },
error: { error: {
@@ -175,7 +175,7 @@ export default {
confirm: { confirm: {
title: 'Conferma utente', title: 'Conferma utente',
not_valid: 'Mmmmm qualcosa è andato storto.', not_valid: 'Mmmmm qualcosa è andato storto.',
valid: 'Il tuo account è stato confermato, ora puoi <a href="/login">entrare</a>', valid: 'Il tuo account è stato confermato, ora puoi <a href="/login">entrare</a>'
}, },
ordinal: { ordinal: {
@@ -184,7 +184,7 @@ export default {
3: 'terzo', 3: 'terzo',
4: 'quarto', 4: 'quarto',
5: 'quinto', 5: 'quinto',
[-1]: 'ultimo', [-1]: 'ultimo'
}, },
about: ` about: `

View File

@@ -8,7 +8,7 @@ module.exports = {
head: { head: {
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }
], ],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }] link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}, },
@@ -29,7 +29,6 @@ module.exports = {
'element-ui/lib/theme-chalk/index.css' 'element-ui/lib/theme-chalk/index.css'
], ],
/* /*
** Plugins to load before mounting the App ** Plugins to load before mounting the App
*/ */
@@ -49,7 +48,7 @@ module.exports = {
['nuxt-express-module', { expressPath: 'server/', routesPath: 'server/routes' }], ['nuxt-express-module', { expressPath: 'server/', routesPath: 'server/routes' }],
// Doc: https://axios.nuxtjs.org/usage // Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios', '@nuxtjs/axios',
'@nuxtjs/auth', '@nuxtjs/auth'
], ],
/* /*
** Axios module configuration ** Axios module configuration
@@ -94,6 +93,6 @@ module.exports = {
splitChunks: { splitChunks: {
layouts: true layouts: true
}, },
cache: true, cache: true
} }
} }

View File

@@ -5,7 +5,7 @@
"author": "lesion", "author": "lesion",
"scripts": { "scripts": {
"dev:nuxt": "cross-env NODE_ENV=development nuxt dev", "dev:nuxt": "cross-env NODE_ENV=development nuxt dev",
"dev": "cross-env DEBUG=*,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js", "dev": "cross-env DEBUG=*,-babel,-follow-redirects,-send,-body-parser:*,-express:*,-connect:*,-sequelize:* NODE_ENV=development node server/index.js",
"build": "nuxt build", "build": "nuxt build",
"start": "cross-env NODE_ENV=production node server/cli.js", "start": "cross-env NODE_ENV=production node server/cli.js",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .", "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",

View File

@@ -30,7 +30,6 @@
el-button.float-right(@click.native='next' :disabled='!couldProceed') {{$t('common.next')}} el-button.float-right(@click.native='next' :disabled='!couldProceed') {{$t('common.next')}}
//- WHERE //- WHERE
el-tab-pane el-tab-pane
span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}} span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}}
@@ -123,6 +122,7 @@ import { Message } from 'element-ui'
export default { export default {
name: 'Add', name: 'Add',
name: 'newEvent',
components: { List }, components: { List },
validate ({ store }) { validate ({ store }) {
return (store.state.auth.loggedIn || store.state.settings.allow_anon_event) return (store.state.auth.loggedIn || store.state.settings.allow_anon_event)
@@ -139,25 +139,26 @@ export default {
event: { event: {
type: 'normal', type: 'normal',
place: { name: '', address: '' }, place: { name: '', address: '' },
title: '', description: '', tags: [], title: '',
description: '',
tags: [],
image: false, image: false,
recurrent: { frequency: '1w', days: [], type: 'weekday' }, recurrent: { frequency: '1w', days: [], type: 'weekday' }
}, },
page: { month, year }, page: { month, year },
fileList: [], fileList: [],
id: null, id: null,
activeTab: "0", activeTab: '0',
date: null, date: null,
time: { start: '20:00', end: null }, time: { start: '20:00', end: null },
edit: false, edit: false,
loading: false loading: false
} }
}, },
name: 'newEvent',
watch: { watch: {
'time.start' (value) { 'time.start' (value) {
if (!value) return if (!value) { return }
let [h, m] = value.split(':') const [h, m] = value.split(':')
this.time.end = (Number(h) + 1) + ':' + m this.time.end = (Number(h) + 1) + ':' + m
}, },
// month selected // month selected
@@ -165,18 +166,6 @@ export default {
this.updateEvents(this.page) this.updateEvents(this.page)
} }
}, },
async fetch ({ store, $axios }) {
try {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
store.commit('setEvents', events)
const { tags, places } = await $axios.$get('/event/meta')
store.commit('update', { tags, places })
} catch(e) {
console.error('Error ', e)
}
moment.locale(store.state.locale)
},
async asyncData ({ params, $axios, error, store }) { async asyncData ({ params, $axios, error, store }) {
if (params.edit) { if (params.edit) {
const data = { time: {}, event: { place: {} } } const data = { time: {}, event: { place: {} } }
@@ -216,6 +205,18 @@ export default {
} }
return {} return {}
}, },
async fetch ({ store, $axios }) {
try {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
store.commit('setEvents', events)
const { tags, places } = await $axios.$get('/event/meta')
store.commit('update', { tags, places })
} catch (e) {
console.error('Error ', e)
}
moment.locale(store.state.locale)
},
computed: { computed: {
...mapState({ ...mapState({
tags: state => state.tags.map(t => t.tag), tags: state => state.tags.map(t => t.tag),
@@ -227,7 +228,7 @@ export default {
}), }),
whenPatterns () { whenPatterns () {
const dates = this.date const dates = this.date
if (!dates || !dates.length) return if (!dates || !dates.length) { return }
const freq = this.event.recurrent.frequency const freq = this.event.recurrent.frequency
const weekDays = uniq(map(dates, date => moment(date).format('dddd'))) const weekDays = uniq(map(dates, date => moment(date).format('dddd')))
@@ -246,23 +247,23 @@ export default {
}, },
todayEvents () { todayEvents () {
if (this.event.type === 'multidate') { if (this.event.type === 'multidate') {
if (!this.date || !this.date.start) return if (!this.date || !this.date.start) { return }
const date_start = moment(this.date.start) const date_start = moment(this.date.start)
const date_end = moment(this.date.end) const date_end = moment(this.date.end)
return this.events.filter(e => return this.events.filter(e =>
!e.multidate ? !e.multidate
date_start.isSame(moment.unix(e.start_datetime), 'day') || ? date_start.isSame(moment.unix(e.start_datetime), 'day') ||
date_start.isBefore(moment.unix(e.start_datime)) && date_end.isAfter(moment.unix(e.start_datetime)) : date_start.isBefore(moment.unix(e.start_datime)) && date_end.isAfter(moment.unix(e.start_datetime))
date_start.isSame(moment.unix(e.start_datetime), 'day') || date_start.isSame(moment.unix(e.end_datetime)) || : date_start.isSame(moment.unix(e.start_datetime), 'day') || date_start.isSame(moment.unix(e.end_datetime)) ||
date_start.isAfter(moment.unix(e.start_datetime)) && date_start.isBefore(moment.unix(e.end_datetime))) date_start.isAfter(moment.unix(e.start_datetime)) && date_start.isBefore(moment.unix(e.end_datetime)))
} else if (this.event.type === 'recurrent') { } else if (this.event.type === 'recurrent') {
} else { } else {
const date = moment(this.date) const date = moment(this.date)
return this.events.filter(e => return this.events.filter(e =>
!e.multidate ? !e.multidate
!e.recurrent && date.isSame(moment.unix(e.start_datetime), 'day') : ? !e.recurrent && date.isSame(moment.unix(e.start_datetime), 'day')
moment.unix(e.start_datetime).isSame(date, 'day') || : moment.unix(e.start_datetime).isSame(date, 'day') ||
moment.unix(e.start_datetime).isBefore(date) && moment.unix(e.end_datetime).isAfter(date) moment.unix(e.start_datetime).isBefore(date) && moment.unix(e.end_datetime).isAfter(date)
) )
} }
@@ -278,7 +279,9 @@ export default {
attributes = attributes.concat(this.filteredEvents attributes = attributes.concat(this.filteredEvents
.filter(e => e.multidate && !e.recurrent) .filter(e => e.multidate && !e.recurrent)
.map( e => ({ key: e.id, highlight: {}, dates: { .map(e => ({ key: e.id,
highlight: {},
dates: {
start: moment.unix(e.start_datetime).toDate(), end: moment.unix(e.end_datetime).toDate() } }))) start: moment.unix(e.start_datetime).toDate(), end: moment.unix(e.end_datetime).toDate() } })))
return attributes return attributes
@@ -297,7 +300,7 @@ export default {
return this.event.place.name.length > 0 && return this.event.place.name.length > 0 &&
this.event.place.address.length > 0 this.event.place.address.length > 0
case 3 + t: case 3 + t:
if (this.date && this.time.start) return true if (this.date && this.time.start) { return true }
case 4 + t: case 4 + t:
return this.event.place.name.length > 0 && return this.event.place.name.length > 0 &&
this.event.place.address.length > 0 && this.event.place.address.length > 0 &&
@@ -309,16 +312,13 @@ export default {
methods: { methods: {
...mapActions(['addEvent', 'updateEvent', 'updateMeta', 'updateEvents']), ...mapActions(['addEvent', 'updateEvent', 'updateMeta', 'updateEvents']),
recurrentDays () { recurrentDays () {
if (this.event.type !== 'recurrent' || !this.date || !this.date.length) return if (this.event.type !== 'recurrent' || !this.date || !this.date.length) { return }
const type = this.event.recurrent.type const type = this.event.recurrent.type
if (type === 'ordinal') if (type === 'ordinal') { return map(this.date, d => moment(d).date()) } else if (type === 'weekday') { return map(this.date, moment(d).day() + 1) }
return map(this.date, d => moment(d).date())
else if (type === 'weekday')
return map(this.date, moment(d).day()+1)
}, },
next () { next () {
this.activeTab = String(Number(this.activeTab) + 1) this.activeTab = String(Number(this.activeTab) + 1)
if (this.activeTab === "2") { if (this.activeTab === '2') {
this.$refs.title.focus() this.$refs.title.focus()
} }
}, },
@@ -374,7 +374,7 @@ export default {
const recurrent = { const recurrent = {
frequency: this.event.recurrent.frequency, frequency: this.event.recurrent.frequency,
days: this.event.recurrent.type === 'ordinal' ? map(this.date, d => moment(d).date()) : map(this.date, d => moment(d).day() + 1), days: this.event.recurrent.type === 'ordinal' ? map(this.date, d => moment(d).date()) : map(this.date, d => moment(d).day() + 1),
type: this.event.recurrent.type, type: this.event.recurrent.type
} }
if (end_hour < start_hour) { if (end_hour < start_hour) {
end_datetime = end_datetime.add(1, 'day') end_datetime = end_datetime.add(1, 'day')
@@ -396,8 +396,7 @@ export default {
if (this.edit) { if (this.edit) {
formData.append('id', this.event.id) formData.append('id', this.event.id)
} }
if (this.event.tags) if (this.event.tags) { this.event.tags.forEach(tag => formData.append('tags[]', tag)) }
this.event.tags.forEach(tag => formData.append('tags[]', tag))
try { try {
if (this.edit) { if (this.edit) {
await this.updateEvent(formData) await this.updateEvent(formData)
@@ -412,7 +411,7 @@ export default {
switch (e.request.status) { switch (e.request.status) {
case 413: case 413:
Message({ type: 'error', showClose: true, message: this.$t('event.image_too_big') }) Message({ type: 'error', showClose: true, message: this.$t('event.image_too_big') })
break; break
default: default:
Message({ type: 'error', showClose: true, message: e }) Message({ type: 'error', showClose: true, message: e })
} }

View File

@@ -61,7 +61,6 @@
el-form-item(v-show='allow_recurrent_event' :label="$t('admin.recurrent_event_visible')") el-form-item(v-show='allow_recurrent_event' :label="$t('admin.recurrent_event_visible')")
el-switch(v-model='recurrent_event_visible') el-switch(v-model='recurrent_event_visible')
el-divider {{$t('admin.federation')}} el-divider {{$t('admin.federation')}}
el-form(inline label-width='400px') el-form(inline label-width='400px')
el-form-item(:label="$t('admin.enable_federation')") el-form-item(:label="$t('admin.enable_federation')")
@@ -91,21 +90,13 @@ export default {
tag: { name: '', color: '' }, tag: { name: '', color: '' },
events: [], events: [],
loading: false, loading: false,
tab: "0", tab: '0',
open: true, open: true
} }
}, },
head () { head () {
return { title: `${this.settings.title} - ${this.$t('common.admin')}` } return { title: `${this.settings.title} - ${this.$t('common.admin')}` }
}, },
async mounted () {
const code = this.$route.query.code
if (code) {
this.tab = "4"
const instance = await this.$axios.$post('/user/code', {code, is_admin: true})
}
},
async asyncData ({ $axios, params, store }) { async asyncData ({ $axios, params, store }) {
try { try {
const users = await $axios.$get('/users') const users = await $axios.$get('/users')
@@ -115,6 +106,14 @@ export default {
console.error(e) console.error(e)
} }
}, },
async mounted () {
const code = this.$route.query.code
if (code) {
this.tab = '4'
const instance = await this.$axios.$post('/user/code', { code, is_admin: true })
}
},
computed: { computed: {
...mapState(['tags', 'settings']), ...mapState(['tags', 'settings']),
allow_registration: { allow_registration: {
@@ -144,7 +143,7 @@ export default {
paginatedTags () { paginatedTags () {
return this.tags.slice((this.tagPage - 1) * this.perPage, return this.tags.slice((this.tagPage - 1) * this.perPage,
this.tagPage * this.perPage) this.tagPage * this.perPage)
}, }
}, },
methods: { methods: {
...mapActions(['setSetting']), ...mapActions(['setSetting']),
@@ -167,7 +166,7 @@ export default {
this.events = this.events.filter(e => e.id !== id) this.events = this.events.filter(e => e.id !== id)
} catch (e) { } catch (e) {
} }
}, }
} }
} }
</script> </script>

View File

@@ -22,13 +22,13 @@ export default {
const now = new Date() const now = new Date()
let params = [] let params = []
if (places) params.push(`places=${places}`) if (places) { params.push(`places=${places}`) }
if (tags) params.push(`tags=${tags}`) if (tags) { params.push(`tags=${tags}`) }
params = params.length ? `?${params.join('&')}` : '' params = params.length ? `?${params.join('&')}` : ''
const events = await $axios.$get(`/export/json${params}`) const events = await $axios.$get(`/export/json${params}`)
return { events, title } return { events, title }
}, }
} }
</script> </script>

View File

@@ -72,14 +72,16 @@ export default {
// }, // },
head () { head () {
if (!this.event) return {} if (!this.event) { return {} }
return { return {
title: `${this.settings.title} - ${this.event.title}`, title: `${this.settings.title} - ${this.event.title}`,
meta: [ meta: [
// hid is used as unique identifier. Do not use `vmid` for it as it will not work // hid is used as unique identifier. Do not use `vmid` for it as it will not work
{ hid: 'description', name: 'description', { hid: 'description',
name: 'description',
content: this.event.description.slice(0, 1000) }, content: this.event.description.slice(0, 1000) },
{ hid: 'og-description', name: 'og:description', { hid: 'og-description',
name: 'og:description',
content: this.event.description.slice(0, 100) }, content: this.event.description.slice(0, 100) },
{ hid: 'og-title', property: 'og:title', content: this.event.title }, { hid: 'og-title', property: 'og:title', content: this.event.title },
{ hid: 'og-url', property: 'og:url', content: `event/${this.event.id}` }, { hid: 'og-url', property: 'og:url', content: `event/${this.event.id}` },
@@ -88,15 +90,6 @@ export default {
] ]
} }
}, },
async fetch ({ $axios, store }) {
try {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
return store.commit('setEvents', events)
} catch(e) {
console.error(e)
}
},
async asyncData ({ $axios, params, error }) { async asyncData ({ $axios, params, error }) {
try { try {
const [ id, start_datetime ] = params.id.split('_') const [ id, start_datetime ] = params.id.split('_')
@@ -108,16 +101,25 @@ export default {
error({ statusCode: 404, message: 'Event not found' }) error({ statusCode: 404, message: 'Event not found' })
} }
}, },
async fetch ({ $axios, store }) {
try {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
return store.commit('setEvents', events)
} catch (e) {
console.error(e)
}
},
computed: { computed: {
...mapGetters(['filteredEvents']), ...mapGetters(['filteredEvents']),
...mapState(['settings']), ...mapState(['settings']),
next () { next () {
let found = false let found = false
const event = this.filteredEvents.find(e => { const event = this.filteredEvents.find(e => {
if (found) return e if (found) { return e }
found = (e.start_datetime === this.event.start_datetime && e.id === this.event.id) found = (e.start_datetime === this.event.start_datetime && e.id === this.event.id)
}) })
if (!event) return false if (!event) { return false }
if (event.recurrent) { if (event.recurrent) {
return `${event.id}_${event.start_datetime}` return `${event.id}_${event.start_datetime}`
} }
@@ -126,10 +128,10 @@ export default {
prev () { prev () {
let event = false let event = false
this.filteredEvents.find(e => { this.filteredEvents.find(e => {
if (e.start_datetime === this.event.start_datetime && e.id === this.event.id) return true if (e.start_datetime === this.event.start_datetime && e.id === this.event.id) { return true }
event = e event = e
}) })
if (!event) return false if (!event) { return false }
if (event.recurrent) { if (event.recurrent) {
return `${event.id}_${event.start_datetime}` return `${event.id}_${event.start_datetime}`
} }
@@ -139,9 +141,9 @@ export default {
return this.event.image_path && '/media/' + this.event.image_path return this.event.image_path && '/media/' + this.event.image_path
}, },
mine () { mine () {
if (!this.$auth.user) return false if (!this.$auth.user) { return false }
return this.event.userId === this.$auth.user.id || this.$auth.user.is_admin return this.event.userId === this.$auth.user.id || this.$auth.user.is_admin
}, }
}, },
methods: { methods: {
...mapActions(['delEvent']), ...mapActions(['delEvent']),
@@ -149,7 +151,7 @@ export default {
return value.replace(/<a.*href="([^">]+).*>(?:.(?!\<\/a\>))*.<\/a>/, (orig, url) => { return value.replace(/<a.*href="([^">]+).*>(?:.(?!\<\/a\>))*.<\/a>/, (orig, url) => {
// get extension // get extension
const ext = url.slice(-4) const ext = url.slice(-4)
if (['.mp3', '.ogg'].indexOf(ext)>-1) { if (['.mp3', '.ogg'].includes(ext)) {
return `<audio controls><source src='${url}'></audio>` return `<audio controls><source src='${url}'></audio>`
} else { } else {
return orig return orig
@@ -164,7 +166,7 @@ export default {
type: 'error' }) type: 'error' })
await this.$axios.delete(`/user/event/${this.id}`) await this.$axios.delete(`/user/event/${this.id}`)
this.delEvent(Number(this.id)) this.delEvent(Number(this.id))
this.$router.replace("/") this.$router.replace('/')
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
@@ -247,4 +249,3 @@ export default {
} }
</style> </style>

View File

@@ -45,7 +45,6 @@
el-input.mb-1(type='textarea' v-model='listScript' readonly ) el-input.mb-1(type='textarea' v-model='listScript' readonly )
el-button.float-right(plain v-clipboard:copy='listScript' type='primary' icon='el-icon-document') {{$t('common.copy')}} el-button.float-right(plain v-clipboard:copy='listScript' type='primary' icon='el-icon-document') {{$t('common.copy')}}
//- TOFIX //- TOFIX
//- el-tab-pane.pt-1(label='calendar' name='calendar') //- el-tab-pane.pt-1(label='calendar' name='calendar')
//- p(v-html='$t(`export.calendar_description`)') //- p(v-html='$t(`export.calendar_description`)')
@@ -76,7 +75,7 @@ export default {
return { return {
type: 'feed', type: 'feed',
notification: { email: '' }, notification: { email: '' },
list: { title: 'Gancio' }, list: { title: 'Gancio' }
} }
}, },
methods: { methods: {
@@ -94,7 +93,7 @@ export default {
}, },
imgPath (event) { imgPath (event) {
return event.image_path && event.image_path return event.image_path && event.image_path
}, }
}, },
computed: { computed: {
...mapState(['filters', 'events', 'settings']), ...mapState(['filters', 'events', 'settings']),
@@ -132,8 +131,8 @@ export default {
return `${this.settings.baseurl}/api/export/${this.type}${query}` return `${this.settings.baseurl}/api/export/${this.type}${query}`
}, },
showLink () { showLink () {
return (['feed', 'ics'].indexOf(this.type)>-1) return (['feed', 'ics'].includes(this.type))
}, }
} }
} }
</script> </script>
@@ -143,5 +142,3 @@ export default {
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

View File

@@ -23,7 +23,6 @@ export default {
} }
}, },
computed: mapState(['events']), computed: mapState(['events']),
components: { Nav, Home }, components: { Nav, Home }
} }
</script> </script>

View File

@@ -41,7 +41,7 @@ export default {
computed: { computed: {
...mapState(['settings']), ...mapState(['settings']),
disabled () { disabled () {
if (process.server) return false if (process.server) { return false }
return !this.email || !this.password return !this.email || !this.password
} }
}, },

View File

@@ -12,7 +12,6 @@
div(v-else) {{$t('recover.not_valid_code')}} div(v-else) {{$t('recover.not_valid_code')}}
</template> </template>
<script> <script>
import { Message } from 'element-ui' import { Message } from 'element-ui'
@@ -27,8 +26,7 @@ export default {
try { try {
const valid = await $axios.$post('/user/check_recover_code', { recover_code: code }) const valid = await $axios.$post('/user/check_recover_code', { recover_code: code })
return { valid, code } return { valid, code }
} } catch (e) {
catch (e) {
return { valid: false } return { valid: false }
} }
}, },
@@ -53,5 +51,3 @@ export default {
} }
} }
</script> </script>

View File

@@ -50,7 +50,7 @@ export default {
computed: { computed: {
...mapState(['settings']), ...mapState(['settings']),
disabled () { disabled () {
if (process.server) return false if (process.server) { return false }
return !this.user.password || !this.user.email || !this.user.description return !this.user.password || !this.user.email || !this.user.description
} }
}, },
@@ -65,7 +65,7 @@ export default {
message: this.$t(`register.${user.is_admin ? 'admin_' : ''}complete`), message: this.$t(`register.${user.is_admin ? 'admin_' : ''}complete`),
type: 'success' type: 'success'
}) })
this.$router.replace("/") this.$router.replace('/')
} catch (e) { } catch (e) {
const error = get(e, 'response.data.errors[0].message', String(e)) const error = get(e, 'response.data.errors[0].message', String(e))
Message({ Message({

View File

@@ -37,13 +37,13 @@ import { Message, MessageBox } from 'element-ui'
import url from 'url' import url from 'url'
export default { export default {
name: 'Settings',
data () { data () {
return { return {
password: '', password: '',
user: { } user: { }
} }
}, },
name: 'Settings',
head () { head () {
return { return {
title: `${this.settings.title} - ${this.$t('common.settings')}` title: `${this.settings.title} - ${this.$t('common.settings')}`
@@ -61,7 +61,7 @@ export default {
}, },
methods: { methods: {
async change_password () { async change_password () {
if (!this.password) return if (!this.password) { return }
const user_data = { id: this.$auth.user.id, password: this.password } const user_data = { id: this.$auth.user.id, password: this.password }
try { try {
const user = await this.$axios.$put('/user', user_data) const user = await this.$axios.$put('/user', user_data)
@@ -93,4 +93,3 @@ export default {
} }
} }
</script> </script>

View File

@@ -27,5 +27,3 @@ export default {
} }
} }
</script> </script>

View File

@@ -4,7 +4,6 @@ import 'dayjs/locale/it'
import 'dayjs/locale/es' import 'dayjs/locale/es'
export default ({ app, store }) => { export default ({ app, store }) => {
// replace links with anchors // replace links with anchors
// TODO: remove fb tracking id // TODO: remove fb tracking id
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')) Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>'))

View File

@@ -10,8 +10,8 @@ export default async ({ app, store }) => {
// This way we can use it in middleware and pages asyncData/fetch // This way we can use it in middleware and pages asyncData/fetch
const user_locale = await app.$axios.$get('/settings/user_locale') const user_locale = await app.$axios.$get('/settings/user_locale')
for(let lang in user_locale) { for (const lang in user_locale) {
if (locales[lang]) merge(locales[lang], user_locale[lang]) if (locales[lang]) { merge(locales[lang], user_locale[lang]) }
} }
app.i18n = new VueI18n({ app.i18n = new VueI18n({

View File

@@ -3,7 +3,7 @@ const { user: User } = require('./models')
const Auth = { const Auth = {
async fillUser (req, res, next) { async fillUser (req, res, next) {
if (!req.user) return next() if (!req.user) { return next() }
req.user = await User.findOne({ req.user = await User.findOne({
where: { id: { [Op.eq]: req.user.id }, is_active: true } where: { id: { [Op.eq]: req.user.id }, is_active: true }
}).catch(e => { }).catch(e => {
@@ -35,9 +35,9 @@ const Auth = {
.status(403) .status(403)
.send({ message: 'Failed to authenticate token ' }) .send({ message: 'Failed to authenticate token ' })
} }
if (req.user.is_admin && req.user.is_active) return next() if (req.user.is_admin && req.user.is_active) { return next() }
return res.status(403).send({ message: 'Admin needed' }) return res.status(403).send({ message: 'Admin needed' })
}, }
} }

View File

@@ -36,7 +36,7 @@ const eventController = {
order: [['weigth', 'DESC']], order: [['weigth', 'DESC']],
attributes: { attributes: {
exclude: ['createdAt', 'updatedAt'] exclude: ['createdAt', 'updatedAt']
}, }
}) })
res.json({ tags, places }) res.json({ tags, places })
@@ -45,16 +45,16 @@ const eventController = {
async getNotifications (event) { async getNotifications (event) {
function match (event, filters) { function match (event, filters) {
// matches if no filter specified // matches if no filter specified
if (!filters) return true if (!filters) { return true }
// check for visibility // check for visibility
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) return false if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
if (!filters.tags && !filters.places) return true if (!filters.tags && !filters.places) { return true }
if (!filters.tags.length && !filters.places.length) return true if (!filters.tags.length && !filters.places.length) { return true }
if (filters.tags.length) { if (filters.tags.length) {
const m = lodash.intersection(event.tags.map(t => t.tag), filters.tags) const m = lodash.intersection(event.tags.map(t => t.tag), filters.tags)
if (m.length > 0) return true if (m.length > 0) { return true }
} }
if (filters.places.length) { if (filters.places.length) {
if (filters.places.find(p => p === event.place.name)) { if (filters.places.find(p => p === event.place.name)) {
@@ -88,7 +88,7 @@ const eventController = {
async get (req, res) { async get (req, res) {
const is_admin = req.user && req.user.is_admin const is_admin = req.user && req.user.is_admin
const id = req.params.event_id const id = req.params.event_id
let event = await Event.findByPk(id, { const event = await Event.findByPk(id, {
plain: true, plain: true,
attributes: { attributes: {
exclude: ['createdAt', 'updatedAt'] exclude: ['createdAt', 'updatedAt']
@@ -112,7 +112,7 @@ const eventController = {
async confirm (req, res) { async confirm (req, res) {
const id = Number(req.params.event_id) const id = Number(req.params.event_id)
const event = await Event.findByPk(id) const event = await Event.findByPk(id)
if (!event) return res.sendStatus(404) if (!event) { return res.sendStatus(404) }
try { try {
event.is_visible = true event.is_visible = true
@@ -131,7 +131,7 @@ const eventController = {
async unconfirm (req, res) { async unconfirm (req, res) {
const id = Number(req.params.event_id) const id = Number(req.params.event_id)
const event = await Event.findByPk(id) const event = await Event.findByPk(id)
if (!event) return sendStatus(404) if (!event) { return sendStatus(404) }
try { try {
event.is_visible = false event.is_visible = false
@@ -193,7 +193,7 @@ const eventController = {
.endOf('month') .endOf('month')
const shownDays = end.diff(start, 'days') const shownDays = end.diff(start, 'days')
if (shownDays <= 35) end = end.add(1, 'week') if (shownDays <= 35) { end = end.add(1, 'week') }
end = end.endOf('week') end = end.endOf('week')
let events = await Event.findAll({ let events = await Event.findAll({
@@ -226,7 +226,7 @@ const eventController = {
function createEventsFromRecurrent (e, dueTo = null) { function createEventsFromRecurrent (e, dueTo = null) {
const events = [] const events = []
const recurrent = JSON.parse(e.recurrent) const recurrent = JSON.parse(e.recurrent)
if (!recurrent.frequency) return false if (!recurrent.frequency) { return false }
let cursor = moment(start).startOf('week') let cursor = moment(start).startOf('week')
const start_date = moment.unix(e.start_datetime) const start_date = moment.unix(e.start_datetime)
@@ -244,10 +244,10 @@ const eventController = {
cursor.add(days[0] - 1, 'day') cursor.add(days[0] - 1, 'day')
if (frequency === '2w') { if (frequency === '2w') {
const nWeeks = cursor.diff(e.start_datetime, 'w') % 2 const nWeeks = cursor.diff(e.start_datetime, 'w') % 2
if (!nWeeks) cursor.add(1, 'week') if (!nWeeks) { cursor.add(1, 'week') }
} }
toAdd.n = Number(frequency[0]) toAdd.n = Number(frequency[0])
toAdd.unit = 'week'; toAdd.unit = 'week'
// cursor.set('hour', start_date.hour()).set('minute', start_date.minutes()) // cursor.set('hour', start_date.hour()).set('minute', start_date.minutes())
} }
@@ -265,31 +265,29 @@ const eventController = {
// add event at specified frequency // add event at specified frequency
while (true) { while (true) {
let first_event_of_week = cursor.clone() const first_event_of_week = cursor.clone()
days.forEach(d => { days.forEach(d => {
if (type === 'ordinal') { if (type === 'ordinal') {
cursor.date(d) cursor.date(d)
} else { } else {
cursor.day(d - 1) cursor.day(d - 1)
} }
if (cursor.isAfter(dueTo) || cursor.isBefore(start)) return if (cursor.isAfter(dueTo) || cursor.isBefore(start)) { return }
e.start_datetime = cursor.unix() e.start_datetime = cursor.unix()
e.end_datetime = e.start_datetime + duration e.end_datetime = e.start_datetime + duration
events.push(Object.assign({}, e)) events.push(Object.assign({}, e))
}) })
if (cursor.isAfter(dueTo)) break if (cursor.isAfter(dueTo)) { break }
cursor = first_event_of_week.add(toAdd.n, toAdd.unit) cursor = first_event_of_week.add(toAdd.n, toAdd.unit)
} }
return events return events
} }
let allEvents = events.filter(e => !e.recurrent) let allEvents = events.filter(e => !e.recurrent)
events.filter(e => e.recurrent).forEach(e => { events.filter(e => e.recurrent).forEach(e => {
const events = createEventsFromRecurrent(e, end) const events = createEventsFromRecurrent(e, end)
if (events) if (events) { allEvents = allEvents.concat(events) }
allEvents = allEvents.concat(events)
}) })
// allEvents.sort((a,b) => a.start_datetime-b.start_datetime) // allEvents.sort((a,b) => a.start_datetime-b.start_datetime)

View File

@@ -88,7 +88,7 @@ const userController = {
eventDetails.image_path = req.file.filename eventDetails.image_path = req.file.filename
} }
let event = await Event.create(eventDetails) const event = await Event.create(eventDetails)
// create place if needed // create place if needed
let place let place
@@ -118,14 +118,12 @@ const userController = {
// send response to client // send response to client
res.json(event) res.json(event)
if (req.user) if (req.user) { federation.sendEvent(event, req.user) }
federation.sendEvent(event, req.user)
// res.sendStatus(200) // res.sendStatus(200)
// send notification (mastodon/email/confirmation) // send notification (mastodon/email/confirmation)
// notifier.notifyEvent(event.id) // notifier.notifyEvent(event.id)
}, },
async updateEvent (req, res) { async updateEvent (req, res) {
@@ -171,7 +169,7 @@ const userController = {
async forgotPassword (req, res) { async forgotPassword (req, res) {
const email = req.body.email const email = req.body.email
const user = await User.findOne({ where: { email: { [Op.eq]: email } } }) const user = await User.findOne({ where: { email: { [Op.eq]: email } } })
if (!user) return res.sendStatus(200) if (!user) { return res.sendStatus(200) }
user.recover_code = crypto.randomBytes(16).toString('hex') user.recover_code = crypto.randomBytes(16).toString('hex')
mail.send(user.email, 'recover', { user, config }) mail.send(user.email, 'recover', { user, config })
@@ -182,9 +180,9 @@ const userController = {
async checkRecoverCode (req, res) { async checkRecoverCode (req, res) {
const recover_code = req.body.recover_code const recover_code = req.body.recover_code
if (!recover_code) return res.sendStatus(400) if (!recover_code) { return res.sendStatus(400) }
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } }) const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
if (!user) return res.sendStatus(400) if (!user) { return res.sendStatus(400) }
try { try {
await user.update({ recover_code: '' }) await user.update({ recover_code: '' })
res.sendStatus(200) res.sendStatus(200)
@@ -196,9 +194,9 @@ const userController = {
async updatePasswordWithRecoverCode (req, res) { async updatePasswordWithRecoverCode (req, res) {
const recover_code = req.body.recover_code const recover_code = req.body.recover_code
const password = req.body.password const password = req.body.password
if (!recover_code || !password) return res.sendStatus(400) if (!recover_code || !password) { return res.sendStatus(400) }
const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } }) const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } })
if (!user) return res.sendStatus(400) if (!user) { return res.sendStatus(400) }
user.recover_code = '' user.recover_code = ''
user.password = password user.password = password
try { try {
@@ -224,7 +222,7 @@ const userController = {
// user to modify // user to modify
user = await User.findByPk(req.body.id) user = await User.findByPk(req.body.id)
if (!user) return res.status(404).json({ success: false, message: 'User not found!' }) if (!user) { return res.status(404).json({ success: false, message: 'User not found!' }) }
if (req.body.id !== req.user.id && !req.user.is_admin) { if (req.body.id !== req.user.id && !req.user.is_admin) {
return res.status(400).json({ succes: false, message: 'Not allowed' }) return res.status(400).json({ succes: false, message: 'Not allowed' })
@@ -233,8 +231,7 @@ const userController = {
// ensure username to not change if not empty // ensure username to not change if not empty
req.body.username = user.username ? user.username : req.body.username req.body.username = user.username ? user.username : req.body.username
if (!req.body.password) if (!req.body.password) { delete req.body.password }
delete req.body.password
await user.update(req.body) await user.update(req.body)
@@ -244,9 +241,8 @@ const userController = {
res.json(user) res.json(user)
}, },
async register (req, res) { async register (req, res) {
if (!settingsController.settings.allow_registration) return res.sendStatus(404) if (!settingsController.settings.allow_registration) { return res.sendStatus(404) }
const n_users = await User.count() const n_users = await User.count()
try { try {
// the first registered user will be an active admin // the first registered user will be an active admin

View File

@@ -14,6 +14,8 @@ const settingsController = require('./controller/settings')
const storage = require('./storage') const storage = require('./storage')
const upload = multer({ storage }) const upload = multer({ storage })
const debug = require('debug')('api')
const api = express.Router() const api = express.Router()
api.use(cookieParser()) api.use(cookieParser())
api.use(bodyParser.urlencoded({ extended: false })) api.use(bodyParser.urlencoded({ extended: false }))
@@ -24,10 +26,10 @@ const jwt = expressJwt({
credentialsRequired: false, credentialsRequired: false,
getToken: function fromHeaderOrQuerystring (req) { getToken: function fromHeaderOrQuerystring (req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1]; return req.headers.authorization.split(' ')[1]
} else if (req.cookies && req.cookies['auth._token.local']) { } else if (req.cookies && req.cookies['auth._token.local']) {
const [ prefix, token ] = req.cookies['auth._token.local'].split(' ') const [ prefix, token ] = req.cookies['auth._token.local'].split(' ')
if (prefix === 'Bearer') return token if (prefix === 'Bearer') { return token }
} }
} }
}) })
@@ -100,4 +102,16 @@ api.get('/export/:type', exportController.export)
api.get('/event/:month/:year', eventController.getAll) api.get('/event/:month/:year', eventController.getAll)
// Handle 404
api.use((req, res) => {
debug('404 Page not found: %s', req.path)
res.status(404).send('404: Page not Found')
})
// Handle 500
api.use((error, req, res, next) => {
debug(error)
res.status(500).send('500: Internal Server Error')
})
module.exports = api module.exports = api

View File

@@ -30,7 +30,7 @@ const mail = {
updateFiles: false, updateFiles: false,
defaultLocale: settings.locale, defaultLocale: settings.locale,
locale: settings.locale, locale: settings.locale,
locales: ['it', 'es'], locales: ['it', 'es']
}, },
transport: config.smtp transport: config.smtp
}) })

View File

@@ -4,7 +4,7 @@
activitypub_id: { activitypub_id: {
type: DataTypes.STRING(18), type: DataTypes.STRING(18),
index: true, index: true,
unique: true, unique: true
}, },
data: DataTypes.JSON data: DataTypes.JSON
}, {}) }, {})
@@ -12,4 +12,4 @@
comment.belongsTo(models.event) comment.belongsTo(models.event)
} }
return comment return comment
}; }

View File

@@ -6,7 +6,7 @@ module.exports = (sequelize, DataTypes) => {
id: { id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
primaryKey: true, primaryKey: true,
autoIncrement: true, autoIncrement: true
}, },
title: DataTypes.STRING, title: DataTypes.STRING,
slug: DataTypes.STRING, slug: DataTypes.STRING,
@@ -45,7 +45,7 @@ module.exports = (sequelize, DataTypes) => {
${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}<br/> ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}<br/>
${tags} <br/>` ${tags} <br/>`
let attachment = [] const attachment = []
if (this.image_path) { if (this.image_path) {
attachment.push({ attachment.push({
type: 'Document', type: 'Document',
@@ -62,19 +62,22 @@ module.exports = (sequelize, DataTypes) => {
// actor: `${config.baseurl}/federation/u/${username}`, // actor: `${config.baseurl}/federation/u/${username}`,
// url: `${config.baseurl}/federation/m/${this.id}`, // url: `${config.baseurl}/federation/m/${this.id}`,
// object: { // object: {
type: 'Note',
id: `${config.baseurl}/federation/m/${this.id}`,
url: `${config.baseurl}/federation/m/${this.id}`,
attachment, attachment,
tag: this.tags.map(tag => ({ tag: this.tags.map(tag => ({
type: 'Hashtag', type: 'Hashtag',
name: '#' + tag.tag name: '#' + tag.tag
})), })),
id: `${config.baseurl}/federation/m/${this.id}`,
type: 'Note',
published: this.createdAt, published: this.createdAt,
attributedTo: `${config.baseurl}/federation/u/${username}`, attributedTo: `${config.baseurl}/federation/u/${username}`,
to: 'https://www.w3.org/ns/activitystreams#Public', to: ['https://www.w3.org/ns/activitystreams#Public'],
cc: follower ? follower: [], cc: follower || [],
content content,
} summary: null,
sensitive: false,
// }
} }
} }

View File

@@ -32,4 +32,3 @@ fs
db.Sequelize = Sequelize db.Sequelize = Sequelize
module.exports = db module.exports = db

View File

@@ -3,7 +3,8 @@ module.exports = (sequelize, DataTypes) => {
const place = sequelize.define('place', { const place = sequelize.define('place', {
name: { name: {
type: DataTypes.STRING, type: DataTypes.STRING,
unique: true, index: true, unique: true,
index: true,
allowNull: false allowNull: false
}, },
address: DataTypes.STRING address: DataTypes.STRING

View File

@@ -15,4 +15,4 @@ module.exports = (sequelize, DataTypes) => {
} }
return tag return tag
}; }

View File

@@ -46,7 +46,7 @@ module.exports = (sequelize, DataTypes) => {
} }
user.prototype.comparePassword = async function (pwd) { user.prototype.comparePassword = async function (pwd) {
if (!this.password) return false if (!this.password) { return false }
const ret = await bcrypt.compare(pwd, this.password) const ret = await bcrypt.compare(pwd, this.password)
return ret return ret
} }
@@ -78,4 +78,4 @@ module.exports = (sequelize, DataTypes) => {
}) })
return user return user
}; }

View File

@@ -11,7 +11,7 @@ module.exports = {
console.error('inReplyTo', inReplyTo) console.error('inReplyTo', inReplyTo)
console.error('match', match) console.error('match', match)
if (!match || match.length < 2) { if (!match || match.length < 2) {
debug("Comment not found %s", inReplyTo) debug('Comment not found %s', inReplyTo)
return res.status(404).send('Event not found!') return res.status(404).send('Event not found!')
} }
let event = await Event.findByPk(Number(match[1])) let event = await Event.findByPk(Number(match[1]))
@@ -20,7 +20,7 @@ module.exports = {
if (!event) { if (!event) {
// in reply to another comment... // in reply to another comment...
const comment = await Comment.findOne({ where: { activitypub_id: inReplyTo }, include: [Event] }) const comment = await Comment.findOne({ where: { activitypub_id: inReplyTo }, include: [Event] })
if (!comment) return res.status(404).send('Not found') if (!comment) { return res.status(404).send('Not found') }
event = comment.event event = comment.event
} }
debug('comment from %s to "%s"', req.body.actor, event.title) debug('comment from %s to "%s"', req.body.actor, event.title)

View File

@@ -5,20 +5,20 @@ const debug = require('debug')('fediverse:ego')
module.exports = { module.exports = {
async boost (req, res) { async boost (req, res) {
const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`) const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)
if (!match || match.length<2) return res.status(404).send('Event not found!') if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
debug('boost %s', match[1]) debug('boost %s', match[1])
const event = await Event.findByPk(Number(match[1])) const event = await Event.findByPk(Number(match[1]))
if (!event) return res.status(404).send('Event not found!') if (!event) { return res.status(404).send('Event not found!') }
await event.update({ boost: [...event.boost, req.body.actor] }) await event.update({ boost: [...event.boost, req.body.actor] })
res.sendStatus(201) res.sendStatus(201)
}, },
async bookmark (req, res) { async bookmark (req, res) {
const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`) const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`)
if (!match || match.length<2) return res.status(404).send('Event not found!') if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
const event = await Event.findByPk(Number(match[1])) const event = await Event.findByPk(Number(match[1]))
debug('%s bookmark %s (%d)', req.body.actor, event.title, event.likes.length) debug('%s bookmark %s (%d)', req.body.actor, event.title, event.likes.length)
if (!event) return res.status(404).send('Event not found!') if (!event) { return res.status(404).send('Event not found!') }
await event.update({ likes: [...event.likes, req.body.actor] }) await event.update({ likes: [...event.likes, req.body.actor] })
res.sendStatus(201) res.sendStatus(201)
}, },
@@ -27,10 +27,10 @@ module.exports = {
const body = req.body const body = req.body
const object = body.object const object = body.object
const match = object.object.match(`${config.baseurl}/federation/m/(.*)`) const match = object.object.match(`${config.baseurl}/federation/m/(.*)`)
if (!match || match.length<2) return res.status(404).send('Event not found!') if (!match || match.length < 2) { return res.status(404).send('Event not found!') }
const event = await Event.findByPk(Number(match[1])) const event = await Event.findByPk(Number(match[1]))
debug('%s unbookmark %s (%d)', body.actor, event.title, event.likes.length) debug('%s unbookmark %s (%d)', body.actor, event.title, event.likes.length)
if (!event) return res.status(404).send('Event not found!') if (!event) { return res.status(404).send('Event not found!') }
await event.update({ likes: [...event.likes.filter(actor => actor !== body.actor)] }) await event.update({ likes: [...event.likes.filter(actor => actor !== body.actor)] })
res.sendStatus(201) res.sendStatus(201)
} }

View File

@@ -8,25 +8,25 @@ module.exports = {
// follow request from fediverse // follow request from fediverse
async follow (req, res) { async follow (req, res) {
const body = req.body const body = req.body
if (typeof body.object !== 'string') return if (typeof body.object !== 'string') { return }
const username = body.object.replace(`${config.baseurl}/federation/u/`, '') const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
const user = await User.findOne({ where: { username } }) const user = await User.findOne({ where: { username } })
if (!user) return res.status(404).send('User not found') if (!user) { return res.status(404).send('User not found') }
// check for duplicate // check for duplicate
if (user.followers.indexOf(body.actor) === -1) { if (!user.followers.includes(body.actor)) {
await user.update({ followers: [...user.followers, body.actor] }) await user.update({ followers: [...user.followers, body.actor] })
debug('%s followed by %s (%d)', username, body.actor, user.followers.length) debug('%s followed by %s (%d)', username, body.actor, user.followers.length)
} else { } else {
debug('duplicate %s followed by %s', username, body.actor) debug('duplicate %s followed by %s', username, body.actor)
} }
const guid = crypto.randomBytes(16).toString('hex') const guid = crypto.randomBytes(16).toString('hex')
let message = { const message = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
'id': `${config.baseurl}/federation/${guid}`, 'id': `${config.baseurl}/federation/${guid}`,
'type': 'Accept', 'type': 'Accept',
'actor': `${config.baseurl}/federation/u/${user.username}`, 'actor': `${config.baseurl}/federation/u/${user.username}`,
'object': body, 'object': body
} }
Helpers.signAndSend(message, user, body.actor) Helpers.signAndSend(message, user, body.actor)
res.sendStatus(200) res.sendStatus(200)
@@ -37,7 +37,7 @@ module.exports = {
const body = req.body const body = req.body
const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '') const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '')
const user = await User.findOne({ where: { username } }) const user = await User.findOne({ where: { username } })
if (!user) return res.status(404).send('User not found') if (!user) { return res.status(404).send('User not found') }
if (body.actor !== body.object.actor) { if (body.actor !== body.object.actor) {
debug('Unfollow an user created by a different actor !?!?') debug('Unfollow an user created by a different actor !?!?')

View File

@@ -36,7 +36,6 @@ const Helpers = {
}, },
method: 'POST', method: 'POST',
body: JSON.stringify(message) }) body: JSON.stringify(message) })
}, },
async sendEvent (event, user) { async sendEvent (event, user) {
@@ -48,7 +47,7 @@ const Helpers = {
return return
} }
for(let follower of instanceAdmin.followers) { for (const follower of instanceAdmin.followers) {
debug('Notify %s with event %s', follower, event.title) debug('Notify %s with event %s', follower, event.title)
const body = { const body = {
id: `${config.baseurl}/federation/m/c_${event.id}`, id: `${config.baseurl}/federation/m/c_${event.id}`,
@@ -67,14 +66,16 @@ const Helpers = {
return return
} }
if (!user.settings.enable_federation || !user.username) return if (!user.settings.enable_federation || !user.username) { return }
for(let follower of user.followers) { for (const follower of user.followers) {
debug('Notify %s with event %s', follower, event.title) debug('Notify %s with event %s', follower, event.title)
const body = { const body = {
id: `${config.baseurl}/federation/m/c_${event.id}`, id: `${config.baseurl}/federation/m/${event.id}#create`,
type: 'Create', type: 'Create',
to: ['https://www.w3.org/ns/activitystreams#Public'],
cc: [`${config.baseurl}/federation/u/${user.username}/followers`],
published: event.createdAt,
actor: `${config.baseurl}/federation/u/${user.username}`, actor: `${config.baseurl}/federation/u/${user.username}`,
url: `${config.baseurl}/federation/m/${event.id}`,
object: event.toAP(user.username, follower) object: event.toAP(user.username, follower)
} }
body['@context'] = 'https://www.w3.org/ns/activitystreams' body['@context'] = 'https://www.w3.org/ns/activitystreams'
@@ -92,7 +93,7 @@ const Helpers = {
// TODO: cache // TODO: cache
async getActor (url, force = false) { async getActor (url, force = false) {
// try with cache first // try with cache first
if (!force && actorCache[url]) return actorCache[url] if (!force && actorCache[url]) { return actorCache[url] }
const user = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } }) const user = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } })
.then(res => { .then(res => {
if (!res.ok) { if (!res.ok) {
@@ -108,7 +109,7 @@ const Helpers = {
// ref: https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ // ref: https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
async verifySignature (req, res, next) { async verifySignature (req, res, next) {
let user = await Helpers.getActor(req.body.actor) let user = await Helpers.getActor(req.body.actor)
if (!user) return res.status(401).send('Actor not found') if (!user) { return res.status(401).send('Actor not found') }
// little hack -> https://github.com/joyent/node-http-signature/pull/83 // little hack -> https://github.com/joyent/node-http-signature/pull/83
req.headers.authorization = 'Signature ' + req.headers.signature req.headers.authorization = 'Signature ' + req.headers.signature
@@ -117,12 +118,12 @@ const Helpers = {
// https://github.com/joyent/node-http-signature/issues/87 // https://github.com/joyent/node-http-signature/issues/87
req.url = '/federation' + req.url req.url = '/federation' + req.url
const parsed = httpSignature.parseRequest(req) const parsed = httpSignature.parseRequest(req)
if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next() if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() }
// signature not valid, try without cache // signature not valid, try without cache
user = await Helpers.getActor(req.body.actor, true) user = await Helpers.getActor(req.body.actor, true)
if (!user) return res.status(401).send('Actor not found') if (!user) { return res.status(401).send('Actor not found') }
if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next() if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() }
// still not valid // still not valid
debug('Invalid signature from user %s', req.body.actor) debug('Invalid signature from user %s', req.body.actor)

View File

@@ -8,6 +8,7 @@ const { event: Event, user: User, tag: Tag, place: Place } = require('../api/mod
const Comments = require('./comments') const Comments = require('./comments')
const Helpers = require('./helpers') const Helpers = require('./helpers')
const Ego = require('./ego') const Ego = require('./ego')
const debug = require('debug')('federation')
/** /**
* Federation is calling! * Federation is calling!
@@ -16,22 +17,20 @@ const Ego = require('./ego')
router.use(cors()) router.use(cors())
router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] })) router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] }))
router.get('/m/:event_id', async (req, res) => { router.get('/m/:event_id', async (req, res) => {
const event_id = req.params.event_id const event_id = req.params.event_id
// if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`) // if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`)
const event = await Event.findByPk(req.params.event_id, { include: [ User, Tag, Place ] }) const event = await Event.findByPk(req.params.event_id, { include: [ User, Tag, Place ] })
if (!event) return res.status(404).send('Not found') if (!event) { return res.status(404).send('Not found') }
return res.json(event.toAP(event.user.username)) return res.json(event.toAP(event.user.username))
}) })
// get any message coming from federation // get any message coming from federation
// Federation is calling! // Federation is calling!
router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => { router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => {
const b = req.body const b = req.body
debug(b.type)
switch (b.type) { switch (b.type) {
case 'Follow': case 'Follow':
Follows.follow(req, res) Follows.follow(req, res)

View File

@@ -21,6 +21,16 @@ module.exports = {
inbox: `${config.baseurl}/federation/u/${name}/inbox`, inbox: `${config.baseurl}/federation/u/${name}/inbox`,
outbox: `${config.baseurl}/federation/u/${name}/outbox`, outbox: `${config.baseurl}/federation/u/${name}/outbox`,
followers: `${config.baseurl}/federation/u/${name}/followers`, followers: `${config.baseurl}/federation/u/${name}/followers`,
attachment: [{
type: 'PropertyValue',
name: 'Website',
value: `<a href='${config.baseurl}'>${config.baseurl}</a>`
}],
icon: {
type: 'Image',
mediaType: 'image/jpeg',
url: config.baseurl + '/favicon.ico'
},
publicKey: { publicKey: {
id: `${config.baseurl}/federation/u/${name}#main-key`, id: `${config.baseurl}/federation/u/${name}#main-key`,
owner: `${config.baseurl}/federation/u/${name}`, owner: `${config.baseurl}/federation/u/${name}`,
@@ -32,24 +42,35 @@ module.exports = {
}, },
async followers (req, res) { async followers (req, res) {
const name = req.params.name const name = req.params.name
const page = req.query.page
debug('Retrieve %s followers', name)
if (!name) return res.status(400).send('Bad request.') if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }}) const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`) if (!user) return res.status(404).send(`No record found for ${name}`)
const ret = {
'@context': [ 'https://www.w3.org/ns/activitystreams' ], res.type('application/activity+json; charset=utf-8')
if (!page) {
debug('No pagination')
return res.json({
'@context': 'https://www.w3.org/ns/activitystreams',
id: `${config.baseurl}/federation/u/${name}/followers`, id: `${config.baseurl}/federation/u/${name}/followers`,
type: 'OrderedCollection', type: 'OrderedCollection',
totalItems: user.followers.length, totalItems: user.followers.length,
first: { first: `${config.baseurl}/federation/u/${name}/followers?page=true`,
id: `${config.baseurl}/federation/u/${name}/followers?page=1`, last: `${config.baseurl}/federation/u/${name}/followers?page=true`,
})
}
return res.json({
'@context': 'https://www.w3.org/ns/activitystreams',
id: `${config.baseurl}/federation/u/${name}/followers?page=${page}`,
type: 'OrderedCollectionPage', type: 'OrderedCollectionPage',
totalItems: user.followers.length, totalItems: user.followers.length,
partOf: `${config.baseurl}/federation/u/${name}/followers` , partOf: `${config.baseurl}/federation/u/${name}/followers` ,
orderedItems: user.followers, orderedItems: user.followers
} })
}
res.json(ret)
}, },
async outbox (req, res) { async outbox (req, res) {
const name = req.params.name const name = req.params.name
const page = req.query.page const page = req.query.page
@@ -61,33 +82,39 @@ module.exports = {
}) })
if (!user) return res.status(404).send(`No record found for ${name}`) if (!user) return res.status(404).send(`No record found for ${name}`)
debug('Inside outbox, should return all events from this user') debug('Inside outbox, should return all events from this user')
// https://www.w3.org/TR/activitypub/#outbox // https://www.w3.org/TR/activitypub/#outbox
res.type('application/activity+json; charset=utf-8')
if (!page) { if (!page) {
const ret = { debug('Without pagination ')
return res.json({
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
id: `${config.baseurl}/federation/u/${name}/outbox`, id: `${config.baseurl}/federation/u/${name}/outbox`,
type: 'OrderedCollection', type: 'OrderedCollection',
totalItems: user.events.length, totalItems: user.events.length,
first: { first: `${config.baseurl}/federation/u/${name}/outbox?page=true`,
id: `${config.baseurl}/federation/u/${name}/outbox?page=true`, last: `${config.baseurl}/federation/u/${name}/outbox?page=true`
type: 'OrderedCollectionPage', })
totalItem: user.events.length,
partOf: `${config.baseurl}/federation/u/${name}/outbox`,
orderedItems: user.events.map(e => e.toAP(user.username))
} }
}
res.type('application/activity+json; charset=utf-8') debug('With pagination %s', page)
return res.json(ret) return res.json({
}
const ret = {
'@context': 'https://www.w3.org/ns/activitystreams', '@context': 'https://www.w3.org/ns/activitystreams',
id: `${config.baseurl}/federation/u/${name}/outbox?page=true`, id: `${config.baseurl}/federation/u/${name}/outbox?page=${page}`,
type: 'OrderedCollectionPage', type: 'OrderedCollectionPage',
totalItems: user.followers.length,
partOf: `${config.baseurl}/federation/u/${name}/outbox` , partOf: `${config.baseurl}/federation/u/${name}/outbox` ,
orderedItems: user.events.map(e => e.toAP(user.username)) orderedItems: user.events.map(e => ({
} id: `${config.baseurl}/federation/m/${e.id}#create`,
res.type('application/activity+json; charset=utf-8') type: 'Create',
res.json(ret) to: ['https://www.w3.org/ns/activitystreams#Public'],
cc: [`${config.baseurl}/federation/u/${user.username}/followers`],
published: e.createdAt,
actor: `${config.baseurl}/federation/u/${user.username}`,
object: e.toAP(user.username)
}))
})
} }
} }

View File

@@ -6,18 +6,30 @@ const settingsController = require('../api/controller/settings')
const config = require('config') const config = require('config')
const version = require('../../package.json').version const version = require('../../package.json').version
const url = require('url') const url = require('url')
const debug = require('debug')('webfinger')
router.use(cors()) router.use(cors())
router.get('/webfinger', async (req, res) => { router.get('/webfinger', async (req, res) => {
const resource = req.query.resource const resource = req.query.resource
if (!resource || !resource.includes('acct:')) { if (!resource || !resource.includes('acct:')) {
debug('Bad webfinger request => %s', resource.query)
return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.') return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
} }
const name = resource.match(/acct:(.*)@/)[1]
const domain = url.parse(config.baseurl).host const domain = url.parse(config.baseurl).host
const [, name, req_domain] = resource.match(/acct:(.*)@(.*)/)
if (domain !== req_domain) {
debug('Bad webfinger request, requested domain "%s" instead of "%s"', req_domain, domain)
return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
}
const user = await User.findOne({ where: { username: name } }) const user = await User.findOne({ where: { username: name } })
if (!user) return res.status(404).send(`No record found for ${name}`) if (!user) {
debug('User not found: %s', name)
return res.status(404).send(`No record found for ${name}`)
}
const ret = { const ret = {
subject: `acct:${name}@${domain}`, subject: `acct:${name}@${domain}`,
links: [ links: [
@@ -40,7 +52,7 @@ router.get('/nodeinfo/:nodeinfo_version', async (req, res) => {
}, },
openRegistrations: settingsController.settings.allow_registration, openRegistrations: settingsController.settings.allow_registration,
protocols: ['activitypub'], protocols: ['activitypub'],
services: { inbound: [], outbound :["atom1.0"]}, services: { inbound: [], outbound: ['atom1.0'] },
software: { software: {
name: 'gancio', name: 'gancio',
version version
@@ -83,18 +95,16 @@ router.get('/x-nodeinfo2', async (req, res) => {
res.json(ret) res.json(ret)
}) })
router.get('/nodeinfo', async (req, res) => { router.get('/nodeinfo', async (req, res) => {
const ret = { const ret = {
links: [ links: [
{ href: `${config.baseurl}/.well-known/nodeinfo/2.0`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.0` }, { href: `${config.baseurl}/.well-known/nodeinfo/2.0`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.0` },
{ href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` }, { href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` }
] ]
} }
res.json(ret) res.json(ret)
}) })
router.use('/host-meta', (req, res) => { router.use('/host-meta', (req, res) => {
res.type('application/xml') res.type('application/xml')
res.send(`<?xml version="1.0" encoding="UTF-8"?> res.send(`<?xml version="1.0" encoding="UTF-8"?>
@@ -104,12 +114,14 @@ router.use('/host-meta', (req, res) => {
}) })
// Handle 404 // Handle 404
router.use(function(req, res) { router.use((req, res) => {
debug('404 Page not found: %s', req.path)
res.status(404).send('404: Page not Found') res.status(404).send('404: Page not Found')
}) })
// Handle 500 // Handle 500
router.use(function(error, req, res, next) { router.use((error, req, res, next) => {
debug(error)
res.status(500).send('500: Internal Server Error') res.status(500).send('500: Internal Server Error')
}) })

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -26,4 +26,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -23,4 +23,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -24,4 +24,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -25,4 +25,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -1,4 +1,4 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
up: (queryInterface, Sequelize) => { up: (queryInterface, Sequelize) => {
@@ -24,4 +24,4 @@ module.exports = {
return queryInterface.dropTable('users'); return queryInterface.dropTable('users');
*/ */
} }
}; }

View File

@@ -65,7 +65,7 @@ const notifier = {
const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } }) const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
const promises = eventNotifications.map(async e => { const promises = eventNotifications.map(async e => {
const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] }) const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] })
if (!event.place) return if (!event.place) { return }
const notification = await Notification.findByPk(e.notificationId) const notification = await Notification.findByPk(e.notificationId)
try { try {
await sendNotification(notification, event, e) await sendNotification(notification, event, e)

View File

@@ -7,6 +7,11 @@ const webfinger = require('./federation/webfinger')
const debug = require('debug')('routes') const debug = require('debug')('routes')
const router = express.Router() const router = express.Router()
router.use((req, res, next) => {
debug(req.path)
next()
})
router.use('/favicon.ico', express.static(path.resolve(config.favicon || 'assets/favicon.ico'))) router.use('/favicon.ico', express.static(path.resolve(config.favicon || 'assets/favicon.ico')))
router.use('/media/', express.static(config.upload_path)) router.use('/media/', express.static(config.upload_path))
router.use('/api', api) router.use('/api', api)
@@ -27,6 +32,4 @@ router.use((error, req, res, next) => {
res.status(500).send('500: Internal Server Error') res.status(500).send('500: Internal Server Error')
}) })
module.exports = router module.exports = router

View File

@@ -12,7 +12,7 @@ export const state = () => ({
allow_anon_event: true, allow_anon_event: true,
allow_recurrent_event: true, allow_recurrent_event: true,
recurrent_event_visible: false, recurrent_event_visible: false,
enable_federation: false, enable_federation: false
}, },
filters: { filters: {
tags: [], tags: [],
@@ -27,28 +27,26 @@ export const getters = {
// filter matches search tag/place // filter matches search tag/place
filteredEvents: state => { filteredEvents: state => {
const search_for_tags = !!state.filters.tags.length const search_for_tags = !!state.filters.tags.length
const search_for_places = !!state.filters.places.length const search_for_places = !!state.filters.places.length
return state.events.filter(e => { return state.events.filter(e => {
// filter past events // filter past events
if (!state.filters.show_past_events && e.past) return false if (!state.filters.show_past_events && e.past) { return false }
// filter recurrent events // filter recurrent events
if (!state.filters.show_recurrent_events && e.recurrent) return false if (!state.filters.show_recurrent_events && e.recurrent) { return false }
if (search_for_places) { if (search_for_places) {
if (find(state.filters.places, p => p === e.place.id)) return true if (find(state.filters.places, p => p === e.place.id)) { return true }
} }
if (search_for_tags) { if (search_for_tags) {
const common_tags = intersection(e.tags, state.filters.tags); const common_tags = intersection(e.tags, state.filters.tags)
if (common_tags.length > 0) return true if (common_tags.length > 0) { return true }
} }
if (!search_for_places && !search_for_tags) return true if (!search_for_places && !search_for_tags) { return true }
return false return false
}) })
@@ -56,7 +54,6 @@ export const getters = {
// filter matches search tag/place including past events // filter matches search tag/place including past events
filteredEventsWithPast: state => { filteredEventsWithPast: state => {
const search_for_tags = !!state.filters.tags.length const search_for_tags = !!state.filters.tags.length
const search_for_places = !!state.filters.places.length const search_for_places = !!state.filters.places.length
@@ -64,18 +61,18 @@ export const getters = {
const match = false const match = false
// filter recurrent events // filter recurrent events
if (!state.filters.show_recurrent_events && e.recurrent) return false if (!state.filters.show_recurrent_events && e.recurrent) { return false }
if (!match && search_for_places) { if (!match && search_for_places) {
if (find(state.filters.places, p => p === e.place.id)) return true if (find(state.filters.places, p => p === e.place.id)) { return true }
} }
if (search_for_tags) { if (search_for_tags) {
const common_tags = intersection(e.tags, state.filters.tags); const common_tags = intersection(e.tags, state.filters.tags)
if (common_tags.length > 0) return true if (common_tags.length > 0) { return true }
} }
if (!search_for_places && !search_for_tags) return true if (!search_for_places && !search_for_tags) { return true }
return false return false
}) })
@@ -101,7 +98,7 @@ export const mutations = {
}, },
updateEvent (state, event) { updateEvent (state, event) {
state.events = state.events.map((e) => { state.events = state.events.map((e) => {
if (e.id !== event.id) return e if (e.id !== event.id) { return e }
return event return event
}) })
}, },
@@ -146,7 +143,6 @@ export const actions = {
// apply settings // apply settings
commit('showRecurrentEvents', settings.allow_recurrent_event && settings.recurrent_event_visible) commit('showRecurrentEvents', settings.allow_recurrent_event && settings.recurrent_event_visible)
}, },
async updateEvents ({ commit }, page) { async updateEvents ({ commit }, page) {
const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`) const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`)
@@ -186,5 +182,5 @@ export const actions = {
async setSetting ({ commit }, setting) { async setSetting ({ commit }, setting) {
await this.$axios.$post('/settings', setting) await this.$axios.$post('/settings', setting)
commit('setSetting', setting) commit('setSetting', setting)
}, }
} }

View File

@@ -4162,6 +4162,11 @@ express-jwt@^5.3.1:
jsonwebtoken "^8.1.0" jsonwebtoken "^8.1.0"
lodash.set "^4.0.0" lodash.set "^4.0.0"
express-middleware-log@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/express-middleware-log/-/express-middleware-log-1.2.0.tgz#62682021ba3b1cbfd6b081e7364ebb1fd6d5a0fb"
integrity sha512-1G9cHlGJs4+nFphSqVduJfCzeaqHeOdpTRBAjceRRcLWeHzj9sXDYP99tNjaeHsHn3N3vlNI+vIn/lb9eYXmuw==
express-unless@^0.3.0: express-unless@^0.3.0:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20" resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20"