New project structure

Beta of extract.js for extracting english locale
This commit is contained in:
Tomas Bures 2018-11-18 15:38:52 +01:00
parent e18d2b2f84
commit 2edbd67205
247 changed files with 6405 additions and 4237 deletions

13
.gitignore vendored
View file

@ -4,19 +4,6 @@
node_modules
npm-debug.log
.DS_Store
config/development.*
config/production.*
config/test.*
workers/reports/config/development.*
workers/reports/config/production.*
workers/reports/config/test.*
dump.rdb
# generate POT file every time you want to update your PO file
languages/mailtrain.pot
config/production.toml
workers/reports/config/production.toml
docker-compose.override.yml
/files

View file

@ -1,19 +0,0 @@
# Crowdfunding Backers of Mailtrain v1
Mailtrain v1 received funding from a [crowdfunding campaign](https://www.indiegogo.com/at/mailtrain/8720095).
This was to enable me to spend the time required to get automation support into Mailtrain v1. These are the people who contributed to this fund raiser.
* iRedMail - free, open source mail server solution <[www.iredmail.org](http://www.iredmail.org/)>
* Richard Adleta
* Wes Bos
* Christophe Lombart
* Anselm Hannemann
* Jens Carroll
* Anonymous
* Brett Nelson
* Jason Pelker
* Leif Singer
* Eve Land
* Diana Espino
* Moussa Clarke
* Carl Hauschke

View file

@ -1,149 +1,5 @@
# Changelog
## 1.23.2 2017-04-04
## 2.0.0 2018-11-23
* Allow skipping DNS check for imports
* Added option to use subscription widgets
## 1.23.0 2017-03-19
* Fixed security issue where description tags were able to include script tags. Reported by Andreas Lindh. Fixed with [ae6affda](https://github.com/Mailtrain-org/mailtrain/commit/ae6affda8193f034e06f7e095ee23821a83d5190)
* Fixed security issue where templates that looked like file paths loaded content from arbitrary files. Reported by Andreas Lindh. Fixed with [0879fa41](https://github.com/Mailtrain-org/mailtrain/commit/0879fa412a2d4a417aeca5cd5092a8f86531e7ef)
* Fixed security issue where users were able to use html tags in subscription values. Reported by Andreas Lindh. Fixed with [9d5fb816](https://github.com/Mailtrain-org/mailtrain/commit/9d5fb816c937114966d4f589e1ad4e164ff3a187)
* Support for multiple HTML editors (Mosaico, Grapesjs, Summernote, HTML code)
## 1.22.0 2017-03-02
* Reverted license back to GPL-v3 to support Mosaico
## 1.21.0 2017-02-17
* Changed license from MIT to EUPL-1.1
* Added support for sending mail using AWS SES
## 1.20.0 2016-12-11
* Added option to distribute sending queue between multiple processes to speed up delivery
## 1.19.0 2016-09-15
* Changed license from GPL-V3 to MIT
## 1.18.0 2016-09-08
* Updated installation script to bundle ZoneMTA as the default sending engine
* Added new option to disable clicked and opened tracking
* Store remote IP for subscription confirmations
## 1.17.0 2016-08-29
* Added new custom field for JSON data that is rendered using Handlebars when included in an email
## 1.16.0 2016-08-29
* Render list values using Handlebars templates
* Added new API method to create custom fields
* Added LDAP authentication support
## 1.15.0 2016-07-28
* Check SMTP settings using AJAX instead of posting entire form
## 1.14.0 2016-07-09
* Fixed ANY match segments with range queries
* Added an option to disable un/subscribe confirmation messages
* Added support for throttling when sending messages
* Added preview links in message lists
## 1.13.0 2016-06-23
* Added API method to delete subscribers
* Added a counter to triggers with a view to list all subscribers that caused this trigger to fire
## 1.12.1 2016-06-23
* Fixed invalid base SQL dump
## 1.12.0 2016-06-22
* Automation support. Create triggers that send a campaign once fired
* Fixed an issue with unresolved URL redirects
* Added support for relative date ranges in segments
## 1.11.0 2016-05-31
* Retry transactional mail if failed with soft error (4xx)
* New feature to preview campaigns using selected test users
## 1.10.1 2016-05-26
* Fix a bug with SMTP transport instance where campaign sending stalled until server was restarted
## 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
* Added views for bounced/unsubscribed/complained etc.
## 1.8.2 2016-05-13
* Added missing views for subscribers who clicked on any link and subscribers who opened the message
## 1.8.1 2016-05-13
* Fixed an issue in API
## 1.8.0 2016-05-13
* Show details about subscribers who clicked on a specific link
## 1.7.0 2016-05-11
* Updated API, added new option **REQUIRE_CONFIRMATION** for subscriptions to send confirmation email before subscribing
## 1.6.0 2016-05-07
* Added simple API support for adding and removing list subscriptions
## 1.5.0 2016-05-05
* Fixed a bug in unsubscribing through the admin interface
* Added individual link click stats
## 1.4.1 2016-05-04
* Added support for RSS templates
## 1.4.0 2016-05-04
* Added support for RSS campaigns
* Subscribers get timezone attached to their profile
* Outgoing messages are preprocessed using juice
* Added installation script for easier setup
## 1.3.0 2016-04-29
* Added option to use an URL as message source (when message needs to be rendered a POST request with Merge Tags as the POST body is made against that URL)
* Added option to schedule sending. You can set optional delay time when starting campaign sending. Once this time is reached sending starts automatically
* Show meaningful MySQL error when connection fails
## 1.2.0 2016-04-25
* Rewrite merge tags in links (allows using links like `http://example.com/?u=[FIRST_NAME]` in messages)
* Added view for Imports to list failed addresses
* Automatic SQL table creation on initial run (no need for the `mysql` command anymore)
* Automatic SQL table updates on startup
* Send welcome and unsubscribe confirmation emails for subscribers
* Added support for GPG encryption for outgoing messages (requires custom field "GPG Key" set up for the list)
* Added new SMTP option: allow self-signed certs
* Added new setting: Disable WYSIWG editor (allows better handling of complex HTML templates)
* Allow downgrading user when server started as root (user is downgraded once all ports are bound)
* Added Nitrous.io one-click install button for easy try-out
* Added Max Post Size option to allow larger payloads from bounce webhooks
* Added VERP support to catch bounces using built in VERP smtp-server (disabled by default)
* This is a complete rewrite of Mailtrain v1 with many features added. Just check it out.

View file

@ -1,18 +0,0 @@
'use strict';
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
eslint: {
all: ['lib/**/*.js', 'test/**/*.js', 'config/**/*.js', 'services/**/*.js', 'Gruntfile.js', 'app.js', 'index.js', 'routes/editorapi.js']
}
});
// Load the plugin(s)
grunt.loadNpmTasks('grunt-eslint');
grunt.task.loadTasks('tasks');
// Tasks
grunt.registerTask('default', ['eslint']);
};

View file

@ -1,6 +1,6 @@
# Mailtrain
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v7+) and MySQL (v5.5+ or MariaDB).
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v10+) and MySQL (v8+) or MariaDB (v10+).
![](http://mailtrain.org/mailtrain.png)
@ -9,14 +9,16 @@
* Subscriber list management
* List segmentation
* Custom fields
* Email templates
* Email templates (including MJML-based templates)
* Large CSV list import files
Subscribe to Mailtrain Newsletter [here](https://mailtrain.org/subscription/S18sew2wM) (uses Mailtrain obviously)
* Custom reports
* Automation (triggered and RSS campaigns)
* Multiple users with granular user permissions and flexible sharing
* Hierarchical namespaces for enterprise-level situations
## Hardware Requirements
* 1 vCPU
* 1024 MB RAM
* 2048 MB RAM
## Quick Start - Deploy with Docker
#### Requirements:
@ -39,7 +41,7 @@ Depending on how you have configured your system and Docker you may need to prep
## Quick Start - Manual Install (any OS that supports Node.js)
### Requirements:
* Mailtrain requires at least **Node.js v7**. If you want to use an older version of Node.js then you should use version v1.24 of Mailtrain. You can either download it [here](https://github.com/Mailtrain-org/mailtrain/archive/v1.24.0.zip) or if using git then run `git checkout v1.24.0` before starting it
* Mailtrain requires at least **Node.js v10**.
1. Download Mailtrain files using git: `git clone git://github.com/Mailtrain-org/mailtrain.git` (or download [zipped repo](https://github.com/Mailtrain-org/mailtrain/archive/master.zip)) and open Mailtrain folder `cd mailtrain`
2. Run `npm install --production` in the Mailtrain folder to install required dependencies
@ -56,7 +58,4 @@ For more information, please [read the docs](http://docs.mailtrain.org/).
## License
* Versions 1.22.0 and up **GPL-V3.0**
* Versions 1.21.0 and up: **EUPL-1.1**
* Versions 1.19.0 and up: **MIT**
* Up to versions 1.18.0 **GPL-V3.0**
**GPL-V3.0**

View file

@ -1,18 +1,9 @@
### Front page
- Some dashboard
### Templates
- Add MJML template editor
- Include Grapesjs with MJML support
- CKEditor to sandbox
- Add Files support to CKEditor
### Message delivery
- Better integration with ZoneMTA to allow multiple send configurations (with different DKIM) against one ZoneMTA instance via different HTTP configuration of ZoneMTA. This may need an extension of ZoneMTA to provide some header entry that identifies the campaign.
### Lists
- CSV Export
### Campaigns
- Statistics for a sent campaign
- List of sent RSS campaigns (?)

View file

@ -1,6 +1,6 @@
{
"name": "mailtrain-client",
"version": "1.0.0",
"version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -5319,7 +5319,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",

View file

@ -1,6 +1,6 @@
{
"name": "mailtrain-client",
"version": "1.0.0",
"version": "2.0.0",
"description": "Self hosted email newsletter app - client components",
"main": "index.js",
"scripts": {
@ -11,7 +11,6 @@
"type": "git",
"url": "git://github.com/Mailtrain-org/mailtrain.git"
},
"author": "",
"license": "GPL-3.0",
"homepage": "https://mailtrain.org/",
"dependencies": {

View file

@ -43,9 +43,9 @@ export function getMailerTypes(t) {
function validateNumber(state, field, label, emptyAllowed = false) {
const value = state.getIn([field, 'value']);
if (typeof value === 'string' && value.trim() === '' && !emptyAllowed) { // After load, the numerical values can be still numbers
state.setIn([field, 'error'], t(`${label} must not be empty`));
state.setIn([field, 'error'], t('{{label}} must not be empty', {label}));
} else if (isNaN(value)) {
state.setIn([field, 'error'], t(`${label} must be a number`));
state.setIn([field, 'error'], t('{{label}} must be a number', {label}));
} else {
state.setIn([field, 'error'], null);
}

View file

@ -578,6 +578,9 @@ export function getEditForm(owner, typeKey, prefix = '') {
{owner.templateTypes[typeKey].getHTMLEditor(owner)}
<ACEEditor id={prefix + 'text'} height="400px" mode="text" label={t('Template content (plain text)')} help={<Trans>To extract the text from HTML click <ActionLink onClickAsync={::owner.extractPlainText}>here</ActionLink>. Please note that your existing plaintext in the field above will be overwritten. This feature uses the <a href="http://premailer.dialect.ca/api">Premailer API</a>, a third party service. Their Terms of Service and Privacy Policy apply.</Trans>}/>
/*prefix:helpers*/<Trans i18nKey="userMessagesUnread" count={count}>
Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>
</div>;
}

View file

@ -1,13 +1,13 @@
FROM node:8.6
# First install dependencies
COPY ./package.json ./app/
COPY ../server/package.json ./app/
WORKDIR /app/
ENV NODE_ENV production
RUN npm install --no-progress --production && npm install --no-progress passport-ldapjs passport-ldapauth
# Later, copy the app files. That improves development speed as buiding the Docker image will not have
# to download and install all the NPM dependencies every time there's a change in the source code
COPY . /app
COPY .. /app
EXPOSE 3000
ENTRYPOINT ["bash", "/app/docker-entrypoint.sh"]
CMD ["node", "index.js"]

View file

@ -1,10 +1,11 @@
# Mailtrain
# Mailtrain v2
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v7+) and MySQL (v5.5+ or MariaDB).
[Mailtrain](http://mailtrain.org) is a self hosted newsletter application built on Node.js (v10+) and MySQL (v8+) or MariaDB (v10+).
![](http://mailtrain.org/mailtrain.png)
> Mailtrain requires at least **Node.js v7**. If you want to use an older version of Node.js then you should use version v1.24 of Mailtrain. You can either download it [here](https://github.com/Mailtrain-org/mailtrain/archive/v1.24.0.zip) or if using git then run `git checkout v1.24.0` before starting it
FIXME: The info below needs to be updated !!!
## Features

View file

@ -95,58 +95,6 @@
"namespace": {
"mustBeSelected": "Namespace must be selected"
},
"": {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": ""
},
"": {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": ""
},
"": {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": ""
},
"": {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": ""
},
"close": "Close",
"name": "Name",
@ -158,7 +106,6 @@
"loading": "Loading ...",
"email": "Email",
"update": "Update",
"": "",
"namespace": "Namespace",
"namespace_plural": "Namespaces",
@ -190,17 +137,7 @@
"validationInProgress": "Validation is in progress...",
"errorsInForm": "There are errors in the form. Please fix them and submit again.",
"updatesCannotBeSaved": "Your updates cannot be saved.",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"mjml": "MJML",
"html": "HTML",
"": "",
"": "",
"": ""
"html": "HTML"
}

297
locales/extract.js Normal file
View file

@ -0,0 +1,297 @@
'use strict';
// Example:
// tUI(/*prefix:account*/'account.passwordChangeRequest', language)
// /*prefix:helpers*/<Trans i18nKey="userMessagesUnread" count={count}>Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.</Trans>
const fs = require('fs');
const path = require('path');
const klawSync = require('klaw-sync');
const acorn = require("acorn");
const acornJsx = require("acorn-jsx");
const ellipsize = require('ellipsize');
const camelCase = require('camelcase');
const slugify = require('slugify');
const readline = require('readline');
const localeFile = 'common/en.json';
const searchDirs = [
'../client/src',
'../server',
'../shared'
];
function findInDict(dict, key) {
const keyElems = key.split('.');
let val = dict;
for (const keyElem of keyElems) {
if (val) {
val = val[keyElem];
} else {
return undefined;
}
}
return val;
}
function setInDict(dict, key, value) {
const keyElems = key.split('.');
let val = dict;
for (const keyElem of keyElems.slice(0, -1)) {
if (val[keyElem]) {
if (typeof val[keyElem] === 'string') {
throw new Error(`Overlapping key ${key}`);
}
} else {
val[keyElem] = {}
}
val = val[keyElem];
}
val[keyElems[keyElems.length - 1]] = value;
}
const assignedKeys = new Map();
function getKeyFromValue(spec, value) {
let key = value.replace(/<\/?[0-9]+>/g, ''); // Remove Trans markup
key = slugify(key, { replacement: ' ', remove: /[()"':.,;\[\]\{\}*+-]/g, lower: false });
key = camelCase(key);
key = ellipsize(key, 40, {
chars: [...Array(26)].map((_, i) => String.fromCharCode('A'.charCodeAt(0) + i)) /* This is an array of characters A-Z */,
ellipse: ''
});
if (spec.prefix) {
key = spec.prefix + '.' + key;
}
let idx = 0;
while (true) {
const keyExt = key + (idx ? '-' + idx : '')
if (assignedKeys.has(keyExt)) {
if (assignedKeys.get(keyExt) === value) {
assignedKeys.set(key, value);
return keyExt;
}
} else {
assignedKeys.set(key, value);
return keyExt;
}
idx++;
}
}
function allowedDirOrFile(item) {
const pp = path.parse(item.path)
return (
(item.stats.isDirectory() &&
pp.base !== 'node_modules'
) ||
(item.stats.isFile() &&
( pp.ext === '.js' || pp.ext === '.jsx')
)
);
}
function parseSpec(specStr) {
const spec = {};
if (specStr) {
const entryMatcher = /([a-zA-Z]*)\s*:\s*(.*)/
const entries = specStr.split(/\s*,\s*/);
for (const entry of entries) {
const elems = entry.match(entryMatcher);
if (elems) {
spec[elems[1]] = elems[2];
}
}
}
return spec;
}
// see http://blog.stevenlevithan.com/archives/match-quoted-string
const tMatcher = /(^|[ {+(=])((?:tUI|tLog|t|tMark)\s*\(\s*(?:\/\*(.*?)\*\/)?\s*)(["'])((?:(?!\1)[^\\]|\\.)*)(\4)/;
const transMatcher = /(\/\*(.*?)\*\/\s*)?(\<Trans[ >][\s\S]*?\<\/Trans\>)/;
const jsxParser = acorn.Parser.extend(acornJsx());
function parseTrans(fragment) {
const match = fragment.match(transMatcher);
const spec = parseSpec(match[2]);
const jsxStr = match[3];
const jsxStrSmpl = jsxStr.replace('{::', '{ '); // Acorn does not handle bind (::) operator. So we just leave it out because we are not interested in the code anyway.
const ast = jsxParser.parse(jsxStrSmpl);
function convertChildren(children) {
const entries = [];
let childNo = 0;
for (const child of children) {
const type = child.type;
if (type === 'JSXText') {
entries.push(child.value);
childNo++;
} else if (type === 'JSXElement') {
const inner = convertChildren(child.children);
entries.push(`<${childNo}>${convertChildren(child.children)}</${childNo}>`);
childNo++;
} else if (type === 'JSXExpressionContainer') {
entries.push(jsxStr.substring(child.start, child.end));
childNo++;
} else {
throw new Error('Unknown JSX node: ' + child);
}
}
return entries.join('');
}
const expr = ast.body[0].expression;
let originalKey;
for (const attr of expr.openingElement.attributes) {
const name = attr.name.name;
if (name === 'i18nKey') {
originalKey = attr.value.value;
}
}
const convValue = convertChildren(expr.children);
if (originalKey === undefined) {
originalKey = convValue;
}
let value;
const originalValue = findInDict(originalResDict, originalKey);
if (originalValue === undefined) {
value = convValue;
} else {
value = originalValue;
}
const key = getKeyFromValue(spec, value);
const replacement = `${match[1] || ''}<Trans i18nKey="${key}">${jsxStr.substring(expr.openingElement.end, expr.closingElement.start)}</Trans>`;
return { key, originalKey, value, replacement };
}
function parseT(fragment) {
const match = fragment.match(tMatcher);
const originalKey = match[5];
const spec = parseSpec(match[3]);
// console.log(`${file}: ${line}`);
// console.log(` |${match[1]}|${match[2]}|${match[4]}|${match[5]}|${match[6]}| - ${JSON.stringify(spec)}`);
let value;
const originalValue = findInDict(originalResDict, originalKey);
if (originalValue === undefined) {
value = originalKey;
} else {
value = originalValue;
}
const key = getKeyFromValue(spec, value);
const replacement = `${match[1]}${match[2]}${match[4]}${key}${match[6]}`;
return { key, originalKey, value, originalValue, replacement };
}
const renamedKeys = new Map();
const resDict = {};
let anyUpdatesToResDict = false;
function processFile(file) {
let source = fs.readFileSync(file, 'utf8');
let anyUpdates = false;
function update(fragments, parseFun) {
if (fragments) {
for (const fragment of fragments) {
const {key, originalKey, value, originalValue, replacement} = parseFun(fragment);
console.log(`${key} <- ${originalKey} | ${value} <- ${originalValue} | ${fragment} -> ${replacement}`);
source = source.split(fragment).join(replacement);
setInDict(resDict, key, value);
if (originalKey) {
renamedKeys.set(originalKey, key);
}
}
}
}
const lines = source.split(/\r?\n/g);
for (const line of lines) {
const fragments = line.match(new RegExp(tMatcher, 'g'));
update(fragments, parseT);
}
const fragments = source.match(new RegExp(transMatcher, 'g'));
update(fragments, parseTrans);
if (false) {
console.log(`Updating ${file}`);
fs.writeFileSync(file, source);
anyUpdatesToResDict = true;
}
}
const originalResDict = JSON.parse(fs.readFileSync(localeFile));
function run() {
/*
for (const dir of searchDirs) {
const files = klawSync(dir, { nodir: true, filter: allowedDirOrFile })
for (const file of files) {
processFile(file.path);
}
}
*/
processFile('../client/src/templates/helpers.js');
if (anyUpdatesToResDict) {
console.log(`Updating ${localeFile}`);
fs.writeFileSync(localeFile, JSON.stringify(resDict));
}
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
run();
/*
console.log('This script does modifications in the source tree. You should first commit all your files in git before proceeding.');
rl.question('To proceed type YES: ', (answer) => {
if (answer === 'YES') {
run();
}
rl.close();
});
*/

46
locales/package-lock.json generated Normal file
View file

@ -0,0 +1,46 @@
{
"name": "mailtrain-locales-extractor",
"version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"acorn": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz",
"integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg=="
},
"acorn-jsx": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.0.tgz",
"integrity": "sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg=="
},
"camelcase": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="
},
"ellipsize": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ellipsize/-/ellipsize-0.1.0.tgz",
"integrity": "sha1-nUNoLUS5GtFuvYQmisEDFwplU/g="
},
"graceful-fs": {
"version": "4.1.15",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"klaw-sync": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
"requires": {
"graceful-fs": "^4.1.11"
}
},
"slugify": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.3.tgz",
"integrity": "sha512-aFvcXobuowA7RqU4IBVJvqmhkREDIqsj4oIJKk6JuZ5EO1PCwtAAwDCl8TdsMs4J9zCoDAVkB9FLUElDjNcRSg=="
}
}
}

21
locales/package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "mailtrain-locales-extractor",
"private": true,
"version": "2.0.0",
"description": "Extractor for t-functions in the code (client and server)",
"main": "extract.js",
"scripts": {},
"license": "GPL-3.0",
"homepage": "https://mailtrain.org/",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"acorn": "^6.0.4",
"acorn-jsx": "^5.0.0",
"camelcase": "^5.0.0",
"ellipsize": "^0.1.0",
"klaw-sync": "^6.0.0",
"slugify": "^1.3.3"
}
}

7
server/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
/config/development.*
/config/production.*
/config/test.*
/workers/reports/config/development.*
/workers/reports/config/production.*
/workers/reports/config/test.*
/files

29
server/Gruntfile.js Normal file
View file

@ -0,0 +1,29 @@
'use strict';
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
eslint: {
all: [
'lib/**/*.js',
'models/**/*.js',
'routes/**/*.js',
'services/**/*.js',
'lib/**/*.js',
'test/**/*.js',
'workers/**/*.js',
'app-builder.js',
'index.js',
'Gruntfile.js',
]
}
});
// Load the plugin(s)
grunt.loadNpmTasks('grunt-eslint');
grunt.task.loadTasks('tasks');
// Tasks
grunt.registerTask('default', ['eslint']);
};

View file

@ -57,10 +57,10 @@ const settingsRest = require('./routes/rest/settings');
const index = require('./routes/index');
const interoperableErrors = require('./shared/interoperable-errors');
const interoperableErrors = require('../shared/interoperable-errors');
const { getTrustedUrl } = require('./lib/urls');
const { AppType } = require('./shared/app');
const { AppType } = require('../shared/app');
hbs.registerPartials(__dirname + '/views/partials');
hbs.registerPartials(__dirname + '/views/subscription/partials/');

24
server/config/test.toml Normal file
View file

@ -0,0 +1,24 @@
[www]
port=3000
[mysql]
user="mailtrain_test"
password="bahquaiphoor"
database="mailtrain_test"
[testServer]
enabled=true
[seleniumWebDriver]
browser="phantomjs"
[ldap]
# enable to use ldap user backend
enabled=false
host="localhost"
port=3002
baseDN="ou=users,dc=example"
filter="(|(username={{username}})(mail={{username}}))"
#Username field in LDAP (uid/cn/username)
uidTag="username"
# nameTag identifies the attribute to be used for user's full name
nameTag="username"
passwordresetlink="xxx"
[reports]
enabled=true

View file

@ -18,7 +18,7 @@ const executor = require('./lib/executor');
const privilegeHelpers = require('./lib/privilege-helpers');
const knex = require('./lib/knex');
const shares = require('./models/shares');
const { AppType } = require('./shared/app');
const { AppType } = require('../shared/app');
const trustedPort = config.www.trustedPort;
const sandboxPort = config.www.sandboxPort;

View file

@ -1,23 +1,23 @@
'use strict';
const config = require('config');
const mailers = require('../lib/mailers');
const knex = require('../lib/knex');
const mailers = require('./mailers');
const knex = require('./knex');
const subscriptions = require('../models/subscriptions');
const contextHelpers = require('../lib/context-helpers');
const contextHelpers = require('./context-helpers');
const campaigns = require('../models/campaigns');
const templates = require('../models/templates');
const lists = require('../models/lists');
const fields = require('../models/fields');
const sendConfigurations = require('../models/send-configurations');
const links = require('../models/links');
const {CampaignSource, CampaignType} = require('../shared/campaigns');
const {SubscriptionStatus} = require('../shared/lists');
const tools = require('../lib/tools');
const {CampaignSource, CampaignType} = require('../../shared/campaigns');
const {SubscriptionStatus} = require('../../shared/lists');
const tools = require('./tools');
const request = require('request-promise');
const files = require('../models/files');
const htmlToText = require('html-to-text');
const {getPublicUrl} = require('../lib/urls');
const {getPublicUrl} = require('./urls');
const blacklist = require('../models/blacklist');
const libmime = require('libmime');

View file

@ -1,6 +1,6 @@
'use strict';
const knex = require('../lib/knex');
const knex = require('./knex');
function getRequestContext(req) {
const context = {

View file

@ -1,7 +1,7 @@
'use strict';
const knex = require('./knex');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const entitySettings = require('./entity-settings');
const shares = require('../models/shares');
const { enforce } = require('./helpers');

View file

@ -1,6 +1,6 @@
'use strict';
const knex = require('../lib/knex');
const knex = require('./knex');
const entitySettings = require('./entity-settings');
async function ajaxListTx(tx, params, queryFun, columns, options) {

View file

@ -4,7 +4,7 @@ const knex = require('./knex');
const fork = require('child_process').fork;
const log = require('./log');
const path = require('path');
const {ImportStatus, RunStatus} = require('../shared/imports');
const {ImportStatus, RunStatus} = require('../../shared/imports');
let messageTid = 0;
let importerProcess;

View file

@ -2,7 +2,7 @@
const config = require('config');
const knex = require('knex')({
const knex = require('server/lib/knex')({
client: 'mysql2',
connection: config.mysql,
migrations: {

View file

@ -2,7 +2,7 @@
const { enforce } = require('./helpers');
const shares = require('../models/shares');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
async function validateEntity(tx, entity) {
enforce(entity.namespace, 'Entity namespace not set');

View file

@ -1,6 +1,6 @@
'use strict';
const nodeify = require('nodeify');
const nodeify = require('server/lib/nodeify');
module.exports.nodeifyPromise = nodeify;

View file

@ -5,7 +5,7 @@ const log = require('./log');
const _ = require('./translate')._;
const util = require('util');
const passport = require('passport');
const passport = require('server/lib/passport');
const LocalStrategy = require('passport-local').Strategy;
const csrf = require('csurf');
@ -13,7 +13,7 @@ const bodyParser = require('body-parser');
const users = require('../models/users');
const { nodeifyFunction, nodeifyPromise } = require('./nodeify');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const contextHelpers = require('./context-helpers');
let authMode = 'local';

View file

@ -3,7 +3,7 @@
const log = require('./log');
const reports = require('../models/reports');
const executor = require('./executor');
const contextHelpers = require('../lib/context-helpers');
const contextHelpers = require('./context-helpers');
let runningWorkersCount = 0;
let maxWorkersCount = 1;

View file

@ -3,8 +3,8 @@
const fork = require('child_process').fork;
const log = require('./log');
const path = require('path');
const knex = require('../lib/knex');
const {CampaignStatus} = require('../shared/campaigns');
const knex = require('./knex');
const {CampaignStatus} = require('../../shared/campaigns');
let messageTid = 0;
let senderProcess;

View file

@ -4,10 +4,10 @@ const log = require('npmlog');
const fields = require('../models/fields');
const settings = require('../models/settings');
const {getTrustedUrl, getPublicUrl} = require('./urls');
const { tUI } = require('./translate');
const { tUI, tMark } = require('./translate');
const util = require('util');
const contextHelpers = require('./context-helpers');
const {getFieldColumn} = require('../shared/lists');
const {getFieldColumn} = require('../../shared/lists');
const forms = require('../models/forms');
const mailers = require('./mailers');
@ -26,7 +26,7 @@ async function sendSubscriptionConfirmed(lang, list, email, subscription) {
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
};
await _sendMail(list, email, 'subscription_confirmed', lang, 'subscription.confirmed', relativeUrls, subscription);
await _sendMail(list, email, 'subscription_confirmed', lang, tMark('subscription.confirmed'), relativeUrls, subscription);
}
async function sendAlreadySubscribed(lang, list, email, subscription) {
@ -34,35 +34,35 @@ async function sendAlreadySubscribed(lang, list, email, subscription) {
preferencesUrl: '/subscription/' + list.cid + '/manage/' + subscription.cid,
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
};
await _sendMail(list, email, 'already_subscribed', lang, 'subscription.alreadyRegistered', relativeUrls, subscription);
await _sendMail(list, email, 'already_subscribed', lang, tMark('subscription.alreadyRegistered'), relativeUrls, subscription);
}
async function sendConfirmAddressChange(lang, list, email, cid, subscription) {
const relativeUrls = {
confirmUrl: '/subscription/confirm/change-address/' + cid
};
await _sendMail(list, email, 'confirm_address_change', lang, 'subscription.confirmEmailChange', relativeUrls, subscription);
await _sendMail(list, email, 'confirm_address_change', lang, tMark('subscription.confirmEmailChange'), relativeUrls, subscription);
}
async function sendConfirmSubscription(lang, list, email, cid, subscription) {
const relativeUrls = {
confirmUrl: '/subscription/confirm/subscribe/' + cid
};
await _sendMail(list, email, 'confirm_subscription', lang, 'subscription.confirmSubscription', relativeUrls, subscription);
await _sendMail(list, email, 'confirm_subscription', lang, tMark('subscription.confirmSubscription'), relativeUrls, subscription);
}
async function sendConfirmUnsubscription(lang, list, email, cid, subscription) {
const relativeUrls = {
confirmUrl: '/subscription/confirm/unsubscribe/' + cid
};
await _sendMail(list, email, 'confirm_unsubscription', lang, 'subscription.confirmUnsubscription', relativeUrls, subscription);
await _sendMail(list, email, 'confirm_unsubscription', lang, tMark('subscription.confirmUnsubscription'), relativeUrls, subscription);
}
async function sendUnsubscriptionConfirmed(lang, list, email, subscription) {
const relativeUrls = {
subscribeUrl: '/subscription/' + list.cid + '?cid=' + subscription.cid
};
await _sendMail(list, email, 'unsubscription_confirmed', lang, 'subscription.unsubscriptionConfirmed', relativeUrls, subscription);
await _sendMail(list, email, 'unsubscription_confirmed', lang, tMark('subscription.unsubscriptionConfirmed'), relativeUrls, subscription);
}
function getDisplayName(flds, subscription) {
@ -148,7 +148,7 @@ async function _sendMail(list, email, template, language, subjectKey, relativeUr
name: getDisplayName(flds, subscription),
address: email
},
subject: tUI(language, subjectKey, { list: list.name }),
subject: tUI(subjectKey, language, { list: list.name }),
encryptionKeys
}, {
html,

View file

@ -93,7 +93,7 @@ async function validateEmail(address) {
function validateEmailGetMessage(result, address, language) {
let t;
if (language) {
t = (key, args) => tUI(language, key, args);
t = (key, args) => tUI(key, language, args);
} else {
t = (key, args) => tLog(key, args);
}

View file

@ -34,7 +34,7 @@ function tLog(key, args) {
return JSON.stringify([key, args]);
}
function tUI(lang, key, args) {
function tUI(key, lang, args) {
if (!args) {
args = {};
}
@ -42,5 +42,10 @@ function tUI(lang, key, args) {
return i18n.t(key, { ...args, defaultValue, lng: lang });
}
function tMark(key) {
return key;
}
module.exports.tLog = tLog;
module.exports.tUI = tUI;
module.exports.tUI = tUI;
module.exports.tMark = tMark;

View file

@ -2,7 +2,7 @@
const config = require('config');
const urllib = require('url');
const {anonymousRestrictedAccessToken} = require('../shared/urls');
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
function getTrustedUrlBase() {
return urllib.resolve(config.www.trustedUrlBase, '');

View file

@ -3,17 +3,17 @@
const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shortid = require('shortid');
const { enforce, filterObject } = require('../lib/helpers');
const shares = require('./shares');
const namespaceHelpers = require('../lib/namespace-helpers');
const files = require('./files');
const templates = require('./templates');
const { CampaignStatus, CampaignSource, CampaignType, getSendConfigurationPermissionRequiredForSend} = require('../shared/campaigns');
const { CampaignStatus, CampaignSource, CampaignType, getSendConfigurationPermissionRequiredForSend} = require('../../shared/campaigns');
const sendConfigurations = require('./send-configurations');
const triggers = require('./triggers');
const {SubscriptionStatus} = require('../shared/lists');
const {SubscriptionStatus} = require('../../shared/lists');
const subscriptions = require('./subscriptions');
const segments = require('./segments');
const senders = require('../lib/senders');

View file

@ -5,17 +5,17 @@ const hasher = require('node-object-hash')();
const slugify = require('slugify');
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const validators = require('../shared/validators');
const validators = require('../../shared/validators');
const shortid = require('shortid');
const segments = require('./segments');
const { formatDate, formatBirthday, parseDate, parseBirthday } = require('../shared/date');
const { getFieldColumn } = require('../shared/lists');
const { formatDate, formatBirthday, parseDate, parseBirthday } = require('../../shared/date');
const { getFieldColumn } = require('../../shared/lists');
const { cleanupFromPost } = require('../lib/helpers');
const Handlebars = require('handlebars');
const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls');
const { getMergeTagsForBases } = require('../shared/templates');
const { getMergeTagsForBases } = require('../../shared/templates');
const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']);

View file

@ -6,7 +6,7 @@ const dtHelpers = require('../lib/dt-helpers');
const shares = require('./shares');
const fs = require('fs-extra-promise');
const path = require('path');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const entitySettings = require('../lib/entity-settings');
const {getPublicUrl} = require('../lib/urls');

View file

@ -4,7 +4,7 @@ const knex = require('../lib/knex');
const { enforce, filterObject } = require('../lib/helpers');
const hasher = require('node-object-hash')();
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const namespaceHelpers = require('../lib/namespace-helpers');
const bluebird = require('bluebird');

View file

@ -3,7 +3,7 @@
const knex = require('../lib/knex');
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
async function getById(context, listId, importId, id) {

View file

@ -4,9 +4,9 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const {ImportSource, MappingType, ImportStatus, RunStatus, prepFinished, prepFinishedAndNotInProgress, runInProgress} = require('../shared/imports');
const {ImportSource, MappingType, ImportStatus, RunStatus, prepFinished, prepFinishedAndNotInProgress, runInProgress} = require('../../shared/imports');
const fs = require('fs-extra-promise');
const path = require('path');
const importer = require('../lib/importer');

View file

@ -5,7 +5,7 @@ const hasher = require('node-object-hash')();
const dtHelpers = require('../lib/dt-helpers');
const shortid = require('shortid');
const { enforce, filterObject } = require('../lib/helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const namespaceHelpers = require('../lib/namespace-helpers');
const fields = require('./fields');
@ -14,7 +14,7 @@ const imports = require('./imports');
const entitySettings = require('../lib/entity-settings');
const dependencyHelpers = require('../lib/dependency-helpers');
const UnsubscriptionMode = require('../shared/lists').UnsubscriptionMode;
const UnsubscriptionMode = require('../../shared/lists').UnsubscriptionMode;
const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name', 'listunsubscribe_disabled']);

View file

@ -4,7 +4,7 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const namespaceHelpers = require('../lib/namespace-helpers');
const shares = require('./shares');
const files = require('./files');

View file

@ -3,7 +3,7 @@
const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const entitySettings = require('../lib/entity-settings');
const namespaceHelpers = require('../lib/namespace-helpers');

View file

@ -4,7 +4,7 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const namespaceHelpers = require('../lib/namespace-helpers');
const shares = require('./shares');
const reports = require('./reports');

View file

@ -4,14 +4,14 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const fields = require('./fields');
const namespaceHelpers = require('../lib/namespace-helpers');
const shares = require('./shares');
const reportHelpers = require('../lib/report-helpers');
const fs = require('fs-extra-promise');
const ReportState = require('../shared/reports').ReportState;
const ReportState = require('../../shared/reports').ReportState;
const allowedKeys = new Set(['name', 'description', 'report_template', 'params', 'namespace']);

View file

@ -2,7 +2,7 @@
const knex = require('../lib/knex');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const { enforce, filterObject } = require('../lib/helpers');
const hasher = require('node-object-hash')();
@ -11,7 +11,7 @@ const fields = require('./fields');
const subscriptions = require('./subscriptions');
const dependencyHelpers = require('../lib/dependency-helpers');
const { parseDate, parseBirthday, DateFormat } = require('../shared/date');
const { parseDate, parseBirthday, DateFormat } = require('../../shared/date');
const allowedKeys = new Set(['name', 'settings']);

View file

@ -5,10 +5,10 @@ const hasher = require('node-object-hash')();
const dtHelpers = require('../lib/dt-helpers');
const shortid = require('shortid');
const { enforce, filterObject } = require('../lib/helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const namespaceHelpers = require('../lib/namespace-helpers');
const {MailerType, getSystemSendConfigurationId} = require('../shared/send-configurations');
const {MailerType, getSystemSendConfigurationId} = require('../../shared/send-configurations');
const contextHelpers = require('../lib/context-helpers');
const mailers = require('../lib/mailers');
const senders = require('../lib/senders');

View file

@ -5,10 +5,10 @@ const config = require('config');
const { enforce } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const entitySettings = require('../lib/entity-settings');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const log = require('../lib/log');
const {getGlobalNamespaceId} = require('../shared/namespaces');
const {getAdminId} = require('../shared/users');
const {getGlobalNamespaceId} = require('../../shared/namespaces');
const {getAdminId} = require('../../shared/users');
// TODO: This would really benefit from some permission cache connected to rebuildPermissions
// A bit of the problem is that the cache would have to expunged as the result of other processes modifying entites/permissions

View file

@ -4,14 +4,14 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const shortid = require('shortid');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const fields = require('./fields');
const { SubscriptionStatus, getFieldColumn } = require('../shared/lists');
const { SubscriptionStatus, getFieldColumn } = require('../../shared/lists');
const segments = require('./segments');
const { enforce, filterObject } = require('../lib/helpers');
const moment = require('moment');
const { formatDate, formatBirthday } = require('../shared/date');
const { formatDate, formatBirthday } = require('../../shared/date');
const crypto = require('crypto');
const campaigns = require('./campaigns');
const lists = require('./lists');

View file

@ -4,7 +4,7 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const namespaceHelpers = require('../lib/namespace-helpers');
const shares = require('./shares');
const reports = require('./reports');

View file

@ -4,9 +4,9 @@ const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const shares = require('./shares');
const {EntityVals, EventVals, Entity} = require('../shared/triggers');
const {EntityVals, EventVals, Entity} = require('../../shared/triggers');
const campaigns = require('./campaigns');
const allowedKeys = new Set(['name', 'description', 'entity', 'event', 'seconds', 'enabled', 'source_campaign']);

View file

@ -4,8 +4,8 @@ const config = require('config');
const knex = require('../lib/knex');
const hasher = require('node-object-hash')();
const { enforce, filterObject } = require('../lib/helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const passwordValidator = require('../shared/password-validator')();
const interoperableErrors = require('../../shared/interoperable-errors');
const passwordValidator = require('../../shared/password-validator')();
const dtHelpers = require('../lib/dt-helpers');
const tools = require('../lib/tools');
const crypto = require('crypto');
@ -310,7 +310,7 @@ async function sendPasswordReset(language, usernameOrEmail) {
to: {
address: user.email
},
subject: tUI(language, 'account.passwordChangeRequest')
subject: tUI('account.passwordChangeRequest', language)
}, {
html: 'emails/password-reset-html.hbs',
text: 'emails/password-reset-text.hbs',

View file

@ -907,11 +907,6 @@
}
}
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
},
"browser-process-hrtime": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
@ -1325,24 +1320,6 @@
}
}
},
"compressjs": {
"version": "github:openpgpjs/compressjs#bfbb371a34d1750afa34bfa49156461acdab79a9",
"from": "github:openpgpjs/compressjs",
"requires": {
"amdefine": "~1.0.0",
"commander": "~2.8.1"
},
"dependencies": {
"commander": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": ">= 1.0.0"
}
}
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1888,19 +1865,6 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"elliptic": {
"version": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"from": "github:openpgpjs/elliptic",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"email-addresses": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.0.1.tgz",
@ -2906,11 +2870,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2923,15 +2889,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -3034,7 +3003,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -3044,6 +3014,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3056,17 +3027,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -3083,6 +3057,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3155,7 +3130,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -3165,6 +3141,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3270,6 +3247,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3569,11 +3547,6 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
},
"graceful-readlink": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
},
"growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
@ -3959,16 +3932,6 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"homedir-polyfill": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz",
@ -5415,11 +5378,6 @@
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -6271,6 +6229,35 @@
"ieee754": "^1.1.4"
}
},
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": ">= 1.0.0"
}
},
"compressjs": {
"version": "github:openpgpjs/compressjs#bfbb371a34d1750afa34bfa49156461acdab79a9",
"from": "github:openpgpjs/compressjs#bfbb371a34d1750afa34bfa49156461acdab79a9",
"requires": {
"amdefine": "~1.0.0",
"commander": "~2.8.1"
}
},
"elliptic": {
"version": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"from": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"openpgp": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/openpgp/-/openpgp-3.0.11.tgz",
@ -6281,8 +6268,6 @@
"asn1.js": "^5.0.0",
"bn.js": "^4.11.8",
"buffer": "^5.0.8",
"compressjs": "github:openpgpjs/compressjs#bfbb371a34d1750afa34bfa49156461acdab79a9",
"elliptic": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"hash.js": "^1.1.3",
"node-fetch": "^1.7.3",
"node-localstorage": "~1.3.0",
@ -7466,22 +7451,18 @@
"requires": {
"@mattiasbuelens/web-streams-polyfill": "0.1.0-alpha.4",
"address-rfc2822": "^2.0.3",
"asmcrypto.js": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83",
"asn1.js": "^5.0.0",
"bn.js": "^4.11.8",
"buffer": "^5.0.8",
"elliptic": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"hash.js": "^1.1.3",
"node-fetch": "^2.1.2",
"node-localstorage": "~1.3.0",
"pako": "^1.0.6",
"seek-bzip": "github:openpgpjs/seek-bzip#3aca608ffedc055a1da1d898ecb244804ef32209",
"web-stream-tools": "github:openpgpjs/web-stream-tools#9ab800d46add161db496506d67338202ad0114ce"
"pako": "^1.0.6"
},
"dependencies": {
"asmcrypto.js": {
"version": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83",
"from": "github:openpgpjs/asmcrypto"
"from": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83"
},
"buffer": {
"version": "5.2.1",
@ -7492,10 +7473,42 @@
"ieee754": "^1.1.4"
}
},
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": ">= 1.0.0"
}
},
"elliptic": {
"version": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"from": "github:openpgpjs/elliptic#e187e706e11fa51bcd20e46e5119054be4e2a4a6",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"node-fetch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
"integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA=="
},
"seek-bzip": {
"version": "github:openpgpjs/seek-bzip#3aca608ffedc055a1da1d898ecb244804ef32209",
"from": "github:openpgpjs/seek-bzip#3aca608ffedc055a1da1d898ecb244804ef32209",
"requires": {
"commander": "~2.8.1"
}
},
"web-stream-tools": {
"version": "github:openpgpjs/web-stream-tools#9ab800d46add161db496506d67338202ad0114ce",
"from": "github:openpgpjs/web-stream-tools#9ab800d46add161db496506d67338202ad0114ce"
}
}
},
@ -8354,23 +8367,6 @@
"xmlchars": "^1.3.1"
}
},
"seek-bzip": {
"version": "github:openpgpjs/seek-bzip#3aca608ffedc055a1da1d898ecb244804ef32209",
"from": "github:openpgpjs/seek-bzip",
"requires": {
"commander": "~2.8.1"
},
"dependencies": {
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"requires": {
"graceful-readlink": ">= 1.0.0"
}
}
}
},
"selenium-webdriver": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
@ -9420,10 +9416,6 @@
}
}
},
"web-stream-tools": {
"version": "github:openpgpjs/web-stream-tools#9ab800d46add161db496506d67338202ad0114ce",
"from": "github:openpgpjs/web-stream-tools"
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",

View file

@ -2,10 +2,9 @@
"name": "mailtrain",
"private": true,
"version": "2.0.0",
"description": "Self hosted email newsletter app",
"description": "Self hosted email newsletter app - server",
"main": "index.js",
"scripts": {
"test": "grunt",
"start": "node index.js",
"sqlinit": "node setup/sql/init.js",
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain${DUMP_NAME_SUFFIX}.sql",

View file

@ -5,13 +5,13 @@ const lists = require('../models/lists');
const tools = require('../lib/tools');
const blacklist = require('../models/blacklist');
const fields = require('../models/fields');
const { SubscriptionStatus, SubscriptionSource } = require('../shared/lists');
const { SubscriptionStatus, SubscriptionSource } = require('../../shared/lists');
const subscriptions = require('../models/subscriptions');
const confirmations = require('../models/confirmations');
const log = require('../lib/log');
const router = require('../lib/router-async').create();
const mailHelpers = require('../lib/subscription-mail-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const contextHelpers = require('../lib/context-helpers');
const shares = require('../models/shares');
const slugify = require('slugify');

View file

@ -4,7 +4,7 @@ const passport = require('../lib/passport');
const _ = require('../lib/translate')._;
const clientHelpers = require('../lib/client-helpers');
const { getTrustedUrl } = require('../lib/urls');
const { AppType } = require('../shared/app');
const { AppType } = require('../../shared/app');
const routerFactory = require('../lib/router-async');

View file

@ -4,7 +4,7 @@ const log = require('../lib/log');
const config = require('config');
const router = require('../lib/router-async').create();
const links = require('../models/links');
const interoperableErrors = require('../shared/interoperable-errors');
const interoperableErrors = require('../../shared/interoperable-errors');
const trackImg = Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64');

Some files were not shown because too many files have changed in this diff Show more