diff --git a/components/Calendar.vue b/components/Calendar.vue index 9b46f014..e67533ba 100644 --- a/components/Calendar.vue +++ b/components/Calendar.vue @@ -19,10 +19,10 @@ import { intersection, sample, take, get } from 'lodash' export default { name: 'Calendar', data () { - const month = moment().month()+1 + const month = moment().month() + 1 const year = moment().year() return { - page: { month, year}, + page: { month, year } } }, watch: { @@ -35,8 +35,8 @@ export default { ...mapActions(['updateEvents']), click (day) { const element = document.getElementById(day.day) - if (element) element.scrollIntoView(); //Even IE6 supports this - }, + if (element) { element.scrollIntoView() } // Even IE6 supports this + } }, computed: { ...mapGetters(['filteredEventsWithPast']), @@ -45,17 +45,17 @@ export default { // TODO: should be better attributes () { const colors = ['green', 'orange', 'yellow', 'teal', 'indigo', 'blue', 'red', 'purple', 'pink', 'grey'] - const tags = take(this.tags, 10).map(t=>t.tag) + const tags = take(this.tags, 10).map(t => t.tag) let attributes = [] - attributes.push ({ key: 'today', dates: new Date(), highlight: { color: 'green' }}) + attributes.push({ key: 'today', dates: new Date(), highlight: { color: 'green' } }) const that = this - 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 tag = get(event, 'tags[0]') - if (!tag) return color + if (!tag) { return color } const idx = tags.indexOf(tag) - if (idx<0) return color + if (idx < 0) { return color } color.color = colors[idx] return color } @@ -65,16 +65,19 @@ export default { .map(e => { const color = getColor(e) return { - key: e.id, + key: e.id, dot: color, - dates: new Date(e.start_datetime*1000) - }})) + dates: new Date(e.start_datetime * 1000) + } + })) attributes = attributes.concat(this.filteredEventsWithPast .filter(e => e.multidate) - .map( e => ({ key: e.id, highlight: getColor(e), dates: { - start: new Date(e.start_datetime*1000), end: new Date(e.end_datetime*1000) }}))) - + .map(e => ({ key: e.id, + highlight: getColor(e), + dates: { + start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) } }))) + return attributes } } diff --git a/components/Event.vue b/components/Event.vue index 68f42907..655b89c7 100644 --- a/components/Event.vue +++ b/components/Event.vue @@ -11,7 +11,7 @@ h2 {{event.title}} //- date / place - .date + .date div {{event|when('home')}} div {{event.place.name}} @@ -32,10 +32,10 @@ export default { showImage: { type: Boolean, default: true - }, + } }, computed: { - date () { + date () { return new Date(this.event.start_datetime).getDate() }, link () { @@ -104,7 +104,7 @@ export default { font-weight: 400; font-size: 1rem; color: white; - } + } } .tags { diff --git a/components/Home.vue b/components/Home.vue index 511ccc39..32f853c4 100644 --- a/components/Home.vue +++ b/components/Home.vue @@ -36,16 +36,16 @@ export default { // hid is used as unique identifier. Do not use `vmid` for it as it will not work { hid: 'description', name: 'description', content: this.settings.description }, { hid: 'og-description', name: 'og:description', content: this.settings.description }, - { hid: 'og-title', property: 'og:title', content: this.settings.title }, - { hid: 'og-url', property: 'og:url', content: this.settings.baseurl }, + { hid: 'og-title', property: 'og:title', content: this.settings.title }, + { hid: 'og-url', property: 'og:url', content: this.settings.baseurl }, { property: 'og:image', content: this.settings.baseurl + '/favicon.ico' } ] } }, + components: { Calendar, Event }, data () { return { } }, - components: { Calendar, Event }, computed: { ...mapGetters(['filteredEvents']), ...mapState(['events', 'settings']) diff --git a/components/List.vue b/components/List.vue index 76c00033..32821651 100644 --- a/components/List.vue +++ b/components/List.vue @@ -20,17 +20,6 @@ import { mapGetters } from 'vuex' export default { name: 'List', - data () { - return { } - }, - methods: { - link (event) { - if (event.recurrent) { - return `${event.id}_${event.start_datetime}` - } - return event.id - } - }, props: { title: { type: String, @@ -52,17 +41,28 @@ export default { }, showTags: { type: Boolean, - default: true, + default: true }, showImage: { type: Boolean, - default: true, + default: true }, showDescription: { type: Boolean, default: true } }, + data () { + return { } + }, + methods: { + link (event) { + if (event.recurrent) { + return `${event.id}_${event.start_datetime}` + } + return event.id + } + } } - diff --git a/pages/export.vue b/pages/export.vue index d620b346..a9ab2bff 100644 --- a/pages/export.vue +++ b/pages/export.vue @@ -15,14 +15,14 @@ //- el-form(@submit.native.prevent) //- //- el-switch(v-model='notification.notify_on_add' :active-text="$t('notify_on_insert')") //- //- br - //- //- el-switch.mt-2(v-model='notification.send_notification' :active-text="$t('send_notification')") + //- //- el-switch.mt-2(v-model='notification.send_notification' :active-text="$t('send_notification')") //- el-input.mt-2(v-model='notification.email' :placeholder="$t('export.insert_your_address')" ref='email') //- el-button.mt-2.float-right(native-type= 'submit' type='success' @click='add_notification') {{$t('common.send')}} el-tab-pane.pt-1(label='feed rss' name='feed') span(v-html='$t(`export.feed_description`)') el-input(v-model='link') - el-button(slot='append' plain + el-button(slot='append' plain v-clipboard:copy='link' type="primary" icon='el-icon-document' ) {{$t("common.copy")}} @@ -45,7 +45,6 @@ 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')}} - //- TOFIX //- el-tab-pane.pt-1(label='calendar' name='calendar') //- p(v-html='$t(`export.calendar_description`)') @@ -61,7 +60,7 @@ import Calendar from '@/components/Calendar' import List from '@/components/List' import Search from '@/components/Search' -import {intersection} from 'lodash' +import { intersection } from 'lodash' import { Message } from 'element-ui' export default { @@ -76,25 +75,25 @@ export default { return { type: 'feed', notification: { email: '' }, - list: { title: 'Gancio' }, + list: { title: 'Gancio' } } }, methods: { copy (msg) { - this.$copyText(msg).then(e => console.error('ok ', e)).catch(e => console.error('err ',e)) + this.$copyText(msg).then(e => console.error('ok ', e)).catch(e => console.error('err ', e)) }, async add_notification () { - if (!this.notification.email){ - Message({message:'Inserisci una mail', showClose: true, type: 'error'}) + if (!this.notification.email) { + Message({ message: 'Inserisci una mail', showClose: true, type: 'error' }) // return this.$refs.email.focus() } // await api.addNotification({ ...this.notification, filters: this.filters}) // this.$refs.modal.hide() - Message({message: this.$t('email_notification_activated'), showClose: true, type: 'success'}) + Message({ message: this.$t('email_notification_activated'), showClose: true, type: 'success' }) }, imgPath (event) { return event.image_path && event.image_path - }, + } }, computed: { ...mapState(['filters', 'events', 'settings']), @@ -119,9 +118,9 @@ export default { const tags = this.filters.tags.join(',') const places = this.filters.places.join(',') let query = '' - if (tags || places) { + if (tags || places) { query = '?' - if (tags) { + if (tags) { query += 'tags=' + tags if (places) { query += '&places=' + places } } else { @@ -132,8 +131,8 @@ export default { return `${this.settings.baseurl}/api/export/${this.type}${query}` }, showLink () { - return (['feed', 'ics'].indexOf(this.type)>-1) - }, + return (['feed', 'ics'].includes(this.type)) + } } } @@ -143,5 +142,3 @@ export default { overflow-y: auto; } - - diff --git a/pages/index.vue b/pages/index.vue index a74231c1..01f10dca 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -18,12 +18,11 @@ export default { store.commit('setEvents', events) const { tags, places } = await $axios.$get('/event/meta') store.commit('update', { tags, places }) - } catch(e) { + } catch (e) { console.error(e) } }, computed: mapState(['events']), - components: { Nav, Home }, + components: { Nav, Home } } - diff --git a/pages/login.vue b/pages/login.vue index 81c43d1c..f4efbdfe 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -41,7 +41,7 @@ export default { computed: { ...mapState(['settings']), disabled () { - if (process.server) return false + if (process.server) { return false } return !this.email || !this.password } }, @@ -49,7 +49,7 @@ export default { ...mapActions(['login']), async forgot () { if (!this.email) { - Message({ message: this.$t('login.insert_email'), showClose:true, type: 'error' }) + Message({ message: this.$t('login.insert_email'), showClose: true, type: 'error' }) this.$refs.email.focus() return } diff --git a/pages/recover/_code.vue b/pages/recover/_code.vue index 8bb61df0..0d3e89f0 100644 --- a/pages/recover/_code.vue +++ b/pages/recover/_code.vue @@ -11,8 +11,7 @@ el-button(plain type="success" icon='el-icon-send', @click='change_password') {{$t('common.send')}} div(v-else) {{$t('recover.not_valid_code')}} - - + - - diff --git a/pages/register.vue b/pages/register.vue index a4f8f67f..a8b3c094 100644 --- a/pages/register.vue +++ b/pages/register.vue @@ -44,13 +44,13 @@ export default { title: this.settings.title + ' - ' + this.$t('common.register') } }, - validate ({store}) { + validate ({ store }) { return store.state.settings.allow_registration }, computed: { ...mapState(['settings']), disabled () { - if (process.server) return false + if (process.server) { return false } 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`), type: 'success' }) - this.$router.replace("/") + this.$router.replace('/') } catch (e) { const error = get(e, 'response.data.errors[0].message', String(e)) Message({ diff --git a/pages/settings.vue b/pages/settings.vue index e4766a3c..8826745d 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -14,19 +14,19 @@ div(v-if='settings.enable_federation') el-form-item(:label="$t('admin.enable_federation')") el-switch(v-model='user.settings.enable_federation') - + div(v-if='user.settings.enable_federation') el-form-item(:label="$t('common.username')") el-input(v-if='user.username.length==0' type='text' name='username' v-model='user.username') template(slot='suffix') @{{baseurl}} span(v-else) {{user.username}}@{{baseurl}} //- el-button(slot='append') {{$t('common.save')}} - + el-form-item(:label="$t('common.displayname')") el-input(type='text' v-model='user.display_name') el-button(type='success' native-type='submit') {{$t('common.save')}} - + el-divider {{$t('settings.danger_section')}} p {{$t('settings.remove_account')}} el-button(type='danger' @click='remove_account') {{$t('common.remove')}} @@ -34,16 +34,16 @@ - diff --git a/pages/user_confirm/_code.vue b/pages/user_confirm/_code.vue index c53de7c9..bc62a80a 100644 --- a/pages/user_confirm/_code.vue +++ b/pages/user_confirm/_code.vue @@ -6,7 +6,7 @@ h5 {{$t('confirm.title')}} p(v-if='valid' v-html='$t("confirm.valid")') p(v-else) {{$t('confirm.not_valid')}} - + - - diff --git a/plugins/axios.js b/plugins/axios.js index 78a17eda..dba773fc 100644 --- a/plugins/axios.js +++ b/plugins/axios.js @@ -1,5 +1,5 @@ -export default function({ $axios, store }) { +export default function ({ $axios, store }) { if (process.client) { $axios.defaults.baseURL = window.location.origin + '/api' } -} \ No newline at end of file +} diff --git a/plugins/filters.js b/plugins/filters.js index 2d9e7694..f48c9162 100644 --- a/plugins/filters.js +++ b/plugins/filters.js @@ -4,7 +4,6 @@ import 'dayjs/locale/it' import 'dayjs/locale/es' export default ({ app, store }) => { - // replace links with anchors // TODO: remove fb tracking id Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '$1')) @@ -21,7 +20,7 @@ export default ({ app, store }) => { Vue.filter('when', (event, where) => { moment.locale(store.state.locale) - //{start,end}_datetime are unix timestamp + // {start,end}_datetime are unix timestamp const start = moment.unix(event.start_datetime) const end = moment.unix(event.end_datetime) @@ -30,12 +29,12 @@ export default ({ app, store }) => { // recurrent event if (event.recurrent && where !== 'home') { const { frequency, days, type } = JSON.parse(event.recurrent) - if ( frequency === '1w' || frequency === '2w' ) { - const recurrent = app.i18n.tc(`event.recurrent_${frequency}_days`, days.length, {days: days.map(d => moment().day(d-1).format('dddd'))}) + if (frequency === '1w' || frequency === '2w') { + const recurrent = app.i18n.tc(`event.recurrent_${frequency}_days`, days.length, { days: days.map(d => moment().day(d - 1).format('dddd')) }) return `${normal} - ${recurrent}` } else if (frequency === '1m' || frequency === '2m') { - const d = type === 'ordinal' ? days : days.map(d => moment().day(d-1).format('dddd')) - const recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, {days: d}) + const d = type === 'ordinal' ? days : days.map(d => moment().day(d - 1).format('dddd')) + const recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, { days: d }) return `${normal} - ${recurrent}` } return 'recurrent ' @@ -44,7 +43,7 @@ export default ({ app, store }) => { // multidate if (event.multidate) { return `${start.format('ddd, D MMMM (HH:mm)')} - ${end.format('ddd, D MMMM')}` - } + } // normal event if (event.end_datetime && event.end_datetime !== event.start_datetime) { diff --git a/plugins/i18n.js b/plugins/i18n.js index 4f5b69a6..340d6f96 100644 --- a/plugins/i18n.js +++ b/plugins/i18n.js @@ -10,8 +10,8 @@ export default async ({ app, store }) => { // This way we can use it in middleware and pages asyncData/fetch const user_locale = await app.$axios.$get('/settings/user_locale') - for(let lang in user_locale) { - if (locales[lang]) merge(locales[lang], user_locale[lang]) + for (const lang in user_locale) { + if (locales[lang]) { merge(locales[lang], user_locale[lang]) } } app.i18n = new VueI18n({ diff --git a/server/api/auth.js b/server/api/auth.js index c88c9333..10a669b8 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -2,8 +2,8 @@ const { Op } = require('sequelize') const { user: User } = require('./models') const Auth = { - async fillUser(req, res, next) { - if (!req.user) return next() + async fillUser (req, res, next) { + if (!req.user) { return next() } req.user = await User.findOne({ where: { id: { [Op.eq]: req.user.id }, is_active: true } }).catch(e => { @@ -12,7 +12,7 @@ const Auth = { }) next() }, - async isAuth(req, res, next) { + async isAuth (req, res, next) { if (!req.user) { return res .status(403) @@ -29,15 +29,15 @@ const Auth = { } next() }, - isAdmin(req, res, next) { + isAdmin (req, res, next) { if (!req.user) { return res .status(403) .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' }) - }, + } } diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 44c47cdf..db9cb6b7 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -9,7 +9,7 @@ const federation = require('../../federation/helpers') const eventController = { - async addComment(req, res) { + async addComment (req, res) { // comment could be added to an event or to another comment let event = await Event.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } } }) if (!event) { @@ -21,11 +21,11 @@ const eventController = { res.json(comment) }, - async getMeta(req, res) { + async getMeta (req, res) { const places = await Place.findAll({ order: [[Sequelize.literal('weigth'), 'DESC']], attributes: { - include: [[Sequelize.fn('count', Sequelize.col('events.placeId')) , 'weigth']], + include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']], exclude: ['weigth', 'createdAt', 'updatedAt'] }, include: [{ model: Event, attributes: [] }], @@ -36,25 +36,25 @@ const eventController = { order: [['weigth', 'DESC']], attributes: { exclude: ['createdAt', 'updatedAt'] - }, + } }) res.json({ tags, places }) }, - async getNotifications(event) { - function match(event, filters) { + async getNotifications (event) { + function match (event, filters) { // matches if no filter specified - if (!filters) return true + if (!filters) { return true } // 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.length && !filters.places.length) return true + if (!filters.tags && !filters.places) { return true } + if (!filters.tags.length && !filters.places.length) { return true } if (filters.tags.length) { 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.find(p => p === event.place.name)) { @@ -68,7 +68,7 @@ const eventController = { return notifications.filter(notification => match(event, notification.filters)) }, - async updateTag(req, res) { + async updateTag (req, res) { const tag = await Tag.findByPk(req.body.tag) if (tag) { res.json(await tag.update(req.body)) @@ -77,7 +77,7 @@ const eventController = { } }, - async updatePlace(req, res) { + async updatePlace (req, res) { const place = await Place.findByPk(req.body.id) await place.update(req.body) res.json(place) @@ -85,12 +85,12 @@ const eventController = { // TODO retrieve next/prev event also // select id, start_datetime, title from events where start_datetime > (select start_datetime from events where id=89) order by start_datetime limit 20; - async get(req, res) { + async get (req, res) { const is_admin = req.user && req.user.is_admin const id = req.params.event_id - let event = await Event.findByPk(id, { + const event = await Event.findByPk(id, { plain: true, - attributes: { + attributes: { exclude: ['createdAt', 'updatedAt'] }, include: [ @@ -109,29 +109,29 @@ const eventController = { } }, - async confirm(req, res) { + async confirm (req, res) { const id = Number(req.params.event_id) const event = await Event.findByPk(id) - if (!event) return res.sendStatus(404) + if (!event) { return res.sendStatus(404) } try { event.is_visible = true await event.save() res.sendStatus(200) - + // send notification - //notifier.notifyEvent(event.id) - //federation.sendEvent(event, req.user) + // notifier.notifyEvent(event.id) + // federation.sendEvent(event, req.user) } catch (e) { res.sendStatus(404) } }, - async unconfirm(req, res) { + async unconfirm (req, res) { const id = Number(req.params.event_id) const event = await Event.findByPk(id) - if (!event) return sendStatus(404) + if (!event) { return sendStatus(404) } try { event.is_visible = false @@ -142,7 +142,7 @@ const eventController = { } }, - async getUnconfirmed(req, res) { + async getUnconfirmed (req, res) { const events = await Event.findAll({ where: { is_visible: false @@ -153,7 +153,7 @@ const eventController = { res.json(events) }, - async addNotification(req, res) { + async addNotification (req, res) { try { const notification = { filters: { is_visible: true }, @@ -168,7 +168,7 @@ const eventController = { } }, - async delNotification(req, res) { + async delNotification (req, res) { const remove_code = req.params.code try { const notification = await Notification.findOne({ where: { remove_code: { [Op.eq]: remove_code } } }) @@ -179,7 +179,7 @@ const eventController = { res.sendStatus(200) }, - async getAll(req, res) { + async getAll (req, res) { // this is due how v-calendar shows dates const start = moment() .year(req.params.year) @@ -193,7 +193,7 @@ const eventController = { .endOf('month') 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') let events = await Event.findAll({ @@ -202,10 +202,10 @@ const eventController = { is_visible: true, [Op.or]: [ // return all recurrent events - {recurrent: { [Op.ne]: null }}, + { recurrent: { [Op.ne]: null } }, // and events in specified range - { start_datetime: { [Op.between]: [start.unix(), end.unix()] }} + { start_datetime: { [Op.between]: [start.unix(), end.unix()] } } ] }, attributes: { exclude: ['createdAt', 'updatedAt', 'placeId' ] }, @@ -223,10 +223,10 @@ const eventController = { }) // build singular events from a recurrent pattern - function createEventsFromRecurrent(e, dueTo=null) { + function createEventsFromRecurrent (e, dueTo = null) { const events = [] const recurrent = JSON.parse(e.recurrent) - if (!recurrent.frequency) return false + if (!recurrent.frequency) { return false } let cursor = moment(start).startOf('week') const start_date = moment.unix(e.start_datetime) @@ -236,18 +236,18 @@ const eventController = { const type = recurrent.type // default frequency is '1d' => each day - const toAdd = { n: 1, unit: 'day'} + const toAdd = { n: 1, unit: 'day' } cursor.set('hour', start_date.hour()).set('minute', start_date.minutes()) // each week or 2 (search for the first specified day) if (frequency === '1w' || frequency === '2w') { - cursor.add(days[0]-1, 'day') + cursor.add(days[0] - 1, 'day') if (frequency === '2w') { - const nWeeks = cursor.diff(e.start_datetime, 'w')%2 - if (!nWeeks) cursor.add(1, 'week') + const nWeeks = cursor.diff(e.start_datetime, 'w') % 2 + if (!nWeeks) { cursor.add(1, 'week') } } toAdd.n = Number(frequency[0]) - toAdd.unit = 'week'; + toAdd.unit = 'week' // cursor.set('hour', start_date.hour()).set('minute', start_date.minutes()) } @@ -263,37 +263,35 @@ const eventController = { } } - // add event at specified frequency + // add event at specified frequency while (true) { - let first_event_of_week = cursor.clone() + const first_event_of_week = cursor.clone() days.forEach(d => { if (type === 'ordinal') { cursor.date(d) } 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.end_datetime = e.start_datetime+duration - events.push( Object.assign({}, e) ) - }) - if (cursor.isAfter(dueTo)) break + e.end_datetime = e.start_datetime + duration + events.push(Object.assign({}, e)) + }) + if (cursor.isAfter(dueTo)) { break } cursor = first_event_of_week.add(toAdd.n, toAdd.unit) } - return events } let allEvents = events.filter(e => !e.recurrent) events.filter(e => e.recurrent).forEach(e => { const events = createEventsFromRecurrent(e, end) - if (events) - allEvents = allEvents.concat(events) + if (events) { allEvents = allEvents.concat(events) } }) // allEvents.sort((a,b) => a.start_datetime-b.start_datetime) - res.json(allEvents.sort((a,b) => a.start_datetime-b.start_datetime)) + res.json(allEvents.sort((a, b) => a.start_datetime - b.start_datetime)) } } diff --git a/server/api/controller/export.js b/server/api/controller/export.js index f664899e..eef46214 100644 --- a/server/api/controller/export.js +++ b/server/api/controller/export.js @@ -5,7 +5,7 @@ const ics = require('ics') const exportController = { - async export(req, res) { + async export (req, res) { const type = req.params.type const tags = req.query.tags const places = req.query.places @@ -40,12 +40,12 @@ const exportController = { } }, - feed(res, events) { + feed (res, events) { res.type('application/rss+xml; charset=UTF-8') res.render('feed/rss.pug', { events, config: process.env, moment }) }, - ics(res, events) { + ics (res, events) { const eventsMap = events.map(e => { const tmpStart = moment.unix(e.start_datetime) const tmpEnd = moment.unix(e.end_datetime) diff --git a/server/api/controller/user.js b/server/api/controller/user.js index e5ae862f..0402b04f 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -11,10 +11,10 @@ const settingsController = require('./settings') const federation = require('../../federation/helpers') const userController = { - async login(req, res) { + async login (req, res) { // find the user const user = await User.findOne({ where: { - [Op.or]: [ + [Op.or]: [ { email: req.body.email }, { username: req.body.email } ] @@ -44,7 +44,7 @@ const userController = { } }, - async delEvent(req, res) { + async delEvent (req, res) { const event = await Event.findByPk(req.params.id) // check if event is mine (or user is admin) if (event && (req.user.is_admin || req.user.id === event.userId)) { @@ -68,7 +68,7 @@ const userController = { }, // ADD EVENT - async addEvent(req, res) { + async addEvent (req, res) { const body = req.body const eventDetails = { @@ -88,7 +88,7 @@ const userController = { eventDetails.image_path = req.file.filename } - let event = await Event.create(eventDetails) + const event = await Event.create(eventDetails) // create place if needed let place @@ -105,7 +105,7 @@ const userController = { if (body.tags) { await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true }) const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } }) - await Promise.all(tags.map(t => t.update({weigth: Number(t.weigth)+1}))) + await Promise.all(tags.map(t => t.update({ weigth: Number(t.weigth) + 1 }))) await event.addTags(tags) event.tags = tags } @@ -118,17 +118,15 @@ const userController = { // send response to client res.json(event) - if (req.user) - federation.sendEvent(event, req.user) + if (req.user) { federation.sendEvent(event, req.user) } // res.sendStatus(200) // send notification (mastodon/email/confirmation) // notifier.notifyEvent(event.id) - }, - async updateEvent(req, res) { + async updateEvent (req, res) { const body = req.body const event = await Event.findByPk(body.id) if (!req.user.is_admin && event.userId !== req.user.id) { @@ -168,10 +166,10 @@ const userController = { res.json(newEvent) }, - async forgotPassword(req, res) { + async forgotPassword (req, res) { const email = req.body.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') mail.send(user.email, 'recover', { user, config }) @@ -180,25 +178,25 @@ const userController = { res.sendStatus(200) }, - async checkRecoverCode(req, res) { + async checkRecoverCode (req, res) { 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 } } }) - if (!user) return res.sendStatus(400) + if (!user) { return res.sendStatus(400) } try { - await user.update({ recover_code: ''}) + await user.update({ recover_code: '' }) res.sendStatus(200) } catch (e) { res.sendStatus(400) } }, - async updatePasswordWithRecoverCode(req, res) { + async updatePasswordWithRecoverCode (req, res) { const recover_code = req.body.recover_code 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 } } }) - if (!user) return res.sendStatus(400) + if (!user) { return res.sendStatus(400) } user.recover_code = '' user.password = password try { @@ -209,32 +207,31 @@ const userController = { } }, - current(req, res) { + current (req, res) { if (req.user) { res.json(req.user) } else { res.sendStatus(404) } }, - async getAll(req, res) { + async getAll (req, res) { const users = await User.findAll({ order: [['createdAt', 'DESC']] }) res.json(users) }, - async update(req, res) { + async update (req, res) { // user to modify 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) { return res.status(400).json({ succes: false, message: 'Not allowed' }) } // 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) - delete req.body.password + if (!req.body.password) { delete req.body.password } await user.update(req.body) @@ -244,9 +241,8 @@ const userController = { res.json(user) }, - - async register(req, res) { - if (!settingsController.settings.allow_registration) return res.sendStatus(404) + async register (req, res) { + if (!settingsController.settings.allow_registration) { return res.sendStatus(404) } const n_users = await User.count() try { // the first registered user will be an active admin @@ -276,7 +272,7 @@ const userController = { } }, - async create(req, res) { + async create (req, res) { try { req.body.is_active = true req.body.recover_code = crypto.randomBytes(16).toString('hex') @@ -288,7 +284,7 @@ const userController = { } }, - async remove(req, res) { + async remove (req, res) { try { const user = await User.findByPk(req.params.id) user.destroy() diff --git a/server/api/index.js b/server/api/index.js index 635eb712..3c556447 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -14,6 +14,8 @@ const settingsController = require('./controller/settings') const storage = require('./storage') const upload = multer({ storage }) +const debug = require('debug')('api') + const api = express.Router() api.use(cookieParser()) api.use(bodyParser.urlencoded({ extended: false })) @@ -24,10 +26,10 @@ const jwt = expressJwt({ credentialsRequired: false, getToken: function fromHeaderOrQuerystring (req) { 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']) { const [ prefix, token ] = req.cookies['auth._token.local'].split(' ') - if (prefix === 'Bearer') return token + if (prefix === 'Bearer') { return token } } } }) @@ -47,10 +49,10 @@ api.post('/user', jwt, isAuth, isAdmin, userController.create) // update user api.put('/user', jwt, isAuth, userController.update) -//delete user +// delete user api.delete('/user/:id', jwt, isAuth, isAdmin, userController.remove) -// +// // api.delete('/user', userController.remove) // get all users @@ -64,7 +66,7 @@ api.put('/place', jwt, isAuth, isAdmin, eventController.updatePlace) // add event api.post('/user/event', jwt, fillUser, upload.single('image'), userController.addEvent) - + // update event api.put('/user/event', jwt, isAuth, upload.single('image'), userController.updateEvent) @@ -100,4 +102,16 @@ api.get('/export/:type', exportController.export) 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 diff --git a/server/api/mail.js b/server/api/mail.js index aa29674c..9dcedcec 100644 --- a/server/api/mail.js +++ b/server/api/mail.js @@ -7,7 +7,7 @@ const debug = require('debug')('email') moment.locale('it') const mail = { - send(addresses, template, locals) { + send (addresses, template, locals) { debug(`Send ${template} email to ${addresses}`) const email = new Email({ views: { root: path.join(__dirname, '..', 'emails') }, @@ -30,7 +30,7 @@ const mail = { updateFiles: false, defaultLocale: settings.locale, locale: settings.locale, - locales: ['it', 'es'], + locales: ['it', 'es'] }, transport: config.smtp }) diff --git a/server/api/models/comment.js b/server/api/models/comment.js index 1eefb43a..7b836812 100644 --- a/server/api/models/comment.js +++ b/server/api/models/comment.js @@ -1,10 +1,10 @@ 'use strict' - module.exports = (sequelize, DataTypes) => { +module.exports = (sequelize, DataTypes) => { const comment = sequelize.define('comment', { activitypub_id: { type: DataTypes.STRING(18), index: true, - unique: true, + unique: true }, data: DataTypes.JSON }, {}) @@ -12,4 +12,4 @@ comment.belongsTo(models.event) } return comment -}; +} diff --git a/server/api/models/event.js b/server/api/models/event.js index 8ca92853..be2c085e 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -6,7 +6,7 @@ module.exports = (sequelize, DataTypes) => { id: { type: DataTypes.INTEGER, primaryKey: true, - autoIncrement: true, + autoIncrement: true }, title: DataTypes.STRING, slug: DataTypes.STRING, @@ -36,8 +36,8 @@ module.exports = (sequelize, DataTypes) => { event.hasMany(models.comment) } - // - event.prototype.toAP = function (username=config.admin, follower) { + // + event.prototype.toAP = function (username = config.admin, follower) { const tags = this.tags && this.tags.map(t => '#' + t.tag).join(' ') const content = `${this.title}
📍${this.place.name}
@@ -45,7 +45,7 @@ module.exports = (sequelize, DataTypes) => { ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}
${tags}
` - let attachment = [] + const attachment = [] if (this.image_path) { attachment.push({ type: 'Document', @@ -62,19 +62,22 @@ module.exports = (sequelize, DataTypes) => { // actor: `${config.baseurl}/federation/u/${username}`, // url: `${config.baseurl}/federation/m/${this.id}`, // object: { - attachment, - tag: this.tags.map(tag => ({ - type: 'Hashtag', - name: '#' + tag.tag - })), - id: `${config.baseurl}/federation/m/${this.id}`, - type: 'Note', - published: this.createdAt, - attributedTo: `${config.baseurl}/federation/u/${username}`, - to: 'https://www.w3.org/ns/activitystreams#Public', - cc: follower ? follower: [], - content - } + type: 'Note', + id: `${config.baseurl}/federation/m/${this.id}`, + url: `${config.baseurl}/federation/m/${this.id}`, + attachment, + tag: this.tags.map(tag => ({ + type: 'Hashtag', + name: '#' + tag.tag + })), + published: this.createdAt, + attributedTo: `${config.baseurl}/federation/u/${username}`, + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: follower || [], + content, + summary: null, + sensitive: false, + // } } } diff --git a/server/api/models/index.js b/server/api/models/index.js index 4a02d06d..a25c09f1 100644 --- a/server/api/models/index.js +++ b/server/api/models/index.js @@ -7,7 +7,7 @@ const consola = require('consola') const db = {} const sequelize = new Sequelize(config.db) -sequelize.authenticate().catch( e => { +sequelize.authenticate().catch(e => { consola.error('Error connecting to DB: ', String(e)) process.exit(-1) }) @@ -21,15 +21,14 @@ fs const model = sequelize.import(path.join(__dirname, file)) db[model.name] = model }) - - Object.keys(db).forEach(modelName => { - if (db[modelName].associate) { - db[modelName].associate(db) - } - }) - - db.sequelize = sequelize - db.Sequelize = Sequelize - - module.exports = db - \ No newline at end of file + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db) + } +}) + +db.sequelize = sequelize +db.Sequelize = Sequelize + +module.exports = db diff --git a/server/api/models/place.js b/server/api/models/place.js index 712cf96f..5dbf7ecf 100644 --- a/server/api/models/place.js +++ b/server/api/models/place.js @@ -3,7 +3,8 @@ module.exports = (sequelize, DataTypes) => { const place = sequelize.define('place', { name: { type: DataTypes.STRING, - unique: true, index: true, + unique: true, + index: true, allowNull: false }, address: DataTypes.STRING diff --git a/server/api/models/tag.js b/server/api/models/tag.js index 0f5465fe..59dd7465 100644 --- a/server/api/models/tag.js +++ b/server/api/models/tag.js @@ -15,4 +15,4 @@ module.exports = (sequelize, DataTypes) => { } return tag -}; +} diff --git a/server/api/models/user.js b/server/api/models/user.js index af062305..809d9430 100644 --- a/server/api/models/user.js +++ b/server/api/models/user.js @@ -18,7 +18,7 @@ module.exports = (sequelize, DataTypes) => { settings: DataTypes.JSON, email: { type: DataTypes.STRING, - unique: { msg: 'error.email_taken' }, + unique: { msg: 'error.email_taken' }, index: true, allowNull: false }, @@ -46,7 +46,7 @@ module.exports = (sequelize, DataTypes) => { } user.prototype.comparePassword = async function (pwd) { - if (!this.password) return false + if (!this.password) { return false } const ret = await bcrypt.compare(pwd, this.password) return ret } @@ -78,4 +78,4 @@ module.exports = (sequelize, DataTypes) => { }) return user -}; +} diff --git a/server/api/storage.js b/server/api/storage.js index 70f84d9e..3ec61c00 100644 --- a/server/api/storage.js +++ b/server/api/storage.js @@ -13,7 +13,7 @@ try { } const DiskStorage = { - _handleFile(req, file, cb) { + _handleFile (req, file, cb) { const filename = crypto.randomBytes(16).toString('hex') + '.jpg' const finalPath = path.resolve(config.upload_path, filename) const thumbPath = path.resolve(config.upload_path, 'thumb', filename) @@ -36,7 +36,7 @@ const DiskStorage = { }) }) }, - _removeFile(req, file, cb) { + _removeFile (req, file, cb) { delete file.destination delete file.filename delete file.path diff --git a/server/federation/comments.js b/server/federation/comments.js index e7b7816e..203902ad 100644 --- a/server/federation/comments.js +++ b/server/federation/comments.js @@ -5,13 +5,13 @@ const debug = require('debug')('fediverse:comment') module.exports = { async create (req, res) { const body = req.body - //search for related event + // search for related event const inReplyTo = body.object.inReplyTo const match = inReplyTo.match('.*\/federation\/m\/(.*)') console.error('inReplyTo', inReplyTo) console.error('match', match) - if (!match || match.length<2) { - debug("Comment not found %s", inReplyTo) + if (!match || match.length < 2) { + debug('Comment not found %s', inReplyTo) return res.status(404).send('Event not found!') } let event = await Event.findByPk(Number(match[1])) @@ -20,7 +20,7 @@ module.exports = { if (!event) { // in reply to another comment... 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 } debug('comment from %s to "%s"', req.body.actor, event.title) @@ -35,7 +35,7 @@ module.exports = { }, async remove (req, res) { - const comment = await Comment.findOne({where: { activitypub_id: req.body.object.id }}) + const comment = await Comment.findOne({ where: { activitypub_id: req.body.object.id } }) if (!comment) { debug('Comment %s not found', req.body.object.id) return res.status(404).send('Not found') diff --git a/server/federation/ego.js b/server/federation/ego.js index c951bdcd..431dd5b6 100644 --- a/server/federation/ego.js +++ b/server/federation/ego.js @@ -5,21 +5,21 @@ const debug = require('debug')('fediverse:ego') module.exports = { async boost (req, res) { 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]) const event = await Event.findByPk(Number(match[1])) - if (!event) return res.status(404).send('Event not found!') - await event.update({ boost: [...event.boost, req.body.actor]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ boost: [...event.boost, req.body.actor] }) res.sendStatus(201) }, async bookmark (req, res) { 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])) debug('%s bookmark %s (%d)', req.body.actor, event.title, event.likes.length) - if (!event) return res.status(404).send('Event not found!') - await event.update({ likes: [...event.likes, req.body.actor]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ likes: [...event.likes, req.body.actor] }) res.sendStatus(201) }, @@ -27,11 +27,11 @@ module.exports = { const body = req.body const object = body.object 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])) debug('%s unbookmark %s (%d)', body.actor, event.title, event.likes.length) - if (!event) return res.status(404).send('Event not found!') - await event.update({ likes: [...event.likes.filter(actor => actor!==body.actor)]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ likes: [...event.likes.filter(actor => actor !== body.actor)] }) res.sendStatus(201) } -} \ No newline at end of file +} diff --git a/server/federation/follows.js b/server/federation/follows.js index 7f626bbe..f686439e 100644 --- a/server/federation/follows.js +++ b/server/federation/follows.js @@ -8,25 +8,25 @@ module.exports = { // follow request from fediverse async follow (req, res) { 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 user = await User.findOne({ where: { username }}) - if (!user) return res.status(404).send('User not found') + const user = await User.findOne({ where: { username } }) + if (!user) { return res.status(404).send('User not found') } // check for duplicate - if (user.followers.indexOf(body.actor) === -1) { + if (!user.followers.includes(body.actor)) { await user.update({ followers: [...user.followers, body.actor] }) debug('%s followed by %s (%d)', username, body.actor, user.followers.length) } else { debug('duplicate %s followed by %s', username, body.actor) } const guid = crypto.randomBytes(16).toString('hex') - let message = { + const message = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': `${config.baseurl}/federation/${guid}`, 'type': 'Accept', 'actor': `${config.baseurl}/federation/u/${user.username}`, - 'object': body, + 'object': body } Helpers.signAndSend(message, user, body.actor) res.sendStatus(200) @@ -36,8 +36,8 @@ module.exports = { async unfollow (req, res) { const body = req.body const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '') - const user = await User.findOne({ where: { username }}) - if (!user) return res.status(404).send('User not found') + const user = await User.findOne({ where: { username } }) + if (!user) { return res.status(404).send('User not found') } if (body.actor !== body.object.actor) { debug('Unfollow an user created by a different actor !?!?') diff --git a/server/federation/helpers.js b/server/federation/helpers.js index a322edce..1693d1a1 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -10,7 +10,7 @@ const url = require('url') const actorCache = [] const Helpers = { - async signAndSend(message, user, to) { + async signAndSend (message, user, to) { // get the URI of the actor object and append 'inbox' to it const toInbox = to + '/inbox' const toOrigin = url.parse(to) @@ -36,19 +36,18 @@ const Helpers = { }, method: 'POST', body: JSON.stringify(message) }) - }, - async sendEvent(event, user) { + async sendEvent (event, user) { // TODO: has to use sharedInbox! // event is sent by user that published it and by the admin instance - const instanceAdmin = await User.findOne({where: { email: config.admin }}) - if(!instanceAdmin || !instanceAdmin.username) { + const instanceAdmin = await User.findOne({ where: { email: config.admin } }) + if (!instanceAdmin || !instanceAdmin.username) { debug('Instance admin not found (there is no user with email => %s)', config.admin) return } - for(let follower of instanceAdmin.followers) { + for (const follower of instanceAdmin.followers) { debug('Notify %s with event %s', follower, event.title) const body = { id: `${config.baseurl}/federation/m/c_${event.id}`, @@ -60,21 +59,23 @@ const Helpers = { body['@context'] = 'https://www.w3.org/ns/activitystreams' Helpers.signAndSend(body, user, follower) } - + // in case the event is published by the Admin itself do not republish if (instanceAdmin.id === user.id) { debug('') return } - if (!user.settings.enable_federation || !user.username) return - for(let follower of user.followers) { + if (!user.settings.enable_federation || !user.username) { return } + for (const follower of user.followers) { debug('Notify %s with event %s', follower, event.title) const body = { - id: `${config.baseurl}/federation/m/c_${event.id}`, + id: `${config.baseurl}/federation/m/${event.id}#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}`, - url: `${config.baseurl}/federation/m/${event.id}`, object: event.toAP(user.username, follower) } body['@context'] = 'https://www.w3.org/ns/activitystreams' @@ -82,18 +83,18 @@ const Helpers = { } }, - async getFederatedUser(address) { + async getFederatedUser (address) { address = address.trim() const [ username, host ] = address.split('@') const url = `https://${host}/.well-known/webfinger?resource=acct:${username}@${host}` return Helpers.getActor(url) }, - + // TODO: cache - async getActor(url, force=false) { + async getActor (url, force = false) { // try with cache first - if (!force && actorCache[url]) return actorCache[url] - const user = await fetch(url, { headers: {'Accept': 'application/jrd+json, application/json'} }) + if (!force && actorCache[url]) { return actorCache[url] } + const user = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } }) .then(res => { if (!res.ok) { debug('[ERR] Actor %s => %s', url, res.statusText) @@ -106,23 +107,23 @@ const Helpers = { }, // 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) - 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 req.headers.authorization = 'Signature ' + req.headers.signature - // another little hack :/ + // another little hack :/ // https://github.com/joyent/node-http-signature/issues/87 req.url = '/federation' + req.url 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 user = await Helpers.getActor(req.body.actor, true) - if (!user) return res.status(401).send('Actor not found') - if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next() + if (!user) { return res.status(401).send('Actor not found') } + if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() } // still not valid debug('Invalid signature from user %s', req.body.actor) diff --git a/server/federation/index.js b/server/federation/index.js index 309ead2a..e2c4c3a5 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -8,31 +8,30 @@ const { event: Event, user: User, tag: Tag, place: Place } = require('../api/mod const Comments = require('./comments') const Helpers = require('./helpers') const Ego = require('./ego') +const debug = require('debug')('federation') /** * Federation is calling! * ref: https://www.w3.org/TR/activitypub/#Overview */ 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) => { const event_id = req.params.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 ] }) - 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)) }) // get any message coming from federation // Federation is calling! router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => { - const b = req.body - - switch(b.type) { + debug(b.type) + switch (b.type) { case 'Follow': Follows.follow(req, res) break diff --git a/server/federation/nodeinfo.js b/server/federation/nodeinfo.js index c5878266..445eb78c 100644 --- a/server/federation/nodeinfo.js +++ b/server/federation/nodeinfo.js @@ -18,7 +18,7 @@ router.get('/', async (req, res) => { }, protocols: ['activitypub'], openRegistrations: settingsController.settings.allow_registration, - usage:{ + usage: { users: { total: 10 } diff --git a/server/federation/users.js b/server/federation/users.js index 80db30b9..8fb2abab 100644 --- a/server/federation/users.js +++ b/server/federation/users.js @@ -21,6 +21,16 @@ module.exports = { inbox: `${config.baseurl}/federation/u/${name}/inbox`, outbox: `${config.baseurl}/federation/u/${name}/outbox`, followers: `${config.baseurl}/federation/u/${name}/followers`, + attachment: [{ + type: 'PropertyValue', + name: 'Website', + value: `${config.baseurl}` + }], + icon: { + type: 'Image', + mediaType: 'image/jpeg', + url: config.baseurl + '/favicon.ico' + }, publicKey: { id: `${config.baseurl}/federation/u/${name}#main-key`, owner: `${config.baseurl}/federation/u/${name}`, @@ -32,62 +42,79 @@ module.exports = { }, async followers (req, res) { const name = req.params.name + const page = req.query.page + debug('Retrieve %s followers', name) if (!name) return res.status(400).send('Bad request.') const user = await User.findOne({where: { username: name }}) if (!user) return res.status(404).send(`No record found for ${name}`) - const ret = { - '@context': [ 'https://www.w3.org/ns/activitystreams' ], - id: `${config.baseurl}/federation/u/${name}/followers`, - type: 'OrderedCollection', - totalItems: user.followers.length, - first: { - id: `${config.baseurl}/federation/u/${name}/followers?page=1`, - type: 'OrderedCollectionPage', + + 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`, + type: 'OrderedCollection', totalItems: user.followers.length, - partOf: `${config.baseurl}/federation/u/${name}/followers`, - orderedItems: user.followers, - } + first: `${config.baseurl}/federation/u/${name}/followers?page=true`, + last: `${config.baseurl}/federation/u/${name}/followers?page=true`, + }) } - res.json(ret) + return res.json({ + '@context': 'https://www.w3.org/ns/activitystreams', + id: `${config.baseurl}/federation/u/${name}/followers?page=${page}`, + type: 'OrderedCollectionPage', + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/followers` , + orderedItems: user.followers + }) }, + async outbox (req, res) { const name = req.params.name const page = req.query.page - + if (!name) return res.status(400).send('Bad request.') const user = await User.findOne({ include: [ { model: Event, include: [ Place, Tag ] } ], 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}`) + debug('Inside outbox, should return all events from this user') + // https://www.w3.org/TR/activitypub/#outbox + res.type('application/activity+json; charset=utf-8') if (!page) { - const ret = { + debug('Without pagination ') + return res.json({ '@context': 'https://www.w3.org/ns/activitystreams', id: `${config.baseurl}/federation/u/${name}/outbox`, type: 'OrderedCollection', totalItems: user.events.length, - first: { - id: `${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') - return res.json(ret) + first: `${config.baseurl}/federation/u/${name}/outbox?page=true`, + last: `${config.baseurl}/federation/u/${name}/outbox?page=true` + }) } - const ret = { + + debug('With pagination %s', page) + return res.json({ '@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', - partOf: `${config.baseurl}/federation/u/${name}/outbox`, - orderedItems: user.events.map(e => e.toAP(user.username)) - } - res.type('application/activity+json; charset=utf-8') - res.json(ret) + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/outbox` , + orderedItems: user.events.map(e => ({ + id: `${config.baseurl}/federation/m/${e.id}#create`, + type: 'Create', + 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) + })) + }) } } diff --git a/server/federation/webfinger.js b/server/federation/webfinger.js index 4eac724e..015b3559 100644 --- a/server/federation/webfinger.js +++ b/server/federation/webfinger.js @@ -6,18 +6,30 @@ const settingsController = require('../api/controller/settings') const config = require('config') const version = require('../../package.json').version const url = require('url') +const debug = require('debug')('webfinger') router.use(cors()) router.get('/webfinger', async (req, res) => { const resource = req.query.resource 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.') } - const name = resource.match(/acct:(.*)@/)[1] const domain = url.parse(config.baseurl).host - const user = await User.findOne({where: { username: name } }) - if (!user) return res.status(404).send(`No record found for ${name}`) + 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 } }) + if (!user) { + debug('User not found: %s', name) + return res.status(404).send(`No record found for ${name}`) + } + const ret = { subject: `acct:${name}@${domain}`, links: [ @@ -38,24 +50,24 @@ router.get('/nodeinfo/:nodeinfo_version', async (req, res) => { nodeDescription: 'Gancio instance', nodeName: config.title }, - openRegistrations : settingsController.settings.allow_registration, - protocols :['activitypub'], - services: { inbound: [], outbound :["atom1.0"]}, + openRegistrations: settingsController.settings.allow_registration, + protocols: ['activitypub'], + services: { inbound: [], outbound: ['atom1.0'] }, software: { name: 'gancio', version }, - usage: { + usage: { localComments: 0, - localPosts:0, + localPosts: 0, users: { - total:3 + total: 3 } }, version: req.params.nodeinfo_version } - if(req.params.nodeinfo_version === '2.1') { + if (req.params.nodeinfo_version === '2.1') { ret.software.repository = 'https://git.lattuga.net/cisti/gancio' } res.json(ret) @@ -72,7 +84,7 @@ router.get('/x-nodeinfo2', async (req, res) => { }, protocols: ['activitypub'], openRegistrations: settingsController.settings.allow_registration, - usage:{ + usage: { users: { total: 10 } @@ -83,18 +95,16 @@ router.get('/x-nodeinfo2', async (req, res) => { res.json(ret) }) - router.get('/nodeinfo', async (req, res) => { const ret = { 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.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) }) - router.use('/host-meta', (req, res) => { res.type('application/xml') res.send(` @@ -104,12 +114,14 @@ router.use('/host-meta', (req, res) => { }) // 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') }) // 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') }) diff --git a/server/firstrun.js b/server/firstrun.js index b8d6fa09..4f9b1a28 100644 --- a/server/firstrun.js +++ b/server/firstrun.js @@ -28,7 +28,7 @@ module.exports = { await db.user.findAll() consola.warn(`⚠️ Non empty db! Please move your current db elsewhere than retry.`) return false - } catch(e) { } + } catch (e) { } consola.info(`Create tables schema`) await db.sequelize.sync().catch(e => { diff --git a/server/index.js b/server/index.js index 3ad41b7a..b6136aa6 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,7 @@ const { Nuxt, Builder } = require('nuxt') const nuxt_config = require('../nuxt.config.js') const config = require('config') -async function main() { +async function main () { nuxt_config.server = config.server // Init Nuxt.js @@ -20,7 +20,7 @@ async function main() { nuxt.listen() // close connections/port/unix socket - function shutdown() { + function shutdown () { nuxt.close(async () => { const db = require('./api/models') await db.sequelize.close() diff --git a/server/migrations/20190728213923-add_username.js b/server/migrations/20190728213923-add_username.js index 8c1950a8..4376e432 100644 --- a/server/migrations/20190728213923-add_username.js +++ b/server/migrations/20190728213923-add_username.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -26,4 +26,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190728214848-add_displayname.js b/server/migrations/20190728214848-add_displayname.js index 073d611e..2bdd4d79 100644 --- a/server/migrations/20190728214848-add_displayname.js +++ b/server/migrations/20190728214848-add_displayname.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190729103119-add_rsa.js b/server/migrations/20190729103119-add_rsa.js index f01a81ca..858fd928 100644 --- a/server/migrations/20190729103119-add_rsa.js +++ b/server/migrations/20190729103119-add_rsa.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190729192753-add_followers.js b/server/migrations/20190729192753-add_followers.js index 9621bd26..d7818968 100644 --- a/server/migrations/20190729192753-add_followers.js +++ b/server/migrations/20190729192753-add_followers.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190801105908-likes.js b/server/migrations/20190801105908-likes.js index 8804d9c4..6b9640ef 100644 --- a/server/migrations/20190801105908-likes.js +++ b/server/migrations/20190801105908-likes.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -24,4 +24,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190801110053-boost.js b/server/migrations/20190801110053-boost.js index 45d74fab..9b372ecb 100644 --- a/server/migrations/20190801110053-boost.js +++ b/server/migrations/20190801110053-boost.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -25,4 +25,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190910085948-user_settings.js b/server/migrations/20190910085948-user_settings.js index 0f6f6b52..10c45991 100644 --- a/server/migrations/20190910085948-user_settings.js +++ b/server/migrations/20190910085948-user_settings.js @@ -1,11 +1,11 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.addColumn('users', 'settings', { type: Sequelize.JSON, defaultValue: {} - }) + }) /* Add altering commands here. Return a promise to correctly handle asynchronicity. @@ -24,4 +24,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/notifier.js b/server/notifier.js index 9cb8b554..ae664515 100644 --- a/server/notifier.js +++ b/server/notifier.js @@ -5,18 +5,18 @@ const config = require('config') const eventController = require('./api/controller/event') const get = require('lodash/get') -const { event: Event, notification: Notification, event_notification: EventNotification, +const { event: Event, notification: Notification, event_notification: EventNotification, user: User, place: Place, tag: Tag } = require('./api/models') const notifier = { - async sendNotification(notification, event) { + async sendNotification (notification, event) { return const promises = [] switch (notification.type) { case 'mail': - return mail.send(notification.email, 'event', { event, config, notification }) - case 'admin_email': - return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) + return mail.send(notification.email, 'event', { event, config, notification }) + case 'admin_email': + return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) // case 'mastodon': // // instance publish // if (bot.bot) { @@ -31,7 +31,7 @@ const notifier = { } return Promise.all(promises) }, - async notifyEvent(eventId) { + async notifyEvent (eventId) { const event = await Event.findByPk(eventId, { include: [ Tag, Place, User ] }) @@ -42,7 +42,7 @@ const notifier = { const eventNotifications = await EventNotification.findAll({ where: { - notificationId: notifications.map(n=>n.id), + notificationId: notifications.map(n => n.id), status: 'new' } }) @@ -57,15 +57,15 @@ const notifier = { } return e.save() }) - + return Promise.all(promises) }, - async notify() { + async notify () { // get all event notification in queue const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } }) const promises = eventNotifications.map(async e => { 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) try { await sendNotification(notification, event, e) @@ -82,4 +82,4 @@ const notifier = { } } -module.exports = notifier \ No newline at end of file +module.exports = notifier diff --git a/server/routes.js b/server/routes.js index 9ee6e85b..19c2e4f7 100644 --- a/server/routes.js +++ b/server/routes.js @@ -7,6 +7,11 @@ const webfinger = require('./federation/webfinger') const debug = require('debug')('routes') 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('/media/', express.static(config.upload_path)) router.use('/api', api) @@ -27,6 +32,4 @@ router.use((error, req, res, next) => { res.status(500).send('500: Internal Server Error') }) - - module.exports = router diff --git a/store/index.js b/store/index.js index bcbab503..8c5ce484 100644 --- a/store/index.js +++ b/store/index.js @@ -12,7 +12,7 @@ export const state = () => ({ allow_anon_event: true, allow_recurrent_event: true, recurrent_event_visible: false, - enable_federation: false, + enable_federation: false }, filters: { tags: [], @@ -26,37 +26,34 @@ export const state = () => ({ export const getters = { // filter matches search tag/place - filteredEvents: state => { - + filteredEvents: state => { const search_for_tags = !!state.filters.tags.length const search_for_places = !!state.filters.places.length return state.events.filter(e => { - // 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 - 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 (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) { - const common_tags = intersection(e.tags, state.filters.tags); - if (common_tags.length > 0) return true + const common_tags = intersection(e.tags, state.filters.tags) + 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 }) }, // filter matches search tag/place including past events - filteredEventsWithPast: state => { - + filteredEventsWithPast: state => { const search_for_tags = !!state.filters.tags.length const search_for_places = !!state.filters.places.length @@ -64,75 +61,75 @@ export const getters = { const match = false // 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 (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) { - const common_tags = intersection(e.tags, state.filters.tags); - if (common_tags.length > 0) return true + const common_tags = intersection(e.tags, state.filters.tags) + 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 }) } } export const mutations = { - setEvents(state, events) { + setEvents (state, events) { // set`past` and `newDay` flags to event let lastDay = null state.events = events.map(e => { const currentDay = moment.unix(e.start_datetime).date() e.newDay = (!lastDay || lastDay !== currentDay) && currentDay lastDay = currentDay - const end_datetime = e.end_datetime || e.start_datetime+3600*2 + const end_datetime = e.end_datetime || e.start_datetime + 3600 * 2 const past = ((moment().unix()) - end_datetime) > 0 e.past = !!past return e }) }, - addEvent(state, event) { + addEvent (state, event) { state.events.push(event) }, - updateEvent(state, event) { + updateEvent (state, event) { state.events = state.events.map((e) => { - if (e.id !== event.id) return e + if (e.id !== event.id) { return e } return event }) }, - delEvent(state, eventId) { + delEvent (state, eventId) { state.events = state.events.filter(ev => { return ev.id !== eventId }) }, - update(state, { tags, places }) { + update (state, { tags, places }) { state.tags = tags state.places = places }, - setSearchTags(state, tags) { + setSearchTags (state, tags) { state.filters.tags = tags }, - setSearchPlaces(state, places) { + setSearchPlaces (state, places) { state.filters.places = places }, - showPastEvents(state, show) { + showPastEvents (state, show) { state.filters.show_past_events = show }, - showRecurrentEvents(state, show) { + showRecurrentEvents (state, show) { state.filters.show_recurrent_events = show }, - setSettings(state, settings) { + setSettings (state, settings) { state.settings = settings }, - setSetting(state, setting) { + setSetting (state, setting) { state.settings[setting.key] = setting.value }, - setLocale(state, locale) { + setLocale (state, locale) { state.locale = locale } } @@ -140,51 +137,50 @@ export const mutations = { export const actions = { // this method is called server side only for each request // we use it to get configuration from db, setting locale, etc... - async nuxtServerInit ({ commit }, { app, req } ) { + async nuxtServerInit ({ commit }, { app, req }) { const settings = await app.$axios.$get('/settings') commit('setSettings', settings) // apply settings 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}`) commit('setEvents', events) }, - async updateMeta({ commit }) { + async updateMeta ({ commit }) { const { tags, places } = await this.$axios.$get('/event/meta') commit('update', { tags, places }) }, - async addEvent({ commit }, formData) { + async addEvent ({ commit }, formData) { const event = await this.$axios.$post('/user/event', formData) if (event.user) { commit('addEvent', event) } }, - async updateEvent({ commit }, formData) { + async updateEvent ({ commit }, formData) { const event = await this.$axios.$put('/user/event', formData) if (event.user) { commit('updateEvent', event) } }, - delEvent({ commit }, eventId) { + delEvent ({ commit }, eventId) { commit('delEvent', eventId) }, - setSearchTags({ commit }, tags) { + setSearchTags ({ commit }, tags) { commit('setSearchTags', tags) }, - setSearchPlaces({ commit }, places) { + setSearchPlaces ({ commit }, places) { commit('setSearchPlaces', places) }, - showPastEvents({ commit }, show) { + showPastEvents ({ commit }, show) { commit('showPastEvents', show) }, - showRecurrentEvents({ commit }, show ) { + showRecurrentEvents ({ commit }, show) { commit('showRecurrentEvents', show) }, - async setSetting({ commit }, setting) { - await this.$axios.$post('/settings', setting ) + async setSetting ({ commit }, setting) { + await this.$axios.$post('/settings', setting) commit('setSetting', setting) - }, + } } diff --git a/yarn.lock b/yarn.lock index 7fea9151..c7717c48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4162,6 +4162,11 @@ express-jwt@^5.3.1: jsonwebtoken "^8.1.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: version "0.3.1" resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20"