Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files - Add "type": "module" to package.json to enable ESM by default - Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible - Convert eslint.config.js to ESM with sourceType: "module" - Replace __dirname/__filename with import.meta.dirname/import.meta.filename - Replace require.main === module with process.argv[1] === import.meta.filename - Remove 'use strict' directives (implicit in ESM) - Convert dynamic require() in switch statements to static import lookup maps (dns.js, domains.js, backupformats.js, backupsites.js, network.js) - Extract self-referencing exports.CONSTANT patterns into standalone const declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.) - Lazify SERVICES object in services.js to avoid circular dependency TDZ issues - Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests - Add _setMockApp() to ldapserver.js for ESM-safe test mocking - Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests - Convert backupupload.js to use dynamic imports so --check exits before loading the module graph (which requires BOX_ENV) - Update check-install to use ESM import for infra_version.js - Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations) - All 1315 tests passing Migration stats (AI-assisted using Cursor with Claude): - Wall clock time: ~3-4 hours - Assistant completions: ~80-100 - Estimated token usage: ~1-2M tokens Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
127
src/mail.js
127
src/mail.js
@@ -1,90 +1,85 @@
|
||||
'use strict';
|
||||
import assert from 'node:assert';
|
||||
import BoxError from './boxerror.js';
|
||||
import constants from './constants.js';
|
||||
import * as database from './database.js';
|
||||
import debugModule from 'debug';
|
||||
import * as dig from './dig.js';
|
||||
import * as dns from './dns.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import * as mailer from './mailer.js';
|
||||
import * as mailServer from './mailserver.js';
|
||||
import net from 'node:net';
|
||||
import * as network from './network.js';
|
||||
import nodemailer from 'nodemailer';
|
||||
import * as notifications from './notifications.js';
|
||||
import path from 'node:path';
|
||||
import * as platform from './platform.js';
|
||||
import safe from 'safetydance';
|
||||
import services from './services.js';
|
||||
import shellModule from './shell.js';
|
||||
import superagent from '@cloudron/superagent';
|
||||
import * as validator from './validator.js';
|
||||
import * as _ from './underscore.js';
|
||||
|
||||
exports = module.exports = {
|
||||
const debug = debugModule('box:mail');
|
||||
const shell = shellModule('mail');
|
||||
|
||||
const OWNERTYPE_USER = 'user';
|
||||
const OWNERTYPE_GROUP = 'group';
|
||||
const OWNERTYPE_APP = 'app';
|
||||
const TYPE_MAILBOX = 'mailbox';
|
||||
const TYPE_LIST = 'list';
|
||||
const TYPE_ALIAS = 'alias';
|
||||
const _delByDomain = delByDomain;
|
||||
const _updateDomain = updateDomain;
|
||||
|
||||
export {
|
||||
getStatus,
|
||||
checkConfiguration,
|
||||
|
||||
listDomains,
|
||||
|
||||
getDomain,
|
||||
clearDomains,
|
||||
|
||||
removePrivateFields,
|
||||
|
||||
setDnsRecords,
|
||||
upsertDnsRecords,
|
||||
|
||||
validateName,
|
||||
validateDisplayName,
|
||||
|
||||
setMailFromValidation,
|
||||
setCatchAllAddress,
|
||||
setMailRelay,
|
||||
setMailEnabled,
|
||||
setBanner,
|
||||
|
||||
sendTestMail,
|
||||
|
||||
listMailboxesByDomain,
|
||||
listMailboxes,
|
||||
getMailbox,
|
||||
addMailbox,
|
||||
updateMailbox,
|
||||
delMailbox,
|
||||
|
||||
getAlias,
|
||||
getAliases,
|
||||
setAliases,
|
||||
searchAlias,
|
||||
|
||||
listMailingListsByDomain,
|
||||
getMailingList,
|
||||
addMailingList,
|
||||
updateMailingList,
|
||||
delMailingList,
|
||||
resolveMailingList,
|
||||
|
||||
getStats,
|
||||
|
||||
checkStatus,
|
||||
|
||||
OWNERTYPE_USER: 'user',
|
||||
OWNERTYPE_GROUP: 'group',
|
||||
OWNERTYPE_APP: 'app',
|
||||
|
||||
TYPE_MAILBOX: 'mailbox',
|
||||
TYPE_LIST: 'list',
|
||||
TYPE_ALIAS: 'alias',
|
||||
|
||||
_delByDomain: delByDomain,
|
||||
_updateDomain: updateDomain
|
||||
OWNERTYPE_USER,
|
||||
OWNERTYPE_GROUP,
|
||||
OWNERTYPE_APP,
|
||||
TYPE_MAILBOX,
|
||||
TYPE_LIST,
|
||||
TYPE_ALIAS,
|
||||
_delByDomain,
|
||||
_updateDomain,
|
||||
};
|
||||
|
||||
const assert = require('node:assert'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
database = require('./database.js'),
|
||||
debug = require('debug')('box:mail'),
|
||||
dig = require('./dig.js'),
|
||||
dns = require('./dns.js'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
mailServer = require('./mailserver.js'),
|
||||
net = require('node:net'),
|
||||
network = require('./network.js'),
|
||||
nodemailer = require('nodemailer'),
|
||||
notifications = require('./notifications.js'),
|
||||
path = require('node:path'),
|
||||
platform = require('./platform.js'),
|
||||
safe = require('safetydance'),
|
||||
services = require('./services.js'),
|
||||
shell = require('./shell.js')('mail'),
|
||||
superagent = require('@cloudron/superagent'),
|
||||
validator = require('./validator.js'),
|
||||
_ = require('./underscore.js');
|
||||
|
||||
const DNS_OPTIONS = { timeout: 20000, tries: 4 };
|
||||
const REMOVE_MAILBOX_CMD = path.join(__dirname, 'scripts/rmmailbox.sh');
|
||||
const REMOVE_MAILBOX_CMD = path.join(import.meta.dirname, 'scripts/rmmailbox.sh');
|
||||
|
||||
// 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(',');
|
||||
@@ -169,7 +164,7 @@ function validateDisplayName(name) {
|
||||
}
|
||||
|
||||
function validateOwnerType(type) {
|
||||
const OWNERTYPES = [ exports.OWNERTYPE_USER, exports.OWNERTYPE_GROUP, exports.OWNERTYPE_APP ];
|
||||
const OWNERTYPES = [ OWNERTYPE_USER, OWNERTYPE_GROUP, OWNERTYPE_APP ];
|
||||
return OWNERTYPES.includes(type);
|
||||
}
|
||||
|
||||
@@ -845,8 +840,8 @@ async function listMailboxesByDomain(domain, page, perPage) {
|
||||
// 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`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${TYPE_ALIAS}') AS m2`
|
||||
+ ' 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'
|
||||
@@ -866,8 +861,8 @@ async function listMailboxes(page, perPage) {
|
||||
assert.strictEqual(typeof perPage, 'number');
|
||||
|
||||
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`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${exports.TYPE_ALIAS}') AS m2`
|
||||
+ ` FROM (SELECT * FROM mailboxes WHERE type='${TYPE_MAILBOX}') AS m1`
|
||||
+ ` LEFT JOIN (SELECT * FROM mailboxes WHERE type='${TYPE_ALIAS}') AS m2`
|
||||
+ ' ON m1.name=m2.aliasName AND m1.domain=m2.aliasDomain AND m1.ownerId=m2.ownerId'
|
||||
+ ' GROUP BY m1.name, m1.domain, m1.ownerId'
|
||||
+ ' ORDER BY name LIMIT ?,?';
|
||||
@@ -914,7 +909,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, TYPE_MAILBOX, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
return postProcessMailbox(results[0]);
|
||||
}
|
||||
@@ -941,7 +936,7 @@ async function addMailbox(name, domain, data, auditSource) {
|
||||
if (!validateOwnerType(ownerType)) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type');
|
||||
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, active, storageQuota, messagesQuota, enablePop3) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ name, exports.TYPE_MAILBOX, domain, ownerId, ownerType, active, storageQuota, messagesQuota, enablePop3 ]));
|
||||
[ name, TYPE_MAILBOX, domain, ownerId, ownerType, active, storageQuota, messagesQuota, enablePop3 ]));
|
||||
if (error && error.sqlCode === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error && error.sqlCode === 'ER_NO_REFERENCED_ROW_2' && error.sqlMessage.includes('mailboxes_domain_constraint')) throw new BoxError(BoxError.NOT_FOUND, `no such domain '${domain}'`);
|
||||
if (error) throw error;
|
||||
@@ -1022,7 +1017,7 @@ async function getAlias(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_ALIAS, domain ]);
|
||||
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE name = ? AND type = ? AND domain = ?`, [ name, TYPE_ALIAS, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
@@ -1034,7 +1029,7 @@ async function searchAlias(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE ? LIKE REPLACE(REPLACE(name, '*', '%'), '_', '\\_') AND type = ? AND domain = ?`, [ name, exports.TYPE_ALIAS, domain ]);
|
||||
const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE ? LIKE REPLACE(REPLACE(name, '*', '%'), '_', '\\_') AND type = ? AND domain = ?`, [ name, TYPE_ALIAS, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
@@ -1048,7 +1043,7 @@ async function getAliases(name, domain) {
|
||||
|
||||
const result = await getMailbox(name, domain); // check if mailbox exists
|
||||
if (result === null) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');
|
||||
return await database.query('SELECT name, domain FROM mailboxes WHERE type = ? AND aliasName = ? AND aliasDomain = ? ORDER BY name', [ exports.TYPE_ALIAS, name, domain ]);
|
||||
return await database.query('SELECT name, domain FROM mailboxes WHERE type = ? AND aliasName = ? AND aliasDomain = ? ORDER BY name', [ TYPE_ALIAS, name, domain ]);
|
||||
}
|
||||
|
||||
async function setAliases(name, domain, aliases, auditSource) {
|
||||
@@ -1075,10 +1070,10 @@ async function setAliases(name, domain, aliases, auditSource) {
|
||||
|
||||
const queries = [];
|
||||
// clear existing aliases
|
||||
queries.push({ query: 'DELETE FROM mailboxes WHERE aliasName = ? AND aliasDomain = ? AND type = ?', args: [ name, domain, exports.TYPE_ALIAS ] });
|
||||
queries.push({ query: 'DELETE FROM mailboxes WHERE aliasName = ? AND aliasDomain = ? AND type = ?', args: [ name, domain, TYPE_ALIAS ] });
|
||||
for (const alias of aliases) {
|
||||
queries.push({ query: 'INSERT INTO mailboxes (name, domain, type, aliasName, aliasDomain, ownerId, ownerType) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ alias.name, alias.domain, exports.TYPE_ALIAS, name, domain, results[0].ownerId, results[0].ownerType ] });
|
||||
args: [ alias.name, alias.domain, TYPE_ALIAS, name, domain, results[0].ownerId, results[0].ownerType ] });
|
||||
}
|
||||
|
||||
const [error] = await safe(database.transaction(queries));
|
||||
@@ -1103,7 +1098,7 @@ async function listMailingListsByDomain(domain, page, perPage) {
|
||||
|
||||
query += 'ORDER BY name LIMIT ?,?';
|
||||
|
||||
const results = await database.query(query, [ exports.TYPE_LIST, domain, (page-1)*perPage, perPage ]);
|
||||
const results = await database.query(query, [ TYPE_LIST, domain, (page-1)*perPage, perPage ]);
|
||||
|
||||
results.forEach(function (result) { postProcessMailbox(result); });
|
||||
|
||||
@@ -1114,7 +1109,7 @@ async function getMailingList(name, domain) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND name = ? AND domain = ?', [ exports.TYPE_LIST, name, domain ]);
|
||||
const results = await database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE type = ? AND name = ? AND domain = ?', [ TYPE_LIST, name, domain ]);
|
||||
if (results.length === 0) return null;
|
||||
|
||||
return postProcessMailbox(results[0]);
|
||||
@@ -1140,7 +1135,7 @@ async function addMailingList(name, domain, data, auditSource) {
|
||||
if (!validator.isEmail(members[i])) throw new BoxError(BoxError.BAD_FIELD, 'Invalid mail member: ' + members[i]);
|
||||
}
|
||||
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, membersJson, membersOnly, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_LIST, domain, 'admin', 'user', JSON.stringify(members), membersOnly, active ]));
|
||||
[error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, membersJson, membersOnly, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ name, TYPE_LIST, domain, 'admin', 'user', JSON.stringify(members), membersOnly, active ]));
|
||||
if (error && error.sqlCode === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists');
|
||||
if (error) throw error;
|
||||
|
||||
@@ -1223,9 +1218,9 @@ async function resolveMailingList(listName, listDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.type === exports.TYPE_MAILBOX) { // concrete mailbox
|
||||
if (entry.type === TYPE_MAILBOX) { // concrete mailbox
|
||||
resolvedMembers.push(member);
|
||||
} else if (entry.type === exports.TYPE_ALIAS) { // resolve aliases
|
||||
} else if (entry.type === TYPE_ALIAS) { // resolve aliases
|
||||
toResolve = toResolve.concat(`${entry.aliasName}@${entry.aliasDomain}`);
|
||||
} else { // resolve list members
|
||||
toResolve = toResolve.concat(entry.members);
|
||||
|
||||
Reference in New Issue
Block a user