v1.10.0
This commit is contained in:
parent
f29a8a1b67
commit
773977dd96
10 changed files with 194 additions and 132 deletions
|
@ -1,5 +1,11 @@
|
|||
# Changelog
|
||||
|
||||
## 1.10.0 2016-05-25
|
||||
|
||||
* Fetch multiple unsent messages at once to speed up delivery
|
||||
* Fixed a bug of counting unsubscribers correctly
|
||||
* Use LONGTEXT for template text fields (messages might include inlined images which are larger than 64kB)
|
||||
|
||||
## 1.9.0 2016-05-16
|
||||
|
||||
* New look
|
||||
|
|
28
lib/caches.js
Normal file
28
lib/caches.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
let cache = module.exports.cache = new Map();
|
||||
|
||||
module.exports.push = (name, value) => {
|
||||
if (!cache.has(name)) {
|
||||
cache.set(name, []);
|
||||
} else if (!Array.isArray(cache.get(name))) {
|
||||
cache.set(name, [].concat(cache.get(name) || []));
|
||||
}
|
||||
cache.get(name).push(value);
|
||||
};
|
||||
|
||||
module.exports.shift = name => {
|
||||
if (!cache.has(name)) {
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(cache.get(name))) {
|
||||
let value = cache.get(name);
|
||||
cache.delete(name);
|
||||
return value;
|
||||
}
|
||||
let value = cache.get(name).shift();
|
||||
if (!cache.get(name).length) {
|
||||
cache.delete(name);
|
||||
}
|
||||
return value;
|
||||
};
|
|
@ -11,6 +11,7 @@ let isUrl = require('is-url');
|
|||
let feed = require('../feed');
|
||||
let log = require('npmlog');
|
||||
let mailer = require('../mailer');
|
||||
let caches = require('../caches');
|
||||
|
||||
let allowedKeys = ['description', 'from', 'address', 'subject', 'template', 'source_url', 'list', 'segment', 'html', 'text'];
|
||||
|
||||
|
@ -809,6 +810,7 @@ module.exports.pause = (id, callback) => {
|
|||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
caches.cache.delete('sender queue');
|
||||
return callback(null, true);
|
||||
});
|
||||
});
|
||||
|
@ -821,7 +823,7 @@ module.exports.reset = (id, callback) => {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
if (campaign.status !== 3 && !(campaign.status === 2 && campaign.scheduled > new Date())) {
|
||||
if (campaign.status !== 3 && campaign.status !== 4 && !(campaign.status === 2 && campaign.scheduled > new Date())) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
|
@ -835,6 +837,8 @@ module.exports.reset = (id, callback) => {
|
|||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
caches.cache.delete('sender queue');
|
||||
connection.query('DELETE FROM links WHERE campaign=?', [id], err => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"schemaVersion": 12
|
||||
"schemaVersion": 13
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mailtrain",
|
||||
"private": true,
|
||||
"version": "1.9.0",
|
||||
"version": "1.10.0",
|
||||
"description": "Self hosted email newsletter app",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -15,9 +15,7 @@ let shortid = require('shortid');
|
|||
let url = require('url');
|
||||
let htmlToText = require('html-to-text');
|
||||
let request = require('request');
|
||||
|
||||
// to speed things up fetch several unsent messages and store these into a cache
|
||||
let fetchCache = [];
|
||||
let caches = require('../lib/caches');
|
||||
|
||||
function findUnsent(callback) {
|
||||
|
||||
|
@ -49,8 +47,8 @@ function findUnsent(callback) {
|
|||
});
|
||||
};
|
||||
|
||||
if (fetchCache.length) {
|
||||
let cached = fetchCache.shift();
|
||||
if (caches.cache.has('sender queue')) {
|
||||
let cached = caches.shift('sender queue');
|
||||
return returnUnsent(cached.row, cached.campaign);
|
||||
}
|
||||
|
||||
|
@ -125,7 +123,7 @@ function findUnsent(callback) {
|
|||
connection.release();
|
||||
|
||||
rows.forEach(row => {
|
||||
fetchCache.push({
|
||||
caches.push('sender queue', {
|
||||
row,
|
||||
campaign
|
||||
});
|
||||
|
|
|
@ -14,7 +14,9 @@ CREATE TABLE `campaign` (
|
|||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `list` (`list`,`segment`,`subscription`),
|
||||
KEY `created` (`created`),
|
||||
KEY `response_id` (`response_id`)
|
||||
KEY `response_id` (`response_id`),
|
||||
KEY `status_index` (`status`),
|
||||
KEY `subscription_index` (`subscription`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE TABLE `campaign_tracker` (
|
||||
`list` int(11) unsigned NOT NULL,
|
||||
|
@ -43,9 +45,9 @@ CREATE TABLE `campaigns` (
|
|||
`from` varchar(255) DEFAULT '',
|
||||
`address` varchar(255) DEFAULT '',
|
||||
`subject` varchar(255) DEFAULT '',
|
||||
`html` text,
|
||||
`html_prepared` text,
|
||||
`text` text,
|
||||
`html` longtext,
|
||||
`html_prepared` longtext,
|
||||
`text` longtext,
|
||||
`status` tinyint(4) unsigned NOT NULL DEFAULT '1',
|
||||
`scheduled` timestamp NULL DEFAULT NULL,
|
||||
`status_change` timestamp NULL DEFAULT NULL,
|
||||
|
@ -174,7 +176,7 @@ CREATE TABLE `segments` (
|
|||
`created` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `list` (`list`),
|
||||
KEY `name` (`name`(191)),
|
||||
KEY `name` (`name`),
|
||||
CONSTRAINT `segments_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE TABLE `settings` (
|
||||
|
@ -183,7 +185,7 @@ CREATE TABLE `settings` (
|
|||
`value` text NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `key` (`key`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8mb4;
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (1,'smtp_hostname','localhost');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (2,'smtp_port','465');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (3,'smtp_encryption','TLS');
|
||||
|
@ -200,7 +202,7 @@ INSERT INTO `settings` (`id`, `key`, `value`) VALUES (13,'default_from','My Awes
|
|||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (14,'default_address','admin@example.com');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (15,'default_subject','Test message');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (16,'default_homepage','http://localhost:3000/');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (17,'db_schema_version','10');
|
||||
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (17,'db_schema_version','13');
|
||||
CREATE TABLE `subscription` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`cid` varchar(255) CHARACTER SET ascii NOT NULL,
|
||||
|
@ -228,8 +230,8 @@ CREATE TABLE `templates` (
|
|||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`description` text,
|
||||
`html` text,
|
||||
`text` text,
|
||||
`html` longtext,
|
||||
`text` longtext,
|
||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`(191))
|
||||
|
|
15
setup/sql/upgrade-00013.sql
Normal file
15
setup/sql/upgrade-00013.sql
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Header section
|
||||
# Define incrementing schema version number
|
||||
SET @schema_version = '13';
|
||||
|
||||
-- {{#each tables.campaign}}
|
||||
|
||||
# Adds separate index for 'subscription' on campaign messages table
|
||||
CREATE INDEX subscription_index ON `{{this}}` (`subscription`);
|
||||
|
||||
-- {{/each}}
|
||||
|
||||
# Footer section
|
||||
LOCK TABLES `settings` WRITE;
|
||||
INSERT INTO `settings` (`key`, `value`) VALUES('db_schema_version', @schema_version) ON DUPLICATE KEY UPDATE `value`=@schema_version;
|
||||
UNLOCK TABLES;
|
|
@ -210,13 +210,21 @@
|
|||
|
||||
{{#if isPaused}}
|
||||
<div class="pull-right">
|
||||
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
|
||||
<form id="resume-sending" class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
|
||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||
<input type="hidden" name="id" value="{{id}}" />
|
||||
|
||||
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Resume
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form id="reset-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would reset all stats about current progress" method="post" action="/campaigns/reset">
|
||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||
<input type="hidden" name="id" value="{{id}}" />
|
||||
</form>
|
||||
|
||||
<button type="submit" form="resume-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Resume
|
||||
</button>
|
||||
|
||||
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Reset
|
||||
</button>
|
||||
</div>
|
||||
<h4>Sending paused</h4>
|
||||
{{/if}}
|
||||
|
|
|
@ -61,140 +61,141 @@
|
|||
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="{{#if showSubscriptions}}active{{/if}}"><a href="#subscriptions" aria-controls="subscriptions" role="tab" data-toggle="tab">Subscriptions</a></li>
|
||||
<li role="presentation" class="{{#if showImports}}active{{/if}}"><a href="#imports" aria-controls="imports" role="tab" data-toggle="tab">Imports</a></li>
|
||||
<li role="presentation" class="{{#if showSubscriptions}}active{{/if}}"><a href="/lists/view/{{id}}" aria-controls="subscriptions" role="tab">Subscriptions</a></li>
|
||||
<li role="presentation" class="{{#if showImports}}active{{/if}}"><a href="/lists/view/{{id}}?tab=imports" aria-controls="imports" role="tab">Imports</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane {{#if showSubscriptions}}active{{/if}}" id="subscriptions">
|
||||
{{#if showSubscriptions}}
|
||||
<p></p>
|
||||
|
||||
<p></p>
|
||||
<div class="table-responsive">
|
||||
<table data-topic-url="/lists" data-topic-id="{{id}}" data-sort-column="1" data-sort-order="asc" {{#if useSegment}} data-topic-args="segment={{useSegment}}" {{/if}} class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1{{customSort}},1,0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-1">
|
||||
#
|
||||
</th>
|
||||
<th>
|
||||
Address
|
||||
</th>
|
||||
<th>
|
||||
First Name
|
||||
</th>
|
||||
<th>
|
||||
Last Name
|
||||
</th>
|
||||
{{#each customFields}}
|
||||
<th>
|
||||
{{name}}
|
||||
</th>
|
||||
{{/each}}
|
||||
<th>
|
||||
Status
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table data-topic-url="/lists" data-topic-id="{{id}}" data-sort-column="1" data-sort-order="asc" {{#if useSegment}} data-topic-args="segment={{useSegment}}" {{/if}} class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1{{customSort}},1,0">
|
||||
<thead>
|
||||
<tr>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane {{#if showImports}}active{{/if}}" id="imports">
|
||||
{{#if showImports}}
|
||||
<p></p>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<th class="col-md-1">
|
||||
#
|
||||
</th>
|
||||
<th>
|
||||
Address
|
||||
Created
|
||||
</th>
|
||||
<th>
|
||||
First Name
|
||||
Finished
|
||||
</th>
|
||||
<th>
|
||||
Last Name
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
Added
|
||||
</th>
|
||||
<th>
|
||||
Updated
|
||||
</th>
|
||||
<th>
|
||||
Failed
|
||||
</th>
|
||||
{{#each customFields}}
|
||||
<th>
|
||||
{{name}}
|
||||
</th>
|
||||
{{/each}}
|
||||
<th>
|
||||
Status
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<th>
|
||||
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane {{#if showImports}}active{{/if}}" id="imports">
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if imports}}
|
||||
{{#each imports}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{index}}
|
||||
</th>
|
||||
|
||||
<p></p>
|
||||
<td>
|
||||
<span class="datestring" data-date="{{created}}" title="{{created}}">{{created}}</span>
|
||||
</td>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<th class="col-md-1">
|
||||
#
|
||||
</th>
|
||||
<th>
|
||||
Created
|
||||
</th>
|
||||
<th>
|
||||
Finished
|
||||
</th>
|
||||
<th>
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
Added
|
||||
</th>
|
||||
<th>
|
||||
Updated
|
||||
</th>
|
||||
<th>
|
||||
Failed
|
||||
</th>
|
||||
<th>
|
||||
Status
|
||||
</th>
|
||||
<th>
|
||||
<td>
|
||||
{{#if finished}}
|
||||
<span class="datestring" data-date="{{finished}}" title="{{finished}}">{{finished}}</span>
|
||||
{{else}}
|
||||
No
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if imports}}
|
||||
{{#each imports}}
|
||||
<td>
|
||||
{{importType}}
|
||||
</td>
|
||||
<td>
|
||||
{{new}}
|
||||
</td>
|
||||
<td>
|
||||
{{updated}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if failed}}<a href="/lists/subscription/{{../id}}/import/{{id}}/failed">{{failed}}</a>{{else}}0 {{/if}}
|
||||
</td>
|
||||
<td class="{{#if error}}text-danger{{/if}}">
|
||||
{{#if error}}
|
||||
<strong><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {{importStatus}}</strong>
|
||||
{{else}}
|
||||
<strong>{{importStatus}}</strong>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form method="post" class="confirm-submit" data-confirm-message="Are you sure? This action should only be called to resolve stalled imports" action="/lists/subscription/import-restart">
|
||||
<input type="hidden" name="_csrf" value="{{../csrfToken}}">
|
||||
<input type="hidden" name="list" value="{{list}}">
|
||||
<input type="hidden" name="import" value="{{id}}">
|
||||
<button type="submit" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Restart</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr>
|
||||
<th scope="row">
|
||||
{{index}}
|
||||
</th>
|
||||
|
||||
<td>
|
||||
<span class="datestring" data-date="{{created}}" title="{{created}}">{{created}}</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{#if finished}}
|
||||
<span class="datestring" data-date="{{finished}}" title="{{finished}}">{{finished}}</span>
|
||||
{{else}}
|
||||
No
|
||||
{{/if}}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{importType}}
|
||||
</td>
|
||||
<td>
|
||||
{{new}}
|
||||
</td>
|
||||
<td>
|
||||
{{updated}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if failed}}<a href="/lists/subscription/{{../id}}/import/{{id}}/failed">{{failed}}</a>{{else}}0 {{/if}}
|
||||
</td>
|
||||
<td class="{{#if error}}text-danger{{/if}}">
|
||||
{{#if error}}
|
||||
<strong><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {{importStatus}}</strong>
|
||||
{{else}}
|
||||
<strong>{{importStatus}}</strong>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form method="post" class="confirm-submit" data-confirm-message="Are you sure? This action should only be called to resolve stalled imports" action="/lists/subscription/import-restart">
|
||||
<input type="hidden" name="_csrf" value="{{../csrfToken}}">
|
||||
<input type="hidden" name="list" value="{{list}}">
|
||||
<input type="hidden" name="import" value="{{id}}">
|
||||
<button type="submit" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Restart</button>
|
||||
</form>
|
||||
<td colspan="9">
|
||||
No data available in table
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
No data available in table
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue