{ "kind": "open-flow-workflow", "version": 1, "name": "Create a Notion Project and Enroll a Team", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_7c109392a40425bb": { "name": "Receive team", "kind": "webhook", "bodyFields": [ { "jsonSchema": {}, "nullable": true, "handle": "projectName" }, { "jsonSchema": {}, "nullable": true, "handle": "projectIdea" }, { "jsonSchema": {}, "nullable": true, "handle": "teamMembers" } ], "method": "POST", "options": { "responseBody": { "kind": "workflow-outputs", "output": "body", "timeoutMs": 60000 }, "responseHeaders": { "WWW-Authenticate": "Basic realm=\"Teams\"" }, "responseStatusCodeOutput": "statusCode" } }, "n_673a735cdff3d214": { "inputs": { "headers": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7c109392a40425bb", "output": "headers" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_7c109392a40425bb", "output": "body" } ] }, "expectedAuthorization": { "kind": "unset" } }, "name": "Authorize request", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "headers" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "expectedAuthorization" } ], "outputs": [ { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "accepted" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "response" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "request" } ], "name": "Authorize request", "moduleId": "n_7b08b92d1be5b531", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_ec3663b3f980c2de": { "inputs": {}, "name": "Request authorized?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_673a735cdff3d214", "output": "accepted" } }, "operator": "isTrue" } ] } ] } ] }, "n_3b40e965210b21dc": { "inputs": { "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_673a735cdff3d214", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_673a735cdff3d214", "output": "response" } ] } }, "name": "Reject request", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "name": "Reject request", "moduleId": "n_5dcf1bab90b32cad", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_ad9de83b90c5cde1": { "inputs": { "request": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_673a735cdff3d214", "output": "request" } ] }, "previous": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ad9de83b90c5cde1", "output": "state" } ] }, "userSourceId": { "kind": "value", "value": "YOUR_USER_DATA_SOURCE_ID" }, "semesterSourceId": { "kind": "value", "value": "YOUR_SEMESTER_DATA_SOURCE_ID" }, "projectSourceId": { "kind": "value", "value": "YOUR_PROJECT_DATA_SOURCE_ID" } }, "name": "Create project and connect members", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "request" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": true, "handle": "previous" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "userSourceId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "semesterSourceId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "projectSourceId" } ], "outputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" }, { "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "more" } ], "name": "Create project and connect members", "moduleId": "n_6ba64ac26c2b7aba", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "notion.query_data_source" }, { "action": "notion.create_page" }, { "action": "notion.retrieve_page_property" }, { "action": "notion.update_page" } ] } ] } }, "n_f2cce6f5fab03880": { "inputs": {}, "name": "More work?", "kind": "condition", "matchMode": "first", "cases": [ { "output": "yes", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_ad9de83b90c5cde1", "output": "more" } }, "operator": "isTrue" } ] } ] } ] }, "n_0d9aea220a449d9c": { "inputs": { "state": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ad9de83b90c5cde1", "output": "state" } ] } }, "name": "Confirm team creation", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "state" } ], "outputs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "name": "Confirm team creation", "moduleId": "n_ddbe2b9dcc50a1ca", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_8fb6168245f870c0": { "name": "Team result", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": true }, "nullable": false, "handle": "body" } ], "outputMappings": { "statusCode": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0d9aea220a449d9c", "output": "statusCode" }, { "kind": "node", "nodeId": "n_3b40e965210b21dc", "output": "statusCode" } ] }, "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_0d9aea220a449d9c", "output": "body" }, { "kind": "node", "nodeId": "n_3b40e965210b21dc", "output": "body" } ] } } } }, "edges": [ { "source": "n_7c109392a40425bb", "target": "n_673a735cdff3d214" }, { "source": "n_673a735cdff3d214", "target": "n_ec3663b3f980c2de" }, { "source": "n_ec3663b3f980c2de", "target": "n_ad9de83b90c5cde1", "sourceHandle": "yes" }, { "source": "n_ec3663b3f980c2de", "target": "n_3b40e965210b21dc", "sourceHandle": "otherwise" }, { "source": "n_3b40e965210b21dc", "target": "n_8fb6168245f870c0" }, { "source": "n_ad9de83b90c5cde1", "target": "n_f2cce6f5fab03880" }, { "source": "n_f2cce6f5fab03880", "target": "n_ad9de83b90c5cde1", "sourceHandle": "yes" }, { "source": "n_f2cce6f5fab03880", "target": "n_0d9aea220a449d9c", "sourceHandle": "otherwise" }, { "source": "n_0d9aea220a449d9c", "target": "n_8fb6168245f870c0" } ] } }, "modules": { "n_7b08b92d1be5b531": { "name": "Authorize request", "imports": [], "source": "import {Buffer} from 'node:buffer';\nexport default function authorize({headers,body,expectedAuthorization}){const expected=typeof expectedAuthorization==='string'?expectedAuthorization.trim():'';const p=/^Basic ([A-Za-z0-9+/]+={0,2})$/.exec(expected),secret=p?Buffer.from(p[1],'base64').toString('utf8'):'';if(!p||!/^[^:]+:.+$/.test(secret)||/[\\r\\n]/.test(secret))throw Error('Configure a Basic authorization secret');const entries=Object.entries(headers||{}).filter(([k])=>k.toLowerCase()==='authorization'),match=entries.length===1&&typeof entries[0][1]==='string'?/^Basic +([A-Za-z0-9+/]+={0,2})$/i.exec(entries[0][1].trim()):null,actual=match?Buffer.from(match[1],'base64').toString('utf8'):'';let diff=secret.length^actual.length;for(let i=0;icontext.actions.call('notion.'+a,p), valid=s=>typeof s==='string'&&s.trim()&&!s.startsWith('YOUR_');\n let s=previous;\n if(!s){if(![userSourceId,semesterSourceId,projectSourceId].every(valid))throw Error('Configure all Notion data source IDs');if(!Array.isArray(request.teamMembers)||!request.teamMembers.length||typeof request.projectName!=='string'||typeof request.projectIdea!=='string')throw Error('Provide projectName, projectIdea and teamMembers');const seen=new Set();for(const m of request.teamMembers){if(!valid(m?.name)||!valid(m?.email)||seen.has(m.email.toLowerCase()))throw Error('Provide distinct member names and emails');seen.add(m.email.toLowerCase())}s={phase:'semester',members:request.teamMembers,index:0,name:request.projectName,idea:request.projectIdea};}\n const list=r=>{if(!Array.isArray(r?.results)||typeof r.has_more!=='boolean')throw Error('Invalid Notion list');return r};\n const advance=(phase,extra={})=>({state:{...s,...extra,phase},more:true});\n if(s.phase==='semester'){const r=list(await call('query_data_source',{dataSourceId:semesterSourceId,filter:{property:'Is Current?',checkbox:{equals:true}},pageSize:2}));if(r.has_more||r.results.length!==1||!valid(r.results[0]?.id))throw Error('Exactly one current semester is required');const p=r.results[0].properties?.Projects?.id;if(!valid(p))throw Error('Semester Projects relation missing');return advance('projects',{semesterId:r.results[0].id,propertyId:p,ids:[],cursor:null,seen:[]})}\n if(['projects','semesters','memberProjects'].includes(s.phase)){const r=list(await call('retrieve_page_property',{pageId:s.phase==='projects'?s.semesterId:s.userId,propertyId:s.propertyId,pageSize:100,...(s.cursor?{startCursor:s.cursor}:{})}));const ids=r.results.map(x=>{if(!valid(x?.relation?.id))throw Error('Invalid relation');return x.relation.id}),next=r.has_more?r.next_cursor:null;if(r.has_more&&(!valid(next)||s.seen.includes(next)))throw Error('Invalid cursor');const all=[...s.ids,...ids];if(next)return advance(s.phase,{ids:all,cursor:next,seen:[...s.seen,next]});if(s.phase==='projects')return advance('createProject',{projectCount:all.length});const target=s.phase==='semesters'?s.semesterId:s.projectId,combined=[target,...all.filter(x=>x!==target)];if(combined.length>100)throw Error('Notion relation exceeds 100');return advance(s.phase==='semesters'?'updateSemester':'updateProject',{relations:combined})}\n if(s.phase==='createProject'){const title=s.name.trim()||`Project Group ${s.projectCount+1}`;const r=await call('create_page',{parent:{data_source_id:projectSourceId},properties:{Name:{title:[{text:{content:title}}]},Semesters:{relation:[{id:s.semesterId}]},'Project Idea':{rich_text:[{text:{content:s.idea}}]}}});if(!valid(r?.id))throw Error('Project creation unconfirmed');return advance('lookup',{projectId:r.id})}\n if(s.phase==='lookup'){const m=s.members[s.index],r=list(await call('query_data_source',{dataSourceId:userSourceId,filter:{property:'Email',email:{equals:m.email}},pageSize:2}));if(r.has_more||r.results.length>1)throw Error('Ambiguous member email');if(!r.results.length)return advance('createUser');const p=r.results[0];if(!valid(p.id)||!valid(p.properties?.Semesters?.id)||!valid(p.properties?.Project?.id))throw Error('Missing member relations');return advance('semesters',{userId:p.id,semesterPropertyId:p.properties.Semesters.id,projectPropertyId:p.properties.Project.id,propertyId:p.properties.Semesters.id,ids:[],cursor:null,seen:[]})}\n if(s.phase==='createUser'){const m=s.members[s.index],p=await call('create_page',{parent:{data_source_id:userSourceId},properties:{Name:{title:[{text:{content:m.name}}]},Email:{email:m.email}}});if(!valid(p?.id)||!valid(p.properties?.Semesters?.id)||!valid(p.properties?.Project?.id))throw Error('User creation unconfirmed');return advance('semesters',{userId:p.id,semesterPropertyId:p.properties.Semesters.id,projectPropertyId:p.properties.Project.id,propertyId:p.properties.Semesters.id,ids:[],cursor:null,seen:[]})}\n if(s.phase==='updateSemester'){const r=await call('update_page',{pageId:s.userId,properties:{Semesters:{relation:s.relations.map(id=>({id}))}}});if(r?.id!==s.userId)throw Error('Semester update unconfirmed');return advance('memberProjects',{propertyId:s.projectPropertyId,ids:[],cursor:null,seen:[]})}\n if(s.phase==='updateProject'){const r=await call('update_page',{pageId:s.userId,properties:{Project:{relation:s.relations.map(id=>({id}))}}});if(r?.id!==s.userId)throw Error('Project update unconfirmed');if(s.index+1===s.members.length)return{state:{...s,phase:'done'},more:false};return advance('lookup',{index:s.index+1})}\n throw Error('Invalid project progress');\n}\n" }, "n_ddbe2b9dcc50a1ca": { "name": "Confirm team creation", "imports": [], "source": "export default function done({state}){return {statusCode:200,body:{projectId:state.projectId,memberCount:state.members.length}}}" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }