{
  "name": "فحص حالة المواقع — Project OS",
  "active": false,
  "pinData": {},
  "settings": { "executionOrder": "v1" },
  "versionId": "site-check-3",
  "meta": { "templateCredsSetupCompleted": true },
  "tags": [],
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "site-check",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "n1-webhook",
      "name": "طلب التشغيل (Webhook)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "webhookId": "site-check",
      "position": [0, 0]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// تجهيز طلب الفحص الوارد من Project OS\nconst input = $input.first().json;\nconst body = input.body ?? input ?? {};\n\n// المواقع الثلاثة التي يراقبها التطبيق كما تحدّدها متطلبات المشروع (تُستخدم عند عدم إرسال قائمة في الطلب)\nconst DEFAULT_SITES = [\n  { label: \"Project OS\", url: \"https://projectos.smvapp.cloud\" },\n  { label: \"n8n\", url: \"https://n8n.smvapp.cloud\" },\n  { label: \"Student Tutor\", url: \"https://student.smvapp.cloud\" }\n];\n\nlet sites = Array.isArray(body.sites) && body.sites.length ? body.sites.slice(0, 20) : DEFAULT_SITES;\nsites = sites\n  .map((s) => ({ label: String(s.label || s.name || s.url || \"موقع\"), url: String(s.url || \"\") }))\n  .filter((s) => /^https:\\/\\//.test(s.url));\nif (!sites.length) sites = DEFAULT_SITES;\n\nreturn [{\n  json: {\n    runId: body.runId ?? body.run_id ?? null,\n    callbackUrl: body.callbackUrl ?? body.callback_url ?? null,\n    triggeredAt: new Date().toISOString(),\n    sites\n  }\n}];"
      },
      "id": "n2-prepare",
      "name": "تجهيز طلب الفحص",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [220, 0]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// توزيع المواقع: عنصر واحد لكل موقع\nconst base = $input.first().json;\nreturn base.sites.map((site) => ({\n  json: {\n    runId: base.runId,\n    callbackUrl: base.callbackUrl,\n    label: site.label,\n    url: site.url\n  }\n}));"
      },
      "id": "n3-split",
      "name": "توزيع المواقع",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, 0]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "n4-loop",
      "name": "فحص موقعًا بموقع",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [660, 0]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// تسجيل لحظة بدء فحص هذا الموقع لحساب زمن الاستجابة بدقة\nreturn {\n  json: { ...$json, startedAt: Date.now() }\n};"
      },
      "id": "n5-start",
      "name": "توقيت بداية الفحص",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [880, -160]
    },
    {
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "text"
            }
          }
        }
      },
      "id": "n6-http",
      "name": "طلب HTTP حقيقي",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [1100, -160]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// تجهيز نتيجة فحص موقع واحد وفق عقد site.check ثم العودة إلى حلقة الفحص\nconst j = $json;\nconst statusCode = typeof j.statusCode === \"number\" ? j.statusCode : null;\nconst networkError = j.error ? (j.error.message ?? String(j.error)) : null;\n\n// ok: وصلت استجابة HTTP فعلية ورمزها أقل من 400\nconst ok = statusCode !== null && statusCode < 400;\n\nlet error = null;\nif (!ok && statusCode === null) {\n  error = networkError ?? \"لم تصل أي استجابة من الموقع\";\n}\n\nreturn {\n  json: {\n    runId: j.runId,\n    callbackUrl: j.callbackUrl,\n    label: j.label,\n    url: j.url,\n    ok,\n    statusCode,\n    latencyMs: Math.max(0, Date.now() - j.startedAt),\n    checkedAt: new Date().toISOString(),\n    error\n  }\n};"
      },
      "id": "n7-result",
      "name": "تجهيز نتيجة الفحص",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1320, -160]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// تجميع نتائج الفحص في عنصر واحد جاهز للإرسال وفق عقد site.check\nconst items = $input.all();\nconst base = items[0].json;\n\nconst results = items.map((item) => {\n  const j = item.json;\n  return {\n    url: j.url,\n    label: j.label,\n    ok: j.ok === true,\n    statusCode: j.statusCode ?? null,\n    latencyMs: j.latencyMs ?? null,\n    error: j.error ?? null\n  };\n});\n\nreturn [{\n  json: {\n    runId: base.runId ?? null,\n    callbackUrl: base.callbackUrl ?? null,\n    kind: \"site.check\",\n    completedAt: new Date().toISOString(),\n    summary: {\n      total: results.length,\n      ok: results.filter((r) => r.ok).length,\n      down: results.filter((r) => !r.ok).length\n    },\n    results\n  }\n}];"
      },
      "id": "n8-aggregate",
      "name": "تجميع النتائج",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [880, 160]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "has-callback",
              "leftValue": "={{ $json.callbackUrl }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "notEmpty", "singleValue": true }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "n9-if",
      "name": "هل يوجد رابط Callback؟",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [1100, 160]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.callbackUrl }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ runId: $json.runId, kind: $json.kind, completedAt: $json.completedAt, summary: $json.summary, results: $json.results }) }}",
        "options": {
          "timeout": 10000,
          "response": { "response": { "neverError": true } }
        }
      },
      "id": "n10-callback",
      "name": "إرسال النتائج إلى Project OS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "onError": "continueRegularOutput",
      "position": [1320, 40]
    },
    {
      "parameters": {
        "respondWith": "firstIncomingItem",
        "options": { "responseCode": 200 }
      },
      "id": "n11-respond",
      "name": "إرجاع النتائج للمُشغِّل",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [1540, 160]
    }
  ],
  "connections": {
    "طلب التشغيل (Webhook)": {
      "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": "طلب HTTP حقيقي", "type": "main", "index": 0 }]]
    },
    "طلب HTTP حقيقي": {
      "main": [[{ "node": "تجهيز نتيجة الفحص", "type": "main", "index": 0 }]]
    },
    "تجهيز نتيجة الفحص": {
      "main": [[{ "node": "فحص موقعًا بموقع", "type": "main", "index": 0 }]]
    },
    "تجميع النتائج": {
      "main": [[{ "node": "هل يوجد رابط Callback؟", "type": "main", "index": 0 }]]
    },
    "هل يوجد رابط Callback؟": {
      "main": [
        [{ "node": "إرسال النتائج إلى Project OS", "type": "main", "index": 0 }],
        [{ "node": "إرجاع النتائج للمُشغِّل", "type": "main", "index": 0 }]
      ]
    },
    "إرسال النتائج إلى Project OS": {
      "main": [[{ "node": "إرجاع النتائج للمُشغِّل", "type": "main", "index": 0 }]]
    }
  }
}