diff --git a/assets/style.less b/assets/style.less index 7199a8df..00bd4bef 100644 --- a/assets/style.less +++ b/assets/style.less @@ -25,7 +25,7 @@ html, body { // } .el-card { - max-width: 630px; + max-width: 660px; margin: 30px auto; } 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 81380793..8826745d 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -1,14 +1,32 @@ - - 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 9c9187cf..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')) @@ -14,14 +13,14 @@ export default ({ app, store }) => { // Vue.filter('hour', value => moment(value).locale(store.state.locale).format('HH:mm')) // shown in mobile homepage - Vue.filter('day', value => moment.unix(value).locale(store.state.locale).format('dddd, D MMMM')) + Vue.filter('day', value => moment.unix(value).locale(store.state.locale).format('dddd, D MMM')) // Vue.filter('month', value => moment(value).locale(store.state.locale).format('MMM')) // format event start/end datetime based on page 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 72a875db..0402b04f 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -11,9 +11,14 @@ 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: { email: { [Op.eq]: req.body && req.body.email } } }) + const user = await User.findOne({ where: { + [Op.or]: [ + { email: req.body.email }, + { username: req.body.email } + ] + } }) if (!user) { res.status(403).json({ success: false, message: 'auth.fail' }) } else if (user) { @@ -39,13 +44,7 @@ const userController = { } }, - async setToken(req, res) { - req.user.mastodon_auth = req.body - await req.user.save() - res.json(req.user) - }, - - 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)) { @@ -69,7 +68,7 @@ const userController = { }, // ADD EVENT - async addEvent(req, res) { + async addEvent (req, res) { const body = req.body const eventDetails = { @@ -89,9 +88,9 @@ const userController = { eventDetails.image_path = req.file.filename } - let event = await Event.create(eventDetails) + const event = await Event.create(eventDetails) - // create place if needs to + // create place if needed let place try { place = await Place.findOrCreate({ where: { name: body.place_name }, @@ -106,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 } @@ -119,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) { @@ -169,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 }) @@ -181,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 { @@ -210,33 +207,42 @@ 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) { - const user = await User.findByPk(req.body.id) - if (user) { - if (!user.is_active && req.body.is_active && user.recover_code) { - mail.send(user.email, 'confirm', { user, config }) - } - await user.update(req.body) - res.json(user) - } else { - res.sendStatus(400) + 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 (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 + + if (!req.body.password) { delete req.body.password } + + await user.update(req.body) + + if (!user.is_active && req.body.is_active && user.recover_code) { + mail.send(user.email, 'confirm', { user, config }) + } + 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 @@ -266,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') @@ -278,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 67c54145..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 } } } }) @@ -44,13 +46,13 @@ api.post('/user/recover_password', userController.updatePasswordWithRecoverCode) api.post('/user/register', userController.register) api.post('/user', jwt, isAuth, isAdmin, userController.create) -// update user (disable/) -api.put('/user', jwt, isAuth, isAdmin, userController.update) +// 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 fbfe85f1..29944938 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,26 +36,48 @@ module.exports = (sequelize, DataTypes) => { event.hasMany(models.comment) } - // - event.prototype.toAP = function (username, follower) { - const tags = this.tags && '-' + this.tags.map(t => '#' + t.tag).join(' ') - const content = `${this.title} @${this.place.name} - ${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}
- ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description} ${tags}
` + // + event.prototype.toAP = function (username, follower = []) { + const tags = this.tags && this.tags.map(t => '#' + t.tag).join(' ') + const content = `${this.title}
+ 📍${this.place.name}
+ ⏰ ${moment.unix(this.start_datetime).format('dddd, D MMMM (HH:mm)')}

+ ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}
+ ${tags}
` + + const attachment = [] + if (this.image_path) { + attachment.push({ + type: 'Document', + mediaType: 'image/jpeg', + url: `${config.baseurl}/media/${this.image_path}`, + name: null, + blurHash: null + }) + } return { - id: `${config.baseurl}/federation/m/c_${this.id}`, - type: 'Create', - actor: `${config.baseurl}/federation/u/${username}`, - object: { - 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 - } + // id: `${config.baseurl}/federation/m/c_${this.id}`, + // type: 'Create', + // actor: `${config.baseurl}/federation/u/${username}`, + // url: `${config.baseurl}/federation/m/${this.id}`, + // object: { + 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 d60aa2dd..809d9430 100644 --- a/server/api/models/user.js +++ b/server/api/models/user.js @@ -2,6 +2,7 @@ const bcrypt = require('bcryptjs') const crypto = require('crypto') const util = require('util') +const debug = require('debug')('model:user') const generateKeyPair = util.promisify(crypto.generateKeyPair) @@ -14,9 +15,10 @@ module.exports = (sequelize, DataTypes) => { allowNull: false }, display_name: DataTypes.STRING, + settings: DataTypes.JSON, email: { type: DataTypes.STRING, - unique: { msg: 'error.email_taken' }, + unique: { msg: 'error.email_taken' }, index: true, allowNull: false }, @@ -44,13 +46,14 @@ 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 } user.beforeSave(async (user, options) => { if (user.changed('password')) { + debug('Password for %s modified', user.username) const salt = await bcrypt.genSalt(10) const hash = await bcrypt.hash(user.password, salt) user.password = hash @@ -58,6 +61,7 @@ module.exports = (sequelize, DataTypes) => { }) user.beforeCreate(async (user, options) => { + debug('Create a new user => %s', user.username) // generate rsa keys const rsa = await generateKeyPair('rsa', { modulusLength: 4096, @@ -74,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 15ef55d0..203902ad 100644 --- a/server/federation/comments.js +++ b/server/federation/comments.js @@ -5,17 +5,22 @@ 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(`${config.baseurl}/federation/m/(.*)`) - if (!match || match.length<2) return res.status(404).send('Event not found!') + 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) + return res.status(404).send('Event not found!') + } let event = await Event.findByPk(Number(match[1])) debug('comment coming for %s', inReplyTo) if (!event) { // in reply to another comment... - const comment = await Comment.findByPk(inReplyTo, { include: [Event] }) - if (!comment) return res.status(404).send('Not found') + const comment = await Comment.findOne({ where: { activitypub_id: inReplyTo }, include: [Event] }) + if (!comment) { return res.status(404).send('Not found') } event = comment.event } debug('comment from %s to "%s"', req.body.actor, event.title) @@ -27,6 +32,16 @@ module.exports = { }) res.sendStatus(201) + }, + async remove (req, res) { + 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') + } + await comment.destroy() + debug('Comment %s removed!', req.body.object.id) + return res.sendStatus(201) } } 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 31b7bad0..4fe8facf 100644 --- a/server/federation/follows.js +++ b/server/federation/follows.js @@ -2,38 +2,50 @@ const config = require('config') const Helpers = require('./helpers') const { user: User } = require('../api/models') const crypto = require('crypto') -const debug = require('debug')('follows') +const debug = require('debug')('federation:follows') 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) { - debug('%s followed by %s (%d)', username, body.actor, user.followers.length) + 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) }, // unfollow request from fediverse - unfollow () { - console.error('inside unfollow') + 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') } + + if (body.actor !== body.object.actor) { + debug('Unfollow an user created by a different actor !?!?') + return res.status(400).send('Bad things') + } + const followers = user.followers.filter(follower => follower !== body.actor) + await user.update({ followers }) + debug('%s unfollowed by %s (%d)', username, body.actor, user.followers.length) + res.sendStatus(200) } } diff --git a/server/federation/helpers.js b/server/federation/helpers.js index 35fc7a0f..c780c6d0 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -3,33 +3,30 @@ const fetch = require('node-fetch') const crypto = require('crypto') const config = require('config') const httpSignature = require('http-signature') -const debug = require('debug')('fediverse:helpers') +const debug = require('debug')('federation:helpers') +const { user: User } = require('../api/models') +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 = new URL(to) - const toPath = toInbox.replace(toOrigin.origin, '') + const toOrigin = url.parse(to) + const toPath = toOrigin.path + '/inbox' // get the private key const privkey = user.rsa.privateKey const signer = crypto.createSign('sha256') const d = new Date() const stringToSign = `(request-target): post ${toPath}\nhost: ${toOrigin.hostname}\ndate: ${d.toUTCString()}` - console.error('stringToSign ', stringToSign) signer.update(stringToSign) signer.end() const signature = signer.sign(privkey) const signature_b64 = signature.toString('base64') const header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"` - console.error('header ', header) - console.error('requestTo ', toInbox) - console.error('host ', toOrigin.hostname) - const response = await fetch(toInbox, { + const ret = await fetch(toInbox, { headers: { 'Host': toOrigin.hostname, 'Date': d.toUTCString(), @@ -39,31 +36,71 @@ const Helpers = { }, method: 'POST', body: JSON.stringify(message) }) - - console.log('Response:', response.body, response.statusCode, response.status, response.statusMessage) + debug('sign %s => %s', ret.status, await ret.text()) }, - async sendEvent(event, user) { - const followers = user.followers - for(let follower of followers) { - debug('Notify %s with event %s', follower, event.title) - const body = event.toAP(user.username, follower) + + 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) { + debug('Instance admin not found (there is no user with email => %s)', config.admin) + return + } + + for (const follower of instanceAdmin.followers) { + debug('Notify %s with event %s (from admin user %s)', follower, event.title, instanceAdmin.username) + const body = { + id: `${config.baseurl}/federation/m/${event.id}#create`, + type: 'Create', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: [`${config.baseurl}/federation/u/${instanceAdmin.username}/followers`, follower], + actor: `${config.baseurl}/federation/u/${instanceAdmin.username}`, + object: event.toAP(instanceAdmin.username, [`${config.baseurl}/federation/u/${instanceAdmin.username}/followers`, follower]) + } + body['@context'] = 'https://www.w3.org/ns/activitystreams' + Helpers.signAndSend(body, instanceAdmin, follower) + } + + // in case the event is published by the Admin itself do not republish + if (instanceAdmin.id === user.id) { + debug('Event published by instance Admin') + return + } + + if (!user.settings.enable_federation || !user.username) { + debug('Federation disabled for user %d (%s)', user.id, user.username) + return + } + + for (const follower of user.followers) { + debug('Notify %s with event %s (from user %s)', follower, event.title, user.username) + const body = { + 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`, follower], + published: event.createdAt, + actor: `${config.baseurl}/federation/u/${user.username}`, + object: event.toAP(user.username, [`${config.baseurl}/federation/u/${user.username}/followers`, follower]) + } body['@context'] = 'https://www.w3.org/ns/activitystreams' Helpers.signAndSend(body, user, follower) - } + } }, - 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) @@ -76,25 +113,26 @@ 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) res.send('Request signature could not be verified', 401) } } diff --git a/server/federation/index.js b/server/federation/index.js index 9560694a..e2c4c3a5 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -4,35 +4,34 @@ const config = require('config') const cors = require('cors') const Follows = require('./follows') const Users = require('./users') -const { event: Event, user: User } = require('../api/models') +const { event: Event, user: User, tag: Tag, place: Place } = require('../api/models') 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}`) + // if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`) - const event = await Event.findByPk(req.params.event_id, { include: [ User ] }) - if (!event) return res.status(404).send('Not found') + const event = await Event.findByPk(req.params.event_id, { include: [ User, Tag, Place ] }) + 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 @@ -56,7 +55,7 @@ router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => { Ego.bookmark(req, res) break case 'Delete': - console.error('Delete ?!?!') + await Comments.remove(req, res) break case 'Create': // this is a reply 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 29f70c00..4e9a1df4 100644 --- a/server/federation/users.js +++ b/server/federation/users.js @@ -1,6 +1,7 @@ -const { user: User, event: Event } = require('../api/models') +const { user: User, event: Event, place: Place, tag: Tag } = require('../api/models') const config = require('config') const get = require('lodash/get') +const debug = require('debug')('fediverse:user') module.exports = { async get (req, res) { @@ -15,10 +16,21 @@ module.exports = { ], id: `${config.baseurl}/federation/u/${name}`, type: 'Person', - preferredUsername: name, + name: user.display_name || user.username, + preferredUsername: user.username, 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/x-icon', + url: config.baseurl + '/favicon.ico' + }, publicKey: { id: `${config.baseurl}/federation/u/${name}#main-key`, owner: `${config.baseurl}/federation/u/${name}`, @@ -30,63 +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: [ Event ], + 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') - console.error('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 2a970bcb..015b3559 100644 --- a/server/federation/webfinger.js +++ b/server/federation/webfinger.js @@ -5,18 +5,31 @@ const cors = require('cors') 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 = new URL(config.baseurl).host - const user = await User.findOne({where: { username: name } }) - if (!user) return res.status(404).send(`No record found for ${name}`) + 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 } }) + 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: [ @@ -37,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) @@ -71,7 +84,7 @@ router.get('/x-nodeinfo2', async (req, res) => { }, protocols: ['activitypub'], openRegistrations: settingsController.settings.allow_registration, - usage:{ + usage: { users: { total: 10 } @@ -82,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(` @@ -103,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 new file mode 100644 index 00000000..10c45991 --- /dev/null +++ b/server/migrations/20190910085948-user_settings.js @@ -0,0 +1,27 @@ +'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. + + Example: + return queryInterface.createTable('users', { id: Sequelize.INTEGER }); + */ + }, + + down: (queryInterface, Sequelize) => { + /* + Add reverting commands here. + Return a promise to correctly handle asynchronicity. + + Example: + 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..370d3c3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -708,6 +708,13 @@ resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" integrity sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw== +"@hapi/boom@^7.4.3": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.3.tgz#79ffed6ef8c625046a3bd069abed5a9d35fc50c1" + integrity sha512-3di+R+BcGS7HKy67Zi6mIga8orf67GdR0ubDEVBG1oqz3y9B70LewsuCMCSvWWLKlI6V1+266zqhYzjMrPGvZw== + dependencies: + "@hapi/hoek" "8.x.x" + "@hapi/hoek@6.x.x": version "6.2.4" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-6.2.4.tgz#4b95fbaccbfba90185690890bdf1a2fbbda10595" @@ -740,21 +747,22 @@ dependencies: "@hapi/hoek" "8.x.x" -"@ladjs/i18n@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@ladjs/i18n/-/i18n-1.1.0.tgz#50a20cbcd3f0f0d880be9dea873e3efec3b6b02c" - integrity sha512-Kynr5osjApDCyiik35MMNZC1lgjgrk7fbV6P1qHXKQ67sR/U85Ddnv1NNPc/2s08PQVjvIBNY96UACb0CivrWg== +"@ladjs/i18n@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@ladjs/i18n/-/i18n-1.2.1.tgz#74478f3495e2f6b1684e58ff043c040954c6ff35" + integrity sha512-rlo8e+2UIylCo/KiZuxd/DJsyGZ1XMFFJaxxVXMj6BO2qyfjB91pjCpIQxUmpSWddWQlPKxsm85avr1o2RG9Uw== dependencies: - auto-bind "^2.0.0" - boolean "^0.2.0" - boom "7.3.0" + "@hapi/boom" "^7.4.3" + auto-bind "^2.1.0" + boolean "^1.0.0" country-language "^0.1.7" + debug "^4.1.1" i18n "^0.8.3" i18n-locales "^0.0.2" - lodash "^4.17.11" - moment "^2.23.0" - qs "^6.6.0" - underscore.string "^3.3.5" + lodash "^4.17.15" + moment "^2.24.0" + qs "^6.8.0" + titleize "^2.1.0" "@nuxt/babel-preset-app@2.9.2": version "2.9.2" @@ -997,30 +1005,20 @@ webpack-node-externals "^1.7.2" webpackbar "^4.0.0" -"@nuxtjs/auth@^4.8.2": - version "4.8.2" - resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.8.2.tgz#0276fe3a4291b61ec0b7fd4328d43a118d47f603" - integrity sha512-LG+71qTGxValqDyhG1Zou5YyJSMQtMq4MaXd0gXsFFYlsPDEyysYtidoAG+LhUsO9grmAwWTvcqkXO2d94LNUg== +"@nuxtjs/auth@^4.8.3": + version "4.8.3" + resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.8.3.tgz#037509e0dea0329c9dae7be4f743cd9ff701efa5" + integrity sha512-t9RsEH/IdEl+tzR3qOV6lQlXv0sqD4CTdtJnpseVL7lBn1f1cKGGyDXsWdhOWiIKeLu7tl9HFzKXfKCQTNKzgA== dependencies: - "@nuxtjs/axios" "^5.5.4" + "@nuxtjs/axios" "^5.6.0" body-parser "^1.19.0" - consola "^2.9.0" + consola "^2.10.1" cookie "^0.4.0" dotprop "^1.2.0" is-https "^1.0.0" - js-cookie "^2.2.0" + js-cookie "^2.2.1" lodash "^4.17.15" - nanoid "^2.0.3" - -"@nuxtjs/axios@^5.5.4": - version "5.5.4" - resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.5.4.tgz#c4aee2322901b19d4072670c03144662a73ea6f4" - integrity sha512-/Ljsyh5VIc9paXGrQue7RQ+PpBNES1oit0g4l+ya1tfyKnZMpHSbghuLcv0xq+BpXlSEr690uemHbz54/N6U5w== - dependencies: - "@nuxtjs/proxy" "^1.3.3" - axios "^0.19.0" - axios-retry "^3.1.2" - consola "^2.7.1" + nanoid "^2.1.0" "@nuxtjs/axios@^5.6.0": version "5.6.0" @@ -1063,10 +1061,10 @@ mustache "^2.3.0" stack-trace "0.0.10" -"@sindresorhus/is@^0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.17.1.tgz#453b27750f358689c4aa3c9f32d9ace1f0929a79" - integrity sha512-kg/maAZD2Z2AHDFp7cY/ACokjUL0e7MaupTtGXkSW2SV4DJQEHdslFUioP0SMccotjwqTdI0b4XH/qZh6CN+kQ== +"@sindresorhus/is@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-1.0.0.tgz#4f475ff3f32b0a309b7faffd33328e93d7953330" + integrity sha512-3rlOB53XCVO7LfjXFx4bCGrZPPjkgYD7pP0E/yo4d57H32aYqD/QNmeXcVnx7CM5SxGScwl2P0b1kCDYZgNWqw== "@types/babel-types@*", "@types/babel-types@^7.0.0": version "7.0.7" @@ -1715,7 +1713,7 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -auto-bind@^2.0.0, auto-bind@^2.1.0: +auto-bind@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-2.1.0.tgz#254e12d53063d7cab90446ce021accfb3faa1464" integrity sha512-qZuFvkes1eh9lB2mg8/HG18C+5GIO51r+RrCSst/lh+i5B1CtVlkhTE488M805Nr3dKl0sM/pIFKSKUIlg3zUg== @@ -1933,7 +1931,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: +bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.3, bluebird@^3.5.5: version "3.5.5" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== @@ -1964,17 +1962,10 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boolean@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-0.2.0.tgz#808dff32ce1c87b828cc381428dc3b158d4f85cb" - integrity sha512-mDcM3ChboDuhv4glLXEH1us7jMiWXRSs3R13Okoo+kkFOlLIjvF1y88507wTfDf9zsuv0YffSDFUwX95VAT/mg== - -boom@7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9" - integrity sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A== - dependencies: - hoek "6.x.x" +boolean@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-1.0.0.tgz#45764b4aac187a050995b0a33d7579b6759f0dfd" + integrity sha512-IB1lgIywn37N9Aff8CciCblVpMUflgL42vyxPUH0IvaDdIi/QwBHKv1lq/HOkATHCfa7c4MbMYJ7Bo7hGuoI+w== bootstrap@^4.3.1: version "4.3.1" @@ -2723,7 +2714,7 @@ consola@^2.10.0, consola@^2.10.1: resolved "https://registry.yarnpkg.com/consola/-/consola-2.10.1.tgz#4693edba714677c878d520e4c7e4f69306b4b927" integrity sha512-4sxpH6SGFYLADfUip4vuY65f/gEogrzJoniVhNUYkJHtng0l8ZjnDCqxxrSVRHOHwKxsy8Vm5ONZh1wOR3/l/w== -consola@^2.5.6, consola@^2.6.0, consola@^2.7.1, consola@^2.9.0: +consola@^2.5.6, consola@^2.6.0, consola@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/consola/-/consola-2.9.0.tgz#57760e3a65a53ec27337f4add31505802d902278" integrity sha512-34Iue+LRcWbndFIfZc5boNizWlsrRjqIBJZTe591vImgbnq7nx2EzlrLtANj9TH2Fxm7puFJBJAOk5BhvZOddQ== @@ -3569,21 +3560,21 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -email-templates@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/email-templates/-/email-templates-6.0.0.tgz#cc235d49c967f16a15488906dd796f748f2a7531" - integrity sha512-NzneEyM+J/DpMY7hK4Ii1HBmiX/BTQyAf8OEZh1yU+O9uYMgnJr+JvpAxLkqRxeWeA0dT2IV5K+6UcF/jMJk7Q== +email-templates@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/email-templates/-/email-templates-6.0.2.tgz#8e71d5a65b51de32a80f36a37bcd4fead0d5c70a" + integrity sha512-eRM3HM6KVDkKhnLTt8sTpg3kFqG/4E/PVNJi3PtYtfVX+LSnd399f3tvRu9XyMu8jVisX3fXAAkvwBIevkwkAA== dependencies: - "@ladjs/i18n" "^1.1.0" - "@sindresorhus/is" "^0.17.1" + "@ladjs/i18n" "^1.2.0" + "@sindresorhus/is" "^1.0.0" auto-bind "^2.1.0" consolidate "^0.15.1" debug "^4.1.1" - get-paths "^0.0.4" + get-paths "^0.0.7" html-to-text "^5.1.1" juice "^5.2.0" - lodash "^4.17.11" - nodemailer "^6.2.1" + lodash "^4.17.15" + nodemailer "^6.3.0" pify "^4.0.1" preview-email "^1.0.1" @@ -3741,10 +3732,10 @@ eslint-ast-utils@^1.0.0: lodash.get "^4.4.2" lodash.zip "^4.2.0" -eslint-config-prettier@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.2.0.tgz#80e0b8714e3f6868c4ac2a25fbf39c02e73527a7" - integrity sha512-VLsgK/D+S/FEsda7Um1+N8FThec6LqE3vhcMyp8mlmto97y3fGf3DX7byJexGuOb1QY0Z/zz222U5t+xSfcZDQ== +eslint-config-prettier@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.3.0.tgz#e73b48e59dc49d950843f3eb96d519e2248286a3" + integrity sha512-EWaGjlDAZRzVFveh2Jsglcere2KK5CJBhkNSa1xs3KfMUGdRiT7lG089eqPdvlzWHpAqaekubOsOMu8W8Yk71A== dependencies: get-stdin "^6.0.0" @@ -4162,6 +4153,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" @@ -4527,15 +4523,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -4618,13 +4605,12 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-paths@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/get-paths/-/get-paths-0.0.4.tgz#0a053ca424bab976383ce49929528ad642e2a0df" - integrity sha512-+AxlfMGN7FuJr2zhT6aErH08HMKkRwynTTHtWCenIWkIZgx2OlkZKgt7SM4+rh8Dfi32lo6HcvqeTLxph3kjQw== +get-paths@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/get-paths/-/get-paths-0.0.7.tgz#15331086752077cf130166ccd233a1cdbeefcf38" + integrity sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA== dependencies: - bluebird "^3.5.1" - fs-extra "^4.0.2" + pify "^4.0.1" get-stdin@^6.0.0: version "6.0.0" @@ -4937,11 +4923,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@6.x.x: - version "6.1.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" - integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== - homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -5705,10 +5686,10 @@ js-beautify@^1.8.8: mkdirp "~0.5.1" nopt "~4.0.1" -js-cookie@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb" - integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s= +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== js-levenshtein@^1.1.3: version "1.1.6" @@ -6563,7 +6544,7 @@ moment-timezone@^0.5.21: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.23.0, moment@^2.24.0: +"moment@>= 2.9.0", moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -6645,10 +6626,10 @@ nan@^2.12.1, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nanoid@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.0.3.tgz#dde999e173bc9d7bd2ee2746b89909ade98e075e" - integrity sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw== +nanoid@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.1.tgz#524fd4acd45c126e0c87cd43ab5ee8346e695df9" + integrity sha512-0YbJdaL4JFoejIOoawgLcYValFGJ2iyUuVDIWL3g8Es87SSOWFbWdRUMV3VMSiyPs3SQ3QxCIxFX00q5DLkMCw== nanomatch@^1.2.9: version "1.2.13" @@ -6809,7 +6790,7 @@ node-res@^5.0.1: on-finished "^2.3.0" vary "^1.1.2" -nodemailer@^6.2.1: +nodemailer@^6.2.1, nodemailer@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12" integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw== @@ -8502,11 +8483,16 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.7.0, qs@^6.6.0: +qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081" + integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -9098,10 +9084,10 @@ sequelize-pool@^2.3.0: resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-2.3.0.tgz#64f1fe8744228172c474f530604b6133be64993d" integrity sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA== -sequelize@^5.18.1: - version "5.18.1" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.18.1.tgz#31d5246dcdf01d0ac317454c28fb598359d5b60a" - integrity sha512-jngo7pqilyOycMv6ZEwHLVn2wuHi/xkSQZfwK4jhjG8ta1HWYJK3XyQDFdhVEOH1GEq9pnqaf+7Kwqm+eqXD9Q== +sequelize@^5.18.4: + version "5.18.4" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.18.4.tgz#1e2c7eabe4c554fa257a0115fad39f271bd56150" + integrity sha512-bBmJqpO1H8Z7L0xzITqVo5KHXFI7GmKfGl/5SIPDKsuUMbuZT98s+gyGeaLXpOWGH1ZUO79hvJ8z74vNcxBWHg== dependencies: bluebird "^3.5.0" cls-bluebird "^2.1.0" @@ -9415,7 +9401,7 @@ split@^1.0.0: dependencies: through "2" -sprintf-js@>=1.0.3, sprintf-js@^1.0.3: +sprintf-js@>=1.0.3: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== @@ -9863,6 +9849,11 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +titleize@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-2.1.0.tgz#5530de07c22147a0488887172b5bd94f5b30a48f" + integrity sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10092,14 +10083,6 @@ underscore.deep@~0.5.1: resolved "https://registry.yarnpkg.com/underscore.deep/-/underscore.deep-0.5.1.tgz#072671f48d68735c34223fcfef63e69e5276cc2b" integrity sha1-ByZx9I1oc1w0Ij/P72PmnlJ2zCs= -underscore.string@^3.3.5: - version "3.3.5" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023" - integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg== - dependencies: - sprintf-js "^1.0.3" - util-deprecate "^1.0.2" - underscore@~1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" @@ -10269,7 +10252,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=