997 lines
71 KiB
JSON
997 lines
71 KiB
JSON
{
|
|
"name": "My workflow 49",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"jsCode": "const missionData = $input.item.json;\nconst binaryData = $input.item.binary;\n\n// Add detailed logging\nconsole.log('Process Mission Data - Input:', {\n hasInput: !!missionData,\n hasBinary: !!binaryData,\n hasBody: !!missionData?.body,\n hasLogo: !!missionData?.logo,\n hasAttachments: Array.isArray(missionData?.attachments),\n attachmentsCount: missionData?.attachments?.length || 0,\n logoDataType: typeof missionData?.logo,\n logoData: missionData?.logo ? 'present' : 'missing',\n contentType: missionData?.missionOriginal?.headers?.['content-type'] || 'unknown',\n binaryKeys: binaryData ? Object.keys(binaryData) : [],\n binaryDataTypes: binaryData ? Object.keys(binaryData).map(key => typeof binaryData[key]?.data) : [],\n creatorId: missionData?.creatorId || 'missing'\n});\n\n// Add detailed logging for services\nconsole.log('Process Mission Data - Services:', {\n originalServices: missionData?.missionOriginal?.body?.services,\n bodyServices: missionData?.body?.services,\n directServices: missionData?.services,\n finalServices: missionData?.missionOriginal?.body?.services || missionData?.body?.services || missionData?.services || []\n});\n\n// Handle raw file input\nif (missionData?.missionOriginal?.headers?.['content-type']?.startsWith('image/')) {\n console.log('Detected raw image file input');\n \n // Get binary data from the first available key\n const binaryKey = Object.keys(binaryData || {})[0];\n const rawData = binaryKey ? binaryData[binaryKey]?.data : null;\n \n if (!rawData) {\n console.error('No binary data found in raw file input');\n throw new Error('No binary data found in raw file input');\n }\n \n // Ensure rawData is a Buffer\n const buffer = Buffer.isBuffer(rawData) ? rawData : Buffer.from(rawData);\n \n // Convert raw data to base64\n const base64Data = buffer.toString('base64');\n const mimeType = missionData.missionOriginal.headers['content-type'];\n \n // Create mission data structure\n return {\n missionOriginal: missionData,\n missionProcessed: {\n name: \"Unnamed Mission\",\n sanitizedName: \"unnamed-mission\",\n intention: \"\",\n description: \"Mission documentation\",\n startDate: new Date().toISOString().split('T')[0],\n endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n missionType: \"default\",\n guardians: {},\n volunteers: [],\n profils: [],\n services: [],\n clientId: 2,\n rocketChatUsernames: [],\n logo: {\n data: `data:${mimeType};base64,${base64Data}`,\n name: \"logo.png\",\n type: mimeType\n },\n attachments: []\n },\n config: {\n GITEA_API_URL: \"https://gite.slm-lab.net/api/v1\",\n GITEA_API_TOKEN: \"310645d564cbf752be1fe3b42582a3d5f5d0bddd\",\n GITEA_OWNER: \"alma\",\n LEANTIME_API_URL: \"https://agilite.slm-lab.net\",\n LEANTIME_API_TOKEN: \"lt_lsdShQdoYHaPUWuL07XZR1Rf3GeySsIs_UDlll3VJPk5EwAuILpMC4BwzJ9MZFRrb\",\n ROCKETCHAT_API_URL: \"https://parole.slm-lab.net/\",\n ROCKETCHAT_AUTH_TOKEN: \"w91TYgkH-Z67Oz72usYdkW5TZLLRwnre7qyAhp7aHJB\",\n ROCKETCHAT_USER_ID: \"Tpuww59PJKsrGNQJB\",\n OUTLINE_API_URL: \"https://chapitre.slm-lab.net/api\",\n OUTLINE_API_TOKEN: \"ol_api_tlLlANBfcoJ4l7zA8GOcpduAeL6QyBTcYvEnlN\",\n MISSION_API_URL: \"https://hub.slm-lab.net\",\n N8N_API_KEY: \"LwgeE1ntADD20OuWC88S3pR0EaO7FtO4\",\n KEYCLOAK_BASE_URL: \"https://connect.slm-lab.net\",\n KEYCLOAK_REALM: \"cercle\",\n KEYCLOAK_CLIENT_ID: \"lab\",\n KEYCLOAK_CLIENT_SECRET: \"LwgeE1ntADD20OuWC88S3P0EaO7FtO4\",\n MINIO_API_URL: \"https://dome-api.slm-lab.net\",\n MINIO_ACCESS_KEY: \"4aBT4CMb7JIMMyUtp4Pl\",\n MINIO_SECRET_KEY: \"HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg\"\n },\n binary: {\n data: buffer\n },\n creatorId: missionData?.creatorId || missionData?.missionOriginal?.body?.creatorId || missionData?.body?.creatorId\n };\n}\n\n// Continue with existing JSON processing\nconst sanitizeName = (name) => {\n if (!name || typeof name !== \"string\") return \"unnamed-mission\";\n return name.toLowerCase()\n .split(\"\")\n .map(c => {\n if (c >= \"a\" && c <= \"z\") return c;\n if (c >= \"0\" && c <= \"9\") return c;\n if (c === \" \" || c === \"-\") return c;\n return \"\";\n })\n .join(\"\")\n .split(\" \")\n .filter(Boolean)\n .join(\"-\");\n};\nconst formatDate = (date) => {\n if (!date) return \"\";\n const d = new Date(date);\n return d.toISOString().split(\"T\")[0];\n};\nconst missionName = missionData?.missionOriginal?.body?.name || missionData?.body?.name || missionData?.name || \"Unnamed Mission\";\n\n// Prepare file data for MinIO\nconst prepareFileData = (file) => {\n if (!file) {\n // Return default logo if no file provided\n return {\n data: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=\",\n name: \"default-logo.png\",\n type: \"image/png\"\n };\n }\n \n // Handle different file formats\n if (typeof file === \"string\") {\n return {\n data: file,\n name: \"logo.png\",\n type: \"image/png\"\n };\n }\n \n if (typeof file === \"object\") {\n // Handle binary data\n if (file.data && typeof file.data === \"object\" && file.data.data) {\n return {\n data: file.data.data,\n name: file.name || \"logo.png\",\n type: file.type || \"image/png\"\n };\n }\n \n // Handle base64 data\n if (file.data && typeof file.data === \"string\") {\n return {\n data: file.data,\n name: file.name || \"logo.png\",\n type: file.type || \"image/png\"\n };\n }\n \n // Handle direct object\n return {\n data: file,\n name: file.name || \"logo.png\",\n type: file.type || \"image/png\"\n };\n }\n \n return null;\n};\n\nconst output = {\n missionOriginal: missionData,\n missionProcessed: {\n name: missionName,\n sanitizedName: sanitizeName(missionName),\n intention: missionData?.missionOriginal?.body?.intention || missionData?.body?.intention || missionData?.intention || \"\",\n description: missionData?.missionOriginal?.body?.intention || missionData?.body?.intention || missionData?.intention || \"Mission documentation\",\n startDate: formatDate(new Date()),\n endDate: formatDate(new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)),\n missionType: missionData?.missionOriginal?.body?.missionType || missionData?.body?.missionType || missionData?.missionType || \"default\",\n guardians: missionData?.missionOriginal?.body?.guardians || missionData?.body?.guardians || missionData?.guardians || {},\n volunteers: missionData?.missionOriginal?.body?.volunteers || missionData?.body?.volunteers || missionData?.volunteers || [],\n profils: missionData?.missionOriginal?.body?.profils || missionData?.body?.profils || missionData?.profils || [],\n services: missionData?.missionOriginal?.body?.services || missionData?.body?.services || missionData?.services || [],\n clientId: (missionData?.missionOriginal?.body?.missionType === \"interne\" || missionData?.body?.missionType === \"interne\" || missionData?.missionType === \"interne\") ? 1 : 2,\n rocketChatUsernames: [],\n logo: prepareFileData(missionData?.logo),\n attachments: Array.isArray(missionData?.attachments) ? missionData.attachments.map(prepareFileData).filter(Boolean) : []\n },\n config: {\n GITEA_API_URL: \"https://gite.slm-lab.net/api/v1\",\n GITEA_API_TOKEN: \"310645d564cbf752be1fe3b42582a3d5f5d0bddd\",\n GITEA_OWNER: \"alma\",\n LEANTIME_API_URL: \"https://agilite.slm-lab.net\",\n LEANTIME_API_TOKEN: \"lt_lsdShQdoYHaPUWuL07XZR1Rf3GeySsIs_UDlll3VJPk5EwAuILpMC4BwzJ9MZFRrb\",\n ROCKETCHAT_API_URL: \"https://parole.slm-lab.net/\",\n ROCKETCHAT_AUTH_TOKEN: \"w91TYgkH-Z67Oz72usYdkW5TZLLRwnre7qyAhp7aHJB\",\n ROCKETCHAT_USER_ID: \"Tpuww59PJKsrGNQJB\",\n OUTLINE_API_URL: \"https://chapitre.slm-lab.net/api\",\n OUTLINE_API_TOKEN: \"ol_api_tlLlANBfcoJ4l7zA8GOcpduAeL6QyBTcYvEnlN\",\n MISSION_API_URL: \"https://hub.slm-lab.net\",\n N8N_API_KEY: \"LwgeE1ntADD20OuWC88S3pR0EaO7FtO4\",\n KEYCLOAK_BASE_URL: \"https://connect.slm-lab.net\",\n KEYCLOAK_REALM: \"cercle\",\n KEYCLOAK_CLIENT_ID: \"lab\",\n KEYCLOAK_CLIENT_SECRET: \"LwgeE1ntADD20OuWC88S3P0EaO7FtO4\",\n MINIO_API_URL: \"https://dome-api.slm-lab.net\",\n MINIO_ACCESS_KEY: \"4aBT4CMb7JIMMyUtp4Pl\",\n MINIO_SECRET_KEY: \"HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg\"\n },\n creatorId: missionData?.creatorId || missionData?.missionOriginal?.body?.creatorId || missionData?.body?.creatorId || missionData?.missionOriginal?.creatorId || missionData?.missionProcessed?.creatorId\n};\n\n// Add binary data to output if available\nif (binaryData) {\n const binaryKey = Object.keys(binaryData)[0];\n if (binaryKey && binaryData[binaryKey]?.data) {\n // Ensure the data is a Buffer\n const data = binaryData[binaryKey].data;\n output.binary = {\n data: Buffer.isBuffer(data) ? data : Buffer.from(data)\n };\n }\n}\n\nconst guardians = missionData?.missionOriginal?.body?.guardians || missionData?.body?.guardians || missionData?.guardians || {};\nif (guardians) {\n for (const role in guardians) {\n const user = guardians[role];\n if (user) output.missionProcessed.rocketChatUsernames.push(user);\n }\n}\nconst volunteers = missionData?.missionOriginal?.body?.volunteers || missionData?.body?.volunteers || missionData?.volunteers || [];\nif (Array.isArray(volunteers)) {\n output.missionProcessed.rocketChatUsernames.push(...volunteers);\n}\noutput.missionProcessed.rocketChatUsernames = [...new Set(output.missionProcessed.rocketChatUsernames)];\n\n// Ensure binary data is always available\nif (!output.binary || !output.binary.data) {\n console.log('No binary data found, using default PNG');\n output.binary = {\n data: Buffer.from(\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=\", 'base64')\n };\n}\n\n// Log the final output\nconsole.log('Process Mission Data - Final Output:', {\n services: output.missionProcessed.services,\n isArray: Array.isArray(output.missionProcessed.services),\n containsGite: Array.isArray(output.missionProcessed.services) && output.missionProcessed.services.includes('Gite')\n});\n\nreturn output;"
|
|
},
|
|
"name": "Process Mission Data",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-1040,
|
|
600
|
|
],
|
|
"id": "31981488-6ec3-458d-bd38-a83aab699bb5"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "const input = $input.item.json;\nconst binaryData = $input.item.binary;\n\n// Detailed input tracing\nconsole.log(\"Decode Logo Data - Detailed Input Trace:\", {\n // Input structure\n hasInput: !!input,\n inputType: typeof input,\n inputKeys: input ? Object.keys(input) : [],\n \n // Binary data\n hasBinary: !!binaryData,\n binaryType: typeof binaryData,\n binaryKeys: binaryData ? Object.keys(binaryData) : [],\n \n // Mission data\n hasMissionProcessed: !!input?.missionProcessed,\n missionProcessedKeys: input?.missionProcessed ? Object.keys(input.missionProcessed) : [],\n \n // Logo data\n hasLogo: !!input?.missionProcessed?.logo,\n logoType: typeof input?.missionProcessed?.logo,\n logoKeys: input?.missionProcessed?.logo ? Object.keys(input.missionProcessed.logo) : [],\n \n // Headers\n hasHeaders: !!input?.missionOriginal?.headers,\n contentType: input?.missionOriginal?.headers?.['content-type'],\n contentDisposition: input?.missionOriginal?.headers?.['content-disposition']\n});\n\n// Default transparent PNG base64 (1x1 pixel)\nconst DEFAULT_PNG = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=\";\n\n// Helper function to create a valid buffer with detailed logging\nconst createValidBuffer = (data, source) => {\n console.log(`Creating buffer from ${source}:`, {\n dataType: typeof data,\n isBuffer: Buffer.isBuffer(data),\n isString: typeof data === 'string',\n isObject: typeof data === 'object',\n hasData: data?.data ? 'yes' : 'no'\n });\n \n try {\n if (!data) {\n console.log(`${source}: No data provided`);\n return null;\n }\n \n // If it's already a buffer, return it\n if (Buffer.isBuffer(data)) {\n console.log(`${source}: Data is already a buffer`);\n return data;\n }\n \n // If it's a string, try to create a buffer\n if (typeof data === 'string') {\n // Check if it's base64\n if (data.includes(',')) {\n console.log(`${source}: Converting base64 string to buffer`);\n const base64Data = data.split(',')[1];\n return Buffer.from(base64Data, 'base64');\n }\n // Try as raw string\n console.log(`${source}: Converting raw string to buffer`);\n return Buffer.from(data);\n }\n \n // If it's an object with data property\n if (data && typeof data === 'object' && data.data) {\n console.log(`${source}: Converting object with data property`);\n return createValidBuffer(data.data, `${source}.data`);\n }\n \n console.log(`${source}: Could not create buffer from data`);\n return null;\n } catch (e) {\n console.error(`Error creating buffer from ${source}:`, e);\n return null;\n }\n};\n\n// Try to get binary data with detailed logging\nconst getBinaryData = () => {\n // First try: Check raw binary data\n if (binaryData) {\n console.log('Checking raw binary data...');\n const binaryKey = Object.keys(binaryData)[0];\n if (binaryKey && binaryData[binaryKey]?.data) {\n const buffer = createValidBuffer(binaryData[binaryKey].data, 'raw binary');\n if (buffer) {\n console.log('Successfully created buffer from raw binary data');\n return {\n buffer,\n fileName: input.missionOriginal?.headers?.['content-disposition']?.split('filename=')[1] || 'logo.png',\n mimeType: input.missionOriginal?.headers?.['content-type'] || 'image/png'\n };\n }\n }\n }\n \n // Second try: Check mission processed logo\n console.log('Checking mission processed logo...');\n const logo = input?.missionProcessed?.logo || input?.body?.logo || input?.logo;\n if (logo?.data) {\n const buffer = createValidBuffer(logo.data, 'mission logo');\n if (buffer) {\n console.log('Successfully created buffer from mission logo');\n return {\n buffer,\n fileName: logo.name || 'logo.png',\n mimeType: logo.type || 'image/png'\n };\n }\n }\n \n // Third try: Check if input is raw binary\n console.log('Checking if input is raw binary...');\n if (input && typeof input === 'object' && !input.missionProcessed) {\n const buffer = createValidBuffer(input, 'raw input');\n if (buffer) {\n console.log('Successfully created buffer from raw input');\n return {\n buffer,\n fileName: 'logo.png',\n mimeType: 'image/png'\n };\n }\n }\n \n // Fallback to default\n console.log('No valid binary data found, using default PNG');\n return {\n buffer: Buffer.from(DEFAULT_PNG, 'base64'),\n fileName: 'default-logo.png',\n mimeType: 'image/png'\n };\n};\n\n// Get the binary data with all fallbacks\nconst { buffer, fileName, mimeType } = getBinaryData();\n\n// Validate buffer before creating output\nif (!buffer || !Buffer.isBuffer(buffer)) {\n console.error('Invalid buffer created, forcing default');\n const defaultBuffer = Buffer.from(DEFAULT_PNG, 'base64');\n \n return {\n json: {\n ...input,\n fileName: 'default-logo.png',\n mimeType: 'image/png',\n sanitizedName: input?.missionProcessed?.sanitizedName || \"unnamed-mission\",\n logoProcessed: true,\n forcedDefault: true\n },\n binary: {\n data: defaultBuffer\n }\n };\n}\n\n// Create output with both json and binary data in the correct structure\nconst output = {\n json: {\n ...input,\n fileName,\n mimeType,\n sanitizedName: input?.missionProcessed?.sanitizedName || \"unnamed-mission\",\n logoProcessed: true,\n bufferSize: buffer.length\n },\n binary: {\n data: buffer\n }\n};\n\n// Log the output for debugging\nconsole.log(\"Decode Logo Data - Final Output:\", {\n fileName: output.json.fileName,\n mimeType: output.json.mimeType,\n bufferSize: output.json.bufferSize,\n hasBinaryData: !!output.binary?.data,\n binaryDataType: typeof output.binary?.data,\n isBuffer: Buffer.isBuffer(output.binary?.data)\n});\n\n// Ensure binary data is always available\nif (!output.binary || !output.binary.data) {\n console.error('Binary data missing in output, forcing default');\n output.binary = {\n data: Buffer.from(DEFAULT_PNG, 'base64')\n };\n}\n\nreturn output;"
|
|
},
|
|
"name": "Decode Logo Data",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-840,
|
|
480
|
|
],
|
|
"id": "b844080e-a6e9-423b-9556-22af43234952"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Process attachments for S3 upload with improved error handling\nconst input = $input.item.json;\nconst attachments = input?.missionProcessed?.attachments || [];\n\n// Create a default output with a flag indicating if there are attachments\nconst defaultOutput = {\n json: {\n ...input,\n hasAttachments: false,\n attachmentUrls: [], // Initialize empty array for consistency\n skipIntegrations: false // Add flag to control integration flow\n }\n};\n\n// If no attachments or invalid input, return default output and skip S3 node\nif (!input || !Array.isArray(attachments) || attachments.length === 0) {\n console.log('No attachments found or invalid input structure');\n return defaultOutput;\n}\n\n// Process the first attachment to check if it's valid\nlet hasValidAttachments = false;\nlet validAttachments = [];\n\n// Check all attachments for validity\ntry {\n for (const attachment of attachments) {\n if (attachment && attachment.data) {\n // Extract pure base64 (remove data:image/... prefix if present)\n let base64Data = attachment.data;\n if (typeof base64Data === 'string' && base64Data.includes(',')) {\n base64Data = base64Data.split(',')[1];\n }\n \n // If we have valid data, mark as valid and include in valid attachments\n if (base64Data && base64Data.trim() !== '') {\n try {\n // Test if it's valid base64 by creating a buffer\n const testBuffer = Buffer.from(base64Data, 'base64');\n if (testBuffer.length > 0) {\n hasValidAttachments = true;\n validAttachments.push({\n data: base64Data,\n name: attachment.name || `attachment-${validAttachments.length}.png`,\n type: attachment.type || 'application/octet-stream'\n });\n }\n } catch (e) {\n console.log(`Skipping invalid attachment: ${e.message}`);\n // Skip this attachment but continue processing others\n }\n }\n }\n }\n} catch (error) {\n // If any error in the loop, log it but don't fail the workflow\n console.error('Error checking attachment validity:', error);\n}\n\n// If no valid attachments after checking, return the default output\nif (!hasValidAttachments || validAttachments.length === 0) {\n console.log('No valid attachments found after validation');\n return defaultOutput;\n}\n\n// At this point, we know we have at least one valid attachment\n// Prepare the output with attachment info\nreturn {\n json: {\n ...input,\n hasAttachments: true,\n attachmentCount: validAttachments.length,\n attachmentData: validAttachments,\n originalAttachmentsCount: attachments.length,\n skipIntegrations: false // Ensure integrations run\n }\n};"
|
|
},
|
|
"name": "Check Attachments",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-840,
|
|
740
|
|
],
|
|
"id": "44c8a7fd-01f6-460b-8d95-7cdb658b90f5"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"boolean": [
|
|
{
|
|
"value1": "={{ $json.hasAttachments }}",
|
|
"value2": true
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"name": "IF Has Attachments",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-620,
|
|
740
|
|
],
|
|
"id": "4320ba6c-7901-4a66-b7f8-1e58be057508"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Process single attachment for S3 upload with improved error handling\nconst input = $input.item.json;\n\n// Log for debugging\nconsole.log('Process Attachment Data input:', {\n hasInput: !!input,\n hasAttachmentData: !!input?.attachmentData,\n attachmentDataLength: input?.attachmentData?.length || 0,\n attachmentData: input?.attachmentData ? 'present' : 'missing'\n});\n\n// Create array to hold processed attachments\nconst outputs = [];\n\n// Early return if no attachment data to prevent race conditions\nif (!Array.isArray(input?.attachmentData) || input.attachmentData.length === 0) {\n console.log('No valid attachment data found, returning placeholder');\n return [{ \n json: { \n ...input,\n processingFailed: true,\n reason: 'No valid attachments to process'\n } \n }];\n}\n\n// Process each attachment\ninput.attachmentData.forEach((attachment, index) => {\n try {\n if (!attachment || !attachment.data) {\n console.log(`Skipping attachment ${index}: No data`);\n return;\n }\n \n // Extract pure base64 (remove data:image/... prefix if present)\n let base64Data = attachment.data;\n if (typeof base64Data === 'string' && base64Data.includes(',')) {\n base64Data = base64Data.split(',')[1];\n }\n \n // Skip if no valid base64 data\n if (!base64Data || base64Data.trim() === '') {\n console.log(`Skipping attachment ${index}: Empty data`);\n return;\n }\n \n try {\n // Verify the base64 data is valid\n const buffer = Buffer.from(base64Data, 'base64');\n \n if (buffer.length === 0) {\n console.log(`Skipping attachment ${index}: Empty buffer`);\n return;\n }\n \n // Create output for this attachment\n outputs.push({\n json: {\n ...input,\n fileName: attachment.name || `attachment-${index}.${attachment.type?.split('/')[1] || 'bin'}`,\n mimeType: attachment.type || 'application/octet-stream',\n index: index,\n totalAttachments: input.attachmentData.length,\n missionId: input.missionProcessed?.sanitizedName || 'unnamed-mission',\n attachmentProcessed: true\n },\n binary: {\n data: buffer\n }\n });\n \n console.log(`Successfully processed attachment ${index}: ${attachment.name}`);\n } catch (e) {\n console.error(`Failed to create buffer for attachment ${index}:`, e);\n }\n } catch (error) {\n // Skip failed attachments but log the error\n console.error(`Failed to process attachment ${index}:`, error);\n }\n});\n\n// Return processed attachments or a placeholder if none processed\nif (outputs.length > 0) {\n console.log(`Successfully processed ${outputs.length} attachments`);\n return outputs;\n} else {\n console.log('No attachments were successfully processed');\n return [{ \n json: { \n ...input,\n processingFailed: true,\n reason: 'All attachments failed processing'\n } \n }];\n}"
|
|
},
|
|
"name": "Process Attachment Data",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-440,
|
|
680
|
|
],
|
|
"id": "e8eb3c2e-b506-48e0-9d76-f77bf599cb65"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "upload",
|
|
"bucketName": "=missions",
|
|
"fileName": "={{$input.item.json.sanitizedName}}/{{$input.item.json.fileName}}",
|
|
"additionalFields": {
|
|
"acl": "public-read"
|
|
}
|
|
},
|
|
"id": "e06d90e2-09fc-4512-9467-03c617b7fef2",
|
|
"name": "S3 Upload Logo",
|
|
"type": "n8n-nodes-base.s3",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-500,
|
|
480
|
|
],
|
|
"credentials": {
|
|
"s3": {
|
|
"id": "xvSkHfsTBJxzopIj",
|
|
"name": "S3 account 2"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Debug node to ensure binary data is properly structured\nconst input = $input.item;\n\n// Log the full input structure\nconsole.log('Debug - Input structure:', {\n hasJson: !!input.json,\n hasBinary: !!input.binary,\n hasBinaryData: !!input.binary?.data,\n binaryDataType: typeof input.binary?.data,\n isBuffer: Buffer.isBuffer(input.binary?.data),\n jsonKeys: input.json ? Object.keys(input.json) : [],\n binaryKeys: input.binary ? Object.keys(input.binary) : []\n});\n\n// Ensure binary data is properly structured\nif (!input.binary?.data) {\n console.error('No binary data found in input');\n throw new Error('No binary data found in input');\n}\n\n// Return the input unchanged\nreturn input;"
|
|
},
|
|
"name": "Debug Binary Data",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-720,
|
|
480
|
|
],
|
|
"id": "76f45b82-130a-45f1-98b8-eb75013e4f67"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Generate empty attachment result when there are no attachments\nconst input = $input.item.json;\n\n// Return input with empty attachment urls and skip integrations flag\nreturn {\n json: {\n ...input,\n attachmentUrls: [],\n noAttachments: true,\n skipIntegrations: true // Skip integrations for this path\n }\n};"
|
|
},
|
|
"name": "Empty Attachment Result",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-320,
|
|
880
|
|
],
|
|
"id": "ea6c4d9d-e936-4f81-82c8-61a6cc50eb7d"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Process upload results and prepare for integrations\nconst input = $input.item.json;\n\n// Log the input for debugging\nconsole.log('Process Upload Results - Input:', {\n hasInput: !!input,\n hasAttachments: input?.hasAttachments,\n logoUrl: input?.logoUrl,\n attachmentUrls: input?.attachmentUrls,\n skipIntegrations: input?.skipIntegrations\n});\n\n// Determine if we should run integrations\nconst shouldRunIntegrations = !input?.skipIntegrations;\n\n// Create the output with all necessary data\nconst output = {\n ...input,\n logoUrl: input?.logoUrl || '',\n attachmentUrls: Array.isArray(input?.attachmentUrls) ? input.attachmentUrls : [],\n hasAttachments: !!input?.hasAttachments,\n skipIntegrations: !shouldRunIntegrations\n};\n\n// Log the output for debugging\nconsole.log('Process Upload Results - Output:', {\n hasLogoUrl: !!output.logoUrl,\n attachmentUrlsCount: output.attachmentUrls.length,\n hasAttachments: output.hasAttachments,\n skipIntegrations: output.skipIntegrations\n});\n\nreturn { json: output };"
|
|
},
|
|
"name": "Process Upload Results",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-60,
|
|
780
|
|
],
|
|
"id": "8696d145-82db-43b2-8119-66ee438066d4"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.KEYCLOAK_BASE_URL + '/realms/' + $node['Process Mission Data'].json.config.KEYCLOAK_REALM + '/protocol/openid-connect/token' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/x-www-form-urlencoded"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "grant_type",
|
|
"value": "client_credentials"
|
|
},
|
|
{
|
|
"name": "client_id",
|
|
"value": "={{ $node['Process Mission Data'].json.config.KEYCLOAK_CLIENT_ID }}"
|
|
},
|
|
{
|
|
"name": "client_secret",
|
|
"value": "LwgeE1ntADD20OuWC88S3pR0EaO7FtO4"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"allowUnauthorizedCerts": true,
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Get Keycloak Token",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
-80,
|
|
520
|
|
],
|
|
"id": "eb6f6a28-865e-437f-8543-9af850af5ddf",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "const input = $input.item.json;\n\n// Log full input for debugging\nconsole.log('Keycloak response received:', JSON.stringify(input));\n\n// Handle potential errors from Keycloak\nif (input.error || (input.statusCode >= 400 && input.statusCode <= 599)) {\n console.error('Keycloak error detected. Status:', input.statusCode);\n console.error('Error details:', JSON.stringify(input.error || input.body || input));\n \n // If there's a specific error message in the response body, extract it\n let errorMessage = 'Unknown error from Keycloak';\n let errorDetails = '';\n \n try {\n if (input.error?.message) {\n // Try to parse the error message if it's JSON\n if (input.error.message.includes('{\"error\"')) {\n const errorJson = JSON.parse(input.error.message.substring(input.error.message.indexOf('{')));\n errorMessage = errorJson.error || errorMessage;\n errorDetails = errorJson.error_description || '';\n } else {\n errorMessage = input.error.message;\n }\n } else if (typeof input.body === 'object' && input.body.error) {\n errorMessage = input.body.error;\n errorDetails = input.body.error_description || '';\n }\n } catch (e) {\n console.error('Error parsing Keycloak error:', e);\n }\n \n // Return a default object to allow workflow to continue\n return { json: { \n ...input, // Preserve all original data\n access_token: 'ERROR_FETCHING_TOKEN',\n error: errorMessage,\n errorDetails: errorDetails,\n original_error: input.error || input.body || input\n }};\n}\n\n// Extract token from successful response\nconst access_token = input.body?.access_token;\nif (!access_token) {\n console.error('No access token received from Keycloak');\n console.error('Response body:', JSON.stringify(input.body || input));\n \n // Continue with a placeholder token instead of throwing an error\n return { json: { \n ...input, // Preserve all original data\n access_token: 'NO_TOKEN_RECEIVED',\n error: 'Token missing in Keycloak response',\n errorDetails: JSON.stringify(input.body || input)\n }};\n}\n\nconsole.log('Keycloak token received successfully');\n\n// Create new object to ensure ALL input data is preserved and passed through\nconst result = {};\n\n// First, copy ALL properties from the input\nfor (const key in input) {\n if (input.hasOwnProperty(key)) {\n result[key] = input[key];\n }\n}\n\n// Then add/override the access token and ensure critical properties exist\nresult.access_token = access_token;\n\n// Double-check that critical mission data is preserved\nresult.missionProcessed = input.missionProcessed || {};\nresult.missionOriginal = input.missionOriginal || {};\nresult.config = input.config || {};\nresult.logoUrl = input.logoUrl || '';\nresult.publicUrl = input.publicUrl || '';\nresult.attachmentUrls = input.attachmentUrls || [];\n\n// Ensure missionProcessed has all required fields\nif (!result.missionProcessed.sanitizedName) {\n result.missionProcessed.sanitizedName = result.missionProcessed.name ? \n result.missionProcessed.name.toLowerCase().replace(/[^a-z0-9]+/g, '-') : \n `mission-${Date.now()}`;\n}\n\nif (!result.missionProcessed.description) {\n result.missionProcessed.description = 'Mission documentation';\n}\n\nif (!Array.isArray(result.missionProcessed.rocketChatUsernames)) {\n result.missionProcessed.rocketChatUsernames = [];\n}\n\n// Ensure config has all required fields\nif (!result.config.ROCKETCHAT_API_URL) {\n result.config.ROCKETCHAT_API_URL = 'https://parole.slm-lab.net/';\n}\n\nif (!result.config.ROCKETCHAT_AUTH_TOKEN) {\n result.config.ROCKETCHAT_AUTH_TOKEN = 'w91TYgkH-Z67Oz72usYdkW5TZLLRwnre7qyAhp7aHJB';\n}\n\nif (!result.config.ROCKETCHAT_USER_ID) {\n result.config.ROCKETCHAT_USER_ID = 'Tpuww59PJKsrGNQJB';\n}\n\nif (!result.config.OUTLINE_API_URL) {\n result.config.OUTLINE_API_URL = 'https://chapitre.slm-lab.net/api';\n}\n\nif (!result.config.OUTLINE_API_TOKEN) {\n result.config.OUTLINE_API_TOKEN = 'ol_api_tlLlANBfcoJ4l7zA8GOcpduAeL6QyBTcYvEnlN';\n}\n\n// Log the final result for debugging\nconsole.log('Process Token - Final Result:', {\n hasMissionProcessed: !!result.missionProcessed,\n hasConfig: !!result.config,\n sanitizedName: result.missionProcessed.sanitizedName,\n description: result.missionProcessed.description,\n rocketChatUsernames: result.missionProcessed.rocketChatUsernames,\n hasRocketChatConfig: !!(result.config.ROCKETCHAT_API_URL && result.config.ROCKETCHAT_AUTH_TOKEN && result.config.ROCKETCHAT_USER_ID),\n hasOutlineConfig: !!(result.config.OUTLINE_API_URL && result.config.OUTLINE_API_TOKEN)\n});\n\n// Return the enhanced object\nreturn { json: result };"
|
|
},
|
|
"name": "Process Token",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
160,
|
|
680
|
|
],
|
|
"id": "08e00de9-c887-428d-b15a-b04d7c27bb32",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Debug function to check what data is flowing to RocketChat and Documentation\nconst input = $input.item.json;\n\n// Enhanced logging for service nodes\nconsole.log('DEBUG - Detailed Service Data Flow:', {\n // Mission Data\n 'missionProcessed exists': !!input.missionProcessed,\n 'missionProcessed.sanitizedName': input.missionProcessed?.sanitizedName,\n 'missionProcessed.name': input.missionProcessed?.name,\n \n // RocketChat specific\n 'rocketChatUsernames': input.missionProcessed?.rocketChatUsernames,\n 'rocketChatUsernames length': input.missionProcessed?.rocketChatUsernames?.length,\n 'rocketChatUsernames type': typeof input.missionProcessed?.rocketChatUsernames,\n 'ROCKETCHAT_API_URL': input.config?.ROCKETCHAT_API_URL,\n 'ROCKETCHAT_AUTH_TOKEN exists': !!input.config?.ROCKETCHAT_AUTH_TOKEN,\n 'ROCKETCHAT_USER_ID exists': !!input.config?.ROCKETCHAT_USER_ID,\n \n // Documentation specific\n 'OUTLINE_API_URL': input.config?.OUTLINE_API_URL,\n 'OUTLINE_API_TOKEN exists': !!input.config?.OUTLINE_API_TOKEN,\n 'mission description': input.missionProcessed?.description,\n \n // Common data\n 'logoUrl': input.logoUrl,\n 'config exists': !!input.config,\n 'keycloak token': input.access_token?.substring(0, 10) + '...',\n 'LEANTIME_API_URL': input.config?.LEANTIME_API_URL\n});\n\n// Create a copy of the input data to ensure we don't modify the original\nconst output = { ...input };\n\n// Ensure missionProcessed exists and has required fields\noutput.missionProcessed = output.missionProcessed || {};\n\n// Ensure sanitizedName exists\nif (!output.missionProcessed.sanitizedName) {\n output.missionProcessed.sanitizedName = output.missionProcessed.name ? \n output.missionProcessed.name.toLowerCase().replace(/[^a-z0-9]+/g, '-') : \n `mission-${Date.now()}`;\n}\n\n// Ensure description exists\nif (!output.missionProcessed.description) {\n output.missionProcessed.description = 'Mission documentation';\n}\n\n// Ensure rocketChatUsernames is an array\nif (!Array.isArray(output.missionProcessed.rocketChatUsernames)) {\n output.missionProcessed.rocketChatUsernames = [];\n}\n\n// Ensure config exists and has required fields\noutput.config = output.config || {};\n\n// Ensure RocketChat config exists\nif (!output.config.ROCKETCHAT_API_URL) {\n output.config.ROCKETCHAT_API_URL = 'https://parole.slm-lab.net/';\n}\nif (!output.config.ROCKETCHAT_AUTH_TOKEN) {\n output.config.ROCKETCHAT_AUTH_TOKEN = 'w91TYgkH-Z67Oz72usYdkW5TZLLRwnre7qyAhp7aHJB';\n}\nif (!output.config.ROCKETCHAT_USER_ID) {\n output.config.ROCKETCHAT_USER_ID = 'Tpuww59PJKsrGNQJB';\n}\n\n// Ensure Documentation config exists\nif (!output.config.OUTLINE_API_URL) {\n output.config.OUTLINE_API_URL = 'https://chapitre.slm-lab.net/api';\n}\nif (!output.config.OUTLINE_API_TOKEN) {\n output.config.OUTLINE_API_TOKEN = 'ol_api_tlLlANBfcoJ4l7zA8GOcpduAeL6QyBTcYvEnlN';\n}\n\n// Log the final output for debugging\nconsole.log('Debug Service Data - Final Output:', {\n hasMissionProcessed: !!output.missionProcessed,\n hasConfig: !!output.config,\n sanitizedName: output.missionProcessed.sanitizedName,\n description: output.missionProcessed.description,\n rocketChatUsernames: output.missionProcessed.rocketChatUsernames,\n hasRocketChatConfig: !!(output.config.ROCKETCHAT_API_URL && output.config.ROCKETCHAT_AUTH_TOKEN && output.config.ROCKETCHAT_USER_ID),\n hasOutlineConfig: !!(output.config.OUTLINE_API_URL && output.config.OUTLINE_API_TOKEN)\n});\n\n// Return the enhanced object\nreturn { json: output };"
|
|
},
|
|
"name": "Debug Service Data",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
160,
|
|
880
|
|
],
|
|
"id": "0669c514-4f7e-44ad-a1a3-a6b9518b3b71"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"string": [
|
|
{
|
|
"value1": "={{ $json.missionProcessed.services }}",
|
|
"operation": "contains",
|
|
"value2": "Gite"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"name": "IF Needs Git Repository",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
140,
|
|
500
|
|
],
|
|
"id": "320f5719-682b-4926-ba20-f7ebc0b15967"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.GITEA_API_URL + '/user/repos' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ 'token ' + $node['Process Mission Data'].json.config.GITEA_API_TOKEN }}"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "name",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}"
|
|
},
|
|
{
|
|
"name": "private",
|
|
"value": "={{ true }}"
|
|
},
|
|
{
|
|
"name": "auto_init",
|
|
"value": "={{ true }}"
|
|
},
|
|
{
|
|
"name": "avatar_url",
|
|
"value": "={{ $node['Process Upload Results'].json.logoUrl }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"allowUnauthorizedCerts": true,
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Create Git Repository",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
460,
|
|
460
|
|
],
|
|
"id": "6b7f99e2-8dcc-48b0-8ef5-dcaaa3acf1ad",
|
|
"continueOnFail": true,
|
|
"errorMessage": "={{ $json.error?.message || 'Unknown error creating Git repository' }}"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.LEANTIME_API_URL + '/api/jsonrpc' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "X-API-Key",
|
|
"value": "={{ $node['Process Mission Data'].json.config.LEANTIME_API_TOKEN }}"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "method",
|
|
"value": "leantime.rpc.Projects.Projects.addProject"
|
|
},
|
|
{
|
|
"name": "jsonrpc",
|
|
"value": "2.0"
|
|
},
|
|
{
|
|
"name": "id",
|
|
"value": "1"
|
|
},
|
|
{
|
|
"name": "params",
|
|
"value": "={{ { values: { name: $node['Process Mission Data'].json.missionProcessed.name, clientId: $node['Process Mission Data'].json.missionProcessed.clientId, details: $node['Process Mission Data'].json.missionProcessed.intention, type: 'project', start: $node['Process Mission Data'].json.missionProcessed.startDate, end: $node['Process Mission Data'].json.missionProcessed.endDate, status: 'open', psettings: 'restricted', avatar: $node['Process Upload Results'].json.logoUrl } } }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"allowUnauthorizedCerts": true,
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Create Leantime Project",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
460,
|
|
620
|
|
],
|
|
"id": "c2530d9b-4733-49de-bead-c4d3af09916d",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.OUTLINE_API_URL + '/collections.create' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Authorization",
|
|
"value": "={{ 'Bearer ' + $node['Process Mission Data'].json.config.OUTLINE_API_TOKEN }}"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "name",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}"
|
|
},
|
|
{
|
|
"name": "description",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.description || 'Mission documentation' }}"
|
|
},
|
|
{
|
|
"name": "color",
|
|
"value": "#4f46e5"
|
|
},
|
|
{
|
|
"name": "permission",
|
|
"value": "read"
|
|
},
|
|
{
|
|
"name": "private",
|
|
"value": "={{ true }}"
|
|
},
|
|
{
|
|
"name": "avatarUrl",
|
|
"value": "={{ $node['Process Upload Results'].json.logoUrl }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"allowUnauthorizedCerts": true,
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Create Documentation Collection",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
480,
|
|
940
|
|
],
|
|
"id": "77a70da5-dc7f-4c48-9836-6d1d2064be1b",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.ROCKETCHAT_API_URL + '/api/v1/channels.create' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "X-Auth-Token",
|
|
"value": "={{ $node['Process Mission Data'].json.config.ROCKETCHAT_AUTH_TOKEN }}"
|
|
},
|
|
{
|
|
"name": "X-User-Id",
|
|
"value": "={{ $node['Process Mission Data'].json.config.ROCKETCHAT_USER_ID }}"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "name",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}"
|
|
},
|
|
{
|
|
"name": "members",
|
|
"value": "={{ Array.isArray($node['Process Mission Data'].json.missionProcessed.rocketChatUsernames) ? $node['Process Mission Data'].json.missionProcessed.rocketChatUsernames : [] }}"
|
|
},
|
|
{
|
|
"name": "readOnly",
|
|
"value": "false"
|
|
},
|
|
{
|
|
"name": "avatarUrl",
|
|
"value": "={{ $node['Process Upload Results'].json.logoUrl }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"allowUnauthorizedCerts": true,
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Create RocketChat Channel",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
460,
|
|
760
|
|
],
|
|
"id": "0bbfdeaa-fc6e-4f00-b85e-fe994bef1bc9",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Combine results from all integrations with better error handling\ntry {\n // Defensively get results from each service node\n let gitRepoResult = {};\n let leantimeResult = {};\n let docCollectionResult = {};\n let rocketChatResult = {};\n let uploadResults = {};\n let keycloakToken = {};\n \n try { \n gitRepoResult = $node['Create Git Repository']?.json || {};\n console.log('Git repo node executed successfully'); \n } catch (e) { \n console.log('Git repo node not executed yet, continuing anyway'); \n }\n \n try { \n leantimeResult = $node['Create Leantime Project']?.json || {};\n console.log('Leantime node executed successfully');\n } catch (e) { \n console.log('Leantime node not executed yet, continuing anyway'); \n }\n \n try { \n docCollectionResult = $node['Create Documentation Collection']?.json || {};\n console.log('Documentation node executed successfully');\n } catch (e) { \n console.log('Documentation node not executed yet, continuing anyway'); \n }\n \n try { \n rocketChatResult = $node['Create RocketChat Channel']?.json || {};\n console.log('RocketChat node executed successfully');\n } catch (e) { \n console.log('RocketChat node not executed yet, continuing anyway'); \n }\n \n try { \n uploadResults = $node['Process Upload Results']?.json || {};\n console.log('Upload Results available');\n } catch (e) { \n console.log('Upload Results not available, continuing anyway'); \n }\n \n try { \n keycloakToken = $node['Process Token']?.json || {};\n console.log('Keycloak token available');\n } catch (e) { \n console.log('Keycloak token not available, continuing anyway'); \n }\n \n // Track which resources were actually created vs already existed\n const resourceStatus = {\n gitRepo: false,\n leantimeProject: false,\n docCollection: false,\n rocketChatChannel: false\n };\n \n // Process Git repository result\n if (gitRepoResult.error?.includes('already exists')) {\n console.log('Git repository already exists');\n gitRepoResult = { exists: true };\n } else if (gitRepoResult.body?.body?.html_url) {\n resourceStatus.gitRepo = true;\n console.log('Git repository created successfully with URL:', gitRepoResult.body.body.html_url);\n }\n \n // Process Leantime project result - Updated to check for array result\n if (leantimeResult.error?.includes('already exists')) {\n console.log('Leantime project already exists');\n leantimeResult = { exists: true };\n } else if (leantimeResult.body?.result && Array.isArray(leantimeResult.body.result) && leantimeResult.body.result.length > 0) {\n resourceStatus.leantimeProject = true;\n console.log('Leantime project created successfully with ID:', leantimeResult.body.result[0]);\n }\n \n // Process Documentation collection result - Updated to check for data wrapper\n if (docCollectionResult.error?.includes('already exists')) {\n console.log('Documentation collection already exists');\n docCollectionResult = { exists: true };\n } else if (docCollectionResult.body?.data?.id) {\n resourceStatus.docCollection = true;\n console.log('Documentation collection created successfully with ID:', docCollectionResult.body.data.id);\n }\n \n // Process RocketChat channel result\n if (rocketChatResult.error?.includes('error-duplicate-channel-name')) {\n console.log('RocketChat channel already exists');\n rocketChatResult = { exists: true };\n } else if (rocketChatResult.body?.channel?._id) {\n resourceStatus.rocketChatChannel = true;\n }\n \n // Gather information about what executed\n const executedNodes = [];\n if (Object.keys(gitRepoResult).length > 0) executedNodes.push('Git');\n if (Object.keys(leantimeResult).length > 0) executedNodes.push('Leantime');\n if (Object.keys(docCollectionResult).length > 0) executedNodes.push('Documentation');\n if (Object.keys(rocketChatResult).length > 0) executedNodes.push('RocketChat');\n \n console.log(`Executed nodes (${executedNodes.length}): ${executedNodes.join(', ')}`);\n \n // Handle empty results with empty objects to prevent errors\n const results = {\n gitRepo: gitRepoResult.error ? { error: gitRepoResult.error.message || 'Git repository creation failed' } : (gitRepoResult.body?.body || gitRepoResult.body || gitRepoResult || {}),\n leantimeProject: leantimeResult.error ? { error: leantimeResult.error.message || 'Leantime project creation failed' } : (leantimeResult.body || leantimeResult || {}),\n docCollection: docCollectionResult.error ? { error: docCollectionResult.error.message || 'Documentation collection creation failed' } : (docCollectionResult.body || docCollectionResult || {}),\n rocketChatChannel: rocketChatResult.error ? { error: rocketChatResult.error.message || 'RocketChat channel creation failed' } : (rocketChatResult.body || rocketChatResult || {}),\n uploadResults: uploadResults || {},\n keycloakToken: keycloakToken || {},\n executedNodes: executedNodes,\n resourceStatus: resourceStatus\n };\n \n // Log key details for debugging\n console.log('Git repo HTML URL:', results.gitRepo?.html_url || 'not available');\n console.log('Leantime project ID:', results.leantimeProject?.result?.[0] || 'not available');\n console.log('Documentation ID:', results.docCollection?.data?.id || 'not available');\n console.log('RocketChat channel ID:', results.rocketChatChannel?.channel?._id || 'not available');\n \n return results;\n} catch (error) {\n console.error('Error in Combine Results:', error);\n // Return minimal object to allow workflow to continue\n return {\n error: `Error combining results: ${error.message}`,\n gitRepo: {},\n leantimeProject: {},\n docCollection: {},\n rocketChatChannel: {}\n };\n}"
|
|
},
|
|
"name": "Combine Results",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
660,
|
|
600
|
|
],
|
|
"id": "dbccf675-ef1a-4b2f-babc-7e59b9e6a216",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.MISSION_API_URL + '/mission-created' }}",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "x-api-key",
|
|
"value": "={{ $node['Process Mission Data'].json.config.N8N_API_KEY }}"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "name",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.name }}"
|
|
},
|
|
{
|
|
"name": "niveau",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.niveau || 'default' }}"
|
|
},
|
|
{
|
|
"name": "intention",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.intention }}"
|
|
},
|
|
{
|
|
"name": "description",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.description }}"
|
|
},
|
|
{
|
|
"name": "logo",
|
|
"value": "={{ $node['Process Upload Results'].json.logoUrl }}"
|
|
},
|
|
{
|
|
"name": "attachments",
|
|
"value": "={{ $node['Process Upload Results'].json.attachmentUrls }}"
|
|
},
|
|
{
|
|
"name": "gitRepoUrl",
|
|
"value": "={{ $node['Combine Results'].json.gitRepo?.html_url || '' }}"
|
|
},
|
|
{
|
|
"name": "leantimeProjectId",
|
|
"value": "={{ $node['Combine Results'].json.leantimeProject?.result?.[0] || '' }}"
|
|
},
|
|
{
|
|
"name": "documentationCollectionId",
|
|
"value": "={{ $node['Combine Results'].json.docCollection?.data?.id || '' }}"
|
|
},
|
|
{
|
|
"name": "rocketchatChannelId",
|
|
"value": "={{ $node['Combine Results'].json.rocketChatChannel?.channel?._id || '' }}"
|
|
},
|
|
{
|
|
"name": "donneurDOrdre",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.donneurDOrdre || 'default' }}"
|
|
},
|
|
{
|
|
"name": "projection",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.projection || 'default' }}"
|
|
},
|
|
{
|
|
"name": "missionType",
|
|
"value": "={{ $node['Process Mission Data'].json.missionProcessed.missionType || 'default' }}"
|
|
},
|
|
{
|
|
"name": "creatorId",
|
|
"value": "={{ $node['Process Mission Data'].json.creatorId }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Save Mission To API",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
820,
|
|
600
|
|
],
|
|
"id": "5b4557b7-3559-4ce4-80a2-179bc3180ac8"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Defensive access to all nodes\nconst missionData = $node['Process Mission Data']?.json?.missionProcessed || {};\nconst integrationResults = $node['Combine Results']?.json || {};\nconst saveMissionResult = $node['Save Mission To API']?.json || {};\nconst errors = [];\nconst warnings = [];\n\n// Check for actual errors vs expected failures\nif (saveMissionResult.error) {\n errors.push(`Failed to save mission: ${saveMissionResult.error.message || 'Unknown error'}`);\n}\n\n// Track which resources were actually created vs already existed\nconst resourceStatus = {\n gitRepo: false,\n leantimeProject: false,\n docCollection: false,\n rocketChatChannel: false\n};\n\n// Check Git repository\nif (integrationResults.gitRepo?.html_url) {\n resourceStatus.gitRepo = true;\n console.log('Git repository created successfully');\n} else if (integrationResults.gitRepo?.error?.includes('already exists')) {\n console.log('Git repository already exists, this is expected');\n warnings.push('Git repository already exists');\n} else if (integrationResults.gitRepo?.error) {\n errors.push(`Git repository creation failed: ${integrationResults.gitRepo.error}`);\n} else {\n errors.push('Git repository creation failed: Unknown error');\n}\n\n// Check Leantime project\nif (integrationResults.leantimeProject?.result?.[0]) {\n resourceStatus.leantimeProject = true;\n console.log('Leantime project created successfully');\n} else if (integrationResults.leantimeProject?.error?.includes('already exists')) {\n console.log('Leantime project already exists, this is expected');\n warnings.push('Leantime project already exists');\n} else if (integrationResults.leantimeProject?.error) {\n errors.push(`Leantime project creation failed: ${integrationResults.leantimeProject.error}`);\n} else {\n errors.push('Leantime project creation failed: Unknown error');\n}\n\n// Check RocketChat channel\nif (integrationResults.rocketChatChannel?.channel?._id) {\n resourceStatus.rocketChatChannel = true;\n console.log('RocketChat channel created successfully');\n} else if (integrationResults.rocketChatChannel?.error?.includes('error-duplicate-channel-name')) {\n console.log('RocketChat channel already exists, this is expected');\n warnings.push('RocketChat channel already exists');\n} else if (integrationResults.rocketChatChannel?.error) {\n errors.push(`RocketChat channel creation failed: ${integrationResults.rocketChatChannel.error}`);\n} else {\n errors.push('RocketChat channel creation failed: Unknown error');\n}\n\n// Check Documentation collection\nif (integrationResults.docCollection?.data?.id) {\n resourceStatus.docCollection = true;\n console.log('Documentation collection created successfully');\n} else if (integrationResults.docCollection?.error?.includes('already exists')) {\n console.log('Documentation collection already exists, this is expected');\n warnings.push('Documentation collection already exists');\n} else if (integrationResults.docCollection?.error) {\n errors.push(`Documentation collection creation failed: ${integrationResults.docCollection.error}`);\n} else {\n errors.push('Documentation collection creation failed: Unknown error');\n}\n\n// Check if any critical resources failed to create\nconst criticalFailures = errors.filter(error => \n !error.includes('already exists') && \n !error.includes('expected')\n);\n\n// If the mission was successfully saved, consider it a success even if some resources already exist\nconst success = saveMissionResult.body?.message === 'Mission updated successfully' || \n saveMissionResult.body?.message === 'Mission created successfully';\n\n// Determine the final status\nconst status = criticalFailures.length > 0 ? 'error' : \n warnings.length > 0 ? 'warning' : \n 'success';\n\nconst output = {\n success: success && criticalFailures.length === 0,\n status,\n error: errors.length > 0 ? errors.join('; ') : null,\n errors,\n warnings,\n missionData,\n integrationResults,\n saveMissionResult,\n resourceStatus,\n message: status === 'success' ? \n 'Mission integration complete: All systems updated successfully' : \n status === 'warning' ? \n `Mission integration complete with warnings: ${warnings.join('; ')}` : \n `Mission integration failed: ${errors.join('; ')}`\n};\n\n// Log the final status\nconsole.log('Process Results - Final Status:', {\n success: output.success,\n status: output.status,\n errors: output.errors.length,\n warnings: output.warnings.length,\n resourceStatus\n});\n\nreturn output;"
|
|
},
|
|
"name": "Process Results",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1000,
|
|
600
|
|
],
|
|
"id": "88c3f621-9d42-45d0-8231-8f7ac0186f0f"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ $node[\"Process Results\"].json }}",
|
|
"options": {}
|
|
},
|
|
"name": "Respond To Webhook",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1220,
|
|
600
|
|
],
|
|
"id": "446ed359-ebc3-4954-b115-9c5dca73747c"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "upload",
|
|
"bucketName": "=missions",
|
|
"fileName": "={{$input.item.json.missionId}}/attachments/{{$input.item.json.fileName}}",
|
|
"additionalFields": {
|
|
"acl": "public-read"
|
|
}
|
|
},
|
|
"name": "S3 Upload Attachments",
|
|
"type": "n8n-nodes-base.s3",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-280,
|
|
680
|
|
],
|
|
"id": "8c233d82-9495-45de-9ffc-aed5faa703b8",
|
|
"credentials": {
|
|
"s3": {
|
|
"id": "xvSkHfsTBJxzopIj",
|
|
"name": "S3 account 2"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"httpMethod": "POST",
|
|
"path": "mission-created",
|
|
"responseMode": "lastNode",
|
|
"responseData": "allEntries",
|
|
"options": {}
|
|
},
|
|
"name": "Mission Created Webhook",
|
|
"type": "n8n-nodes-base.webhook",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-1320,
|
|
600
|
|
],
|
|
"webhookId": "mission-created",
|
|
"id": "ded0bff4-be16-4e88-991b-47e85120de29"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"boolean": [
|
|
{
|
|
"value1": "={{ $json.skipIntegrations }}",
|
|
"value2": false
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"name": "IF Run Integrations",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
140,
|
|
500
|
|
],
|
|
"id": "320f5719-682b-4926-ba20-f7ebc0b15967"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Merge paths after Process Upload Results\nconst input = $input.item.json;\n\n// Log the input for debugging\nconsole.log('Merge Paths - Input:', {\n hasInput: !!input,\n hasAttachments: input?.hasAttachments,\n skipIntegrations: input?.skipIntegrations,\n logoUrl: input?.logoUrl,\n attachmentUrls: input?.attachmentUrls,\n hasMissionProcessed: !!input?.missionProcessed,\n hasConfig: !!input?.config\n});\n\n// Get mission data from Process Mission Data node\nconst missionData = $node['Process Mission Data']?.json || {};\n\n// Ensure we have all necessary data\nconst output = {\n ...input,\n // Ensure these fields exist even if they weren't in the input\n logoUrl: input?.logoUrl || '',\n attachmentUrls: Array.isArray(input?.attachmentUrls) ? input.attachmentUrls : [],\n hasAttachments: !!input?.hasAttachments,\n skipIntegrations: !!input?.skipIntegrations,\n // Add mission data from Process Mission Data node\n missionProcessed: {\n ...missionData.missionProcessed,\n name: missionData.missionProcessed?.name || input?.missionProcessed?.name || 'Unnamed Mission',\n sanitizedName: missionData.missionProcessed?.sanitizedName || input?.missionProcessed?.sanitizedName || 'unnamed-mission',\n intention: missionData.missionProcessed?.intention || input?.missionProcessed?.intention || '',\n description: missionData.missionProcessed?.description || input?.missionProcessed?.description || 'Mission documentation',\n startDate: missionData.missionProcessed?.startDate || input?.missionProcessed?.startDate || new Date().toISOString().split('T')[0],\n endDate: missionData.missionProcessed?.endDate || input?.missionProcessed?.endDate || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],\n missionType: missionData.missionProcessed?.missionType || input?.missionProcessed?.missionType || 'default',\n guardians: missionData.missionProcessed?.guardians || input?.missionProcessed?.guardians || {},\n volunteers: Array.isArray(missionData.missionProcessed?.volunteers) ? missionData.missionProcessed.volunteers : (Array.isArray(input?.missionProcessed?.volunteers) ? input.missionProcessed.volunteers : []),\n profils: Array.isArray(missionData.missionProcessed?.profils) ? missionData.missionProcessed.profils : (Array.isArray(input?.missionProcessed?.profils) ? input.missionProcessed.profils : []),\n services: Array.isArray(missionData.missionProcessed?.services) ? missionData.missionProcessed.services : (Array.isArray(input?.missionProcessed?.services) ? input.missionProcessed.services : []),\n clientId: missionData.missionProcessed?.clientId || input?.missionProcessed?.clientId || 2,\n rocketChatUsernames: Array.isArray(missionData.missionProcessed?.rocketChatUsernames) ? missionData.missionProcessed.rocketChatUsernames : (Array.isArray(input?.missionProcessed?.rocketChatUsernames) ? input.missionProcessed.rocketChatUsernames : [])\n },\n config: {\n ...missionData.config,\n GITEA_API_URL: missionData.config?.GITEA_API_URL || input?.config?.GITEA_API_URL || 'https://gite.slm-lab.net/api/v1',\n GITEA_API_TOKEN: missionData.config?.GITEA_API_TOKEN || input?.config?.GITEA_API_TOKEN || '310645d564cbf752be1fe3b42582a3d5f5d0bddd',\n GITEA_OWNER: missionData.config?.GITEA_OWNER || input?.config?.GITEA_OWNER || 'alma',\n LEANTIME_API_URL: missionData.config?.LEANTIME_API_URL || input?.config?.LEANTIME_API_URL || 'https://agilite.slm-lab.net',\n LEANTIME_API_TOKEN: missionData.config?.LEANTIME_API_TOKEN || input?.config?.LEANTIME_API_TOKEN || 'lt_lsdShQdoYHaPUWuL07XZR1Rf3GeySsIs_UDlll3VJPk5EwAuILpMC4BwzJ9MZFRrb',\n ROCKETCHAT_API_URL: missionData.config?.ROCKETCHAT_API_URL || input?.config?.ROCKETCHAT_API_URL || 'https://parole.slm-lab.net/',\n ROCKETCHAT_AUTH_TOKEN: missionData.config?.ROCKETCHAT_AUTH_TOKEN || input?.config?.ROCKETCHAT_AUTH_TOKEN || 'w91TYgkH-Z67Oz72usYdkW5TZLLRwnre7qyAhp7aHJB',\n ROCKETCHAT_USER_ID: missionData.config?.ROCKETCHAT_USER_ID || input?.config?.ROCKETCHAT_USER_ID || 'Tpuww59PJKsrGNQJB',\n OUTLINE_API_URL: missionData.config?.OUTLINE_API_URL || input?.config?.OUTLINE_API_URL || 'https://chapitre.slm-lab.net/api',\n OUTLINE_API_TOKEN: missionData.config?.OUTLINE_API_TOKEN || input?.config?.OUTLINE_API_TOKEN || 'ol_api_tlLlANBfcoJ4l7zA8GOcpduAeL6QyBTcYvEnlN',\n MISSION_API_URL: missionData.config?.MISSION_API_URL || input?.config?.MISSION_API_URL || 'https://hub.slm-lab.net',\n N8N_API_KEY: missionData.config?.N8N_API_KEY || input?.config?.N8N_API_KEY || 'LwgeE1ntADD20OuWC88S3pR0EaO7FtO4'\n },\n creatorId: missionData.creatorId || input?.creatorId\n};\n\n// Log the output for debugging\nconsole.log('Merge Paths - Output:', {\n hasLogoUrl: !!output.logoUrl,\n attachmentUrlsCount: output.attachmentUrls.length,\n hasAttachments: output.hasAttachments,\n skipIntegrations: output.skipIntegrations,\n hasMissionProcessed: !!output.missionProcessed,\n hasConfig: !!output.config,\n missionName: output.missionProcessed?.name,\n missionType: output.missionProcessed?.missionType,\n services: output.missionProcessed?.services\n});\n\nreturn { json: output };"
|
|
},
|
|
"name": "Merge Paths",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
0,
|
|
600
|
|
],
|
|
"id": "merge-paths-node"
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Process Mission Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Decode Logo Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Decode Logo Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Debug Binary Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Debug Binary Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "S3 Upload Logo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"S3 Upload Logo": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Upload Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process Upload Results": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Check Attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Check Attachments": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "IF Has Attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"IF Has Attachments": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Attachment Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Empty Attachment Result",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process Attachment Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "S3 Upload Attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"S3 Upload Attachments": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Get Keycloak Token",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Empty Attachment Result": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Get Keycloak Token",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Get Keycloak Token": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Token",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process Token": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Debug Service Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Debug Service Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Merge Paths",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Merge Paths": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "IF Run Integrations",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"IF Run Integrations": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "IF Needs Git Repository",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Combine Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"IF Needs Git Repository": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create Git Repository",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "Create Leantime Project",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create Git Repository": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create Leantime Project",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create Leantime Project": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create Documentation Collection",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create Documentation Collection": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Create RocketChat Channel",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Create RocketChat Channel": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Combine Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Combine Results": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Save Mission To API",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Save Mission To API": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process Results": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Respond To Webhook",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Mission Created Webhook": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Mission Data",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": false,
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"versionId": "850a95bd-fd96-4626-af02-56d489a194e5",
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true,
|
|
"instanceId": "575d8de48bd511243817deebddae0cc97d73be64c6c4737e5d4e9caddec881d8"
|
|
},
|
|
"id": "CAO29zOiTqMcscxO",
|
|
"tags": []
|
|
} |