{
  "name": "Anycon: обработка файла (опрос)",
  "nodes": [
    {
      "parameters": {},
      "id": "n01-manual",
      "name": "Запуск вручную",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "n01-exec",
      "name": "Из подпроцесса",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        200
      ]
    },
    {
      "parameters": {
        "fileSelector": "/data/input/example.jpg",
        "options": {
          "dataPropertyName": "data"
        }
      },
      "id": "n01-read",
      "name": "Прочитать файл",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        220,
        0
      ],
      "notes": "Замените путь. Для папки: /data/input/*.jpg (см. 03).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a-0005",
              "name": "baseUrl",
              "value": "",
              "type": "string"
            },
            {
              "id": "a-0006",
              "name": "clientRef",
              "value": "default",
              "type": "string"
            },
            {
              "id": "a-0007",
              "name": "copies",
              "value": 1,
              "type": "number"
            },
            {
              "id": "a-0008",
              "name": "mode",
              "value": "clean",
              "type": "string"
            },
            {
              "id": "a-0009",
              "name": "maxPolls",
              "value": 480,
              "type": "number"
            },
            {
              "id": "a-0010",
              "name": "maxPollSeconds",
              "value": 7200,
              "type": "number"
            },
            {
              "id": "a-0011",
              "name": "startedAt",
              "value": "={{ $now.toISO() }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "n01-config",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        100
      ],
      "notes": "Единственное место с адресом API и настроек. clientRef: 1-40 символов A-Za-z0-9_.:- ; смените, чтобы сознательно заказать тот же файл ещё раз.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "POST",
        "url": "={{ $('Config').first().json.baseUrl }}/uploads",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "file_name",
              "value": "={{ $binary.data.fileName }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {
          "timeout": 600000
        }
      },
      "id": "n01-upload",
      "name": "Загрузить файл",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        660,
        100
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_CREDENTIAL_ID",
          "name": "Anycon API key"
        }
      }
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "POST",
        "url": "={{ $('Config').first().json.baseUrl }}/orders",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "=anycon-{{ ($json.sha256 || String($json.size_bytes)).replace(/[^0-9a-f]/gi, '').slice(0, 40) }}-c{{ String($('Config').first().json.copies).replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 3) }}-{{ String($('Config').first().json.mode).replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 20) }}-{{ String($('Config').first().json.clientRef).replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 40) || 'default' }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ upload_id: $json.upload_id, copies: $('Config').first().json.copies, options: { mode: $('Config').first().json.mode } }) }}",
        "options": {}
      },
      "id": "n01-order",
      "name": "Создать заказ",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        100
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_CREDENTIAL_ID",
          "name": "Anycon API key"
        }
      },
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "notes": "Повтор безопасен: тот же Idempotency-Key вернёт тот же заказ.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "resume": "timeInterval",
        "amount": "={{ $runIndex === 0 ? 5 : 15 }}",
        "unit": "seconds"
      },
      "id": "n01-wait",
      "name": "Ждать (5 с, затем 15 с)",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1100,
        100
      ],
      "webhookId": "0a1b2c3d-0001-4000-8000-000000000001"
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "GET",
        "url": "={{ $('Config').first().json.baseUrl }}/orders/{{ $json.order_id }}",
        "options": {}
      },
      "id": "n01-status",
      "name": "Статус заказа",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        100
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_CREDENTIAL_ID",
          "name": "Anycon API key"
        }
      },
      "retryOnFail": true,
      "maxTries": 5,
      "waitBetweenTries": 5000,
      "notes": "Повтор при сетевых ошибках, 5xx и 429 (пауза 5 с).",
      "notesInFlow": true
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "const cfg = $('Config').first().json;\nconst poll_count = $runIndex + 1;\nconst elapsed = (Date.now() - Date.parse(cfg.startedAt)) / 1000;\nconst timed_out = poll_count >= Number(cfg.maxPolls) || elapsed >= Number(cfg.maxPollSeconds);\nreturn { json: { ...$json, poll_count, poll_started_at: cfg.startedAt, timed_out } };"
      },
      "id": "n01-counter",
      "name": "Счётчик опроса",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1430,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c-0012",
              "leftValue": "={{ $json.timed_out }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "n01-limit",
      "name": "Лимит опроса?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1650,
        300
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a-0013",
              "name": "order_id",
              "value": "={{ $json.order_id }}",
              "type": "string"
            },
            {
              "id": "a-0014",
              "name": "last_status",
              "value": "={{ $json.status }}",
              "type": "string"
            },
            {
              "id": "a-0015",
              "name": "poll_count",
              "value": "={{ $json.poll_count }}",
              "type": "number"
            },
            {
              "id": "a-0016",
              "name": "hint",
              "value": "Лимит опроса исчерпан, заказ НЕ отменён и может завершиться позже. Проверьте GET /orders/{order_id} или запустите опрос заново.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "n01-timeout",
      "name": "Опрос остановлен",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1870,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c-0017",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c-0018",
              "leftValue": "={{ $json.status }}",
              "rightValue": "failed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c-0019",
              "leftValue": "={{ $json.status }}",
              "rightValue": "cancelled",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "c-0020",
              "leftValue": "={{ $json.status }}",
              "rightValue": "review",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "n01-terminal",
      "name": "Заказ завершён?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1540,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c-0021",
              "leftValue": "={{ $json.status }}",
              "rightValue": "completed",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "n01-ok",
      "name": "Успешно?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1760,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "GET",
        "url": "={{ $('Config').first().json.baseUrl }}/orders/{{ $json.order_id }}/results",
        "options": {}
      },
      "id": "n01-results",
      "name": "Получить результаты",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_CREDENTIAL_ID",
          "name": "Anycon API key"
        }
      }
    },
    {
      "parameters": {
        "fieldToSplitOut": "files",
        "options": {}
      },
      "id": "n01-split",
      "name": "Разделить файлы",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        2200,
        0
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.download_url.startsWith('http') ? $json.download_url : $('Config').first().json.baseUrl.replace(/\\/api\\/public\\/v1$/, '') + $json.download_url }}",
        "authentication": "none",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "id": "n01-download",
      "name": "Скачать файл",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        0
      ],
      "notes": "Ссылка действует 15 минут и сама служит доступом: не логируйте её.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "// Сверка sha256 скачанного файла с значением из /results. Нужен NODE_FUNCTION_ALLOW_BUILTIN=crypto.\nconst crypto = require('crypto');\nconst meta = $('Разделить файлы').item.json;\nconst buf = await this.helpers.getBinaryDataBuffer($itemIndex, 'data');\nconst actual = crypto.createHash('sha256').update(buf).digest('hex');\nif (meta.sha256 && actual !== String(meta.sha256).toLowerCase()) throw new Error('sha256 не совпал для файла ' + meta.file_id);\nreturn { json: { file_id: meta.file_id, name: meta.name, kind: meta.kind, sha256: actual, sha256_ok: true }, binary: $input.item.binary };"
      },
      "id": "n01-sha",
      "name": "Проверить sha256",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        0
      ]
    },
    {
      "parameters": {},
      "id": "n01-next",
      "name": "Отправить дальше (заглушка)",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2860,
        0
      ],
      "notes": "Здесь подключите Drive, S3, почту и т.д. Файл — в binary.data.",
      "notesInFlow": true
    },
    {
      "parameters": {},
      "id": "n01-stopped",
      "name": "Заказ не выполнен",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1980,
        200
      ],
      "notes": "failed / cancelled / review: смотрите failure_code в ответе GET /orders/{id}.",
      "notesInFlow": true
    }
  ],
  "connections": {
    "Запуск вручную": {
      "main": [
        [
          {
            "node": "Прочитать файл",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Прочитать файл": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Из подпроцесса": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Загрузить файл",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Загрузить файл": {
      "main": [
        [
          {
            "node": "Создать заказ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Создать заказ": {
      "main": [
        [
          {
            "node": "Ждать (5 с, затем 15 с)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ждать (5 с, затем 15 с)": {
      "main": [
        [
          {
            "node": "Статус заказа",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Статус заказа": {
      "main": [
        [
          {
            "node": "Счётчик опроса",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Счётчик опроса": {
      "main": [
        [
          {
            "node": "Заказ завершён?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Заказ завершён?": {
      "main": [
        [
          {
            "node": "Успешно?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Лимит опроса?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Лимит опроса?": {
      "main": [
        [
          {
            "node": "Опрос остановлен",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ждать (5 с, затем 15 с)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Успешно?": {
      "main": [
        [
          {
            "node": "Получить результаты",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Заказ не выполнен",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Получить результаты": {
      "main": [
        [
          {
            "node": "Разделить файлы",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Разделить файлы": {
      "main": [
        [
          {
            "node": "Скачать файл",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Скачать файл": {
      "main": [
        [
          {
            "node": "Проверить sha256",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Проверить sha256": {
      "main": [
        [
          {
            "node": "Отправить дальше (заглушка)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
