16578 mailout api from mailing list
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
import axios from "axios";
|
||||
import CryptoJS from "crypto-js";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
getToken,
|
||||
azureHeaders,
|
||||
azureHeadersPaged,
|
||||
consoleLogger,
|
||||
sendEmail,
|
||||
} from "../../../actions";
|
||||
import { formatDates } from "../../../components/utils";
|
||||
|
||||
var NotifyClient = require("notifications-node-client").NotifyClient;
|
||||
|
||||
const notifyClient = new NotifyClient(process.env.NOTIFY_API_KEY);
|
||||
|
||||
const WORDKEY = process.env.HASHKEY;
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
|
||||
|
||||
const hashAPIPath = (queryPath) => {
|
||||
const hash = CryptoJS.HmacSHA256(
|
||||
queryPath,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
).toString(CryptoJS.enc.Hex);
|
||||
return queryPath.includes("?") ? `&hash=${hash}` : `?hash=${hash}`;
|
||||
};
|
||||
|
||||
const encryptDocReference = (documentRef) => {
|
||||
const hash = CryptoJS.HmacSHA256(
|
||||
`documents/download/${documentRef}`,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
).toString(CryptoJS.enc.Hex);
|
||||
return `/api/documents/download/${documentRef}?hash=${hash}`;
|
||||
};
|
||||
|
||||
const flattenWatchlistEntry = (entry) => {
|
||||
const flattened = { ...entry };
|
||||
if (entry.pinswg_Contact) {
|
||||
flattened.contactid = entry.pinswg_Contact.contactid;
|
||||
flattened.contact_email = entry.pinswg_Contact.emailaddress1;
|
||||
flattened.firstname = entry.pinswg_Contact.firstname;
|
||||
flattened.lastname = entry.pinswg_Contact.lastname;
|
||||
delete flattened.pinswg_Contact;
|
||||
}
|
||||
return flattened;
|
||||
};
|
||||
|
||||
// s
|
||||
const buildNotifyPayloads = (watchlistByEmail) => {
|
||||
const reference = "PEDW-CASE-UPDATES";
|
||||
const templateId = "217f7f96-2f8a-4034-a27f-9ffcf8d89edb";
|
||||
|
||||
const formatDocuments = (docs = []) =>
|
||||
docs
|
||||
.map((doc) => {
|
||||
const ref = doc.pinswg_isharedocumentreference || "No Ref";
|
||||
const name = doc.pinswg_name || "Untitled";
|
||||
const date =
|
||||
formatDates(
|
||||
doc.pinswg_documentpublisheddate?.split("T")[0]
|
||||
) || "No Date";
|
||||
const link =
|
||||
process.env.API_ROOT + doc.pinswg_hashlink || "[No Link]";
|
||||
return ` - ${ref}: ${name} (${date})\n Link: ${link}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const formatSipEvents = (events = []) =>
|
||||
events
|
||||
.map((event) => {
|
||||
const name = event.pinswg_name || "Unnamed Event";
|
||||
const date =
|
||||
formatDates(
|
||||
event.pinswg_dateeventrequested?.split("T")[0]
|
||||
) || "No Date";
|
||||
return ` - ${name} on ${date}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const formatReps = (reps = []) =>
|
||||
reps
|
||||
.map((rep) => {
|
||||
const start =
|
||||
formatDates(rep.pinswg_consultationopen?.split("T")[0]) ||
|
||||
"N/A";
|
||||
const end =
|
||||
formatDates(rep.pinswg_consultationclose?.split("T")[0]) ||
|
||||
"N/A";
|
||||
return ` - Open: ${start}to Close: ${end}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
const extractNameFromEmail = (email) =>
|
||||
email
|
||||
.split("@")[0]
|
||||
.replace(/\./g, " ")
|
||||
.split(/[\s_-]+/)
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join(" ");
|
||||
|
||||
return Object.entries(watchlistByEmail)
|
||||
.map(([email, entries]) => {
|
||||
const contactName =
|
||||
entries[0].firstname + " " + entries[0].lastname;
|
||||
const contactId = entries[0].contactId;
|
||||
|
||||
const filteredEntries = entries.filter(
|
||||
(entry) =>
|
||||
(entry.documents && entry.documents.length > 0) ||
|
||||
(entry.sipEvents && entry.sipEvents.length > 0) ||
|
||||
(entry.repsPeriods && entry.repsPeriods.length > 0)
|
||||
);
|
||||
if (filteredEntries.length === 0) return null;
|
||||
|
||||
const personalisation = {
|
||||
contact_name: contactName,
|
||||
case_sections: filteredEntries
|
||||
.map((entry) => {
|
||||
const caseRef =
|
||||
entry[
|
||||
"_pinswg_watchedcase_value@OData.Community.Display.V1.FormattedValue"
|
||||
] || "Unknown Case";
|
||||
|
||||
const docSection = entry.documents?.length
|
||||
? `Documents added or updated:\n${formatDocuments(
|
||||
entry.documents
|
||||
)}\n`
|
||||
: "";
|
||||
const sipSection = entry.sipEvents?.length
|
||||
? `SIP Events created:\n${formatSipEvents(
|
||||
entry.sipEvents
|
||||
)}\n`
|
||||
: "";
|
||||
const repsSection = entry.repsPeriods?.length
|
||||
? `Consultation Periods:\n${formatReps(
|
||||
entry.repsPeriods
|
||||
)}\n`
|
||||
: "";
|
||||
|
||||
const caseUnsubscribelink = `Unsubscribe to updates on this case https://${process.env.API_ROOT}/unsubscribe/${entry.pinswg_watchlistid}`;
|
||||
|
||||
return `Case: ${caseRef}\n${[
|
||||
docSection,
|
||||
sipSection,
|
||||
repsSection,
|
||||
caseUnsubscribelink,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")}`;
|
||||
})
|
||||
.join("\n\n"),
|
||||
unsubscribeLink: `Unsubscribe by selecting this link if you no longer want to receive any updates ${
|
||||
process.env.API_ROOT + "unsubscribeall/" + "contactId"
|
||||
}`,
|
||||
};
|
||||
|
||||
return {
|
||||
email_address: email,
|
||||
template_id: templateId,
|
||||
personalisation,
|
||||
reference,
|
||||
oneClickUnsubscribeURL:
|
||||
process.env.API_ROOT + "unsubscribe/" + "eee",
|
||||
};
|
||||
})
|
||||
.filter(Boolean); // Remove any nulls from filtered-out users
|
||||
};
|
||||
|
||||
export default async function CombinedApiProxy(req, res) {
|
||||
try {
|
||||
const token = await getToken();
|
||||
|
||||
// Step 1: Fetch all watchlist entries
|
||||
const watchlistQuery = `pinswg_watchlists?$count=true&$filter=pinswg_emailnotifications ne null&$expand=pinswg_Contact($select=contactid,emailaddress1,firstname,lastname)&$select=pinswg_emailnotifications,_pinswg_watchedcase_value`;
|
||||
const watchlistRes = await axios.get(
|
||||
WEBAPI_URL + watchlistQuery + hashAPIPath(watchlistQuery),
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
const watchlistData = watchlistRes.data.value.map(
|
||||
flattenWatchlistEntry
|
||||
);
|
||||
|
||||
// Step 2: For each entry, gather docs and SIP events
|
||||
const now = new Date();
|
||||
const twoWeeksAgo = new Date();
|
||||
twoWeeksAgo.setDate(now.getDate() - 7);
|
||||
const nowStr = now.toISOString();
|
||||
const twoWeeksAgoStr = twoWeeksAgo.toISOString();
|
||||
|
||||
const results = await Promise.all(
|
||||
watchlistData.map(async (entry) => {
|
||||
const incidentID = entry._pinswg_watchedcase_value;
|
||||
|
||||
// === Fetch documents ===
|
||||
let documents = [];
|
||||
try {
|
||||
const docsQuery = `pinswg_documents?$count=true&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq ${incidentID} and pinswg_documentpublisheddate ne null and pinswg_documentpublisheddate ge ${twoWeeksAgoStr} and pinswg_documentpublisheddate le ${nowStr}&$select=pinswg_name,pinswg_isharedocumentlocations,_pinswg_documentids_value,pinswg_isharelabelcasetype,pinswg_isharelabellpaname,pinswg_publishtoweb,pinswg_uploadstatus,pinswg_isharedocumentclassification,pinswg_isharedocumentreference,pinswg_name,pinswg_latestpublishedversion,pinswg_latestpublisheddate,pinswg_documentpublisheddate`;
|
||||
const docsRes = await axios.get(
|
||||
WEBAPI_URL + docsQuery + hashAPIPath(docsQuery),
|
||||
azureHeadersPaged(token.access_token)
|
||||
);
|
||||
documents = docsRes.data.value.map((doc) => ({
|
||||
...doc,
|
||||
pinswg_hashlink: encryptDocReference(
|
||||
doc.pinswg_isharedocumentreference
|
||||
),
|
||||
}));
|
||||
} catch (err) {
|
||||
consoleLogger(
|
||||
`Document fetch failed for incidentID: ${incidentID}`
|
||||
);
|
||||
}
|
||||
|
||||
// === Fetch SIP events ===
|
||||
let sipEvents = [];
|
||||
try {
|
||||
const sipsQuery = `pinswg_sipses?$filter=_pinswg_sipscase_value eq ${incidentID}&$select=pinswg_sipsid`;
|
||||
const sipsRes = await axios.get(
|
||||
WEBAPI_URL + sipsQuery + hashAPIPath(sipsQuery),
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
const sipsRecords = _.get(sipsRes, "data.value", []);
|
||||
if (sipsRecords.length > 0) {
|
||||
const sipsId = sipsRecords[0].pinswg_sipsid;
|
||||
const eventsQuery = `pinswg_sipsevents?$count=true&$filter=_pinswg_sipseventsid_value eq ${sipsId} and modifiedon ge ${twoWeeksAgoStr} and modifiedon le ${nowStr}&$select=pinswg_name,pinswg_dateeventrequested,pinswg_typeofevent,createdon,modifiedon`;
|
||||
const eventsRes = await axios.get(
|
||||
WEBAPI_URL + eventsQuery + hashAPIPath(eventsQuery),
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
sipEvents = eventsRes.data.value;
|
||||
}
|
||||
} catch (err) {
|
||||
consoleLogger(
|
||||
`SIP events fetch failed for incidentID: ${incidentID}`
|
||||
);
|
||||
}
|
||||
|
||||
// === Fetch Reps open events ===
|
||||
let repsPeriods;
|
||||
|
||||
try {
|
||||
const repsQuery = `pinswg_sipses?$filter=_pinswg_sipscase_value eq ${incidentID} and pinswg_consultationopen ne null &$select=pinswg_consultationopen,pinswg_consultationclose`;
|
||||
const repsRes = await axios.get(
|
||||
WEBAPI_URL + repsQuery + hashAPIPath(repsQuery),
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
|
||||
repsPeriods = repsRes.data.value;
|
||||
} catch (err) {
|
||||
consoleLogger(
|
||||
`Reps fetch failed for incidentID: ${incidentID}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...entry,
|
||||
repsPeriods,
|
||||
documents,
|
||||
sipEvents,
|
||||
};
|
||||
})
|
||||
);
|
||||
// res.status(200).json(results);
|
||||
// Step 3: Group results by contact_email
|
||||
const groupedByEmail = _.groupBy(results, "contact_email");
|
||||
|
||||
//Convert to array format
|
||||
// const groupedArray = Object.entries(groupedByEmail).map(([email, entries]) => ({ contact_email: email, entries }));
|
||||
|
||||
//console.log(buildNotifyPayloads(groupedByEmail));
|
||||
|
||||
const payloads = buildNotifyPayloads(groupedByEmail); // watchlistByEmail should be defined
|
||||
|
||||
// Iterate and send
|
||||
const sendingResults = [];
|
||||
|
||||
for (const payload of payloads) {
|
||||
if (!payload) continue; // skip null entries
|
||||
|
||||
console.log(payload);
|
||||
const result = {
|
||||
email: payload.email_address,
|
||||
reference: payload.reference,
|
||||
template_id: payload.template_id,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await notifyClient.sendEmail(
|
||||
payload.template_id,
|
||||
payload.email_address,
|
||||
{
|
||||
personalisation: payload.personalisation,
|
||||
reference: payload.reference,
|
||||
}
|
||||
);
|
||||
result.status = "success";
|
||||
result.notify_id = response.id;
|
||||
} catch (error) {
|
||||
result.status = "error";
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
sendingResults.push(result);
|
||||
}
|
||||
|
||||
res.status(200).json({ sendingResults });
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
res.status(500).json({
|
||||
error: "An error occurred while retrieving combined data.",
|
||||
details: error.message || error.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import axios from "axios";
|
||||
import CryptoJS from "crypto-js";
|
||||
import _ from "lodash";
|
||||
import { azureHeadersPaged, consoleLogger, getToken } from "../../../actions";
|
||||
|
||||
const WORDKEY = process.env.HASHKEY;
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
|
||||
|
||||
/**
|
||||
* Generates a hash-secured query string.
|
||||
*/
|
||||
const hashAPIPath = (queryPath) => {
|
||||
const hash = CryptoJS.HmacSHA256(
|
||||
queryPath,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
).toString(CryptoJS.enc.Hex);
|
||||
return queryPath.includes("?") ? `&hash=${hash}` : `?hash=${hash}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypts a document reference into a secure hash link.
|
||||
*/
|
||||
const encryptDocReference = (documentRef) => {
|
||||
const hash = CryptoJS.HmacSHA256(
|
||||
`documents/download/${documentRef}`,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
).toString(CryptoJS.enc.Hex);
|
||||
return `/api/documents/download/${documentRef}?hash=${hash}`;
|
||||
};
|
||||
|
||||
export default async function ApiProxy(req, res) {
|
||||
const incidentID = req.query.incidentid;
|
||||
|
||||
if (!incidentID) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "incidentid query parameter is required." });
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await getToken();
|
||||
|
||||
// Date range: today and 7 days ago
|
||||
const now = new Date();
|
||||
const twoWeeksAgo = new Date();
|
||||
twoWeeksAgo.setDate(now.getDate() - 7);
|
||||
|
||||
const nowStr = now.toISOString();
|
||||
const twoWeeksAgoStr = twoWeeksAgo.toISOString();
|
||||
|
||||
// Build query
|
||||
const query =
|
||||
`pinswg_documents?$count=true` +
|
||||
`&$filter=pinswg_publishtoweb eq true and _pinswg_documentids_value eq ${incidentID}` +
|
||||
` and pinswg_documentpublisheddate ne null` +
|
||||
` and pinswg_documentpublisheddate ge ${twoWeeksAgoStr}` +
|
||||
` and pinswg_documentpublisheddate le ${nowStr}` +
|
||||
`&$select=pinswg_isharedocumentlocations,` +
|
||||
`_pinswg_documentids_value,` +
|
||||
`pinswg_isharelabelcasetype,` +
|
||||
`pinswg_isharelabellpaname,` +
|
||||
`pinswg_publishtoweb,` +
|
||||
`pinswg_uploadstatus,` +
|
||||
`pinswg_isharedocumentclassification,` +
|
||||
`pinswg_isharedocumentreference,` +
|
||||
`pinswg_name,` +
|
||||
`pinswg_latestpublishedversion,` +
|
||||
`pinswg_latestpublisheddate,` +
|
||||
`pinswg_documentpublisheddate`;
|
||||
|
||||
const fullUrl = `${WEBAPI_URL}${query}${hashAPIPath(query)}`;
|
||||
|
||||
console.log("Querying documents from:", fullUrl);
|
||||
|
||||
const { data } = await axios.get(
|
||||
fullUrl,
|
||||
azureHeadersPaged(token.access_token)
|
||||
);
|
||||
|
||||
// Add hash download links to each result
|
||||
const resultsWithLinks = data.value.map((doc) => ({
|
||||
...doc,
|
||||
pinswg_hashlink: encryptDocReference(
|
||||
doc.pinswg_isharedocumentreference
|
||||
),
|
||||
}));
|
||||
|
||||
res.status(200).json({ ...data, value: resultsWithLinks });
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
res.status(500).json({
|
||||
error: "Failed to fetch document details.",
|
||||
details: error.message || error.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import axios from "axios";
|
||||
import CryptoJS from "crypto-js";
|
||||
import _ from "lodash";
|
||||
import { getToken, azureHeaders, consoleLogger } from "../../../actions";
|
||||
|
||||
const WORDKEY = process.env.HASHKEY;
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
|
||||
|
||||
/**
|
||||
* Generate hashed query string for secured API access.
|
||||
*/
|
||||
const hashAPIPath = (queryPath) => {
|
||||
const hash = CryptoJS.HmacSHA256(
|
||||
queryPath,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
).toString(CryptoJS.enc.Hex);
|
||||
|
||||
return queryPath.includes("?") ? `&hash=${hash}` : `?hash=${hash}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main proxy handler.
|
||||
*/
|
||||
export default async function ApiProxy(req, res) {
|
||||
const { incidentID } = req.query;
|
||||
|
||||
if (!incidentID) {
|
||||
return res.status(400).json({ error: "incidentID is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
const token = await getToken();
|
||||
|
||||
// Step 1: Get SIPs record linked to incident
|
||||
const sipsQuery = `pinswg_sipses?$filter=_pinswg_sipscase_value eq ${incidentID}&$select=pinswg_sipsid`;
|
||||
const sipsUrl = `${WEBAPI_URL}${sipsQuery}${hashAPIPath(sipsQuery)}`;
|
||||
const sipsResponse = await axios.get(
|
||||
sipsUrl,
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
|
||||
const sipsRecords = _.get(sipsResponse, "data.value", []);
|
||||
if (sipsRecords.length === 0) {
|
||||
return res
|
||||
.status(404)
|
||||
.json({ error: "No SIPs record found for this incidentID." });
|
||||
}
|
||||
|
||||
const sipsId = sipsRecords[0].pinswg_sipsid;
|
||||
|
||||
// Step 2: Get related SIP Events
|
||||
const eventsQuery = `pinswg_sipsevents?$count=true&$filter=_pinswg_sipseventsid_value eq ${sipsId}&$select=pinswg_name,pinswg_dateeventrequested,pinswg_typeofevent,createdon`;
|
||||
|
||||
///&$select=pinswg_name,pinswg_dateeventrequested,pinswg_typeofevent
|
||||
const eventsUrl = `${WEBAPI_URL}${eventsQuery}${hashAPIPath(
|
||||
eventsQuery
|
||||
)}`;
|
||||
const eventsResponse = await axios.get(
|
||||
eventsUrl,
|
||||
azureHeaders(token.access_token)
|
||||
);
|
||||
|
||||
res.status(200).json(eventsResponse.data);
|
||||
} catch (error) {
|
||||
consoleLogger(error);
|
||||
res.status(500).json({
|
||||
error: "An error occurred while retrieving data.",
|
||||
details: error.message || error.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import axios from "axios";
|
||||
import CryptoJS from "crypto-js";
|
||||
import _ from "lodash";
|
||||
import { getToken, azureHeaders, consoleLogger } from "../../../actions";
|
||||
|
||||
const WORDKEY = process.env.HASHKEY;
|
||||
|
||||
const WEBAPI_URL =
|
||||
process.env.RELAY_ROOT ||
|
||||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
|
||||
|
||||
const hashAPIPath = (queryPath) => {
|
||||
var hashlink = CryptoJS.HmacSHA256(
|
||||
queryPath,
|
||||
CryptoJS.enc.Hex.parse(WORDKEY)
|
||||
);
|
||||
hashlink = hashlink.toString(CryptoJS.enc.Hex);
|
||||
|
||||
//return "&hash=" + hashlink;
|
||||
|
||||
return (queryPath.indexOf("?") > -1 ? "&hash=" : "?hash=") + hashlink;
|
||||
};
|
||||
|
||||
function flattenWatchlistEntry(entry) {
|
||||
const flattened = { ...entry };
|
||||
|
||||
if (entry.pinswg_Contact) {
|
||||
flattened.contactid = entry.pinswg_Contact.contactid;
|
||||
flattened.contact_email = entry.pinswg_Contact.emailaddress1;
|
||||
delete flattened.pinswg_Contact; // Optional: remove nested object
|
||||
}
|
||||
|
||||
return flattened;
|
||||
}
|
||||
|
||||
export default async function ApiProxy(req, res) {
|
||||
var token = await getToken();
|
||||
|
||||
var queryUrl =
|
||||
"pinswg_watchlists?$count=true&$filter=pinswg_emailnotifications ne null&$expand=pinswg_Contact($select=contactid,emailaddress1)&$select=pinswg_emailnotifications,_pinswg_watchedcase_value";
|
||||
|
||||
return axios
|
||||
.get(
|
||||
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
|
||||
azureHeaders(token.access_token)
|
||||
)
|
||||
.then(({ data }) => {
|
||||
var dataStr;
|
||||
|
||||
const flattenedResults = data.value.map(flattenWatchlistEntry);
|
||||
|
||||
res.status(200).json(flattenedResults);
|
||||
})
|
||||
.catch((error) => {
|
||||
consoleLogger(error);
|
||||
res.status(400).json(error);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user