Files
MobilityOps/n8n/mobilityops-return-processing.json
NuklearRabbit 59d663a43a M4: implement n8n automation
Outbox dispatcher (background thread, FOR UPDATE SKIP LOCKED claim, exponential backoff, no transaction held during HTTP I/O). n8n callback endpoint with shared-secret auth and idempotency by event ID. Automation nav + UI with manual retry. 49 backend tests passing, ruff clean. Fixed a crash-on-redelivery bug in seeded outbox payloads and made the dispatcher defensive against malformed payloads. Verified the full live round trip against a real n8n instance: return -> outbox -> dispatcher -> n8n workflow -> callback -> succeeded, including the S5 failed-retry demo scenario.
2026-08-01 22:39:25 +02:00

127 lines
3.2 KiB
JSON

{
"id": "mobilityops-return-processing",
"name": "MobilityOps - Vehicle Return Processing",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "mobilityops-return",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-node",
"name": "Return webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
],
"webhookId": "mobilityops-return"
},
{
"parameters": {
"jsCode": "const e = $json.body ?? $json;\nif (e.event_type !== 'vehicle.returned.v1') throw new Error('Unsupported event type');\nconst reasons = e.data?.attention_reasons ?? [];\nreturn [{json: {event_id: e.event_id, event_type: e.event_type, aggregate_ref: e.aggregate.public_ref, follow_up: reasons.length ? 'attention_required' : 'cleaning', summary: reasons.join('; ') || 'Create cleaning follow-up'}}];"
},
"id": "validate-node",
"name": "Validate and derive follow-up",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
500,
300
]
},
{
"parameters": {
"method": "POST",
"url": "={{$env.MOBILITYOPS_CALLBACK_URL || 'http://api:8000/api/v1/integrations/n8n/return-callback'}}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Idempotency-Key",
"value": "={{$json.event_id}}"
},
{
"name": "X-Service-Token",
"value": "={{$env.MOBILITYOPS_CALLBACK_TOKEN}}"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{JSON.stringify($json)}}",
"options": {}
},
"id": "callback-node",
"name": "Record follow-up",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
760,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { ok: true, event_id: $('Validate and derive follow-up').item.json.event_id, result: $json } }}",
"options": {}
},
"id": "response-node",
"name": "Return result",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
1020,
300
]
}
],
"connections": {
"Return webhook": {
"main": [
[
{
"node": "Validate and derive follow-up",
"type": "main",
"index": 0
}
]
]
},
"Validate and derive follow-up": {
"main": [
[
{
"node": "Record follow-up",
"type": "main",
"index": 0
}
]
]
},
"Record follow-up": {
"main": [
[
{
"node": "Return result",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"active": true,
"versionId": "11111111-1111-4111-8111-111111111111",
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}