{
  "name": "Camelus → GitHub Issues",
  "nodes": [
    {
      "parameters": { "httpMethod": "POST", "path": "camelus-github", "responseMode": "onReceived", "options": { "rawBody": true } },
      "id": "b1b2c3d4-0001-4000-8000-000000000001",
      "name": "Camelus webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "webhookId": "camelus-github"
    },
    {
      "parameters": {
        "jsCode": "// Paste the destination's signing secret (whsec_…) here.\nconst SECRET = 'whsec_PASTE_YOUR_SECRET';\nconst crypto = require('crypto');\nconst out = [];\nfor (const item of $input.all()) {\n  const headers = item.json.headers;\n  const raw = Buffer.from(item.binary.data.data, 'base64').toString('utf8');\n  const ts = headers['camelus-timestamp'];\n  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error('Stale or missing Camelus-Timestamp');\n  const expected = crypto.createHmac('sha256', SECRET).update(`${ts}.${raw}`).digest('hex');\n  const ok = (headers['camelus-signature'] || '').split(',').some((p) => p.trim().replace(/^v1=/, '') === expected);\n  if (!ok) throw new Error('Bad Camelus-Signature');\n  const body = JSON.parse(raw);\n  if (!body.data || !body.data.theme) continue; // test.ping and non-theme events\n  out.push({ json: body });\n}\nreturn out;"
      },
      "id": "b1b2c3d4-0001-4000-8000-000000000002",
      "name": "Verify",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [220, 0]
    },
    {
      "parameters": {
        "owner": "YOUR_GITHUB_OWNER",
        "repository": "YOUR_REPOSITORY",
        "title": "={{ $json.data.theme.title }}",
        "body": "={{ $json.data.theme.summary || '' }}\n\nAsked by {{ $json.data.theme.requesters }} people ({{ $json.data.theme.paying_requesters }} paying). Score {{ $json.data.theme.score }}.\n\n{{ $json.data.theme.representative_items.map(i => '- ' + i.summary_en).join('\\n') }}\n\n[Open in Camelus]({{ $json.data.theme.url }})\n\n<!-- camelus-callback: {{ $json.callback.url }} {{ $json.callback.token }} -->",
        "additionalParameters": { "labels": [{ "label": "feedback" }] }
      },
      "id": "b1b2c3d4-0001-4000-8000-000000000003",
      "name": "Create GitHub issue",
      "type": "n8n-nodes-base.github",
      "typeVersion": 1,
      "position": [440, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Verify').item.json.callback.url }}",
        "sendHeaders": true,
        "headerParameters": { "parameters": [{ "name": "Authorization", "value": "=Bearer {{ $('Verify').item.json.callback.token }}" }] },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ status: 'planned', external_id: '#' + $json.number, external_url: $json.html_url }) }}",
        "options": {}
      },
      "id": "b1b2c3d4-0001-4000-8000-000000000004",
      "name": "Report planned",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [660, 0]
    },
    {
      "parameters": { "owner": "YOUR_GITHUB_OWNER", "repository": "YOUR_REPOSITORY", "events": ["issues"] },
      "id": "b1b2c3d4-0001-4000-8000-000000000005",
      "name": "GitHub issue event",
      "type": "n8n-nodes-base.githubTrigger",
      "typeVersion": 1,
      "position": [0, 260],
      "webhookId": "camelus-github-issues"
    },
    {
      "parameters": {
        "jsCode": "// Issues closed as completed that came from Camelus: read the callback from the hidden footer.\nconst out = [];\nfor (const item of $input.all()) {\n  const b = item.json.body || item.json;\n  const issue = b.issue || {};\n  const closed = b.action === 'closed' && issue.state_reason !== 'not_planned';\n  const m = (issue.body || '').match(/camelus-callback: (\\S+) (cbt_\\S+)/);\n  if (closed && m) out.push({ json: { url: m[1], token: m[2], external_id: '#' + issue.number, external_url: issue.html_url } });\n}\nreturn out;"
      },
      "id": "b1b2c3d4-0001-4000-8000-000000000006",
      "name": "Closed Camelus issues",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [220, 260]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.url }}",
        "sendHeaders": true,
        "headerParameters": { "parameters": [{ "name": "Authorization", "value": "=Bearer {{ $json.token }}" }] },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ status: 'shipped', external_id: $json.external_id, external_url: $json.external_url }) }}",
        "options": {}
      },
      "id": "b1b2c3d4-0001-4000-8000-000000000007",
      "name": "Report shipped",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [440, 260]
    }
  ],
  "connections": {
    "Camelus webhook": { "main": [[{ "node": "Verify", "type": "main", "index": 0 }]] },
    "Verify": { "main": [[{ "node": "Create GitHub issue", "type": "main", "index": 0 }]] },
    "Create GitHub issue": { "main": [[{ "node": "Report planned", "type": "main", "index": 0 }]] },
    "GitHub issue event": { "main": [[{ "node": "Closed Camelus issues", "type": "main", "index": 0 }]] },
    "Closed Camelus issues": { "main": [[{ "node": "Report shipped", "type": "main", "index": 0 }]] }
  },
  "settings": { "executionOrder": "v1" },
  "meta": { "templateCredsSetupCompleted": false, "description": "Camelus themes → GitHub issues, with status callbacks. https://camelus.si/docs/destinations#n8n" }
}
