Author: Mike Swanson Machine: DESKTOP-0O8A1RL Timestamp: 2026-05-12 06:47:00
185 lines
5.8 KiB
JavaScript
185 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
const os = require('os');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const CREDS_PATH = 'C:\\ProgramData\\dataforth-uploader\\credentials.json';
|
|
const TO = ['mike@azcomputerguru.com']; // add jlehman@dataforth.com once confirmed working
|
|
const FROM = 'sysadmin@dataforth.com';
|
|
const HOST = os.hostname();
|
|
const PREFIX = '[NOTIFY]';
|
|
const SEP_EQ = '='.repeat(60);
|
|
const SEP_DAS = '-'.repeat(60);
|
|
|
|
function loadSmtpCreds() {
|
|
try {
|
|
const raw = fs.readFileSync(CREDS_PATH, 'utf8');
|
|
const c = JSON.parse(raw);
|
|
if (!c.SMTP_USER || !c.SMTP_PASS) {
|
|
process.stderr.write(`${PREFIX} SMTP_USER/SMTP_PASS not present in credentials.json — skipping email\n`);
|
|
return null;
|
|
}
|
|
return { user: c.SMTP_USER, pass: c.SMTP_PASS };
|
|
} catch (e) {
|
|
process.stderr.write(`${PREFIX} Could not load credentials.json: ${e.message} — skipping email\n`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function buildTransport(creds) {
|
|
const nodemailer = require('nodemailer');
|
|
return nodemailer.createTransport({
|
|
host: 'smtp.office365.com',
|
|
port: 587,
|
|
requireTLS: true,
|
|
auth: {
|
|
user: creds.user,
|
|
pass: creds.pass,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function sendMail(subject, text) {
|
|
const creds = loadSmtpCreds();
|
|
if (!creds) return;
|
|
|
|
try {
|
|
const transport = buildTransport(creds);
|
|
await transport.sendMail({
|
|
from: FROM,
|
|
to: TO,
|
|
subject,
|
|
text,
|
|
});
|
|
} catch (e) {
|
|
process.stderr.write(`${PREFIX} sendMail failed: ${e.message}\n`);
|
|
}
|
|
}
|
|
|
|
function buildAlertText(subject, context) {
|
|
const lines = [];
|
|
const ts = new Date().toISOString();
|
|
|
|
lines.push(`Host: ${HOST}`);
|
|
lines.push(`Time: ${ts}`);
|
|
lines.push(`Subject: [TestDataDB] ALERT: ${subject}`);
|
|
lines.push(SEP_DAS);
|
|
|
|
if (context.stage !== undefined) {
|
|
lines.push(`Stage: ${context.stage}`);
|
|
}
|
|
|
|
if (context.error !== undefined) {
|
|
lines.push('');
|
|
lines.push(`Error: ${context.error}`);
|
|
}
|
|
|
|
if (Array.isArray(context.details) && context.details.length > 0) {
|
|
lines.push('');
|
|
for (const line of context.details) {
|
|
lines.push(` ${line}`);
|
|
}
|
|
}
|
|
|
|
if (context.stats !== undefined) {
|
|
lines.push('');
|
|
lines.push(`Stats: ${JSON.stringify(context.stats, null, 2)}`);
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Send an alert email for pipeline errors.
|
|
* Never throws — callers do not guard this.
|
|
*
|
|
* @param {string} subject - Short description; prefixed with [TestDataDB] ALERT:
|
|
* @param {object} [context={}]
|
|
* @param {string} [context.stage] - Pipeline stage where the alert fired
|
|
* @param {string} [context.error] - Error message
|
|
* @param {string[]} [context.details] - Additional detail lines
|
|
* @param {object} [context.stats] - Stats object
|
|
*/
|
|
function alert(subject, context) {
|
|
context = context || {};
|
|
|
|
// Always log to stderr so the service log captures it regardless of SMTP
|
|
try {
|
|
const preview = [
|
|
`${PREFIX} ${SEP_EQ}`,
|
|
`${PREFIX} ALERT: [TestDataDB] ${subject}`,
|
|
`${PREFIX} Host: ${HOST} | Time: ${new Date().toISOString()}`,
|
|
];
|
|
if (context.stage) preview.push(`${PREFIX} Stage: ${context.stage}`);
|
|
if (context.error) preview.push(`${PREFIX} Error: ${context.error}`);
|
|
if (context.details) for (const d of context.details) preview.push(`${PREFIX} ${d}`);
|
|
if (context.stats) preview.push(`${PREFIX} Stats: ${JSON.stringify(context.stats)}`);
|
|
preview.push(`${PREFIX} ${SEP_EQ}`);
|
|
for (const line of preview) process.stderr.write(line + '\n');
|
|
} catch (_) {
|
|
// stderr write failure — nothing to do
|
|
}
|
|
|
|
const text = buildAlertText(subject, context);
|
|
// fire-and-forget; caller never awaits notify.alert
|
|
sendMail(`[TestDataDB] ALERT: ${subject}`, text).catch(() => {});
|
|
}
|
|
|
|
/**
|
|
* Send a daily pipeline summary email.
|
|
* Called from run-pipeline.ps1 via a separate Node invocation.
|
|
*
|
|
* @param {object} stats
|
|
* @param {number} stats.received
|
|
* @param {number} stats.created
|
|
* @param {number} stats.updated
|
|
* @param {number} stats.unchanged
|
|
* @param {number} stats.errors
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function summary(stats) {
|
|
const date = new Date().toISOString().slice(0, 10);
|
|
const status = (stats.errors > 0) ? 'FAIL' : 'OK';
|
|
const subject = `[TestDataDB] Daily pipeline ${status} — ${date}`;
|
|
|
|
const lines = [
|
|
`Host: ${HOST}`,
|
|
`Time: ${new Date().toISOString()}`,
|
|
`Date: ${date}`,
|
|
`Status: ${status}`,
|
|
SEP_DAS,
|
|
`Received: ${stats.received}`,
|
|
`Created: ${stats.created}`,
|
|
`Updated: ${stats.updated}`,
|
|
`Unchanged: ${stats.unchanged}`,
|
|
`Errors: ${stats.errors}`,
|
|
];
|
|
|
|
process.stderr.write(`${PREFIX} Sending daily summary (${status}) for ${date}\n`);
|
|
await sendMail(subject, lines.join('\n'));
|
|
}
|
|
|
|
// Allow run-pipeline.ps1 to invoke `node notify.js summary <json>`
|
|
if (require.main === module) {
|
|
const cmd = process.argv[2];
|
|
if (cmd === 'summary') {
|
|
let stats;
|
|
try {
|
|
stats = JSON.parse(process.argv[3] || '{}');
|
|
} catch (e) {
|
|
process.stderr.write(`${PREFIX} Could not parse stats JSON: ${e.message}\n`);
|
|
process.exit(1);
|
|
}
|
|
summary(stats).then(() => process.exit(0)).catch((e) => {
|
|
process.stderr.write(`${PREFIX} summary failed: ${e.message}\n`);
|
|
process.exit(1);
|
|
});
|
|
} else {
|
|
process.stderr.write(`${PREFIX} Unknown command: ${cmd}\n`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
module.exports = { alert, summary };
|