{ "kind": "open-flow-workflow", "version": 1, "name": "Plan Random Mealie Dinners", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_03e50d6f2caebba2": { "name": "Friday at 20:00 UTC", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 20 * * 5", "timezone": "UTC" } ] }, "n_aa54573fd9002005": { "name": "Plan dinners now", "kind": "start", "inputDefs": [] }, "n_9b6831a52b856576": { "inputs": { "baseUrl": { "kind": "value", "value": "https://YOUR_MEALIE_SERVER" }, "categoryId": { "kind": "value", "value": "YOUR_CATEGORY_ID" } }, "name": "Configure Mealie", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "baseUrl" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "categoryId" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "base" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "readUrl" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "postUrl" } ], "name": "Configure Mealie", "moduleId": "n_217a38892bfedfe3", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b6938d6c42827f27": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_9b6831a52b856576", "output": "readUrl" } ] }, "auth.token": { "kind": "unset" } }, "name": "Read recipes", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "auth.token" } ], "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 recipes", "executor": { "method": "GET", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "bearer" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_f2d393ecffee9431": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b6938d6c42827f27", "output": "body" } ] }, "count": { "kind": "value", "value": 5 }, "offset": { "kind": "value", "value": 3 } }, "name": "Choose dinner plans", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "offset" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "plans" } ], "name": "Choose dinner plans", "moduleId": "n_9ef65b2c34bd041a", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_82d9b898d75a17e1": { "inputs": { "plans": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f2d393ecffee9431", "output": "plans" } ] }, "previousState": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_82d9b898d75a17e1", "output": "state" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e8ee629e3d257248", "output": "body" } ] } }, "name": "Prepare the next dinner", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "nullable": false, "handle": "plans" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previousState" }, { "jsonSchema": {}, "nullable": true, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "name": "Prepare the next dinner", "moduleId": "n_9b0f8b6b2c2bf5de", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_89da8ebf2aa7c620": { "inputs": {}, "name": "More dinners to create?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "more", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_82d9b898d75a17e1", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_e8ee629e3d257248": { "inputs": { "url": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_9b6831a52b856576", "output": "postUrl" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_82d9b898d75a17e1", "output": "body" } ] }, "auth.token": { "kind": "unset" } }, "name": "Create a dinner plan", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "auth.token" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Create a dinner plan", "executor": { "method": "POST", "query": { "mode": "fields", "names": [] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "json", "authentication": { "type": "bearer" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_c59285e45ed87664": { "name": "Created dinner count", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "count" } ], "outputMappings": { "count": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_82d9b898d75a17e1", "output": "count" } ] } } } }, "edges": [ { "source": "n_03e50d6f2caebba2", "target": "n_9b6831a52b856576" }, { "source": "n_aa54573fd9002005", "target": "n_9b6831a52b856576" }, { "source": "n_9b6831a52b856576", "target": "n_b6938d6c42827f27" }, { "source": "n_b6938d6c42827f27", "target": "n_f2d393ecffee9431" }, { "source": "n_f2d393ecffee9431", "target": "n_82d9b898d75a17e1" }, { "source": "n_82d9b898d75a17e1", "target": "n_89da8ebf2aa7c620" }, { "source": "n_89da8ebf2aa7c620", "target": "n_e8ee629e3d257248", "sourceHandle": "more" }, { "source": "n_e8ee629e3d257248", "target": "n_82d9b898d75a17e1" }, { "source": "n_89da8ebf2aa7c620", "target": "n_c59285e45ed87664", "sourceHandle": "otherwise" } ] } }, "modules": { "n_217a38892bfedfe3": { "name": "Configure Mealie", "imports": [], "source": "export default function config({baseUrl,categoryId}) {\n if(typeof baseUrl!=='string'||!/^https?:\\/\\/[^\\/?#@\\s]+(?:\\/[^?#\\s]*)?$/i.test(baseUrl)||baseUrl.includes('YOUR_'))throw Error('Configure a Mealie base URL without credentials');\n if(typeof categoryId!=='string'||categoryId.includes('YOUR_'))throw Error('Configure a Mealie category ID or leave it empty');\n const base=baseUrl.replace(/\\/+$/,'');return {base,readUrl:base+'/api/recipes?perPage=100'+(categoryId.trim()?'&categories='+encodeURIComponent(categoryId.trim()):''),postUrl:base+'/api/households/mealplans'};\n}\n" }, "n_9ef65b2c34bd041a": { "name": "Choose dinner plans", "imports": [], "source": "export default function plan({body,count,offset}) {\n if(!body||!Array.isArray(body.items))throw Error('Mealie did not return a recipe list');\n const items=body.items;\n if(!Number.isInteger(count)||count<1||count>100||!Number.isInteger(offset)||offset<0||offset>365)throw Error('Configure a recipe count from 1 to 100 and a day offset from 0 to 365');\n if(items.length!x||typeof x.id!=='string'||!x.id||typeof x.name!=='string'))throw Error('A recipe is missing its ID or name');\n const ids=items.map(x=>x.id);if(new Set(ids).size!==ids.length)throw Error('Recipe IDs must be unique');\n const shuffled=[...items];for(let i=shuffled.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[shuffled[i],shuffled[j]]=[shuffled[j],shuffled[i]]}\n const today=new Date();today.setUTCHours(0,0,0,0);return {plans:shuffled.slice(0,count).map((r,i)=>{const d=new Date(today);d.setUTCDate(d.getUTCDate()+offset+i);return {date:d.toISOString().slice(0,10),entryType:'dinner',recipeId:r.id,name:r.name}})};\n}\n" }, "n_9b0f8b6b2c2bf5de": { "name": "Prepare the next dinner", "imports": [], "source": "export default function next({plans,previousState,body}) {\n const i=previousState?.index??0;if(i>0&&(!body||!Number.isSafeInteger(body.id)||body.recipeId!==plans[i-1].recipeId||body.date!==plans[i-1].date))throw Error('Mealie did not confirm the previous meal');\n return {state:{index:i+1},more:i