454 lines
18 KiB
JavaScript
454 lines
18 KiB
JavaScript
import axios from "axios";
|
|
import CryptoJS from "crypto-js";
|
|
import _ from "lodash";
|
|
import { azureHeaders, azureHeadersPaged } from "../../../actions/core/headers";
|
|
import { consoleLogger } from "../../../actions/core/logger";
|
|
import { getToken } from "../../../actions/core/token";
|
|
import { formatDates } from "../../../components/utils";
|
|
import { hashAPIPath } from "../../../actions/core/hash";
|
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
|
|
|
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 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;
|
|
flattened.pinswg_preferredlanguage =
|
|
entry.pinswg_Contact.pinswg_preferredlanguage;
|
|
delete flattened.pinswg_Contact;
|
|
}
|
|
return flattened;
|
|
};
|
|
|
|
// s
|
|
const buildNotifyPayloads = (watchlistByEmail) => {
|
|
const reference = "PEDW-CASE-UPDATES";
|
|
const templateId = "217f7f96-2f8a-4034-a27f-9ffcf8d89edb";
|
|
|
|
const templateIdCY = "09d5cece-65d3-4717-84cd-edab99c8870c";
|
|
|
|
// 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.NEXTAUTH_URL + doc.pinswg_hashlink ||
|
|
// "[No Link]";
|
|
// return ` - ${name} (${date})\n Link: ${link}`;
|
|
// })
|
|
// .join("\n");
|
|
|
|
const formatDocuments = (docs = [], language = "en") => {
|
|
// Labels based on language
|
|
const labels = {
|
|
en: {
|
|
noRef: "No Ref",
|
|
untitled: "Untitled",
|
|
noDate: "No Date",
|
|
link: "Link"
|
|
},
|
|
cy: {
|
|
noRef: "Dim Cyfeirnod",
|
|
untitled: "Heb Deitl",
|
|
noDate: "Dim Dyddiad",
|
|
link: "Dolen"
|
|
}
|
|
};
|
|
|
|
const t = labels[language] || labels.en;
|
|
|
|
return docs
|
|
.map((doc) => {
|
|
const ref = doc.pinswg_isharedocumentreference || t.noRef;
|
|
const name = doc.pinswg_name || t.untitled;
|
|
const date =
|
|
formatDates(
|
|
doc.pinswg_documentpublisheddate?.split("T")[0],
|
|
language
|
|
) || t.noDate;
|
|
const link =
|
|
(process.env.NEXTAUTH_URL || "") + doc.pinswg_hashlink ||
|
|
"[No Link]";
|
|
|
|
return ` - ${name} (${date})\n ${t.link}: ${link}`;
|
|
})
|
|
.join("\n");
|
|
};
|
|
|
|
const formatSipEvents = (events = [], language = "en") => {
|
|
const labels = {
|
|
en: {
|
|
unnamed: "Unnamed Event",
|
|
noDate: "No Date",
|
|
on: "on"
|
|
},
|
|
cy: {
|
|
unnamed: "Digwyddiad Heb Enw",
|
|
noDate: "Dim Dyddiad",
|
|
on: "ar"
|
|
}
|
|
};
|
|
|
|
const t = labels[language] || labels.en;
|
|
|
|
return events
|
|
.map((event) => {
|
|
const name = event.pinswg_name || t.unnamed;
|
|
const date =
|
|
formatDates(
|
|
event.pinswg_dateeventrequested?.split("T")[0],
|
|
language
|
|
) || t.noDate;
|
|
return ` - ${name} ${t.on} ${date}`;
|
|
})
|
|
.join("\n");
|
|
};
|
|
|
|
const formatReps = (reps = [], language = "en") => {
|
|
const labels = {
|
|
en: {
|
|
open: "Open",
|
|
to: "to",
|
|
close: "Close",
|
|
na: "N/A"
|
|
},
|
|
cy: {
|
|
open: "Agor",
|
|
to: "i",
|
|
close: "Cau",
|
|
na: "Dim ar gael"
|
|
}
|
|
};
|
|
|
|
const t = labels[language] || labels.en;
|
|
|
|
return reps
|
|
.map((rep) => {
|
|
const start =
|
|
formatDates(
|
|
rep.pinswg_consultationopen?.split("T")[0],
|
|
language
|
|
) || t.na;
|
|
const end =
|
|
formatDates(
|
|
rep.pinswg_consultationclose?.split("T")[0],
|
|
language
|
|
) || t.na;
|
|
|
|
return ` - ${t.open}: ${start} ${t.to} ${t.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 prefLanguage = entries[0].pinswg_preferredlanguage;
|
|
|
|
console.log("Language to use for email tmeplace:", prefLanguage);
|
|
|
|
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,
|
|
"en"
|
|
)}\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 ${process.env.NEXTAUTH_URL}/unsubscribe/${entry.pinswg_watchlistid}`;
|
|
|
|
return `Case: ${caseRef}\nLink to case:${
|
|
process.env.NEXTAUTH_URL
|
|
}/case/${caseRef}\n\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.NEXTAUTH_URL + "/unsubscribeall/" + contactId
|
|
}`
|
|
};
|
|
|
|
const personalisationCY = {
|
|
contact_name: contactName,
|
|
case_sections: filteredEntries
|
|
.map((entry) => {
|
|
const caseRef =
|
|
entry[
|
|
"_pinswg_watchedcase_value@OData.Community.Display.V1.FormattedValue"
|
|
] || "Achos Anhysbys";
|
|
|
|
const docSection = entry.documents?.length
|
|
? `Dogfennau wedi'u hychwanegu neu eu diweddaru:\n${formatDocuments(
|
|
entry.documents,
|
|
"cy"
|
|
)}\n`
|
|
: "";
|
|
const sipSection = entry.sipEvents?.length
|
|
? `Digwyddiadau SIP wedi'u creu:\n${formatSipEvents(
|
|
entry.sipEvents,
|
|
"cy"
|
|
)}\n`
|
|
: "";
|
|
const repsSection = entry.repsPeriods?.length
|
|
? `Cyfnodau Ymgynghori:\n${formatReps(
|
|
entry.repsPeriods,
|
|
"cy"
|
|
)}\n`
|
|
: "";
|
|
|
|
const caseUnsubscribelink = `Dad-danysgrifio i ddiweddariadau ar yr achos hwn ${process.env.NEXTAUTH_URL}/unsubscribe/${entry.pinswg_watchlistid}`;
|
|
|
|
return `Achos: ${caseRef}\nDolen i'r achos: ${
|
|
process.env.NEXTAUTH_URL
|
|
}/case/${caseRef}\n\n${[
|
|
docSection,
|
|
sipSection,
|
|
repsSection,
|
|
caseUnsubscribelink
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n")}`;
|
|
})
|
|
.join("\n\n"),
|
|
unsubscribeLink: `Dad-danysgrifiwch drwy ddewis y ddolen hon os nad ydych chi eisiau derbyn unrhyw ddiweddariadau mwyach ${
|
|
process.env.NEXTAUTH_URL + "/unsubscribeall/" + contactId
|
|
}`
|
|
};
|
|
|
|
return {
|
|
email_address: email,
|
|
template_id:
|
|
prefLanguage == 846040001 ? templateId : templateIdCY,
|
|
personalisation:
|
|
prefLanguage == 846040001
|
|
? personalisation
|
|
: personalisationCY,
|
|
reference,
|
|
oneClickUnsubscribeURL:
|
|
process.env.NEXTAUTH_URL +
|
|
(prefLanguage == 846040001 ? "en" : "cy") +
|
|
"/unsubscribeall/" +
|
|
contactId
|
|
};
|
|
})
|
|
.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,pinswg_preferredlanguage)&$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,
|
|
language:
|
|
payload.pinswg_preferredlanguage == 846040001 ? "en" : "cy"
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
return respondSuccess(res, { sendingResults });
|
|
} catch (error) {
|
|
consoleLogger(error);
|
|
return respondError(res, {
|
|
status: 500,
|
|
code: "EMAIL_COMBINED_FETCH_FAILED",
|
|
message: "An error occurred while retrieving combined data.",
|
|
details: error.message || error.toString()
|
|
});
|
|
}
|
|
}
|