diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 4c143369..1f803ee5 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -2,17 +2,16 @@ const crypto = require('crypto') const moment = require('moment') const { Op } = require('sequelize') const lodash = require('lodash') -const { event: Event, comment: Comment, tag: Tag, place: Place, user: User, notification: Notification } = require('../models') +const { event: Event, comment: Comment, tag: Tag, place: Place, + user: User, notification: Notification, event_notification: EventNotification } = require('../models') const Sequelize = require('sequelize') -const notifier = require('../../notifier') -const federation = require('../../federation/helpers') const debug = require('debug')('controller:event') const eventController = { - // NOT USED ANYWHERE, comments are added from fediverse + // NOT USED ANYWHERE, comments are added from fediverse, should we remove this? async addComment (req, res) { - // comment could be added to an event or to another comment + // comments 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) { const comment = await Comment.findOne({ where: { activitypub_id: { [Op.eq]: req.body.id } }, include: Event }) @@ -44,7 +43,8 @@ const eventController = { res.json({ tags, places }) }, - async getNotifications (event) { + async getNotifications (event, action) { + debug('getNotifications "%s" (%s)', event.title, action) function match (event, filters) { // matches if no filter specified if (!filters) { return true } @@ -64,10 +64,12 @@ const eventController = { } } } - const notifications = await Notification.findAll() + + const notifications = await Notification.findAll({ where: { action }, include: [ Event ] }) // get notification that matches with selected event - return notifications.filter(notification => match(event, notification.filters)) + const ret = notifications.filter(notification => match(event, notification.filters)) + return ret }, async updateTag (req, res) { @@ -123,8 +125,8 @@ const eventController = { res.sendStatus(200) // send notification - // notifier.notifyEvent(event.id) - // federation.sendEvent(event, req.user) + const notifier = require('../../notifier') + notifier.notifyEvent('Create', event.id) } catch (e) { res.sendStatus(404) } diff --git a/server/api/controller/export.js b/server/api/controller/export.js index d7ad6f55..ad7753c6 100644 --- a/server/api/controller/export.js +++ b/server/api/controller/export.js @@ -32,7 +32,7 @@ const exportController = { }, include: [ { model: Tag, ...where_tags }, { model: Place, attributes: ['name', 'id', 'address'] }] }) - + switch (type) { case 'rss': case 'feed': diff --git a/server/api/controller/user.js b/server/api/controller/user.js index c876f8e7..edcb25de 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -8,9 +8,6 @@ const config = require('config') const mail = require('../mail') const { user: User, event: Event, tag: Tag, place: Place, fed_users: FedUsers } = require('../models') const settingsController = require('./settings') -const federation = require('../../federation/helpers') -const util = require('util') -const generateKeyPair = util.promisify(crypto.generateKeyPair) const debug = require('debug')('user:controller') const userController = { @@ -56,12 +53,14 @@ const userController = { try { console.error('media files not removed') // TOFIX - // await fs.unlink(old_path) - // await fs.unlink(old_thumb_path) + await fs.unlink(old_thumb_path) + await fs.unlink(old_path) } catch (e) { - console.error(e) + debug(e) } } + const notifier = require('../../notifier') + await notifier.notifyEvent('Delete', event.id) await event.destroy() res.sendStatus(200) } else { @@ -75,12 +74,11 @@ const userController = { const eventDetails = { title: body.title, - // remove html tag + // remove html tags description: body.description ? body.description.replace(/(<([^>]+)>)/ig, '') : '', multidate: body.multidate, start_datetime: body.start_datetime, end_datetime: body.end_datetime, - recurrent: body.recurrent, // publish this event only if authenticated is_visible: !!req.user @@ -101,8 +99,9 @@ const userController = { await event.setPlace(place) event.place = place } catch (e) { - console.error(e) + debug(e) } + // create/assign tags if (body.tags) { await Tag.bulkCreate(body.tags.map(t => ({ tag: t })), { ignoreDuplicates: true }) @@ -112,21 +111,18 @@ const userController = { event.tags = tags } + // associate user to event and reverse if (req.user) { await req.user.addEvent(event) await event.setUser(req.user) } - // send response to client + // return created event to the client res.json(event) - const user = await User.findByPk(req.user.id, { include: { model: FedUsers, as: 'followers' }}) - if (user) { federation.sendEvent(event, user) } - - // res.sendStatus(200) - - // send notification (mastodon/email/confirmation) - // notifier.notifyEvent(event.id) + // send notification (mastodon/email) + const notifier = require('../../notifier') + notifier.notifyEvent('Create', event.id) }, async updateEvent (req, res) { @@ -167,6 +163,8 @@ const userController = { } const newEvent = await Event.findByPk(event.id, { include: [Tag, Place] }) res.json(newEvent) + const notifier = require('../../notifier') + notifier.notifyEvent('Update', event.id) }, async forgotPassword (req, res) { diff --git a/server/api/mail.js b/server/api/mail.js index e5bf5056..e9eacf52 100644 --- a/server/api/mail.js +++ b/server/api/mail.js @@ -22,7 +22,7 @@ const mail = { } }, message: { - from: `${config.title} <${config.admin_email}>` + from: `📅 ${config.title} <${config.admin_email}>` }, send: true, i18n: { @@ -32,7 +32,7 @@ const mail = { updateFiles: false, defaultLocale: settings.locale, locale: settings.locale, - locales: ['it', 'es'] + locales: ['it', 'es'] // TOFIX }, transport: config.smtp }) @@ -44,9 +44,9 @@ const mail = { }, locals: { ...locals, - locale: 'it', + locale: 'it', // TOFIX config: { title: config.title, baseurl: config.baseurl, description: config.description }, - datetime: datetime => moment(datetime).format('ddd, D MMMM HH:mm') + datetime: datetime => moment.unix(datetime).utc(false).format('ddd, D MMMM HH:mm') } } return email.send(msg) diff --git a/server/api/models/eventnotification.js b/server/api/models/eventnotification.js index 5e1cff64..fd99c944 100644 --- a/server/api/models/eventnotification.js +++ b/server/api/models/eventnotification.js @@ -10,5 +10,5 @@ module.exports = (sequelize, DataTypes) => { } }, {}) - return event_notification + return event_notification } diff --git a/server/api/models/notification.js b/server/api/models/notification.js index abb1c6ea..b016c934 100644 --- a/server/api/models/notification.js +++ b/server/api/models/notification.js @@ -4,13 +4,23 @@ module.exports = (sequelize, DataTypes) => { filters: DataTypes.JSON, email: DataTypes.STRING, remove_code: DataTypes.STRING, + action: { + type: DataTypes.ENUM, + values: ['Create', 'Update', 'Delete'] + }, type: { type: DataTypes.ENUM, - values: ['mail', 'admin_email', 'mastodon'] + values: ['mail', 'admin_email', 'ap'] } - }, {}) + }, { + indexes: [{ + unique: true, + fields: ['action', 'type'] + }] + }) + notification.associate = function (models) { - notification.belongsToMany(models.event, { through: models.event_notification }) + notification.belongsToMany(models.event, { through: 'event_notification' }) // associations can be defined here } return notification diff --git a/server/federation/helpers.js b/server/federation/helpers.js index 08697a63..2f434f64 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -52,7 +52,7 @@ const Helpers = { debug('sign %s => %s', ret.status, await ret.text()) }, - async sendEvent (event, user) { + async sendEvent (event, user, type='Create') { if (!settingsController.settings.enable_federation) { debug('event not send, federation disabled') return @@ -77,7 +77,7 @@ const Helpers = { debug('Notify %s with event %s (from admin %s) cc => %d', sharedInbox, event.title, instanceAdmin.username, recipients[sharedInbox].length) const body = { id: `${config.baseurl}/federation/m/${event.id}#create`, - type: 'Create', + type, to: ['https://www.w3.org/ns/activitystreams#Public'], cc: [`${config.baseurl}/federation/u/${instanceAdmin.username}/followers`, ...recipients[sharedInbox]], //cc: recipients[sharedInbox], diff --git a/server/migrations/20190927160148-new_notification.js b/server/migrations/20190927160148-new_notification.js new file mode 100644 index 00000000..1057f7dd --- /dev/null +++ b/server/migrations/20190927160148-new_notification.js @@ -0,0 +1,56 @@ +'use strict'; +const { notification: Notification } = require('../api/models') + +module.exports = { + up: async (queryInterface, Sequelize) => { + + // remove all notifications + await Notification.destroy({where: []}) + + // add `action` field to notification + try { + await queryInterface.addColumn('notifications', 'action', { + type: Sequelize.ENUM, + values: ['Create', 'Update', 'Delete'] + }) + } catch {} + + // modify values of `type` field + try { + await queryInterface.removeColumn('notifications', 'type') + } catch {} + + try { + await queryInterface.addColumn('notifications', 'type', { + type: Sequelize.ENUM, + values: ['mail', 'admin_email', 'ap'] + }) + } catch {} + + await queryInterface.addIndex('notifications', { + unique: true, + fields: ['action', 'type' ] + }) + + // add AP notifications + await Notification.create({ action: 'Create', type: 'ap', filters: { is_visible: true } }) + await Notification.create({ action: 'Update', type: 'ap', filters: { is_visible: true } }) + await Notification.create({ action: 'Delete', type: 'ap', filters: { is_visible: true } }) + + // send anon events via email to admin + await Notification.create({ action: 'Create', type: 'admin_email', filters: { is_visible: false } }) + + return true + }, + + down: (queryInterface, Sequelize) => { + return Promise.resolve() + /* + 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 ae664515..bc6af4b9 100644 --- a/server/notifier.js +++ b/server/notifier.js @@ -1,63 +1,54 @@ const mail = require('./api/mail') // const bot = require('./api/controller/fediverse') -const settingsController = require('./api/controller/settings') const config = require('config') -const eventController = require('./api/controller/event') -const get = require('lodash/get') +const debug = require('debug')('notifier') +const fediverseHelpers = require('./federation/helpers') const { event: Event, notification: Notification, event_notification: EventNotification, - user: User, place: Place, tag: Tag } = require('./api/models') + user: User, place: Place, tag: Tag, fed_users: FedUsers } = require('./api/models') +const eventController = require('./api/controller/event') const notifier = { async sendNotification (notification, event) { - return const promises = [] + debug('Send %s notification %s', notification.type, notification.action) + let p 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 }) - // case 'mastodon': - // // instance publish - // if (bot.bot) { - // const b = bot.post(event).then(b => { - // event.activitypub_id = String(b.data.id) - // return event.save() - // }).catch(e => { - // console.error("ERROR !! ", e) - // }) - // promises.push(b) - // } + p = mail.send([config.smtp.auth.user, config.admin_email], 'event', { event, to_confirm: !event.is_visible, config, notification }) + promises.push(p) + case 'ap': + p = fediverseHelpers.sendEvent(event, event.user, notification.action) + promises.push(p) } return Promise.all(promises) }, - async notifyEvent (eventId) { - const event = await Event.findByPk(eventId, { - include: [ Tag, Place, User ] + async notifyEvent (action, eventId) { + let event = await Event.findByPk(eventId, { + + include: [ Tag, Place, Notification, { model: User, include: { model: FedUsers, as: 'followers'}} ] }) + debug('%s -> %s', action, event.title) + // insert notifications - const notifications = await eventController.getNotifications(event) - const a = await event.setNotifications(notifications) + const notifications = await eventController.getNotifications(event, action) + await event.addNotifications(notifications) + const event_notifications = await event.getNotifications() - const eventNotifications = await EventNotification.findAll({ - where: { - notificationId: notifications.map(n => n.id), - status: 'new' - } - }) - - const promises = eventNotifications.map(async e => { - const notification = await Notification.findByPk(e.notificationId) + const promises = event_notifications.map(async notification => { try { + // await notification.event_notification.update({ status: 'sending' }) await notifier.sendNotification(notification, event) - e.status = 'sent' + notification.event_notification.status = 'sent' } catch (err) { - e.status = 'error' + debug(err) + notification.event_notification.status = 'error' } - return e.save() + return notification.event_notification.save() }) - return Promise.all(promises) }, async notify () { @@ -68,7 +59,7 @@ const notifier = { if (!event.place) { return } const notification = await Notification.findByPk(e.notificationId) try { - await sendNotification(notification, event, e) + await sendNotification(type, notification, event) e.status = 'sent' return e.save() } catch (err) {