Implemented email notifications
This commit is contained in:
parent
b17916be4b
commit
0ac43ce0e5
|
@ -22,5 +22,26 @@ module.exports = {
|
||||||
// If a collision occurs, a new code will be generated and tested again for up to 100 times.
|
// If a collision occurs, a new code will be generated and tested again for up to 100 times.
|
||||||
alphabet: '1234567890abcdef',
|
alphabet: '1234567890abcdef',
|
||||||
length: 8
|
length: 8
|
||||||
|
},
|
||||||
|
|
||||||
|
notifications: {
|
||||||
|
interval: 10,
|
||||||
|
maxAttempt: 12,
|
||||||
|
adminUrl: 'http://localhost:3001/admin/',
|
||||||
|
|
||||||
|
mail: {
|
||||||
|
from: '"Recv" <recv@localhost>',
|
||||||
|
|
||||||
|
transport: {
|
||||||
|
// passed directly to NodeMailer's createTransport. See nodemailer.com for more information.
|
||||||
|
host: 'smtp.ethereal.email',
|
||||||
|
port: 587,
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: 'recv',
|
||||||
|
pass: 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
<%
|
||||||
|
function humanFileSize(bytes, si)
|
||||||
|
{
|
||||||
|
var thresh = si ? 1000 : 1024;
|
||||||
|
if(Math.abs(bytes) < thresh)
|
||||||
|
{
|
||||||
|
return bytes + ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
var units = si
|
||||||
|
? ['kB','MB','GB','TB','PB','EB','ZB','YB']
|
||||||
|
: ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
||||||
|
var u = -1;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytes /= thresh;
|
||||||
|
++u;
|
||||||
|
}
|
||||||
|
while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||||
|
|
||||||
|
return bytes.toFixed(1) + ' ' + units[u];
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
<p>Hello <%= user.name %>,</p>
|
||||||
|
<p>The following files have just been uploaded:</p>
|
||||||
|
<ul>
|
||||||
|
<% upload.files.forEach((file) => { %>
|
||||||
|
<li><%= file.name %> (<%= humanFileSize(file.size, true) %>)</li>
|
||||||
|
<% }) %>
|
||||||
|
</ul>
|
||||||
|
<p>You can download these files by logging in to <a href="<%= adminUrl %>"><%= adminUrl %></a></p>
|
||||||
|
<p>Cheers,<br />Recv</p>
|
|
@ -0,0 +1 @@
|
||||||
|
📄 File upload notification
|
5
index.js
5
index.js
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const Repository = require('./lib/repository');
|
const Repository = require('./lib/repository');
|
||||||
|
const NotificationWorker = require('./lib/workers/notification');
|
||||||
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -96,6 +97,10 @@ const webpackConfig = require('./webpack.config.js');
|
||||||
app.get('/admin', (req, res) => { res.redirect(301, '/#/admin/') });
|
app.get('/admin', (req, res) => { res.redirect(301, '/#/admin/') });
|
||||||
|
|
||||||
|
|
||||||
|
// Background workers
|
||||||
|
var notificationWorker = new NotificationWorker(repository);
|
||||||
|
notificationWorker.start(config.notifications.interval * 1000);
|
||||||
|
|
||||||
|
|
||||||
var server = app.listen(config.port, () => console.log('Recv running on port ' + server.address().port));
|
var server = app.listen(config.port, () => console.log('Recv running on port ' + server.address().port));
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,11 @@ module.exports = (repository, tusServer) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadId = await repository.uploads.insert(decoded.codeUserId, decoded.code, req.body.files, expiration);
|
var uploadId = await repository.uploads.insert(decoded.codeUserId, decoded.code, req.body.files, expiration);
|
||||||
|
await repository.notifications.insert({
|
||||||
|
userId: decoded.codeUserId,
|
||||||
|
uploadId: uploadId
|
||||||
|
});
|
||||||
|
|
||||||
res.send({ id: uploadId });
|
res.send({ id: uploadId });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ const Datastore = require('nedb');
|
||||||
const UserRepository = require('./user').UserRepository;
|
const UserRepository = require('./user').UserRepository;
|
||||||
const CodeRepository = require('./code').CodeRepository;
|
const CodeRepository = require('./code').CodeRepository;
|
||||||
const UploadRepository = require('./upload').UploadRepository;
|
const UploadRepository = require('./upload').UploadRepository;
|
||||||
|
const NotificationRepository = require('./notification').NotificationRepository;
|
||||||
|
|
||||||
|
|
||||||
class Repository
|
class Repository
|
||||||
|
@ -46,6 +47,7 @@ class Repository
|
||||||
self.users = new UserRepository(initStore('user.db'));
|
self.users = new UserRepository(initStore('user.db'));
|
||||||
self.codes = new CodeRepository(initStore('code.db'));
|
self.codes = new CodeRepository(initStore('code.db'));
|
||||||
self.uploads = new UploadRepository(initStore('upload.db'));
|
self.uploads = new UploadRepository(initStore('upload.db'));
|
||||||
|
self.notifications = new NotificationRepository(initStore('notification.db'));
|
||||||
|
|
||||||
self.users.init()
|
self.users.init()
|
||||||
.then(() => { resolve() })
|
.then(() => { resolve() })
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
|
||||||
|
class Notification
|
||||||
|
{
|
||||||
|
constructor(values)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.id = values.id || values._id || null;
|
||||||
|
self.userId = values.userId || null;
|
||||||
|
self.uploadId = values.uploadId || null;
|
||||||
|
self.attempt = values.attempt || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationRepository
|
||||||
|
{
|
||||||
|
constructor(store)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
self.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
list()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.find({}, (err, docs) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(_.map(docs, (doc) => new Notification(doc)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
insert(notification)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.insert({
|
||||||
|
userId: notification.userId,
|
||||||
|
uploadId: notification.uploadId,
|
||||||
|
attempt: notification.attempt
|
||||||
|
}, (err, dbNotification) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(dbNotification._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
update(notification)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.update({ _id: notification.id }, { $set: {
|
||||||
|
attempt: notification.attempt
|
||||||
|
}},
|
||||||
|
(err, numAffected) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numAffected == 0)
|
||||||
|
{
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
delete(notificationId)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
self.store.remove({ _id: notificationId }, (err, numRemoved) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Notification,
|
||||||
|
NotificationRepository
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
const config = require('../../config');
|
||||||
|
const async = require('async');
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const Email = require('email-templates');
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationWorker
|
||||||
|
{
|
||||||
|
constructor(repository)
|
||||||
|
{
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
start(interval)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.stop();
|
||||||
|
self.timer = setInterval(async () =>
|
||||||
|
{
|
||||||
|
if (self.ticking)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self.ticking = true;
|
||||||
|
await self.tick();
|
||||||
|
self.ticking = false;
|
||||||
|
}, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stop()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
if (self.timer)
|
||||||
|
{
|
||||||
|
clearInterval(self.timer);
|
||||||
|
self.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async tick()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var notifications = await self.repository.notifications.list();
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
async.eachOfSeries(notifications,
|
||||||
|
async (item) => { await self.sendNotification(item) },
|
||||||
|
(err) =>
|
||||||
|
{
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async sendNotification(notification)
|
||||||
|
{
|
||||||
|
let self = this;
|
||||||
|
let user = await self.repository.users.get(notification.userId);
|
||||||
|
if (user === null || !user.email)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let upload = await self.repository.uploads.get(notification.uploadId);
|
||||||
|
if (upload === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
{
|
||||||
|
const email = new Email({
|
||||||
|
message: {
|
||||||
|
from: config.notifications.mail.from
|
||||||
|
},
|
||||||
|
|
||||||
|
send: true,
|
||||||
|
preview: false,
|
||||||
|
transport: nodemailer.createTransport(config.notifications.mail.transport),
|
||||||
|
views: {
|
||||||
|
options: {
|
||||||
|
extension: 'ejs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
email
|
||||||
|
.send({
|
||||||
|
template: 'uploadnotification',
|
||||||
|
message: {
|
||||||
|
to: user.email
|
||||||
|
},
|
||||||
|
locals: {
|
||||||
|
user: user,
|
||||||
|
upload: upload,
|
||||||
|
adminUrl: config.notifications.adminUrl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async () =>
|
||||||
|
{
|
||||||
|
await self.repository.notifications.delete(notification.id);
|
||||||
|
console.log('Notification sent to: ' + user.email + ' for upload ID: ' + upload.id);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(async (error) =>
|
||||||
|
{
|
||||||
|
notification.attempt++;
|
||||||
|
if (notification.attempt > config.notifications.maxAttempts)
|
||||||
|
await self.repository.notifications.delete(notification.id);
|
||||||
|
else
|
||||||
|
await self.repository.notifications.update(notification);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NotificationWorker;
|
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,8 @@
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
"debug": "^3.1.0",
|
"debug": "^3.1.0",
|
||||||
"diskusage": "^0.2.4",
|
"diskusage": "^0.2.4",
|
||||||
|
"ejs": "^2.5.9",
|
||||||
|
"email-templates": "^3.6.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"express-async-handler": "^1.1.2",
|
"express-async-handler": "^1.1.2",
|
||||||
"js-cookie": "^2.2.0",
|
"js-cookie": "^2.2.0",
|
||||||
|
@ -36,7 +38,9 @@
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
"nanoid": "^1.0.2",
|
"nanoid": "^1.0.2",
|
||||||
"nedb": "^1.8.0",
|
"nedb": "^1.8.0",
|
||||||
|
"nodemailer": "^4.6.4",
|
||||||
"npm": "^5.8.0",
|
"npm": "^5.8.0",
|
||||||
|
"pug": "^2.0.3",
|
||||||
"resolve-path": "^1.4.0",
|
"resolve-path": "^1.4.0",
|
||||||
"tus-node-server": "^0.2.10"
|
"tus-node-server": "^0.2.10"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue