diff --git a/nodejs/models/email.js b/nodejs/models/email.js index 8847937..b131715 100644 --- a/nodejs/models/email.js +++ b/nodejs/models/email.js @@ -7,6 +7,16 @@ const conf = require('@simpleworkjs/conf'); var Mail = {}; Mail.send = function(to, subject, message, from){ + // Never let the automated test suite deliver real mail — tests run against + // this app's real routes (notification broadcast, password reset, invite, + // OTP-by-email, …) with NODE_ENV=test, and any of them resolving a real + // recipient list must not actually hit SMTP. Tests already tolerate this + // (see e.g. tests/misc.test.js: "SMTP failure is non-fatal") since none + // assert on real delivery. + if(conf.environment === 'test'){ + return Promise.resolve({accepted: [], rejected: [], response: 'skipped: NODE_ENV=test'}); + } + return new Promise(function(resolve, reject){ var transportOpts = { host: conf.smtp.host || 'localhost', diff --git a/nodejs/views/dashboard.ejs b/nodejs/views/dashboard.ejs index 94a74b7..f12d2f4 100644 --- a/nodejs/views/dashboard.ejs +++ b/nodejs/views/dashboard.ejs @@ -83,27 +83,42 @@ } function toggleFilterInputs() { - const type = document.querySelector('input[name="notif-filter"]:checked').value; + // No radio is checked by default (see f-all-active below) — a "send to + // everyone" option must be a deliberate choice, not whatever happens to + // be pre-selected when someone's just trying the form out. + const checked = document.querySelector('input[name="notif-filter"]:checked'); + const type = checked ? checked.value : null; document.getElementById('notif-group-row').style.display = type === 'group' ? '' : 'none'; document.getElementById('notif-users-row').style.display = type === 'users' ? '' : 'none'; document.getElementById('notif-active-row').style.display = (type === 'group' || type === 'all') ? '' : 'none'; } async function sendNotification() { - const subject = document.getElementById('notif-subject').value.trim(); - const message = document.getElementById('notif-message').value.trim(); - const filterType = document.querySelector('input[name="notif-filter"]:checked').value; - const groupValue = document.getElementById('notif-group').value.trim(); - const usersValue = document.getElementById('notif-users').value.trim(); - const activeOnly = document.getElementById('notif-active-only').checked; - const msgEl = document.getElementById('notif-result'); + const subject = document.getElementById('notif-subject').value.trim(); + const message = document.getElementById('notif-message').value.trim(); + const filterCheck = document.querySelector('input[name="notif-filter"]:checked'); + const groupValue = document.getElementById('notif-group').value.trim(); + const usersValue = document.getElementById('notif-users').value.trim(); + const activeOnly = document.getElementById('notif-active-only').checked; + const msgEl = document.getElementById('notif-result'); + const $compose = $('#notif-subject').closest('.card-body'); if (!subject || !message) { alert('Subject and message are required.'); return; } + if (!filterCheck) { alert('Choose who to send this to.'); return; } + const filterType = filterCheck.value; let filter_value = ''; if (filterType === 'group') filter_value = groupValue; if (filterType === 'users') filter_value = JSON.stringify(usersValue.split(',').map(s => s.trim()).filter(Boolean)); + // Broadcasting to everyone is easy to trigger by accident while just + // trying the form out — make it a deliberate, confirmed action. + if (filterType === 'all' || filterType === 'all_active') { + const label = filterType === 'all' ? 'ALL users (including inactive)' : 'all ACTIVE users'; + const confirmed = await app.util.actionConfirm(`Send this notification to ${label}?`, $compose, 'warning'); + if (!confirmed) return; + } + msgEl.className = 'alert alert-info mt-2'; msgEl.textContent = 'Sending…'; msgEl.style.display = ''; @@ -267,6 +282,7 @@
Compose
+
@@ -279,7 +295,7 @@
- +