{
  "nodes": [
    {
      "id": "233a882a-a1bb-4b03-8be1-eca89fc84e1e",
      "name": "Document Upload Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1632,
        96
      ],
      "parameters": {
        "options": {
          "appendAttribution": false
        },
        "formTitle": "Tax Document Upload",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "Select Document",
              "multipleFiles": false,
              "requiredField": true,
              "acceptFileTypes": ".pdf, .png, .jpg, .jpeg"
            }
          ]
        },
        "formDescription": "Upload tax-related documents (invoices, 1099, W-2, receipts) for automated processing"
      },
      "typeVersion": 2.3
    },
    {
      "id": "bb4394e9-a3f7-40a7-8481-5fbea0378c30",
      "name": "Webhook Upload",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1632,
        288
      ],
      "parameters": {
        "path": "document-upload",
        "options": {
          "rawBody": true
        },
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a66491fa-d58f-48b5-8821-3ac28abc46e7",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1344,
        208
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "maxFileSizeMB",
              "type": "number",
              "value": 50
            },
            {
              "id": "id-2",
              "name": "allowedMimeTypes",
              "type": "string",
              "value": "application/pdf,image/png,image/jpeg"
            },
            {
              "id": "id-3",
              "name": "storageBasePath",
              "type": "string",
              "value": "/tmp/tax-documents"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "81c37b00-369b-4699-9a8e-af855ed2c9d3",
      "name": "Check File Size",
      "type": "n8n-nodes-base.if",
      "position": [
        -1120,
        208
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $binary.data.fileSize }}",
              "rightValue": "={{ $('Workflow Configuration').item.json.maxFileSizeMB * 1024 * 1024 }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "92052172-2490-49bc-89cf-1c8105c04178",
      "name": "Check MIME Type",
      "type": "n8n-nodes-base.if",
      "position": [
        -864,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "array",
                "operation": "contains"
              },
              "leftValue": "={{ $('Workflow Configuration').item.json.allowedMimeTypes }}",
              "rightValue": "={{ $binary.data.mimeType }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "17d16b6e-1923-4281-9e24-9db84d894942",
      "name": "Generate Document Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        -640,
        -16
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const crypto = require('crypto');\n\n// Get binary data from the uploaded file\nconst binaryData = $binary.data;\nconst buffer = Buffer.from(binaryData.data, 'base64');\n\n// Calculate SHA-256 hash of file content\nconst hash = crypto.createHash('sha256').update(buffer).digest('hex');\n\n// Generate UUID for document_id\nconst documentId = crypto.randomUUID();\n\n// Extract file metadata\nconst fileName = binaryData.fileName || 'unknown';\nconst fileSize = buffer.length;\nconst mimeType = binaryData.mimeType || 'application/octet-stream';\n\n// Get current timestamp\nconst uploadedAt = new Date().toISOString();\n\n// Determine source (form or webhook)\nconst source = $node.name.includes('Form') ? 'form' : 'webhook';\n\n// Return metadata and preserve binary data\nreturn {\n  json: {\n    document_id: documentId,\n    file_name: fileName,\n    file_size: fileSize,\n    mime_type: mimeType,\n    file_hash: hash,\n    uploaded_at: uploadedAt,\n    source: source\n  },\n  binary: {\n    data: binaryData\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "129736a4-2406-499f-b776-e7423c12f528",
      "name": "Check for Duplicate",
      "type": "n8n-nodes-base.postgres",
      "position": [
        -480,
        -16
      ],
      "parameters": {
        "query": "SELECT document_id, status FROM documents WHERE file_hash = $1 LIMIT 1",
        "options": {
          "queryReplacement": "={{ $json.file_hash }}"
        },
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "e3d4134d-83fc-4c11-8066-d321e28e9e79",
      "name": "Is Duplicate?",
      "type": "n8n-nodes-base.if",
      "position": [
        -224,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $('Check for Duplicate').item.json.length }}",
              "rightValue": "0"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1e2aab77-6bad-4d17-b966-c1f86f85e15c",
      "name": "Insert Document Record",
      "type": "n8n-nodes-base.postgres",
      "position": [
        16,
        320
      ],
      "parameters": {
        "query": "INSERT INTO documents (document_id, file_hash, file_name, file_size, mime_type, source, status, created_at) VALUES ($1, $2, $3, $4, $5, $6, 'received', NOW()) RETURNING *",
        "options": {
          "queryReplacement": "={{ $('Generate Document Metadata').item.json.document_id }},={{ $('Generate Document Metadata').item.json.file_hash }},={{ $('Generate Document Metadata').item.json.file_name }},={{ $('Generate Document Metadata').item.json.file_size }},={{ $('Generate Document Metadata').item.json.mime_type }},={{ $('Generate Document Metadata').item.json.source }}"
        },
        "operation": "executeQuery"
      },
      "typeVersion": 2.6
    },
    {
      "id": "2a297f88-0611-4429-8539-7ab3c0eebaf0",
      "name": "Return Success Response",
      "type": "n8n-nodes-base.set",
      "position": [
        192,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "status",
              "type": "string",
              "value": "success"
            },
            {
              "id": "id-2",
              "name": "message",
              "type": "string",
              "value": "Document uploaded successfully"
            },
            {
              "id": "id-3",
              "name": "document_id",
              "type": "string",
              "value": "={{ $('Generate Document Metadata').item.json.document_id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1cad7e0a-a26f-4b72-aa60-54474755e0a5",
      "name": "Return Duplicate Response",
      "type": "n8n-nodes-base.set",
      "position": [
        96,
        -128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "status",
              "type": "string",
              "value": "duplicate"
            },
            {
              "id": "id-2",
              "name": "message",
              "type": "string",
              "value": "Document already exists (duplicate detected via file hash)"
            },
            {
              "id": "id-3",
              "name": "existing_document_id",
              "type": "string",
              "value": "={{ $('Check for Duplicate').item.json[0].document_id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d6807296-11b5-4172-9b57-7cb60f8c48e9",
      "name": "Return Validation Error",
      "type": "n8n-nodes-base.set",
      "position": [
        -656,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "status",
              "type": "string",
              "value": "error"
            },
            {
              "id": "id-2",
              "name": "message",
              "type": "string",
              "value": "File validation failed: invalid size or file type"
            },
            {
              "id": "id-3",
              "name": "allowed_types",
              "type": "string",
              "value": "PDF, PNG, JPEG"
            },
            {
              "id": "id-4",
              "name": "max_size_mb",
              "type": "string",
              "value": "={{ $('Workflow Configuration').item.json.maxFileSizeMB }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0276b453-0b64-4acf-8b78-e0ef46e9a70c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1696,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 544,
        "content": "## Upload Input\nAccepts document via form or webhook upload."
      },
      "typeVersion": 1
    },
    {
      "id": "c1796c06-6915-4d94-9217-4536a44c2369",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1376,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 368,
        "content": "## Configuration and Validation\nDefines file size limits, allowed types, and storage settings and Checks file size and MIME type before processing."
      },
      "typeVersion": 1
    },
    {
      "id": "be61a78f-6df3-43a4-bc7e-cdb06c983faa",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 320,
        "content": "## Error Handling\nReturns error if file validation fails."
      },
      "typeVersion": 1
    },
    {
      "id": "64a28d6c-b407-4384-acc8-ca034e0f3dc8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 352,
        "height": 352,
        "content": "## Metadata Generation and Duplicate check \nCreates document ID, hash, and extracts file details and Checks if file already exists using hash comparison."
      },
      "typeVersion": 1
    },
    {
      "id": "048bb137-e535-4c66-ae29-e6ebf2328235",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 320,
        "content": "## Duplicate Handling\nReturns response if duplicate document is detected."
      },
      "typeVersion": 1
    },
    {
      "id": "1cbae323-7c3e-4d0e-a77b-33ff6ea437fd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 320,
        "content": "## Duplicate Handling\nReturns response if duplicate document is detected."
      },
      "typeVersion": 1
    },
    {
      "id": "55a52db2-941c-426e-839d-d758dd527017",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -16,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 384,
        "content": "## Success Response and Record Storage\nStores document metadata in Postgres database and \nReturns success message with document ID."
      },
      "typeVersion": 1
    },
    {
      "id": "eec948bd-c212-4769-9b3d-3c51ea86ac29",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2288,
        16
      ],
      "parameters": {
        "width": 464,
        "height": 576,
        "content": "## How it works\nThis workflow handles secure document uploads from a form or webhook.\n\nUploaded files are first validated for size and type to ensure they meet defined limits. Once validated, the workflow generates metadata including a unique document ID, file hash, and file details.\n\nThe file hash is used to check for duplicates in the database. If a duplicate is found, the workflow returns an existing record instead of storing it again.\n\nIf the file is new, its metadata is stored in Postgres with a \u201creceived\u201d status. A success response is returned with the document ID for tracking.\n\n## Setup\n1. Configure form or webhook for uploads  \n2. Set max file size and allowed types  \n3. Connect Postgres database  \n4. Create documents table  \n5. Test with sample uploads  \n6. Activate the workflow"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Is Duplicate?": {
      "main": [
        [
          {
            "node": "Return Duplicate Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Insert Document Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Upload": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check File Size": {
      "main": [
        [
          {
            "node": "Check MIME Type",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check MIME Type": {
      "main": [
        [
          {
            "node": "Generate Document Metadata",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Duplicate": {
      "main": [
        [
          {
            "node": "Is Duplicate?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Document Upload Form": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert Document Record": {
      "main": [
        [
          {
            "node": "Return Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Check File Size",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Document Metadata": {
      "main": [
        [
          {
            "node": "Check for Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}