+ );
+ }
+}
\ No newline at end of file
diff --git a/client/src/lists/root.js b/client/src/lists/root.js
index 64d01dab..52209d3a 100644
--- a/client/src/lists/root.js
+++ b/client/src/lists/root.js
@@ -13,7 +13,7 @@ import SubscriptionsCUD from './subscriptions/CUD';
import SegmentsList from './segments/List';
import SegmentsCUD from './segments/CUD';
import Share from '../shares/Share';
-
+import TriggersList from './TriggersList';
function getMenus(t) {
return {
@@ -127,6 +127,11 @@ function getMenus(t) {
}
}
},
+ triggers: {
+ title: t('Triggers'),
+ link: params => `/lists/${params.listId}/triggers`,
+ panelRender: props =>
+ },
share: {
title: t('Share'),
link: params => `/lists/${params.listId}/share`,
diff --git a/client/src/templates/CUD.js b/client/src/templates/CUD.js
index 0497cbc7..214edf01 100644
--- a/client/src/templates/CUD.js
+++ b/client/src/templates/CUD.js
@@ -94,17 +94,17 @@ export default class CUD extends Component {
localValidateFormValues(state) {
const t = this.props.t;
+ for (const key of state.keys()) {
+ state.setIn([key, 'error'], null);
+ }
+
if (!state.getIn(['name', 'value'])) {
state.setIn(['name', 'error'], t('Name must not be empty'));
- } else {
- state.setIn(['name', 'error'], null);
}
const typeKey = state.getIn(['type', 'value']);
if (!typeKey) {
state.setIn(['type', 'error'], t('Type must be selected'));
- } else {
- state.setIn(['type', 'error'], null);
}
validateNamespace(t, state);
diff --git a/client/src/templates/helpers.js b/client/src/templates/helpers.js
index 737ba017..990ab8b5 100644
--- a/client/src/templates/helpers.js
+++ b/client/src/templates/helpers.js
@@ -112,14 +112,12 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
if (!mosaicoTemplate) {
state.setIn([prefix + 'mosaicoTemplate', 'error'], t('Mosaico template must be selected'));
- } else {
- state.setIn([prefix + 'mosaicoTemplate', 'error'], null);
}
}
};
- const mosaicoFsTemplatesOptions = mailtrainConfig.mosaico.fsTemplates.map(([key, label]) => ({key, label}));
- const mosaicoFsTemplatesLabels = new Map(mailtrainConfig.mosaico.fsTemplates);
+ const mosaicoFsTemplatesOptions = mailtrainConfig.mosaico.fsTemplates;
+ const mosaicoFsTemplatesLabels = new Map(mailtrainConfig.mosaico.fsTemplates.map(({key, label}) => ([key, label])));
templateTypes.mosaicoWithFsTemplate = {
typeName: t('Mosaico with predefined templates'),
@@ -151,7 +149,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
});
},
initData: () => ({
- [prefix + 'mosaicoFsTemplate']: mailtrainConfig.mosaico.fsTemplates[0][0],
+ [prefix + 'mosaicoFsTemplate']: mailtrainConfig.mosaico.fsTemplates[0].key,
[prefix + 'mosaicoData']: {}
}),
afterLoad: data => {
diff --git a/config/default.toml b/config/default.toml
deleted file mode 100644
index 24e9e70f..00000000
--- a/config/default.toml
+++ /dev/null
@@ -1,322 +0,0 @@
-# This file is the default config file for Mailtrain. To use a environment specific
-# configuration add new file {ENV}.{ext} (eg. "production.toml") to the same folder.
-# {ENV} is defined by NODE_ENV environment variable.
-#
-# Do not modify this file directly, otherwise you might lose your modifications when upgrading
-#
-# You should only define the options you want to change in your additional config file.
-# For example if the only thing you want to change is the port number for the www server
-# then your additional config file should look like this:
-# # production.toml
-# [www]
-# port=80
-# or if you want to use Javascript instead of TOML then the same file could look like this:
-# // production.js
-# module.exports = {
-# www: {
-# port: 80
-# }
-# };
-
-# Process title visible in monitoring logs and process listing
-title="mailtrain"
-
-# Enabled HTML editors
-editors=["ckeditor", "codeeditor", "mosaico", "mosaicoWithFsTemplate"]
-
-# Default language to use
-language="en"
-
-# Inject custom scripts in subscription/layout.mjml.hbs
-# customSubscriptionScripts=["/custom/hello-world.js"]
-
-# If you start out as a root user (eg. if you want to use ports lower than 1000)
-# then you can downgrade the user once all services are up and running
-#user="mailtrain"
-#group="mailtrain"
-
-# If Mailtrain is started as root, "Reports" feature drops the privileges of script generating the report to disallow
-# any modifications of Mailtrain code and even prohibits reading the production configuration (which contains the MySQL
-# password for read/write operations). The roUser/roGroup determines the user to be used
-#roUser="nobody"
-#roGroup="nogroup"
-
-[log]
-# silly|verbose|info|http|warn|error|silent
-level="verbose"
-
-[www]
-# HTTP port to listen on
-port=3000
-# HTTP port to listen on for sandboxed requests
-sandboxPort=8081
-# HTTP interface to listen on
-host="0.0.0.0"
-# URL base for trusted urls. It must be absolute (starting with http:// or https://). If Mailtrain is served on
-# a non-standard port (e.g. 3000), the URL must also specify the port.
-trustedUrlBase="http://localhost:3000"
-# URL base for sandbox urls. It must be absolute (starting with http:// or https://) and contain the sandbox port.
-sandboxUrlBase="http://localhost:8081"
-
-# Secret for signing the session ID cookie
-secret="a cat"
-# Session length in seconds when "remember me" is checked
-remember=2592000 # 30 days
-# logger interface for expressjs morgan
-log="dev"
-# Is the server behind a proxy? true/false
-# Set this to true if you are serving Mailtrain as a virtual domain through Nginx or Apache
-proxy=false
-# maximum POST body size
-postSize="2MB"
-
-[mysql]
-host="localhost"
-user="mailtrain"
-password="mailtrain"
-database="mailtrain"
-# Some installations, eg. MAMP can use a different port (8889)
-# MAMP users should also turn on "Allow network access to MySQL" otherwise MySQL might not be accessible
-port=3306
-charset="utf8mb4"
-# The timezone configured on the MySQL server. This can be 'local', 'Z', or an offset in the form +HH:MM or -HH:MM
-timezone="local"
-
-[redis]
-# enable to use Redis session cache or disable if Redis is not installed
-enabled=false
-host="localhost"
-port=6379
-db=5
-# Uncomment if your Redis installation requires a password
-#password=""
-
-[verp]
-# Enable to start an MX server that detects bounced messages using VERP
-# In most cases you do not want to use it
-# Requires root privileges
-enabled=false
-port=2525
-host="0.0.0.0"
-# With DMARC, the Return-Path and From address must match the same domain.
-# By default we get around this by using the VERP address in the Sender header,
-# with the side effect that some email clients diplay an ugly "on behalf of" message.
-# You can safely disable this Sender header if you're not using DMARC or your
-# VERP hostname is in the same domain as the From address.
-# disablesenderheader=true
-
-[ldap]
-# enable to use ldap user backend
-enabled=false
-# method is ldapjs or ldapauth - it chooses the library to be used. If not given, it chooses the one present.
-# method="ldapjs"
-host="localhost"
-port=3002
-baseDN="ou=users,dc=company"
-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=""
-newUserRole="master"
-# Global namespace id
-newUserNamespaceId=1
-# Use a different user to bind LDAP (final bind DN will be: {{uidTag}}={{bindUser}},{{baseDN}})
-bindUser="name@company.net"
-bindPassword="mySecretPassword"
-
-[postfixbounce]
-# Enable to allow writing Postfix bounce log to Mailtrain listener
-# If enabled, tail mail.log to Mailtrain with the following command:
-# tail -f -n +0 /var/log/mail.log | nc localhost 5699 -
-enabled=false
-port=5699
-# allow connections from localhost only
-host="127.0.0.1"
-
-# extra options for nodemailer
-[nodemailer]
-#textEncoding="base64"
-
-[queue]
-# How many parallel sender processes to spawn
-# You can use more than 1 process only if you have Redis enabled
-processes=1
-
-[cors]
-# Allow subscription widgets to be embedded
-# origins=['https://www.example.com']
-
-[mosaico]
-# Installed templates
-fsTemplates=[["versafix-1", "Versafix One"]]
-# Inject custom scripts
-# customscripts=["/mosaico/custom/my-mosaico-plugin.js"]
-
-[grapejs]
-# Installed templates
-templates=[
- ["demo", "HTML Template"],
- ["aves", "MJML Template"]
-]
-
-[reports]
-# The whole reporting functionality can be disabled below if the they are not needed and the DB cannot be
-# properly protected.
-# Reports rely on custom user defined Javascript snippets defined in the report template. The snippets are run on the
-# server when generating a report. As these snippets are stored in the DB, they pose a security risk because they can
-# help gaining access to the server if the DB cannot
-# be properly protected (e.g. if it is shared with another application with security weaknesses).
-# Mailtrain mitigates this problem by running the custom Javascript snippets in a chrooted environment and under a
-# DB user that cannot modify the database (see userRO in [mysql] above). However the chrooted environment is available
-# only if Mailtrain is started as root. The chrooted environment still does not prevent the custom JS script in
-# performing network operations and in generating XSS attacks as part of the report.
-# The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted,
-# then it's safer to switch off the reporting functionality below.
-enabled=false
-
-[testserver]
-# Starts a vanity server that redirects all mail to /dev/null
-# Mostly needed for local development
-enabled=false
-port=5587
-mailboxserverport=3001
-host="0.0.0.0"
-username="testuser"
-password="testpass"
-logger=false
-
-[seleniumwebdriver]
-browser="phantomjs"
-
-
-
-[roles.global.master]
-name="Master"
-admin=true
-description="All permissions"
-permissions=["rebuildPermissions", "createJavascriptWithROAccess", "manageBlacklist", "manageSettings"]
-rootNamespaceRole="master"
-
-[roles.namespace.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "createNamespace", "createList", "createCustomForm", "createReport", "createReportTemplate", "createTemplate", "createMosaicoTemplate", "createSendConfiguration", "createCampaign", "manageUsers"]
-
-[roles.namespace.master.children]
-sendConfiguration=["viewPublic", "viewPrivate", "edit", "delete", "share", "sendWithoutOverrides", "sendWithAllowedOverrides", "sendWithAnyOverrides"]
-list=["view", "edit", "delete", "share", "viewFields", "manageFields", "viewSubscriptions", "manageSubscriptions", "viewSegments", "manageSegments"]
-customForm=["view", "edit", "delete", "share"]
-campaign=["view", "edit", "delete", "share", "viewFiles", "manageFiles", "viewAttachments", "manageAttachments", "viewTriggers", "manageTriggers", "send", "viewStats"]
-template=["view", "edit", "delete", "share", "viewFiles", "manageFiles"]
-report=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
-reportTemplate=["view", "edit", "delete", "share", "execute"]
-mosaicoTemplate=["view", "edit", "delete", "share", "viewFiles", "manageFiles"]
-namespace=["view", "edit", "delete", "share", "createNamespace", "createList", "createCustomForm", "createReport", "createReportTemplate", "createTemplate", "createMosaicoTemplate", "createSendConfiguration", "createCampaign", "manageUsers"]
-
-[roles.sendConfiguration.master]
-name="Master"
-description="All permissions"
-permissions=["viewPublic", "viewPrivate", "edit", "delete", "share", "sendWithoutOverrides", "sendWithAllowedOverrides", "sendWithAnyOverrides"]
-
-[roles.list.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "viewFields", "manageFields", "viewSubscriptions", "manageSubscriptions", "viewSegments", "manageSegments"]
-
-[roles.customForm.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share"]
-
-[roles.campaign.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "viewFiles", "manageFiles", "viewAttachments", "manageAttachments", "viewTriggers", "manageTriggers", "send", "viewStats"]
-
-[roles.template.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "viewFiles", "manageFiles"]
-
-[roles.report.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "execute", "viewContent", "viewOutput"]
-
-[roles.reportTemplate.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "execute"]
-
-[roles.mosaicoTemplate.master]
-name="Master"
-description="All permissions"
-permissions=["view", "edit", "delete", "share", "viewFiles", "manageFiles"]
-
-
-
-
-[roles.global.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-ownNamespaceRole="editor"
-
-[roles.namespace.editor]
-name="Editor"
-description="XXX"
-permissions=["view", "edit", "delete"]
-
-[roles.namespace.editor.children]
-sendConfiguration=[]
-list=[]
-customForm=[]
-campaign=[]
-template=[]
-report=[]
-reportTemplate=[]
-namespace=["view", "edit", "delete"]
-mosaicoTemplate=[]
-
-[roles.sendConfiguration.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.list.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.customForm.editor]
-name="Editor"
-description="All permissions"
-permissions=[]
-
-[roles.campaign.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.template.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.report.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.reportTemplate.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
-[roles.mosaicoTemplate.editor]
-name="Editor"
-description="XXX"
-permissions=[]
-
diff --git a/config/default.yaml b/config/default.yaml
new file mode 100644
index 00000000..1fba864b
--- /dev/null
+++ b/config/default.yaml
@@ -0,0 +1,268 @@
+# This file is the default config file for Mailtrain. To use a environment specific
+# configuration add new file {ENV}.{ext} (eg. production.yaml) to the same folder.
+# {ENV} is defined by NODE_ENV environment variable.
+#
+# Do not modify this file directly, otherwise you might lose your modifications when upgrading
+#
+# You should only define the options you want to change in your additional config file.
+# For example if the only thing you want to change is the port number for the www server
+# then your additional config file should look like this:
+# # production.yaml
+# www:
+# port: 80
+
+# Process title visible in monitoring logs and process listing
+title: mailtrain
+
+# Enabled HTML editors
+editors:
+- ckeditor
+- codeeditor
+- mosaico
+- mosaicoWithFsTemplate
+
+# Default language to use
+language: en
+
+# Inject custom scripts in subscription/layout.mjml.hbs
+# customSubscriptionScripts: [/custom/hello-world.js]
+
+# If you start out as a root user (eg. if you want to use ports lower than 1000)
+# then you can downgrade the user once all services are up and running
+#user: mailtrain
+#group: mailtrain
+
+# If Mailtrain is started as root, Reports feature drops the privileges of script generating the report to disallow
+# any modifications of Mailtrain code and even prohibits reading the production configuration (which contains the MySQL
+# password for read/write operations). The roUser/roGroup determines the user to be used
+#roUser: nobody
+#roGroup: nogroup
+
+log:
+ # silly|verbose|info|http|warn|error|silent
+ level: verbose
+
+www:
+ # HTTP port to listen on
+ port: 3000
+ # HTTP port to listen on for sandboxed requests
+ sandboxPort: 8081
+ # HTTP interface to listen on
+ host: 0.0.0.0
+ # URL base for trusted urls. It must be absolute (starting with http:// or https://). If Mailtrain is served on
+ # a non-standard port (e.g. 3000), the URL must also specify the port.
+ trustedUrlBase: http://localhost:3000
+ # URL base for sandbox urls. It must be absolute (starting with http:// or https://) and contain the sandbox port.
+ sandboxUrlBase: http://localhost:8081
+
+ # Secret for signing the session ID cookie
+ secret: a cat
+ # Session length in seconds when remember me is checked
+ remember: 2592000 # 30 days
+ # logger interface for expressjs morgan
+ log: dev
+ # Is the server behind a proxy? true/false
+ # Set this to true if you are serving Mailtrain as a virtual domain through Nginx or Apache
+ proxy: false
+ # maximum POST body size
+ postSize: 2MB
+
+mysql:
+ host: localhost
+ user: mailtrain
+ password: mailtrain
+ database: mailtrain
+ # Some installations, eg. MAMP can use a different port (8889)
+ # MAMP users should also turn on Allow network access to MySQL otherwise MySQL might not be accessible
+ port: 3306
+ charset: utf8mb4
+ # The timezone configured on the MySQL server. This can be 'local', 'Z', or an offset in the form +HH:MM or -HH:MM
+ timezone: local
+
+redis:
+ # enable to use Redis session cache or disable if Redis is not installed
+ enabled: false
+ host: localhost
+ port: 6379
+ db: 5
+ # Uncomment if your Redis installation requires a password
+ #password:
+
+verp:
+ # Enable to start an MX server that detects bounced messages using VERP
+ # In most cases you do not want to use it
+ # Requires root privileges
+ enabled: false
+ port: 2525
+ host: 0.0.0.0
+ # With DMARC, the Return-Path and From address must match the same domain.
+ # By default we get around this by using the VERP address in the Sender header,
+ # with the side effect that some email clients diplay an ugly on behalf of message.
+ # You can safely disable this Sender header if you're not using DMARC or your
+ # VERP hostname is in the same domain as the From address.
+ # disablesenderheader: true
+
+ldap:
+ # enable to use ldap user backend
+ enabled: false
+ # method is ldapjs or ldapauth - it chooses the library to be used. If not given, it chooses the one present.
+ # method: ldapjs
+ host: localhost
+ port: 3002
+ baseDN: ou=users,dc=company
+ 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:
+ newUserRole: master
+ # Global namespace id
+ newUserNamespaceId: 1
+ # Use a different user to bind LDAP (final bind DN will be: {{uidTag}}: {{bindUser}},{{baseDN}})
+ bindUser: name@company.net
+ bindPassword: mySecretPassword
+
+postfixbounce:
+ # Enable to allow writing Postfix bounce log to Mailtrain listener
+ # If enabled, tail mail.log to Mailtrain with the following command:
+ # tail -f -n +0 /var/log/mail.log | nc localhost 5699 -
+ enabled: false
+ port: 5699
+ # allow connections from localhost only
+ host: 127.0.0.1
+
+# extra options for nodemailer
+nodemailer:
+ #textEncoding: base64
+
+queue:
+ # How many parallel sender processes to spawn
+ # You can use more than 1 process only if you have Redis enabled
+ processes: 1
+
+cors:
+ # Allow subscription widgets to be embedded
+ # origins: ['https://www.example.com']
+
+mosaico:
+ # Installed templates
+ fsTemplates:
+ - key: versafix-1
+ label: Versafix One
+ # Inject custom scripts
+ # customscripts:
+ # - /mosaico/custom/my-mosaico-plugin.js
+
+grapejs:
+# Installed templates
+ templates:
+ - key: demo
+ label: HTML Template
+ - key: aves
+ label: MJML Template
+
+reports:
+ # The whole reporting functionality can be disabled below if the they are not needed and the DB cannot be
+ # properly protected.
+ # Reports rely on custom user defined Javascript snippets defined in the report template. The snippets are run on the
+ # server when generating a report. As these snippets are stored in the DB, they pose a security risk because they can
+ # help gaining access to the server if the DB cannot
+ # be properly protected (e.g. if it is shared with another application with security weaknesses).
+ # Mailtrain mitigates this problem by running the custom Javascript snippets in a chrooted environment and under a
+ # DB user that cannot modify the database (see userRO in [mysql] above). However the chrooted environment is available
+ # only if Mailtrain is started as root. The chrooted environment still does not prevent the custom JS script in
+ # performing network operations and in generating XSS attacks as part of the report.
+ # The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted,
+ # then it's safer to switch off the reporting functionality below.
+ enabled: false
+
+testserver:
+ # Starts a vanity server that redirects all mail to /dev/null
+ # Mostly needed for local development
+ enabled: false
+ port: 5587
+ mailboxserverport: 3001
+ host: 0.0.0.0
+ username: testuser
+ password: testpass
+ logger: false
+
+seleniumwebdriver:
+ browser: phantomjs
+
+
+roles:
+ global:
+ master:
+ name: Master
+ admin: true
+ description: All permissions
+ permissions: [rebuildPermissions, createJavascriptWithROAccess, manageBlacklist, manageSettings]
+ rootNamespaceRole: master
+
+ namespace:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, createNamespace, createList, createCustomForm, createReport, createReportTemplate, createTemplate, createMosaicoTemplate, createSendConfiguration, createCampaign, manageUsers]
+ children:
+ sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
+ list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, manageSubscriptions, viewSegments, manageSegments]
+ customForm: [view, edit, delete, share]
+ campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats]
+ template: [view, edit, delete, share, viewFiles, manageFiles]
+ report: [view, edit, delete, share, execute, viewContent, viewOutput]
+ reportTemplate: [view, edit, delete, share, execute]
+ mosaicoTemplate: [view, edit, delete, share, viewFiles, manageFiles]
+ namespace: [view, edit, delete, share, createNamespace, createList, createCustomForm, createReport, createReportTemplate, createTemplate, createMosaicoTemplate, createSendConfiguration, createCampaign, manageUsers]
+
+ sendConfiguration:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
+
+ list:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, manageSubscriptions, viewSegments, manageSegments]
+
+ customForm:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share]
+
+ campaign:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, viewStats]
+
+ template:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, viewFiles, manageFiles]
+
+ report:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, execute, viewContent, viewOutput]
+
+ reportTemplate:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, execute]
+
+ mosaicoTemplate:
+ master:
+ name: Master
+ description: All permissions
+ permissions: [view, edit, delete, share, viewFiles, manageFiles]
+
+
diff --git a/lib/urls.js b/lib/urls.js
index 8400c329..53e32da8 100644
--- a/lib/urls.js
+++ b/lib/urls.js
@@ -2,6 +2,7 @@
const config = require('config');
const urllib = require('url');
+const {anonymousRestrictedAccessToken} = require('../shared/urls');
function getTrustedUrlBase() {
return urllib.resolve(config.www.trustedUrlBase, '');
@@ -19,7 +20,7 @@ function getSandboxUrl(path, context) {
if (context && context.user && context.user.restrictedAccessToken) {
return urllib.resolve(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''));
} else {
- return urllib.resolve(config.www.sandboxUrlBase, 'ANONYMOUS/' + (path || ''));
+ return urllib.resolve(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''));
}
}
diff --git a/models/campaigns.js b/models/campaigns.js
index aecba584..bf44b5b8 100644
--- a/models/campaigns.js
+++ b/models/campaigns.js
@@ -72,6 +72,19 @@ async function listWithContentDTAjax(context, params) {
);
}
+async function listOthersByListDTAjax(context, campaignId, listId, params) {
+ return await dtHelpers.ajaxListWithPermissions(
+ context,
+ [{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
+ params,
+ builder => builder.from('campaigns')
+ .innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
+ .whereNot('campaigns.id', campaignId)
+ .where('campaigns.list', listId),
+ ['campaigns.id', 'campaigns.name', 'campaigns.description', 'campaigns.type', 'campaigns.created', 'namespaces.name']
+ );
+}
+
async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) {
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view');
let entity = await tx('campaigns').where('id', id).first();
@@ -309,8 +322,8 @@ async function enforceSendPermissionTx(tx, context, campaignId) {
const requiredPermission = getSendConfigurationPermissionRequiredForSend(campaign, sendConfiguration);
- await shares.enforceEntityPermissionTx(tx, context, 'send_configuration', campaign.send_configuration, requiredPermission);
- await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, requiredPermission);
+ await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', campaign.send_configuration, requiredPermission);
+ await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'send');
}
// This is to handle circular dependency with triggers.js
@@ -319,6 +332,7 @@ Object.assign(module.exports, {
hash,
listDTAjax,
listWithContentDTAjax,
+ listOthersByListDTAjax,
getByIdTx,
getById,
create,
diff --git a/models/lists.js b/models/lists.js
index aa4e6da4..dc4cd265 100644
--- a/models/lists.js
+++ b/models/lists.js
@@ -10,6 +10,7 @@ const shares = require('./shares');
const namespaceHelpers = require('../lib/namespace-helpers');
const fields = require('./fields');
const segments = require('./segments');
+const entitySettings = require('../lib/entity-settings');
const UnsubscriptionMode = require('../shared/lists').UnsubscriptionMode;
@@ -21,6 +22,8 @@ function hash(entity) {
async function listDTAjax(context, params) {
+ const campaignEntityType = entitySettings.getEntityType('campaign');
+
return await dtHelpers.ajaxListWithPermissions(
context,
[{ entityTypeId: 'list', requiredOperations: ['view'] }],
@@ -28,7 +31,16 @@ async function listDTAjax(context, params) {
builder => builder
.from('lists')
.innerJoin('namespaces', 'namespaces.id', 'lists.namespace'),
- ['lists.id', 'lists.name', 'lists.cid', 'lists.subscribers', 'lists.description', 'namespaces.name']
+ ['lists.id', 'lists.name', 'lists.cid', 'lists.subscribers', 'lists.description', 'namespaces.name',
+ { query: builder =>
+ builder.from('campaigns')
+ .whereRaw('campaigns.list = lists.id')
+ .innerJoin(campaignEntityType.permissionsTable, 'campaigns.id', `${campaignEntityType.permissionsTable}.entity`)
+ .where(`${campaignEntityType.permissionsTable}.operation`, 'viewTriggers')
+ .innerJoin('triggers', 'campaigns.id', 'triggers.campaign')
+ .count()
+ }
+ ]
);
}
diff --git a/models/triggers.js b/models/triggers.js
index fc9db830..eb87d289 100644
--- a/models/triggers.js
+++ b/models/triggers.js
@@ -6,10 +6,10 @@ const { enforce, filterObject } = require('../lib/helpers');
const dtHelpers = require('../lib/dt-helpers');
const interoperableErrors = require('../shared/interoperable-errors');
const shares = require('./shares');
-const {EntityVals, ActionVals} = require('../shared/triggers');
+const {EntityVals, EventVals, Entity} = require('../shared/triggers');
const campaigns = require('./campaigns');
-const allowedKeys = new Set(['name', 'description', 'entity', 'action', 'seconds_after', 'enabled', 'source_campaign']);
+const allowedKeys = new Set(['name', 'description', 'entity', 'event', 'seconds_after', 'enabled', 'source_campaign']);
function hash(entity) {
return hasher.hash(filterObject(entity, allowedKeys));
@@ -34,9 +34,10 @@ async function listByCampaignDTAjax(context, campaignId, params) {
params,
builder => builder
.from('triggers')
- .innerJoin('campaigns', 'campaign.id', 'triggers.campaign')
+ .innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
+ .innerJoin('lists', 'lists.id', 'campaigns.list')
.where('triggers.campaign', campaignId),
- [ 'triggers.id', 'triggers.name', 'campaign.list', 'triggers.entity', 'triggers.action', 'triggers.source_campaign', 'triggers.enabled' ]
+ [ 'triggers.id', 'triggers.name', 'triggers.description', 'lists.name', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled' ]
);
});
}
@@ -44,13 +45,13 @@ async function listByCampaignDTAjax(context, campaignId, params) {
async function listByListDTAjax(context, listId, params) {
return await dtHelpers.ajaxListWithPermissions(
context,
- [{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
+ [{ entityTypeId: 'campaign', requiredOperations: ['viewTriggers'] }],
params,
builder => builder
.from('triggers')
- .innerJoin('campaigns', 'campaign.id', 'triggers.campaign')
- .where('campaign.list', listId),
- [ 'triggers.id', 'triggers.name', 'trigger.campaign', 'triggers.entity', 'triggers.action', 'triggers.source_campaign', 'triggers.enabled' ]
+ .innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
+ .where('campaigns.list', listId),
+ [ 'triggers.id', 'triggers.name', 'triggers.description', 'campaigns.name', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled', 'triggers.campaign' ]
);
}
@@ -58,7 +59,7 @@ async function _validateAndPreprocess(tx, context, campaignId, entity) {
enforce(Number.isInteger(entity.seconds_after));
enforce(entity.seconds_after >= 0, 'Seconds after must not be negative');
enforce(entity.entity in EntityVals, 'Invalid entity');
- enforce(entity.action in ActionVals[entity.entity], 'Invalid action');
+ enforce(entity.event in EventVals[entity.entity], 'Invalid event');
if (entity.entity === Entity.CAMPAIGN) {
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.source_campaign, 'view');
@@ -94,7 +95,7 @@ async function updateWithConsistencyCheck(context, campaignId, entity) {
await knex.transaction(async tx => {
await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'manageTriggers');
- const existing = await tx('campaign').where({campaign: campaignId, id: entity.id}).first();
+ const existing = await tx('triggers').where({campaign: campaignId, id: entity.id}).first();
if (!existing) {
throw new interoperableErrors.NotFoundError();
}
@@ -138,7 +139,8 @@ Object.assign(module.exports, {
hash,
getById,
listByCampaignDTAjax,
- listByListDTAjax, create,
+ listByListDTAjax,
+ create,
updateWithConsistencyCheck,
removeTx,
remove,
diff --git a/routes/rest/campaigns.js b/routes/rest/campaigns.js
index e3f4304d..0d4f6d5e 100644
--- a/routes/rest/campaigns.js
+++ b/routes/rest/campaigns.js
@@ -14,6 +14,11 @@ router.postAsync('/campaigns-with-content-table', passport.loggedIn, async (req,
return res.json(await campaigns.listWithContentDTAjax(req.context, req.body));
});
+router.postAsync('/campaigns-others-by-list-table/:campaignId/:listId', passport.loggedIn, async (req, res) => {
+ return res.json(await campaigns.listOthersByListDTAjax(req.context, req.params.campaignId, req.params.listId, req.body));
+});
+
+
router.getAsync('/campaigns-settings/:campaignId', passport.loggedIn, async (req, res) => {
const campaign = await campaigns.getById(req.context, req.params.campaignId, true, campaigns.Content.WITHOUT_SOURCE_CUSTOM);
campaign.hash = campaigns.hash(campaign, campaigns.Content.WITHOUT_SOURCE_CUSTOM);
diff --git a/setup/knex/migrations/20170506102634_v1_to_v2.js b/setup/knex/migrations/20170506102634_v1_to_v2.js
index 5bf06124..94eeaa28 100644
--- a/setup/knex/migrations/20170506102634_v1_to_v2.js
+++ b/setup/knex/migrations/20170506102634_v1_to_v2.js
@@ -7,7 +7,7 @@ const entityTypesAddNamespace = ['list', 'custom_form', 'template', 'campaign',
const shareableEntityTypes = ['list', 'custom_form', 'template', 'campaign', 'report', 'report_template', 'namespace', 'send_configuration', 'mosaico_template'];
const { MailerType, getSystemSendConfigurationId } = require('../../../shared/send-configurations');
const { enforce } = require('../../../lib/helpers');
-const { EntityVals: TriggerEntityVals, ActionVals: TriggerActionVals } = require('../../../shared/triggers');
+const { EntityVals: TriggerEntityVals, EventVals: TriggerEventVals } = require('../../../shared/triggers');
const entityTypesWithFiles = {
campaign: {
@@ -951,7 +951,7 @@ async function migrateAttachments(knex) {
async function migrateTriggers(knex) {
await knex.schema.table('triggers', table => {
table.renameColumn('rule', 'entity');
- table.renameColumn('column', 'action');
+ table.renameColumn('column', 'event');
table.renameColumn('dest_campaign', 'campaign');
table.renameColumn('seconds', 'seconds_after');
});
@@ -964,7 +964,7 @@ async function migrateTriggers(knex) {
enforce(campaign.list === trigger.list, 'The list of trigger and campaign have to be the same.');
enforce(trigger.entity in TriggerEntityVals);
- enforce(trigger.action in TriggerActionVals[trigger.entity]);
+ enforce(trigger.event in TriggerEventVals[trigger.entity]);
}
await knex.schema.table('triggers', table => {
diff --git a/shared/triggers.js b/shared/triggers.js
index 07553a89..7aa5e794 100644
--- a/shared/triggers.js
+++ b/shared/triggers.js
@@ -5,7 +5,7 @@ const Entity = {
CAMPAIGN: 'campaign'
};
-const Action = {
+const Event = {
[Entity.SUBSCRIPTION]: {
CREATED: 'created',
LATEST_OPEN: 'latest_open',
@@ -25,7 +25,7 @@ const EntityVals = {
campaign: 'CAMPAIGN'
};
-const ActionVals = {
+const EventVals = {
[Entity.SUBSCRIPTION]: {
created: 'CREATED',
latest_open: 'LATEST_OPEN',
@@ -42,7 +42,7 @@ const ActionVals = {
module.exports = {
Entity,
- Action,
+ Event,
EntityVals,
- ActionVals
+ EventVals
};
\ No newline at end of file
diff --git a/shared/urls.js b/shared/urls.js
new file mode 100644
index 00000000..79c85efe
--- /dev/null
+++ b/shared/urls.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const anonymousRestrictedAccessToken = 'public';
+
+module.exports = {
+ anonymousRestrictedAccessToken
+};
\ No newline at end of file