419 lines
No EOL
13 KiB
JSON
419 lines
No EOL
13 KiB
JSON
{
|
|
"name": "Genome: ingest",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "forgejo-push",
|
|
"options": {}
|
|
},
|
|
"id": "8c44b478-1a95-4c3b-8ac1-d7c57e228414",
|
|
"name": "Webhook",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1520,
|
|
1728
|
|
],
|
|
"webhookId": "cf215f5d31e04dd2"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Bell filter: proceed ONLY on develop pushes that actually touch raw/.\n// Returning [] stops the flow (no node needed).\n// Performance: never wake vm101 for wiki-only pushes (e.g. an ingest PR merged back to develop).\n// pending-raw remains the source of truth.\nconst item = $input.first().json;\nconst b = item.body || item;\nconst ref = String(b.ref || '');\nconst genome = String((b.repository && b.repository.name) || '').toLowerCase().trim();\n\n// Branch filter\nif (ref !== 'refs/heads/develop') return [];\n\n// Genome name validation (DNS-like: lowercase alphanum + hyphen, 1-64 chars)\nif (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(genome)) return [];\n\n// Collect all touched paths safely (added, modified, removed)\nconst commits = Array.isArray(b.commits) ? b.commits : [];\nconst touched = [];\nfor (const c of commits) {\n if (!c || typeof c !== 'object') continue;\n for (const key of ['added', 'modified', 'removed']) {\n const list = c[key];\n if (!Array.isArray(list)) continue;\n for (const p of list) {\n if (typeof p === 'string' && p.startsWith('raw/')) {\n touched.push(p);\n }\n }\n }\n}\n\n// Gate: stop if nothing under raw/ was touched\nif (touched.length === 0) return [];\n\nreturn [{ json: { genome, touchedCount: touched.length } }];"
|
|
},
|
|
"id": "604787c7-4e83-468e-9a98-3ac084203040",
|
|
"name": "Gate push",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1744,
|
|
1728
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"workflowId": {
|
|
"__rl": true,
|
|
"value": "zbtRXWsLt56nEIfz",
|
|
"mode": "list",
|
|
"cachedResultUrl": "/workflow/zbtRXWsLt56nEIfz",
|
|
"cachedResultName": "Power Manager"
|
|
},
|
|
"workflowInputs": {
|
|
"mappingMode": "defineBelow",
|
|
"value": {
|
|
"mode": "ensure-on"
|
|
},
|
|
"matchingColumns": [
|
|
"mode"
|
|
],
|
|
"schema": [
|
|
{
|
|
"id": "mode",
|
|
"displayName": "mode",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
}
|
|
],
|
|
"attemptToConvertTypes": false,
|
|
"convertFieldsToString": true
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "f93073a3-7753-4ce1-9ef1-2a0c16386543",
|
|
"name": "Power Manager - ensure-on",
|
|
"type": "n8n-nodes-base.executeWorkflow",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
1952,
|
|
1728
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"authentication": "privateKey",
|
|
"command": "=ssh vm101 'pi pending-raw {{ $('Gate push').first().json.genome }}'"
|
|
},
|
|
"id": "876dbdaf-3620-4c2c-a65b-336f0b11198c",
|
|
"name": "SSH: pending-raw",
|
|
"type": "n8n-nodes-base.ssh",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
2176,
|
|
1728
|
|
],
|
|
"credentials": {
|
|
"sshPrivateKey": {
|
|
"id": "GJQjKzte7Hjdfz89",
|
|
"name": "n8n container -> n8n-runner@nexus"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Parse pending-raw -> one item per raw, carrying everything run-one-ingest needs.\n// Unsafe filenames (spaces / odd chars) are NOT ingested -> a 'badname' item -> ntfy.\nconst out = String($input.first().json.stdout || '').trim();\nlet d;\ntry {\n d = JSON.parse(out);\n} catch (e) {\n return [{ json: { _kind: 'error', reason: 'pending-raw non parsabile', raw: out.substring(0, 500) } }];\n}\n\nif (!d || typeof d !== 'object') {\n return [{ json: { _kind: 'error', reason: 'pending-raw non è un oggetto JSON', raw: out.substring(0, 500) } }];\n}\n\nconst files = Array.isArray(d.files) ? d.files : [];\nif (files.length === 0) return [];\n\n// Build reason map from detail array\nconst why = {};\nfor (const it of (Array.isArray(d.detail) ? d.detail : [])) {\n if (it && typeof it.path === 'string' && typeof it.reason === 'string') {\n why[it.path] = it.reason;\n }\n}\n\nconst SAFE = /^[A-Za-z0-9._\\/-]+$/;\nconst items = [];\nfor (const raw of files) {\n if (typeof raw !== 'string') {\n items.push({ json: { _kind: 'badname', genome: d.genome, raw: String(raw),\n hint: String(raw).replace(/[^A-Za-z0-9._\\/-]+/g, '-').toLowerCase() || 'invalid' } });\n continue;\n }\n if (SAFE.test(raw)) {\n items.push({ json: { _kind: 'ingest', genome: d.genome, raw,\n mode: 'ingest', feedback_b64: '', reason: why[raw] || 'new', prevPr: '' } });\n } else {\n const hint = raw.replace(/[^A-Za-z0-9._\\/-]+/g, '-').toLowerCase() || 'invalid';\n items.push({ json: { _kind: 'badname', genome: d.genome, raw, hint } });\n }\n}\nreturn items;"
|
|
},
|
|
"id": "f5bbbed3-222e-4129-a764-7cf47d69c5ce",
|
|
"name": "Split raw files",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2400,
|
|
1728
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 2
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "cbacf5d98d594ba5",
|
|
"leftValue": "={{ $json._kind }}",
|
|
"rightValue": "ingest",
|
|
"operator": {
|
|
"type": "string",
|
|
"operation": "equals"
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "5398e2c4-c7ca-4ca4-a2d7-e75077453b7c",
|
|
"name": "Nome valido?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
2624,
|
|
1728
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"workflowId": {
|
|
"__rl": true,
|
|
"value": "VIi2ovb5gJxNJLbg",
|
|
"mode": "list",
|
|
"cachedResultUrl": "/workflow/VIi2ovb5gJxNJLbg",
|
|
"cachedResultName": "Genome: run-one-ingest"
|
|
},
|
|
"workflowInputs": {
|
|
"mappingMode": "defineBelow",
|
|
"value": {
|
|
"genome": "={{ $json.genome }}",
|
|
"raw": "={{ $json.raw }}",
|
|
"mode": "ingest",
|
|
"feedback_b64": "",
|
|
"reason": "={{ $json.reason }}",
|
|
"prevPr": ""
|
|
},
|
|
"matchingColumns": [],
|
|
"schema": [
|
|
{
|
|
"id": "genome",
|
|
"displayName": "genome",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "raw",
|
|
"displayName": "raw",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "mode",
|
|
"displayName": "mode",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "feedback_b64",
|
|
"displayName": "feedback_b64",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "reason",
|
|
"displayName": "reason",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
},
|
|
{
|
|
"id": "prevPr",
|
|
"displayName": "prevPr",
|
|
"required": false,
|
|
"defaultMatch": false,
|
|
"display": true,
|
|
"canBeUsedToMatch": true,
|
|
"type": "string",
|
|
"removed": false
|
|
}
|
|
],
|
|
"attemptToConvertTypes": false,
|
|
"convertFieldsToString": true
|
|
},
|
|
"options": {
|
|
"waitForSubWorkflow": false
|
|
}
|
|
},
|
|
"id": "0f274662-62bb-448b-ae4b-47e4bbcfd35a",
|
|
"name": "Run one ingest",
|
|
"type": "n8n-nodes-base.executeWorkflow",
|
|
"typeVersion": 1.3,
|
|
"position": [
|
|
2832,
|
|
1616
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"mode": "runOnceForEachItem",
|
|
"jsCode": "// Build ntfy notification for files with invalid names.\n// Run Once for Each Item: $json is the current badname item.\nconst d = $json || {};\nconst genome = d.genome || 'unknown';\nconst raw = String(d.raw || 'unknown');\nconst hint = String(d.hint || 'unknown');\n\n// Escape backticks to avoid breaking markdown\nconst rawEsc = raw.replace(/`/g, '\\`');\nconst hintEsc = hint.replace(/`/g, '\\`');\n\nreturn {\n topic: 'genome-ingest',\n title: `${genome} · file da rinominare`,\n priority: 'high',\n tags: 'warning',\n click: '',\n actions: '',\n body: `Il file \\`${rawEsc}\\` ha spazi o caratteri non ammessi e **non** è stato ingerito.\\nRinominalo in: \\`${hintEsc}\\``\n};"
|
|
},
|
|
"id": "0f785bcd-cdc6-4dac-9ced-1c5cfa3453dc",
|
|
"name": "Build ntfy badname",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
2832,
|
|
1840
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "=http://ntfy/{{ $json.topic }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpBearerAuth",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Title",
|
|
"value": "={{ $json.title }}"
|
|
},
|
|
{
|
|
"name": "Priority",
|
|
"value": "={{ $json.priority }}"
|
|
},
|
|
{
|
|
"name": "Tags",
|
|
"value": "={{ $json.tags }}"
|
|
},
|
|
{
|
|
"name": "Click",
|
|
"value": "={{ $json.click }}"
|
|
},
|
|
{
|
|
"name": "Actions",
|
|
"value": "={{ $json.actions }}"
|
|
},
|
|
{
|
|
"name": "Markdown",
|
|
"value": "yes"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"contentType": "raw",
|
|
"rawContentType": "Raw / Text",
|
|
"body": "={{ $json.body }}",
|
|
"options": {
|
|
"timeout": 15000
|
|
}
|
|
},
|
|
"id": "9cd2bde3-6846-4855-ad01-e3a4cdbce208",
|
|
"name": "ntfy: send",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.4,
|
|
"position": [
|
|
3056,
|
|
1840
|
|
],
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "TBPXSWOF63k9mvm8",
|
|
"name": "ntfy-token"
|
|
},
|
|
"httpBearerAuth": {
|
|
"id": "nCv4CUN7Ef086Ewj",
|
|
"name": "Bearer Auth account"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Webhook": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Gate push",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Gate push": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Power Manager - ensure-on",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Power Manager - ensure-on": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "SSH: pending-raw",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"SSH: pending-raw": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Split raw files",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Split raw files": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Nome valido?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Nome valido?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Run one ingest",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Build ntfy badname",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Build ntfy badname": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "ntfy: send",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": true,
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"binaryMode": "separate",
|
|
"timeSavedMode": "fixed",
|
|
"errorWorkflow": "7Vws3gCX3QnjM3oD",
|
|
"callerPolicy": "workflowsFromSameOwner",
|
|
"availableInMCP": false
|
|
},
|
|
"versionId": "63863925-606f-4200-824c-52f1919f2bb1",
|
|
"meta": {
|
|
"instanceId": "96b2f0ec76a4400bbd481c617b24b3b87024cc7a913efacccaf9fc85722e7417"
|
|
},
|
|
"id": "mUJUuQxcDiiPWcUE",
|
|
"tags": []
|
|
} |