{
  "name": "ezGPT - Download JSON File",
  "active": false,
  "nodes": [
    {
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        200,
        300
      ],
      "parameters": {}
    },
    {
      "id": "download",
      "name": "Download Catalog",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        440,
        300
      ],
      "parameters": {
        "method": "GET",
        "url": "https://ezgpt.ru/storage/article-files/catalog-demo.json",
        "authentication": "none",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": false,
              "responseFormat": "file",
              "outputPropertyName": "document"
            }
          },
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000,
          "allowUnauthorizedCerts": false,
          "lowercaseHeaders": true
        }
      }
    },
    {
      "id": "check-download",
      "name": "Check Download",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1) throw new Error('one_download_required');\nconst item = items[0];\nif (item.json.statusCode !== 200) throw new Error('http_200_required');\nconst type = String(item.json.headers?.['content-type'] ?? '').split(';')[0].trim().toLowerCase();\nif (type !== 'application/json') throw new Error('json_content_type_required');\nif (Object.keys(item.binary ?? {}).join('|') !== 'document') throw new Error('document_binary_required');\nconst bytes = await this.helpers.getBinaryDataBuffer(0, 'document');\nif (bytes.length < 1 || bytes.length > 16384) throw new Error('file_size_invalid');\nif (bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) throw new Error('bom_not_expected');\nconst text = bytes.toString('utf8');\nif (!Buffer.from(text, 'utf8').equals(bytes)) throw new Error('utf8_invalid');\nif (!text.trimStart().startsWith('[')) throw new Error('json_array_expected');\nreturn [{\n  json: { download: { status: 200, bytes: bytes.length } },\n  binary: item.binary\n}];"
      }
    },
    {
      "id": "read-json",
      "name": "Read JSON",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        920,
        300
      ],
      "parameters": {
        "operation": "fromJson",
        "binaryPropertyName": "document",
        "destinationKey": "rows",
        "options": {
          "encoding": "utf8",
          "stripBOM": true,
          "keepSource": "both"
        }
      }
    },
    {
      "id": "catalog-preview",
      "name": "Catalog Preview",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1160,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const inputs = $input.all();\nif (inputs.length !== 1) throw new Error('one_result_required');\nconst rows = inputs[0].json.rows;\nif (!Array.isArray(rows) || rows.length < 1 || rows.length > 20) throw new Error('rows_invalid_or_empty');\nconst items = rows.map(json => ({ json }));\nconst ids = new Set();\nfor (const { json: row } of items) {\n  if (Object.keys(row).sort().join('|') !== 'active|id|note|quantity|tags|title') {\n    throw new Error('row_fields_invalid');\n  }\n  if (typeof row.id !== 'string' || !/^\\d{3}$/.test(row.id) || ids.has(row.id)) {\n    throw new Error('id_invalid_or_duplicate');\n  }\n  if (typeof row.title !== 'string' || !row.title.trim() || row.title.length > 100) {\n    throw new Error('title_invalid');\n  }\n  if (!Number.isSafeInteger(row.quantity) || row.quantity < 0) throw new Error('quantity_invalid');\n  if (typeof row.active !== 'boolean') throw new Error('active_invalid');\n  if (row.note !== null && (typeof row.note !== 'string' || row.note.length > 200)) {\n    throw new Error('note_invalid');\n  }\n  if (!Array.isArray(row.tags) || row.tags.length > 3 || row.tags.some(t => typeof t !== 'string' || !t.trim() || t.length > 30)) {\n    throw new Error('tags_invalid');\n  }\n  ids.add(row.id);\n}\nif (!inputs[0].binary?.document) throw new Error('download_file_missing');\nif (!Number.isSafeInteger(inputs[0].json.download?.bytes)) throw new Error('download_metadata_missing');\nreturn [{\n  json: {\n    status: 'preview', count: rows.length, ids: rows.map(r => r.id),\n    zero_quantity_ids: rows.filter(r => r.quantity === 0).map(r => r.id),\n    inactive_ids: rows.filter(r => r.active === false).map(r => r.id),\n    bytes: inputs[0].json.download.bytes, action: false\n  },\n  binary: inputs[0].binary\n}];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Download Catalog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Catalog": {
      "main": [
        [
          {
            "node": "Check Download",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Download": {
      "main": [
        [
          {
            "node": "Read JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read JSON": {
      "main": [
        [
          {
            "node": "Catalog Preview",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataSuccessExecution": "all",
    "saveDataErrorExecution": "all"
  },
  "pinData": {}
}
