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.
|
||||
alphabet: '1234567890abcdef',
|
||||
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 Repository = require('./lib/repository');
|
||||
const NotificationWorker = require('./lib/workers/notification');
|
||||
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
|
@ -96,6 +97,10 @@ const webpackConfig = require('./webpack.config.js');
|
|||
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));
|
||||
}
|
||||
|
|
|
@ -106,6 +106,11 @@ module.exports = (repository, tusServer) =>
|
|||
}
|
||||
|
||||
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 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ const Datastore = require('nedb');
|
|||
const UserRepository = require('./user').UserRepository;
|
||||
const CodeRepository = require('./code').CodeRepository;
|
||||
const UploadRepository = require('./upload').UploadRepository;
|
||||
const NotificationRepository = require('./notification').NotificationRepository;
|
||||
|
||||
|
||||
class Repository
|
||||
|
@ -46,6 +47,7 @@ class Repository
|
|||
self.users = new UserRepository(initStore('user.db'));
|
||||
self.codes = new CodeRepository(initStore('code.db'));
|
||||
self.uploads = new UploadRepository(initStore('upload.db'));
|
||||
self.notifications = new NotificationRepository(initStore('notification.db'));
|
||||
|
||||
self.users.init()
|
||||
.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",
|
||||
"debug": "^3.1.0",
|
||||
"diskusage": "^0.2.4",
|
||||
"ejs": "^2.5.9",
|
||||
"email-templates": "^3.6.0",
|
||||
"express": "^4.16.3",
|
||||
"express-async-handler": "^1.1.2",
|
||||
"js-cookie": "^2.2.0",
|
||||
|
@ -36,7 +38,9 @@
|
|||
"mz": "^2.7.0",
|
||||
"nanoid": "^1.0.2",
|
||||
"nedb": "^1.8.0",
|
||||
"nodemailer": "^4.6.4",
|
||||
"npm": "^5.8.0",
|
||||
"pug": "^2.0.3",
|
||||
"resolve-path": "^1.4.0",
|
||||
"tus-node-server": "^0.2.10"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue