{ "kind": "open-flow-workflow", "version": 1, "name": "Track New ISS Position", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_6a71a132414f7144": { "name": "Every minute", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "* * * * *", "timezone": "UTC" } ] }, "n_e0f8d937da6e332e": { "inputs": {}, "name": "Prepare the current timestamp", "kind": "task", "task": { "inputs": [], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "timestamp" } ], "name": "Prepare the current timestamp", "moduleId": "n_10994798908bebda", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_d67542826ed6bfcf": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e0f8d937da6e332e", "output": "url" } ] } }, "name": "Read the ISS position", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Read the ISS position", "executor": { "method": "GET", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "none" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_e03841616b1f9d39": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_d67542826ed6bfcf", "output": "body" } ] }, "timestamp": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e0f8d937da6e332e", "output": "timestamp" } ] } }, "name": "Validate the position", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "timestamp" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "position" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "rows" } ], "name": "Validate the position", "moduleId": "n_a1877f13bd70fa19", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_515454fe4c862a2c": { "inputs": { "position": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e03841616b1f9d39", "output": "position" } ] } }, "name": "Keep only a new timestamp", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "position" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "result" } ], "name": "Keep only a new timestamp", "moduleId": "n_d940f4a5d7aff713", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_e8dbb6b0798c4095": { "name": "Position or no change", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "result" } ], "outputMappings": { "result": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_515454fe4c862a2c", "output": "result" } ] } } } }, "edges": [ { "source": "n_6a71a132414f7144", "target": "n_e0f8d937da6e332e" }, { "source": "n_e0f8d937da6e332e", "target": "n_d67542826ed6bfcf" }, { "source": "n_d67542826ed6bfcf", "target": "n_e03841616b1f9d39" }, { "source": "n_e03841616b1f9d39", "target": "n_515454fe4c862a2c" }, { "source": "n_515454fe4c862a2c", "target": "n_e8dbb6b0798c4095" } ] } }, "modules": { "n_10994798908bebda": { "name": "Prepare the current timestamp", "imports": [], "source": "export default function query() {const timestamp=Math.floor(Date.now()/1000);return {timestamp,url:'https://api.wheretheiss.at/v1/satellites/25544/positions?timestamps='+timestamp};}\n" }, "n_a1877f13bd70fa19": { "name": "Validate the position", "imports": [], "source": "export default function row({body,timestamp}) {\n if(!Array.isArray(body)||body.length!==1)throw Error('Expected one ISS position');const p=body[0];\n if(!p||p.id!==25544||typeof p.name!=='string'||!p.name||!Number.isFinite(p.latitude)||p.latitude < -90||p.latitude>90||!Number.isFinite(p.longitude)||p.longitude < -180||p.longitude>180||p.timestamp!==timestamp)throw Error('ISS position does not match the request');\n const position={name:p.name,latitude:p.latitude,longitude:p.longitude,timestamp:p.timestamp};return {position,rows:[{json:position}]};\n}\n" }, "n_d940f4a5d7aff713": { "name": "Keep only a new timestamp", "imports": [], "source": "export default async function dedup({position},context){const p=position;if(!p||!Number.isSafeInteger(p.timestamp))throw Error('Invalid position');const prior=await context.state.get('last-iss-timestamps');if(prior!==null&&!Array.isArray(prior))throw Error('Invalid saved timestamps');const duplicate=prior?.includes(p.timestamp)??false;await context.state.set('last-iss-timestamps',[p.timestamp]);return{result:duplicate?{message:'No new items'}:{timestamp:p.timestamp,latitude:p.latitude,longitude:p.longitude}}}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }