Merge branch 'development' of https://github.com/Mailtrain-org/mailtrain into transactional-mail-v2
This commit is contained in:
commit
e3e1e7a086
153 changed files with 10570 additions and 9267 deletions
|
@ -1,76 +1,74 @@
|
|||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const config = require('config');
|
||||
const log = require('./lib/log');
|
||||
const config = require("config");
|
||||
const log = require("./lib/log");
|
||||
|
||||
const express = require('express');
|
||||
const expressLocale = require('express-locale');
|
||||
const bodyParser = require('body-parser');
|
||||
const path = require('path');
|
||||
const favicon = require('serve-favicon');
|
||||
const logger = require('morgan');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const session = require('express-session');
|
||||
const flash = require('connect-flash');
|
||||
const hbs = require('hbs');
|
||||
const compression = require('compression');
|
||||
const passport = require('./lib/passport');
|
||||
const contextHelpers = require('./lib/context-helpers');
|
||||
const express = require("express");
|
||||
const expressLocale = require("express-locale");
|
||||
const bodyParser = require("body-parser");
|
||||
const path = require("path");
|
||||
const favicon = require("serve-favicon");
|
||||
const logger = require("morgan");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const session = require("express-session");
|
||||
const flash = require("connect-flash");
|
||||
const hbs = require("hbs");
|
||||
const compression = require("compression");
|
||||
const passport = require("./lib/passport");
|
||||
const contextHelpers = require("./lib/context-helpers");
|
||||
|
||||
const api = require('./routes/api');
|
||||
const api = require("./routes/api");
|
||||
|
||||
// These are routes for the new React-based client
|
||||
const reports = require('./routes/reports');
|
||||
const subscriptions = require('./routes/subscriptions');
|
||||
const subscription = require('./routes/subscription');
|
||||
const sandboxedMosaico = require('./routes/sandboxed-mosaico');
|
||||
const sandboxedCKEditor = require('./routes/sandboxed-ckeditor');
|
||||
const sandboxedGrapesJS = require('./routes/sandboxed-grapesjs');
|
||||
const sandboxedCodeEditor = require('./routes/sandboxed-codeeditor');
|
||||
const files = require('./routes/files');
|
||||
const links = require('./routes/links');
|
||||
const archive = require('./routes/archive');
|
||||
const webhooks = require('./routes/webhooks');
|
||||
const reports = require("./routes/reports");
|
||||
const subscriptions = require("./routes/subscriptions");
|
||||
const subscription = require("./routes/subscription");
|
||||
const sandboxedMosaico = require("./routes/sandboxed-mosaico");
|
||||
const sandboxedCKEditor = require("./routes/sandboxed-ckeditor");
|
||||
const sandboxedGrapesJS = require("./routes/sandboxed-grapesjs");
|
||||
const sandboxedCodeEditor = require("./routes/sandboxed-codeeditor");
|
||||
const files = require("./routes/files");
|
||||
const links = require("./routes/links");
|
||||
const archive = require("./routes/archive");
|
||||
const webhooks = require("./routes/webhooks");
|
||||
|
||||
const namespacesRest = require('./routes/rest/namespaces');
|
||||
const sendConfigurationsRest = require('./routes/rest/send-configurations');
|
||||
const usersRest = require('./routes/rest/users');
|
||||
const accountRest = require('./routes/rest/account');
|
||||
const reportTemplatesRest = require('./routes/rest/report-templates');
|
||||
const reportsRest = require('./routes/rest/reports');
|
||||
const campaignsRest = require('./routes/rest/campaigns');
|
||||
const triggersRest = require('./routes/rest/triggers');
|
||||
const listsRest = require('./routes/rest/lists');
|
||||
const formsRest = require('./routes/rest/forms');
|
||||
const fieldsRest = require('./routes/rest/fields');
|
||||
const importsRest = require('./routes/rest/imports');
|
||||
const importRunsRest = require('./routes/rest/import-runs');
|
||||
const sharesRest = require('./routes/rest/shares');
|
||||
const segmentsRest = require('./routes/rest/segments');
|
||||
const subscriptionsRest = require('./routes/rest/subscriptions');
|
||||
const templatesRest = require('./routes/rest/templates');
|
||||
const mosaicoTemplatesRest = require('./routes/rest/mosaico-templates');
|
||||
const blacklistRest = require('./routes/rest/blacklist');
|
||||
const editorsRest = require('./routes/rest/editors');
|
||||
const filesRest = require('./routes/rest/files');
|
||||
const settingsRest = require('./routes/rest/settings');
|
||||
const namespacesRest = require("./routes/rest/namespaces");
|
||||
const sendConfigurationsRest = require("./routes/rest/send-configurations");
|
||||
const usersRest = require("./routes/rest/users");
|
||||
const accountRest = require("./routes/rest/account");
|
||||
const reportTemplatesRest = require("./routes/rest/report-templates");
|
||||
const reportsRest = require("./routes/rest/reports");
|
||||
const campaignsRest = require("./routes/rest/campaigns");
|
||||
const triggersRest = require("./routes/rest/triggers");
|
||||
const listsRest = require("./routes/rest/lists");
|
||||
const formsRest = require("./routes/rest/forms");
|
||||
const fieldsRest = require("./routes/rest/fields");
|
||||
const importsRest = require("./routes/rest/imports");
|
||||
const importRunsRest = require("./routes/rest/import-runs");
|
||||
const sharesRest = require("./routes/rest/shares");
|
||||
const segmentsRest = require("./routes/rest/segments");
|
||||
const subscriptionsRest = require("./routes/rest/subscriptions");
|
||||
const templatesRest = require("./routes/rest/templates");
|
||||
const mosaicoTemplatesRest = require("./routes/rest/mosaico-templates");
|
||||
const blacklistRest = require("./routes/rest/blacklist");
|
||||
const editorsRest = require("./routes/rest/editors");
|
||||
const filesRest = require("./routes/rest/files");
|
||||
const settingsRest = require("./routes/rest/settings");
|
||||
|
||||
const index = require('./routes/index');
|
||||
const index = require("./routes/index");
|
||||
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
|
||||
const { getTrustedUrl } = require('./lib/urls');
|
||||
const { AppType } = require('../shared/app');
|
||||
const interoperableErrors = require("../shared/interoperable-errors");
|
||||
|
||||
const { getTrustedUrl } = require("./lib/urls");
|
||||
const { AppType } = require("../shared/app");
|
||||
|
||||
let isReady = false;
|
||||
function setReady() {
|
||||
isReady = true;
|
||||
isReady = true;
|
||||
}
|
||||
|
||||
|
||||
hbs.registerPartials(__dirname + '/views/partials');
|
||||
hbs.registerPartials(__dirname + '/views/subscription/partials/');
|
||||
hbs.registerPartials(__dirname + "/views/partials");
|
||||
hbs.registerPartials(__dirname + "/views/subscription/partials/");
|
||||
|
||||
/**
|
||||
* We need this helper to make sure that we consume flash messages only
|
||||
|
@ -78,337 +76,388 @@ hbs.registerPartials(__dirname + '/views/subscription/partials/');
|
|||
* in a situation where we consume a flash messages but then comes a redirect
|
||||
* and the message is never displayed
|
||||
*/
|
||||
hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer-arrow-callback
|
||||
if (typeof this.flash !== 'function') { // eslint-disable-line no-invalid-this
|
||||
return '';
|
||||
}
|
||||
hbs.registerHelper("flash_messages", function() {
|
||||
// eslint-disable-line prefer-arrow-callback
|
||||
if (typeof this.flash !== "function") {
|
||||
// eslint-disable-line no-invalid-this
|
||||
return "";
|
||||
}
|
||||
|
||||
const messages = this.flash(); // eslint-disable-line no-invalid-this
|
||||
const response = [];
|
||||
const messages = this.flash(); // eslint-disable-line no-invalid-this
|
||||
const response = [];
|
||||
|
||||
// group messages by type
|
||||
for (const key in messages) {
|
||||
let el = '<div class="alert alert-' + key + ' alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>';
|
||||
// group messages by type
|
||||
for (const key in messages) {
|
||||
let el =
|
||||
'<div class="alert alert-' +
|
||||
key +
|
||||
' alert-dismissible" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>';
|
||||
|
||||
if (key === 'danger') {
|
||||
el += '<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ';
|
||||
}
|
||||
if (key === "danger") {
|
||||
el +=
|
||||
'<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> ';
|
||||
}
|
||||
|
||||
let rows = [];
|
||||
let rows = [];
|
||||
|
||||
for (const message of messages[key]) {
|
||||
rows.push(hbs.handlebars.escapeExpression(message).replace(/(\r\n|\n|\r)/gm, '<br>'));
|
||||
}
|
||||
for (const message of messages[key]) {
|
||||
rows.push(
|
||||
hbs.handlebars
|
||||
.escapeExpression(message)
|
||||
.replace(/(\r\n|\n|\r)/gm, "<br>")
|
||||
);
|
||||
}
|
||||
|
||||
if (rows.length > 1) {
|
||||
el += '<p>' + rows.join('</p>\n<p>') + '</p>';
|
||||
} else {
|
||||
el += rows.join('');
|
||||
}
|
||||
if (rows.length > 1) {
|
||||
el += "<p>" + rows.join("</p>\n<p>") + "</p>";
|
||||
} else {
|
||||
el += rows.join("");
|
||||
}
|
||||
|
||||
el += '</div>';
|
||||
el += "</div>";
|
||||
|
||||
response.push(el);
|
||||
}
|
||||
response.push(el);
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(
|
||||
response.join('\n')
|
||||
);
|
||||
return new hbs.handlebars.SafeString(response.join("\n"));
|
||||
});
|
||||
|
||||
|
||||
|
||||
function createApp(appType) {
|
||||
const app = express();
|
||||
const app = express();
|
||||
|
||||
function install404Fallback(url) {
|
||||
app.use(url, (req, res, next) => {
|
||||
next(new interoperableErrors.NotFoundError());
|
||||
});
|
||||
function install404Fallback(url) {
|
||||
app.use(url, (req, res, next) => {
|
||||
next(new interoperableErrors.NotFoundError());
|
||||
});
|
||||
|
||||
app.use(url + '/*', (req, res, next) => {
|
||||
next(new interoperableErrors.NotFoundError());
|
||||
});
|
||||
}
|
||||
app.use(url + "/*", (req, res, next) => {
|
||||
next(new interoperableErrors.NotFoundError());
|
||||
});
|
||||
}
|
||||
|
||||
function useWith404Fallback(url, route) {
|
||||
app.use(url, route);
|
||||
install404Fallback(url);
|
||||
}
|
||||
function useWith404Fallback(url, route) {
|
||||
app.use(url, route);
|
||||
install404Fallback(url);
|
||||
}
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'hbs');
|
||||
// view engine setup
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "hbs");
|
||||
|
||||
// Handle proxies. Needed to resolve client IP
|
||||
if (config.www.proxy) {
|
||||
app.set('trust proxy', config.www.proxy);
|
||||
}
|
||||
// Handle proxies. Needed to resolve client IP
|
||||
if (config.www.proxy) {
|
||||
app.set("trust proxy", config.www.proxy);
|
||||
}
|
||||
|
||||
// Do not expose software used
|
||||
app.disable('x-powered-by');
|
||||
// Do not expose software used
|
||||
app.disable("x-powered-by");
|
||||
|
||||
app.use(compression());
|
||||
app.use(favicon(path.join(__dirname, '..', 'client', 'static', 'favicon.ico')));
|
||||
app.use(compression());
|
||||
app.use(
|
||||
favicon(path.join(__dirname, "..", "client", "static", "favicon.ico"))
|
||||
);
|
||||
|
||||
app.use(logger(config.www.log, {
|
||||
stream: {
|
||||
write: message => {
|
||||
message = (message || '').toString();
|
||||
if (message) {
|
||||
log.info('HTTP', message.replace('\n', '').trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
logger(config.www.log, {
|
||||
stream: {
|
||||
write: message => {
|
||||
message = (message || "").toString();
|
||||
if (message) {
|
||||
log.info("HTTP", message.replace("\n", "").trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
app.use(cookieParser());
|
||||
app.use(cookieParser());
|
||||
|
||||
if (config.redis.enabled) {
|
||||
const RedisStore = require('connect-redis')(session);
|
||||
if (config.redis.enabled) {
|
||||
const RedisStore = require("connect-redis")(session);
|
||||
|
||||
app.use(session({
|
||||
store: new RedisStore(config.redis),
|
||||
secret: config.www.secret,
|
||||
saveUninitialized: false,
|
||||
resave: false
|
||||
}));
|
||||
} else {
|
||||
app.use(session({
|
||||
store: false,
|
||||
secret: config.www.secret,
|
||||
saveUninitialized: false,
|
||||
resave: false
|
||||
}));
|
||||
}
|
||||
app.use(
|
||||
session({
|
||||
store: new RedisStore(config.redis),
|
||||
secret: config.www.secret,
|
||||
saveUninitialized: false,
|
||||
resave: false
|
||||
})
|
||||
);
|
||||
} else {
|
||||
app.use(
|
||||
session({
|
||||
store: false,
|
||||
secret: config.www.secret,
|
||||
saveUninitialized: false,
|
||||
resave: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
app.use(expressLocale({
|
||||
priority: ['query', 'cookie', 'accept-language', 'default'],
|
||||
query: {
|
||||
name: 'locale'
|
||||
},
|
||||
cookie: {
|
||||
name: 'i18nextLng'
|
||||
},
|
||||
default: config.defaultLanguage
|
||||
}));
|
||||
app.use(
|
||||
expressLocale({
|
||||
priority: ["query", "cookie", "accept-language", "default"],
|
||||
query: {
|
||||
name: "locale"
|
||||
},
|
||||
cookie: {
|
||||
name: "i18nextLng"
|
||||
},
|
||||
default: config.defaultLanguage
|
||||
})
|
||||
);
|
||||
|
||||
app.use(flash());
|
||||
app.use(flash());
|
||||
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
limit: config.www.postSize
|
||||
}));
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: true,
|
||||
limit: config.www.postSize
|
||||
})
|
||||
);
|
||||
|
||||
app.use(bodyParser.text({
|
||||
limit: config.www.postSize
|
||||
}));
|
||||
app.use(
|
||||
bodyParser.text({
|
||||
limit: config.www.postSize
|
||||
})
|
||||
);
|
||||
|
||||
app.use(bodyParser.json({
|
||||
limit: config.www.postSize
|
||||
}));
|
||||
app.use(
|
||||
bodyParser.json({
|
||||
limit: config.www.postSize
|
||||
})
|
||||
);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (isReady) {
|
||||
next();
|
||||
} else {
|
||||
res.status(500);
|
||||
res.render("error", {
|
||||
message: "Mailtrain is starting. Try again after a few seconds.",
|
||||
error: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (isReady) {
|
||||
next();
|
||||
} else {
|
||||
res.status(500);
|
||||
res.render('error', {
|
||||
message: 'Mailtrain is starting. Try again after a few seconds.',
|
||||
error: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (appType === AppType.TRUSTED) {
|
||||
passport.setupRegularAuth(app);
|
||||
} else if (appType === AppType.SANDBOXED) {
|
||||
app.use(passport.tryAuthByRestrictedAccessToken);
|
||||
}
|
||||
|
||||
if (appType === AppType.TRUSTED) {
|
||||
passport.setupRegularAuth(app);
|
||||
} else if (appType === AppType.SANDBOXED) {
|
||||
app.use(passport.tryAuthByRestrictedAccessToken);
|
||||
}
|
||||
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
|
||||
// Endpoint under /api are authenticated by access token
|
||||
app.all("/api/*", passport.authByAccessToken);
|
||||
}
|
||||
|
||||
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
|
||||
// Endpoint under /api are authenticated by access token
|
||||
app.all('/api/*', passport.authByAccessToken);
|
||||
}
|
||||
useWith404Fallback(
|
||||
"/static",
|
||||
express.static(path.join(__dirname, "..", "client", "static"))
|
||||
);
|
||||
useWith404Fallback(
|
||||
"/client",
|
||||
express.static(path.join(__dirname, "..", "client", "dist"))
|
||||
);
|
||||
|
||||
useWith404Fallback('/static', express.static(path.join(__dirname, '..', 'client', 'static')));
|
||||
useWith404Fallback('/client', express.static(path.join(__dirname, '..', 'client', 'dist')));
|
||||
useWith404Fallback(
|
||||
"/static-npm/fontawesome",
|
||||
express.static(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"node_modules",
|
||||
"@fortawesome",
|
||||
"fontawesome-free",
|
||||
"webfonts"
|
||||
)
|
||||
)
|
||||
);
|
||||
useWith404Fallback(
|
||||
"/static-npm/jquery.min.js",
|
||||
express.static(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"node_modules",
|
||||
"jquery",
|
||||
"dist",
|
||||
"jquery.min.js"
|
||||
)
|
||||
)
|
||||
);
|
||||
useWith404Fallback(
|
||||
"/static-npm/popper.min.js",
|
||||
express.static(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"node_modules",
|
||||
"popper.js",
|
||||
"dist",
|
||||
"umd",
|
||||
"popper.min.js"
|
||||
)
|
||||
)
|
||||
);
|
||||
useWith404Fallback(
|
||||
"/static-npm/bootstrap.min.js",
|
||||
express.static(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"node_modules",
|
||||
"bootstrap",
|
||||
"dist",
|
||||
"js",
|
||||
"bootstrap.min.js"
|
||||
)
|
||||
)
|
||||
);
|
||||
useWith404Fallback(
|
||||
"/static-npm/coreui.min.js",
|
||||
express.static(
|
||||
path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"node_modules",
|
||||
"@coreui",
|
||||
"coreui",
|
||||
"dist",
|
||||
"js",
|
||||
"coreui.min.js"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
useWith404Fallback('/static-npm/fontawesome', express.static(path.join(__dirname, '..', 'client', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts')));
|
||||
useWith404Fallback('/static-npm/jquery.min.js', express.static(path.join(__dirname, '..', 'client', 'node_modules', 'jquery', 'dist', 'jquery.min.js')));
|
||||
useWith404Fallback('/static-npm/popper.min.js', express.static(path.join(__dirname, '..', 'client', 'node_modules', 'popper.js', 'dist', 'umd', 'popper.min.js')));
|
||||
useWith404Fallback('/static-npm/bootstrap.min.js', express.static(path.join(__dirname, '..', 'client', 'node_modules', 'bootstrap', 'dist', 'js', 'bootstrap.min.js')));
|
||||
useWith404Fallback('/static-npm/coreui.min.js', express.static(path.join(__dirname, '..', 'client', 'node_modules', '@coreui', 'coreui', 'dist', 'js', 'coreui.min.js')));
|
||||
// Make sure flash messages are available
|
||||
// Currently, flash messages are used only from routes/subscription.js
|
||||
app.use((req, res, next) => {
|
||||
res.locals.flash = req.flash.bind(req);
|
||||
next();
|
||||
});
|
||||
|
||||
// Marks the following endpoint to return JSON object when error occurs
|
||||
app.all("/api/*", (req, res, next) => {
|
||||
req.needsAPIJSONResponse = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// Make sure flash messages are available
|
||||
// Currently, flash messages are used only from routes/subscription.js
|
||||
app.use((req, res, next) => {
|
||||
res.locals.flash = req.flash.bind(req);
|
||||
next();
|
||||
});
|
||||
app.all("/rest/*", (req, res, next) => {
|
||||
req.needsRESTJSONResponse = true;
|
||||
next();
|
||||
});
|
||||
|
||||
// Marks the following endpoint to return JSON object when error occurs
|
||||
app.all('/api/*', (req, res, next) => {
|
||||
req.needsAPIJSONResponse = true;
|
||||
next();
|
||||
});
|
||||
// Initializes the request context to be used for authorization
|
||||
app.use((req, res, next) => {
|
||||
req.context = contextHelpers.getRequestContext(req);
|
||||
next();
|
||||
});
|
||||
|
||||
app.all('/rest/*', (req, res, next) => {
|
||||
req.needsRESTJSONResponse = true;
|
||||
next();
|
||||
});
|
||||
if (appType === AppType.PUBLIC) {
|
||||
useWith404Fallback("/subscription", subscription);
|
||||
useWith404Fallback("/links", links);
|
||||
useWith404Fallback("/archive", archive);
|
||||
useWith404Fallback("/files", files);
|
||||
}
|
||||
|
||||
// Initializes the request context to be used for authorization
|
||||
app.use((req, res, next) => {
|
||||
req.context = contextHelpers.getRequestContext(req);
|
||||
next();
|
||||
});
|
||||
useWith404Fallback("/mosaico", sandboxedMosaico.getRouter(appType));
|
||||
useWith404Fallback("/ckeditor", sandboxedCKEditor.getRouter(appType));
|
||||
useWith404Fallback("/grapesjs", sandboxedGrapesJS.getRouter(appType));
|
||||
useWith404Fallback("/codeeditor", sandboxedCodeEditor.getRouter(appType));
|
||||
|
||||
if (appType === AppType.PUBLIC) {
|
||||
useWith404Fallback('/subscription', subscription);
|
||||
useWith404Fallback('/links', links);
|
||||
useWith404Fallback('/archive', archive);
|
||||
useWith404Fallback('/files', files);
|
||||
}
|
||||
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
|
||||
useWith404Fallback("/subscriptions", subscriptions);
|
||||
useWith404Fallback("/webhooks", webhooks);
|
||||
|
||||
useWith404Fallback('/mosaico', sandboxedMosaico.getRouter(appType));
|
||||
useWith404Fallback('/ckeditor', sandboxedCKEditor.getRouter(appType));
|
||||
useWith404Fallback('/grapesjs', sandboxedGrapesJS.getRouter(appType));
|
||||
useWith404Fallback('/codeeditor', sandboxedCodeEditor.getRouter(appType));
|
||||
if (config.reports && config.reports.enabled === true) {
|
||||
useWith404Fallback("/rpts", reports); // This needs to be different from "reports", which is already used by the UI
|
||||
}
|
||||
|
||||
if (appType === AppType.TRUSTED || appType === AppType.SANDBOXED) {
|
||||
useWith404Fallback('/subscriptions', subscriptions);
|
||||
useWith404Fallback('/webhooks', webhooks);
|
||||
// API endpoints
|
||||
useWith404Fallback("/api", api);
|
||||
|
||||
if (config.reports && config.reports.enabled === true) {
|
||||
useWith404Fallback('/rpts', reports); // This needs to be different from "reports", which is already used by the UI
|
||||
}
|
||||
// REST endpoints
|
||||
app.use("/rest", namespacesRest);
|
||||
app.use("/rest", sendConfigurationsRest);
|
||||
app.use("/rest", usersRest);
|
||||
app.use("/rest", accountRest);
|
||||
app.use("/rest", campaignsRest);
|
||||
app.use("/rest", triggersRest);
|
||||
app.use("/rest", listsRest);
|
||||
app.use("/rest", formsRest);
|
||||
app.use("/rest", fieldsRest);
|
||||
app.use("/rest", importsRest);
|
||||
app.use("/rest", importRunsRest);
|
||||
app.use("/rest", sharesRest);
|
||||
app.use("/rest", segmentsRest);
|
||||
app.use("/rest", subscriptionsRest);
|
||||
app.use("/rest", templatesRest);
|
||||
app.use("/rest", mosaicoTemplatesRest);
|
||||
app.use("/rest", blacklistRest);
|
||||
app.use("/rest", editorsRest);
|
||||
app.use("/rest", filesRest);
|
||||
app.use("/rest", settingsRest);
|
||||
|
||||
// API endpoints
|
||||
useWith404Fallback('/api', api);
|
||||
if (config.reports && config.reports.enabled === true) {
|
||||
app.use("/rest", reportTemplatesRest);
|
||||
app.use("/rest", reportsRest);
|
||||
}
|
||||
install404Fallback("/rest");
|
||||
}
|
||||
|
||||
// REST endpoints
|
||||
app.use('/rest', namespacesRest);
|
||||
app.use('/rest', sendConfigurationsRest);
|
||||
app.use('/rest', usersRest);
|
||||
app.use('/rest', accountRest);
|
||||
app.use('/rest', campaignsRest);
|
||||
app.use('/rest', triggersRest);
|
||||
app.use('/rest', listsRest);
|
||||
app.use('/rest', formsRest);
|
||||
app.use('/rest', fieldsRest);
|
||||
app.use('/rest', importsRest);
|
||||
app.use('/rest', importRunsRest);
|
||||
app.use('/rest', sharesRest);
|
||||
app.use('/rest', segmentsRest);
|
||||
app.use('/rest', subscriptionsRest);
|
||||
app.use('/rest', templatesRest);
|
||||
app.use('/rest', mosaicoTemplatesRest);
|
||||
app.use('/rest', blacklistRest);
|
||||
app.use('/rest', editorsRest);
|
||||
app.use('/rest', filesRest);
|
||||
app.use('/rest', settingsRest);
|
||||
app.use("/", index.getRouter(appType));
|
||||
|
||||
if (config.reports && config.reports.enabled === true) {
|
||||
app.use('/rest', reportTemplatesRest);
|
||||
app.use('/rest', reportsRest);
|
||||
}
|
||||
install404Fallback('/rest');
|
||||
}
|
||||
app.use((err, req, res, next) => {
|
||||
if (!err) {
|
||||
return next();
|
||||
}
|
||||
|
||||
app.use('/', index.getRouter(appType));
|
||||
if (req.needsRESTJSONResponse) {
|
||||
const resp = {
|
||||
message: err.message,
|
||||
error: config.sendStacktracesToClient ? err : {}
|
||||
};
|
||||
|
||||
// Error handlers
|
||||
if (app.get('env') === 'development' || app.get('env') === 'test') {
|
||||
// development error handler
|
||||
// will print stacktrace
|
||||
app.use((err, req, res, next) => {
|
||||
if (!err) {
|
||||
return next();
|
||||
}
|
||||
if (err instanceof interoperableErrors.InteroperableError) {
|
||||
resp.type = err.type;
|
||||
resp.data = err.data;
|
||||
}
|
||||
|
||||
if (req.needsRESTJSONResponse) {
|
||||
const resp = {
|
||||
message: err.message,
|
||||
error: err
|
||||
};
|
||||
log.verbose("HTTP", err);
|
||||
res.status(err.status || 500).json(resp);
|
||||
} else if (req.needsAPIJSONResponse) {
|
||||
const resp = {
|
||||
error: err.message || err,
|
||||
data: []
|
||||
};
|
||||
|
||||
if (err instanceof interoperableErrors.InteroperableError) {
|
||||
resp.type = err.type;
|
||||
resp.data = err.data;
|
||||
}
|
||||
log.verbose("HTTP", err);
|
||||
return res.status(err.status || 500).json(resp);
|
||||
} else {
|
||||
// TODO: Render interoperable errors using a special client that does internationalization of the error message
|
||||
|
||||
res.status(err.status || 500).json(resp);
|
||||
if (err instanceof interoperableErrors.NotLoggedInError) {
|
||||
return res.redirect(
|
||||
getTrustedUrl("/login?next=" + encodeURIComponent(req.originalUrl))
|
||||
);
|
||||
} else {
|
||||
log.verbose("HTTP", err);
|
||||
res.status(err.status || 500);
|
||||
res.render("error", {
|
||||
message: err.message,
|
||||
error: config.sendStacktracesToClient ? err : {}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (req.needsAPIJSONResponse) {
|
||||
const resp = {
|
||||
error: err.message || err,
|
||||
data: []
|
||||
};
|
||||
|
||||
return res.status(err.status || 500).json(resp);
|
||||
|
||||
} else {
|
||||
if (err instanceof interoperableErrors.NotLoggedInError) {
|
||||
return res.redirect(getTrustedUrl('/login?next=' + encodeURIComponent(req.originalUrl)));
|
||||
} else {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} else {
|
||||
// production error handler
|
||||
// no stacktraces leaked to user
|
||||
app.use((err, req, res, next) => {
|
||||
if (!err) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.needsRESTJSONResponse) {
|
||||
const resp = {
|
||||
message: err.message,
|
||||
error: {}
|
||||
};
|
||||
|
||||
if (err instanceof interoperableErrors.InteroperableError) {
|
||||
resp.type = err.type;
|
||||
resp.data = err.data;
|
||||
}
|
||||
|
||||
res.status(err.status || 500).json(resp);
|
||||
|
||||
} else if (req.needsAPIJSONResponse) {
|
||||
const resp = {
|
||||
error: err.message || err,
|
||||
data: []
|
||||
};
|
||||
|
||||
return res.status(err.status || 500).json(resp);
|
||||
|
||||
} else {
|
||||
// TODO: Render interoperable errors using a special client that does internationalization of the error message
|
||||
|
||||
if (err instanceof interoperableErrors.NotLoggedInError) {
|
||||
return res.redirect(getTrustedUrl('/login?next=' + encodeURIComponent(req.originalUrl)));
|
||||
} else {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return app;
|
||||
return app;
|
||||
}
|
||||
|
||||
module.exports.createApp = createApp;
|
||||
|
|
|
@ -42,6 +42,7 @@ defaultLanguage: en-US
|
|||
# Enabled languages
|
||||
enabledLanguages:
|
||||
- en-US
|
||||
- es-ES
|
||||
- fk-FK
|
||||
|
||||
# Inject custom scripts in subscription/layout.mjml.hbs
|
||||
|
@ -72,6 +73,7 @@ redis:
|
|||
log:
|
||||
# silly|verbose|info|http|warn|error|silent
|
||||
level: info
|
||||
sendStacktracesToClient: false
|
||||
|
||||
www:
|
||||
# HTTP port to listen on for trusted requests (logged-in users)
|
||||
|
@ -217,6 +219,8 @@ builtinZoneMTA:
|
|||
redis: redis://localhost:6379/2
|
||||
log:
|
||||
level: warn
|
||||
processes: 2
|
||||
connections: 5
|
||||
|
||||
seleniumWebDriver:
|
||||
browser: phantomjs
|
||||
|
@ -228,7 +232,7 @@ roles:
|
|||
name: Global Master
|
||||
admin: true
|
||||
description: All permissions
|
||||
permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings, setupAutomation]
|
||||
permissions: [rebuildPermissions, createJavascriptWithROAccess, displayManageUsers, manageBlacklist, manageSettings, setupAutomation]
|
||||
rootNamespaceRole: master
|
||||
campaignsAdmin:
|
||||
name: Campaigns Admin
|
||||
|
|
|
@ -24,6 +24,7 @@ const { AppType } = require('../shared/app');
|
|||
const builtinZoneMta = require('./lib/builtin-zone-mta');
|
||||
|
||||
const { uploadedFilesDir } = require('./lib/file-helpers');
|
||||
const { filesDir } = require('./models/files');
|
||||
|
||||
const trustedPort = config.www.trustedPort;
|
||||
const sandboxPort = config.www.sandboxPort;
|
||||
|
@ -113,6 +114,7 @@ dbcheck(err => { // Check if database needs upgrading before starting the server
|
|||
startHTTPServer(AppType.SANDBOXED, 'sandbox', sandboxPort, () =>
|
||||
startHTTPServer(AppType.PUBLIC, 'public', publicPort, async () => {
|
||||
|
||||
await privilegeHelpers.ensureMailtrainDir(filesDir);
|
||||
await privilegeHelpers.ensureMailtrainDir(uploadedFilesDir);
|
||||
|
||||
privilegeHelpers.dropRootPrivileges();
|
||||
|
|
54
server/lib/activity-log.js
Normal file
54
server/lib/activity-log.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
async function _logActivity(typeId, data) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/*
|
||||
Extra data:
|
||||
|
||||
campaign:
|
||||
- status : CampaignStatus
|
||||
|
||||
list:
|
||||
- subscriptionId
|
||||
- subscriptionStatus : SubscriptionStatus
|
||||
- fieldId
|
||||
- segmentId
|
||||
- importId
|
||||
- importStatus : ImportStatus
|
||||
*/
|
||||
async function logEntityActivity(entityTypeId, activityType, entityId, extraData = {}) {
|
||||
const data = {
|
||||
...extraData,
|
||||
type: activityType,
|
||||
entity: entityId
|
||||
};
|
||||
|
||||
await _logActivity(entityTypeId, data);
|
||||
}
|
||||
|
||||
async function logCampaignTrackerActivity(activityType, campaignId, listId, subscriptionId, extraData = {}) {
|
||||
const data = {
|
||||
...extraData,
|
||||
type: activityType,
|
||||
campaign: campaignId,
|
||||
list: listId,
|
||||
subscription: subscriptionId
|
||||
};
|
||||
|
||||
await _logActivity('campaign_tracker', data);
|
||||
}
|
||||
|
||||
async function logBlacklistActivity(activityType, email) {
|
||||
const data = {
|
||||
type: activityType,
|
||||
email
|
||||
};
|
||||
|
||||
await _logActivity('blacklist', data);
|
||||
}
|
||||
|
||||
module.exports.logEntityActivity = logEntityActivity;
|
||||
module.exports.logBlacklistActivity = logBlacklistActivity;
|
||||
module.exports.logCampaignTrackerActivity = logCampaignTrackerActivity;
|
|
@ -108,8 +108,8 @@ async function createConfig() {
|
|||
default: {
|
||||
preferIPv6: false,
|
||||
ignoreIPv6: true,
|
||||
processes: 1,
|
||||
connections: 5,
|
||||
processes: config.builtinZoneMTA.processes,
|
||||
connections: config.builtinZoneMTA.connections,
|
||||
pool: 'default'
|
||||
}
|
||||
}
|
||||
|
|
32
server/lib/campaign-content.js
Normal file
32
server/lib/campaign-content.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
||||
|
||||
function convertText(text) {
|
||||
if (text) {
|
||||
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
||||
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
||||
|
||||
const encodedFromUrl = encodeURIComponent(fromUrl);
|
||||
const encodedToUrl = encodeURIComponent(toUrl);
|
||||
|
||||
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
||||
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
||||
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
sourceCustom.html = convertText(sourceCustom.html);
|
||||
sourceCustom.text = convertText(sourceCustom.text);
|
||||
|
||||
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.convertFileURLs = convertFileURLs;
|
|
@ -394,6 +394,18 @@ class CampaignSender {
|
|||
try {
|
||||
const info = await mailer.sendMassMail(mail);
|
||||
status = SubscriptionStatus.SUBSCRIBED;
|
||||
|
||||
/*
|
||||
ZoneMTA
|
||||
info.response: 250 Message queued as 1691ad7f7ae00080fd
|
||||
info.messageId: <e65c9386-e899-7d01-b21e-ec03c3a9d9b4@sathyasai.org>
|
||||
|
||||
Postal Mail Server
|
||||
info.response: 250 OK
|
||||
info.messageId: <xxxxxxxxx@xxx.xx> (postal messageId)
|
||||
*/
|
||||
|
||||
console.log(`response: ${info.response} messageId: ${info.messageId}`);
|
||||
response = info.response || info.messageId;
|
||||
|
||||
await knex('campaigns').where('id', campaign.id).increment('delivered');
|
||||
|
|
|
@ -1,151 +1,151 @@
|
|||
'use strict';
|
||||
|
||||
const ReplacementBehavior = {
|
||||
NONE: 1,
|
||||
REPLACE: 2,
|
||||
RENAME: 3
|
||||
};
|
||||
|
||||
const entityTypes = {
|
||||
namespace: {
|
||||
entitiesTable: 'namespaces',
|
||||
sharesTable: 'shares_namespace',
|
||||
permissionsTable: 'permissions_namespace',
|
||||
clientLink: id => `/namespaces/${id}`
|
||||
},
|
||||
list: {
|
||||
entitiesTable: 'lists',
|
||||
sharesTable: 'shares_list',
|
||||
permissionsTable: 'permissions_list',
|
||||
clientLink: id => `/lists/${id}`
|
||||
},
|
||||
customForm: {
|
||||
entitiesTable: 'custom_forms',
|
||||
sharesTable: 'shares_custom_form',
|
||||
permissionsTable: 'permissions_custom_form',
|
||||
clientLink: id => `/lists/forms/${id}`
|
||||
},
|
||||
campaign: {
|
||||
entitiesTable: 'campaigns',
|
||||
sharesTable: 'shares_campaign',
|
||||
permissionsTable: 'permissions_campaign',
|
||||
dependentPermissions: {
|
||||
extraColumns: ['parent'],
|
||||
getParent: entity => entity.parent
|
||||
},
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_campaign_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
attachment: {
|
||||
table: 'files_campaign_attachment',
|
||||
permissions: {
|
||||
view: 'viewAttachments',
|
||||
manage: 'manageAttachments'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.NONE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/campaigns/${id}`
|
||||
},
|
||||
template: {
|
||||
entitiesTable: 'templates',
|
||||
sharesTable: 'shares_template',
|
||||
permissionsTable: 'permissions_template',
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_template_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/${id}`
|
||||
},
|
||||
sendConfiguration: {
|
||||
entitiesTable: 'send_configurations',
|
||||
sharesTable: 'shares_send_configuration',
|
||||
permissionsTable: 'permissions_send_configuration',
|
||||
clientLink: id => `/send-configurations/${id}`
|
||||
},
|
||||
report: {
|
||||
entitiesTable: 'reports',
|
||||
sharesTable: 'shares_report',
|
||||
permissionsTable: 'permissions_report',
|
||||
clientLink: id => `/reports/${id}`
|
||||
},
|
||||
reportTemplate: {
|
||||
entitiesTable: 'report_templates',
|
||||
sharesTable: 'shares_report_template',
|
||||
permissionsTable: 'permissions_report_template',
|
||||
clientLink: id => `/reports/templates/${id}`
|
||||
},
|
||||
mosaicoTemplate: {
|
||||
entitiesTable: 'mosaico_templates',
|
||||
sharesTable: 'shares_mosaico_template',
|
||||
permissionsTable: 'permissions_mosaico_template',
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_mosaico_template_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
block: {
|
||||
table: 'files_mosaico_template_block',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/mosaico/${id}`
|
||||
},
|
||||
user: {
|
||||
entitiesTable: 'users',
|
||||
clientLink: id => `/users/${id}`
|
||||
}
|
||||
};
|
||||
|
||||
const entityTypesWithPermissions = {};
|
||||
for (const key in entityTypes) {
|
||||
if (entityTypes[key].permissionsTable) {
|
||||
entityTypesWithPermissions[key] = entityTypes[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getEntityTypes() {
|
||||
return entityTypes;
|
||||
}
|
||||
|
||||
function getEntityTypesWithPermissions() {
|
||||
return entityTypesWithPermissions;
|
||||
}
|
||||
|
||||
function getEntityType(entityTypeId) {
|
||||
const entityType = entityTypes[entityTypeId];
|
||||
|
||||
if (!entityType) {
|
||||
throw new Error(`Unknown entity type ${entityTypeId}`);
|
||||
}
|
||||
|
||||
return entityType
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEntityTypes,
|
||||
getEntityTypesWithPermissions,
|
||||
getEntityType,
|
||||
ReplacementBehavior
|
||||
'use strict';
|
||||
|
||||
const ReplacementBehavior = {
|
||||
NONE: 1,
|
||||
REPLACE: 2,
|
||||
RENAME: 3
|
||||
};
|
||||
|
||||
const entityTypes = {
|
||||
namespace: {
|
||||
entitiesTable: 'namespaces',
|
||||
sharesTable: 'shares_namespace',
|
||||
permissionsTable: 'permissions_namespace',
|
||||
clientLink: id => `/namespaces/${id}`
|
||||
},
|
||||
list: {
|
||||
entitiesTable: 'lists',
|
||||
sharesTable: 'shares_list',
|
||||
permissionsTable: 'permissions_list',
|
||||
clientLink: id => `/lists/${id}`
|
||||
},
|
||||
customForm: {
|
||||
entitiesTable: 'custom_forms',
|
||||
sharesTable: 'shares_custom_form',
|
||||
permissionsTable: 'permissions_custom_form',
|
||||
clientLink: id => `/lists/forms/${id}`
|
||||
},
|
||||
campaign: {
|
||||
entitiesTable: 'campaigns',
|
||||
sharesTable: 'shares_campaign',
|
||||
permissionsTable: 'permissions_campaign',
|
||||
dependentPermissions: {
|
||||
extraColumns: ['parent'],
|
||||
getParent: entity => entity.parent
|
||||
},
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_campaign_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
attachment: {
|
||||
table: 'files_campaign_attachment',
|
||||
permissions: {
|
||||
view: 'viewAttachments',
|
||||
manage: 'manageAttachments'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.NONE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/campaigns/${id}`
|
||||
},
|
||||
template: {
|
||||
entitiesTable: 'templates',
|
||||
sharesTable: 'shares_template',
|
||||
permissionsTable: 'permissions_template',
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_template_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/${id}`
|
||||
},
|
||||
sendConfiguration: {
|
||||
entitiesTable: 'send_configurations',
|
||||
sharesTable: 'shares_send_configuration',
|
||||
permissionsTable: 'permissions_send_configuration',
|
||||
clientLink: id => `/send-configurations/${id}`
|
||||
},
|
||||
report: {
|
||||
entitiesTable: 'reports',
|
||||
sharesTable: 'shares_report',
|
||||
permissionsTable: 'permissions_report',
|
||||
clientLink: id => `/reports/${id}`
|
||||
},
|
||||
reportTemplate: {
|
||||
entitiesTable: 'report_templates',
|
||||
sharesTable: 'shares_report_template',
|
||||
permissionsTable: 'permissions_report_template',
|
||||
clientLink: id => `/reports/templates/${id}`
|
||||
},
|
||||
mosaicoTemplate: {
|
||||
entitiesTable: 'mosaico_templates',
|
||||
sharesTable: 'shares_mosaico_template',
|
||||
permissionsTable: 'permissions_mosaico_template',
|
||||
files: {
|
||||
file: {
|
||||
table: 'files_mosaico_template_file',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
},
|
||||
block: {
|
||||
table: 'files_mosaico_template_block',
|
||||
permissions: {
|
||||
view: 'viewFiles',
|
||||
manage: 'manageFiles'
|
||||
},
|
||||
defaultReplacementBehavior: ReplacementBehavior.REPLACE
|
||||
}
|
||||
},
|
||||
clientLink: id => `/templates/mosaico/${id}`
|
||||
},
|
||||
user: {
|
||||
entitiesTable: 'users',
|
||||
clientLink: id => `/users/${id}`
|
||||
}
|
||||
};
|
||||
|
||||
const entityTypesWithPermissions = {};
|
||||
for (const key in entityTypes) {
|
||||
if (entityTypes[key].permissionsTable) {
|
||||
entityTypesWithPermissions[key] = entityTypes[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getEntityTypes() {
|
||||
return entityTypes;
|
||||
}
|
||||
|
||||
function getEntityTypesWithPermissions() {
|
||||
return entityTypesWithPermissions;
|
||||
}
|
||||
|
||||
function getEntityType(entityTypeId) {
|
||||
const entityType = entityTypes[entityTypeId];
|
||||
|
||||
if (!entityType) {
|
||||
throw new Error(`Unknown entity type ${entityTypeId}`);
|
||||
}
|
||||
|
||||
return entityType
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEntityTypes,
|
||||
getEntityTypesWithPermissions,
|
||||
getEntityType,
|
||||
ReplacementBehavior
|
||||
}
|
|
@ -5,6 +5,8 @@ const fork = require('child_process').fork;
|
|||
const log = require('./log');
|
||||
const path = require('path');
|
||||
const {ImportStatus, RunStatus} = require('../../shared/imports');
|
||||
const {ListActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('./activity-log');
|
||||
|
||||
let messageTid = 0;
|
||||
let importerProcess;
|
||||
|
@ -18,11 +20,17 @@ function spawn(callback) {
|
|||
log.verbose('Importer', 'Spawning importer process');
|
||||
|
||||
knex.transaction(async tx => {
|
||||
await tx('imports').where('status', ImportStatus.PREP_RUNNING).update({status: ImportStatus.PREP_SCHEDULED});
|
||||
await tx('imports').where('status', ImportStatus.PREP_STOPPING).update({status: ImportStatus.PREP_FAILED});
|
||||
const updateStatus = async (fromStatus, toStatus) => {
|
||||
for (const impt of await tx('imports').where('status', fromStatus).select(['id', 'list'])) {
|
||||
await tx('imports').where('id', impt.id).update({status: toStatus});
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: toStatus});
|
||||
}
|
||||
}
|
||||
|
||||
await tx('imports').where('status', ImportStatus.RUN_RUNNING).update({status: ImportStatus.RUN_SCHEDULED});
|
||||
await tx('imports').where('status', ImportStatus.RUN_STOPPING).update({status: ImportStatus.RUN_FAILED});
|
||||
await updateStatus(ImportStatus.PREP_RUNNING, ImportStatus.PREP_SCHEDULED);
|
||||
await updateStatus(ImportStatus.PREP_STOPPING, ImportStatus.PREP_FAILED);
|
||||
await updateStatus(ImportStatus.RUN_RUNNING, ImportStatus.RUN_SCHEDULED);
|
||||
await updateStatus(ImportStatus.RUN_STOPPING, ImportStatus.RUN_FAILED);
|
||||
|
||||
await tx('import_runs').where('status', RunStatus.RUNNING).update({status: RunStatus.SCHEDULED});
|
||||
await tx('import_runs').where('status', RunStatus.STOPPING).update({status: RunStatus.FAILED});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const log = require('npmlog');
|
||||
|
||||
log.level = config.log.level;
|
||||
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const log = require('npmlog');
|
||||
|
||||
log.level = config.log.level;
|
||||
|
||||
module.exports = log;
|
|
@ -173,7 +173,7 @@ async function _createTransport(sendConfiguration) {
|
|||
}
|
||||
};
|
||||
|
||||
if (mailerType === MailerType.ZONE_MTA || mailerSettings.zoneMTAType === ZoneMTAType.BUILTIN) {
|
||||
if (mailerType === MailerType.ZONE_MTA && mailerSettings.zoneMtaType === ZoneMTAType.BUILTIN) {
|
||||
transportOptions.host = config.builtinZoneMTA.host;
|
||||
transportOptions.port = config.builtinZoneMTA.port;
|
||||
transportOptions.secure = false;
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
const { enforce } = require('./helpers');
|
||||
const interoperableErrors = require('../../shared/interoperable-errors');
|
||||
const shares = require('../models/shares');
|
||||
|
||||
async function validateEntity(tx, entity) {
|
||||
enforce(entity.namespace, 'Entity namespace not set');
|
||||
if (!await tx('namespaces').where('id', entity.namespace).first()) {
|
||||
throw new interoperableErrors.NamespaceNotFoundError();
|
||||
}
|
||||
}
|
||||
|
||||
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
||||
if (existing.namespace !== entity.namespace) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
|
||||
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateEntity,
|
||||
validateMove
|
||||
'use strict';
|
||||
|
||||
const { enforce } = require('./helpers');
|
||||
const interoperableErrors = require('../../shared/interoperable-errors');
|
||||
const shares = require('../models/shares');
|
||||
|
||||
async function validateEntity(tx, entity) {
|
||||
enforce(entity.namespace, 'Entity namespace not set');
|
||||
if (!await tx('namespaces').where('id', entity.namespace).first()) {
|
||||
throw new interoperableErrors.NamespaceNotFoundError();
|
||||
}
|
||||
}
|
||||
|
||||
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
||||
if (existing.namespace !== entity.namespace) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
|
||||
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateEntity,
|
||||
validateMove
|
||||
};
|
|
@ -1,15 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const nodeify = require('nodeify');
|
||||
|
||||
module.exports.nodeifyPromise = nodeify;
|
||||
|
||||
module.exports.nodeifyFunction = (asyncFun) => {
|
||||
return (...args) => {
|
||||
const callback = args.pop();
|
||||
|
||||
const promise = asyncFun(...args);
|
||||
|
||||
return module.exports.nodeifyPromise(promise, callback);
|
||||
};
|
||||
};
|
||||
'use strict';
|
||||
|
||||
const nodeify = require('nodeify');
|
||||
|
||||
module.exports.nodeifyPromise = nodeify;
|
||||
|
||||
module.exports.nodeifyFunction = (asyncFun) => {
|
||||
return (...args) => {
|
||||
const callback = args.pop();
|
||||
|
||||
const promise = asyncFun(...args);
|
||||
|
||||
return module.exports.nodeifyPromise(promise, callback);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -129,7 +129,7 @@ async function _sendMail(list, email, template, locale, subjectKey, relativeUrls
|
|||
};
|
||||
|
||||
if (list.default_form) {
|
||||
const form = await forms.getById(contextHelpers.getAdminContext(), list.default_form);
|
||||
const form = await forms.getById(contextHelpers.getAdminContext(), list.default_form, false);
|
||||
|
||||
text.template = form['mail_' + template + '_text'] || text.template;
|
||||
html.template = form['mail_' + template + '_html'] || html.template;
|
||||
|
|
|
@ -13,6 +13,7 @@ function loadLanguage(longCode) {
|
|||
}
|
||||
|
||||
loadLanguage('en-US');
|
||||
loadLanguage('es-ES');
|
||||
resourcesCommon['fk-FK'] = convertToFake(resourcesCommon['en-US']);
|
||||
|
||||
const resources = {};
|
||||
|
|
|
@ -1,72 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const urllib = require('url');
|
||||
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
|
||||
const {getLangCodeFromExpressLocale} = require('./translate');
|
||||
|
||||
function getTrustedUrlBase() {
|
||||
return urllib.resolve(config.www.trustedUrlBase, '');
|
||||
}
|
||||
|
||||
function getSandboxUrlBase() {
|
||||
return urllib.resolve(config.www.sandboxUrlBase, '');
|
||||
}
|
||||
|
||||
function getPublicUrlBase() {
|
||||
return urllib.resolve(config.www.publicUrlBase, '');
|
||||
}
|
||||
|
||||
function _getUrl(urlBase, path, opts) {
|
||||
const url = new URL(path || '', urlBase);
|
||||
|
||||
if (opts && opts.locale) {
|
||||
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function getTrustedUrl(path, opts) {
|
||||
return _getUrl(config.www.trustedUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, context, opts) {
|
||||
if (context && context.user && context.user.restrictedAccessToken) {
|
||||
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
|
||||
} else {
|
||||
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
|
||||
}
|
||||
}
|
||||
|
||||
function getPublicUrl(path, opts) {
|
||||
return _getUrl(config.www.publicUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
|
||||
function getTrustedUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
function getSandboxUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
function getPublicUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTrustedUrl,
|
||||
getSandboxUrl,
|
||||
getPublicUrl,
|
||||
getTrustedUrlBase,
|
||||
getSandboxUrlBase,
|
||||
getPublicUrlBase,
|
||||
getTrustedUrlBaseDir,
|
||||
getSandboxUrlBaseDir,
|
||||
getPublicUrlBaseDir
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const urllib = require('url');
|
||||
const {anonymousRestrictedAccessToken} = require('../../shared/urls');
|
||||
const {getLangCodeFromExpressLocale} = require('./translate');
|
||||
|
||||
function getTrustedUrlBase() {
|
||||
return urllib.resolve(config.www.trustedUrlBase, '');
|
||||
}
|
||||
|
||||
function getSandboxUrlBase() {
|
||||
return urllib.resolve(config.www.sandboxUrlBase, '');
|
||||
}
|
||||
|
||||
function getPublicUrlBase() {
|
||||
return urllib.resolve(config.www.publicUrlBase, '');
|
||||
}
|
||||
|
||||
function _getUrl(urlBase, path, opts) {
|
||||
const url = new URL(path || '', urlBase);
|
||||
|
||||
if (opts && opts.locale) {
|
||||
url.searchParams.append('locale', getLangCodeFromExpressLocale(opts.locale));
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function getTrustedUrl(path, opts) {
|
||||
return _getUrl(config.www.trustedUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, context, opts) {
|
||||
if (context && context.user && context.user.restrictedAccessToken) {
|
||||
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
|
||||
} else {
|
||||
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
|
||||
}
|
||||
}
|
||||
|
||||
function getPublicUrl(path, opts) {
|
||||
return _getUrl(config.www.publicUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
|
||||
function getTrustedUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.trustedUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
function getSandboxUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.sandboxUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
function getPublicUrlBaseDir() {
|
||||
const mailtrainUrl = urllib.parse(config.www.publicUrlBase);
|
||||
return mailtrainUrl.pathname;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTrustedUrl,
|
||||
getSandboxUrl,
|
||||
getPublicUrl,
|
||||
getTrustedUrlBase,
|
||||
getSandboxUrlBase,
|
||||
getPublicUrlBase,
|
||||
getTrustedUrlBaseDir,
|
||||
getSandboxUrlBaseDir,
|
||||
getPublicUrlBaseDir
|
||||
};
|
|
@ -6,6 +6,10 @@ const shares = require('./shares');
|
|||
const tools = require('../lib/tools');
|
||||
const { enforce } = require('../lib/helpers');
|
||||
|
||||
const {BlacklistActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
shares.enforceGlobalPermission(context, 'manageBlacklist');
|
||||
|
||||
|
@ -44,14 +48,21 @@ async function add(context, email) {
|
|||
if (!existing) {
|
||||
await tx('blacklist').insert({email});
|
||||
}
|
||||
|
||||
await activityLog.logBlacklistActivity(BlacklistActivityType.ADD, email);
|
||||
});
|
||||
}
|
||||
|
||||
async function remove(context, email) {
|
||||
enforce(email, 'Email has to be set');
|
||||
|
||||
shares.enforceGlobalPermission(context, 'manageBlacklist');
|
||||
await knex('blacklist').where('email', email).del();
|
||||
return await knex.transaction(async tx => {
|
||||
shares.enforceGlobalPermission(context, 'manageBlacklist');
|
||||
|
||||
await tx('blacklist').where('email', email).del();
|
||||
|
||||
await activityLog.logBlacklistActivity(BlacklistActivityType.REMOVE, email);
|
||||
});
|
||||
}
|
||||
|
||||
async function isBlacklisted(email) {
|
||||
|
|
|
@ -20,6 +20,10 @@ const senders = require('../lib/senders');
|
|||
const {LinkId} = require('./links');
|
||||
const feedcheck = require('../lib/feedcheck');
|
||||
const contextHelpers = require('../lib/context-helpers');
|
||||
const {convertFileURLs} = require('../lib/campaign-content');
|
||||
|
||||
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const allowedKeysCommon = ['name', 'description', 'segment', 'namespace',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url'];
|
||||
|
@ -60,18 +64,32 @@ function hash(entity, content) {
|
|||
return hasher.hash(filteredEntity);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
async function _listDTAjax(context, namespaceId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
|
||||
params,
|
||||
builder => builder.from('campaigns')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
|
||||
.whereNull('campaigns.parent'),
|
||||
builder => {
|
||||
builder = builder.from('campaigns')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
|
||||
.whereNull('campaigns.parent');
|
||||
if (namespaceId) {
|
||||
builder = builder.where('namespaces.id', namespaceId);
|
||||
}
|
||||
return builder;
|
||||
},
|
||||
['campaigns.id', 'campaigns.name', 'campaigns.cid', 'campaigns.description', 'campaigns.type', 'campaigns.status', 'campaigns.scheduled', 'campaigns.source', 'campaigns.created', 'namespaces.name']
|
||||
);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
return await _listDTAjax(context, undefined, params);
|
||||
}
|
||||
|
||||
async function listByNamespaceDTAjax(context, namespaceId, params) {
|
||||
return await _listDTAjax(context, namespaceId, params);
|
||||
}
|
||||
|
||||
async function listChildrenDTAjax(context, campaignId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
|
@ -427,35 +445,6 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
|||
}
|
||||
}
|
||||
|
||||
function convertFileURLs(sourceCustom, fromEntityType, fromEntityId, toEntityType, toEntityId) {
|
||||
|
||||
function convertText(text) {
|
||||
if (text) {
|
||||
const fromUrl = `/files/${fromEntityType}/file/${fromEntityId}`;
|
||||
const toUrl = `/files/${toEntityType}/file/${toEntityId}`;
|
||||
|
||||
const encodedFromUrl = encodeURIComponent(fromUrl);
|
||||
const encodedToUrl = encodeURIComponent(toUrl);
|
||||
|
||||
text = text.split('[URL_BASE]' + fromUrl).join('[URL_BASE]' + toUrl);
|
||||
text = text.split('[SANDBOX_URL_BASE]' + fromUrl).join('[SANDBOX_URL_BASE]' + toUrl);
|
||||
text = text.split('[ENCODED_URL_BASE]' + encodedFromUrl).join('[ENCODED_URL_BASE]' + encodedToUrl);
|
||||
text = text.split('[ENCODED_SANDBOX_URL_BASE]' + encodedFromUrl).join('[ENCODED_SANDBOX_URL_BASE]' + encodedToUrl);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
sourceCustom.html = convertText(sourceCustom.html);
|
||||
sourceCustom.text = convertText(sourceCustom.text);
|
||||
|
||||
if (sourceCustom.type === 'mosaico' || sourceCustom.type === 'mosaicoWithFsTemplate') {
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.model = convertText(sourceCustom.data.model);
|
||||
sourceCustom.data.metadata = convertText(sourceCustom.data.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
async function _createTx(tx, context, entity, content) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
|
||||
|
@ -533,6 +522,8 @@ async function _createTx(tx, context, entity, content) {
|
|||
}).where('id', id);
|
||||
}
|
||||
|
||||
await activityLog.logEntityActivity('campaign', EntityActivityType.CREATE, id, {status: filteredEntity.status});
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
@ -566,7 +557,7 @@ async function updateWithConsistencyCheck(context, entity, content) {
|
|||
|
||||
} else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
filteredEntity.data.sourceCustom = existing.data.sourceCustom;
|
||||
await namespaceHelpers.validateMove(context, filteredEntity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMove(context, filteredEntity, existing, 'campaign', 'createCampaign', 'delete'); // XXX TB - try with entity
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
const data = existing.data;
|
||||
|
@ -591,6 +582,8 @@ async function updateWithConsistencyCheck(context, entity, content) {
|
|||
await tx('campaigns').where('id', entity.id).update(filteredEntity);
|
||||
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: entity.id });
|
||||
|
||||
await activityLog.logEntityActivity('campaign', EntityActivityType.UPDATE, entity.id, {status: filteredEntity.status});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -628,6 +621,8 @@ async function _removeTx(tx, context, id, existing = null) {
|
|||
.del();
|
||||
|
||||
await tx('campaigns').where('id', id).del();
|
||||
|
||||
await activityLog.logEntityActivity('campaign', EntityActivityType.REMOVE, id);
|
||||
}
|
||||
|
||||
|
||||
|
@ -727,9 +722,7 @@ async function _changeStatusByMessageTx(tx, context, message, subscriptionStatus
|
|||
|
||||
const statusField = statusFieldMapping[subscriptionStatus];
|
||||
|
||||
if (message.status === SubscriptionStatus.SUBSCRIBED) {
|
||||
await tx('campaigns').increment(statusField, 1).where('id', message.campaign);
|
||||
}
|
||||
await tx('campaigns').increment(statusField, 1).where('id', message.campaign);
|
||||
|
||||
await tx('campaign_messages')
|
||||
.where('id', message.id)
|
||||
|
@ -745,10 +738,14 @@ async function changeStatusByCampaignCidAndSubscriptionIdTx(tx, context, campaig
|
|||
const message = await tx('campaign_messages')
|
||||
.innerJoin('campaigns', 'campaign_messages.campaign', 'campaigns.id')
|
||||
.where('campaigns.cid', campaignCid)
|
||||
.where({subscription: subscriptionId, list: listId});
|
||||
.where({subscription: subscriptionId, list: listId})
|
||||
.select([
|
||||
'campaign_messages.id', 'campaign_messages.campaign', 'campaign_messages.list', 'campaign_messages.subscription', 'campaign_messages.status'
|
||||
])
|
||||
.first();
|
||||
|
||||
if (!message) {
|
||||
throw new Error('Invalid campaign.')
|
||||
throw new Error('Invalid campaign.');
|
||||
}
|
||||
|
||||
await _changeStatusByMessageTx(tx, context, message, subscriptionStatus);
|
||||
|
@ -863,6 +860,8 @@ async function _changeStatus(context, campaignId, permittedCurrentStates, newSta
|
|||
status: newState,
|
||||
scheduled
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, campaignId, {status: newState});
|
||||
});
|
||||
|
||||
senders.scheduleCheck();
|
||||
|
@ -949,6 +948,7 @@ module.exports.Content = Content;
|
|||
module.exports.hash = hash;
|
||||
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
|
||||
module.exports.listChildrenDTAjax = listChildrenDTAjax;
|
||||
module.exports.listWithContentDTAjax = listWithContentDTAjax;
|
||||
module.exports.listOthersWhoseListsAreIncludedDTAjax = listOthersWhoseListsAreIncludedDTAjax;
|
||||
|
|
|
@ -16,6 +16,8 @@ const { cleanupFromPost } = require('../lib/helpers');
|
|||
const Handlebars = require('handlebars');
|
||||
const { getTrustedUrl, getSandboxUrl, getPublicUrl } = require('../lib/urls');
|
||||
const { getMergeTagsForBases } = require('../../shared/templates');
|
||||
const {ListActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
|
||||
const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']);
|
||||
|
@ -565,6 +567,8 @@ async function createTx(tx, context, listId, entity) {
|
|||
await knex.schema.raw('ALTER TABLE `subscription__' + listId + '` ADD `source_' + columnName +'` int(11) DEFAULT NULL');
|
||||
}
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.CREATE_FIELD, listId, {fieldId: id});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -594,6 +598,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
|
|||
|
||||
await tx('custom_fields').where({list: listId, id: entity.id}).update(filterObject(entity, allowedKeysUpdate));
|
||||
await _sortIn(tx, listId, entity.id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_FIELD, listId, {fieldId: entity.id});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -620,6 +626,8 @@ async function removeTx(tx, context, listId, id) {
|
|||
|
||||
await segments.removeRulesByColumnTx(tx, context, listId, existing.column);
|
||||
}
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_FIELD, listId, {fieldId: id});
|
||||
}
|
||||
|
||||
async function remove(context, listId, id) {
|
||||
|
|
|
@ -10,6 +10,8 @@ const {ImportSource, MappingType, ImportStatus, RunStatus, prepFinished, prepFin
|
|||
const fs = require('fs-extra-promise');
|
||||
const path = require('path');
|
||||
const importer = require('../lib/importer');
|
||||
const {ListActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const files = require('./files');
|
||||
const filesDir = path.join(files.filesDir, 'imports');
|
||||
|
@ -117,6 +119,8 @@ async function create(context, listId, entity, files) {
|
|||
const ids = await tx('imports').insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.CREATE_IMPORT, listId, {importId: id, importStatus: entity.status});
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
|
@ -148,6 +152,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
|
|||
filteredEntity.mapping = JSON.stringify(filteredEntity.mapping);
|
||||
|
||||
await tx('imports').where({list: listId, id: entity.id}).update(filteredEntity);
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_IMPORT, listId, {importId: entity.id, importStatus: entity.status});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -170,6 +176,8 @@ async function removeTx(tx, context, listId, id) {
|
|||
await tx('import_failed').whereIn('run', function() {this.from('import_runs').select('id').where('import', id)}).del();
|
||||
await tx('import_runs').where('import', id).del();
|
||||
await tx('imports').where({list: listId, id}).del();
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_IMPORT, listId, {importId: id});
|
||||
}
|
||||
|
||||
async function remove(context, listId, id) {
|
||||
|
@ -208,6 +216,8 @@ async function start(context, listId, id) {
|
|||
status: RunStatus.SCHEDULED,
|
||||
mapping: entity.mapping
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, listId, {importId: id, importStatus: ImportStatus.RUN_SCHEDULED});
|
||||
});
|
||||
|
||||
importer.scheduleCheck();
|
||||
|
@ -234,6 +244,8 @@ async function stop(context, listId, id) {
|
|||
await tx('import_runs').where('import', id).whereIn('status', [RunStatus.SCHEDULED, RunStatus.RUNNING]).update({
|
||||
status: RunStatus.STOPPING
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, listId, {importId: id, importStatus: ImportStatus.RUN_STOPPING});
|
||||
});
|
||||
|
||||
importer.scheduleCheck();
|
||||
|
|
|
@ -14,6 +14,9 @@ const imports = require('./imports');
|
|||
const entitySettings = require('../lib/entity-settings');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
const {EntityActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const {UnsubscriptionMode, FieldWizard} = require('../../shared/lists');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name', 'listunsubscribe_disabled', 'send_configuration']);
|
||||
|
@ -23,16 +26,22 @@ function hash(entity) {
|
|||
}
|
||||
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
async function _listDTAjax(context, namespaceId, params) {
|
||||
const campaignEntityType = entitySettings.getEntityType('campaign');
|
||||
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'list', requiredOperations: ['view'] }],
|
||||
params,
|
||||
builder => builder
|
||||
.from('lists')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'lists.namespace'),
|
||||
builder => {
|
||||
builder = builder
|
||||
.from('lists')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'lists.namespace');
|
||||
if (namespaceId) {
|
||||
builder = builder.where('lists.namespace', namespaceId);
|
||||
}
|
||||
return builder;
|
||||
},
|
||||
['lists.id', 'lists.name', 'lists.cid', 'lists.subscribers', 'lists.description', 'namespaces.name',
|
||||
{
|
||||
name: 'triggerCount',
|
||||
|
@ -50,6 +59,14 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
return await _listDTAjax(context, undefined, params);
|
||||
}
|
||||
|
||||
async function listByNamespaceDTAjax(context, namespaceId, params) {
|
||||
return await _listDTAjax(context, namespaceId, params);
|
||||
}
|
||||
|
||||
async function listWithSegmentByCampaignDTAjax(context, campaignId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
|
@ -196,6 +213,8 @@ async function create(context, entity) {
|
|||
await fields.createTx(tx, context, id, fld);
|
||||
}
|
||||
|
||||
await activityLog.logEntityActivity('list', EntityActivityType.CREATE, id);
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
@ -221,6 +240,8 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'list', entityId: entity.id });
|
||||
|
||||
await activityLog.logEntityActivity('list', EntityActivityType.UPDATE, entity.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -244,6 +265,8 @@ async function remove(context, id) {
|
|||
|
||||
await tx('lists').where('id', id).del();
|
||||
await knex.schema.dropTableIfExists('subscription__' + id);
|
||||
|
||||
await activityLog.logEntityActivity('list', EntityActivityType.REMOVE, id);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -251,6 +274,7 @@ async function remove(context, id) {
|
|||
module.exports.UnsubscriptionMode = UnsubscriptionMode;
|
||||
module.exports.hash = hash;
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
|
||||
module.exports.listWithSegmentByCampaignDTAjax = listWithSegmentByCampaignDTAjax;
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
|
|
|
@ -10,6 +10,8 @@ const moment = require('moment');
|
|||
const fields = require('./fields');
|
||||
const subscriptions = require('./subscriptions');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
const {ListActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const allowedKeys = new Set(['name', 'settings']);
|
||||
|
||||
|
@ -304,6 +306,8 @@ async function create(context, listId, entity) {
|
|||
const ids = await tx('segments').insert(filteredEntity);
|
||||
const id = ids[0];
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.CREATE_SEGMENT, listId, {segmentId: id});
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
@ -327,6 +331,8 @@ async function updateWithConsistencyCheck(context, listId, entity) {
|
|||
await _validateAndPreprocess(tx, listId, entity, false);
|
||||
|
||||
await tx('segments').where({list: listId, id: entity.id}).update(filterObject(entity, allowedKeys));
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.UPDATE_SEGMENT, listId, {segmentId: entity.id});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -346,6 +352,8 @@ async function removeTx(tx, context, listId, id) {
|
|||
|
||||
// The listId "where" is here to prevent deleting segment of a list for which a user does not have permission
|
||||
await tx('segments').where({list: listId, id}).del();
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.REMOVE_SEGMENT, listId, {segmentId: id});
|
||||
}
|
||||
|
||||
async function remove(context, listId, id) {
|
||||
|
|
|
@ -22,18 +22,32 @@ function hash(entity) {
|
|||
return hasher.hash(filterObject(entity, allowedKeys));
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
async function _listDTAjax(context, namespaceId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'sendConfiguration', requiredOperations: ['viewPublic'] }],
|
||||
params,
|
||||
builder => builder
|
||||
.from('send_configurations')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'send_configurations.namespace'),
|
||||
builder => {
|
||||
builder = builder
|
||||
.from('send_configurations')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'send_configurations.namespace');
|
||||
if (namespaceId) {
|
||||
builder = builder.where('send_configurations.namespace', namespaceId);
|
||||
}
|
||||
return builder;
|
||||
},
|
||||
['send_configurations.id', 'send_configurations.name', 'send_configurations.cid', 'send_configurations.description', 'send_configurations.mailer_type', 'send_configurations.created', 'namespaces.name']
|
||||
);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
return await _listDTAjax(context, undefined, params);
|
||||
}
|
||||
|
||||
async function listByNamespaceDTAjax(context, namespaceId, params) {
|
||||
return await _listDTAjax(context, namespaceId, params);
|
||||
}
|
||||
|
||||
async function listWithSendPermissionDTAjax(context, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
|
@ -175,6 +189,7 @@ async function getSystemSendConfiguration() {
|
|||
|
||||
module.exports.hash = hash;
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
|
||||
module.exports.listWithSendPermissionDTAjax = listWithSendPermissionDTAjax;
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
|
|
|
@ -10,6 +10,7 @@ const shares = require('./shares');
|
|||
const reports = require('./reports');
|
||||
const files = require('./files');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
const {convertFileURLs} = require('../lib/campaign-content');
|
||||
|
||||
const allowedKeys = new Set(['name', 'description', 'type', 'data', 'html', 'text', 'namespace']);
|
||||
|
||||
|
@ -35,16 +36,30 @@ async function getById(context, id, withPermissions = true) {
|
|||
});
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
async function _listDTAjax(context, namespaceId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'template', requiredOperations: ['view'] }],
|
||||
params,
|
||||
builder => builder.from('templates').innerJoin('namespaces', 'namespaces.id', 'templates.namespace'),
|
||||
builder => {
|
||||
builder = builder.from('templates').innerJoin('namespaces', 'namespaces.id', 'templates.namespace');
|
||||
if (namespaceId) {
|
||||
builder = builder.where('namespaces.id', namespaceId);
|
||||
}
|
||||
return builder;
|
||||
},
|
||||
[ 'templates.id', 'templates.name', 'templates.description', 'templates.type', 'templates.created', 'namespaces.name' ]
|
||||
);
|
||||
}
|
||||
|
||||
async function listDTAjax(context, params) {
|
||||
return await _listDTAjax(context, undefined, params);
|
||||
}
|
||||
|
||||
async function listByNamespaceDTAjax(context, namespaceId, params) {
|
||||
return await _listDTAjax(context, namespaceId, params);
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, entity) {
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
|
@ -57,6 +72,15 @@ async function create(context, entity) {
|
|||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createTemplate');
|
||||
|
||||
if (entity.fromSourceTemplate) {
|
||||
const template = await getByIdTx(tx, context, entity.sourceTemplate, false);
|
||||
|
||||
entity.type = template.type;
|
||||
entity.data = template.data;
|
||||
entity.html = template.html;
|
||||
entity.text = template.text;
|
||||
}
|
||||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
const ids = await tx('templates').insert(filterObject(entity, allowedKeys));
|
||||
|
@ -64,6 +88,13 @@ async function create(context, entity) {
|
|||
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'template', entityId: id });
|
||||
|
||||
if (entity.fromSourceTemplate) {
|
||||
await files.copyAllTx(tx, context, 'template', 'file', entity.sourceTemplate, 'template', 'file', id);
|
||||
|
||||
convertFileURLs(entity, 'template', entity.sourceTemplate, 'template', id);
|
||||
await tx('templates').update(filterObject(entity, allowedKeys)).where('id', id);
|
||||
}
|
||||
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
@ -118,6 +149,7 @@ module.exports.hash = hash;
|
|||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listByNamespaceDTAjax = listByNamespaceDTAjax;
|
||||
module.exports.create = create;
|
||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||
module.exports.remove = remove;
|
||||
|
|
4
server/protected/reports/.gitignore
vendored
4
server/protected/reports/.gitignore
vendored
|
@ -1,3 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
|
@ -19,6 +19,10 @@ router.postAsync('/campaigns-others-by-list-table/:campaignId/:listIds', passpor
|
|||
return res.json(await campaigns.listOthersWhoseListsAreIncludedDTAjax(req.context, castToInteger(req.params.campaignId), req.params.listIds.split(';').map(x => castToInteger(x)), req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/campaigns-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await campaigns.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/campaigns-children/:campaignId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await campaigns.listChildrenDTAjax(req.context, castToInteger(req.params.campaignId), req.body));
|
||||
});
|
||||
|
|
|
@ -11,6 +11,10 @@ router.postAsync('/lists-table', passport.loggedIn, async (req, res) => {
|
|||
return res.json(await lists.listDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/lists-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await lists.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/lists-with-segment-by-campaign-table/:campaignId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await lists.listWithSegmentByCampaignDTAjax(req.context, castToInteger(req.params.campaignId), req.body));
|
||||
});
|
||||
|
|
|
@ -40,6 +40,10 @@ router.postAsync('/send-configurations-table', passport.loggedIn, async (req, re
|
|||
return res.json(await sendConfigurations.listDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/send-configurations-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await sendConfigurations.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/send-configurations-with-send-permission-table', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await sendConfigurations.listWithSendPermissionDTAjax(req.context, req.body));
|
||||
});
|
||||
|
|
|
@ -35,6 +35,10 @@ router.postAsync('/templates-table', passport.loggedIn, async (req, res) => {
|
|||
return res.json(await templates.listDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/templates-by-namespace-table/:namespaceId', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await templates.listByNamespaceDTAjax(req.context, castToInteger(req.params.namespaceId), req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/template-test-send', passport.loggedIn, passport.csrfProtection, async (req, res) => {
|
||||
const data = req.body;
|
||||
const result = await CampaignSender.testSend(req.context, data.listCid, data.subscriptionCid, data.campaignId, data.sendConfigurationId, data.html, data.text);
|
||||
|
|
|
@ -156,7 +156,6 @@ function getRouter(appType) {
|
|||
// This is a fallback to versafix-1 if the block thumbnail is not defined by the template
|
||||
router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', '..', 'client', 'static', 'mosaico', 'templates', 'versafix-1', 'edres')));
|
||||
|
||||
|
||||
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file', resp => {
|
||||
return {
|
||||
files: resp.files.map(f => ({name: f.name, url: f.url, size: f.size, thumbnailUrl: f.thumbnailUrl}))
|
||||
|
|
|
@ -183,7 +183,6 @@ async function _renderSubscribe(req, res, list, subscription) {
|
|||
const htmlRenderer = await tools.getTemplate(data.template, req.locale);
|
||||
|
||||
data.isWeb = true;
|
||||
data.needsJsWarning = true;
|
||||
|
||||
data.flashMessages = await captureFlashMessages(res);
|
||||
|
||||
|
@ -385,7 +384,6 @@ router.getAsync('/:lcid/manage/:ucid', passport.csrfProtection, async (req, res)
|
|||
const htmlRenderer = await tools.getTemplate(data.template, req.locale);
|
||||
|
||||
data.isWeb = true;
|
||||
data.needsJsWarning = true;
|
||||
data.isManagePreferences = true;
|
||||
data.flashMessages = await captureFlashMessages(res);
|
||||
|
||||
|
@ -435,7 +433,6 @@ router.getAsync('/:lcid/manage-address/:ucid', passport.csrfProtection, async (r
|
|||
const htmlRenderer = await tools.getTemplate(data.template, req.locale);
|
||||
|
||||
data.isWeb = true;
|
||||
data.needsJsWarning = true;
|
||||
data.isManagePreferences = true;
|
||||
data.flashMessages = await captureFlashMessages(res);
|
||||
|
||||
|
@ -535,7 +532,6 @@ router.getAsync('/:lcid/unsubscribe/:ucid', passport.csrfProtection, async (req,
|
|||
const htmlRenderer = await tools.getTemplate(data.template, req.locale);
|
||||
|
||||
data.isWeb = true;
|
||||
data.needsJsWarning = true;
|
||||
data.flashMessages = await captureFlashMessages(res);
|
||||
|
||||
res.send(htmlRenderer(data));
|
||||
|
@ -679,7 +675,7 @@ async function webNotice(type, req, res) {
|
|||
}
|
||||
};
|
||||
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'web_' + type + '_notice', data);
|
||||
await injectCustomFormData(req.query.fid || list.default_form, 'web_' + type.replace('-', '_') + '_notice', data);
|
||||
|
||||
const htmlRenderer = await tools.getTemplate(data.template, req.locale);
|
||||
|
||||
|
|
|
@ -10,8 +10,16 @@ const stringify = require('csv-stringify')
|
|||
const fields = require('../models/fields');
|
||||
const lists = require('../models/lists');
|
||||
const moment = require('moment');
|
||||
const {SubscriptionStatus} = require('../../shared/lists');
|
||||
|
||||
router.getAsync('/export/:listId/:segmentId', passport.loggedIn, async (req, res) => {
|
||||
const statusStrings = {
|
||||
[SubscriptionStatus.SUBSCRIBED]: 'subscribed',
|
||||
[SubscriptionStatus.UNSUBSCRIBED]: 'unsubscribed',
|
||||
[SubscriptionStatus.BOUNCED]: 'bounced',
|
||||
[SubscriptionStatus.COMPLAINED]: 'complained'
|
||||
};
|
||||
|
||||
const listId = castToInteger(req.params.listId);
|
||||
const segmentId = castToInteger(req.params.segmentId);
|
||||
|
||||
|
@ -19,6 +27,7 @@ router.getAsync('/export/:listId/:segmentId', passport.loggedIn, async (req, res
|
|||
|
||||
const columns = [
|
||||
{key: 'cid', header: 'cid'},
|
||||
{key: 'status', header: 'status'},
|
||||
{key: 'hash_email', header: 'HASH_EMAIL'},
|
||||
{key: 'email', header: 'EMAIL'},
|
||||
];
|
||||
|
@ -50,6 +59,8 @@ router.getAsync('/export/:listId/:segmentId', passport.loggedIn, async (req, res
|
|||
stringifier.pipe(res);
|
||||
|
||||
for await (const subscription of subscriptions.listIterator(req.context, listId, segmentId, false)) {
|
||||
subscription.status = statusStrings[subscription.status];
|
||||
|
||||
stringifier.write(subscription);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ const contextHelpers = require('../lib/context-helpers');
|
|||
const tools = require('../lib/tools');
|
||||
const shares = require('../models/shares');
|
||||
const { tLog } = require('../lib/translate');
|
||||
const {ListActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
|
||||
const csvparse = require('csv-parse');
|
||||
|
@ -41,6 +43,8 @@ function prepareCsv(impt) {
|
|||
error: msg + '\n' + err.message
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FAILED});
|
||||
|
||||
await fsExtra.removeAsync(filePath);
|
||||
};
|
||||
|
||||
|
@ -56,6 +60,8 @@ function prepareCsv(impt) {
|
|||
error: null
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FINISHED});
|
||||
|
||||
await fsExtra.removeAsync(filePath);
|
||||
};
|
||||
|
||||
|
@ -263,12 +269,16 @@ async function _execImportRun(impt, handlers) {
|
|||
status: ImportStatus.RUN_FINISHED
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_FINISHED});
|
||||
|
||||
} catch (err) {
|
||||
await knex('imports').where('id', impt.id).update({
|
||||
last_run: new Date(),
|
||||
error: err.message,
|
||||
status: ImportStatus.RUN_FAILED
|
||||
});
|
||||
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_FAILED});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,14 +371,20 @@ async function getTask() {
|
|||
|
||||
if (impt.source === ImportSource.CSV_FILE && impt.status === ImportStatus.PREP_SCHEDULED) {
|
||||
await tx('imports').where('id', impt.id).update('status', ImportStatus.PREP_RUNNING);
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.PREP_RUNNING});
|
||||
|
||||
return () => prepareCsv(impt);
|
||||
|
||||
} else if (impt.status === ImportStatus.RUN_SCHEDULED && impt.mapping_type === MappingType.BASIC_SUBSCRIBE) {
|
||||
await tx('imports').where('id', impt.id).update('status', ImportStatus.RUN_RUNNING);
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_RUNNING});
|
||||
|
||||
return () => basicSubscribe(impt);
|
||||
|
||||
} else if (impt.status === ImportStatus.RUN_SCHEDULED && impt.mapping_type === MappingType.BASIC_UNSUBSCRIBE) {
|
||||
await tx('imports').where('id', impt.id).update('status', ImportStatus.RUN_RUNNING);
|
||||
await activityLog.logEntityActivity('list', ListActivityType.IMPORT_STATUS_CHANGE, impt.list, {importId: impt.id, importStatus: ImportStatus.RUN_RUNNING});
|
||||
|
||||
return () => basicUnsubscribe(impt);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ const {CampaignStatus, CampaignType} = require('../../shared/campaigns');
|
|||
const { enforce } = require('../lib/helpers');
|
||||
const campaigns = require('../models/campaigns');
|
||||
const builtinZoneMta = require('../lib/builtin-zone-mta');
|
||||
const {CampaignActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
|
||||
let messageTid = 0;
|
||||
const workerProcesses = new Map();
|
||||
|
@ -127,6 +130,8 @@ async function processCampaign(campaignId) {
|
|||
}
|
||||
|
||||
await knex('campaigns').where('id', campaignId).update({status: CampaignStatus.FINISHED});
|
||||
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, campaignId, {status: CampaignStatus.FINISHED});
|
||||
|
||||
messageQueue.delete(campaignId);
|
||||
}
|
||||
|
||||
|
@ -214,6 +219,7 @@ async function scheduleCampaigns() {
|
|||
|
||||
if (scheduledCampaign) {
|
||||
await tx('campaigns').where('id', scheduledCampaign.id).update({status: CampaignStatus.SENDING});
|
||||
await activityLog.logEntityActivity('campaign', CampaignActivityType.STATUS_CHANGE, scheduledCampaign.id, {status: CampaignStatus.SENDING});
|
||||
campaignId = scheduledCampaign.id;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ defaultLanguage: en-US
|
|||
# Enabled languages
|
||||
enabledLanguages:
|
||||
- en-US
|
||||
- es-ES
|
||||
- fk-FK
|
||||
|
||||
mysql:
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
if (!process.env.NODE_CONFIG_DIR) {
|
||||
process.env.NODE_CONFIG_DIR = __dirname + '/../../config';
|
||||
}
|
||||
|
||||
const config = require('server/setup/knex/config');
|
||||
|
||||
module.exports = config;
|
||||
'use strict';
|
||||
|
||||
if (!process.env.NODE_CONFIG_DIR) {
|
||||
process.env.NODE_CONFIG_DIR = __dirname + '/../../config';
|
||||
}
|
||||
|
||||
const config = require('server/setup/knex/config');
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
client: 'mysql',
|
||||
connection: config.mysql
|
||||
};
|
||||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
client: 'mysql',
|
||||
connection: config.mysql
|
||||
};
|
||||
|
|
|
@ -526,8 +526,9 @@ async function migrateSegments(knex) {
|
|||
|
||||
switch (fieldType) {
|
||||
case 'text':
|
||||
case 'string':
|
||||
case 'website':
|
||||
rules.push({ column: oldRule.column, value: oldSettings.value });
|
||||
rules.push({ type: 'like', column: oldRule.column, value: oldSettings.value });
|
||||
break;
|
||||
case 'number':
|
||||
if (oldSettings.range) {
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
{{#if uaCode}}
|
||||
<script>
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function() {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '{{uaCode}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{{/if}}
|
||||
{{#if uaCode}}
|
||||
<script>
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function() {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '{{uaCode}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
{{{flashMessages}}}
|
||||
|
||||
{{#if needsJsWarning}}
|
||||
<div class="alert alert-danger js-warning" role="alert">
|
||||
<strong>{{#translate}}warning!{{/translate}}</strong>
|
||||
{{#translate}}javaScriptMustBeEnabledInOrderForThis{{/translate}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue