933 lines
60 KiB
JSON
933 lines
60 KiB
JSON
{
|
|
"name": "My workflow 38",
|
|
"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});\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-${Date.now()}`,\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 };\n}\n\n// Continue with existing JSON processing\nconst sanitizeName = (name) => {\n if (!name || typeof name !== \"string\") return \"unnamed-mission\";\n const timestamp = new Date().getTime();\n const uniqueSuffix = `-${timestamp}`;\n const result = 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 return result + uniqueSuffix;\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};\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\nreturn output;"
|
|
},
|
|
"name": "Process Mission Data",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
-1040,
|
|
600
|
|
],
|
|
"id": "4d14c453-908d-4294-8af8-7ae4786e99dd"
|
|
},
|
|
{
|
|
"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": "328a5788-fd96-42b8-8cf3-ebc3ab51dcdf"
|
|
},
|
|
{
|
|
"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 }\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 }\n};"
|
|
},
|
|
"name": "Check Attachments",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-840,
|
|
740
|
|
],
|
|
"id": "06dd593a-0905-41fd-a833-dd7a1bfd5cfa"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"boolean": [
|
|
{
|
|
"value1": "={{ $json.hasAttachments }}",
|
|
"value2": true
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"name": "IF Has Attachments",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-620,
|
|
740
|
|
],
|
|
"id": "2ae10c1a-3f77-4614-9912-33a5ab16206f"
|
|
},
|
|
{
|
|
"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": "b6010989-f0d7-4ce3-8ac9-fd87a99d06af"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "upload",
|
|
"bucketName": "=missions",
|
|
"fileName": "={{$input.item.json.sanitizedName}}/{{$input.item.json.fileName}}",
|
|
"additionalFields": {
|
|
"acl": "public-read"
|
|
}
|
|
},
|
|
"id": "d2fa51d1-dcc7-463d-a1b7-b75911da28d6",
|
|
"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": "bc8f77df-baad-43db-a0b7-992401913aa5"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Generate empty attachment result when there are no attachments\nconst input = $input.item.json;\n\n// Return input with empty attachment urls\nreturn {\n json: {\n ...input,\n attachmentUrls: [],\n noAttachments: true\n }\n};"
|
|
},
|
|
"name": "Empty Attachment Result",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-320,
|
|
880
|
|
],
|
|
"id": "de904a5f-06c7-41ef-be2d-e210f3cafc68"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"functionCode": "// Combine file upload results with improved error handling\nconst input = $input.item.json;\n\n// Debug log\nconsole.log('Process Upload Results received input');\n\n// Defensive access to logo result\nlet logoResult;\ntry {\n logoResult = $node['S3 Upload Logo']?.json || {};\n console.log('Logo upload result accessed successfully');\n} catch (e) {\n console.error('Error accessing logo upload result:', e);\n logoResult = {};\n}\n\n// Check if we have a valid logo URL\nlet logoUrl = '';\nif (logoResult && logoResult.Location) {\n logoUrl = logoResult.Location;\n console.log('Found logo URL:', logoUrl);\n} else if (logoResult && logoResult.error) {\n console.error('Logo upload error:', logoResult.error);\n} else {\n console.log('No logo URL found in upload result');\n}\n\n// Initialize empty attachment URLs array\nlet attachmentUrls = [];\n\n// Handle attachments based on whether they exist\nif (input.hasAttachments === true) {\n try {\n // Try to get results from S3 Upload Attachments node\n const attachmentsResults = $node['S3 Upload Attachments']?.json;\n console.log('Attachment results accessed successfully');\n \n if (attachmentsResults) {\n if (Array.isArray(attachmentsResults)) {\n // Filter out any undefined or null values\n attachmentUrls = attachmentsResults\n .filter(result => result && result.Location)\n .map(result => result.Location);\n console.log(`Processed ${attachmentUrls.length} attachment URLs from array`);\n } else if (attachmentsResults.Location) {\n // Single attachment case\n attachmentUrls = [attachmentsResults.Location];\n console.log('Processed single attachment URL');\n }\n } else {\n console.log('No attachment results found');\n }\n } catch (error) {\n console.error('Error processing attachment results:', error.message);\n }\n} else {\n // If no attachments, use empty array\n console.log('No attachments to process');\n attachmentUrls = input.attachmentUrls || [];\n}\n\n// Create result object with necessary data for downstream nodes\nconst result = {\n ...input,\n logoUrl: logoUrl,\n attachmentUrls: attachmentUrls,\n publicUrl: logoUrl, // For backward compatibility\n logoProcessed: !!logoUrl,\n attachmentsProcessed: attachmentUrls.length > 0,\n processingTimestamp: new Date().toISOString()\n};\n\n// Ensure critical properties exist\nif (!result.missionProcessed) {\n console.log('Restoring missionProcessed from input');\n result.missionProcessed = input.missionProcessed || {};\n}\n\nif (!result.missionOriginal) {\n console.log('Restoring missionOriginal from input');\n result.missionOriginal = input.missionOriginal || {};\n}\n\nif (!result.config) {\n console.log('Restoring config from input');\n result.config = input.config || {};\n}\n\nconsole.log('Process Upload Results completed');\nreturn { json: result };"
|
|
},
|
|
"name": "Process Upload Results",
|
|
"type": "n8n-nodes-base.function",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
-60,
|
|
780
|
|
],
|
|
"id": "e8470228-a671-43c0-a5d6-0ed097832b0e"
|
|
},
|
|
{
|
|
"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": "5a4cb717-cd3a-4c1a-9d49-ca660c724490",
|
|
"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": "c89254b0-ff16-4d33-8b23-cd084f268afe",
|
|
"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": "70b8b15b-c8d8-4ba0-8016-323c5650352e"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"string": [
|
|
{
|
|
"value1": "={{ Array.isArray($node['Process Mission Data'].json.missionProcessed.services) ? $node['Process Mission Data'].json.missionProcessed.services.includes('Gite') || $node['Process Mission Data'].json.missionProcessed.services.includes('Calcul') : false }}",
|
|
"value2": "true"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"name": "IF Needs Git Repository",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
140,
|
|
500
|
|
],
|
|
"id": "c1f5c793-d0c7-40c6-8ab7-1a482b40603d"
|
|
},
|
|
{
|
|
"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
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"name": "Create Git Repository",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
460,
|
|
460
|
|
],
|
|
"id": "9e0eec9b-554a-40f9-94e1-ed2d597c2aa6",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"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": "6d5f5486-e9e0-461f-b7f8-fbcbb22154ad",
|
|
"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": "58d40cb1-4231-439f-90c5-bcd7d35a3968",
|
|
"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": "a7791aeb-841d-4d95-b836-16c03b4254ad",
|
|
"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 // 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 || 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 };\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?.id || 'not available');\n console.log('Documentation ID:', results.docCollection?.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": "fbd19f1e-6247-4691-91eb-19414ff9070c",
|
|
"continueOnFail": true
|
|
},
|
|
{
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ $node['Process Mission Data'].json.config.MISSION_API_URL + '/api/missions' }}",
|
|
"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?.id || '' }}"
|
|
},
|
|
{
|
|
"name": "documentationCollectionId",
|
|
"value": "={{ $node['Combine Results'].json.docCollection?.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' }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"response": {
|
|
"response": {
|
|
"fullResponse": true
|
|
}
|
|
},
|
|
"timeout": 30000
|
|
}
|
|
},
|
|
"name": "Save Mission To API",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 3,
|
|
"position": [
|
|
820,
|
|
600
|
|
],
|
|
"id": "d5dfa1f9-604d-4df4-a402-d09245eabdcd"
|
|
},
|
|
{
|
|
"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 = [];\nif (saveMissionResult.error) errors.push(`Failed to save mission: ${saveMissionResult.error.message || 'Unknown error'}`);\nif (!integrationResults.gitRepo?.html_url) errors.push('Git repository creation failed');\nif (!integrationResults.leantimeProject?.result?.id) errors.push('Leantime project creation failed');\nif (!integrationResults.rocketChatChannel?.channel?._id) errors.push('RocketChat channel creation failed');\nif (!integrationResults.docCollection?.id) errors.push('Documentation collection creation failed');\nconst output = {\n success: errors.length === 0,\n error: errors.length > 0 ? errors.join('; ') : null,\n errors: errors,\n missionData,\n integrationResults,\n saveMissionResult,\n message: errors.length === 0 ? 'Mission integration complete: All systems updated successfully' : `Mission integration failed: ${errors.join('; ')}`\n};\nreturn output;"
|
|
},
|
|
"name": "Process Results",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1000,
|
|
600
|
|
],
|
|
"id": "1e1cbe4e-2922-488e-b4f2-9e0e905f1da3"
|
|
},
|
|
{
|
|
"parameters": {
|
|
"respondWith": "json",
|
|
"responseBody": "={{ $node[\"Process Results\"].json }}",
|
|
"options": {}
|
|
},
|
|
"name": "Respond To Webhook",
|
|
"type": "n8n-nodes-base.respondToWebhook",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
1220,
|
|
600
|
|
],
|
|
"id": "34f13ce7-cd3a-4811-b1ab-9811afa04d83"
|
|
},
|
|
{
|
|
"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": "819a0741-25dc-48ee-8771-285c9e4b47ef"
|
|
},
|
|
{
|
|
"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": "b8c06483-20d1-453c-8468-450d2f435d6a",
|
|
"credentials": {
|
|
"s3": {
|
|
"id": "xvSkHfsTBJxzopIj",
|
|
"name": "S3 account 2"
|
|
}
|
|
},
|
|
"continueOnFail": true
|
|
}
|
|
],
|
|
"pinData": {},
|
|
"connections": {
|
|
"Process Mission Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Decode Logo Data",
|
|
"type": "main",
|
|
"index": 0
|
|
},
|
|
{
|
|
"node": "Check Attachments",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Decode Logo Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Debug Binary Data",
|
|
"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": "Process Upload Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Empty Attachment Result": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Upload Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"S3 Upload Logo": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process Upload Results",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process Upload Results": {
|
|
"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": "IF Needs Git Repository",
|
|
"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
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Debug Binary Data": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "S3 Upload Logo",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"active": false,
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"versionId": "832c05ca-cb6b-4cc9-82ad-25bf63a98414",
|
|
"meta": {
|
|
"templateCredsSetupCompleted": true,
|
|
"instanceId": "575d8de48bd511243817deebddae0cc97d73be64c6c4737e5d4e9caddec881d8"
|
|
},
|
|
"id": "Pi9vMcgThia5elue",
|
|
"tags": []
|
|
} |