diff --git a/Lightning_Report_Automatic.json b/Lightning_Report_Automatic.json index 06e614c..fd717c4 100644 --- a/Lightning_Report_Automatic.json +++ b/Lightning_Report_Automatic.json @@ -9,11 +9,11 @@ }, "type": "n8n-nodes-base.stickyNote", "position": [ - 185968, - 75248 + 193024, + 78016 ], "typeVersion": 1, - "id": "cb5392eb-e9df-492a-b64d-a1b8c4ad1fc0", + "id": "fabfab42-5146-4bcd-9638-14129e096bc1", "name": "Sticky Note1" }, { @@ -35,13 +35,13 @@ }, "options": {} }, - "id": "b470ce92-3b5b-4a35-8142-5137ae662262", + "id": "ccb6966e-72fc-4b64-abae-722f5d631953", "name": "Login to iklim.co", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ - 184768, - 74336 + 191824, + 77104 ], "onError": "continueErrorOutput" }, @@ -92,10 +92,10 @@ "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ - 185008, - 74320 + 192064, + 77088 ], - "id": "788a6725-e53c-414c-80c9-3c9770d86f0b", + "id": "01027f48-993a-401b-80fb-4361c13938a5", "name": "CalculateExpirations" }, { @@ -145,10 +145,10 @@ "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ - 185072, - 74576 + 192128, + 77344 ], - "id": "15e21445-ce11-467b-96d1-54794b971a9b", + "id": "fa0b49ea-3b74-48c7-8a4b-3eefcfc8f028", "name": "CalculateExpirations1" }, { @@ -166,13 +166,13 @@ }, "options": {} }, - "id": "d2308726-30a7-418c-8b64-f0a451a277f6", + "id": "951348af-1bf8-4302-a9e9-dd2e25b7f21d", "name": "Refresh Token", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ - 184768, - 74592 + 191824, + 77360 ], "onError": "continueErrorOutput" }, @@ -260,10 +260,10 @@ "type": "n8n-nodes-base.switch", "typeVersion": 3.4, "position": [ - 184544, - 74560 + 191600, + 77328 ], - "id": "e282cb45-291d-466f-ae32-c0ce0a062525", + "id": "0e162c28-007a-4652-8225-7d424ceee8db", "name": "Switch" }, { @@ -273,10 +273,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 184352, - 74576 + 191408, + 77344 ], - "id": "3bb1f381-b161-4b81-a034-54792adbf29d", + "id": "f4a61331-25fc-496f-8a5e-5bfcf638ee32", "name": "Restore Credentials", "retryOnFail": false }, @@ -287,10 +287,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185296, - 74576 + 192352, + 77344 ], - "id": "dc6089e2-2b46-4c83-b37d-823e7daef9fb", + "id": "8f464ccf-9a38-4ebc-a3d0-f5b81cee8747", "name": "Store Refresh Credentials" }, { @@ -300,10 +300,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185216, - 74320 + 192272, + 77088 ], - "id": "bc07c096-7437-4bc3-8460-8caec2da28de", + "id": "b7da4601-8cab-420f-8ac2-5a9065f7f208", "name": "Store Login Credentials" }, { @@ -323,10 +323,10 @@ "type": "n8n-nodes-base.slack", "typeVersion": 2.4, "position": [ - 185376, - 73968 + 192432, + 76736 ], - "id": "61f6a561-6e84-49f3-9413-8aa46cc30316", + "id": "6596c8b9-dd8b-4990-b626-62e21460a5fa", "name": "Send Error Notification", "webhookId": "0b3582cf-042e-43d6-bc02-4c9debab0566", "credentials": { @@ -347,13 +347,13 @@ ] } }, - "id": "ddb0ed90-1f27-44ff-8015-14716cf61adc", + "id": "10b39b14-3bc2-447f-9c4e-98bd56cd24b6", "name": "Daily Trigger", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1, "position": [ - 183808, - 74560 + 190864, + 77328 ] }, { @@ -398,13 +398,13 @@ "jsonBody": "={{ $json.requestBody }}", "options": {} }, - "id": "1f8eb335-b960-433c-aba1-2b0d1963524a", + "id": "91371278-11d3-4a5e-a3bc-6a7d72381261", "name": "Lightning Request", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ - 185872, - 74848 + 192928, + 77616 ], "onError": "continueErrorOutput" }, @@ -423,10 +423,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 184352, - 74832 + 191408, + 77600 ], - "id": "1e940e2c-cefc-4ea4-97ec-865183ecfd93", + "id": "06b3f62c-030f-4c2b-9d30-cd0a1dba1c70", "name": "Iterate Customers" }, { @@ -452,10 +452,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 185088, - 74848 + 192144, + 77616 ], - "id": "92fdbfd4-4859-43ef-8b4a-3ea96a2590a3", + "id": "2c7393ba-1f5b-443d-9457-37cd1b07697e", "name": "Get Customer Wind Turbines" }, { @@ -475,10 +475,10 @@ "type": "n8n-nodes-base.slack", "typeVersion": 2.4, "position": [ - 186512, - 74416 + 193568, + 77184 ], - "id": "e6c71e37-3a07-4fdc-83f2-8fc470eb8bea", + "id": "286e6469-bcf1-4430-8098-680a3cf4cfa7", "name": "Request Error Notification", "webhookId": "38b3ebc5-a136-4b78-9c7a-98f40e92a9d5", "notesInFlow": false, @@ -497,10 +497,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 186096, - 74416 + 193152, + 77184 ], - "id": "160ae94f-7520-49a8-87b9-6c7da1316331", + "id": "16593a5b-ce44-4e21-a3c3-63f4968ea399", "name": "Lightning Retry Handler" }, { @@ -531,10 +531,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 186304, - 74416 + 193360, + 77184 ], - "id": "0433eff7-71c7-4e18-af8b-d1dc79944b01", + "id": "ca073d74-cb75-4304-b5f4-f7e0dc1df666", "name": "Retry Lightning Request?" }, { @@ -544,10 +544,10 @@ "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ - 186512, - 74592 + 193568, + 77360 ], - "id": "a3b56e3a-c9d8-427b-8db1-c0d1f6b23ed5", + "id": "d966bbdc-91a1-40d5-a302-0c90fa0ee8be", "name": "Wait Before Retry", "webhookId": "b7c8d9e0-f1a2-4345-b678-889900112233" }, @@ -558,10 +558,10 @@ "type": "n8n-nodes-base.stopAndError", "typeVersion": 1, "position": [ - 186688, - 74416 + 193744, + 77184 ], - "id": "52193d81-eced-4e6a-bdaa-4f6a16b4d804", + "id": "6c5f3fd7-ca6e-4dd1-a026-fe7ad6d4e855", "name": "Stop Workflow" }, { @@ -571,10 +571,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 184992, - 73968 + 192048, + 76736 ], - "id": "c0e8a94f-c52f-400b-9961-bd2e795d4f96", + "id": "97c051a3-e7f9-4f55-882e-802e3c3ad936", "name": "Auth Retry Handler" }, { @@ -605,10 +605,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 185184, - 73968 + 192240, + 76736 ], - "id": "14c38d7d-4e22-4cc6-a1e3-fa329416bf8f", + "id": "125b55e9-5463-4497-876e-a6519cb752cd", "name": "Retry Auth Request?" }, { @@ -618,10 +618,10 @@ "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ - 185376, - 74128 + 192432, + 76896 ], - "id": "481af533-78c5-49f3-847e-194de9c34a7a", + "id": "c1bc4b5b-8f19-4718-a899-d688d492bbec", "name": "Wait Before Auth Retry", "webhookId": "c8d9e0f1-a2b3-4345-b678-889900112244" }, @@ -632,10 +632,10 @@ "type": "n8n-nodes-base.stopAndError", "typeVersion": 1, "position": [ - 185552, - 73968 + 192608, + 76736 ], - "id": "4590f7cd-d9c4-445c-baa5-b601b3087f0c", + "id": "f8efb8f0-88bb-4b84-99b2-0a85f39262ff", "name": "Stop Workflow Auth" }, { @@ -645,10 +645,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185296, - 74848 + 192352, + 77616 ], - "id": "24edf958-ae27-4d20-b607-0f8936851155", + "id": "2e1f09a2-33a8-47f4-81a7-84325996554f", "name": "Centroid & Distance Ring calculation" }, { @@ -658,10 +658,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185488, - 74848 + 192544, + 77616 ], - "id": "7f7b1ffa-1eaa-442c-8ec3-ce5356e7b668", + "id": "f236d46d-7e7a-47f1-8525-d3de2b4c2335", "name": "Loop init" }, { @@ -671,10 +671,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 186112, - 74848 + 193168, + 77616 ], - "id": "875d62a3-c869-440d-a134-129273655cc9", + "id": "52d6204a-a965-45dd-b789-fca4189d153f", "name": "Logic Gate" }, { @@ -705,10 +705,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 186304, - 74800 + 193360, + 77568 ], - "id": "967ea144-e03f-4139-9f59-187af709f00b", + "id": "34b7b435-2c56-4e47-a62c-e4841ebaf7db", "name": "Stop Loop" }, { @@ -738,10 +738,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 186496, - 74832 + 193552, + 77600 ], - "id": "f0ce78ca-87b5-4cdb-9058-c7262cfbadd5", + "id": "0c2fd4a8-325a-43d1-b496-0a93e67d1052", "name": "Lightning found?" }, { @@ -768,10 +768,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 186736, - 74864 + 193792, + 77632 ], - "id": "c34ffdaf-eaab-464a-9a13-edc1064079cb", + "id": "a0014ff6-7642-4a30-b55b-90393ff111e7", "name": "Get row(s)", "alwaysOutputData": true }, @@ -803,10 +803,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 187120, - 74864 + 194176, + 77632 ], - "id": "50999c06-4b44-4981-978e-c5737e0f92b9", + "id": "84d5aefa-ff56-411e-88d8-03dbb1db625a", "name": "If" }, { @@ -921,10 +921,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 187360, - 74864 + 194416, + 77632 ], - "id": "eeb16715-8834-4526-9c28-1948fb2f3c0c", + "id": "12c7498f-0f3e-4aab-b4c9-ac464044e988", "name": "Insert row" }, { @@ -934,10 +934,10 @@ "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, "position": [ - 184848, - 74832 + 191904, + 77600 ], - "id": "a1e9404b-4921-4309-ba59-6b3619639415", + "id": "f63756e6-684e-47a1-871b-0edcbb873cf9", "name": "Loop Over Items" }, { @@ -947,10 +947,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 187584, - 74864 + 194640, + 77632 ], - "id": "39c827bd-b54f-4bba-8a5c-b59e07ef14b3", + "id": "e0338883-8932-461a-b46e-e987deadade6", "name": "Calculate Thunderstorm Headers" }, { @@ -998,10 +998,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ - 187776, - 74864 + 194832, + 77632 ], - "id": "a621b3ce-7f5b-4142-8449-de863076aaf5", + "id": "bc871f9d-3b59-43d3-b97c-7fec3e6c8b3d", "name": "Thunderstorm Request", "onError": "continueErrorOutput" }, @@ -1012,10 +1012,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 188400, - 74896 + 195456, + 77664 ], - "id": "99ec685f-53fd-4cfb-8130-473e52c59874", + "id": "edeff220-abc6-4194-ab43-2341a48294fc", "name": "Thunderstorm Error Fallback" }, { @@ -1025,10 +1025,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 188000, - 74672 + 195056, + 77440 ], - "id": "0ca5e334-df17-418f-a7cc-676079c03bbc", + "id": "ef38ff2f-9fb5-4c6d-8a17-f4cf7b466b9b", "name": "Thunderstorm Retry Handler" }, { @@ -1059,10 +1059,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 188192, - 74672 + 195248, + 77440 ], - "id": "b6f5c0f5-8a3f-4c94-8850-d8e64a5cf31e", + "id": "6d09cfcc-5a15-4c84-b08e-5415e0635458", "name": "Retry Thunderstorm Request?" }, { @@ -1072,10 +1072,10 @@ "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ - 188416, - 74672 + 195472, + 77440 ], - "id": "92d82197-45fe-47f3-b610-652ff650abcc", + "id": "6f1a53f9-ed19-449a-8f52-fa2545a5f3dd", "name": "Wait Before Thunderstorm Retry", "webhookId": "c8d9e0f1-a2b3-4456-c789-0123456789ab" }, @@ -1086,10 +1086,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185680, - 74848 + 192736, + 77616 ], - "id": "362b2eb6-4e95-45cb-840b-23985d56b36a", + "id": "15e7469c-1d42-4fee-8a7e-22df6093b7b3", "name": "Calculate Lightning Headers" }, { @@ -1099,10 +1099,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 188592, - 75008 + 195648, + 77776 ], - "id": "1b44a56d-989a-4ba5-9cb0-b3bef4cfce2b", + "id": "7ce4bd24-6888-41c8-9822-132b43843d6d", "name": "Build Report Payload" }, { @@ -1136,10 +1136,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ - 188800, - 74864 + 195856, + 77632 ], - "id": "1886734f-2c34-4b7c-986b-672c83bf0045", + "id": "a9405d4d-c78c-4b13-8d0c-b8abe9d1bf1c", "name": "Start Async Report", "onError": "continueErrorOutput" }, @@ -1150,10 +1150,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 189008, - 74864 + 196064, + 77632 ], - "id": "537b9fcc-517e-4689-8559-f453ef010804", + "id": "a7c59ab1-f1ef-48aa-b289-3ea32e576a46", "name": "Attach Report Job" }, { @@ -1163,10 +1163,10 @@ "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [ - 189216, - 74864 + 196272, + 77632 ], - "id": "61f8a0c8-5df9-4a61-ab06-078b4c32103d", + "id": "5722c248-b22d-4468-aa82-a153196623cb", "name": "Wait for Report", "webhookId": "d4e5f6a7-b8c9-4012-d345-6789abcdef01" }, @@ -1193,10 +1193,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ - 189424, - 74864 + 196480, + 77632 ], - "id": "ad4c417a-7cca-43d7-87ad-4ee19e980ab4", + "id": "fa4a2f64-0eca-4e37-9b99-f9a6a0d70c95", "name": "Poll Report Status", "onError": "continueErrorOutput" }, @@ -1207,10 +1207,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 189632, - 74864 + 196688, + 77632 ], - "id": "20576011-755c-49a3-bdf2-b1a9e821345a", + "id": "27eaf27f-b51e-49fe-a6a1-95be4aacca39", "name": "Merge Poll Status" }, { @@ -1240,10 +1240,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 189840, - 74864 + 196896, + 77632 ], - "id": "23bade91-e9d6-49aa-b6ad-3580e0329414", + "id": "fd307ad6-a3bd-4bfe-b7ca-3fb35edb1597", "name": "Report Complete?" }, { @@ -1273,10 +1273,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 190032, - 74528 + 197088, + 77296 ], - "id": "857c4817-a6a2-4c79-8908-d971595ec3e6", + "id": "b982bc43-73b3-4072-9482-97fb07782f0e", "name": "Report Failed?" }, { @@ -1306,10 +1306,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 190256, - 74576 + 197312, + 77344 ], - "id": "43d42a48-1d5e-456e-9b26-e7155317a9dc", + "id": "f0cdea17-dc81-424e-88c8-4824ea444ba5", "name": "Continue Polling?" }, { @@ -1319,10 +1319,10 @@ "type": "n8n-nodes-base.stopAndError", "typeVersion": 1, "position": [ - 190240, - 74384 + 197296, + 77152 ], - "id": "14b2aaa8-fafb-4253-936b-40f63fe241f1", + "id": "a0a1f9ee-b5de-4421-8aab-cca896ab68e8", "name": "Stop Report Failed" }, { @@ -1332,10 +1332,10 @@ "type": "n8n-nodes-base.stopAndError", "typeVersion": 1, "position": [ - 190464, - 74592 + 197520, + 77360 ], - "id": "97959bc6-dc3f-4c0a-9fc9-5cb0c85ec188", + "id": "57914e12-4e59-4f70-abbf-fc4aa3d6296b", "name": "Stop Report Timeout" }, { @@ -1366,10 +1366,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ - 190064, - 74848 + 197120, + 77616 ], - "id": "b4a70bff-175b-40b9-968f-61c15bdb64cc", + "id": "3d18ff22-b4c8-4929-bd4b-99959efb22fc", "name": "Download Report", "onError": "continueErrorOutput" }, @@ -1380,10 +1380,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 190304, - 74832 + 197360, + 77600 ], - "id": "3c95cce3-2e5f-4cbe-b7cd-8e4b82d16a5c", + "id": "d88489d3-d04c-4dff-bdbe-c56d4a013324", "name": "Prepare DOCX Metadata" }, { @@ -1405,10 +1405,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 190512, - 74832 + 197568, + 77600 ], - "id": "b8274dc5-4d0c-4361-9cff-8ae8378ee6b0", + "id": "3992f130-8176-4823-861f-7d8502bd14b0", "name": "Get Storm Logs For Next Id", "alwaysOutputData": true }, @@ -1419,10 +1419,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 190720, - 74832 + 197776, + 77600 ], - "id": "36574944-ac36-4599-b626-bfff858e69d0", + "id": "43bc1189-a887-4714-a901-86ac901ab575", "name": "Resolve Storm Log Id" }, { @@ -1436,10 +1436,10 @@ "type": "n8n-nodes-base.slack", "typeVersion": 2.4, "position": [ - 191296, - 74832 + 198352, + 77600 ], - "id": "80bc4ca9-430d-4a49-91e7-f3b10c265579", + "id": "f60d271c-bc14-4582-9952-258c1b1ef90f", "name": "Upload Report to Slack", "webhookId": "c4d5e6f7-a8b9-4012-cdef-345678901234", "credentials": { @@ -1466,10 +1466,10 @@ "type": "n8n-nodes-base.slack", "typeVersion": 2.4, "position": [ - 191872, - 74976 + 198928, + 77744 ], - "id": "6e80009a-aa2e-4f8c-b199-3d847e8b2707", + "id": "5b019c4e-be55-497c-9faa-00fee5a6da55", "name": "Send Approval Buttons", "webhookId": "d4e5f6a7-b8c9-0123-def0-456789abcdef", "credentials": { @@ -1489,10 +1489,10 @@ "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [ - 184016, - 75296 + 191072, + 78064 ], - "id": "e5da58ff-245e-445c-901c-416e6db117d0", + "id": "93d76d11-0765-4ee4-b6fa-26395ba494b4", "name": "Slack Interaction Webhook", "webhookId": "c9d8e7f6-a5b4-4321-fedc-ba9876543211" }, @@ -1503,10 +1503,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 184256, - 75296 + 191312, + 78064 ], - "id": "f30eb3eb-993f-46c0-8d50-245483955b37", + "id": "bc708cdf-95ca-4267-8317-2e2ed47e4560", "name": "Parse Slack Button Payload" }, { @@ -1537,10 +1537,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 184496, - 75296 + 191552, + 78064 ], - "id": "1894347b-8115-4609-bf7a-1b9e44626d35", + "id": "b7de0d16-5523-4739-9460-94049d67d027", "name": "Valid Storm Approval Button?" }, { @@ -1587,10 +1587,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 185248, - 75296 + 192304, + 78064 ], - "id": "c63845f7-2265-4fe2-8eea-bbb9bcdc96d4", + "id": "2419265f-4bef-4188-9f7a-3ac8ed5db1b5", "name": "Approve Pending Row" }, { @@ -1611,13 +1611,13 @@ "jsonBody": "={{ (() => {\n const ack = $('Still Valid After Ack?').first().json;\n const isConfirmed = ack.status === 'confirmed';\n const id = String(ack.id ?? '');\n const customer = String(ack.customer_name || 'unknown customer');\n const verb = isConfirmed ? 'approved' : 'rejected';\n return {\n response_type: 'in_channel',\n replace_original: false,\n text: `Storm log #${id} for ${customer} ${verb} and recorded in storm_logs.`,\n };\n})() }}", "options": {} }, - "id": "06f5a2ef-14fc-4b30-bdba-3ae335f61b24", + "id": "d6d0ea81-9340-49f6-b4e9-69d01f7fb809", "name": "Notify Slack Recording Result", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ - 188304, - 75680 + 195360, + 78448 ] }, { @@ -1629,10 +1629,10 @@ "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [ - 184752, - 75296 + 191808, + 78064 ], - "id": "5fb7578e-0894-4aaf-ac81-f93888ea58b7", + "id": "463d8f11-0df9-437d-af8e-2801e6bf0d45", "name": "Respond Slack Interaction" }, { @@ -1663,10 +1663,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 184944, - 75296 + 192000, + 78064 ], - "id": "7d17e3ce-829a-4224-add9-2b27e4b319a7", + "id": "e7f3c857-f069-4671-82e7-f7305d0c7d8f", "name": "Still Valid After Ack?" }, { @@ -1686,10 +1686,10 @@ "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ - 184080, - 74560 + 191136, + 77328 ], - "id": "5d2974c2-9fb2-4675-b31c-27dbc9743be5", + "id": "fa1a4e65-dcfe-422a-afe9-099f05c5fbd9", "name": "Test Configuration" }, { @@ -1699,10 +1699,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 186912, - 74864 + 193968, + 77632 ], - "id": "ec58fb51-f6bd-41be-a227-83f66f12faa8", + "id": "359d57ae-d661-4062-a43c-628e3df7e084", "name": "Prepare Insert Decision" }, { @@ -1712,10 +1712,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 190912, - 74832 + 197968, + 77600 ], - "id": "444de45d-369b-44f5-a728-0508a52d3b69", + "id": "701bb193-2c94-42b4-8b39-8b9ae4845ac0", "name": "Prepare Customer Email Context" }, { @@ -1725,11 +1725,11 @@ }, "type": "n8n-nodes-base.stickyNote", "position": [ - 185200, - 75872 + 192256, + 78640 ], "typeVersion": 1, - "id": "1a7735f2-0f5f-4e4b-98fe-69925fbc99cf", + "id": "78313ea9-b9b8-4b95-9a7e-69ab97401db6", "name": "Sticky Note Storm Logs Email Columns" }, { @@ -1739,10 +1739,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 191104, - 74832 + 198160, + 77600 ], - "id": "20102737-cded-4422-8e76-a702ee42a584", + "id": "917032b0-eb08-41c1-abca-4ab2c4ec2638", "name": "Build Pending Email Record" }, { @@ -1773,10 +1773,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 191488, - 74832 + 198544, + 77600 ], - "id": "f5b7e702-11b4-4732-8711-116948a81474", + "id": "bd377b9d-6867-4940-abe3-f0f0bf07c80d", "name": "Can Persist Report?" }, { @@ -1856,10 +1856,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 191728, - 74816 + 198784, + 77584 ], - "id": "776b3703-8cf3-49a7-ad57-07c7542be927", + "id": "6d9be5f6-488a-4c36-8707-6eb19710b9c7", "name": "Persist Report To Storm Logs" }, { @@ -1889,10 +1889,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 185440, - 75632 + 192496, + 78400 ], - "id": "ec6e37ca-1bd0-4554-9a17-8b349a4758db", + "id": "43a12b59-0bcf-4a77-9be6-0c748a05679e", "name": "Report Approved?" }, { @@ -1918,10 +1918,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 185712, - 75488 + 192768, + 78256 ], - "id": "7da9aada-73ef-42cb-8762-4016d0252f05", + "id": "94c73441-3c4f-4ab7-aed5-97ebaccd1dbd", "name": "Get Pending Report From Storm Logs", "alwaysOutputData": true }, @@ -1932,10 +1932,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 185936, - 75488 + 192992, + 78256 ], - "id": "fa310f78-df56-4e9e-934c-18de78e84b65", + "id": "b56db58b-07f6-49b1-b98f-63f6a1dc865c", "name": "Load Queued Customer Report" }, { @@ -1962,10 +1962,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 186176, - 75488 + 193232, + 78256 ], - "id": "8994d8ce-6777-44ff-8456-d94a3bd23965", + "id": "560f795a-6879-4fb9-a5fa-83529679663a", "name": "Lookup Customer Contact Email", "alwaysOutputData": true }, @@ -1976,10 +1976,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 186384, - 75488 + 193440, + 78256 ], - "id": "c6758a30-25ad-409f-b2be-eb7bce546e2a", + "id": "6526f28e-d6ea-4556-9482-057f11d8b443", "name": "Merge Customer Contact Email" }, { @@ -2019,10 +2019,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 186592, - 75488 + 193648, + 78256 ], - "id": "9f600a75-f7ae-4b4d-8fbd-59ee81e2b58c", + "id": "89da9171-185e-4bd2-bddf-df1a3254a71b", "name": "Ready To Email Customer?" }, { @@ -2032,10 +2032,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 186832, - 75472 + 193888, + 78240 ], - "id": "80618df6-4706-4cf2-a2e0-194ec5e00b28", + "id": "37e3b05a-a1b8-4f6f-90af-dad1574e713e", "name": "Prepare SendGrid Email Payload" }, { @@ -2061,10 +2061,10 @@ "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ - 187072, - 75472 + 194128, + 78240 ], - "id": "ab593b12-2d4b-46dc-8428-07995403825c", + "id": "fb46dc09-9b35-4e41-afc0-026209d9559c", "name": "Send Customer Report Email", "credentials": { "sendGridApi": { @@ -2151,10 +2151,10 @@ "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ - 187696, - 75680 + 194752, + 78448 ], - "id": "be22fe43-ccd7-4b7e-8235-dab4e4f419f3", + "id": "c88ded04-d632-4485-b0ab-46ecf922184d", "name": "Clear Pending Report In Storm Logs" }, { @@ -2164,10 +2164,10 @@ "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 187888, - 75680 + 194944, + 78448 ], - "id": "79c57e0a-f806-4768-b309-7d54af4be30a", + "id": "4a5b2068-8d0d-4c90-8270-04384d209777", "name": "Restore Approval Context" }, { @@ -2197,10 +2197,10 @@ "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ - 188080, - 75680 + 195136, + 78448 ], - "id": "a0d8ad32-4c13-419b-8089-8d558da91307", + "id": "4de950cb-6b8c-4c47-ab36-52a593dc820a", "name": "Has Slack Response URL?" }, { @@ -2220,10 +2220,10 @@ "type": "n8n-nodes-base.slack", "typeVersion": 2.4, "position": [ - 187344, - 75472 + 194400, + 78240 ], - "id": "990b4f5e-164e-4032-a2cc-84a459ae4828", + "id": "88bea895-5c1d-4da4-bdb8-fdaa2f0125d9", "name": "Email Sent Notification", "webhookId": "e1f2a3b4-c5d6-7890-abcd-ef0123456789", "credentials": { @@ -3208,7 +3208,7 @@ "executionOrder": "v1", "binaryMode": "separate" }, - "versionId": "589523b4-4e27-4c3a-a85b-3b84415501c8", + "versionId": "4b32c35a-7a40-4cdc-97e9-8c714ca93bf9", "meta": { "instanceId": "15c4ff3a74619031c77894fe5fb8c0fd585362ef637b1873abd56a139f543e12" }, diff --git a/Lightning_Report_Manual.json b/Lightning_Report_Manual.json new file mode 100644 index 0000000..68b94cc --- /dev/null +++ b/Lightning_Report_Manual.json @@ -0,0 +1,3241 @@ +{ + "name": "Lightning_Report_Manual", + "nodes": [ + { + "parameters": { + "content": "export REPORT_SERVICE_TOKEN=\"your-long-random-secret\"\nTo run: uvicorn report_service.main:app --host 0.0.0.0 --port 8000\n\nOn another terminal: ngrok http 8000\n\nReport generation uses async polling nodes (Start Async Report -> Wait -> Poll -> Download). Update ngrok base URL in those HTTP nodes when the tunnel URL changes.\n", + "height": 128, + "width": 512 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 200944, + 80864 + ], + "typeVersion": 1, + "id": "ec8b33d2-a74a-42dc-aae5-458ecab0d0a3", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "POST", + "url": "https://api-test.iklim.co/v1/auth/login", + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "username", + "value": "info@iklim.co" + }, + { + "name": "password", + "value": "gn6aV01Z759HTw9I" + } + ] + }, + "options": {} + }, + "id": "27edeb8d-4885-440a-8c12-3f52b5e0fd48", + "name": "Login to iklim.co", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 199744, + 79952 + ], + "onError": "continueErrorOutput" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "5c1668fb-f97b-4ec8-80f9-61bfb0b8274e", + "name": "username", + "value": "={{ $json.username }}", + "type": "string" + }, + { + "id": "97c76bac-aa08-437f-ac48-a0cad9b93526", + "name": "type", + "value": "={{ $json.type }}", + "type": "string" + }, + { + "id": "fc59d51f-b225-4abb-9f17-93173e650652", + "name": "accessToken", + "value": "={{ $json.accessToken }}", + "type": "string" + }, + { + "id": "95e5ec41-2369-492b-9950-fc230a47f7fa", + "name": "accessTokenExpiration", + "value": "={{ $now.plus({ hours: 1 }).toMillis() }}", + "type": "string" + }, + { + "id": "3d79d06a-ed55-48af-b4bc-b7fd67d80be1", + "name": "refreshToken", + "value": "={{ $json.refreshToken }}", + "type": "string" + }, + { + "id": "aedf0ef4-9971-42e8-8b32-7d039342dd88", + "name": "refreshTokenExpiration", + "value": "={{ $now.plus({ days: 1 }).toMillis() }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 199984, + 79936 + ], + "id": "8f423a2f-0766-4cfe-ba11-779722e2f895", + "name": "CalculateExpirations" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "5c1668fb-f97b-4ec8-80f9-61bfb0b8274e", + "name": "username", + "value": "={{ $json.username }}", + "type": "string" + }, + { + "id": "97c76bac-aa08-437f-ac48-a0cad9b93526", + "name": "type", + "value": "={{ $json.type }}", + "type": "string" + }, + { + "id": "fc59d51f-b225-4abb-9f17-93173e650652", + "name": "accessToken", + "value": "={{ $json.accessToken }}", + "type": "string" + }, + { + "id": "95e5ec41-2369-492b-9950-fc230a47f7fa", + "name": "accessTokenExpiration", + "value": "={{ $now.plus({ hours: 1 }).toMillis() }}", + "type": "string" + }, + { + "id": "3d79d06a-ed55-48af-b4bc-b7fd67d80be1", + "name": "refreshToken", + "value": "={{ $json.refreshToken }}", + "type": "string" + }, + { + "id": "aedf0ef4-9971-42e8-8b32-7d039342dd88", + "name": "refreshTokenExpiration", + "value": "={{ $now.plus({ days: 1 }).toMillis() }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 200048, + 80192 + ], + "id": "6e0be6c6-9509-43fc-bd0e-63958759230f", + "name": "CalculateExpirations1" + }, + { + "parameters": { + "method": "POST", + "url": "https://api-test.iklim.co/v1/auth/refresh", + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "refreshToken", + "value": "={{ $input.auth..refreshToken }}" + } + ] + }, + "options": {} + }, + "id": "e37a94b7-cdb8-4ef9-9f4e-f6334d35e1d2", + "name": "Refresh Token", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 199744, + 80208 + ], + "onError": "continueErrorOutput" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "leftValue": "={{ DateTime.fromMillis($json.refreshTokenExpiration - 300000) }}", + "rightValue": "={{ $now }}", + "operator": { + "type": "dateTime", + "operation": "before" + }, + "id": "2e2eea90-cc23-412f-a0f3-c8b9747d0fae" + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Relogin" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "2c01f003-c0d1-4ed7-b41b-5e281b7353bb", + "leftValue": "={{ DateTime.fromMillis($json.accessTokenExpiration - 600000)}}", + "rightValue": "={{ $now }}", + "operator": { + "type": "dateTime", + "operation": "before" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Refresh" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "590bec27-6e6f-4f0b-a701-3a359032c4ed", + "leftValue": "={{ $now }}", + "rightValue": "", + "operator": { + "type": "dateTime", + "operation": "exists", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "Valid" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.4, + "position": [ + 199520, + 80176 + ], + "id": "0c2c1b9d-a222-4505-8814-736d5fad5a06", + "name": "Switch" + }, + { + "parameters": { + "jsCode": "const staticData = $getWorkflowStaticData('global');\n\nif (!staticData.hasOwnProperty('user')) {\n staticData.user = null;\n}\n\nif (!staticData.hasOwnProperty('authType')) {\n staticData.authType = 'Bearer';\n}\n\nif (!staticData.hasOwnProperty('accessToken')) {\n staticData.accessToken = null;\n}\n\nif (!staticData.hasOwnProperty('accessTokenExpiration')) {\n staticData.accessTokenExpiration = 0;\n}\n\nif (!staticData.hasOwnProperty('refreshToken')) {\n staticData.refreshToken = null;\n}\n\nif (!staticData.hasOwnProperty('refreshTokenExpiration')) {\n staticData.refreshTokenExpiration = 0;\n}\n\nconst input = $input.first()?.json || {};\nconst currentTime = input.timestamp ?? Date.now();\n\nreturn [\n {\n json: {\n user: staticData.user,\n bearer: staticData.bearer,\n accessToken: staticData.accessToken,\n accessTokenExpiration: staticData.accessTokenExpiration,\n refreshToken: staticData.refreshToken,\n refreshTokenExpiration: staticData.refreshTokenExpiration,\n timestamp: currentTime,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 199328, + 80192 + ], + "id": "f3cc5f51-1896-48cf-af79-d930302b13c8", + "name": "Restore Credentials", + "retryOnFail": false + }, + { + "parameters": { + "jsCode": "const staticData = $getWorkflowStaticData('global');\nstaticData.authRetryAttempt = 0;\nfor (const item of $input.all()) {\n staticData.user = item.json.username;\n staticData.authType = item.json.type;\n staticData.accessToken = item.json.accessToken;\n staticData.accessTokenExpiration = item.json.accessTokenExpiration;\n staticData.refreshToken = item.json.refreshToken;\n staticData.refreshTokenExpiration = item.json.refreshTokenExpiration;\n}\n\nreturn $input.all();" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200272, + 80192 + ], + "id": "932e6d12-b693-42f8-a746-90e14c646495", + "name": "Store Refresh Credentials" + }, + { + "parameters": { + "jsCode": "const staticData = $getWorkflowStaticData('global');\nstaticData.authRetryAttempt = 0;\nfor (const item of $input.all()) {\n staticData.user = item.json.username;\n staticData.authType = item.json.type;\n staticData.accessToken = item.json.accessToken;\n staticData.accessTokenExpiration = item.json.accessTokenExpiration;\n staticData.refreshToken = item.json.refreshToken;\n staticData.refreshTokenExpiration = item.json.refreshTokenExpiration;\n}\n\nreturn $input.all();" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200192, + 79936 + ], + "id": "9de02592-f925-4e8d-b757-a49b47dffb82", + "name": "Store Login Credentials" + }, + { + "parameters": { + "select": "channel", + "channelId": { + "__rl": true, + "value": "C0A9K1AC7SN", + "mode": "list", + "cachedResultName": "n8n-events" + }, + "text": "=🛑 {{ $workflow.name }} — yetkilendirme başarısız ({{ $json.authRetryAttempt }}/3 deneme)\n*Hata:* {{ $json.last_error_message || $json.error?.message || $json.message }}", + "otherOptions": { + "includeLinkToWorkflow": false + } + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 200352, + 79584 + ], + "id": "5f0581b3-0cd3-40b4-a759-facd1de685ed", + "name": "Send Error Notification", + "webhookId": "0b3582cf-042e-43d6-bc02-4c9debab0566", + "credentials": { + "slackApi": { + "id": "OKgM8VkM05pJl9kU", + "name": "Tarla Slack Account" + } + } + }, + { + "parameters": {}, + "id": "9a59d0fe-ce46-4c1c-bd24-ea15c277d68d", + "name": "Daily Trigger", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 198624, + 80192 + ] + }, + { + "parameters": { + "method": "POST", + "url": "https://api-test.iklim.co/v1/lightnings/within", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "={{ $json.headers.Authorization }}" + }, + { + "name": "X-Signature", + "value": "={{ $json.headers['X-Signature'] }}" + }, + { + "name": "X-Timestamp", + "value": "={{ $json.headers['X-Timestamp'] }}" + }, + { + "name": "X-Nonce", + "value": "={{ $json.headers['X-Nonce'] }}" + }, + { + "name": "X-Idempotency-Key", + "value": "={{ $json.headers['X-Idempotency-Key'] }}" + }, + { + "name": "Content-Type", + "value": "={{ $json.headers['Content-Type'] }}" + }, + { + "name": "Accept", + "value": "={{ $json.headers['Content-Type'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.requestBody }}", + "options": {} + }, + "id": "219f9dcb-9ee6-4384-9dde-6ce32df51391", + "name": "Lightning Request", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 200848, + 80464 + ], + "onError": "continueErrorOutput" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "bMJhm1lTDbCj9eY1", + "mode": "list", + "cachedResultName": "customers", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/bMJhm1lTDbCj9eY1" + }, + "filters": { + "conditions": [ + { + "keyValue": "={{ $('Manual Input').first().json.customer_id }}" + } + ] + } + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 199328, + 80448 + ], + "id": "3f7fc370-13f1-4fdc-b333-46f5f4533c33", + "name": "Iterate Customers" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "e2gQ68IGiYmCZkES", + "mode": "list", + "cachedResultName": "wind_turbine_farm", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/e2gQ68IGiYmCZkES" + }, + "filters": { + "conditions": [ + { + "keyName": "customer_id", + "keyValue": "={{ $json.id }}" + } + ] + }, + "returnAll": true + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 200064, + 80464 + ], + "id": "fac9d472-e86e-4969-aae2-2cde2d32fa86", + "name": "Get Customer Wind Turbines" + }, + { + "parameters": { + "select": "channel", + "channelId": { + "__rl": true, + "value": "C0A9K1AC7SN", + "mode": "list", + "cachedResultName": "n8n-events" + }, + "text": "=🛑 {{ $workflow.name }} — iş akışı durduruldu ({{ $json.lightningRetryAttempt }}/3 deneme başarısız)\n*Müşteri:* {{ $json.customer_name }}\n*Türbin:* {{ $json.turbine_name }}\n*Hata:* {{ $json.last_error_message || $json.error?.message || $json.message }}", + "otherOptions": { + "includeLinkToWorkflow": false + } + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 201488, + 80032 + ], + "id": "3b7413b7-9b23-4e07-a363-d47ac5c6ee31", + "name": "Request Error Notification", + "webhookId": "38b3ebc5-a136-4b78-9c7a-98f40e92a9d5", + "notesInFlow": false, + "executeOnce": true, + "credentials": { + "slackApi": { + "id": "OKgM8VkM05pJl9kU", + "name": "Tarla Slack Account" + } + } + }, + { + "parameters": { + "jsCode": "const MAX_ATTEMPTS = 3;\nconst farm = $('Loop init').all().pop().json;\nconst errorPayload = $input.first().json || {};\nconst errorMessage = errorPayload.error?.message || errorPayload.message || JSON.stringify(errorPayload);\n\nlet turbineName = 'N/A';\ntry {\n turbineName = $('Get Customer Wind Turbines').first().json.name || turbineName;\n} catch (e) {}\n\nlet previousAttempt = 0;\ntry {\n const prev = $('Lightning Retry Handler').all().pop().json;\n if (prev?.lightningRetryAttempt != null) {\n previousAttempt = Number(prev.lightningRetryAttempt);\n }\n} catch (e) {}\n\nconst attempt = previousAttempt + 1;\n\nif (attempt < MAX_ATTEMPTS) {\n return [{\n json: {\n ...farm,\n lightningRetryAttempt: attempt,\n retry_lightning: true,\n pageNumber: farm.pageNumber ?? 0,\n last_error_message: errorMessage,\n turbine_name: turbineName,\n },\n }];\n}\n\nreturn [{\n json: {\n ...farm,\n lightningRetryAttempt: attempt,\n retry_lightning: false,\n last_error_message: errorMessage,\n turbine_name: turbineName,\n error: errorPayload.error || errorPayload,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 201072, + 80032 + ], + "id": "10a56f8f-3034-4653-9a63-998852abae72", + "name": "Lightning Retry Handler" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f5a6b7c8-d9e0-4123-f456-667788990011", + "leftValue": "={{ $json.retry_lightning }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 201280, + 80032 + ], + "id": "0acc1f28-4854-49db-a738-8b83b6f98dc6", + "name": "Retry Lightning Request?" + }, + { + "parameters": { + "amount": 30 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 201488, + 80208 + ], + "id": "c2684274-7bb6-4b2a-9ef6-de385c6445b0", + "name": "Wait Before Retry", + "webhookId": "b7c8d9e0-f1a2-4345-b678-889900112233" + }, + { + "parameters": { + "errorMessage": "=Lightning API failed after 3 attempts for {{ $json.customer_name }}: {{ $json.last_error_message || $json.error?.message || 'unknown error' }}" + }, + "type": "n8n-nodes-base.stopAndError", + "typeVersion": 1, + "position": [ + 201664, + 80032 + ], + "id": "d3078187-3fcd-4254-86cf-c9b54b2a74e9", + "name": "Stop Workflow" + }, + { + "parameters": { + "jsCode": "const MAX_ATTEMPTS = 3;\nconst errorPayload = $input.first().json || {};\nconst errorMessage = errorPayload.error?.message || errorPayload.message || JSON.stringify(errorPayload);\nconst staticData = $getWorkflowStaticData('global');\n\nlet previousAttempt = 0;\ntry {\n const prev = $('Auth Retry Handler').all().pop().json;\n if (prev?.authRetryAttempt != null) {\n previousAttempt = Number(prev.authRetryAttempt);\n }\n} catch (e) {\n previousAttempt = Number(staticData.authRetryAttempt || 0);\n}\n\nconst attempt = previousAttempt + 1;\nstaticData.authRetryAttempt = attempt;\n\nif (attempt < MAX_ATTEMPTS) {\n return [{\n json: {\n retry_auth: true,\n authRetryAttempt: attempt,\n last_error_message: errorMessage,\n },\n }];\n}\n\nreturn [{\n json: {\n retry_auth: false,\n authRetryAttempt: attempt,\n last_error_message: errorMessage,\n error: errorPayload.error || errorPayload,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 199968, + 79584 + ], + "id": "e11e74b9-b5da-4554-b749-dcfe945ab19a", + "name": "Auth Retry Handler" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "a9b8c7d6-e5f4-3210-abcd-auth000002", + "leftValue": "={{ $json.retry_auth }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 200160, + 79584 + ], + "id": "7c21738a-3964-49c9-821e-37565ac3847e", + "name": "Retry Auth Request?" + }, + { + "parameters": { + "amount": 30 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 200352, + 79744 + ], + "id": "fad025fa-21b6-49c4-a561-c718fcd633cf", + "name": "Wait Before Auth Retry", + "webhookId": "c8d9e0f1-a2b3-4345-b678-889900112244" + }, + { + "parameters": { + "errorMessage": "=iklim.co authentication failed after 3 attempts: {{ $json.last_error_message || $json.error?.message || 'unknown error' }}" + }, + "type": "n8n-nodes-base.stopAndError", + "typeVersion": 1, + "position": [ + 200528, + 79584 + ], + "id": "9f4bcb30-b4ae-4f27-bcf0-f2dd440f2c27", + "name": "Stop Workflow Auth" + }, + { + "parameters": { + "jsCode": "const turbines = $input.all();\n\n// 1. Safeguard: Skip if no turbines are found\nif (turbines.length === 0) {\n return { skip_customer: true };\n}\n\nconst lats = turbines.map(t => Number(t.json.latitude));\nconst lons = turbines.map(t => Number(t.json.longitude));\n\n// 2. Calculate Centroid\nconst n = lats.length;\nconst centroid_lat = lats.reduce((a, b) => a + b, 0) / n;\nconst centroid_lon = lons.reduce((a, b) => a + b, 0) / n;\n\n// 3. Haversine Distance Helper\nconst getDist = (lat1, lon1, lat2, lon2) => {\n const R = 6371e3; // Earth radius in meters\n const dLat = (lat2 - lat1) * Math.PI / 180;\n const dLon = (lon2 - lon1) * Math.PI / 180;\n const a = Math.sin(dLat/2)**2 + Math.cos(lat1*Math.PI/180)*Math.cos(lat2*Math.PI/180)*Math.sin(dLon/2)**2;\n return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n};\n\n// 4. Find Farthest Turbine\nlet maxDist = 0;\nfor (let i = 0; i < n; i++) {\n const d = getDist(centroid_lat, centroid_lon, lats[i], lons[i]);\n if (d > maxDist) maxDist = d;\n}\n\n// 5. Calculate Rings\n// Updated: Ensures a minimum 1000m radius even for a single turbine point\nconst r1 = Math.max(1000, Math.ceil(maxDist / 1000) * 1000);\nconst r4 = r1 + 6000;\nconst boundary = Math.min(r4 + 2000, 49999); \n\nreturn {\n skip_customer: false,\n farm_id: turbines[0].json.customer_id,\n customer_name: ($('Loop Over Items').first().json || {}).customer_name,\n language: String(($('Loop Over Items').first().json || {}).language || 'en').trim() || 'en',\n centroid_latitude: centroid_lat,\n centroid_longitude: centroid_lon,\n monitoring_boundary_m: boundary,\n rings: { r1, r2: r1+2000, r3: r1+4000, r4 }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200272, + 80464 + ], + "id": "454daa20-4d93-4522-bc00-56fa8a842f5a", + "name": "Centroid & Distance Ring calculation" + }, + { + "parameters": { + "jsCode": "// Merge the centroid/rings data with the new loop variables\nreturn {\n ...$input.item.json, \n pageNumber: 0,\n stopLoop: false,\n allStrikes: [],\n tStart: null,\n tLast: null,\n lightningRetryAttempt: 0\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200464, + 80464 + ], + "id": "687960ad-f669-4286-9403-6f8cccfa4742", + "name": "Loop init" + }, + { + "parameters": { + "jsCode": "const SILENCE_MS = 12 * 60 * 60 * 1000;\nconst response = $input.first().json;\nconst batchStrikes = response.lightnings || [];\nconst currentPage = response.pageNumber || 0;\n\nconst farm = $('Loop init').all().pop().json;\nlet allStrikes = [];\nlet stopLoop = false;\n\nif (currentPage > 0) {\n try {\n const prevState = $('Logic Gate').all().pop().json;\n allStrikes = prevState.allStrikes ? [...prevState.allStrikes] : [];\n } catch (e) { }\n}\n\nif (batchStrikes.length === 0 && currentPage === 0) {\n return { stopLoop: true, allStrikes: [], pageNumber: 0, status: \"EMPTY\", customer_name: farm.customer_name };\n}\n\nfor (let i = 0; i < batchStrikes.length; i++) {\n const currentTs = new Date(batchStrikes[i].captured).getTime();\n batchStrikes[i].timestamp = currentTs;\n allStrikes.push(batchStrikes[i]);\n\n if (batchStrikes[i + 1]) {\n const nextTs = new Date(batchStrikes[i + 1].captured).getTime();\n if (currentTs - nextTs > SILENCE_MS) {\n stopLoop = true;\n break;\n }\n }\n}\n\nif (batchStrikes.length < 100) {\n stopLoop = true;\n}\n\nconst timestamps = allStrikes.map((s) => s.timestamp).filter((ts) => Number.isFinite(ts));\nconst final_tStart = timestamps.length > 0 ? Math.min(...timestamps) : null;\nconst final_tLast = timestamps.length > 0 ? Math.max(...timestamps) : null;\n\nconst roundToMinute = (ts) => Math.floor(Number(ts || 0) / 60000);\nconst stormKey = [\n String(farm.customer_name || ''),\n roundToMinute(final_tStart),\n roundToMinute(final_tLast),\n].join('_');\n\nreturn {\n stopLoop,\n pageNumber: currentPage + 1,\n allStrikes,\n tStart: final_tStart,\n tLast: final_tLast,\n storm_start_min: roundToMinute(final_tStart),\n storm_end_min: roundToMinute(final_tLast),\n storm_key: stormKey,\n customer_name: farm.customer_name,\n status: stopLoop ? \"FINISHED\" : \"PAGING\"\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 201088, + 80464 + ], + "id": "8e5a335a-e1b9-475a-9e93-3987fd079271", + "name": "Logic Gate" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "68ba538e-8070-4470-b0fa-30106a1f65b5", + "leftValue": "={{ $node[\"Logic Gate\"].json.stopLoop }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 201280, + 80416 + ], + "id": "1ac5d05f-8299-439a-ad71-565d749b1951", + "name": "Stop Loop" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "8b6c4fdd-0f94-4bc8-82ac-0996af68c534", + "leftValue": "={{ $node[\"Logic Gate\"].json.allStrikes.length }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 201472, + 80448 + ], + "id": "c0547d10-b5ae-4431-b396-a3df79782a80", + "name": "Lightning found?" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "matchType": "allConditions", + "filters": { + "conditions": [ + { + "keyName": "storm_key", + "keyValue": "={{ $json.storm_key }}" + } + ] + }, + "limit": 1 + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 201712, + 80480 + ], + "id": "c260df11-6b96-4528-9511-1764bf2646e7", + "name": "Get row(s)", + "alwaysOutputData": true + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "02a4a5c6-2645-4d72-9952-9a62c69fa19f", + "leftValue": "={{ $json.skip_insert }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "false", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 202096, + 80480 + ], + "id": "e6f58776-6818-4d00-bbed-96860d555c1c", + "name": "If" + }, + { + "parameters": { + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "storm_start": "={{ $json.tStart }}", + "storm_end": "={{ $json.tLast }}", + "total_strikes": "={{ $json.allStrikes.length }}", + "customer_name": "={{ $json.customer_name }}", + "status": "waiting for confirmation", + "storm_key": "={{ $json.storm_key }}", + "storm_start_min": "={{ $json.storm_start_min }}", + "storm_end_min": "={{ $json.storm_end_min }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "customer_name", + "displayName": "customer_name", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "total_strikes", + "displayName": "total_strikes", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "readOnly": false, + "removed": false + }, + { + "id": "storm_start", + "displayName": "storm_start", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "readOnly": false, + "removed": false + }, + { + "id": "storm_end", + "displayName": "storm_end", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "readOnly": false, + "removed": false + }, + { + "id": "status", + "displayName": "status", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "storm_key", + "displayName": "storm_key", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "storm_start_min", + "displayName": "storm_start_min", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "readOnly": false, + "removed": false + }, + { + "id": "storm_end_min", + "displayName": "storm_end_min", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "readOnly": false, + "removed": false + } + ], + "attemptToConvertTypes": true, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 202336, + 80480 + ], + "id": "66d45469-2129-4c8c-a512-42ce291a9f67", + "name": "Insert row" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + 199824, + 80448 + ], + "id": "1557d7b5-4207-4516-ad56-bf68e0693be7", + "name": "Loop Over Items" + }, + { + "parameters": { + "jsCode": "const crypto = require('crypto');\nconst HMAC_SECRET = 'c88f845bd6d520ded507ef6b02efc223019ccf68f41d9070705712d480ba5166';\nconst URI = '/v1/thunderstorms/within';\n\nconst storm = $('Logic Gate').all().pop().json;\nconst farm = $('Loop init').all().pop().json;\nconst auth = $('Restore Credentials').all().pop().json;\n\nif (!storm.tLast || !storm.tStart) {\n throw new Error(\"Missing storm timestamps. Skipping Thunderstorm query.\");\n}\n\nconst durationSeconds = Math.floor((storm.tLast - storm.tStart) / 1000);\nconst timestamp = Date.now().toString();\n\nconst bodyPayload = {\n latitude: Number(Number(farm.centroid_latitude).toFixed(6)),\n longitude: Number(Number(farm.centroid_longitude).toFixed(6)),\n radius: Math.max(50000, farm.monitoring_boundary_m), // Ensure coverage[cite: 2]\n backwardInterval: Math.max(3600, durationSeconds + 600), // Dynamic padding[cite: 2]\n endTimeEpoch: Number(storm.tLast), // Use actual storm end[cite: 2]\n intersectsWith: \"THREAT_POLYGON\",\n pageNumber: 0,\n pageSize: 10\n};\n\nconst bodyString = JSON.stringify(bodyPayload);\nconst dataToSign = `POST|${URI}|${timestamp}|${bodyString}`;\nconst signature = crypto.createHmac('sha256', HMAC_SECRET).update(dataToSign).digest('hex').toLowerCase();\n\nreturn {\n requestBody: bodyPayload,\n headers: {\n \"X-Signature\": signature,\n \"X-Timestamp\": timestamp,\n \"X-Nonce\": crypto.randomUUID(),\n \"X-Idempotency-Key\": crypto.randomUUID(),\n \"Authorization\": \"Bearer \" + auth.accessToken,\n \"Content-Type\": \"application/json\"\n }\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 202560, + 80480 + ], + "id": "31a343f7-1724-4177-aea4-73aa2ccc3b58", + "name": "Calculate Thunderstorm Headers" + }, + { + "parameters": { + "method": "POST", + "url": "https://api-test.iklim.co/v1/thunderstorms/within", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "={{ $json.headers.Authorization }}" + }, + { + "name": "X-Signature", + "value": "={{ $json.headers['X-Signature'] }}" + }, + { + "name": "X-Timestamp", + "value": "={{ $json.headers['X-Timestamp'] }}" + }, + { + "name": "X-Nonce", + "value": "={{ $json.headers['X-Nonce'] }}" + }, + { + "name": "X-Idempotency-Key", + "value": "={{ $json.headers['X-Idempotency-Key'] }}" + }, + { + "name": "Content-Type", + "value": "={{ $json.headers['Content-Type'] }}" + }, + { + "name": "Accept", + "value": "={{ $json.headers['Content-Type'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.requestBody }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, + "position": [ + 202752, + 80480 + ], + "id": "89c7675a-641e-4002-8e21-7f51be063b4b", + "name": "Thunderstorm Request", + "onError": "continueErrorOutput" + }, + { + "parameters": { + "jsCode": "const item = $input.first();\nconst error = item.json.error || {};\n\nconst extractErrorMessage = (payload) => {\n const err = payload.error || {};\n const candidates = [\n payload.last_error_message,\n payload.errorMessage,\n err.message,\n payload.message,\n ];\n for (const candidate of candidates) {\n if (!candidate) continue;\n const text = String(candidate);\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}$/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.message) return String(parsed.message);\n } catch (e) {}\n }\n return text;\n }\n return 'Thunderstorm API request failed';\n};\n\nconst rawMessage = extractErrorMessage(item.json);\nconst isAccountMissing = /account is not found/i.test(String(rawMessage));\nconst reason = isAccountMissing\n ? 'Thunderstorm API account is not provisioned for this user on api-test.iklim.co'\n : String(rawMessage);\n\nreturn [{\n json: {\n thunderstorms: [],\n thunderstorm_fetch_skipped: true,\n thunderstorm_fetch_error: reason,\n thunderstorm_retry_attempts: item.json.thunderstormRetryAttempt || null,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 203376, + 80512 + ], + "id": "00c3cf00-9f21-4d5e-94e6-bcef39375d9d", + "name": "Thunderstorm Error Fallback" + }, + { + "parameters": { + "jsCode": "const MAX_ATTEMPTS = 3;\nconst errorPayload = $input.first().json || {};\nconst error = errorPayload.error || {};\n\nconst extractErrorMessage = (payload) => {\n const err = payload.error || {};\n const candidates = [\n payload.last_error_message,\n payload.errorMessage,\n err.message,\n payload.message,\n ];\n for (const candidate of candidates) {\n if (!candidate) continue;\n const text = String(candidate);\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}$/);\n if (jsonMatch) {\n try {\n const parsed = JSON.parse(jsonMatch[0]);\n if (parsed.message) return String(parsed.message);\n } catch (e) {}\n }\n return text;\n }\n return 'Thunderstorm API request failed';\n};\n\nconst rawMessage = extractErrorMessage(errorPayload);\n\nlet previousAttempt = 0;\ntry {\n const prev = $('Thunderstorm Retry Handler').all().pop().json;\n if (prev?.thunderstormRetryAttempt != null) {\n previousAttempt = Number(prev.thunderstormRetryAttempt);\n }\n} catch (e) {}\n\nconst attempt = previousAttempt + 1;\n\nif (attempt < MAX_ATTEMPTS) {\n return [{\n json: {\n thunderstormRetryAttempt: attempt,\n retry_thunderstorm: true,\n last_error_message: rawMessage,\n },\n }];\n}\n\nreturn [{\n json: {\n thunderstormRetryAttempt: attempt,\n retry_thunderstorm: false,\n last_error_message: rawMessage,\n error: errorPayload.error || errorPayload,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 202976, + 80288 + ], + "id": "09ac6f1e-7bd8-49d8-8353-fc409d905f3a", + "name": "Thunderstorm Retry Handler" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "e5f6a7b8-c9d0-4123-e456-7890abcdef01", + "leftValue": "={{ $json.retry_thunderstorm }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 203168, + 80288 + ], + "id": "3510b96e-2785-4202-bcdd-3c39ab725b01", + "name": "Retry Thunderstorm Request?" + }, + { + "parameters": { + "amount": 30 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 203392, + 80288 + ], + "id": "d1975a07-4f25-42c9-aa3b-d3300eca91fc", + "name": "Wait Before Thunderstorm Retry", + "webhookId": "c8d9e0f1-a2b3-4456-c789-0123456789ab" + }, + { + "parameters": { + "jsCode": "const crypto = require('crypto');\nconst HMAC_SECRET = 'c88f845bd6d520ded507ef6b02efc223019ccf68f41d9070705712d480ba5166';\nconst METHOD = 'POST';\nconst URI = '/v1/lightnings/within';\n\nconst manualInput = $('Manual Input').first().json;\nconst endTime = Number(manualInput.end_time_epoch_ms);\nconst startTime = Number(manualInput.start_time_epoch_ms);\n\nif (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) {\n throw new Error('Manual Input must include valid start_time_epoch_ms and end_time_epoch_ms (end > start).');\n}\n\nconst backwardInterval = Math.max(60, Math.floor((endTime - startTime) / 1000));\n\nconst staticData = $getWorkflowStaticData('global');\nconst accessToken = staticData.accessToken;\nif (!accessToken) {\n throw new Error('Missing accessToken in workflow static data');\n}\n\nconst farmBase = $('Loop init').all().pop().json;\nconst allItems = $input.all();\nconst processedItems = [];\n\nfor (const item of allItems) {\n const farmData = { ...farmBase, ...item.json };\n const pageNumber = farmData.pageNumber ?? 0;\n const timestamp = Date.now().toString();\n\n const bodyPayload = {\n latitude: farmData.centroid_latitude,\n longitude: farmData.centroid_longitude,\n radius: farmData.monitoring_boundary_m,\n backwardInterval,\n endTimeEpoch: endTime,\n pageNumber,\n pageSize: 100,\n };\n\n const bodyString = JSON.stringify(bodyPayload);\n const dataToSign = `${METHOD.toUpperCase()}|${URI}|${timestamp}|${bodyString}`;\n const signature = crypto.createHmac('sha256', HMAC_SECRET).update(dataToSign).digest('hex').toLowerCase();\n\n processedItems.push({\n json: {\n requestBody: bodyPayload,\n headers: {\n 'X-Signature': signature,\n 'X-Timestamp': timestamp,\n 'X-Nonce': crypto.randomUUID(),\n 'X-Idempotency-Key': crypto.randomUUID(),\n Authorization: 'Bearer ' + accessToken,\n 'Content-Type': 'application/json',\n },\n },\n });\n}\n\nreturn processedItems;" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200656, + 80464 + ], + "id": "fea0c908-869d-4676-a1ed-285e1eeb06b9", + "name": "Calculate Lightning Headers" + }, + { + "parameters": { + "jsCode": "const turbines = $('Get Customer Wind Turbines').all().map((t) => t.json);\nconst storm = $('Logic Gate').all().pop().json;\nconst farm = $('Loop init').all().pop().json;\n\nconst unwrapThunderstorms = (items) => {\n const records = [];\n for (const item of items) {\n if (!item || item.thunderstorm_fetch_skipped) {\n continue;\n }\n if (Array.isArray(item.thunderstorms)) {\n records.push(...item.thunderstorms);\n continue;\n }\n if (Array.isArray(item.cells)) {\n records.push(...item.cells);\n continue;\n }\n if (Array.isArray(item.data)) {\n records.push(...item.data);\n continue;\n }\n if (Array.isArray(item.items)) {\n records.push(...item.items);\n continue;\n }\n records.push(item);\n }\n return records;\n};\n\nconst thunder = unwrapThunderstorms($input.all().map((t) => t.json));\n\nconst getNodeRowsSafely = (nodeName) => {\n try {\n return $(nodeName)\n .all()\n .map((i) => i.json)\n .filter((r) => r && r.id !== undefined && r.id !== null);\n } catch (error) {\n return [];\n }\n};\n\nconst existingRows = getNodeRowsSafely('Get row(s)');\nconst insertedRows = getNodeRowsSafely('Insert row');\nconst stormLogId = insertedRows[0]?.id ?? existingRows[0]?.id ?? null;\n\nlet customer = {};\ntry {\n customer = $('Loop Over Items').first().json || {};\n} catch (e) {\n customer = {};\n}\nconst language = String(customer.language || farm.language || 'en').trim() || 'en';\n\nconst manual = $('Manual Input').first().json;\nconst startMs = Number(manual.start_time_epoch_ms);\nconst endMs = Number(manual.end_time_epoch_ms);\nlet strikes = storm.allStrikes || [];\n\nif (Number.isFinite(startMs) && Number.isFinite(endMs)) {\n strikes = strikes.filter((strike) => {\n const ts = Number(strike.timestamp ?? new Date(strike.captured).getTime());\n return Number.isFinite(ts) && ts >= startMs && ts <= endMs;\n });\n}\n\nconst tStart = Number.isFinite(startMs) ? startMs : storm.tStart;\nconst tEnd = Number.isFinite(endMs) ? endMs : storm.tLast;\n\nreturn {\n customer_name: storm.customer_name,\n storm_log_id: stormLogId,\n language,\n centroid_lat: farm.centroid_latitude,\n centroid_lon: farm.centroid_longitude,\n boundary_m: farm.monitoring_boundary_m,\n rings: farm.rings,\n timezone: 'Europe/Istanbul',\n t_start: tStart,\n t_end: tEnd,\n n_strikes: strikes.length,\n turbines: turbines,\n strikes: strikes,\n storm_records: thunder,\n};" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 203568, + 80624 + ], + "id": "0f824542-6b2c-406b-a49e-94def23459f1", + "name": "Build Report Payload" + }, + { + "parameters": { + "method": "POST", + "url": "https://patrol-plural-gooey.ngrok-free.dev/generate/async", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "X-Report-Token", + "value": "test-secret-123" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "ngrok-skip-browser-warning", + "value": "1" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json }}", + "options": { + "timeout": 120000 + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, + "position": [ + 203776, + 80480 + ], + "id": "034b0df6-609b-46bc-a8dd-eb6908dcdea6", + "name": "Start Async Report", + "onError": "continueErrorOutput" + }, + { + "parameters": { + "jsCode": "const payload = $('Build Report Payload').first().json;\nconst start = $input.first().json || {};\nif (!start.job_id) {\n throw new Error('Async report start did not return job_id');\n}\nreturn [{\n json: {\n ...payload,\n report_job_id: start.job_id,\n poll_attempt: 0,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 203984, + 80480 + ], + "id": "2f632822-7e25-4a29-9989-1da22278bd97", + "name": "Attach Report Job" + }, + { + "parameters": { + "amount": 15 + }, + "type": "n8n-nodes-base.wait", + "typeVersion": 1.1, + "position": [ + 204192, + 80480 + ], + "id": "8216a8fb-3cc5-4593-880d-afde4f354eb4", + "name": "Wait for Report", + "webhookId": "d4e5f6a7-b8c9-4012-d345-6789abcdef01" + }, + { + "parameters": { + "url": "=https://patrol-plural-gooey.ngrok-free.dev/generate/async/{{ $json.report_job_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "X-Report-Token", + "value": "test-secret-123" + }, + { + "name": "ngrok-skip-browser-warning", + "value": "1" + } + ] + }, + "options": { + "timeout": 120000 + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, + "position": [ + 204400, + 80480 + ], + "id": "f5d6a077-4d67-4013-98bc-66e5ca70ba37", + "name": "Poll Report Status", + "onError": "continueErrorOutput" + }, + { + "parameters": { + "jsCode": "const ctx = $('Wait for Report').item.json;\nconst poll = $input.first().json || {};\nreturn [{\n json: {\n ...ctx,\n poll_status: poll.status,\n poll_error: poll.error || null,\n poll_filename: poll.filename || null,\n poll_attempt: Number(ctx.poll_attempt || 0) + 1,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 204608, + 80480 + ], + "id": "ec5beb20-a1a6-4c42-8887-d28c0a251fc5", + "name": "Merge Poll Status" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f5a6b7c8-d9e0-4123-a456-6677889900aa", + "leftValue": "={{ $json.poll_status }}", + "rightValue": "complete", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 204816, + 80480 + ], + "id": "7d0e5e6a-0a35-4284-b900-e854e4dea9b2", + "name": "Report Complete?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f5a6b7c8-d9e0-4123-a456-6677889900ac", + "leftValue": "={{ $json.poll_status }}", + "rightValue": "failed", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 205008, + 80144 + ], + "id": "fb6b4c25-3976-4596-9e7e-980af24fd6c1", + "name": "Report Failed?" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f5a6b7c8-d9e0-4123-a456-6677889900ae", + "leftValue": "={{ $json.poll_attempt }}", + "rightValue": 60, + "operator": { + "type": "number", + "operation": "lt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 205232, + 80192 + ], + "id": "1bc1aaf0-1c31-44d6-9865-aa83b1e107a9", + "name": "Continue Polling?" + }, + { + "parameters": { + "errorMessage": "=Report generation failed: {{ $json.poll_error || 'unknown error' }}" + }, + "type": "n8n-nodes-base.stopAndError", + "typeVersion": 1, + "position": [ + 205216, + 80000 + ], + "id": "7078547d-f2f5-4675-89b4-b9e7087adea5", + "name": "Stop Report Failed" + }, + { + "parameters": { + "errorMessage": "=Report generation timed out after {{ $json.poll_attempt || 60 }} poll attempts" + }, + "type": "n8n-nodes-base.stopAndError", + "typeVersion": 1, + "position": [ + 205440, + 80208 + ], + "id": "3b8b9430-a03f-47fd-b87b-cdcb8b8faedb", + "name": "Stop Report Timeout" + }, + { + "parameters": { + "url": "=https://patrol-plural-gooey.ngrok-free.dev/generate/async/{{ $json.report_job_id }}/download", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "X-Report-Token", + "value": "test-secret-123" + }, + { + "name": "ngrok-skip-browser-warning", + "value": "1" + } + ] + }, + "options": { + "response": { + "response": { + "responseFormat": "file" + } + }, + "timeout": 180000 + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, + "position": [ + 205040, + 80464 + ], + "id": "b6e5831e-19ed-4388-8ad1-bb7ad6cbc9c9", + "name": "Download Report", + "onError": "continueErrorOutput" + }, + { + "parameters": { + "jsCode": "const customerName = String($node['Build Report Payload'].json.customer_name || 'customer').trim().replace(/\\s+/g, '_');\nconst now = new Date();\nconst year = String(now.getFullYear()).slice(-2);\nconst month = String(now.getMonth() + 1).padStart(2, '0');\nconst day = String(now.getDate()).padStart(2, '0');\nconst hour = String(now.getHours()).padStart(2, '0');\nconst minute = String(now.getMinutes()).padStart(2, '0');\nconst generatedAt = `${year}-${month}-${day}_${hour}-${minute}`;\nconst fileName = `${generatedAt}_${customerName}_iklimco_lightning_report.docx`;\n\nfor (const item of $input.all()) {\n if (!item.binary || !item.binary.data) {\n continue;\n }\n\n item.binary.data.fileName = fileName;\n item.binary.data.fileExtension = 'docx';\n item.binary.data.mimeType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n}\n\nreturn $input.all();" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 205280, + 80448 + ], + "id": "032cc551-9e82-44d5-8ddb-9385c796277c", + "name": "Prepare DOCX Metadata" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "matchType": "allConditions", + "filters": { + "conditions": [] + }, + "limit": 1000 + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 205488, + 80448 + ], + "id": "0d8b9a7a-7cf1-4137-a338-5bc026429ac9", + "name": "Get Storm Logs For Next Id", + "alwaysOutputData": true + }, + { + "parameters": { + "jsCode": "const payload = $node['Build Report Payload'].json || {};\nconst reportFileItem = $('Prepare DOCX Metadata').all()[0] || {};\nconst rows = $input.all().map((item) => item.json || {});\n\nconst normalizeId = (row) => {\n const raw = row?.id ?? row?.ID ?? row?._id ?? null;\n\n if (raw === null || raw === undefined) {\n return null;\n }\n\n if (typeof raw === 'string' && raw.trim() === '') {\n return null;\n }\n\n const num = Number(raw);\n if (Number.isFinite(num) && num > 0) {\n return num;\n }\n\n return raw;\n};\n\nconst hasValue = (v) => !(v === null || v === undefined || v === '');\n\nlet resolvedStormLogId = hasValue(payload.storm_log_id) ? payload.storm_log_id : null;\n\nif (!hasValue(resolvedStormLogId)) {\n const byKey = rows.find((row) => String(row.storm_key || '') === String(payload.storm_key || ''));\n if (byKey) {\n resolvedStormLogId = normalizeId(byKey);\n }\n}\n\nif (!hasValue(resolvedStormLogId)) {\n const ids = rows\n .map(normalizeId)\n .map((id) => Number(id))\n .filter((id) => Number.isFinite(id) && id > 0);\n\n if (ids.length > 0) {\n resolvedStormLogId = Math.max(...ids);\n }\n}\n\nreturn [{\n json: {\n ...(reportFileItem.json || {}),\n ...payload,\n storm_log_id: hasValue(resolvedStormLogId) ? resolvedStormLogId : 'unknown',\n },\n binary: reportFileItem.binary,\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 205696, + 80448 + ], + "id": "d5a5ca1f-70c2-4310-b9cc-ffd9ccdcd656", + "name": "Resolve Storm Log Id" + }, + { + "parameters": { + "resource": "file", + "options": { + "channelId": "C0A9K1AC7SN", + "initialComment": "=✅ Lightning report uploaded for *{{ $json.customer_name }}* (log id: *{{ $json.storm_log_id }}*)." + } + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 206272, + 80448 + ], + "id": "9c01e454-8f6c-47bc-a705-84d0a6dbb6da", + "name": "Upload Report to Slack", + "webhookId": "c4d5e6f7-a8b9-4012-cdef-345678901234", + "credentials": { + "slackApi": { + "id": "S5M0i1zlyebqU6oP", + "name": "n8n_lightning_report_bot" + } + } + }, + { + "parameters": { + "select": "channel", + "channelId": { + "__rl": true, + "value": "C0A9K1AC7SN", + "mode": "list", + "cachedResultName": "n8n-events" + }, + "messageType": "block", + "blocksUi": "={{ {\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": `Approve or reject this report for *${$('Resolve Storm Log Id').item.json.customer_name ?? ''}* (log id: \\`${$('Resolve Storm Log Id').item.json.storm_log_id}\\`).`\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"Approve\", \"emoji\": true },\n \"style\": \"primary\",\n \"action_id\": \"approve_storm_log\",\n \"value\": String($('Resolve Storm Log Id').item.json.storm_log_id)\n },\n {\n \"type\": \"button\",\n \"text\": { \"type\": \"plain_text\", \"text\": \"Reject\", \"emoji\": true },\n \"style\": \"danger\",\n \"action_id\": \"reject_storm_log\",\n \"value\": String($('Resolve Storm Log Id').item.json.storm_log_id)\n }\n ]\n }\n ]\n} }}", + "text": "Lightning storm report approval", + "otherOptions": {} + }, + "type": "n8n-nodes-base.slack", + "typeVersion": 2.4, + "position": [ + 206848, + 80592 + ], + "id": "a665bfe7-2e8b-4c0e-b10f-bbf0a39bea81", + "name": "Send Approval Buttons", + "webhookId": "d4e5f6a7-b8c9-0123-def0-456789abcdef", + "credentials": { + "slackApi": { + "id": "S5M0i1zlyebqU6oP", + "name": "n8n_lightning_report_bot" + } + } + }, + { + "parameters": { + "httpMethod": "POST", + "path": "slack-storm-log-approval", + "responseMode": "responseNode", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [ + 198992, + 80912 + ], + "id": "7707402e-8f62-4065-a57d-7eb8006ac166", + "name": "Slack Interaction Webhook", + "webhookId": "c9d8e7f6-a5b4-4321-fedc-ba9876543211" + }, + { + "parameters": { + "jsCode": "const raw = $input.first().json;\nlet payloadRaw = raw.payload;\nif (payloadRaw === undefined && raw.body !== undefined) {\n const b = raw.body;\n if (typeof b === 'string' && b.includes('payload=')) {\n try {\n payloadRaw = new URLSearchParams(b).get('payload');\n } catch {\n payloadRaw = undefined;\n }\n } else if (b && typeof b === 'object' && b.payload !== undefined) {\n payloadRaw = b.payload;\n }\n}\nlet payload;\nif (payloadRaw !== null && payloadRaw !== undefined && typeof payloadRaw === 'object') {\n payload = payloadRaw;\n} else if (typeof payloadRaw === 'string') {\n try {\n payload = JSON.parse(payloadRaw);\n } catch {\n return [{ json: { valid: false } }];\n }\n} else {\n return [{ json: { valid: false } }];\n}\nif (payload.type !== 'block_actions') {\n return [{ json: { valid: false } }];\n}\nconst action = payload.actions?.[0];\nconst rawId = String(action?.value ?? '').trim();\nif (!rawId || !action?.action_id) {\n return [{ json: { valid: false } }];\n}\nlet status = null;\nif (action.action_id === 'approve_storm_log') {\n status = 'confirmed';\n} else if (action.action_id === 'reject_storm_log') {\n status = 'rejected';\n}\nif (!status) {\n return [{ json: { valid: false } }];\n}\n\nconst sectionText = payload?.message?.blocks?.find((block) => block?.type === 'section')?.text?.text || '';\nconst fallbackText = payload?.message?.text || '';\nconst sourceText = sectionText || fallbackText;\nconst customerMatch = sourceText.match(/for \\*(.*?)\\* \\(log id:/i) || sourceText.match(/for (.*?) \\(log id:/i);\nconst customer_name = customerMatch?.[1]?.trim() || 'unknown customer';\n\nconst numId = Number(rawId);\nconst id = Number.isFinite(numId) ? numId : rawId;\nconst slackResponseUrl = typeof payload.response_url === 'string' ? payload.response_url.trim() : '';\nreturn [{ json: { valid: true, id, status, customer_name, slack_response_url: slackResponseUrl } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 199232, + 80912 + ], + "id": "bcd115e6-f807-4468-a7dd-7f9e84823b77", + "name": "Parse Slack Button Payload" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "b8c9d0e1-f2a3-4455-b667-c78890123456", + "leftValue": "={{ $json.valid }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 199472, + 80912 + ], + "id": "23f1a622-c3ec-41ab-9984-f39cc4a4380e", + "name": "Valid Storm Approval Button?" + }, + { + "parameters": { + "operation": "update", + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "matchType": "allConditions", + "filters": { + "conditions": [ + { + "keyValue": "={{ $json.id }}" + } + ] + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "status": "={{ $json.status }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "status", + "displayName": "status", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 200224, + 80912 + ], + "id": "77bc5739-9d4f-4640-ad1d-585e7edf10af", + "name": "Approve Pending Row" + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Still Valid After Ack?').first().json.slack_response_url }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ (() => {\n const ack = $('Still Valid After Ack?').first().json;\n const isConfirmed = ack.status === 'confirmed';\n const id = String(ack.id ?? '');\n const customer = String(ack.customer_name || 'unknown customer');\n const verb = isConfirmed ? 'approved' : 'rejected';\n return {\n response_type: 'in_channel',\n replace_original: false,\n text: `Storm log #${id} for ${customer} ${verb} and recorded in storm_logs.`,\n };\n})() }}", + "options": {} + }, + "id": "f7e97be7-f790-40ff-85e0-621f838f692f", + "name": "Notify Slack Recording Result", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 203280, + 81296 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "{}", + "options": {} + }, + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.1, + "position": [ + 199728, + 80912 + ], + "id": "686c8a3d-8e76-4081-99b2-67bb70f505a2", + "name": "Respond Slack Interaction" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "f7e8d9c0-a1b2-4333-c445-d56677889901", + "leftValue": "={{ $json.valid }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 199920, + 80912 + ], + "id": "14d670e2-c7ce-486e-b018-14c36c6e5160", + "name": "Still Valid After Ack?" + }, + { + "parameters": { + "jsCode": "\n\n// Grab the data from the Logic Gate node explicitly\nconst stormData = $('Logic Gate').item.json;\n\n// Check if \"Get row(s)\" found anything\nconst existing = $input.all().filter(i => i.json && i.json.id !== undefined && i.json.id !== null);\n\nif (existing.length > 0) {\n // If found, merge and skip\n return [{ json: { ...stormData, skip_insert: true } }];\n}\n\n// If not found, merge and allow insert\nreturn [{ json: { ...stormData, skip_insert: false } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 201888, + 80480 + ], + "id": "cace5f97-bb92-4e60-a78a-1eff57fabf57", + "name": "Prepare Insert Decision" + }, + { + "parameters": { + "jsCode": "const reportItem = $input.first();\nlet customer = {};\ntry {\n customer = $('Loop Over Items').first().json || {};\n} catch (e) {\n customer = {};\n}\nconst contactEmail = String(customer.contact_email || '').trim();\nconst fileName = reportItem.binary?.data?.fileName || 'lightning_report.docx';\nconst customerName = reportItem.json.customer_name || customer.customer_name || 'Customer';\n\nreturn [{\n json: {\n ...reportItem.json,\n contact_email: contactEmail,\n customer_id: customer.id,\n email_file_name: fileName,\n customer_name: customerName,\n },\n binary: reportItem.binary,\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 205888, + 80448 + ], + "id": "5af333d1-b692-4c2d-b721-9a144f06dca6", + "name": "Prepare Customer Email Context" + }, + { + "parameters": { + "content": "Add these columns to the storm_logs datatable:\n- pending_contact_email (string)\n- pending_email_file_name (string)\n- pending_report_base64 (long text)\n- pending_report_mime_type (string)\n\nReports persist here until approval email is sent or rejected.", + "width": 420 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 200176, + 81488 + ], + "typeVersion": 1, + "id": "02a93a79-3473-49e3-b412-4f880a34c75c", + "name": "Sticky Note Storm Logs Email Columns" + }, + { + "parameters": { + "jsCode": "const item = $input.first();\nconst stormLogId = String(item.json.storm_log_id ?? '').trim();\nconst binaryMeta = item.binary?.data || {};\nlet reportBase64 = '';\n\ntry {\n const buffer = await this.helpers.getBinaryDataBuffer(0, 'data');\n reportBase64 = buffer.toString('base64');\n} catch {\n const raw = binaryMeta.data;\n if (typeof raw === 'string') {\n reportBase64 = raw.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\n } else if (raw) {\n reportBase64 = Buffer.from(raw).toString('base64');\n }\n}\n\nconst persistSkipped = !stormLogId || stormLogId === 'unknown' || !reportBase64;\n\nreturn [{\n json: {\n ...item.json,\n storm_log_id: stormLogId,\n pending_contact_email: item.json.contact_email || '',\n pending_email_file_name: item.json.email_file_name || 'lightning_report.docx',\n pending_report_base64: reportBase64,\n pending_report_mime_type: binaryMeta.mimeType || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n persist_skipped: persistSkipped,\n },\n binary: item.binary,\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 206080, + 80448 + ], + "id": "ff06bf5e-4755-424b-a3c3-80250259de2d", + "name": "Build Pending Email Record" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "a9b8c7d6-e5f4-3210-abcd-ef0987654321", + "leftValue": "={{ $('Build Pending Email Record').first().json.persist_skipped }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "false", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 206464, + 80448 + ], + "id": "01709423-0268-4d04-ab93-1fd35c005e3f", + "name": "Can Persist Report?" + }, + { + "parameters": { + "operation": "update", + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "matchType": "allConditions", + "filters": { + "conditions": [ + { + "keyValue": "={{ $('Build Pending Email Record').first().json.storm_log_id }}" + } + ] + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "pending_contact_email": "={{ $('Build Pending Email Record').first().json.pending_contact_email }}", + "pending_email_file_name": "={{ $('Build Pending Email Record').first().json.pending_email_file_name }}", + "pending_report_base64": "={{ $('Build Pending Email Record').first().json.pending_report_base64 }}", + "pending_report_mime_type": "={{ $('Build Pending Email Record').first().json.pending_report_mime_type }}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "pending_contact_email", + "displayName": "pending_contact_email", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "pending_email_file_name", + "displayName": "pending_email_file_name", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "pending_report_base64", + "displayName": "pending_report_base64", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + }, + { + "id": "pending_report_mime_type", + "displayName": "pending_report_mime_type", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "readOnly": false, + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 206704, + 80432 + ], + "id": "9085f244-e475-477c-9168-f020628af606", + "name": "Persist Report To Storm Logs" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "leftValue": "={{ $('Still Valid After Ack?').first().json.status }}", + "rightValue": "confirmed", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 200416, + 81248 + ], + "id": "8239a011-b5cf-4a6d-8fbd-df6655b4a0da", + "name": "Report Approved?" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "PCYhmruG5hu52Alf", + "mode": "list", + "cachedResultName": "storm_logs", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/PCYhmruG5hu52Alf" + }, + "matchType": "allConditions", + "filters": { + "conditions": [ + { + "keyValue": "={{ $('Still Valid After Ack?').first().json.id }}" + } + ] + }, + "limit": 1 + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 200688, + 81104 + ], + "id": "196a2d9e-dc7d-4a28-bdf1-49969010e3ac", + "name": "Get Pending Report From Storm Logs", + "alwaysOutputData": true + }, + { + "parameters": { + "jsCode": "const ack = $('Still Valid After Ack?').first().json;\nconst stormLogId = String(ack.id ?? '').trim();\nconst rows = $('Get Pending Report From Storm Logs').all()\n .map((item) => item.json)\n .filter((row) => row && row.id !== undefined && row.id !== null);\nconst row = rows[0] || {};\nlet reportBase64 = String(row.pending_report_base64 || '').trim();\nreportBase64 = reportBase64.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\nconst hasQueued = reportBase64.length > 0;\n\nreturn [{\n json: {\n ...ack,\n has_queued_report: hasQueued,\n contact_email: String(row.pending_contact_email || '').trim(),\n customer_name: row.customer_name || ack.customer_name,\n email_file_name: row.pending_email_file_name || 'lightning_report.docx',\n pending_report_base64: reportBase64,\n pending_report_mime_type: row.pending_report_mime_type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n storm_log_id: stormLogId,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 200912, + 81104 + ], + "id": "9ea4bf46-ff7b-4db3-8fc7-bc101a75190d", + "name": "Load Queued Customer Report" + }, + { + "parameters": { + "operation": "get", + "dataTableId": { + "__rl": true, + "value": "bMJhm1lTDbCj9eY1", + "mode": "list", + "cachedResultName": "customers", + "cachedResultUrl": "/projects/dWFeIPZ8ouir7Ex0/datatables/bMJhm1lTDbCj9eY1" + }, + "matchType": "allConditions", + "filters": { + "conditions": [ + { + "keyName": "customer_name", + "keyValue": "={{ $('Load Queued Customer Report').item.json.customer_name }}" + } + ] + }, + "limit": 1 + }, + "type": "n8n-nodes-base.dataTable", + "typeVersion": 1.1, + "position": [ + 201152, + 81104 + ], + "id": "f4923090-6139-4fe1-b657-add072662217", + "name": "Lookup Customer Contact Email", + "alwaysOutputData": true + }, + { + "parameters": { + "jsCode": "const ack = $('Load Queued Customer Report').first().json;\nconst lookupRows = $input.all().map((i) => i.json).filter((r) => r && (r.contact_email || r.id));\nconst lookupEmail = lookupRows.length > 0 ? String(lookupRows[0].contact_email || '').trim() : '';\nconst contactEmail = String(ack.contact_email || '').trim() || lookupEmail;\n\nreturn [{\n json: {\n ...ack,\n contact_email: contactEmail,\n },\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 201360, + 81104 + ], + "id": "81bf0ff7-7145-460f-a977-cd811ac0a2c8", + "name": "Merge Customer Contact Email" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "e5f6a7b8-c9d0-1234-ef01-345678901234", + "leftValue": "={{ $json.contact_email }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "notEmpty" + } + }, + { + "id": "f6a7b8c9-d0e1-2345-f012-456789012346", + "leftValue": "={{ $json.has_queued_report }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 201568, + 81104 + ], + "id": "73370502-a532-4990-84ef-1532668dbfc1", + "name": "Ready To Email Customer?" + }, + { + "parameters": { + "jsCode": "const item = $input.first();\nconst toEmail = String(item.json.contact_email || '').trim();\nconst fromEmail = 'info@iklim.co';\nconst customerName = String(item.json.customer_name || 'Customer');\nconst fileName = String(item.json.email_file_name || 'lightning_report.docx');\nconst mimeType = item.json.pending_report_mime_type || 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n\nlet content = String(item.json.pending_report_base64 || '').trim();\ncontent = content.replace(/^data:[^;]+;base64,/, '').replace(/\\s+/g, '');\n\nif (!toEmail) {\n throw new Error('Missing contact_email for SendGrid');\n}\nif (!content) {\n throw new Error('Missing pending_report_base64 for SendGrid attachment');\n}\n\ntry {\n Buffer.from(content, 'base64');\n} catch {\n throw new Error('pending_report_base64 is not valid base64');\n}\n\nreturn [{\n json: {\n ...item.json,\n sendgrid_payload: {\n personalizations: [{ to: [{ email: toEmail }] }],\n from: { email: fromEmail, name: 'iklim.co' },\n subject: `Yıldırım Aktivite Raporu – ${customerName}`,\n content: [{\n type: 'text/html',\n value: `
Sayın yetkili,
${customerName} için yıldırım aktivite raporunuz ektedir.
İyi çalışmalar,
İklim.co