{"updatedAt":"2025-11-09T00:03:52.000Z","createdAt":"2025-11-06T18:09:52.807Z","id":"BAJTmhPwfq6zLFsC","name":"New web to notion","active":true,"isArchived":false,"nodes":[{"parameters":{"httpMethod":"POST","path":"fetch-files","responseMode":"responseNode","options":{}},"type":"n8n-nodes-base.webhook","typeVersion":2.1,"position":[0,0],"id":"2d4d8050-6af9-482a-a102-a82206648dec","name":"Fetch Files Webhook","webhookId":"25986bfb-35ec-48ec-ae3b-664b3b161905"},{"parameters":{"jsCode":"// Get input body (from webhook)\nconst body = items[0]?.json || {};\nconst now = new Date();\n\n// Extract or default values\nlet startISO = body?.timeRange?.startISO || null;\nlet endISO   = body?.timeRange?.endISO   || null;\n\nlet startMs = startISO ? Date.parse(startISO) : NaN;\nlet endMs   = endISO   ? Date.parse(endISO)   : NaN;\n\n// Handle invalid or missing timestamps\nif (isNaN(startMs) && isNaN(endMs)) {\n  // No range given → default to last 1 hour\n  endMs   = now.getTime();\n  startMs = endMs - 60 * 60 * 1000;\n} else if (isNaN(startMs)) {\n  // Missing start → one hour before end\n  startMs = endMs - 60 * 60 * 1000;\n} else if (isNaN(endMs)) {\n  // Missing end → one hour after start\n  endMs = startMs + 60 * 60 * 1000;\n}\n\n// Convert back to ISO strings (UTC)\nstartISO = new Date(startMs).toISOString();\nendISO   = new Date(endMs).toISOString();\n\n// Pass along entry type + clean range\nreturn [\n  {\n    json: {\n      entryType: body.entryType || \"thoughts\",\n      timeRange: { startISO, endISO },\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[208,144],"id":"395ef335-ec64-40a3-85b7-2fbd6aeadb78","name":"Normalize Input."},{"parameters":{"jsCode":"// We now trust the frontend's payload directly.\nconst body = items[0].json || {};\nconst timeRange = body.timeRange || {};\n\nconst startMs = Date.parse(timeRange.startISO);\nconst endMs   = Date.parse(timeRange.endISO);\n\nconst fs = require('fs');\nconst path = require('path');\n\n// Folder where your PublicFiles live\nconst baseDir = '/mnt/data/PublicFiles/data';\n\nfunction walk(dir) {\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n  let result = [];\n  for (const e of entries) {\n    const full = path.join(dir, e.name);\n    if (e.isDirectory()) {\n      result = result.concat(walk(full));\n    } else {\n      const stat = fs.statSync(full);\n      result.push({ path: full, mtimeMs: stat.mtimeMs });\n    }\n  }\n  return result;\n}\n\nconst allFiles = walk(baseDir);\n\n// For debugging: keep a copy of what we think the range is\nconst debugRange = {\n  startISO: timeRange.startISO || null,\n  endISO:   timeRange.endISO   || null,\n  startMs,\n  endMs,\n};\n\n// Filter by the picked time range (inclusive).\n// If either side is invalid, we DO NOT filter at all.\nconst filtered = allFiles.filter(f => {\n  if (isNaN(startMs) || isNaN(endMs)) return true;\n  return f.mtimeMs >= startMs && f.mtimeMs <= endMs;\n});\n\n// Map each file to what the frontend expects\nconst files = filtered.map(f => {\n  const rel = f.path.replace(baseDir, '');\n  const relPath = rel.startsWith('/') ? rel : '/' + rel;\n  const url = `https://min.velvetcld.gleeze.com/data${relPath}`;\n\n  return {\n    id: f.path,\n    name: path.basename(f.path),\n    createdAt: new Date(f.mtimeMs).toISOString(),\n    url,\n    thumbnailUrl: url,\n  };\n});\n\n// We return a single item with { files, debugRange } so you can inspect it\nreturn [\n  {\n    json: {\n      debugRange,\n      files,\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[432,128],"id":"3617134c-25f2-481f-bf0f-1d8263428e3c","name":"Scan data"},{"parameters":{"jsCode":"// Collect all file objects from previous node (one item per file)\nconst files = items.map(i => i.json);\n\n// Wrap them into a single JSON object for the webhook response\nreturn [\n  {\n    json: { files },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[688,160],"id":"bbb9a011-0046-46c9-a3ac-cc90fa963b97","name":"Build Response"},{"parameters":{"respondWith":"json","responseBody":"={{ { files: $json.files } }}\n","options":{}},"type":"n8n-nodes-base.respondToWebhook","typeVersion":1.4,"position":[832,0],"id":"c12a505b-fb6e-4257-bb0a-bb92f665fc16","name":"Respond to Webhook"},{"parameters":{"jsCode":"// --- 1. Read input from webhook ---\nconst body = items[0].json || {};\nconst timeRange = body.timeRange || {};\n\n// Helper to robustly parse the flatpickr values\nfunction parseDate(value) {\n  if (!value) return NaN;\n\n  // Try normal JS parsing first\n  let t = Date.parse(value);\n  if (!isNaN(t)) return t;\n\n  // Try \"YYYY-MM-DD HH:mm\" (flatpickr default we used)\n  const m = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})[ T](\\d{2}):(\\d{2})/);\n  if (m) {\n    const year   = Number(m[1]);\n    const month  = Number(m[2]) - 1; // JS months 0–11\n    const day    = Number(m[3]);\n    const hour   = Number(m[4]);\n    const minute = Number(m[5]);\n    // Interpret as local time; if you prefer UTC, use Date.UTC\n    return new Date(year, month, day, hour, minute).getTime();\n  }\n\n  return NaN;\n}\n\nconst startMs = parseDate(timeRange.startISO);\nconst endMs   = parseDate(timeRange.endISO);\n\n// We'll keep some debug info in the execution so you can inspect it in n8n\nconst debugRange = {\n  rawStart: timeRange.startISO || null,\n  rawEnd:   timeRange.endISO   || null,\n  startMs,\n  endMs,\n};\n\n// --- 2. Walk filesystem and collect files ---\nconst fs = require('fs');\nconst path = require('path');\n\nconst baseDir = '/mnt/data/PublicFiles/data';\n\nfunction walk(dir) {\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n  let result = [];\n  for (const e of entries) {\n    const full = path.join(dir, e.name);\n    if (e.isDirectory()) {\n      result = result.concat(walk(full));\n    } else {\n      const stat = fs.statSync(full);\n      result.push({ path: full, mtimeMs: stat.mtimeMs });\n    }\n  }\n  return result;\n}\n\nconst allFiles = walk(baseDir);\n\n// --- 3. Apply time filter ---\n// If both timestamps are valid, filter by them.\n// If either is invalid, we still filter using a wide default range.\nlet effectiveStart = startMs;\nlet effectiveEnd   = endMs;\n\n// If both invalid, fall back to \"last 30 days\"\nif (isNaN(effectiveStart) && isNaN(effectiveEnd)) {\n  const now = Date.now();\n  effectiveEnd   = now;\n  effectiveStart = now - 30 * 24 * 60 * 60 * 1000;\n} else if (isNaN(effectiveStart)) {\n  // Only end valid → 24h before end\n  effectiveStart = effectiveEnd - 24 * 60 * 60 * 1000;\n} else if (isNaN(effectiveEnd)) {\n  // Only start valid → 24h after start\n  effectiveEnd = effectiveStart + 24 * 60 * 60 * 1000;\n}\n\ndebugRange.effectiveStartMs = effectiveStart;\ndebugRange.effectiveEndMs   = effectiveEnd;\ndebugRange.effectiveStartISO = new Date(effectiveStart).toISOString();\ndebugRange.effectiveEndISO   = new Date(effectiveEnd).toISOString();\n\nconst filtered = allFiles.filter(f => {\n  return f.mtimeMs >= effectiveStart && f.mtimeMs <= effectiveEnd;\n});\n\n// --- 4. Map to frontend shape ---\nconst files = filtered.map(f => {\n  const rel = f.path.slice(baseDir.length);           // keep leading slash\n  const relPath = rel.startsWith('/') ? rel : '/' + rel;\n  const url = `https://min.velvetcld.gleeze.com/data${relPath}`;\n\n  return {\n    id: f.path,\n    name: path.basename(f.path),\n    createdAt: new Date(f.mtimeMs).toISOString(),\n    url,\n    thumbnailUrl: url,\n  };\n});\n\n// Return ONE item: { files, debugRange, debugInput: body }\nreturn [\n  {\n    json: {\n      files,\n      debugRange,\n      debugInput: body,\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[416,-16],"id":"88dcc148-be6d-4960-8bdc-682ad8332a72","name":"Filter Files"}],"connections":{"Fetch Files Webhook":{"main":[[{"node":"Filter Files","type":"main","index":0}]]},"Normalize Input.":{"main":[[{"node":"Scan data","type":"main","index":0}]]},"Scan data":{"main":[[{"node":"Build Response","type":"main","index":0}]]},"Build Response":{"main":[[]]},"Respond to Webhook":{"main":[[]]},"Filter Files":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"staticData":null,"meta":null,"pinData":{},"versionId":"869b9926-cb3d-45be-9d7d-bfa5261e0759","activeVersionId":"869b9926-cb3d-45be-9d7d-bfa5261e0759","triggerCount":1,"shared":[{"updatedAt":"2025-11-06T18:09:52.811Z","createdAt":"2025-11-06T18:09:52.811Z","role":"workflow:owner","workflowId":"BAJTmhPwfq6zLFsC","projectId":"lh2csO3ZG7MzpN8o"}],"activeVersion":{"updatedAt":"2025-11-27T21:37:34.259Z","createdAt":"2025-11-27T21:37:34.259Z","versionId":"869b9926-cb3d-45be-9d7d-bfa5261e0759","workflowId":"BAJTmhPwfq6zLFsC","nodes":[{"parameters":{"httpMethod":"POST","path":"fetch-files","responseMode":"responseNode","options":{}},"type":"n8n-nodes-base.webhook","typeVersion":2.1,"position":[0,0],"id":"2d4d8050-6af9-482a-a102-a82206648dec","name":"Fetch Files Webhook","webhookId":"25986bfb-35ec-48ec-ae3b-664b3b161905"},{"parameters":{"jsCode":"// Get input body (from webhook)\nconst body = items[0]?.json || {};\nconst now = new Date();\n\n// Extract or default values\nlet startISO = body?.timeRange?.startISO || null;\nlet endISO   = body?.timeRange?.endISO   || null;\n\nlet startMs = startISO ? Date.parse(startISO) : NaN;\nlet endMs   = endISO   ? Date.parse(endISO)   : NaN;\n\n// Handle invalid or missing timestamps\nif (isNaN(startMs) && isNaN(endMs)) {\n  // No range given → default to last 1 hour\n  endMs   = now.getTime();\n  startMs = endMs - 60 * 60 * 1000;\n} else if (isNaN(startMs)) {\n  // Missing start → one hour before end\n  startMs = endMs - 60 * 60 * 1000;\n} else if (isNaN(endMs)) {\n  // Missing end → one hour after start\n  endMs = startMs + 60 * 60 * 1000;\n}\n\n// Convert back to ISO strings (UTC)\nstartISO = new Date(startMs).toISOString();\nendISO   = new Date(endMs).toISOString();\n\n// Pass along entry type + clean range\nreturn [\n  {\n    json: {\n      entryType: body.entryType || \"thoughts\",\n      timeRange: { startISO, endISO },\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[208,144],"id":"395ef335-ec64-40a3-85b7-2fbd6aeadb78","name":"Normalize Input."},{"parameters":{"jsCode":"// We now trust the frontend's payload directly.\nconst body = items[0].json || {};\nconst timeRange = body.timeRange || {};\n\nconst startMs = Date.parse(timeRange.startISO);\nconst endMs   = Date.parse(timeRange.endISO);\n\nconst fs = require('fs');\nconst path = require('path');\n\n// Folder where your PublicFiles live\nconst baseDir = '/mnt/data/PublicFiles/data';\n\nfunction walk(dir) {\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n  let result = [];\n  for (const e of entries) {\n    const full = path.join(dir, e.name);\n    if (e.isDirectory()) {\n      result = result.concat(walk(full));\n    } else {\n      const stat = fs.statSync(full);\n      result.push({ path: full, mtimeMs: stat.mtimeMs });\n    }\n  }\n  return result;\n}\n\nconst allFiles = walk(baseDir);\n\n// For debugging: keep a copy of what we think the range is\nconst debugRange = {\n  startISO: timeRange.startISO || null,\n  endISO:   timeRange.endISO   || null,\n  startMs,\n  endMs,\n};\n\n// Filter by the picked time range (inclusive).\n// If either side is invalid, we DO NOT filter at all.\nconst filtered = allFiles.filter(f => {\n  if (isNaN(startMs) || isNaN(endMs)) return true;\n  return f.mtimeMs >= startMs && f.mtimeMs <= endMs;\n});\n\n// Map each file to what the frontend expects\nconst files = filtered.map(f => {\n  const rel = f.path.replace(baseDir, '');\n  const relPath = rel.startsWith('/') ? rel : '/' + rel;\n  const url = `https://min.velvetcld.gleeze.com/data${relPath}`;\n\n  return {\n    id: f.path,\n    name: path.basename(f.path),\n    createdAt: new Date(f.mtimeMs).toISOString(),\n    url,\n    thumbnailUrl: url,\n  };\n});\n\n// We return a single item with { files, debugRange } so you can inspect it\nreturn [\n  {\n    json: {\n      debugRange,\n      files,\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[432,128],"id":"3617134c-25f2-481f-bf0f-1d8263428e3c","name":"Scan data"},{"parameters":{"jsCode":"// Collect all file objects from previous node (one item per file)\nconst files = items.map(i => i.json);\n\n// Wrap them into a single JSON object for the webhook response\nreturn [\n  {\n    json: { files },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[688,160],"id":"bbb9a011-0046-46c9-a3ac-cc90fa963b97","name":"Build Response"},{"parameters":{"respondWith":"json","responseBody":"={{ { files: $json.files } }}\n","options":{}},"type":"n8n-nodes-base.respondToWebhook","typeVersion":1.4,"position":[832,0],"id":"c12a505b-fb6e-4257-bb0a-bb92f665fc16","name":"Respond to Webhook"},{"parameters":{"jsCode":"// --- 1. Read input from webhook ---\nconst body = items[0].json || {};\nconst timeRange = body.timeRange || {};\n\n// Helper to robustly parse the flatpickr values\nfunction parseDate(value) {\n  if (!value) return NaN;\n\n  // Try normal JS parsing first\n  let t = Date.parse(value);\n  if (!isNaN(t)) return t;\n\n  // Try \"YYYY-MM-DD HH:mm\" (flatpickr default we used)\n  const m = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})[ T](\\d{2}):(\\d{2})/);\n  if (m) {\n    const year   = Number(m[1]);\n    const month  = Number(m[2]) - 1; // JS months 0–11\n    const day    = Number(m[3]);\n    const hour   = Number(m[4]);\n    const minute = Number(m[5]);\n    // Interpret as local time; if you prefer UTC, use Date.UTC\n    return new Date(year, month, day, hour, minute).getTime();\n  }\n\n  return NaN;\n}\n\nconst startMs = parseDate(timeRange.startISO);\nconst endMs   = parseDate(timeRange.endISO);\n\n// We'll keep some debug info in the execution so you can inspect it in n8n\nconst debugRange = {\n  rawStart: timeRange.startISO || null,\n  rawEnd:   timeRange.endISO   || null,\n  startMs,\n  endMs,\n};\n\n// --- 2. Walk filesystem and collect files ---\nconst fs = require('fs');\nconst path = require('path');\n\nconst baseDir = '/mnt/data/PublicFiles/data';\n\nfunction walk(dir) {\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n  let result = [];\n  for (const e of entries) {\n    const full = path.join(dir, e.name);\n    if (e.isDirectory()) {\n      result = result.concat(walk(full));\n    } else {\n      const stat = fs.statSync(full);\n      result.push({ path: full, mtimeMs: stat.mtimeMs });\n    }\n  }\n  return result;\n}\n\nconst allFiles = walk(baseDir);\n\n// --- 3. Apply time filter ---\n// If both timestamps are valid, filter by them.\n// If either is invalid, we still filter using a wide default range.\nlet effectiveStart = startMs;\nlet effectiveEnd   = endMs;\n\n// If both invalid, fall back to \"last 30 days\"\nif (isNaN(effectiveStart) && isNaN(effectiveEnd)) {\n  const now = Date.now();\n  effectiveEnd   = now;\n  effectiveStart = now - 30 * 24 * 60 * 60 * 1000;\n} else if (isNaN(effectiveStart)) {\n  // Only end valid → 24h before end\n  effectiveStart = effectiveEnd - 24 * 60 * 60 * 1000;\n} else if (isNaN(effectiveEnd)) {\n  // Only start valid → 24h after start\n  effectiveEnd = effectiveStart + 24 * 60 * 60 * 1000;\n}\n\ndebugRange.effectiveStartMs = effectiveStart;\ndebugRange.effectiveEndMs   = effectiveEnd;\ndebugRange.effectiveStartISO = new Date(effectiveStart).toISOString();\ndebugRange.effectiveEndISO   = new Date(effectiveEnd).toISOString();\n\nconst filtered = allFiles.filter(f => {\n  return f.mtimeMs >= effectiveStart && f.mtimeMs <= effectiveEnd;\n});\n\n// --- 4. Map to frontend shape ---\nconst files = filtered.map(f => {\n  const rel = f.path.slice(baseDir.length);           // keep leading slash\n  const relPath = rel.startsWith('/') ? rel : '/' + rel;\n  const url = `https://min.velvetcld.gleeze.com/data${relPath}`;\n\n  return {\n    id: f.path,\n    name: path.basename(f.path),\n    createdAt: new Date(f.mtimeMs).toISOString(),\n    url,\n    thumbnailUrl: url,\n  };\n});\n\n// Return ONE item: { files, debugRange, debugInput: body }\nreturn [\n  {\n    json: {\n      files,\n      debugRange,\n      debugInput: body,\n    },\n  },\n];\n"},"type":"n8n-nodes-base.code","typeVersion":2,"position":[416,-16],"id":"88dcc148-be6d-4960-8bdc-682ad8332a72","name":"Filter Files"}],"connections":{"Fetch Files Webhook":{"main":[[{"node":"Filter Files","type":"main","index":0}]]},"Normalize Input.":{"main":[[{"node":"Scan data","type":"main","index":0}]]},"Scan data":{"main":[[{"node":"Build Response","type":"main","index":0}]]},"Build Response":{"main":[[]]},"Respond to Webhook":{"main":[[]]},"Filter Files":{"main":[[{"node":"Respond to Webhook","type":"main","index":0}]]}},"authors":"system migration","name":null,"description":null,"autosaved":false},"tags":[]}