From ce3fd08c22d2e806617a8f1d780c8af2a57e2444 Mon Sep 17 00:00:00 2001 From: les Date: Sat, 7 Sep 2019 11:57:54 +0200 Subject: [PATCH 1/7] enable federation at user level --- assets/style.less | 2 +- pages/settings.vue | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) 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/pages/settings.vue b/pages/settings.vue index 81380793..8e77566a 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -1,13 +1,27 @@ - - diff --git a/pages/register.vue b/pages/register.vue index a4f8f67f..a8b3c094 100644 --- a/pages/register.vue +++ b/pages/register.vue @@ -44,13 +44,13 @@ export default { title: this.settings.title + ' - ' + this.$t('common.register') } }, - validate ({store}) { + validate ({ store }) { return store.state.settings.allow_registration }, computed: { ...mapState(['settings']), disabled () { - if (process.server) return false + if (process.server) { return false } return !this.user.password || !this.user.email || !this.user.description } }, @@ -65,7 +65,7 @@ export default { message: this.$t(`register.${user.is_admin ? 'admin_' : ''}complete`), type: 'success' }) - this.$router.replace("/") + this.$router.replace('/') } catch (e) { const error = get(e, 'response.data.errors[0].message', String(e)) Message({ diff --git a/pages/settings.vue b/pages/settings.vue index e4766a3c..8826745d 100644 --- a/pages/settings.vue +++ b/pages/settings.vue @@ -14,19 +14,19 @@ div(v-if='settings.enable_federation') el-form-item(:label="$t('admin.enable_federation')") el-switch(v-model='user.settings.enable_federation') - + div(v-if='user.settings.enable_federation') el-form-item(:label="$t('common.username')") el-input(v-if='user.username.length==0' type='text' name='username' v-model='user.username') template(slot='suffix') @{{baseurl}} span(v-else) {{user.username}}@{{baseurl}} //- el-button(slot='append') {{$t('common.save')}} - + el-form-item(:label="$t('common.displayname')") el-input(type='text' v-model='user.display_name') el-button(type='success' native-type='submit') {{$t('common.save')}} - + el-divider {{$t('settings.danger_section')}} p {{$t('settings.remove_account')}} el-button(type='danger' @click='remove_account') {{$t('common.remove')}} @@ -34,16 +34,16 @@ - diff --git a/pages/user_confirm/_code.vue b/pages/user_confirm/_code.vue index c53de7c9..bc62a80a 100644 --- a/pages/user_confirm/_code.vue +++ b/pages/user_confirm/_code.vue @@ -6,7 +6,7 @@ h5 {{$t('confirm.title')}} p(v-if='valid' v-html='$t("confirm.valid")') p(v-else) {{$t('confirm.not_valid')}} - + - - diff --git a/plugins/axios.js b/plugins/axios.js index 78a17eda..dba773fc 100644 --- a/plugins/axios.js +++ b/plugins/axios.js @@ -1,5 +1,5 @@ -export default function({ $axios, store }) { +export default function ({ $axios, store }) { if (process.client) { $axios.defaults.baseURL = window.location.origin + '/api' } -} \ No newline at end of file +} diff --git a/plugins/filters.js b/plugins/filters.js index 2d9e7694..f48c9162 100644 --- a/plugins/filters.js +++ b/plugins/filters.js @@ -4,7 +4,6 @@ import 'dayjs/locale/it' import 'dayjs/locale/es' export default ({ app, store }) => { - // replace links with anchors // TODO: remove fb tracking id Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '$1')) @@ -21,7 +20,7 @@ export default ({ app, store }) => { Vue.filter('when', (event, where) => { moment.locale(store.state.locale) - //{start,end}_datetime are unix timestamp + // {start,end}_datetime are unix timestamp const start = moment.unix(event.start_datetime) const end = moment.unix(event.end_datetime) @@ -30,12 +29,12 @@ export default ({ app, store }) => { // recurrent event if (event.recurrent && where !== 'home') { const { frequency, days, type } = JSON.parse(event.recurrent) - if ( frequency === '1w' || frequency === '2w' ) { - const recurrent = app.i18n.tc(`event.recurrent_${frequency}_days`, days.length, {days: days.map(d => moment().day(d-1).format('dddd'))}) + if (frequency === '1w' || frequency === '2w') { + const recurrent = app.i18n.tc(`event.recurrent_${frequency}_days`, days.length, { days: days.map(d => moment().day(d - 1).format('dddd')) }) return `${normal} - ${recurrent}` } else if (frequency === '1m' || frequency === '2m') { - const d = type === 'ordinal' ? days : days.map(d => moment().day(d-1).format('dddd')) - const recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, {days: d}) + const d = type === 'ordinal' ? days : days.map(d => moment().day(d - 1).format('dddd')) + const recurrent = app.i18n.tc(`event.recurrent_${frequency}_${type}`, days.length, { days: d }) return `${normal} - ${recurrent}` } return 'recurrent ' @@ -44,7 +43,7 @@ export default ({ app, store }) => { // multidate if (event.multidate) { return `${start.format('ddd, D MMMM (HH:mm)')} - ${end.format('ddd, D MMMM')}` - } + } // normal event if (event.end_datetime && event.end_datetime !== event.start_datetime) { diff --git a/plugins/i18n.js b/plugins/i18n.js index 4f5b69a6..340d6f96 100644 --- a/plugins/i18n.js +++ b/plugins/i18n.js @@ -10,8 +10,8 @@ export default async ({ app, store }) => { // This way we can use it in middleware and pages asyncData/fetch const user_locale = await app.$axios.$get('/settings/user_locale') - for(let lang in user_locale) { - if (locales[lang]) merge(locales[lang], user_locale[lang]) + for (const lang in user_locale) { + if (locales[lang]) { merge(locales[lang], user_locale[lang]) } } app.i18n = new VueI18n({ diff --git a/server/api/auth.js b/server/api/auth.js index c88c9333..10a669b8 100644 --- a/server/api/auth.js +++ b/server/api/auth.js @@ -2,8 +2,8 @@ const { Op } = require('sequelize') const { user: User } = require('./models') const Auth = { - async fillUser(req, res, next) { - if (!req.user) return next() + async fillUser (req, res, next) { + if (!req.user) { return next() } req.user = await User.findOne({ where: { id: { [Op.eq]: req.user.id }, is_active: true } }).catch(e => { @@ -12,7 +12,7 @@ const Auth = { }) next() }, - async isAuth(req, res, next) { + async isAuth (req, res, next) { if (!req.user) { return res .status(403) @@ -29,15 +29,15 @@ const Auth = { } next() }, - isAdmin(req, res, next) { + isAdmin (req, res, next) { if (!req.user) { return res .status(403) .send({ message: 'Failed to authenticate token ' }) } - if (req.user.is_admin && req.user.is_active) return next() + if (req.user.is_admin && req.user.is_active) { return next() } return res.status(403).send({ message: 'Admin needed' }) - }, + } } diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 44c47cdf..db9cb6b7 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -9,7 +9,7 @@ const federation = require('../../federation/helpers') const eventController = { - async addComment(req, res) { + async addComment (req, res) { // comment could be added to an event or to another comment let event = await Event.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } } }) if (!event) { @@ -21,11 +21,11 @@ const eventController = { res.json(comment) }, - async getMeta(req, res) { + async getMeta (req, res) { const places = await Place.findAll({ order: [[Sequelize.literal('weigth'), 'DESC']], attributes: { - include: [[Sequelize.fn('count', Sequelize.col('events.placeId')) , 'weigth']], + include: [[Sequelize.fn('count', Sequelize.col('events.placeId')), 'weigth']], exclude: ['weigth', 'createdAt', 'updatedAt'] }, include: [{ model: Event, attributes: [] }], @@ -36,25 +36,25 @@ const eventController = { order: [['weigth', 'DESC']], attributes: { exclude: ['createdAt', 'updatedAt'] - }, + } }) res.json({ tags, places }) }, - async getNotifications(event) { - function match(event, filters) { + async getNotifications (event) { + function match (event, filters) { // matches if no filter specified - if (!filters) return true + if (!filters) { return true } // check for visibility - if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) return false + if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false } - if (!filters.tags && !filters.places) return true - if (!filters.tags.length && !filters.places.length) return true + if (!filters.tags && !filters.places) { return true } + if (!filters.tags.length && !filters.places.length) { return true } if (filters.tags.length) { const m = lodash.intersection(event.tags.map(t => t.tag), filters.tags) - if (m.length > 0) return true + if (m.length > 0) { return true } } if (filters.places.length) { if (filters.places.find(p => p === event.place.name)) { @@ -68,7 +68,7 @@ const eventController = { return notifications.filter(notification => match(event, notification.filters)) }, - async updateTag(req, res) { + async updateTag (req, res) { const tag = await Tag.findByPk(req.body.tag) if (tag) { res.json(await tag.update(req.body)) @@ -77,7 +77,7 @@ const eventController = { } }, - async updatePlace(req, res) { + async updatePlace (req, res) { const place = await Place.findByPk(req.body.id) await place.update(req.body) res.json(place) @@ -85,12 +85,12 @@ const eventController = { // TODO retrieve next/prev event also // select id, start_datetime, title from events where start_datetime > (select start_datetime from events where id=89) order by start_datetime limit 20; - async get(req, res) { + async get (req, res) { const is_admin = req.user && req.user.is_admin const id = req.params.event_id - let event = await Event.findByPk(id, { + const event = await Event.findByPk(id, { plain: true, - attributes: { + attributes: { exclude: ['createdAt', 'updatedAt'] }, include: [ @@ -109,29 +109,29 @@ const eventController = { } }, - async confirm(req, res) { + async confirm (req, res) { const id = Number(req.params.event_id) const event = await Event.findByPk(id) - if (!event) return res.sendStatus(404) + if (!event) { return res.sendStatus(404) } try { event.is_visible = true await event.save() res.sendStatus(200) - + // send notification - //notifier.notifyEvent(event.id) - //federation.sendEvent(event, req.user) + // notifier.notifyEvent(event.id) + // federation.sendEvent(event, req.user) } catch (e) { res.sendStatus(404) } }, - async unconfirm(req, res) { + async unconfirm (req, res) { const id = Number(req.params.event_id) const event = await Event.findByPk(id) - if (!event) return sendStatus(404) + if (!event) { return sendStatus(404) } try { event.is_visible = false @@ -142,7 +142,7 @@ const eventController = { } }, - async getUnconfirmed(req, res) { + async getUnconfirmed (req, res) { const events = await Event.findAll({ where: { is_visible: false @@ -153,7 +153,7 @@ const eventController = { res.json(events) }, - async addNotification(req, res) { + async addNotification (req, res) { try { const notification = { filters: { is_visible: true }, @@ -168,7 +168,7 @@ const eventController = { } }, - async delNotification(req, res) { + async delNotification (req, res) { const remove_code = req.params.code try { const notification = await Notification.findOne({ where: { remove_code: { [Op.eq]: remove_code } } }) @@ -179,7 +179,7 @@ const eventController = { res.sendStatus(200) }, - async getAll(req, res) { + async getAll (req, res) { // this is due how v-calendar shows dates const start = moment() .year(req.params.year) @@ -193,7 +193,7 @@ const eventController = { .endOf('month') const shownDays = end.diff(start, 'days') - if (shownDays <= 35) end = end.add(1, 'week') + if (shownDays <= 35) { end = end.add(1, 'week') } end = end.endOf('week') let events = await Event.findAll({ @@ -202,10 +202,10 @@ const eventController = { is_visible: true, [Op.or]: [ // return all recurrent events - {recurrent: { [Op.ne]: null }}, + { recurrent: { [Op.ne]: null } }, // and events in specified range - { start_datetime: { [Op.between]: [start.unix(), end.unix()] }} + { start_datetime: { [Op.between]: [start.unix(), end.unix()] } } ] }, attributes: { exclude: ['createdAt', 'updatedAt', 'placeId' ] }, @@ -223,10 +223,10 @@ const eventController = { }) // build singular events from a recurrent pattern - function createEventsFromRecurrent(e, dueTo=null) { + function createEventsFromRecurrent (e, dueTo = null) { const events = [] const recurrent = JSON.parse(e.recurrent) - if (!recurrent.frequency) return false + if (!recurrent.frequency) { return false } let cursor = moment(start).startOf('week') const start_date = moment.unix(e.start_datetime) @@ -236,18 +236,18 @@ const eventController = { const type = recurrent.type // default frequency is '1d' => each day - const toAdd = { n: 1, unit: 'day'} + const toAdd = { n: 1, unit: 'day' } cursor.set('hour', start_date.hour()).set('minute', start_date.minutes()) // each week or 2 (search for the first specified day) if (frequency === '1w' || frequency === '2w') { - cursor.add(days[0]-1, 'day') + cursor.add(days[0] - 1, 'day') if (frequency === '2w') { - const nWeeks = cursor.diff(e.start_datetime, 'w')%2 - if (!nWeeks) cursor.add(1, 'week') + const nWeeks = cursor.diff(e.start_datetime, 'w') % 2 + if (!nWeeks) { cursor.add(1, 'week') } } toAdd.n = Number(frequency[0]) - toAdd.unit = 'week'; + toAdd.unit = 'week' // cursor.set('hour', start_date.hour()).set('minute', start_date.minutes()) } @@ -263,37 +263,35 @@ const eventController = { } } - // add event at specified frequency + // add event at specified frequency while (true) { - let first_event_of_week = cursor.clone() + const first_event_of_week = cursor.clone() days.forEach(d => { if (type === 'ordinal') { cursor.date(d) } else { - cursor.day(d-1) + cursor.day(d - 1) } - if (cursor.isAfter(dueTo) || cursor.isBefore(start)) return + if (cursor.isAfter(dueTo) || cursor.isBefore(start)) { return } e.start_datetime = cursor.unix() - e.end_datetime = e.start_datetime+duration - events.push( Object.assign({}, e) ) - }) - if (cursor.isAfter(dueTo)) break + e.end_datetime = e.start_datetime + duration + events.push(Object.assign({}, e)) + }) + if (cursor.isAfter(dueTo)) { break } cursor = first_event_of_week.add(toAdd.n, toAdd.unit) } - return events } let allEvents = events.filter(e => !e.recurrent) events.filter(e => e.recurrent).forEach(e => { const events = createEventsFromRecurrent(e, end) - if (events) - allEvents = allEvents.concat(events) + if (events) { allEvents = allEvents.concat(events) } }) // allEvents.sort((a,b) => a.start_datetime-b.start_datetime) - res.json(allEvents.sort((a,b) => a.start_datetime-b.start_datetime)) + res.json(allEvents.sort((a, b) => a.start_datetime - b.start_datetime)) } } diff --git a/server/api/controller/export.js b/server/api/controller/export.js index f664899e..eef46214 100644 --- a/server/api/controller/export.js +++ b/server/api/controller/export.js @@ -5,7 +5,7 @@ const ics = require('ics') const exportController = { - async export(req, res) { + async export (req, res) { const type = req.params.type const tags = req.query.tags const places = req.query.places @@ -40,12 +40,12 @@ const exportController = { } }, - feed(res, events) { + feed (res, events) { res.type('application/rss+xml; charset=UTF-8') res.render('feed/rss.pug', { events, config: process.env, moment }) }, - ics(res, events) { + ics (res, events) { const eventsMap = events.map(e => { const tmpStart = moment.unix(e.start_datetime) const tmpEnd = moment.unix(e.end_datetime) diff --git a/server/api/controller/user.js b/server/api/controller/user.js index e5ae862f..0402b04f 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -11,10 +11,10 @@ const settingsController = require('./settings') const federation = require('../../federation/helpers') const userController = { - async login(req, res) { + async login (req, res) { // find the user const user = await User.findOne({ where: { - [Op.or]: [ + [Op.or]: [ { email: req.body.email }, { username: req.body.email } ] @@ -44,7 +44,7 @@ const userController = { } }, - async delEvent(req, res) { + async delEvent (req, res) { const event = await Event.findByPk(req.params.id) // check if event is mine (or user is admin) if (event && (req.user.is_admin || req.user.id === event.userId)) { @@ -68,7 +68,7 @@ const userController = { }, // ADD EVENT - async addEvent(req, res) { + async addEvent (req, res) { const body = req.body const eventDetails = { @@ -88,7 +88,7 @@ const userController = { eventDetails.image_path = req.file.filename } - let event = await Event.create(eventDetails) + const event = await Event.create(eventDetails) // create place if needed let place @@ -105,7 +105,7 @@ const userController = { if (body.tags) { await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true }) const tags = await Tag.findAll({ where: { tag: { [Op.in]: body.tags } } }) - await Promise.all(tags.map(t => t.update({weigth: Number(t.weigth)+1}))) + await Promise.all(tags.map(t => t.update({ weigth: Number(t.weigth) + 1 }))) await event.addTags(tags) event.tags = tags } @@ -118,17 +118,15 @@ const userController = { // send response to client res.json(event) - if (req.user) - federation.sendEvent(event, req.user) + if (req.user) { federation.sendEvent(event, req.user) } // res.sendStatus(200) // send notification (mastodon/email/confirmation) // notifier.notifyEvent(event.id) - }, - async updateEvent(req, res) { + async updateEvent (req, res) { const body = req.body const event = await Event.findByPk(body.id) if (!req.user.is_admin && event.userId !== req.user.id) { @@ -168,10 +166,10 @@ const userController = { res.json(newEvent) }, - async forgotPassword(req, res) { + async forgotPassword (req, res) { const email = req.body.email const user = await User.findOne({ where: { email: { [Op.eq]: email } } }) - if (!user) return res.sendStatus(200) + if (!user) { return res.sendStatus(200) } user.recover_code = crypto.randomBytes(16).toString('hex') mail.send(user.email, 'recover', { user, config }) @@ -180,25 +178,25 @@ const userController = { res.sendStatus(200) }, - async checkRecoverCode(req, res) { + async checkRecoverCode (req, res) { const recover_code = req.body.recover_code - if (!recover_code) return res.sendStatus(400) + if (!recover_code) { return res.sendStatus(400) } const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } }) - if (!user) return res.sendStatus(400) + if (!user) { return res.sendStatus(400) } try { - await user.update({ recover_code: ''}) + await user.update({ recover_code: '' }) res.sendStatus(200) } catch (e) { res.sendStatus(400) } }, - async updatePasswordWithRecoverCode(req, res) { + async updatePasswordWithRecoverCode (req, res) { const recover_code = req.body.recover_code const password = req.body.password - if (!recover_code || !password) return res.sendStatus(400) + if (!recover_code || !password) { return res.sendStatus(400) } const user = await User.findOne({ where: { recover_code: { [Op.eq]: recover_code } } }) - if (!user) return res.sendStatus(400) + if (!user) { return res.sendStatus(400) } user.recover_code = '' user.password = password try { @@ -209,32 +207,31 @@ const userController = { } }, - current(req, res) { + current (req, res) { if (req.user) { res.json(req.user) } else { res.sendStatus(404) } }, - async getAll(req, res) { + async getAll (req, res) { const users = await User.findAll({ order: [['createdAt', 'DESC']] }) res.json(users) }, - async update(req, res) { + async update (req, res) { // user to modify user = await User.findByPk(req.body.id) - if (!user) return res.status(404).json({ success: false, message: 'User not found!' }) + if (!user) { return res.status(404).json({ success: false, message: 'User not found!' }) } if (req.body.id !== req.user.id && !req.user.is_admin) { return res.status(400).json({ succes: false, message: 'Not allowed' }) } // ensure username to not change if not empty - req.body.username = user.username ? user.username : req.body.username + req.body.username = user.username ? user.username : req.body.username - if (!req.body.password) - delete req.body.password + if (!req.body.password) { delete req.body.password } await user.update(req.body) @@ -244,9 +241,8 @@ const userController = { res.json(user) }, - - async register(req, res) { - if (!settingsController.settings.allow_registration) return res.sendStatus(404) + async register (req, res) { + if (!settingsController.settings.allow_registration) { return res.sendStatus(404) } const n_users = await User.count() try { // the first registered user will be an active admin @@ -276,7 +272,7 @@ const userController = { } }, - async create(req, res) { + async create (req, res) { try { req.body.is_active = true req.body.recover_code = crypto.randomBytes(16).toString('hex') @@ -288,7 +284,7 @@ const userController = { } }, - async remove(req, res) { + async remove (req, res) { try { const user = await User.findByPk(req.params.id) user.destroy() diff --git a/server/api/index.js b/server/api/index.js index 635eb712..3c556447 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -14,6 +14,8 @@ const settingsController = require('./controller/settings') const storage = require('./storage') const upload = multer({ storage }) +const debug = require('debug')('api') + const api = express.Router() api.use(cookieParser()) api.use(bodyParser.urlencoded({ extended: false })) @@ -24,10 +26,10 @@ const jwt = expressJwt({ credentialsRequired: false, getToken: function fromHeaderOrQuerystring (req) { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { - return req.headers.authorization.split(' ')[1]; + return req.headers.authorization.split(' ')[1] } else if (req.cookies && req.cookies['auth._token.local']) { const [ prefix, token ] = req.cookies['auth._token.local'].split(' ') - if (prefix === 'Bearer') return token + if (prefix === 'Bearer') { return token } } } }) @@ -47,10 +49,10 @@ api.post('/user', jwt, isAuth, isAdmin, userController.create) // update user api.put('/user', jwt, isAuth, userController.update) -//delete user +// delete user api.delete('/user/:id', jwt, isAuth, isAdmin, userController.remove) -// +// // api.delete('/user', userController.remove) // get all users @@ -64,7 +66,7 @@ api.put('/place', jwt, isAuth, isAdmin, eventController.updatePlace) // add event api.post('/user/event', jwt, fillUser, upload.single('image'), userController.addEvent) - + // update event api.put('/user/event', jwt, isAuth, upload.single('image'), userController.updateEvent) @@ -100,4 +102,16 @@ api.get('/export/:type', exportController.export) api.get('/event/:month/:year', eventController.getAll) +// Handle 404 +api.use((req, res) => { + debug('404 Page not found: %s', req.path) + res.status(404).send('404: Page not Found') +}) + +// Handle 500 +api.use((error, req, res, next) => { + debug(error) + res.status(500).send('500: Internal Server Error') +}) + module.exports = api diff --git a/server/api/mail.js b/server/api/mail.js index aa29674c..9dcedcec 100644 --- a/server/api/mail.js +++ b/server/api/mail.js @@ -7,7 +7,7 @@ const debug = require('debug')('email') moment.locale('it') const mail = { - send(addresses, template, locals) { + send (addresses, template, locals) { debug(`Send ${template} email to ${addresses}`) const email = new Email({ views: { root: path.join(__dirname, '..', 'emails') }, @@ -30,7 +30,7 @@ const mail = { updateFiles: false, defaultLocale: settings.locale, locale: settings.locale, - locales: ['it', 'es'], + locales: ['it', 'es'] }, transport: config.smtp }) diff --git a/server/api/models/comment.js b/server/api/models/comment.js index 1eefb43a..7b836812 100644 --- a/server/api/models/comment.js +++ b/server/api/models/comment.js @@ -1,10 +1,10 @@ 'use strict' - module.exports = (sequelize, DataTypes) => { +module.exports = (sequelize, DataTypes) => { const comment = sequelize.define('comment', { activitypub_id: { type: DataTypes.STRING(18), index: true, - unique: true, + unique: true }, data: DataTypes.JSON }, {}) @@ -12,4 +12,4 @@ comment.belongsTo(models.event) } return comment -}; +} diff --git a/server/api/models/event.js b/server/api/models/event.js index 8ca92853..be2c085e 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -6,7 +6,7 @@ module.exports = (sequelize, DataTypes) => { id: { type: DataTypes.INTEGER, primaryKey: true, - autoIncrement: true, + autoIncrement: true }, title: DataTypes.STRING, slug: DataTypes.STRING, @@ -36,8 +36,8 @@ module.exports = (sequelize, DataTypes) => { event.hasMany(models.comment) } - // - event.prototype.toAP = function (username=config.admin, follower) { + // + event.prototype.toAP = function (username = config.admin, follower) { const tags = this.tags && this.tags.map(t => '#' + t.tag).join(' ') const content = `${this.title}
📍${this.place.name}
@@ -45,7 +45,7 @@ module.exports = (sequelize, DataTypes) => { ${this.description.length > 200 ? this.description.substr(0, 200) + '...' : this.description}
${tags}
` - let attachment = [] + const attachment = [] if (this.image_path) { attachment.push({ type: 'Document', @@ -62,19 +62,22 @@ module.exports = (sequelize, DataTypes) => { // actor: `${config.baseurl}/federation/u/${username}`, // url: `${config.baseurl}/federation/m/${this.id}`, // object: { - attachment, - tag: this.tags.map(tag => ({ - type: 'Hashtag', - name: '#' + tag.tag - })), - id: `${config.baseurl}/federation/m/${this.id}`, - type: 'Note', - published: this.createdAt, - attributedTo: `${config.baseurl}/federation/u/${username}`, - to: 'https://www.w3.org/ns/activitystreams#Public', - cc: follower ? follower: [], - content - } + type: 'Note', + id: `${config.baseurl}/federation/m/${this.id}`, + url: `${config.baseurl}/federation/m/${this.id}`, + attachment, + tag: this.tags.map(tag => ({ + type: 'Hashtag', + name: '#' + tag.tag + })), + published: this.createdAt, + attributedTo: `${config.baseurl}/federation/u/${username}`, + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: follower || [], + content, + summary: null, + sensitive: false, + // } } } diff --git a/server/api/models/index.js b/server/api/models/index.js index 4a02d06d..a25c09f1 100644 --- a/server/api/models/index.js +++ b/server/api/models/index.js @@ -7,7 +7,7 @@ const consola = require('consola') const db = {} const sequelize = new Sequelize(config.db) -sequelize.authenticate().catch( e => { +sequelize.authenticate().catch(e => { consola.error('Error connecting to DB: ', String(e)) process.exit(-1) }) @@ -21,15 +21,14 @@ fs const model = sequelize.import(path.join(__dirname, file)) db[model.name] = model }) - - Object.keys(db).forEach(modelName => { - if (db[modelName].associate) { - db[modelName].associate(db) - } - }) - - db.sequelize = sequelize - db.Sequelize = Sequelize - - module.exports = db - \ No newline at end of file + +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db) + } +}) + +db.sequelize = sequelize +db.Sequelize = Sequelize + +module.exports = db diff --git a/server/api/models/place.js b/server/api/models/place.js index 712cf96f..5dbf7ecf 100644 --- a/server/api/models/place.js +++ b/server/api/models/place.js @@ -3,7 +3,8 @@ module.exports = (sequelize, DataTypes) => { const place = sequelize.define('place', { name: { type: DataTypes.STRING, - unique: true, index: true, + unique: true, + index: true, allowNull: false }, address: DataTypes.STRING diff --git a/server/api/models/tag.js b/server/api/models/tag.js index 0f5465fe..59dd7465 100644 --- a/server/api/models/tag.js +++ b/server/api/models/tag.js @@ -15,4 +15,4 @@ module.exports = (sequelize, DataTypes) => { } return tag -}; +} diff --git a/server/api/models/user.js b/server/api/models/user.js index af062305..809d9430 100644 --- a/server/api/models/user.js +++ b/server/api/models/user.js @@ -18,7 +18,7 @@ module.exports = (sequelize, DataTypes) => { settings: DataTypes.JSON, email: { type: DataTypes.STRING, - unique: { msg: 'error.email_taken' }, + unique: { msg: 'error.email_taken' }, index: true, allowNull: false }, @@ -46,7 +46,7 @@ module.exports = (sequelize, DataTypes) => { } user.prototype.comparePassword = async function (pwd) { - if (!this.password) return false + if (!this.password) { return false } const ret = await bcrypt.compare(pwd, this.password) return ret } @@ -78,4 +78,4 @@ module.exports = (sequelize, DataTypes) => { }) return user -}; +} diff --git a/server/api/storage.js b/server/api/storage.js index 70f84d9e..3ec61c00 100644 --- a/server/api/storage.js +++ b/server/api/storage.js @@ -13,7 +13,7 @@ try { } const DiskStorage = { - _handleFile(req, file, cb) { + _handleFile (req, file, cb) { const filename = crypto.randomBytes(16).toString('hex') + '.jpg' const finalPath = path.resolve(config.upload_path, filename) const thumbPath = path.resolve(config.upload_path, 'thumb', filename) @@ -36,7 +36,7 @@ const DiskStorage = { }) }) }, - _removeFile(req, file, cb) { + _removeFile (req, file, cb) { delete file.destination delete file.filename delete file.path diff --git a/server/federation/comments.js b/server/federation/comments.js index e7b7816e..203902ad 100644 --- a/server/federation/comments.js +++ b/server/federation/comments.js @@ -5,13 +5,13 @@ const debug = require('debug')('fediverse:comment') module.exports = { async create (req, res) { const body = req.body - //search for related event + // search for related event const inReplyTo = body.object.inReplyTo const match = inReplyTo.match('.*\/federation\/m\/(.*)') console.error('inReplyTo', inReplyTo) console.error('match', match) - if (!match || match.length<2) { - debug("Comment not found %s", inReplyTo) + if (!match || match.length < 2) { + debug('Comment not found %s', inReplyTo) return res.status(404).send('Event not found!') } let event = await Event.findByPk(Number(match[1])) @@ -20,7 +20,7 @@ module.exports = { if (!event) { // in reply to another comment... const comment = await Comment.findOne({ where: { activitypub_id: inReplyTo }, include: [Event] }) - if (!comment) return res.status(404).send('Not found') + if (!comment) { return res.status(404).send('Not found') } event = comment.event } debug('comment from %s to "%s"', req.body.actor, event.title) @@ -35,7 +35,7 @@ module.exports = { }, async remove (req, res) { - const comment = await Comment.findOne({where: { activitypub_id: req.body.object.id }}) + const comment = await Comment.findOne({ where: { activitypub_id: req.body.object.id } }) if (!comment) { debug('Comment %s not found', req.body.object.id) return res.status(404).send('Not found') diff --git a/server/federation/ego.js b/server/federation/ego.js index c951bdcd..431dd5b6 100644 --- a/server/federation/ego.js +++ b/server/federation/ego.js @@ -5,21 +5,21 @@ const debug = require('debug')('fediverse:ego') module.exports = { async boost (req, res) { const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`) - if (!match || match.length<2) return res.status(404).send('Event not found!') + if (!match || match.length < 2) { return res.status(404).send('Event not found!') } debug('boost %s', match[1]) const event = await Event.findByPk(Number(match[1])) - if (!event) return res.status(404).send('Event not found!') - await event.update({ boost: [...event.boost, req.body.actor]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ boost: [...event.boost, req.body.actor] }) res.sendStatus(201) }, async bookmark (req, res) { const match = req.body.object.match(`${config.baseurl}/federation/m/(.*)`) - if (!match || match.length<2) return res.status(404).send('Event not found!') + if (!match || match.length < 2) { return res.status(404).send('Event not found!') } const event = await Event.findByPk(Number(match[1])) debug('%s bookmark %s (%d)', req.body.actor, event.title, event.likes.length) - if (!event) return res.status(404).send('Event not found!') - await event.update({ likes: [...event.likes, req.body.actor]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ likes: [...event.likes, req.body.actor] }) res.sendStatus(201) }, @@ -27,11 +27,11 @@ module.exports = { const body = req.body const object = body.object const match = object.object.match(`${config.baseurl}/federation/m/(.*)`) - if (!match || match.length<2) return res.status(404).send('Event not found!') + if (!match || match.length < 2) { return res.status(404).send('Event not found!') } const event = await Event.findByPk(Number(match[1])) debug('%s unbookmark %s (%d)', body.actor, event.title, event.likes.length) - if (!event) return res.status(404).send('Event not found!') - await event.update({ likes: [...event.likes.filter(actor => actor!==body.actor)]}) + if (!event) { return res.status(404).send('Event not found!') } + await event.update({ likes: [...event.likes.filter(actor => actor !== body.actor)] }) res.sendStatus(201) } -} \ No newline at end of file +} diff --git a/server/federation/follows.js b/server/federation/follows.js index 7f626bbe..f686439e 100644 --- a/server/federation/follows.js +++ b/server/federation/follows.js @@ -8,25 +8,25 @@ module.exports = { // follow request from fediverse async follow (req, res) { const body = req.body - if (typeof body.object !== 'string') return + if (typeof body.object !== 'string') { return } const username = body.object.replace(`${config.baseurl}/federation/u/`, '') - const user = await User.findOne({ where: { username }}) - if (!user) return res.status(404).send('User not found') + const user = await User.findOne({ where: { username } }) + if (!user) { return res.status(404).send('User not found') } // check for duplicate - if (user.followers.indexOf(body.actor) === -1) { + if (!user.followers.includes(body.actor)) { await user.update({ followers: [...user.followers, body.actor] }) debug('%s followed by %s (%d)', username, body.actor, user.followers.length) } else { debug('duplicate %s followed by %s', username, body.actor) } const guid = crypto.randomBytes(16).toString('hex') - let message = { + const message = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': `${config.baseurl}/federation/${guid}`, 'type': 'Accept', 'actor': `${config.baseurl}/federation/u/${user.username}`, - 'object': body, + 'object': body } Helpers.signAndSend(message, user, body.actor) res.sendStatus(200) @@ -36,8 +36,8 @@ module.exports = { async unfollow (req, res) { const body = req.body const username = body.object.object.replace(`${config.baseurl}/federation/u/`, '') - const user = await User.findOne({ where: { username }}) - if (!user) return res.status(404).send('User not found') + const user = await User.findOne({ where: { username } }) + if (!user) { return res.status(404).send('User not found') } if (body.actor !== body.object.actor) { debug('Unfollow an user created by a different actor !?!?') diff --git a/server/federation/helpers.js b/server/federation/helpers.js index a322edce..1693d1a1 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -10,7 +10,7 @@ const url = require('url') const actorCache = [] const Helpers = { - async signAndSend(message, user, to) { + async signAndSend (message, user, to) { // get the URI of the actor object and append 'inbox' to it const toInbox = to + '/inbox' const toOrigin = url.parse(to) @@ -36,19 +36,18 @@ const Helpers = { }, method: 'POST', body: JSON.stringify(message) }) - }, - async sendEvent(event, user) { + async sendEvent (event, user) { // TODO: has to use sharedInbox! // event is sent by user that published it and by the admin instance - const instanceAdmin = await User.findOne({where: { email: config.admin }}) - if(!instanceAdmin || !instanceAdmin.username) { + const instanceAdmin = await User.findOne({ where: { email: config.admin } }) + if (!instanceAdmin || !instanceAdmin.username) { debug('Instance admin not found (there is no user with email => %s)', config.admin) return } - for(let follower of instanceAdmin.followers) { + for (const follower of instanceAdmin.followers) { debug('Notify %s with event %s', follower, event.title) const body = { id: `${config.baseurl}/federation/m/c_${event.id}`, @@ -60,21 +59,23 @@ const Helpers = { body['@context'] = 'https://www.w3.org/ns/activitystreams' Helpers.signAndSend(body, user, follower) } - + // in case the event is published by the Admin itself do not republish if (instanceAdmin.id === user.id) { debug('') return } - if (!user.settings.enable_federation || !user.username) return - for(let follower of user.followers) { + if (!user.settings.enable_federation || !user.username) { return } + for (const follower of user.followers) { debug('Notify %s with event %s', follower, event.title) const body = { - id: `${config.baseurl}/federation/m/c_${event.id}`, + id: `${config.baseurl}/federation/m/${event.id}#create`, type: 'Create', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: [`${config.baseurl}/federation/u/${user.username}/followers`], + published: event.createdAt, actor: `${config.baseurl}/federation/u/${user.username}`, - url: `${config.baseurl}/federation/m/${event.id}`, object: event.toAP(user.username, follower) } body['@context'] = 'https://www.w3.org/ns/activitystreams' @@ -82,18 +83,18 @@ const Helpers = { } }, - async getFederatedUser(address) { + async getFederatedUser (address) { address = address.trim() const [ username, host ] = address.split('@') const url = `https://${host}/.well-known/webfinger?resource=acct:${username}@${host}` return Helpers.getActor(url) }, - + // TODO: cache - async getActor(url, force=false) { + async getActor (url, force = false) { // try with cache first - if (!force && actorCache[url]) return actorCache[url] - const user = await fetch(url, { headers: {'Accept': 'application/jrd+json, application/json'} }) + if (!force && actorCache[url]) { return actorCache[url] } + const user = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } }) .then(res => { if (!res.ok) { debug('[ERR] Actor %s => %s', url, res.statusText) @@ -106,23 +107,23 @@ const Helpers = { }, // ref: https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ - async verifySignature(req, res, next) { + async verifySignature (req, res, next) { let user = await Helpers.getActor(req.body.actor) - if (!user) return res.status(401).send('Actor not found') + if (!user) { return res.status(401).send('Actor not found') } // little hack -> https://github.com/joyent/node-http-signature/pull/83 req.headers.authorization = 'Signature ' + req.headers.signature - // another little hack :/ + // another little hack :/ // https://github.com/joyent/node-http-signature/issues/87 req.url = '/federation' + req.url const parsed = httpSignature.parseRequest(req) - if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next() - + if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() } + // signature not valid, try without cache user = await Helpers.getActor(req.body.actor, true) - if (!user) return res.status(401).send('Actor not found') - if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) return next() + if (!user) { return res.status(401).send('Actor not found') } + if (httpSignature.verifySignature(parsed, user.publicKey.publicKeyPem)) { return next() } // still not valid debug('Invalid signature from user %s', req.body.actor) diff --git a/server/federation/index.js b/server/federation/index.js index 309ead2a..e2c4c3a5 100644 --- a/server/federation/index.js +++ b/server/federation/index.js @@ -8,31 +8,30 @@ const { event: Event, user: User, tag: Tag, place: Place } = require('../api/mod const Comments = require('./comments') const Helpers = require('./helpers') const Ego = require('./ego') +const debug = require('debug')('federation') /** * Federation is calling! * ref: https://www.w3.org/TR/activitypub/#Overview */ router.use(cors()) -router.use(express.json({type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"']})) - +router.use(express.json({ type: ['application/json', 'application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'] })) router.get('/m/:event_id', async (req, res) => { const event_id = req.params.event_id // if (req.accepts('html')) return res.redirect(301, `/event/${event_id}`) const event = await Event.findByPk(req.params.event_id, { include: [ User, Tag, Place ] }) - if (!event) return res.status(404).send('Not found') + if (!event) { return res.status(404).send('Not found') } return res.json(event.toAP(event.user.username)) }) // get any message coming from federation // Federation is calling! router.post('/u/:name/inbox', Helpers.verifySignature, async (req, res) => { - const b = req.body - - switch(b.type) { + debug(b.type) + switch (b.type) { case 'Follow': Follows.follow(req, res) break diff --git a/server/federation/nodeinfo.js b/server/federation/nodeinfo.js index c5878266..445eb78c 100644 --- a/server/federation/nodeinfo.js +++ b/server/federation/nodeinfo.js @@ -18,7 +18,7 @@ router.get('/', async (req, res) => { }, protocols: ['activitypub'], openRegistrations: settingsController.settings.allow_registration, - usage:{ + usage: { users: { total: 10 } diff --git a/server/federation/users.js b/server/federation/users.js index 80db30b9..8fb2abab 100644 --- a/server/federation/users.js +++ b/server/federation/users.js @@ -21,6 +21,16 @@ module.exports = { inbox: `${config.baseurl}/federation/u/${name}/inbox`, outbox: `${config.baseurl}/federation/u/${name}/outbox`, followers: `${config.baseurl}/federation/u/${name}/followers`, + attachment: [{ + type: 'PropertyValue', + name: 'Website', + value: `${config.baseurl}` + }], + icon: { + type: 'Image', + mediaType: 'image/jpeg', + url: config.baseurl + '/favicon.ico' + }, publicKey: { id: `${config.baseurl}/federation/u/${name}#main-key`, owner: `${config.baseurl}/federation/u/${name}`, @@ -32,62 +42,79 @@ module.exports = { }, async followers (req, res) { const name = req.params.name + const page = req.query.page + debug('Retrieve %s followers', name) if (!name) return res.status(400).send('Bad request.') const user = await User.findOne({where: { username: name }}) if (!user) return res.status(404).send(`No record found for ${name}`) - const ret = { - '@context': [ 'https://www.w3.org/ns/activitystreams' ], - id: `${config.baseurl}/federation/u/${name}/followers`, - type: 'OrderedCollection', - totalItems: user.followers.length, - first: { - id: `${config.baseurl}/federation/u/${name}/followers?page=1`, - type: 'OrderedCollectionPage', + + res.type('application/activity+json; charset=utf-8') + + if (!page) { + debug('No pagination') + return res.json({ + '@context': 'https://www.w3.org/ns/activitystreams', + id: `${config.baseurl}/federation/u/${name}/followers`, + type: 'OrderedCollection', totalItems: user.followers.length, - partOf: `${config.baseurl}/federation/u/${name}/followers`, - orderedItems: user.followers, - } + first: `${config.baseurl}/federation/u/${name}/followers?page=true`, + last: `${config.baseurl}/federation/u/${name}/followers?page=true`, + }) } - res.json(ret) + return res.json({ + '@context': 'https://www.w3.org/ns/activitystreams', + id: `${config.baseurl}/federation/u/${name}/followers?page=${page}`, + type: 'OrderedCollectionPage', + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/followers` , + orderedItems: user.followers + }) }, + async outbox (req, res) { const name = req.params.name const page = req.query.page - + if (!name) return res.status(400).send('Bad request.') const user = await User.findOne({ include: [ { model: Event, include: [ Place, Tag ] } ], where: { username: name } }) - if (!user) return res.status(404).send(`No record found for ${name}`) + if (!user) return res.status(404).send(`No record found for ${name}`) + debug('Inside outbox, should return all events from this user') + // https://www.w3.org/TR/activitypub/#outbox + res.type('application/activity+json; charset=utf-8') if (!page) { - const ret = { + debug('Without pagination ') + return res.json({ '@context': 'https://www.w3.org/ns/activitystreams', id: `${config.baseurl}/federation/u/${name}/outbox`, type: 'OrderedCollection', totalItems: user.events.length, - first: { - id: `${config.baseurl}/federation/u/${name}/outbox?page=true`, - type: 'OrderedCollectionPage', - totalItem: user.events.length, - partOf: `${config.baseurl}/federation/u/${name}/outbox`, - orderedItems: user.events.map(e => e.toAP(user.username)) - } - } - res.type('application/activity+json; charset=utf-8') - return res.json(ret) + first: `${config.baseurl}/federation/u/${name}/outbox?page=true`, + last: `${config.baseurl}/federation/u/${name}/outbox?page=true` + }) } - const ret = { + + debug('With pagination %s', page) + return res.json({ '@context': 'https://www.w3.org/ns/activitystreams', - id: `${config.baseurl}/federation/u/${name}/outbox?page=true`, + id: `${config.baseurl}/federation/u/${name}/outbox?page=${page}`, type: 'OrderedCollectionPage', - partOf: `${config.baseurl}/federation/u/${name}/outbox`, - orderedItems: user.events.map(e => e.toAP(user.username)) - } - res.type('application/activity+json; charset=utf-8') - res.json(ret) + totalItems: user.followers.length, + partOf: `${config.baseurl}/federation/u/${name}/outbox` , + orderedItems: user.events.map(e => ({ + id: `${config.baseurl}/federation/m/${e.id}#create`, + type: 'Create', + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: [`${config.baseurl}/federation/u/${user.username}/followers`], + published: e.createdAt, + actor: `${config.baseurl}/federation/u/${user.username}`, + object: e.toAP(user.username) + })) + }) } } diff --git a/server/federation/webfinger.js b/server/federation/webfinger.js index 4eac724e..015b3559 100644 --- a/server/federation/webfinger.js +++ b/server/federation/webfinger.js @@ -6,18 +6,30 @@ const settingsController = require('../api/controller/settings') const config = require('config') const version = require('../../package.json').version const url = require('url') +const debug = require('debug')('webfinger') router.use(cors()) router.get('/webfinger', async (req, res) => { const resource = req.query.resource if (!resource || !resource.includes('acct:')) { + debug('Bad webfinger request => %s', resource.query) return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.') } - const name = resource.match(/acct:(.*)@/)[1] const domain = url.parse(config.baseurl).host - const user = await User.findOne({where: { username: name } }) - if (!user) return res.status(404).send(`No record found for ${name}`) + const [, name, req_domain] = resource.match(/acct:(.*)@(.*)/) + + if (domain !== req_domain) { + debug('Bad webfinger request, requested domain "%s" instead of "%s"', req_domain, domain) + return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.') + } + + const user = await User.findOne({ where: { username: name } }) + if (!user) { + debug('User not found: %s', name) + return res.status(404).send(`No record found for ${name}`) + } + const ret = { subject: `acct:${name}@${domain}`, links: [ @@ -38,24 +50,24 @@ router.get('/nodeinfo/:nodeinfo_version', async (req, res) => { nodeDescription: 'Gancio instance', nodeName: config.title }, - openRegistrations : settingsController.settings.allow_registration, - protocols :['activitypub'], - services: { inbound: [], outbound :["atom1.0"]}, + openRegistrations: settingsController.settings.allow_registration, + protocols: ['activitypub'], + services: { inbound: [], outbound: ['atom1.0'] }, software: { name: 'gancio', version }, - usage: { + usage: { localComments: 0, - localPosts:0, + localPosts: 0, users: { - total:3 + total: 3 } }, version: req.params.nodeinfo_version } - if(req.params.nodeinfo_version === '2.1') { + if (req.params.nodeinfo_version === '2.1') { ret.software.repository = 'https://git.lattuga.net/cisti/gancio' } res.json(ret) @@ -72,7 +84,7 @@ router.get('/x-nodeinfo2', async (req, res) => { }, protocols: ['activitypub'], openRegistrations: settingsController.settings.allow_registration, - usage:{ + usage: { users: { total: 10 } @@ -83,18 +95,16 @@ router.get('/x-nodeinfo2', async (req, res) => { res.json(ret) }) - router.get('/nodeinfo', async (req, res) => { const ret = { links: [ { href: `${config.baseurl}/.well-known/nodeinfo/2.0`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.0` }, - { href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` }, + { href: `${config.baseurl}/.well-known/nodeinfo/2.1`, rel: `http://nodeinfo.diaspora.software/ns/schema/2.1` } ] } res.json(ret) }) - router.use('/host-meta', (req, res) => { res.type('application/xml') res.send(` @@ -104,12 +114,14 @@ router.use('/host-meta', (req, res) => { }) // Handle 404 -router.use(function(req, res) { +router.use((req, res) => { + debug('404 Page not found: %s', req.path) res.status(404).send('404: Page not Found') }) // Handle 500 -router.use(function(error, req, res, next) { +router.use((error, req, res, next) => { + debug(error) res.status(500).send('500: Internal Server Error') }) diff --git a/server/firstrun.js b/server/firstrun.js index b8d6fa09..4f9b1a28 100644 --- a/server/firstrun.js +++ b/server/firstrun.js @@ -28,7 +28,7 @@ module.exports = { await db.user.findAll() consola.warn(`⚠️ Non empty db! Please move your current db elsewhere than retry.`) return false - } catch(e) { } + } catch (e) { } consola.info(`Create tables schema`) await db.sequelize.sync().catch(e => { diff --git a/server/index.js b/server/index.js index 3ad41b7a..b6136aa6 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,7 @@ const { Nuxt, Builder } = require('nuxt') const nuxt_config = require('../nuxt.config.js') const config = require('config') -async function main() { +async function main () { nuxt_config.server = config.server // Init Nuxt.js @@ -20,7 +20,7 @@ async function main() { nuxt.listen() // close connections/port/unix socket - function shutdown() { + function shutdown () { nuxt.close(async () => { const db = require('./api/models') await db.sequelize.close() diff --git a/server/migrations/20190728213923-add_username.js b/server/migrations/20190728213923-add_username.js index 8c1950a8..4376e432 100644 --- a/server/migrations/20190728213923-add_username.js +++ b/server/migrations/20190728213923-add_username.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -26,4 +26,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190728214848-add_displayname.js b/server/migrations/20190728214848-add_displayname.js index 073d611e..2bdd4d79 100644 --- a/server/migrations/20190728214848-add_displayname.js +++ b/server/migrations/20190728214848-add_displayname.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190729103119-add_rsa.js b/server/migrations/20190729103119-add_rsa.js index f01a81ca..858fd928 100644 --- a/server/migrations/20190729103119-add_rsa.js +++ b/server/migrations/20190729103119-add_rsa.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190729192753-add_followers.js b/server/migrations/20190729192753-add_followers.js index 9621bd26..d7818968 100644 --- a/server/migrations/20190729192753-add_followers.js +++ b/server/migrations/20190729192753-add_followers.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -23,4 +23,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190801105908-likes.js b/server/migrations/20190801105908-likes.js index 8804d9c4..6b9640ef 100644 --- a/server/migrations/20190801105908-likes.js +++ b/server/migrations/20190801105908-likes.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -24,4 +24,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190801110053-boost.js b/server/migrations/20190801110053-boost.js index 45d74fab..9b372ecb 100644 --- a/server/migrations/20190801110053-boost.js +++ b/server/migrations/20190801110053-boost.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { @@ -25,4 +25,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/migrations/20190910085948-user_settings.js b/server/migrations/20190910085948-user_settings.js index 0f6f6b52..10c45991 100644 --- a/server/migrations/20190910085948-user_settings.js +++ b/server/migrations/20190910085948-user_settings.js @@ -1,11 +1,11 @@ -'use strict'; +'use strict' module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.addColumn('users', 'settings', { type: Sequelize.JSON, defaultValue: {} - }) + }) /* Add altering commands here. Return a promise to correctly handle asynchronicity. @@ -24,4 +24,4 @@ module.exports = { return queryInterface.dropTable('users'); */ } -}; +} diff --git a/server/notifier.js b/server/notifier.js index 9cb8b554..ae664515 100644 --- a/server/notifier.js +++ b/server/notifier.js @@ -5,18 +5,18 @@ const config = require('config') const eventController = require('./api/controller/event') const get = require('lodash/get') -const { event: Event, notification: Notification, event_notification: EventNotification, +const { event: Event, notification: Notification, event_notification: EventNotification, user: User, place: Place, tag: Tag } = require('./api/models') const notifier = { - async sendNotification(notification, event) { + async sendNotification (notification, event) { return const promises = [] switch (notification.type) { case 'mail': - return mail.send(notification.email, 'event', { event, config, notification }) - case 'admin_email': - return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) + return mail.send(notification.email, 'event', { event, config, notification }) + case 'admin_email': + return mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) // case 'mastodon': // // instance publish // if (bot.bot) { @@ -31,7 +31,7 @@ const notifier = { } return Promise.all(promises) }, - async notifyEvent(eventId) { + async notifyEvent (eventId) { const event = await Event.findByPk(eventId, { include: [ Tag, Place, User ] }) @@ -42,7 +42,7 @@ const notifier = { const eventNotifications = await EventNotification.findAll({ where: { - notificationId: notifications.map(n=>n.id), + notificationId: notifications.map(n => n.id), status: 'new' } }) @@ -57,15 +57,15 @@ const notifier = { } return e.save() }) - + return Promise.all(promises) }, - async notify() { + async notify () { // get all event notification in queue const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } }) const promises = eventNotifications.map(async e => { const event = await Event.findByPk(e.eventId, { include: [User, Place, Tag] }) - if (!event.place) return + if (!event.place) { return } const notification = await Notification.findByPk(e.notificationId) try { await sendNotification(notification, event, e) @@ -82,4 +82,4 @@ const notifier = { } } -module.exports = notifier \ No newline at end of file +module.exports = notifier diff --git a/server/routes.js b/server/routes.js index 9ee6e85b..19c2e4f7 100644 --- a/server/routes.js +++ b/server/routes.js @@ -7,6 +7,11 @@ const webfinger = require('./federation/webfinger') const debug = require('debug')('routes') const router = express.Router() +router.use((req, res, next) => { + debug(req.path) + next() +}) + router.use('/favicon.ico', express.static(path.resolve(config.favicon || 'assets/favicon.ico'))) router.use('/media/', express.static(config.upload_path)) router.use('/api', api) @@ -27,6 +32,4 @@ router.use((error, req, res, next) => { res.status(500).send('500: Internal Server Error') }) - - module.exports = router diff --git a/store/index.js b/store/index.js index bcbab503..8c5ce484 100644 --- a/store/index.js +++ b/store/index.js @@ -12,7 +12,7 @@ export const state = () => ({ allow_anon_event: true, allow_recurrent_event: true, recurrent_event_visible: false, - enable_federation: false, + enable_federation: false }, filters: { tags: [], @@ -26,37 +26,34 @@ export const state = () => ({ export const getters = { // filter matches search tag/place - filteredEvents: state => { - + filteredEvents: state => { const search_for_tags = !!state.filters.tags.length const search_for_places = !!state.filters.places.length return state.events.filter(e => { - // filter past events - if (!state.filters.show_past_events && e.past) return false + if (!state.filters.show_past_events && e.past) { return false } // filter recurrent events - if (!state.filters.show_recurrent_events && e.recurrent) return false + if (!state.filters.show_recurrent_events && e.recurrent) { return false } if (search_for_places) { - if (find(state.filters.places, p => p === e.place.id)) return true + if (find(state.filters.places, p => p === e.place.id)) { return true } } if (search_for_tags) { - const common_tags = intersection(e.tags, state.filters.tags); - if (common_tags.length > 0) return true + const common_tags = intersection(e.tags, state.filters.tags) + if (common_tags.length > 0) { return true } } - if (!search_for_places && !search_for_tags) return true - + if (!search_for_places && !search_for_tags) { return true } + return false }) }, // filter matches search tag/place including past events - filteredEventsWithPast: state => { - + filteredEventsWithPast: state => { const search_for_tags = !!state.filters.tags.length const search_for_places = !!state.filters.places.length @@ -64,75 +61,75 @@ export const getters = { const match = false // filter recurrent events - if (!state.filters.show_recurrent_events && e.recurrent) return false + if (!state.filters.show_recurrent_events && e.recurrent) { return false } if (!match && search_for_places) { - if (find(state.filters.places, p => p === e.place.id)) return true + if (find(state.filters.places, p => p === e.place.id)) { return true } } if (search_for_tags) { - const common_tags = intersection(e.tags, state.filters.tags); - if (common_tags.length > 0) return true + const common_tags = intersection(e.tags, state.filters.tags) + if (common_tags.length > 0) { return true } } - if (!search_for_places && !search_for_tags) return true - + if (!search_for_places && !search_for_tags) { return true } + return false }) } } export const mutations = { - setEvents(state, events) { + setEvents (state, events) { // set`past` and `newDay` flags to event let lastDay = null state.events = events.map(e => { const currentDay = moment.unix(e.start_datetime).date() e.newDay = (!lastDay || lastDay !== currentDay) && currentDay lastDay = currentDay - const end_datetime = e.end_datetime || e.start_datetime+3600*2 + const end_datetime = e.end_datetime || e.start_datetime + 3600 * 2 const past = ((moment().unix()) - end_datetime) > 0 e.past = !!past return e }) }, - addEvent(state, event) { + addEvent (state, event) { state.events.push(event) }, - updateEvent(state, event) { + updateEvent (state, event) { state.events = state.events.map((e) => { - if (e.id !== event.id) return e + if (e.id !== event.id) { return e } return event }) }, - delEvent(state, eventId) { + delEvent (state, eventId) { state.events = state.events.filter(ev => { return ev.id !== eventId }) }, - update(state, { tags, places }) { + update (state, { tags, places }) { state.tags = tags state.places = places }, - setSearchTags(state, tags) { + setSearchTags (state, tags) { state.filters.tags = tags }, - setSearchPlaces(state, places) { + setSearchPlaces (state, places) { state.filters.places = places }, - showPastEvents(state, show) { + showPastEvents (state, show) { state.filters.show_past_events = show }, - showRecurrentEvents(state, show) { + showRecurrentEvents (state, show) { state.filters.show_recurrent_events = show }, - setSettings(state, settings) { + setSettings (state, settings) { state.settings = settings }, - setSetting(state, setting) { + setSetting (state, setting) { state.settings[setting.key] = setting.value }, - setLocale(state, locale) { + setLocale (state, locale) { state.locale = locale } } @@ -140,51 +137,50 @@ export const mutations = { export const actions = { // this method is called server side only for each request // we use it to get configuration from db, setting locale, etc... - async nuxtServerInit ({ commit }, { app, req } ) { + async nuxtServerInit ({ commit }, { app, req }) { const settings = await app.$axios.$get('/settings') commit('setSettings', settings) // apply settings commit('showRecurrentEvents', settings.allow_recurrent_event && settings.recurrent_event_visible) - }, - async updateEvents({ commit }, page) { + async updateEvents ({ commit }, page) { const events = await this.$axios.$get(`/event/${page.month - 1}/${page.year}`) commit('setEvents', events) }, - async updateMeta({ commit }) { + async updateMeta ({ commit }) { const { tags, places } = await this.$axios.$get('/event/meta') commit('update', { tags, places }) }, - async addEvent({ commit }, formData) { + async addEvent ({ commit }, formData) { const event = await this.$axios.$post('/user/event', formData) if (event.user) { commit('addEvent', event) } }, - async updateEvent({ commit }, formData) { + async updateEvent ({ commit }, formData) { const event = await this.$axios.$put('/user/event', formData) if (event.user) { commit('updateEvent', event) } }, - delEvent({ commit }, eventId) { + delEvent ({ commit }, eventId) { commit('delEvent', eventId) }, - setSearchTags({ commit }, tags) { + setSearchTags ({ commit }, tags) { commit('setSearchTags', tags) }, - setSearchPlaces({ commit }, places) { + setSearchPlaces ({ commit }, places) { commit('setSearchPlaces', places) }, - showPastEvents({ commit }, show) { + showPastEvents ({ commit }, show) { commit('showPastEvents', show) }, - showRecurrentEvents({ commit }, show ) { + showRecurrentEvents ({ commit }, show) { commit('showRecurrentEvents', show) }, - async setSetting({ commit }, setting) { - await this.$axios.$post('/settings', setting ) + async setSetting ({ commit }, setting) { + await this.$axios.$post('/settings', setting) commit('setSetting', setting) - }, + } } diff --git a/yarn.lock b/yarn.lock index 7fea9151..c7717c48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4162,6 +4162,11 @@ express-jwt@^5.3.1: jsonwebtoken "^8.1.0" lodash.set "^4.0.0" +express-middleware-log@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/express-middleware-log/-/express-middleware-log-1.2.0.tgz#62682021ba3b1cbfd6b081e7364ebb1fd6d5a0fb" + integrity sha512-1G9cHlGJs4+nFphSqVduJfCzeaqHeOdpTRBAjceRRcLWeHzj9sXDYP99tNjaeHsHn3N3vlNI+vIn/lb9eYXmuw== + express-unless@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-0.3.1.tgz#2557c146e75beb903e2d247f9b5ba01452696e20" From 8607d6f582100d86aec46d874c7073f040ecdbee Mon Sep 17 00:00:00 2001 From: les Date: Wed, 11 Sep 2019 21:20:44 +0200 Subject: [PATCH 7/7] debugging fedi & upgrade --- package.json | 9 +- server/api/models/event.js | 6 +- server/federation/follows.js | 2 +- server/federation/helpers.js | 56 ++++++----- server/federation/users.js | 2 +- yarn.lock | 188 ++++++++++++++++------------------- 6 files changed, 124 insertions(+), 139 deletions(-) diff --git a/package.json b/package.json index b9a0d5af..6e8df4c2 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ".nuxt/" ], "dependencies": { - "@nuxtjs/auth": "^4.8.2", + "@nuxtjs/auth": "^4.8.3", "@nuxtjs/axios": "^5.6.0", "accept-language": "^3.0.18", "axios": "^0.19.0", @@ -51,9 +51,10 @@ "cross-env": "^5.2.1", "dayjs": "^1.8.16", "element-ui": "^2.12.0", - "email-templates": "^6.0.0", + "email-templates": "^6.0.2", "express": "^4.17.1", "express-jwt": "^5.3.1", + "express-middleware-log": "^1.2.0", "http-signature": "^1.2.0", "ics": "^2.16.0", "inquirer": "^7.0.0", @@ -68,7 +69,7 @@ "nuxt-express-module": "^0.0.11", "pg": "^7.12.1", "sass-loader": "^8.0.0", - "sequelize": "^5.18.1", + "sequelize": "^5.18.4", "sequelize-cli": "^5.5.1", "sharp": "^0.23.0", "sqlite3": "^4.1.0", @@ -83,7 +84,7 @@ "@nuxtjs/eslint-config": "^1.1.2", "babel-eslint": "^10.0.3", "eslint": "^6.3.0", - "eslint-config-prettier": "^6.2.0", + "eslint-config-prettier": "^6.3.0", "eslint-config-standard": ">=14.1.0", "eslint-loader": "^3.0.0", "eslint-plugin-import": ">=2.17.3", diff --git a/server/api/models/event.js b/server/api/models/event.js index be2c085e..29944938 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -37,13 +37,13 @@ module.exports = (sequelize, DataTypes) => { } // - event.prototype.toAP = function (username = config.admin, follower) { + event.prototype.toAP = function (username, follower = []) { const tags = this.tags && this.tags.map(t => '#' + t.tag).join(' ') - const content = `${this.title}
+ 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}
` + ${tags}
` const attachment = [] if (this.image_path) { diff --git a/server/federation/follows.js b/server/federation/follows.js index f686439e..4fe8facf 100644 --- a/server/federation/follows.js +++ b/server/federation/follows.js @@ -2,7 +2,7 @@ const config = require('config') const Helpers = require('./helpers') const { user: User } = require('../api/models') const crypto = require('crypto') -const debug = require('debug')('fedivers:follows') +const debug = require('debug')('federation:follows') module.exports = { // follow request from fediverse diff --git a/server/federation/helpers.js b/server/federation/helpers.js index 1693d1a1..c780c6d0 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -3,7 +3,7 @@ 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') @@ -26,7 +26,7 @@ const Helpers = { 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}"` - return await fetch(toInbox, { + const ret = await fetch(toInbox, { headers: { 'Host': toOrigin.hostname, 'Date': d.toUTCString(), @@ -36,6 +36,7 @@ const Helpers = { }, method: 'POST', body: JSON.stringify(message) }) + debug('sign %s => %s', ret.status, await ret.text()) }, async sendEvent (event, user) { @@ -48,35 +49,40 @@ const Helpers = { } for (const follower of instanceAdmin.followers) { - debug('Notify %s with event %s', follower, event.title) - const body = { - id: `${config.baseurl}/federation/m/c_${event.id}`, - type: 'Create', - actor: `${config.baseurl}/federation/u/${instanceAdmin.username}`, - url: `${config.baseurl}/federation/m/${event.id}`, - object: event.toAP(instanceAdmin.username, follower) - } - body['@context'] = 'https://www.w3.org/ns/activitystreams' - Helpers.signAndSend(body, user, follower) - } - - // in case the event is published by the Admin itself do not republish - if (instanceAdmin.id === user.id) { - debug('') - return - } - - if (!user.settings.enable_federation || !user.username) { return } - for (const follower of user.followers) { - debug('Notify %s with event %s', follower, event.title) + 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/${user.username}/followers`], + 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, follower) + 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) diff --git a/server/federation/users.js b/server/federation/users.js index 8fb2abab..4e9a1df4 100644 --- a/server/federation/users.js +++ b/server/federation/users.js @@ -28,7 +28,7 @@ module.exports = { }], icon: { type: 'Image', - mediaType: 'image/jpeg', + mediaType: 'image/x-icon', url: config.baseurl + '/favicon.ico' }, publicKey: { diff --git a/yarn.lock b/yarn.lock index c7717c48..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" @@ -4532,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" @@ -4623,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" @@ -4942,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" @@ -5710,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" @@ -6568,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== @@ -6650,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" @@ -6814,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== @@ -8507,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" @@ -9103,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" @@ -9420,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== @@ -9868,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" @@ -10097,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" @@ -10274,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=