{
  "id": "63JYhSmjThe2KYJw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "GDPR Data Subject Request Handler with Microsoft Graph and SharePoint",
  "tags": [],
  "nodes": [
    {
      "id": "8a4ab4ef-ba9d-4113-b4aa-4a28e4205f41",
      "name": "DSAR Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "notes": "Receives DSAR request. Expected body: { subjectName, subjectEmail, requestType, requestDate, requestRef }",
      "position": [
        4656,
        -1408
      ],
      "parameters": {
        "path": "gdpr-dsar",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "b246a3e1-4ad8-4c4a-8b05-af589b04c0ad",
      "name": "Validate and Prepare DSAR",
      "type": "n8n-nodes-base.code",
      "position": [
        4880,
        -1408
      ],
      "parameters": {
        "jsCode": "const body = $input.first().json.body || $input.first().json;\n\nif (!body.subjectEmail) {\n  throw new Error('subjectEmail is required in DSAR request');\n}\n\nconst requestRef = body.requestRef || `DSAR-${Date.now()}`;\nconst deadline = new Date();\ndeadline.setDate(deadline.getDate() + 30);\n\nreturn [{\n  json: {\n    requestRef,\n    subjectName: body.subjectName || '',\n    subjectEmail: body.subjectEmail,\n    requestType: body.requestType || 'access',\n    requestDate: body.requestDate || new Date().toISOString(),\n    deadline: deadline.toISOString(),\n    status: 'processing',\n    searchQuery: `\"${body.subjectEmail}\"`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ab5638cd-b89a-4655-ac8f-5e7044e686ec",
      "name": "Create eDiscovery Case",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "No native n8n node covers the Microsoft Security / Compliance eDiscovery API \u2014 HTTP request is required here.\nRequired Azure AD permission: eDiscovery.ReadWrite.All\nThis is the only Graph API path that allows a background app (Client Credentials) to search across all tenant mailboxes.",
      "position": [
        5152,
        -1568
      ],
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/security/cases/ediscoveryCases",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"displayName\": \"DSAR - {{ $json.subjectEmail }} - {{ $json.requestRef }}\",\n  \"description\": \"Automated GDPR DSAR. RequestRef: {{ $json.requestRef }}\",\n  \"externalId\": \"{{ $json.requestRef }}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "microsoftGraphOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "fe3417c1-62ee-46f0-b3c2-e7c35c7f6e86",
      "name": "Create eDiscovery Search (All Mailboxes)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "No native n8n node covers the eDiscovery Search sub-resource \u2014 HTTP request is required here.\nKQL participants: operator matches From, To, CC, BCC across all tenant mailboxes.\nRequired permission: eDiscovery.ReadWrite.All",
      "position": [
        5376,
        -1568
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/security/cases/ediscoveryCases/{{ $json.id }}/searches",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"displayName\": \"Email search for {{ $('Validate and Prepare DSAR').first().json.subjectEmail }}\",\n  \"contentQuery\": \"participants:{{ $('Validate and Prepare DSAR').first().json.subjectEmail }}\",\n  \"dataSourceScopes\": \"allMailboxes\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "microsoftGraphOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "a7fdb846-078b-44bb-ad5b-3c251e6f3446",
      "name": "Trigger Statistics Estimate",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "No native n8n node covers the eDiscovery estimateStatistics action \u2014 HTTP request is required here.\nPOST initiates the async background estimation job. Returns 202 Accepted immediately.\nResults are polled via /lastEstimateStatisticsOperation after the Wait node.",
      "position": [
        5600,
        -1568
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/security/cases/ediscoveryCases/{{ $('Create eDiscovery Case').first().json.id }}/searches/{{ $json.id }}/estimateStatistics",
        "method": "POST",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftGraphOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "1ebbc5bc-a005-4777-91e2-7f9faf92922b",
      "name": "Wait for Estimate to Complete",
      "type": "n8n-nodes-base.wait",
      "notes": "eDiscovery estimate jobs are async. 60s covers most tenants. Increase to 120s for large orgs or implement a poll loop checking status === 'success'.",
      "position": [
        5824,
        -1568
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 60
      },
      "typeVersion": 1
    },
    {
      "id": "1a7e390d-0f51-478f-9160-649fec189887",
      "name": "Fetch eDiscovery Statistics",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "No native n8n node covers the eDiscovery lastEstimateStatisticsOperation sub-resource \u2014 HTTP request is required here.\nReturns a single object: { status, mailboxCount, mailboxesWithResults, itemCount, itemSize }.\nIf status !== 'success', increase the Wait node duration above.",
      "position": [
        6048,
        -1568
      ],
      "parameters": {
        "url": "=https://graph.microsoft.com/v1.0/security/cases/ediscoveryCases/{{ $('Create eDiscovery Case').first().json.id }}/searches/{{ $('Create eDiscovery Search (All Mailboxes)').first().json.id }}/lastEstimateStatisticsOperation",
        "options": {},
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftGraphOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "e0633cf3-b6fd-4cbc-8604-c9d97d9a2e19",
      "name": "Search SharePoint + OneDrive",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "notes": "NATIVE NODE: returnAll: true \u2014 n8n handles all offset pagination internally and emits a single flat array of all matching items directly to the Merge node.\nLoop node removed: the loop done-branch data loss trap no longer applies.\nCovers SharePoint listItem and OneDrive driveItem within the same tenant search scope.\nCredential: Microsoft SharePoint OAuth2 (requires Sites.Read.All and Files.Read.All).",
      "position": [
        5584,
        -1184
      ],
      "parameters": {
        "operation": "search",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "03167abc-bb35-4142-9be8-c65970c0d906",
      "name": "Merge Email + SP Results",
      "type": "n8n-nodes-base.merge",
      "notes": "Input 0: eDiscovery statistics object. Input 1: SharePoint + OneDrive flat item array (all pages, via returnAll).",
      "position": [
        6272,
        -1440
      ],
      "parameters": {
        "mode": "waitForAll"
      },
      "typeVersion": 3
    },
    {
      "id": "88f78e48-f844-4bf6-8e1e-40b170de91b5",
      "name": "Aggregate and Sanitize Results",
      "type": "n8n-nodes-base.code",
      "position": [
        6496,
        -1440
      ],
      "parameters": {
        "jsCode": "// Safely fetch items by directly querying the generating nodes \u2014 immune to Merge array index shifts.\nconst emailInput = $('Fetch eDiscovery Statistics').first().json;\nconst spInputItems = $('Search SharePoint + OneDrive').all();\n\n// --- EMAIL STATISTICS ---\nconst emailStats = {\n  mailboxesSearched: emailInput?.mailboxCount ?? 0,\n  mailboxesWithHits: emailInput?.mailboxesWithResults ?? 0,\n  estimatedItemCount: emailInput?.itemCount ?? 0,\n  estimatedSizeBytes: emailInput?.itemSize ?? 0\n};\n\n// --- SHAREPOINT + ONEDRIVE RESULTS ---\nconst spodResults = [];\nfor (const item of spInputItems) {\n  const data = item.json;\n  if (!data) continue;\n  const isOneDrive = data['@odata.type']?.includes('driveItem') ||\n                     data.parentReference?.driveType === 'business';\n  spodResults.push({\n    source: isOneDrive ? 'OneDrive' : 'SharePoint',\n    id: data.id,\n    name: data.name,\n    url: data.webUrl,\n    modifiedAt: data.lastModifiedDateTime,\n    containsPII: true\n  });\n}\n\nconst spDocs = spodResults.filter(r => r.source === 'SharePoint');\nconst odFiles = spodResults.filter(r => r.source === 'OneDrive');\n\nconst dsarMeta = $('Validate and Prepare DSAR').first().json;\nconst caseId = $('Create eDiscovery Case').first().json.id;\nconst searchId = $('Create eDiscovery Search (All Mailboxes)').first().json.id;\n\nreturn [{\n  json: {\n    dsarMeta,\n    eDiscoveryCaseId: caseId,\n    eDiscoverySearchId: searchId,\n    findings: {\n      emailStats,\n      sharePointDocuments: spDocs,\n      oneDriveFiles: odFiles,\n      totalEstimatedEmails: emailStats.estimatedItemCount,\n      totalSPDocsFound: spDocs.length,\n      totalODFilesFound: odFiles.length,\n      totalFound: emailStats.estimatedItemCount + spodResults.length\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "cf2ae535-8446-49d7-9f5d-6369658f10d0",
      "name": "Generate Cover Letter (Basic LLM Chain)",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "notes": "NATIVE NODE: Replaced HTTP Request to Ollama with the native LangChain Basic LLM Chain node.\nReceives ONLY counts and metadata \u2014 zero raw PII reaches the LLM.\nConnect the Ollama Model sub-node (below) as the language model input.\nData sovereignty preserved: Ollama runs locally, no PII leaves your infrastructure.",
      "position": [
        6720,
        -1440
      ],
      "parameters": {
        "text": "=You are a GDPR compliance assistant. Draft a formal, polite cover letter for a Data Subject Access Request response. Use only the statistics provided \u2014 do NOT invent or infer any personal data records.\n\nRequest reference: {{ $json.dsarMeta.requestRef }}\nData subject: {{ $json.dsarMeta.subjectName }} ({{ $json.dsarMeta.subjectEmail }})\nRequest type: {{ $json.dsarMeta.requestType }}\nRequest date: {{ $json.dsarMeta.requestDate }}\nResponse deadline: {{ $json.dsarMeta.deadline }}\n\nData found across Microsoft 365:\n- Estimated emails across all mailboxes: {{ $json.findings.totalEstimatedEmails }} (across {{ $json.findings.emailStats.mailboxesWithHits }} mailboxes)\n- SharePoint documents: {{ $json.findings.totalSPDocsFound }}\n- OneDrive files: {{ $json.findings.totalODFilesFound }}\n- Total items identified: {{ $json.findings.totalFound }}\n\nDraft a cover letter stating that the request has been received, the relevant data has been identified and is under review by the Data Protection Officer, and that the subject will receive a full response before the deadline. Keep it professional and under 200 words. Output only the letter text with no preamble.",
        "promptType": "define"
      },
      "typeVersion": 1.4
    },
    {
      "id": "29852a54-e1fe-4bc3-bbff-da9424d784f0",
      "name": "Ollama Model",
      "type": "@n8n/n8n-nodes-langchain.lmOllama",
      "notes": "NATIVE NODE: Ollama language model sub-node. Connect to the 'model' input of the Basic LLM Chain node above.\nDefault base URL: http://localhost:11434 \u2014 change in credentials if Ollama is hosted elsewhere.\nRequires llama3 model pulled: run 'ollama pull llama3' on your server.",
      "position": [
        6720,
        -1216
      ],
      "parameters": {
        "model": "llama3",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "20e79fac-ded1-45f8-9195-73f2b6ab372b",
      "name": "Build GDPR Report Document",
      "type": "n8n-nodes-base.code",
      "position": [
        7072,
        -1440
      ],
      "parameters": {
        "jsCode": "const findings = $('Aggregate and Sanitize Results').first().json;\n// LangChain chainLlm outputs the text in item.json.text\nconst coverLetter = $input.first().json.text || 'Cover letter generation failed \u2014 review findings manually.';\n\nconst dsarMeta = findings.dsarMeta;\nconst f = findings.findings;\n\nconst reportContent = `GDPR DATA SUBJECT REQUEST REPORT\n=========================================\nRequest Reference: ${dsarMeta.requestRef}\nRequest Type: ${dsarMeta.requestType.toUpperCase()}\nData Subject: ${dsarMeta.subjectName} (${dsarMeta.subjectEmail})\nRequest Date: ${dsarMeta.requestDate}\nResponse Deadline: ${dsarMeta.deadline}\nReport Generated: ${new Date().toISOString()}\nProcessed By: AutomiQ GDPR Automation System\neDiscovery Case ID: ${findings.eDiscoveryCaseId}\neDiscovery Search ID: ${findings.eDiscoverySearchId}\n\n-----------------------------------------\nDPO COVER LETTER DRAFT\n-----------------------------------------\n${coverLetter}\n\n-----------------------------------------\nDATA INVENTORY (deterministic \u2014 for DPO review)\n-----------------------------------------\nEMAIL STATISTICS (via eDiscovery API \u2014 tenant-wide)\n  Mailboxes searched: all tenant mailboxes\n  Mailboxes with hits: ${f.emailStats.mailboxesWithHits}\n  Estimated items: ${f.emailStats.estimatedItemCount}\n  Estimated size: ${(f.emailStats.estimatedSizeBytes / 1024 / 1024).toFixed(2)} MB\n  NOTE: Full email export must be performed by DPO via Microsoft Purview Compliance Portal\n  using eDiscovery Case ID: ${findings.eDiscoveryCaseId}\n\nSHAREPOINT DOCUMENTS (${f.totalSPDocsFound} items \u2014 all pages retrieved)\n${f.sharePointDocuments.map(d => `  - ${d.name} | Modified: ${d.modifiedAt} | URL: ${d.url}`).join('\\n') || '  None found'}\n\nONEDRIVE FILES (${f.totalODFilesFound} items \u2014 all pages retrieved)\n${f.oneDriveFiles.map(fi => `  - ${fi.name} | Modified: ${fi.modifiedAt} | URL: ${fi.url}`).join('\\n') || '  None found'}\n\n-----------------------------------------\nCOMPLIANCE RECORD\n-----------------------------------------\nTotal Data Items Identified: ${f.totalFound}\nSearch Scope: Exchange Online (all mailboxes via eDiscovery), SharePoint Online, OneDrive for Business\nEmail search method: Microsoft Graph eDiscovery API (Application permissions \u2014 tenant-wide)\nDocument search method: Native Microsoft SharePoint node with offset loop\nLLM method: Native LangChain Ollama node (local \u2014 no PII leaves infrastructure)\nProcessed in compliance with GDPR Article 15 (Right of Access)\n`;\n\nreturn [{\n  json: {\n    dsarMeta,\n    findings: f,\n    eDiscoveryCaseId: findings.eDiscoveryCaseId,\n    reportContent,\n    reportFileName: `GDPR_DSAR_${dsarMeta.requestRef}_${new Date().toISOString().split('T')[0]}.txt`,\n    coverLetter\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "de8fc469-5a39-4037-8a39-2b29b80c526e",
      "name": "Convert Report to Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        7296,
        -1440
      ],
      "parameters": {
        "jsCode": "const item = $input.first();\nconst reportText = item.json.reportContent;\nconst encoded = Buffer.from(reportText, 'utf-8').toString('base64');\n\nreturn [{\n  json: item.json,\n  binary: {\n    data: {\n      data: encoded,\n      mimeType: 'text/plain',\n      fileName: item.json.reportFileName,\n      fileSize: reportText.length\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d9ca1e0e-3e8e-44c1-b4a8-4b4e04fcfa6e",
      "name": "Archive Report to SharePoint GDPR Library",
      "type": "n8n-nodes-base.microsoftSharePoint",
      "notes": "Restrict this SharePoint library to DPO and legal only.",
      "position": [
        7680,
        -1632
      ],
      "parameters": {
        "site": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "fileName": "={{ $('Build GDPR Report Document').first().json.reportFileName }}",
        "operation": "upload",
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "d94b8979-539e-4036-b675-c1c3589d5f50",
      "name": "Log DSAR to Compliance Register",
      "type": "n8n-nodes-base.microsoftExcel",
      "notes": "GDPR compliance register Excel. Keep in the restricted SharePoint library.",
      "position": [
        7680,
        -1440
      ],
      "parameters": {
        "operation": "appendOrUpdate"
      },
      "typeVersion": 2
    },
    {
      "id": "b782d376-7547-4d14-8296-b5828ee031bd",
      "name": "Notify DPO for Review (NOT Data Subject)",
      "type": "n8n-nodes-base.microsoftOutlook",
      "notes": "Email goes to internal DPO/Legal team only. Set the To field to your DPO email address.",
      "position": [
        7712,
        -1152
      ],
      "parameters": {
        "subject": "=[DPO ACTION REQUIRED] GDPR DSAR {{ $('Build GDPR Report Document').first().json.dsarMeta.requestType.toUpperCase() }} \u2013 {{ $('Build GDPR Report Document').first().json.dsarMeta.requestRef }} \u2013 Deadline {{ $('Build GDPR Report Document').first().json.dsarMeta.deadline.split('T')[0] }}",
        "additionalFields": {}
      },
      "typeVersion": 2
    },
    {
      "id": "1b093410-8276-41bf-a45b-c304f631835c",
      "name": "Merge A \u2013 SP Archive + Excel Log",
      "type": "n8n-nodes-base.merge",
      "notes": "Daisy-chained merge (n8n v3 Merge supports 2 inputs max).\nInput 0: SharePoint archive. Input 1: Excel log.",
      "position": [
        7904,
        -1536
      ],
      "parameters": {
        "mode": "waitForAll"
      },
      "typeVersion": 3
    },
    {
      "id": "5a425d3d-eede-46d0-9375-31a7baf1e8ff",
      "name": "Merge B \u2013 (SP+Excel) + DPO Email",
      "type": "n8n-nodes-base.merge",
      "notes": "Input 0: Merge A output. Input 1: DPO email.\n200 response only fires when all three final actions have succeeded.",
      "position": [
        8128,
        -1440
      ],
      "parameters": {
        "mode": "waitForAll"
      },
      "typeVersion": 3
    },
    {
      "id": "8a1d78e0-9c61-4c53-b7cc-6d7b94a44e82",
      "name": "Respond 200 Accepted",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        8352,
        -1440
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'accepted', requestRef: $('Build GDPR Report Document').first().json.dsarMeta.requestRef, deadline: $('Build GDPR Report Document').first().json.dsarMeta.deadline, itemsFound: $('Build GDPR Report Document').first().json.findings.totalFound, eDiscoveryCaseId: $('Build GDPR Report Document').first().json.eDiscoveryCaseId }) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "3ef4fbc4-1544-45f5-93c9-5adbe0dc0f6b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        -1776
      ],
      "parameters": {
        "width": 704,
        "height": 976,
        "content": "## GDPR DSAR Automation: Microsoft 365 eDiscovery Search with Ollama Cover Letter and Compliance Logging\n\n### Webhook-Triggered Data Subject Access Request Pipeline for Microsoft 365 Tenants\n\n### How it works\nThis workflow receives a GDPR Data Subject Access Request via webhook, searches\nfor the subject's data across all Microsoft 365 services using the eDiscovery\nAPI and native SharePoint node, generates a DPO cover letter draft using a\nlocal Ollama model, archives the report to a restricted SharePoint library,\nlogs the request to a compliance register, and notifies the DPO by email \u2014\nall without raw PII touching any external AI service.\n\n### Setup steps\n- [ ] Connect your Microsoft Graph OAuth2 credential (with\n      eDiscovery.ReadWrite.All) to the three eDiscovery HTTP Request nodes.\n- [ ] Connect your Microsoft SharePoint OAuth2 credential (with\n      Sites.Read.All and Files.Read.All) to the Search SharePoint + OneDrive\n      node and the Archive Report to SharePoint GDPR Library node.\n- [ ] Configure the Archive Report to SharePoint GDPR Library node with\n      your restricted GDPR document library site and folder. Restrict access\n      to DPO and legal team only.\n- [ ] Connect your Microsoft Excel credential to the Log DSAR to Compliance\n      Register node and point it to your compliance register workbook.\n- [ ] Connect your Microsoft Outlook credential to the Notify DPO for Review\n      node and set the To field to your DPO email address.\n- [ ] Ensure Ollama is running locally with llama3 pulled:\n      `ollama pull llama3`. Update the base URL in the Ollama credential if\n      Ollama is hosted on a different server.\n- [ ] For large Microsoft 365 tenants, increase the Wait for Estimate to\n      Complete node from 60 seconds to 120 seconds or implement a poll loop\n      checking status === 'success'.\n\n### Required credentials\n- **Microsoft Graph OAuth2** \u2014 eDiscovery.ReadWrite.All\n- **Microsoft SharePoint OAuth2** \u2014 Sites.Read.All, Files.Read.All,\n  Sites.ReadWrite.All (for archive upload)\n- **Microsoft Excel** (compliance register logging)\n- **Microsoft Outlook OAuth2** (DPO notification email)\n- **Ollama** (local LLM \u2014 no external API key required)\n\n### Data sovereignty note\nZero raw PII reaches any external AI service. The Ollama LLM receives only\ncounts and metadata. All processing stays within your infrastructure.\nFull email content export must be performed manually by the DPO via the\nMicrosoft Purview Compliance Portal using the eDiscovery Case ID provided\nin the report."
      },
      "typeVersion": 1
    },
    {
      "id": "eb29d1f0-3c7c-47b1-9027-f92deb7f57e6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4560,
        -1568
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 448,
        "content": "### Webhook Intake & Validation\nReceives the DSAR request via HTTP POST, validates that subjectEmail is\npresent, generates a request reference if none is provided, and calculates\nthe 30-day GDPR response deadline."
      },
      "typeVersion": 1
    },
    {
      "id": "b16aa676-82ef-4910-8501-213fd2e60498",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5120,
        -1712
      ],
      "parameters": {
        "color": 7,
        "width": 1056,
        "height": 352,
        "content": "### eDiscovery Email Search\nCreates a Microsoft Purview eDiscovery case, runs a tenant-wide mailbox search using the KQL participants: operator, triggers an async statistics\nestimate, waits for completion, and retrieves mailbox count, item count, and estimated size. Requires eDiscovery.ReadWrite.All app permission."
      },
      "typeVersion": 1
    },
    {
      "id": "eaf791d1-c5a6-45a5-95ad-61fb4331c912",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5120,
        -1312
      ],
      "parameters": {
        "color": 7,
        "width": 1056,
        "height": 352,
        "content": "### SharePoint & OneDrive Search\nSearches SharePoint Online and OneDrive for Business for documents and files matching the subject's email using the native SharePoint node with\nfull pagination. All pages are retrieved in a single flat array."
      },
      "typeVersion": 1
    },
    {
      "id": "bdb77fb6-43fd-4a7a-a11e-84485ba2ea1a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6224,
        -1568
      ],
      "parameters": {
        "color": 7,
        "width": 1216,
        "height": 512,
        "content": "### Merge, Sanitize, AI Draft & Report Assembly\nWaits for both search branches to complete and merges results. Aggregates and sanitizes findings, only counts and metadata pass downstream, no raw PII. Passes statistics only to a local Ollama LLM to generate a formal DPO cover letter draft. Assembles the full GDPR report document with cover letter, email statistics, SharePoint and OneDrive inventory, eDiscovery case IDs, and compliance metadata. Converts to binary for upload.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9eda25a1-7190-44f5-b587-94dc54fad45a",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7488,
        -1728
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 768,
        "content": "### Archive, Log, Notify & Respond\nArchives the report to a restricted SharePoint GDPR library, logs the request to the compliance register Excel workbook, and notifies the DPO\nby email, all three in parallel. A 200 Accepted response is returned to the webhook caller only after all three actions complete successfully."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "732a0843-ffa7-469f-94b7-5f3ffb3e6521",
  "nodeGroups": [],
  "connections": {
    "Ollama Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Cover Letter (Basic LLM Chain)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "DSAR Webhook Trigger": {
      "main": [
        [
          {
            "node": "Validate and Prepare DSAR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create eDiscovery Case": {
      "main": [
        [
          {
            "node": "Create eDiscovery Search (All Mailboxes)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Report to Binary": {
      "main": [
        [
          {
            "node": "Archive Report to SharePoint GDPR Library",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log DSAR to Compliance Register",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify DPO for Review (NOT Data Subject)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Email + SP Results": {
      "main": [
        [
          {
            "node": "Aggregate and Sanitize Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate and Prepare DSAR": {
      "main": [
        [
          {
            "node": "Create eDiscovery Case",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search SharePoint + OneDrive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build GDPR Report Document": {
      "main": [
        [
          {
            "node": "Convert Report to Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch eDiscovery Statistics": {
      "main": [
        [
          {
            "node": "Merge Email + SP Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger Statistics Estimate": {
      "main": [
        [
          {
            "node": "Wait for Estimate to Complete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search SharePoint + OneDrive": {
      "main": [
        [
          {
            "node": "Merge Email + SP Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Wait for Estimate to Complete": {
      "main": [
        [
          {
            "node": "Fetch eDiscovery Statistics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate and Sanitize Results": {
      "main": [
        [
          {
            "node": "Generate Cover Letter (Basic LLM Chain)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log DSAR to Compliance Register": {
      "main": [
        [
          {
            "node": "Merge A \u2013 SP Archive + Excel Log",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge A \u2013 SP Archive + Excel Log": {
      "main": [
        [
          {
            "node": "Merge B \u2013 (SP+Excel) + DPO Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge B \u2013 (SP+Excel) + DPO Email": {
      "main": [
        [
          {
            "node": "Respond 200 Accepted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Cover Letter (Basic LLM Chain)": {
      "main": [
        [
          {
            "node": "Build GDPR Report Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create eDiscovery Search (All Mailboxes)": {
      "main": [
        [
          {
            "node": "Trigger Statistics Estimate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify DPO for Review (NOT Data Subject)": {
      "main": [
        [
          {
            "node": "Merge B \u2013 (SP+Excel) + DPO Email",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Archive Report to SharePoint GDPR Library": {
      "main": [
        [
          {
            "node": "Merge A \u2013 SP Archive + Excel Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}