Files
Facilitor/UTILS/Exchange/res_to_graph.js

509 lines
22 KiB
JavaScript

/* Globals */
var DEZE; // context voor alle globale functies vanuit ASP
var Oracle;
var custabspath;
var config;
var token;
/*////////////////////////////////
// //
// Helper functions //
// //
////////////////////////////////*/
function initialize(params)
{
/* Vul de globals */
DEZE = params.DEZE;
__Log = DEZE.__Log;
__DoLog = DEZE.__DoLog;
_AiAi = DEZE._AiAi;
safe = DEZE.safe;
custabspath = params.custabspath;
Oracle = DEZE.Oracle;
customerId = DEZE.customerId;
config = loadconfig(custabspath + "\\Exchange\\exchange.config");
if (config === null) {
return null;
}
token = requestToken(config);
return token;
}
/*////////////////////////////////
// //
// Main functions //
// //
////////////////////////////////*/
function _getSql(CRUD) {
return "SELECT rrr.res_rsv_ruimte_key,"
+ " rr.res_ruimte_key,"
+ " rr.res_ruimte_extern_id,"
+ " rrr.res_rsv_ruimte_omschrijving,"
+ " rrr.res_rsv_ruimte_opmerking,"
+ (CRUD == "U" || CRUD == "D"
? " rrr.res_rsv_ruimte_externnr," /* ID van het event in de kalender v/d ruimte-resource */
+ " rrr.res_rsv_ruimte_externnr2," /* ID van het event in de kalender v/d organisator */
: "")
+ " rrr.res_rsv_ruimte_extern_meeting,"
+ " p_host.prs_perslid_email host_mail,"
+ " (SELECT prs_perslid_naam_friendly FROM prs_v_perslid_fullnames_all p WHERE p.prs_perslid_key = p_host.prs_perslid_key) host_name,"
+ " TO_CHAR(rrr.res_rsv_ruimte_van, 'YYYY-MM-DD\"T\"HH24:MI:SS') res_rsv_ruimte_van,"
+ " TO_CHAR(rrr.res_rsv_ruimte_tot, 'YYYY-MM-DD\"T\"HH24:MI:SS') res_rsv_ruimte_tot,"
+ " rrr.res_rsv_ruimte_visibility"
+ " FROM res_rsv_ruimte rrr, res_ruimte_opstelling ro, res_ruimte rr, prs_perslid p_host"
+ " WHERE rrr.res_ruimte_opstel_key = ro.res_ruimte_opstel_key"
+ " AND ro.res_ruimte_key = rr.res_ruimte_key"
+ " AND rrr.res_rsv_ruimte_host_key = p_host.prs_perslid_key"
+ " AND rrr.res_rsv_ruimte_tot >= TRUNC(SYSDATE) - " + config.fullpast
+ " AND rrr.res_rsv_ruimte_van <= TRUNC(SYSDATE) + " + config.fullfuture
+ " AND ( rr.res_ruimte_startdatum IS NULL"
+ " OR rr.res_ruimte_startdatum <= rrr.res_rsv_ruimte_van)"
+ " AND ( rr.res_ruimte_vervaldatum IS NULL"
+ " OR rr.res_ruimte_vervaldatum > rrr.res_rsv_ruimte_tot)"
+ (CRUD == "D"
? " AND rrr.res_rsv_ruimte_verwijder IS NOT NULL"
: " AND rrr.res_rsv_ruimte_verwijder IS NULL");
}
function subscription(userPrincipalName, res_ruimte_key, CRUD) {
if (CRUD == "C") {
var result = false;
var notificationUrl = getNotificationUrl(DEZE.customerId);
if (notificationUrl) {
result = createSubscription(userPrincipalName, res_ruimte_key, notificationUrl);
} else {
__DoLog("Error: Failed to craft notificationUrl for customer [" + DEZE.customerId + "]", "#FF0000");
}
return result;
} else if (CRUD == "R") {
// Unsupported
} else if (CRUD == "U") {
// We updaten liever niet, in plaats daarvan verwijderen we de subscription en creeeren we een nieuwe
} else if (CRUD == "D") {
return deleteSubscription(userPrincipalName);
}
}
function batchToGraph(CRUD, params) { // [C]reate, [R]ead, [U]pdate & [D]elete
params = params || {};
var MAX_BATCH = 20; // TODO (effectief) async maken en throttling testen/afhandelen
var MAX_TIME = 900000; // 15m, de time-out van 20m willen we handmatig voor zijn zodat we niet halvewege een batch eindigen in een AiAi
/* Handel een hele batch met requests af */
function _processBatch(batch, httpResponse) {
var requests = JSON.parse(batch.body).requests; // Dit kan wel veilig want we hebben dit object zojuist zelf stringified
if (!httpResponse.responses) {
__DoLog("Error: Geen responses ontvangen van MS Graph", "#FF0000"); // Wat dan wel?;
__DoLog(httpResponse);
return false;
}
for (var i = 0; i < httpResponse.responses.length; i++) {
var response = httpResponse.responses[i];
var request = requests[response.id - 1];
request.params.callback = resToGraphCallback;
// Returned true als resToGraphCallback klaar is, of false als het niet goed is gegaan
if (_handleMsGraphResponse(request, response)) {
result.done++;
} else {
result.failed++;
}
}
return true;
}
var sql = _getSql(CRUD);
if (CRUD == "C") {
sql += " AND rrr.res_rsv_ruimte_externsyncdate IS NULL"
+ " AND rrr.res_rsv_ruimte_externnr IS NULL"
+ " AND rrr.res_rsv_ruimte_externnr2 IS NULL";
}
if (params.res_ruimte_key) {
sql += " AND rr.res_ruimte_key = " + params.res_ruimte_key;
} else if (params.res_rsv_ruimte_key_arr && params.res_rsv_ruimte_key_arr.length) {
sql += " AND rrr.res_rsv_ruimte_key IN (" + params.res_rsv_ruimte_key_arr.join(",") + ")";
} else { // Er moet minstens 1 beperkende voorwaarde zijn (vooralsnog)
return { "success": false, "warning": "Missing scope" };
}
var oRs = Oracle.Execute(sql);
var result = {
"success": true,
"total": 0,
"done": 0,
"failed": 0
};
if (oRs.EOF) {
if (params.res_ruimte_key) {
__Log("Error: res_ruimte-record met res_ruimte_key = " + params.res_ruimte_key + " niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
} else /* if (params.res_rsv_ruimte_key_arr && params.res_rsv_ruimte_key_arr.length) */ {
__Log("Error: rsv_ruimte-record met rsv_ruimte_key IN (" + params.res_rsv_ruimte_key_arr.join(",") + ") niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
}
oRs.Close();
return { "success": false, "warning": "Not found" };
}
var start = new Date().getTime();
var extern_id = oRs("res_ruimte_extern_id").Value;
while (!oRs.EoF && (new Date().getTime() - start < MAX_TIME)) {
// Creeer batch request
var batch = [];
while (!oRs.EoF && batch.length < MAX_BATCH) {
var batch_params = { "batch": true };
// Als we gaan [U]pdaten moeten we wel weten in welke ruimte we zaten
if ("old_extern_ids" in params && extern_id != params.old_extern_ids[oRs("res_rsv_ruimte_key").Value]) {
batch_params.old_extern_id = params.old_extern_ids[oRs("res_rsv_ruimte_key").Value];
}
var req = _resToGraph(oRs, CRUD, batch_params);
if (!req || !req.length) {
oRs.moveNext();
continue;
}
for (var i = 0; i < req.length; i++) {
if (batch.length) {
req[i].dependsOn = [ batch.length ]; // <- sequentieel, zo gebruiken we 1 mailbox kanaal per batch-request
}
req[i].id = batch.length + 1;
batch.push(req[i]);
}
result.total += req.length;
oRs.moveNext();
}
if (batch.length) {
var batch_request = {
"method": "POST",
"url": "https://graph.microsoft.com/v1.0/$batch",
"body": JSON.stringify({ "requests": batch }),
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
"params": { "async": true, "callback": _processBatch }
}
if (!doHTTP(batch_request)) { // Voert de callback uit indien succesvol
result.failed += batch.length;
}
}
}
if (!oRs.EoF) {
__DoLog("Error: Het synchroniseren is voortijdig afgebroken, mogelijk is de maximale tijd van " + MAX_TIME + "ms overschreden", "#FF0000");
result.warning = "Timed out";
while (!oRs.EoF) {
result.failed++;
oRs.moveNext();
}
}
oRs.Close();
var summary = result.done + "/" + result.total + " reserveringen gesynchroniseerd naar outlook (" + result.failed + " mislukt)";
if (result.warning) {
result.warning += "\n" + summary;
} else {
result.toaster = summary
}
return result;
}
function resToGraph(rsv_ruimte_key, CRUD, params) // [C]reate, [R]ead, [U]pdate & [D]elete
{
__Log("Now in res_to_graph.wsc for rsv_ruimte_key (" + CRUD + ") = " + rsv_ruimte_key);
params = params || {};
if ((getMSGraphSyncLevel() & 6) == 0) {
__DoLog("Error: resToGraph aangeroepen zonder schrijf-intenties volgens S(msgraph_sync_level)", "#FF0000");
return false;
}
/* Eerst wat informatie vergaren */
var sql = _getSql(CRUD)
+ " AND res_rsv_ruimte_key = " + rsv_ruimte_key;
var oRs = Oracle.Execute(sql);
if (oRs.EOF) {
__Log("Error: rsv_ruimte-record met rsv_ruimte_key = " + rsv_ruimte_key + " niet gevonden\nWaarschijnlijk is de reservering buiten het geconfigureerde tijdsframe gemaakt", "#FF0000");
return false;
}
var result = _resToGraph(oRs, CRUD, params);
oRs.Close();
return result;
}
function _resToGraph(oRs, CRUD, params)
{
params = params || {};
var roomChanged;
var rsv_ruimte_key = oRs("res_rsv_ruimte_key").Value;
var res_ruimte_extern_id = oRs("res_ruimte_extern_id").Value; // 'Zaal email'
var res_subject = oRs("res_rsv_ruimte_omschrijving").Value || "";
var res_body = oRs("res_rsv_ruimte_opmerking").Value || "";
if (typeof DEZE !== "undefined") {
res_body = DEZE.shared.stripbbcodes(res_body);
}
var res_start = oRs("res_rsv_ruimte_van").Value;
var res_end = oRs("res_rsv_ruimte_tot").Value;
var host_name = oRs("host_name").Value;
var host_mail = oRs("host_mail").Value;
if (CRUD == "U" || CRUD == "D") {
var roomEventID = (oRs("res_rsv_ruimte_externnr").Value || "");
roomEventID = roomEventID.split("|")[1] || roomEventID.split("|")[0]; // <- COALESCE(occurrenceId, eventId)
var organizerEventID = oRs("res_rsv_ruimte_externnr2").Value || "";
roomChanged = ("old_extern_id" in params);
}
var extern_meeting = oRs("res_rsv_ruimte_extern_meeting").Value; // Online meeting provider
var visibility = oRs("res_rsv_ruimte_visibility").Value;
if ((CRUD == "U" || CRUD == "D") && (getMSGraphSyncLevel() & 4)) {
if (organizerEventID == "##PENDING##") { // Ooit door Facilitor opgestuurd maar nooit goed gekoppeld (FOUT)
var thisEvent = getCalendarEventByRsv(host_mail, rsv_ruimte_key); // Is deze afspraak al wel bekend in Exchange?
if (thisEvent === null) { /* Niet gevonden */
if (CRUD == "U") {
return _resToGraph(oRs, "C", params); // Dan maken we hem bij deze alsnog aan
} else {
return false; // Niet gevonden [D]eletes hoeft niets mee gedaan te worden.
}
} else { /* We hebben hem toch gevonden! */
organizerEventID = thisEvent.id;
updateFcltRecord(rsv_ruimte_key, thisEvent, thisEvent.id, { "iCalUId": true });
/* En doorrr */
}
} else if (organizerEventID == "##PENDING_OLD##") {
__DoLog("FATAL ERROR: Deze (oudere) reservering is niet goed gekoppeld met Exchange", "#FF0000");
return false;
}
}
if (roomChanged) { // De ruimte is aangepast, deze moeten net even anders
if (CRUD == "D") { // [D]elete de afspraak van de [oude] ruimte
res_ruimte_extern_id = params.old_extern_id; // -> Verwijder de afspraak gekoppeld aan de [oude] ruimte
} else if (CRUD == "U") {
if (res_ruimte_extern_id == null) { // [Nieuwe] ruimte is niet aan Outlook gekoppeld (De oude wel, anders komen we hier niet)
if (roomEventID === "" && organizerEventID === "") { // De oude ruimte was wel gekoppeld, maar de reservering zelf niet
__Log("Oude reservering was niet gekoppeld -> Geen actie");
return false;
} else {
return _resToGraph(oRs, "D", params); // -> Verwijder de hele afspraak in Outlook
}
} else if (params.old_extern_id == null) { // [Oude] ruimte was niet aan Outlook gekoppeld
return _resToGraph(oRs, "C", params); // -> Maak een [nieuwe] afspraak in Outlook
} else { // [Oude] ruimte was ook aan Outlook gekoppeld
if ((getMSGraphSyncLevel() & 6) == 2) { /* We boeken op naam vd ruimte */
var res_D = _resToGraph(oRs, "D", params); // -> Verwijder de afspraak van de [oude] ruimte
var res_C = _resToGraph(oRs, "C", params); // -> Maak een nieuwe afspraak aan voor de [nieuwe] ruimte
return (params.batch ? res_D.concat(res_C) : res_C);
} else if (organizerEventID == "") { // Van deze Outlook reservering moeten we eerst nog (bij de oude ruimte) het organizerEventID opvragen
organizerEventID = updateExternnr2(params.old_extern_id, roomEventID, host_mail);
}
}
}
}
if (res_ruimte_extern_id == null) {
__DoLog("Error: resToGraph is aangeroepen voor een reservering van een ruimte zonder Extern ID", "#FF0000");
return false;
}
var userPrincipalName = res_ruimte_extern_id;
if (getMSGraphSyncLevel() & 4) {
userPrincipalName = host_mail;
}
if (userPrincipalName === undefined || userPrincipalName === null) {
__DoLog("Error: Kan geen gebruiker vinden namens wie we de reservering kunnen maken", "#FF0000");
return false;
}
if ((CRUD == "U" || CRUD == "D") && organizerEventID == "") {
if (roomEventID == "") {
__Log("Deze reservering is niet gekoppeld -> Geen actie");
return false
} else if (getMSGraphSyncLevel() & 4) {
organizerEventID = updateExternnr2(res_ruimte_extern_id, roomEventID, host_mail);
} else if ((getMSGraphSyncLevel() & 6) == 2) {
organizerEventID = roomEventID;
}
}
if ((CRUD == "U" || CRUD == "D") && organizerEventID == "") {
__DoLog("Error: deze reservering is niet muteerbaar (of vindbaar) in Exchange", "#FF0000");
return false;
}
// Event object volgens https://docs.microsoft.com/en-us/graph/api/resources/event?view=graph-rest-1.0
// Wij muteren alleen deze velden
var data = null;
if (CRUD != "D")
{
var tz = "Europe/Amsterdam";
if (typeof DEZE !== "undefined") { // Alleen vanuit de browser
tz = DEZE.Session("time_zone") || DEZE.S("fac_server_timezone");
}
data = {
"start": {
"dateTime": res_start,
"timeZone": tz
},
"end": {
"dateTime": res_end,
"timeZone": tz
}
};
if (CRUD == "C" || visibility) {
data.subject = res_subject;
}
if (extern_meeting == 1) {
data.isOnlineMeeting = true;
data.onlineMeetingProvider = "teamsForBusiness";
} else if (CRUD == "C") { // Van een Teams meeting maken we nooit achteraf meer een niet-teams meeting in Outlook
data.isOnlineMeeting = false;
}
// Voor deze velden ondersteunen we (nog) geen bi-directionele sync, dus alleen initieel meegeven
if (CRUD == "C") {
data.body = {
"contentType": "text", // Simpele keuze = geen html
"content": res_body
};
data.singleValueExtendedProperties = [{
"id": FCLT_KEY_PROP_ID_NAME,
"value": String(rsv_ruimte_key)
}];
// data.sensitivity = visibility ? "normal" : "private"; (Nog) niet vanuit Facilitor muteerbaar
}
/* In deze modus nodigen we de gastheer/vrouw toe als deelnemer */
var inviteHost = (getMSGraphSyncLevel() & 2) && // We boeken op naam vd Ruimte (anders is de host al de organisator)
data.isOnlineMeeting && // Dit wordt een online meeting
(extern_meeting == 0 // Dit was nog geen online meeting; *Dit werkt niet, voor bestaande rsv kun je dit dus nooit meer veranderen
|| CRUD == "C"); // Dit was nog geen meeting :)
if (CRUD == "C" || inviteHost || roomChanged) {
/* Voeg de ruimte toe
dit willen we zo min mogelijk doen want als we dit meegeven worden eventuele verrijkingen in Outlook overschreven */
data.attendees = [];
if (roomChanged && CRUD == "U") { // Bij [C]reate & [D]elete is dit nvt
// location leegmaken, wordt vanzelf gevuld met de juiste ruimte
data.location = {};
data.locations = [];
if (getMSGraphSyncLevel() & 4) {
data.attendees = getAttendeeList(userPrincipalName, organizerEventID, params.old_extern_id);
}
}
data.attendees.push(
{
"type": "resource",
"emailAddress": {
"name": res_ruimte_extern_id.split("@")[0],
"address": res_ruimte_extern_id
}
}
);
}
if (inviteHost) {
data.attendees.push(
{
"type": "required",
"emailAddress": {
"name": host_name,
"address": host_mail
}
}
);
}
}
var url = (params.batch ? "" : "https://graph.microsoft.com/v1.0") + "/users/" + encodeURIComponent(userPrincipalName) + "/events";
if (CRUD == "U" || CRUD == "D")
url += "/" + organizerEventID;
var method;
switch (CRUD) { // [R]ead is in dit bestand nvt
case "C": method = "POST"; break;
case "U": method = "PATCH"; break;
case "D": method = "DELETE"; break;
default: _AiAi("INTERNAL ERROR: invalid CRUD-action specified in resToGraph(): CRUD = " + CRUD + "");
}
var request = {
"method": method,
"url": url,
"body": data,
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
"res_rsv_ruimte_key": rsv_ruimte_key,
"params": {
"roomChanged": (roomChanged ? "1" : "0")
}
}
/* Maak 'pending' zodat we binnenkomende notificaties kunnen matchen op deze reservering als we nog niet in de callback zijn gekomen */
if (CRUD == "C" && (getMSGraphSyncLevel() & 4)) {
var sql = "UPDATE res_rsv_ruimte"
+ " SET res_rsv_ruimte_externnr = NULL"
+ " , res_rsv_ruimte_externnr2 = '##PENDING##'"
+ " , res_rsv_ruimte_externsyncdate = SYSDATE"
+ " WHERE res_rsv_ruimte_key = " + rsv_ruimte_key;
Oracle.Execute(sql);
}
if (params.batch) { // Deze bundelen we eerst in een $batch-request en versturen hem dan pas
request.batch = true;
return [request];
} else {
request.params.callback = resToGraphCallback;
__Log("(" + method + ")" + " to: " + url);
if (data) {
__Log(data);
request.body = JSON.stringify(request.body);
}
}
return doHTTP(request);
}
/* Deze query wordt uitgevoerd als we een (succesvol) response van MS Graph ontvangen hebben */
var resToGraphCallback = function _resToGraphCallback(request, msGraphResponse) {
// We hebben hier nog niet de informatie van het ruimte-resource event, die krijgen we straks via de webhook binnen (indien S(msgraph_sync_level) &4)
// Vul voor nu alleen het organisator-event-id in bij res_rsv_ruimte_externnr2 zodat we deze res_rsv herkennen in de process_webhook waar we hem straks zullen aanvullen
var thisEvent, externnr2;
var params = {
"onlyPending": (getMSGraphSyncLevel() & 4)
};
if (getMSGraphSyncLevel() & 4) {
if (request.params.roomChanged === "1") {
thisEvent = msGraphResponse;
params.iCalUId = true;
params.onlyPending = false; // Forceer een update van het externnr naar iCalUId
}
externnr2 = msGraphResponse.id;
} else {
thisEvent = msGraphResponse;
}
return updateFcltRecord(request.res_rsv_ruimte_key, thisEvent, externnr2, params);
}
function checkExpiringAuth() {
return doCheckExpiringAuth();
}
function getCalendarEvent(method, userPrincipalName, id) {
if (method == "id") {
return getCalendarEventByID(userPrincipalName, id);
} else if (method == "iCalUId") {
return getCalendarEventByICalUId(userPrincipalName, id);
} else if (method == "res_rsv_ruimte_key") {
return getCalendarEventByRsv(userPrincipalName, id);
} else if (method == "date") {
var date_from = id.split("##")[0];
var date_to = id.split("##")[1];
return getCalendarEventsByDate(userPrincipalName, date_from, date_to);
} else {
return false;
}
}