diff --git a/pages/api/email/getall.js b/pages/api/email/getall.js new file mode 100644 index 00000000..6e28f02a --- /dev/null +++ b/pages/api/email/getall.js @@ -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(), + }); + } +} diff --git a/pages/api/email/getdocuments.js b/pages/api/email/getdocuments.js new file mode 100644 index 00000000..b9574c24 --- /dev/null +++ b/pages/api/email/getdocuments.js @@ -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(), + }); + } +} diff --git a/pages/api/email/getevents.js b/pages/api/email/getevents.js new file mode 100644 index 00000000..06eaf19f --- /dev/null +++ b/pages/api/email/getevents.js @@ -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(), + }); + } +} diff --git a/pages/api/email/getmailinglist.js b/pages/api/email/getmailinglist.js new file mode 100644 index 00000000..a4a056e9 --- /dev/null +++ b/pages/api/email/getmailinglist.js @@ -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); + }); +}