{
  "nodes": [
    {
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "target_path"
            },
            {
              "name": "root_folder_id"
            },
            {
              "name": "root_path"
            },
            {
              "name": "current_folder_id"
            },
            {
              "name": "current_path"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        5376,
        2064
      ],
      "id": "0e7d5f9e-d032-488a-b84e-20670364c8c8",
      "name": "Trigger"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "is-recursion",
              "leftValue": "={{ $json.current_folder_id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5600,
        2064
      ],
      "id": "581b29d1-6b74-4e46-98a2-6834f1daf5c8",
      "name": "Is Recursion?"
    },
    {
      "parameters": {
        "jsCode": "const targetPath = $('Trigger').item.json.target_path;\nconst rootPath = $('Trigger').item.json.root_path;\nconst rootFolderId = $('Trigger').item.json.root_folder_id;\n\n// Generate ancestor paths (longest first)\nconst paths = [];\nlet p = targetPath;\nwhile (p.length >= rootPath.length) {\n  paths.push(p);\n  if (p === rootPath) break;\n  p = p.substring(0, p.lastIndexOf('/'));\n}\n\n// Pad to 4 paths (use root_path for unused slots)\nwhile (paths.length < 4) {\n  paths.push(rootPath);\n}\n\nreturn [{\n  json: {\n    path1: paths[0],\n    path2: paths[1],\n    path3: paths[2],\n    path4: paths[3],\n    target_path: targetPath,\n    root_path: rootPath,\n    root_folder_id: rootFolderId\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5824,
        2064
      ],
      "id": "68c4b519-c750-4c7c-9a78-a490310756b2",
      "name": "Generate Ancestor Paths"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE",
          "mode": "list",
          "cachedResultName": "PathToIDLookup",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE/edit#gid=0"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "path",
              "lookupValue": "={{ $json.path1 }}"
            },
            {
              "lookupColumn": "path",
              "lookupValue": "={{ $json.path2 }}"
            },
            {
              "lookupColumn": "path",
              "lookupValue": "={{ $json.path3 }}"
            },
            {
              "lookupColumn": "path",
              "lookupValue": "={{ $json.path4 }}"
            }
          ]
        },
        "combineFilters": "OR",
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6048,
        2064
      ],
      "id": "858d6ce1-c090-4995-8545-e606f129b296",
      "name": "Cache OR Query",
      "alwaysOutputData": true,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get cache results from OR query\nconst cacheResults = $input.all();\nconst meta = $('Generate Ancestor Paths').item.json;\nconst targetPath = meta.target_path;\nconst rootPath = meta.root_path;\nconst rootFolderId = meta.root_folder_id;\n\nlet currentPath = rootPath;\nlet currentFolderId = rootFolderId;\n\n// Find deepest cached path that is a prefix of the target\nlet deepestCached = null;\nfor (const item of cacheResults) {\n  if (item.json.path && item.json.folder_id && targetPath.startsWith(item.json.path)) {\n    if (!deepestCached || item.json.path.length > deepestCached.path.length) {\n      deepestCached = { path: item.json.path, folder_id: item.json.folder_id };\n    }\n  }\n}\n\n// If target is cached, we're done!\nif (deepestCached && deepestCached.path === targetPath) {\n  return [{ json: {\n    status: 'done',\n    folder_id: deepestCached.folder_id,\n    folder_path: targetPath\n  }}];\n}\n\n// Use deepest cached as starting point\nif (deepestCached) {\n  currentPath = deepestCached.path;\n  currentFolderId = deepestCached.folder_id;\n}\n\n// Calculate next segment to traverse\nconst remaining = targetPath.substring(currentPath.length);\nconst segments = remaining.split('/').filter(s => s);\nconst nextSegment = segments[0];\nconst nextPath = currentPath + '/' + nextSegment;\n\nreturn [{\n  json: {\n    status: 'traverse',\n    target_path: targetPath,\n    root_folder_id: rootFolderId,\n    root_path: rootPath,\n    current_folder_id: currentFolderId,\n    current_path: currentPath,\n    next_segment: nextSegment,\n    next_path: nextPath\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6272,
        2064
      ],
      "id": "73f44d44-16ca-420e-b798-c2aeee75eeb5",
      "name": "Find Deepest Cached"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "is-done",
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "done",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "done"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "allMatchingOutputs": false
        }
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        6496,
        2064
      ],
      "id": "60b3c304-6657-4527-a468-31ae87335629",
      "name": "Status Check"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "out-id",
              "name": "folder_id",
              "value": "={{ $json.folder_id }}",
              "type": "string"
            },
            {
              "id": "out-path",
              "name": "folder_path",
              "value": "={{ $json.folder_path }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6720,
        2064
      ],
      "id": "96c584e5-c327-4037-91e0-9839bc2243de",
      "name": "Return (Cache Hit)"
    },
    {
      "parameters": {
        "jsCode": "// MERGE POINT: receives from TWO paths\n// Path 1 (recursion): from Is Recursion? (no 'status' field)\n// Path 2 (first call): from Status Check (has 'status' field)\n\nconst input = $input.item.json;\n\nconst targetPath = input.target_path;\nconst rootPath = input.root_path;\nconst rootFolderId = input.root_folder_id;\n\nlet currentPath, currentFolderId, nextSegment, nextPath;\n\nif (input.status) {\n  // First call path - has status='traverse', already calculated\n  currentPath = input.current_path;\n  currentFolderId = input.current_folder_id;\n  nextSegment = input.next_segment;\n  nextPath = input.next_path;\n} else {\n  // Recursion path - no status, need to calculate next segment\n  currentPath = input.current_path;\n  currentFolderId = input.current_folder_id;\n\n  const remaining = targetPath.substring(currentPath.length);\n  const segments = remaining.split('/').filter(s => s);\n  nextSegment = segments[0];\n  nextPath = currentPath + '/' + nextSegment;\n}\n\nreturn [{\n  json: {\n    target_path: targetPath,\n    root_folder_id: rootFolderId,\n    root_path: rootPath,\n    current_folder_id: currentFolderId,\n    current_path: currentPath,\n    next_segment: nextSegment,\n    next_path: nextPath\n  }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5376,
        2288
      ],
      "id": "6ed5e135-af08-41a6-bb4a-409aff03e247",
      "name": "Calculate Next Segment"
    },
    {
      "parameters": {
        "resource": "fileFolder",
        "searchMethod": "query",
        "queryString": "=name = '{{ $json.next_segment }}' and mimeType = 'application/vnd.google-apps.folder' and '{{ $json.current_folder_id }}' in parents and trashed = false",
        "limit": 1,
        "filter": {
          "folderId": {
            "__rl": true,
            "value": "={{ $json.current_folder_id }}",
            "mode": "id"
          },
          "whatToSearch": "folders"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        5600,
        2288
      ],
      "id": "1f08dd40-382f-4109-b1fe-61d13299fa85",
      "name": "Google Drive: Find Folder",
      "alwaysOutputData": true,
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "folder-found",
              "leftValue": "={{ $json.id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "exists"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        5824,
        2288
      ],
      "id": "8f942b44-ce38-48b7-9045-038ec3dc7f5c",
      "name": "Folder Found?"
    },
    {
      "parameters": {
        "resource": "folder",
        "name": "={{ $('Calculate Next Segment').item.json.next_segment }}",
        "driveId": {
          "__rl": true,
          "value": "MyDrive",
          "mode": "list",
          "cachedResultName": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $('Calculate Next Segment').item.json.current_folder_id }}",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        6048,
        2464
      ],
      "id": "4b4d96ab-aa74-431d-abea-656470542859",
      "name": "Create Folder",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Prepare data for caching and next steps\nconst driveResult = $input.item.json;\nconst meta = $('Calculate Next Segment').item.json;\n\nreturn [{\n  json: {\n    found_folder_id: driveResult.id,\n    found_folder_name: driveResult.name,\n    next_path: meta.next_path,\n    target_path: meta.target_path,\n    root_folder_id: meta.root_folder_id,\n    root_path: meta.root_path\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6048,
        2288
      ],
      "id": "db3c2790-96bf-4812-bc6f-e142527874e8",
      "name": "Prepare Cache Data"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE",
          "mode": "list",
          "cachedResultName": "PathToIDLookup",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/16vuiDJEZz3EOMN-8z7CF_1ENDWzjfORyykFNxqhGOWE/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "path": "={{ $json.next_path }}",
            "folder_id": "={{ $json.found_folder_id }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "path",
              "displayName": "path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "folder_id",
              "displayName": "folder_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        6256,
        2288
      ],
      "id": "98b97f9c-4996-4fbd-8f98-65ab61584a95",
      "name": "Cache New Path",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "is-target",
              "leftValue": "={{ $('Prepare Cache Data').item.json.next_path }}",
              "rightValue": "={{ $('Prepare Cache Data').item.json.target_path }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        6480,
        2288
      ],
      "id": "300756cb-2d98-4ace-ae55-3da8d3bd51e4",
      "name": "Reached Target?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "out-id",
              "name": "folder_id",
              "value": "={{ $('Prepare Cache Data').item.json.found_folder_id }}",
              "type": "string"
            },
            {
              "id": "out-path",
              "name": "folder_path",
              "value": "={{ $('Prepare Cache Data').item.json.target_path }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        6704,
        2272
      ],
      "id": "2294e345-3eb2-4487-b0f0-ed276ca0b342",
      "name": "Return (Found)"
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "vFnk7s9sqVnrt4hC",
          "mode": "list",
          "cachedResultUrl": "/workflow/vFnk7s9sqVnrt4hC",
          "cachedResultName": "gdrive-recursion"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {
            "target_path": "={{ $('Prepare Cache Data').item.json.target_path }}",
            "current_path": "={{ $('Prepare Cache Data').item.json.next_path }}",
            "current_folder_id": "={{ $('Prepare Cache Data').item.json.found_folder_id }}",
            "root_path": "={{ $('Prepare Cache Data').item.json.root_path }}",
            "root_folder_id": "={{ $('Prepare Cache Data').item.json.root_folder_id }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "target_path",
              "displayName": "target_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "root_folder_id",
              "displayName": "root_folder_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "root_path",
              "displayName": "root_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "current_folder_id",
              "displayName": "current_folder_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            },
            {
              "id": "current_path",
              "displayName": "current_path",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "canBeUsedToMatch": true,
              "type": "string"
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.2,
      "position": [
        6704,
        2448
      ],
      "id": "c5eb54a7-d424-40fd-b167-99c8f9fc9db3",
      "name": "Recurse (Call Self)"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        5152,
        1872
      ],
      "id": "de899d9c-9048-492a-a5dc-b1960c362c34",
      "name": "Manual Test Trigger"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "test-target",
              "name": "target_path",
              "value": "/Accounting/2025/11_November/Expense",
              "type": "string"
            },
            {
              "id": "test-root-id",
              "name": "root_folder_id",
              "value": "1tiI-FHH-yeiWjku8jU5dClDRFERIe4gS",
              "type": "string"
            },
            {
              "id": "test-root-path",
              "name": "root_path",
              "value": "/Accounting",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        5376,
        1872
      ],
      "id": "b5046fde-e201-4995-8201-a9f404f35066",
      "name": "Test Data"
    },
    {
      "parameters": {
        "content": "# Folder ID Lookup (Self-Recursive)\n\n**First call:** Cache OR query \u2192 Find deepest cached \u2192 Traverse\n**Recursion:** Skip cache \u2192 Traverse directly\n\n## Flow\n1. Check if recursion (current_folder_id exists?)\n2. First call: Generate paths \u2192 OR query \u2192 Find deepest\n3. Both: Calculate next segment \u2192 Drive query \u2192 Cache \u2192 Done/Recurse",
        "height": 584,
        "width": 376
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4944,
        2016
      ],
      "typeVersion": 1,
      "id": "3d56ba52-6414-4095-8aea-ab3578125255",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "Trigger": {
      "main": [
        [
          {
            "node": "Is Recursion?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Recursion?": {
      "main": [
        [
          {
            "node": "Calculate Next Segment",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate Ancestor Paths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Ancestor Paths": {
      "main": [
        [
          {
            "node": "Cache OR Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cache OR Query": {
      "main": [
        [
          {
            "node": "Find Deepest Cached",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Deepest Cached": {
      "main": [
        [
          {
            "node": "Status Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Status Check": {
      "main": [
        [
          {
            "node": "Return (Cache Hit)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Calculate Next Segment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Next Segment": {
      "main": [
        [
          {
            "node": "Google Drive: Find Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive: Find Folder": {
      "main": [
        [
          {
            "node": "Folder Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Folder Found?": {
      "main": [
        [
          {
            "node": "Prepare Cache Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Folder": {
      "main": [
        [
          {
            "node": "Prepare Cache Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Cache Data": {
      "main": [
        [
          {
            "node": "Cache New Path",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cache New Path": {
      "main": [
        [
          {
            "node": "Reached Target?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reached Target?": {
      "main": [
        [
          {
            "node": "Return (Found)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Recurse (Call Self)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Test Trigger": {
      "main": [
        [
          {
            "node": "Test Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test Data": {
      "main": [
        [
          {
            "node": "Is Recursion?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}