AutomationFlowsData & Sheets › Projects

Projects

Projects. Uses postgres. Webhook trigger; 58 nodes.

Webhook trigger★★★★★ complexity58 nodesPostgres
Data & Sheets Trigger: Webhook Nodes: 58 Complexity: ★★★★★ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "createdAt": "2025-10-11T18:45:07.947Z",
  "updatedAt": "2025-11-30T12:01:10.000Z",
  "id": "Zb1PTnzM4o5TnSVZ",
  "name": "Projects",
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "path": "api/components/projects/meta",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Projects Meta Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        2224
      ],
      "id": "836126c2-9d48-41ca-bdeb-223fff943d87"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT\n  COUNT(*) AS total_projects,\n  COUNT(CASE WHEN status = 'CREATED' THEN 1 END) AS active_projects,\n  COUNT(CASE WHEN status = 'COMPLETE' THEN 1 END) AS completed_projects\nFROM projects;",
        "options": {}
      },
      "name": "Execute Projects Meta Query",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -512,
        2224
      ],
      "id": "df8c6252-b70d-4209-8746-73286da9059b",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const meta = $input.first().json;\nreturn [{ json: {\n  totalProjects: parseInt(meta.total_projects || 0),\n  activeProjects: parseInt(meta.active_projects || 0),\n  completedProjects: parseInt(meta.completed_projects || 0)\n} }];"
      },
      "name": "Format Projects Meta",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -288,
        2224
      ],
      "id": "b8a1f2fa-f652-4a6b-a729-3bbe2855965c"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond Projects Meta",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        -64,
        2224
      ],
      "id": "89e2b490-5c80-44e4-ba4c-92e1d90c5168"
    },
    {
      "parameters": {
        "jsCode": "// Extract and default page/filter parameters\nconst q = $input.first().json.query || {};\nconst search = q.search || \"\";\nconst status = q.status || \"\";\nconst limit = Number.isInteger(parseInt(q.pageSize)) ? parseInt(q.pageSize) : 12;\nconst pageNo = Number.isInteger(parseInt(q.page)) ? parseInt(q.page) : 1;\nconst offset = (pageNo - 1) * limit;\nreturn [{ json: { search, status, limit, offset } }];"
      },
      "name": "Parse Project List Params",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        -16
      ],
      "id": "81b1ba1b-88a5-4158-831d-ff38e04b33ad"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond Project List",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        -64,
        -16
      ],
      "id": "83934ca4-261c-4b92-86bf-d0c088a416e2"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH filtered_projects AS (\n  SELECT \n    p.project_id, \n    p.project_name, \n    p.start_date, \n    p.end_date, \n    p.status, \n    p.description, \n    p.is_yt_project, \n    p.git_repository\n  FROM projects p\n  WHERE \n    CASE \n      WHEN '{{ $json.search }}' != '' THEN p.project_name ILIKE '%{{ $json.search }}%' \n      ELSE TRUE \n    END\n    AND CASE \n      WHEN '{{ $json.status }}' != '' THEN p.status = '{{ $json.status }}' \n      ELSE TRUE \n    END\n),\nproject_stats AS (\n  SELECT \n    fp.*,\n    -- Count of components used in this project\n    COALESCE(COUNT(DISTINCT pc.component_id), 0) as component_count,\n    \n    -- Total cost: sum of (quantity_used * latest unit_cost for each component)\n    COALESCE(\n      SUM(\n        pc.quantity_used * COALESCE(\n          (\n            SELECT op.unit_cost \n            FROM order_parts op \n            WHERE op.component_id = pc.component_id \n            ORDER BY op.order_part_id DESC \n            LIMIT 1\n          ), \n          0\n        )\n      ), \n      0\n    ) as total_cost\n  FROM filtered_projects fp\n  LEFT JOIN project_components pc ON fp.project_id = pc.project_id\n  GROUP BY \n    fp.project_id, \n    fp.project_name, \n    fp.start_date, \n    fp.end_date, \n    fp.status, \n    fp.description, \n    fp.is_yt_project, \n    fp.git_repository\n),\npaginated_projects AS (\n  SELECT * FROM project_stats\n  ORDER BY project_id DESC\n  {{ $json.limit ? 'LIMIT ' + $json.limit : 'LIMIT 12' }}\n  {{ $json.offset ? 'OFFSET ' + $json.offset : 'OFFSET 0' }}\n)\nSELECT \n  *,\n  (SELECT COUNT(*) FROM filtered_projects) AS total_records\nFROM paginated_projects;\n",
        "options": {}
      },
      "name": "PostgreSQL - Get Projects",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        -16
      ],
      "id": "fd119484-8902-4faf-88d1-4f4207f76ec0",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "path": "api/components/projects",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Get Projects",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        -16
      ],
      "id": "1b9f29df-db65-4af7-a841-4bca9d9d7053"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Projects Create Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        208
      ],
      "id": "0909dfb6-f2ba-4e29-b80c-94609791a796"
    },
    {
      "parameters": {
        "jsCode": "// Validate and sanitize input data\nconst body = $input.first().json.body || {};\n\n// Required fields validation\nif (!body.project_name || body.project_name.trim() === '') {\n  throw new Error('Project name is required');\n}\n\n// Escape single quotes for SQL safety\nfunction escapeSql(str) {\n  if (!str) return null;\n  return String(str).replace(/'/g, \"''\");\n}\n\nfunction formatDate(date) {\n  if (!date) return null;\n  const d = new Date(date);\n  return d.toISOString().split('T')[0]; // YYYY-MM-DD\n}\n\n// Sanitize and prepare data\nconst projectData = {\n  project_name: escapeSql(body.project_name.trim()),\n  start_date: formatDate(body.start_date),\n  end_date: formatDate(body.end_date),\n  status: 'Created',\n  description: escapeSql(body.project_name.trim()),\n  is_yt_project: body.is_yt_project === true || body.is_yt_project === 'true',\n  git_repository: body.git_repository ? escapeSql(body.git_repository.trim()) : null\n};\n\nreturn [{ json: projectData }];"
      },
      "name": "Validate & Prepare Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        208
      ],
      "id": "7ee0f047-bca4-4730-b779-f12e5d565d16"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO projects (\n  project_name, \n  start_date, \n  end_date, \n  status, \n  description, \n  is_yt_project, \n  git_repository\n)\nVALUES (\n  '{{ $json.project_name }}',\n  {{ $json.start_date ? \"'\" + $json.start_date + \"'\" : \"NULL\" }},\n  {{ $json.end_date ? \"'\" + $json.end_date + \"'\" : \"NULL\" }},\n  '{{ $json.status }}',\n  '{{ $json.description }}',\n  {{ $json.is_yt_project }},\n  {{ $json.git_repository ? \"'\" + $json.git_repository + \"'\" : \"NULL\" }}\n)\nRETURNING *;",
        "options": {}
      },
      "name": "Execute Project Insert",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        208
      ],
      "id": "2dfbe606-63a9-4625-af82-47ce8d268cd3",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\nreturn [{ \n  json: { \n    success: true, \n    message: 'Project created successfully', \n    project: data \n  } \n}];"
      },
      "name": "Format Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        208
      ],
      "id": "4569fda9-6b15-4f59-9afd-2bb6849c885b"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        208
      ],
      "id": "76536e09-6f18-47fe-873c-a7e275eb79e5"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects/delete",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Projects Delete Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        432
      ],
      "id": "4b3533e3-31f7-4972-b001-b7e920bce719"
    },
    {
      "parameters": {
        "jsCode": "// Validate project_id from request body\nconst body = $input.first().json.body || {};\nconst projectId = parseInt(body.project_id);\n\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project_id is required');\n}\n\nreturn [{ json: { project_id: projectId } }];"
      },
      "name": "Validate Project ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        432
      ],
      "id": "055cc38b-a0d6-44f1-a6d4-3318e89dcba5"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "DELETE FROM projects WHERE project_id = {{ $json.project_id }} RETURNING *;",
        "options": {}
      },
      "name": "Execute Project Delete",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        432
      ],
      "id": "7fc5f31e-e79e-447b-b798-5b474319c55d",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\n\n// Check if project was deleted (RETURNING clause returns the deleted row)\nif (result && result.project_id) {\n  return [{ \n    json: { \n      success: true, \n      message: 'Project deleted successfully',\n      deletedProject: result\n    } \n  }];\n}\n\n// No row returned means project wasn't found\nreturn [{ \n  json: { \n    success: false, \n    message: 'Project not found or already deleted' \n  } \n}];"
      },
      "name": "Format Delete Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        432
      ],
      "id": "1864bdc1-cf46-4312-bead-da2781c87956"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook1",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        432
      ],
      "id": "2aaa1105-02df-41e7-bdba-253122347303"
    },
    {
      "parameters": {
        "path": "api/components/projects/single",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Get Single Project Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        656
      ],
      "id": "16962f60-d51b-4821-9498-cb56d431d9f5"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH project_stats AS (\n  SELECT \n    p.*,\n    COALESCE(COUNT(DISTINCT pc.component_id), 0) as component_count,\n    COALESCE(\n      SUM(\n        pc.quantity_used * COALESCE(\n          (\n            SELECT op.unit_cost \n            FROM order_parts op \n            WHERE op.component_id = pc.component_id \n            ORDER BY op.order_part_id DESC \n            LIMIT 1\n          ), \n          0\n        )\n      ), \n      0\n    ) as total_cost\n  FROM projects p\n  LEFT JOIN project_components pc ON p.project_id = pc.project_id\n  WHERE p.project_id = {{ $json.project_id }}\n  GROUP BY \n    p.project_id,\n    p.project_name,\n    p.start_date,\n    p.end_date,\n    p.status,\n    p.description,\n    p.is_yt_project,\n    p.git_repository\n)\nSELECT * FROM project_stats;",
        "options": {}
      },
      "name": "Get Project Details",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        656
      ],
      "id": "3093b280-6320-473d-add4-98e3f4403b79",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get project_id from query parameter\nconst query = $input.first().json.query || {};\nconst projectId = parseInt(query.id);\n\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project id is required');\n}\n\nreturn [{ json: { project_id: projectId } }];"
      },
      "name": "Validate Project ID1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        656
      ],
      "id": "1197e65b-84d3-4dbc-a770-71e1a161675a"
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nif (result && result.project_id) {\n  return [{ \n    json: { \n      success: true,\n      project: result\n    } \n  }];\n}\n\nreturn [{ \n  json: { \n    success: false, \n    message: 'Project not found' \n  } \n}];"
      },
      "name": "Format Response1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        656
      ],
      "id": "3a77f2ac-ceb4-4833-93c1-7e11a7cf330b"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook2",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        656
      ],
      "id": "bdd49743-dc20-466b-b3ee-8ff6982aa21b"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects/update",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Projects Update Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        1776
      ],
      "id": "45949f84-23bd-4166-a29b-3dc5a862eae1"
    },
    {
      "parameters": {
        "jsCode": "// Validate and sanitize input data\nconst body = $input.first().json.body || {};\n\n// Required: project_id\nconst projectId = parseInt(body.project_id);\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project_id is required');\n}\n\n// Escape single quotes for SQL safety\nfunction escapeSql(str) {\n  if (!str) return null;\n  return String(str).replace(/'/g, \"''\");\n}\n\nfunction formatDate(date) {\n  if (!date) return null;\n  const d = new Date(date);\n  return d.toISOString().split('T')[0];\n}\n\n// Build update fields dynamically\nconst updates = [];\nconst data = { project_id: projectId };\n\nif (body.project_name !== undefined) {\n  data.project_name = escapeSql(body.project_name.trim());\n  updates.push(`project_name = '${data.project_name}'`);\n}\n\nif (body.start_date !== undefined) {\n  data.start_date = formatDate(body.start_date);\n  updates.push(`start_date = ${data.start_date ? \"'\" + data.start_date + \"'\" : \"NULL\"}`);\n}\n\nif (body.end_date !== undefined) {\n  data.end_date = formatDate(body.end_date);\n  updates.push(`end_date = ${data.end_date ? \"'\" + data.end_date + \"'\" : \"NULL\"}`);\n}\n\nif (body.status !== undefined) {\n  data.status = escapeSql(body.status);\n  updates.push(`status = '${data.status}'`);\n}\n\nif (body.description !== undefined) {\n  data.description = body.description ? escapeSql(body.description) : null;\n  updates.push(`description = ${data.description ? \"'\" + data.description + \"'\" : \"NULL\"}`);\n}\n\nif (body.is_yt_project !== undefined) {\n  data.is_yt_project = body.is_yt_project === true || body.is_yt_project === 'true';\n  updates.push(`is_yt_project = ${data.is_yt_project}`);\n}\n\nif (body.git_repository !== undefined) {\n  data.git_repository = body.git_repository ? escapeSql(body.git_repository.trim()) : null;\n  updates.push(`git_repository = ${data.git_repository ? \"'\" + data.git_repository + \"'\" : \"NULL\"}`);\n}\n\nif (updates.length === 0) {\n  throw new Error('No fields to update');\n}\n\ndata.updateClause = updates.join(', ');\nreturn [{ json: data }];"
      },
      "name": "Validate & Prepare Update",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        1776
      ],
      "id": "29427a77-fd77-4eab-a91e-cb071264f822"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE projects\nSET {{ $json.updateClause }}\nWHERE project_id = {{ $json.project_id }}\nRETURNING *;",
        "options": {}
      },
      "name": "Execute Project Update",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        1776
      ],
      "id": "13a0e80c-5737-4819-b9f1-f7dac74cdc5a",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\nif (data && data.project_id) {\n  return [{ \n    json: { \n      success: true, \n      message: 'Project updated successfully', \n      project: data \n    } \n  }];\n}\n\nreturn [{ \n  json: { \n    success: false, \n    message: 'Project not found' \n  } \n}];"
      },
      "name": "Format Update Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        1776
      ],
      "id": "f510ad26-8686-4692-85b3-b0e8880192d7"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook3",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        1776
      ],
      "id": "3708ba27-ecad-432c-b9cb-594394005cfa"
    },
    {
      "parameters": {
        "path": "api/components/projects/video",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Get Video Details Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        880
      ],
      "id": "5e718169-18b9-40a5-8837-031d7edb8bdc"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT \n  v.video_id, v.title, v.description AS description, v.script, v.status AS video_status, v.publish_date, v.youtube_video_id\nFROM video v\nWHERE v.projects_id = {{ $json.project_id }}\nORDER BY v.publish_date DESC\nLIMIT 1;",
        "options": {}
      },
      "name": "Fetch Video Details",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        880
      ],
      "id": "559decc9-969f-4d96-9ea7-e76afab52987",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const query = $input.first().json.query || {};\nconst projectId = parseInt(query.project_id);\n\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project_id is required');\n}\n\nreturn [{ json: { project_id: projectId } }];"
      },
      "name": "Validate Project ID2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        880
      ],
      "id": "a1049bc0-e90f-4e4c-9a7f-b59a7149bf29"
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nif (!result || result.length === 0) {\n  return [{ json: { success: false, message: 'Video not found for project.' } }];\n}\n\n// Handle both single object and array\nconst items = Array.isArray(result) ? result : [result];\nconst firstItem = items[0];\n\n// Format publish_date for HTML date input (YYYY-MM-DD)\nconst formattedVideo = {\n  ...firstItem,\n  publish_date: firstItem.publish_date ? \n    new Date(firstItem.publish_date).toISOString().split('T')[0] : \n    null\n};\n\nreturn [{ json: { \n  success: true, \n  video: formattedVideo \n} }];\n"
      },
      "name": "Format Response2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        880
      ],
      "id": "8b3fa579-f3e7-4f9c-96cd-812ffe24cc51"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook4",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        880
      ],
      "id": "3853c2ac-e082-4447-8071-432710d53ad9"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects/video/update",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Update Video Details Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        1104
      ],
      "id": "21cacdc3-1a9e-4564-977c-3c8d9e48a088"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE video SET {{ $json.updateClause }} WHERE projects_id = {{ $json.project_id }} RETURNING *;\n",
        "options": {}
      },
      "name": "Execute Video Update",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        1104
      ],
      "id": "f677a94f-e787-4e31-ae94-2b11c86a75d2",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Validation and sanitization\nconst body = $input.first().json.body || {};\n\nconst project_id = parseInt(body.project_id);\nif (!project_id || isNaN(project_id)) {\n  throw new Error('Valid project_id is required');\n}\n\nfunction escapeSql(str) {\n  if (!str) return null;\n  return String(str).replace(/'/g, \"''\");\n}\n\nfunction formatDate(date) {\n  if (!date) return null;\n  const d = new Date(date);\n  return d.toISOString().split('T')[0];\n}\n\n// Dynamic fields (accept single or multiple fields)\nconst updates = [];\n\nif (body.title !== undefined) updates.push(`title = '${escapeSql(body.title)}'`);\nif (body.description !== undefined) updates.push(`description = ${body.description ? `'${escapeSql(body.description)}'` : 'NULL'}`);\nif (body.script !== undefined) updates.push(`script = ${body.script ? `'${escapeSql(body.script)}'` : 'NULL'}`);\nif (body.status !== undefined) updates.push(`status = '${escapeSql(body.status)}'`);\nif (body.publish_date !== undefined) updates.push(`publish_date = ${body.publish_date ? `'${formatDate(body.publish_date)}'` : 'NULL'}`);\nif (body.youtube_video_id !== undefined) updates.push(`youtube_video_id = ${body.youtube_video_id ? `'${escapeSql(body.youtube_video_id)}'` : 'NULL'}`);\n\nif (updates.length === 0) throw new Error('No fields to update');\n\nreturn [{ json: { project_id, updateClause: updates.join(', ') } }];\n"
      },
      "name": "Validate & Prepare Update1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        1104
      ],
      "id": "6b5a2442-c9c5-4489-b11c-f3d1947d3d85"
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nif (result && Array.isArray(result) && result.length) {\n  return [{ json: { success: true, message: 'Video updated', video: result[0] } }];\n}\nreturn [{ json: { success: false, message: 'Video not updated' } }];\n"
      },
      "name": "Format Update Response1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        1104
      ],
      "id": "4f819fba-c8fa-4c23-b20c-26d062e75d9b"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook5",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        1104
      ],
      "id": "feb3d631-9e2c-4b4f-97ef-6e9a4350fa00"
    },
    {
      "parameters": {
        "path": "api/components/projects/components",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Get Components Used Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        1552
      ],
      "id": "ec1e4a36-99cd-4b76-83d3-7a8f5ff5b077"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH purchased_cte AS (\n    SELECT component_id, SUM(quantity_ordered) AS purchased\n    FROM order_parts\n    GROUP BY component_id\n),\nused_cte AS (\n    SELECT component_id, SUM(quantity_used) AS used\n    FROM (\n        SELECT component_id, quantity_used FROM project_components\n        UNION ALL\n        SELECT component_id, quantity_used FROM general_usage\n    ) AS combined_usage\n    GROUP BY component_id\n),\navg_price_cte AS (\n    SELECT component_id, AVG(unit_cost) AS avg_unit_price\n    FROM order_parts\n    GROUP BY component_id\n),\ntotal_items_cte AS (\n    SELECT COUNT(1) AS total_items\n    FROM components\n    WHERE name ILIKE '%{{ $json.params.name }}%'\n)\nSELECT \n     pc.quantity_used,\n    c.name,\n    c.component_id AS ID,\n    c.description,\n    c.date_added,\n    c.image_content,\n    c.image_type,\n    b.box_code,\n    c.category_id,\n    b.box_label,\n    ct.category_name,\n    b.box_id,\n    COALESCE(purchased_cte.purchased, 0) AS purchased,\n    COALESCE(used_cte.used, 0) AS used,\n    COALESCE(purchased_cte.purchased, 0) - COALESCE(used_cte.used, 0) AS stock,\n    total_items_cte.total_items AS totalItems,\n    COALESCE(avg_price_cte.avg_unit_price, 0) AS avg_unit_price,\n    CASE WHEN COALESCE(purchased_cte.purchased, 0) - COALESCE(used_cte.used, 0) > 0 THEN 'In Stock' ELSE 'No Stock' END AS status\nFROM project_components pc\nJOIN  components c ON pc.component_id = c.component_id\nLEFT JOIN purchased_cte ON purchased_cte.component_id = c.component_id\nLEFT JOIN used_cte ON used_cte.component_id = c.component_id\nLEFT JOIN avg_price_cte ON avg_price_cte.component_id = c.component_id\nLEFT JOIN boxes b ON b.box_id = c.box_id\nLEFT JOIN categories ct ON ct.category_id = c.category_id\nCROSS JOIN total_items_cte\nWHERE pc.project_id = {{ $json.project_id }};\n",
        "options": {}
      },
      "name": "Fetch Project Components",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        1552
      ],
      "id": "5d6c6c22-c123-41de-8340-5f9229065c88",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond with Components",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        1552
      ],
      "id": "8ba89263-ddd8-4634-8951-81bba55757d0"
    },
    {
      "parameters": {
        "jsCode": "const query = $input.first().json.query || {};\nconst projectId = parseInt(query.project_id);\n\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project_id is required');\n}\n\nreturn [{ json: { project_id: projectId } }];"
      },
      "name": "Validate Project ID3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        1552
      ],
      "id": "2fe53e6a-614e-4c53-94d8-02728a06e392"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects/components/add",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Add Component Usage Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        2000
      ],
      "id": "d849009e-d1bc-4528-b3aa-530fb3dfa942"
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.body || {};\nconst projectId = parseInt(body.project_id);\nconst componentId = parseInt(body.component_id);\nconst quantity = parseInt(body.quantity_used);\n\nif (!projectId || isNaN(projectId)) {\n  throw new Error('Valid project_id is required');\n}\nif (!componentId || isNaN(componentId)) {\n  throw new Error('Valid component_id is required');\n}\nif (!quantity || isNaN(quantity) || quantity <= 0) {\n  throw new Error('Valid quantity_used is required and must be > 0');\n}\n\nreturn [{ json: { project_id: projectId, component_id: componentId, quantity_used: quantity } }];"
      },
      "name": "Validate Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        2000
      ],
      "id": "91252fcc-c932-4863-8510-531cfd73f81e"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO project_components (project_id, component_id, quantity_used)\nVALUES ({{ $json.project_id }}, {{ $json.component_id }}, {{ $json.quantity_used }})\nRETURNING *;",
        "options": {}
      },
      "name": "Insert Component Usage",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        2000
      ],
      "id": "c5827f15-97f7-4ac4-b36b-1b30ee826faa",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nif (result && result.project_id) {\n  return [{ json: { success: true, message: 'Component added', data: result } }];\n}\nreturn [{ json: { success: false, message: 'Failed to add component' } }];"
      },
      "name": "Format Response3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        2000
      ],
      "id": "6e981abf-c6e5-4776-a177-722458970a17"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook6",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        2000
      ],
      "id": "c252e45a-8a39-4d82-8c42-7d834cdf3811"
    },
    {
      "parameters": {
        "jsCode": "const keysToRemove = ['image_content', 'image_type'];\nconst items = $input.all();\n\nif (!items.length) {\n  return [];\n}\n\nreturn items.map(item => {\n  const json = { ...item.json };\n  keysToRemove.forEach(k => delete json[k]);\n\n  json.filename = `http://192.168.0.7:5678/webhook/image?filename=component_${item.json.id}.${item.json.image_type || 'jpeg'}`;\n\n  return { json };\n});\n"
      },
      "name": "Format Update Response2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        1552
      ],
      "id": "d9d91ec7-260c-4baa-b2dd-aae393b9c4c6"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "api/components/projects/video/updatestatus",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Update Video Status Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        2448
      ],
      "id": "173fec3e-2b6d-44bd-95ae-97cfa02e8210"
    },
    {
      "parameters": {
        "jsCode": "// Validation and sanitization\nconst body = $input.first().json.body || {};\n\nconst video_id = parseInt(body.video_id);\nif (!video_id || isNaN(video_id)) {\n  throw new Error('Valid video_id is required');\n}\n\nif (!body.video_status) {\n  throw new Error('status is required');\n}\n\nfunction escapeSql(str) {\n  if (!str) return null;\n  return String(str).replace(/'/g, \"''\");\n}\n\nfunction formatDate(date) {\n  if (!date) return null;\n  const d = new Date(date);\n  return d.toISOString();\n}\n\nconst status = escapeSql(body.video_status);\nconst date_changed = body.date_changed ? formatDate(body.date_changed) : null;\n\nreturn [{ \n  json: { \n    video_id, \n    status, \n    date_changed \n  } \n}];"
      },
      "name": "Validate & Prepare Status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        2448
      ],
      "id": "0a0ce66b-9994-41d1-8b64-b04b662d18d5"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH status_insert AS (\n  INSERT INTO videostatus (video_id, status, date_changed)\n  VALUES (\n    {{ $json.video_id }},\n    '{{ $json.status }}',\n    {{ $json.date_changed ? `'${$json.date_changed}'` : 'NOW()' }}\n  )\n  RETURNING *\n)\nUPDATE video \nSET status = '{{ $json.status }}' \nFROM status_insert\nWHERE video.video_id = {{ $json.video_id }}\nRETURNING video.*, status_insert.*;\n",
        "options": {}
      },
      "name": "Insert Video Status",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        2448
      ],
      "id": "c3f538f3-763b-47ac-bce8-160a0db63ee7",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nif (result && Array.isArray(result) && result.length) {\n  return [{ json: { \n    success: true, \n    message: 'Video status recorded', \n    status_record: result[0] \n  } }];\n}\nreturn [{ json: { \n  success: false, \n  message: 'Status not recorded' \n} }];"
      },
      "name": "Format Status Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        2448
      ],
      "id": "52140aaf-e962-4d7f-b119-6c8d23752f35"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook7",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        2448
      ],
      "id": "00ae1790-aa4f-4dbc-adfa-ca4686fc415e"
    },
    {
      "parameters": {
        "path": "api/components/projects/video/status-timeline",
        "responseMode": "responseNode",
        "options": {}
      },
      "name": "Video Status Timeline Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -736,
        1328
      ],
      "id": "8fae52a6-1e87-4993-aeb8-218852d68fe6"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT v.video_id, vs.status_id, vs.status, vs.date_changed\nFROM video v\nJOIN videostatus vs ON v.video_id = vs.video_id\nWHERE v.projects_id = {{ $json.project_id }}\nORDER BY vs.date_changed DESC\nLIMIT 10;",
        "options": {}
      },
      "name": "Get Status Timeline",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -288,
        1328
      ],
      "id": "632a36cd-64cd-4bff-9219-69ef0ab77982",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const results = $input.all().map(item => item.json);  // Get ALL rows\n\nif (!results || results.length === 0) {\n  return [{ json: { success: true, timeline: [] } }];\n}\n\nconsole.log('Results length:', results.length);  // Debug log\n\nconst formatted = results\n  .map(item => ({\n    ...item,\n    date_formatted: item.date_changed ? new Date(item.date_changed).toISOString().split('T')[0] : null\n  }))\n  .reverse();\n\nconsole.log('Final timeline length:', formatted.length);  // Debug log\nconsole.log('Final timeline:', formatted);\n\nreturn [{ json: { success: true, timeline: formatted } }];\n"
      },
      "name": "Format Timeline Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -64,
        1328
      ],
      "id": "79f31efa-51e6-4f8f-a6e7-2b88ccf09b52"
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json.query || {};\nconst project_id = parseInt(body.project_id);\nif (!project_id || isNaN(project_id)) {\n  throw new Error('Valid project_id is required');\n}\nreturn [{ json: { project_id } }];"
      },
      "name": "Validate Project ID4",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        1328
      ],
      "id": "2cd15328-8a98-4466-8e48-9211d2834230"
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "name": "Respond to Webhook8",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        160,
        1328
      ],
      "id": "951f0366-005b-481f-93f3-581b56e003b4"
    }
  ],
  "connections": {
    "Projects Meta Webhook": {
      "main": [
        [
          {
            "node": "Execute Projects Meta Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Projects Meta Query": {
      "main": [
        [
          {
            "node": "Format Projects Meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Projects Meta": {
      "main": [
        [
          {
            "node": "Respond Projects Meta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Project List Params": {
      "main": [
        [
          {
            "node": "PostgreSQL - Get Projects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostgreSQL - Get Projects": {
      "main": [
        [
          {
            "node": "Respond Project List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Projects": {
      "main": [
        [
          {
            "node": "Parse Project List Params",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Projects Create Webhook": {
      "main": [
        [
          {
            "node": "Validate & Prepare Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Data": {
      "main": [
        [
          {
            "node": "Execute Project Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Project Insert": {
      "main": [
        [
          {
            "node": "Format Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Projects Delete Webhook": {
      "main": [
        [
          {
            "node": "Validate Project ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Project ID": {
      "main": [
        [
          {
            "node": "Execute Project Delete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Project Delete": {
      "main": [
        [
          {
            "node": "Format Delete Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Delete Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Single Project Webhook": {
      "main": [
        [
          {
            "node": "Validate Project ID1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Project Details": {
      "main": [
        [
          {
            "node": "Format Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Project ID1": {
      "main": [
        [
          {
            "node": "Get Project Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response1": {
      "main": [
        [
          {
            "node": "Respond to Webhook2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Projects Update Webhook": {
      "main": [
        [
          {
            "node": "Validate & Prepare Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Update": {
      "main": [
        [
          {
            "node": "Execute Project Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Project Update": {
      "main": [
        [
          {
            "node": "Format Update Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Update Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Video Details Webhook": {
      "main": [
        [
          {
            "node": "Validate Project ID2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Project ID2": {
      "main": [
        [
          {
            "node": "Fetch Video Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Video Details": {
      "main": [
        [
          {
            "node": "Format Response2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response2": {
      "main": [
        [
          {
            "node": "Respond to Webhook4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Video Details Webhook": {
      "main": [
        [
          {
            "node": "Validate & Prepare Update1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Update1": {
      "main": [
        [
          {
            "node": "Execute Video Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Video Update": {
      "main": [
        [
          {
            "node": "Format Update Response1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Update Response1": {
      "main": [
        [
          {
            "node": "Respond to Webhook5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Components Used Webhook": {
      "main": [
        [
          {
            "node": "Validate Project ID3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Project ID3": {
      "main": [
        [
          {
            "node": "Fetch Project Components",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Project Components": {
      "main": [
        [
          {
            "node": "Format Update Response2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Component Usage Webhook": {
      "main": [
        [
          {
            "node": "Validate Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Request": {
      "main": [
        [
          {
            "node": "Insert Component Usage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Component Usage": {
      "main": [
        [
          {
            "node": "Format Response3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Response3": {
      "main": [
        [
          {
            "node": "Respond to Webhook6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Update Response2": {
      "main": [
        [
          {
            "node": "Respond with Components",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Video Status Webhook": {
      "main": [
        [
          {
            "node": "Validate & Prepare Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Prepare Status": {
      "main": [
        [
          {
            "node": "Insert Video Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Video Status": {
      "main": [
        [
          {
            "node": "Format Status Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Status Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Video Status Timeline Webhook": {
      "main": [
        [
          {
            "node": "Validate Project ID4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Project ID4": {
      "main": [
        [
          {
            "node": "Get Status Timeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Status Timeline": {
      "main": [
        [
          {
            "node": "Format Timeline Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Timeline Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "meta": null,
  "versionId": "97cbb10a-409d-48ef-9219-3ee57bf0ca0e",
  "triggerCount": 12,
  "tags": [],
  "shared": [
    {
      "createdAt": "2025-10-11T18:45:07.961Z",
      "updatedAt": "2025-10-11T18:45:07.961Z",
      "role": "workflow:owner",
      "workflowId": "Zb1PTnzM4o5TnSVZ",
      "projectId": "0tBJbgcFWwxEMKPn",
      "project": {
        "createdAt": "2025-10-05T16:55:31.619Z",
        "updatedAt": "2025-10-05T16:55:58.616Z",
        "id": "0tBJbgcFWwxEMKPn",
        "name": "Bikash Panda <oksbwn@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null
      }
    }
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Projects. Uses postgres. Webhook trigger; 58 nodes.

Source: https://github.com/oksbwn/Inventory-Management-System/blob/7f26462a83a574049df178775808226e970cd4f8/n8n/Zb1PTnzM4o5TnSVZ.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

CMM. Uses httpRequest, postgres, redis. Webhook trigger; 90 nodes.

HTTP Request, Postgres, Redis
Data & Sheets

Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.

HTTP Request, Postgres, @Apify/N8N Nodes Apify
Data & Sheets

Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.

HTTP Request, Postgres, Error Trigger
Data & Sheets

How it works

Postgres, Email Send
Data & Sheets

LogSentinel Workflow. Uses postgres, emailSend, httpRequest. Webhook trigger; 44 nodes.

Postgres, Email Send, HTTP Request