sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-12 07:04:17
Author: Mike Swanson Machine: DESKTOP-0O8A1RL Timestamp: 2026-05-12 07:04:17
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const qs = require('querystring');
|
||||
|
||||
const CREDS_PATH = 'C:\\ProgramData\\dataforth-uploader\\credentials.json';
|
||||
const TO = ['mike@azcomputerguru.com']; // add jlehman@dataforth.com once confirmed working
|
||||
@@ -12,46 +13,76 @@ const PREFIX = '[NOTIFY]';
|
||||
const SEP_EQ = '='.repeat(60);
|
||||
const SEP_DAS = '-'.repeat(60);
|
||||
|
||||
function loadSmtpCreds() {
|
||||
function loadGraphCreds() {
|
||||
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`);
|
||||
if (!c.GRAPH_TENANT_ID || !c.GRAPH_CLIENT_ID || !c.GRAPH_CLIENT_SECRET) {
|
||||
process.stderr.write(`${PREFIX} GRAPH_TENANT_ID/CLIENT_ID/CLIENT_SECRET not in credentials.json — skipping email\n`);
|
||||
return null;
|
||||
}
|
||||
return { user: c.SMTP_USER, pass: c.SMTP_PASS };
|
||||
return { tenantId: c.GRAPH_TENANT_ID, clientId: c.GRAPH_CLIENT_ID, clientSecret: c.GRAPH_CLIENT_SECRET };
|
||||
} 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,
|
||||
},
|
||||
function httpsPost(hostname, path, headers, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const data = Buffer.from(body);
|
||||
const req = https.request({ hostname, path, method: 'POST', headers: { ...headers, 'Content-Length': data.length } }, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (c) => chunks.push(c));
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(data);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function getToken(creds) {
|
||||
const body = qs.stringify({
|
||||
grant_type: 'client_credentials',
|
||||
client_id: creds.clientId,
|
||||
client_secret: creds.clientSecret,
|
||||
scope: 'https://graph.microsoft.com/.default',
|
||||
});
|
||||
const res = await httpsPost(
|
||||
'login.microsoftonline.com',
|
||||
`/${creds.tenantId}/oauth2/v2.0/token`,
|
||||
{ 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body
|
||||
);
|
||||
const parsed = JSON.parse(res.body);
|
||||
if (!parsed.access_token) throw new Error(`Token error: ${parsed.error} — ${parsed.error_description}`);
|
||||
return parsed.access_token;
|
||||
}
|
||||
|
||||
async function sendMail(subject, text) {
|
||||
const creds = loadSmtpCreds();
|
||||
const creds = loadGraphCreds();
|
||||
if (!creds) return;
|
||||
|
||||
try {
|
||||
const transport = buildTransport(creds);
|
||||
await transport.sendMail({
|
||||
from: FROM,
|
||||
to: TO,
|
||||
subject,
|
||||
text,
|
||||
const token = await getToken(creds);
|
||||
const payload = JSON.stringify({
|
||||
message: {
|
||||
subject,
|
||||
body: { contentType: 'Text', content: text },
|
||||
toRecipients: TO.map((a) => ({ emailAddress: { address: a } })),
|
||||
from: { emailAddress: { address: FROM } },
|
||||
},
|
||||
});
|
||||
const res = await httpsPost(
|
||||
'graph.microsoft.com',
|
||||
`/v1.0/users/${FROM}/sendMail`,
|
||||
{ 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
||||
payload
|
||||
);
|
||||
if (res.status !== 202) {
|
||||
process.stderr.write(`${PREFIX} sendMail HTTP ${res.status}: ${res.body.slice(0, 200)}\n`);
|
||||
}
|
||||
} catch (e) {
|
||||
process.stderr.write(`${PREFIX} sendMail failed: ${e.message}\n`);
|
||||
}
|
||||
@@ -66,9 +97,7 @@ function buildAlertText(subject, context) {
|
||||
lines.push(`Subject: [TestDataDB] ALERT: ${subject}`);
|
||||
lines.push(SEP_DAS);
|
||||
|
||||
if (context.stage !== undefined) {
|
||||
lines.push(`Stage: ${context.stage}`);
|
||||
}
|
||||
if (context.stage !== undefined) lines.push(`Stage: ${context.stage}`);
|
||||
|
||||
if (context.error !== undefined) {
|
||||
lines.push('');
|
||||
@@ -77,9 +106,7 @@ function buildAlertText(subject, context) {
|
||||
|
||||
if (Array.isArray(context.details) && context.details.length > 0) {
|
||||
lines.push('');
|
||||
for (const line of context.details) {
|
||||
lines.push(` ${line}`);
|
||||
}
|
||||
for (const line of context.details) lines.push(` ${line}`);
|
||||
}
|
||||
|
||||
if (context.stats !== undefined) {
|
||||
@@ -104,7 +131,6 @@ function buildAlertText(subject, context) {
|
||||
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}`,
|
||||
@@ -117,18 +143,14 @@ function alert(subject, context) {
|
||||
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
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
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
|
||||
@@ -139,8 +161,8 @@ function alert(subject, context) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function summary(stats) {
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
const status = (stats.errors > 0) ? 'FAIL' : 'OK';
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
const status = (stats.errors > 0) ? 'FAIL' : 'OK';
|
||||
const subject = `[TestDataDB] Daily pipeline ${status} — ${date}`;
|
||||
|
||||
const lines = [
|
||||
@@ -160,7 +182,6 @@ async function summary(stats) {
|
||||
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') {
|
||||
|
||||
Reference in New Issue
Block a user