{
  "name": "ezGPT - ZIP Roundtrip",
  "active": false,
  "nodes": [
    {
      "id": "trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {}
    },
    {
      "id": "sample-files",
      "name": "Sample Files",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const files = [\n  {\n    \"field\": \"orders\",\n    \"name\": \"orders.csv\",\n    \"mime\": \"text/csv\",\n    \"text\": \"order_id,client,amount_minor\\n001,Альфа,150000\\n002,Бета,0\\n\"\n  },\n  {\n    \"field\": \"readme\",\n    \"name\": \"readme.txt\",\n    \"mime\": \"text/plain\",\n    \"text\": \"Учебный архив ezGPT. Только вымышленные данные.\\n\"\n  },\n  {\n    \"field\": \"meta\",\n    \"name\": \"metadata.json\",\n    \"mime\": \"application/json\",\n    \"text\": \"{\\\"batch_id\\\":\\\"zip-demo-001\\\",\\\"currency\\\":\\\"RUB\\\"}\\n\"\n  }\n];\nconst binary = {};\nfor (const file of files) {\n  binary[file.field] = await this.helpers.prepareBinaryData(\n    Buffer.from(file.text, 'utf8'), file.name, file.mime,\n  );\n}\nreturn [{ json: { batch_id: 'zip-demo-001' }, binary }];"
      }
    },
    {
      "id": "check-files",
      "name": "Check Files",
      "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_file_item_required');\nconst item = items[0];\nconst binary = item.binary || {};\nif (Object.keys(binary).sort().join('|') !== 'meta|orders|readme') {\n  throw new Error('binary_fields_mismatch');\n}\nconst names = new Set();\nconst manifest = [];\nconst expected = { orders: 'orders.csv', readme: 'readme.txt', meta: 'metadata.json' };\nfor (const [field, fileName] of Object.entries(expected)) {\n  const actualName = binary[field].fileName;\n  if (typeof actualName !== 'string' || !actualName || names.has(actualName)) {\n    throw new Error('file_name_missing_or_duplicate');\n  }\n  if (actualName !== fileName) throw new Error('file_name_mismatch');\n  names.add(actualName);\n  const buffer = await this.helpers.getBinaryDataBuffer(0, field);\n  if (!buffer.length || buffer.length > 16384) throw new Error('file_size_invalid');\n  const text = buffer.toString('utf8');\n  if (!Buffer.from(text, 'utf8').equals(buffer)) throw new Error('file_utf8_invalid');\n  manifest.push({ file_name: fileName, bytes: buffer.length, text });\n}\nreturn [{ json: { batch_id: item.json.batch_id, manifest }, binary }];"
      }
    },
    {
      "id": "make-zip",
      "name": "Make ZIP",
      "type": "n8n-nodes-base.compression",
      "typeVersion": 1.1,
      "position": [
        900,
        300
      ],
      "parameters": {
        "operation": "compress",
        "binaryPropertyName": "orders,readme,meta",
        "outputFormat": "zip",
        "fileName": "ezgpt-demo.zip",
        "binaryPropertyOutput": "bundle"
      }
    },
    {
      "id": "check-zip",
      "name": "Check ZIP",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1 || Object.keys(items[0].binary || {}).join('|') !== 'bundle') {\n  throw new Error('zip_binary_field_required');\n}\nconst file = items[0].binary.bundle;\nif (file.fileName !== 'ezgpt-demo.zip' || file.fileExtension !== 'zip') {\n  throw new Error('zip_name_or_extension_invalid');\n}\nconst buffer = await this.helpers.getBinaryDataBuffer(0, 'bundle');\nif (buffer.length > 65536 || buffer.subarray(0, 4).toString('hex') !== '504b0304') {\n  throw new Error('zip_header_or_size_invalid');\n}\nreturn items;"
      }
    },
    {
      "id": "open-zip",
      "name": "Open ZIP",
      "type": "n8n-nodes-base.compression",
      "typeVersion": 1.1,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "operation": "decompress",
        "binaryPropertyName": "bundle",
        "outputPrefix": "file_"
      }
    },
    {
      "id": "check-roundtrip",
      "name": "Check Roundtrip",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nif (items.length !== 1) throw new Error('one_unpacked_item_required');\nconst binary = items[0].binary || {};\nconst expected = $('Check Files').first().json.manifest;\nif (Object.keys(binary).length !== expected.length) throw new Error('unpacked_count_mismatch');\nconst found = new Set();\nconst files = [];\nfor (const [field, metadata] of Object.entries(binary)) {\n  const original = expected.find(file => file.file_name === metadata.fileName);\n  if (!original || found.has(metadata.fileName)) throw new Error('unpacked_name_mismatch');\n  const buffer = await this.helpers.getBinaryDataBuffer(0, field);\n  if (!buffer.equals(Buffer.from(original.text, 'utf8'))) throw new Error('unpacked_content_mismatch');\n  found.add(metadata.fileName);\n  files.push({ file_name: metadata.fileName, bytes: buffer.length, matches_original: true });\n}\nfiles.sort((a, b) => a.file_name.localeCompare(b.file_name));\nreturn [{ json: {\n  batch_id: $('Check Files').first().json.batch_id,\n  archive_name: 'ezgpt-demo.zip',\n  file_count: files.length,\n  files,\n  roundtrip_ok: true,\n  action_performed: false,\n} }];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Sample Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Files": {
      "main": [
        [
          {
            "node": "Check Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Files": {
      "main": [
        [
          {
            "node": "Make ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Make ZIP": {
      "main": [
        [
          {
            "node": "Check ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check ZIP": {
      "main": [
        [
          {
            "node": "Open ZIP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Open ZIP": {
      "main": [
        [
          {
            "node": "Check Roundtrip",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataSuccessExecution": "all",
    "saveDataErrorExecution": "all"
  },
  "pinData": {}
}
