rework mail domain stats

We can now show list count, alias count as well in the mail domains UI
This commit is contained in:
Girish Ramakrishnan
2025-12-05 12:54:48 +01:00
parent 425e196dfc
commit f714cd66f7
9 changed files with 106 additions and 163 deletions
+35 -40
View File
@@ -25,9 +25,8 @@ exports = module.exports = {
sendTestMail,
getMailboxCount,
listMailboxesByDomain,
listMailboxes,
listAllMailboxes,
getMailbox,
addMailbox,
updateMailbox,
@@ -38,13 +37,14 @@ exports = module.exports = {
setAliases,
searchAlias,
getListCount,
getLists,
getList,
addList,
updateList,
delList,
resolveList,
listMailingListsByDomain,
getMailingList,
addMailingList,
updateMailingList,
delMailingList,
resolveMailingList,
getStats,
checkStatus,
@@ -70,7 +70,6 @@ const assert = require('node:assert'),
eventlog = require('./eventlog.js'),
mailer = require('./mailer.js'),
mailServer = require('./mailserver.js'),
mysql = require('mysql2'),
net = require('node:net'),
network = require('./network.js'),
nodemailer = require('nodemailer'),
@@ -87,7 +86,7 @@ const assert = require('node:assert'),
const DNS_OPTIONS = { timeout: 20000, tries: 4 };
const REMOVE_MAILBOX_CMD = path.join(__dirname, 'scripts/rmmailbox.sh');
// if you add a field here, listMailboxes has to be updated
// if you add a field here, listMailboxes* has to be updated
const MAILBOX_FIELDS = [ 'name', 'type', 'ownerId', 'ownerType', 'aliasName', 'aliasDomain', 'creationTime', 'membersJson', 'membersOnly', 'domain', 'active', 'enablePop3', 'storageQuota', 'messagesQuota' ].join(',');
const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimKeyJson', 'dkimSelector', 'bannerJson' ].join(',');
@@ -837,14 +836,13 @@ async function sendTestMail(domain, to) {
await mailer.sendTestMail(result.domain, to);
}
async function listMailboxes(domain, search, page, perPage) {
async function listMailboxesByDomain(domain, page, perPage) {
assert.strictEqual(typeof domain, 'string');
assert(typeof search === 'string' || search === null);
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
const escapedSearch = mysql.escape('%' + search + '%'); // this also quotes the string
const searchQuery = search ? ` HAVING (name LIKE ${escapedSearch} OR aliasNames LIKE ${escapedSearch} OR aliasDomains LIKE ${escapedSearch})` : ''; // having instead of where because of aggregated columns use
// const escapedSearch = mysql.escape('%' + search + '%'); // this also quotes the string
// const searchQuery = search ? ` HAVING (name LIKE ${escapedSearch} OR aliasNames LIKE ${escapedSearch} OR aliasDomains LIKE ${escapedSearch})` : ''; // having instead of where because of aggregated columns use
const query = 'SELECT m1.name AS name, m1.domain AS domain, m1.ownerId AS ownerId, m1.ownerType as ownerType, m1.active as active, JSON_ARRAYAGG(m2.name) AS aliasNames, JSON_ARRAYAGG(m2.domain) AS aliasDomains, m1.enablePop3 AS enablePop3, m1.storageQuota AS storageQuota, m1.messagesQuota AS messagesQuota '
+ ` FROM (SELECT * FROM mailboxes WHERE type='${exports.TYPE_MAILBOX}') AS m1`
@@ -852,7 +850,7 @@ async function listMailboxes(domain, search, page, perPage) {
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
+ ' WHERE m1.domain = ?'
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
+ searchQuery
// + searchQuery
+ ' ORDER BY name LIMIT ?,?';
const results = await database.query(query, [ domain, (page-1)*perPage, perPage ]);
@@ -863,7 +861,7 @@ async function listMailboxes(domain, search, page, perPage) {
return results;
}
async function listAllMailboxes(page, perPage) {
async function listMailboxes(page, perPage) {
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
@@ -882,12 +880,18 @@ async function listAllMailboxes(page, perPage) {
return results;
}
async function getMailboxCount(domain) {
async function getStats(domain) {
assert.strictEqual(typeof domain, 'string');
const results = await database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_MAILBOX, domain ]);
const mailboxes = await listMailboxesByDomain(domain, 1, 10000);
const mailingLists = await listMailingListsByDomain(domain, 1, 10000);
return results[0].total;
return {
mailboxCount: mailboxes.length,
pop3Count: mailboxes.filter(mb => mb.enablePop3).length,
aliasCount: mailboxes.map(mb => mb.aliases.length).reduce((a, b) => a + b, 0),
mailingListCount: mailingLists.length
};
}
async function delByDomain(domain) {
@@ -900,7 +904,7 @@ async function get(name, domain) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof domain, 'string');
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND domain = ?', [ name, domain ]);
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE name = ? AND domain = ?`, [ name, domain ]);
if (results.length === 0) return null;
return postProcessMailbox(results[0]);
@@ -910,7 +914,7 @@ async function getMailbox(name, domain) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof domain, 'string');
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND type = ? AND domain = ?', [ name, exports.TYPE_MAILBOX, domain ]);
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE name = ? AND type = ? AND domain = ?`, [ name, exports.TYPE_MAILBOX, domain ]);
if (results.length === 0) return null;
return postProcessMailbox(results[0]);
}
@@ -1089,22 +1093,13 @@ async function setAliases(name, domain, aliases, auditSource) {
await eventlog.add(eventlog.ACTION_MAIL_MAILBOX_UPDATE, auditSource, { name, domain, aliases });
}
async function getListCount(domain) {
async function listMailingListsByDomain(domain, page, perPage) {
assert.strictEqual(typeof domain, 'string');
const results = await database.query('SELECT COUNT(*) AS total FROM mailboxes WHERE type = ? AND domain = ?', [ exports.TYPE_LIST, domain ]);
return results[0].total;
}
async function getLists(domain, search, page, perPage) {
assert.strictEqual(typeof domain, 'string');
assert(typeof search === 'string' || search === null);
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
let query = `SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE type = ? AND domain = ?`;
if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
// if (search) query += ' AND (name LIKE ' + mysql.escape('%' + search + '%') + ' OR membersJson LIKE ' + mysql.escape('%' + search + '%') + ')';
query += 'ORDER BY name LIMIT ?,?';
@@ -1115,7 +1110,7 @@ async function getLists(domain, search, page, perPage) {
return results;
}
async function getList(name, domain) {
async function getMailingList(name, domain) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof domain, 'string');
@@ -1125,7 +1120,7 @@ async function getList(name, domain) {
return postProcessMailbox(results[0]);
}
async function addList(name, domain, data, auditSource) {
async function addMailingList(name, domain, data, auditSource) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof data, 'object');
@@ -1152,7 +1147,7 @@ async function addList(name, domain, data, auditSource) {
await eventlog.add(eventlog.ACTION_MAIL_LIST_ADD, auditSource, { name, domain, members, membersOnly, active });
}
async function updateList(name, domain, data, auditSource) {
async function updateMailingList(name, domain, data, auditSource) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof data, 'object');
@@ -1179,7 +1174,7 @@ async function updateList(name, domain, data, auditSource) {
await eventlog.add(eventlog.ACTION_MAIL_LIST_UPDATE, auditSource, { name, domain, oldMembers: result.members, members, membersOnly, active });
}
async function delList(name, domain, auditSource) {
async function delMailingList(name, domain, auditSource) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof auditSource, 'object');
@@ -1192,14 +1187,14 @@ async function delList(name, domain, auditSource) {
}
// resolves the members of a list. i.e the lists and aliases
async function resolveList(listName, listDomain) {
async function resolveMailingList(listName, listDomain) {
assert.strictEqual(typeof listName, 'string');
assert.strictEqual(typeof listDomain, 'string');
const mailDomains = await listDomains();
const mailInDomains = mailDomains.filter(function (d) { return d.enabled; }).map(function (d) { return d.domain; }).join(',');
const list = await getList(listName, listDomain);
const list = await getMailingList(listName, listDomain);
if (!list) throw new BoxError(BoxError.NOT_FOUND, 'List not found');
const resolvedMembers = [], visited = []; // slice creates a copy of array
@@ -1217,7 +1212,7 @@ async function resolveList(listName, listDomain) {
const member =`${memberName}@${memberDomain}`; // cleaned up without any '+' subaddress
if (visited.includes(member)) {
debug(`resolveList: list ${listName}@${listDomain} has a recursion at member ${member}`);
debug(`resolveMailingList: list ${listName}@${listDomain} has a recursion at member ${member}`);
continue;
}
visited.push(member);