diff --git a/.DS_Store b/.DS_Store index a683ba31..21e5b201 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Missionspre.json b/Missionspre.json deleted file mode 100644 index 6a4c3327..00000000 --- a/Missionspre.json +++ /dev/null @@ -1,611 +0,0 @@ -{ - "name": "Missions", - "nodes": [ - { - "parameters": { - "jsCode": "const missionData = $input.item.json;\nconst sanitizeName = (name) => {\n if (!name || typeof name !== 'string') return 'unnamed-mission';\n const timestamp = new Date().getTime();\n const uniqueSuffix = `-${timestamp}`;\n return name.toLowerCase().replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').trim() + 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) return null;\n return {\n data: file.data || file,\n name: file.name || 'logo.png',\n type: file.type || 'image/png'\n };\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) : []\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://minio.slm-lab.net\"\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\nreturn output;" - }, - "name": "Process Mission Data", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [60, 560], - "id": "process-mission-data" - }, - { - "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": "={{ 'Bearer ' + $node['Process Mission Data'].json.config.GITEA_API_TOKEN }}" - } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { - "name": "name", - "value": "={{ $node[\"Process Mission Data\"].json.missionProcessed.name }}" - }, - { - "name": "private", - "value": "={{ true }}" - }, - { - "name": "auto_init", - "value": "={{ true }}" - } - ] - }, - "options": {} - }, - "name": "Create Git Repository", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 460 - ], - "id": "71eea3d3-2a75-4a14-9bc6-c23ca49be59a", - "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": "={{ { \n \"values\": {\n \"name\": $node[\"Process Mission Data\"].json.missionProcessed.name,\n \"clientId\": $node[\"Process Mission Data\"].json.missionProcessed.clientId,\n \"details\": $node[\"Process Mission Data\"].json.missionProcessed.intention,\n \"type\": \"project\",\n \"start\": $node[\"Process Mission Data\"].json.missionProcessed.startDate,\n \"end\": $node[\"Process Mission Data\"].json.missionProcessed.endDate,\n \"status\": \"open\",\n \"psettings\": \"restricted\"\n }\n} }}" - } - ] - }, - "options": { - "response": { - "response": { - "fullResponse": true - } - } - } - }, - "name": "Create Leantime Project", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 560 - ], - "id": "0cb15b0b-f718-4455-8e52-6ad8ceb563eb", - "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.name }}" - }, - { - "name": "description", - "value": "={{ $node[\"Process Mission Data\"].json.missionProcessed.description }}" - }, - { - "name": "color", - "value": "#4f46e5" - }, - { - "name": "permission", - "value": "read" - }, - { - "name": "private", - "value": "={{ true }}" - } - ] - }, - "options": {} - }, - "name": "Create Documentation Collection", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 760 - ], - "id": "ed034321-54b1-4e59-b204-c93619561fec", - "continueOnFail": true - }, - { - "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": "={{ $node['Process Mission Data'].json.config.KEYCLOAK_CLIENT_SECRET }}" - }, - { - "name": "scope", - "value": "openid profile email" - } - ] - }, - "options": { - "bodyContentType": "form-urlencoded", - "response": { - "response": { - "fullResponse": true - } - }, - "timeout": 30000 - } - }, - "name": "Get Keycloak Token", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 460 - ], - "id": "get-keycloak-token", - "continueOnFail": false - }, - { - "parameters": { - "functionCode": "const input = $input.item.json;\nconsole.log('KEYCLOAK RESPONSE:', JSON.stringify(input, null, 2));\n\n// Extract the access token from the response body\nconst access_token = input.body?.access_token;\nif (!access_token) {\n throw new Error('No access token received from Keycloak');\n}\n\nreturn {\n json: {\n access_token: access_token\n }\n};" - }, - "name": "Process Token", - "type": "n8n-nodes-base.function", - "typeVersion": 1, - "position": [ - 580, - 460 - ], - "id": "process-token" - }, - { - "parameters": { - "functionCode": "// Get all inputs\nconst inputs = $input.item.json;\n\n// Initialize results object\nconst results = {\n gitRepo: null,\n leantimeProject: null,\n docCollection: null,\n rocketChatChannel: null\n};\n\n// Process each input\nif (Array.isArray(inputs)) {\n // First input is gitRepo\n if (inputs[0] && typeof inputs[0] === 'object') {\n results.gitRepo = inputs[0];\n }\n \n // Second input is leantimeProject\n if (inputs[1] && typeof inputs[1] === 'object') {\n // Extract project ID from Leantime response\n const leantimeResponse = inputs[1];\n if (leantimeResponse && leantimeResponse.result) {\n results.leantimeProject = {\n id: leantimeResponse.result,\n name: leantimeResponse.name || ''\n };\n }\n }\n \n // Third input is docCollection\n if (inputs[2] && typeof inputs[2] === 'object') {\n results.docCollection = inputs[2];\n }\n \n // Fourth input is rocketChatChannel\n if (inputs[3] && typeof inputs[3] === 'object') {\n results.rocketChatChannel = inputs[3];\n }\n}\n\n// If we got a single object, it's the gitRepo\nif (!Array.isArray(inputs) && inputs && typeof inputs === 'object') {\n results.gitRepo = inputs;\n}\n\nconsole.log('Input:', JSON.stringify(inputs, null, 2));\nconsole.log('Combined results:', JSON.stringify(results, null, 2));\nreturn results;" - }, - "name": "Combine Results", - "type": "n8n-nodes-base.function", - "typeVersion": 1, - "position": [ - 560, - 560 - ], - "id": "combine-results" - }, - { - "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": "gitRepoUrl", - "value": "={{ $node['Combine Results'].json.gitRepo?.html_url || '' }}" - }, - { - "name": "leantimeProjectId", - "value": "={{ $node['Combine Results'].json.leantimeProject?.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": [ - 780, - 460 - ], - "id": "save-mission-to-api" - }, - { - "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 || {};\n\nconst errors = [];\n\nif (saveMissionResult.error) {\n errors.push(`Failed to save mission: ${saveMissionResult.error.message || 'Unknown error'}`);\n}\nif (!integrationResults.gitRepo?.html_url) {\n errors.push('Git repository creation failed');\n}\nif (!integrationResults.leantimeProject?.id) {\n errors.push('Leantime project creation failed');\n}\nif (!integrationResults.rocketChatChannel?.channel?._id) {\n errors.push('RocketChat channel creation failed');\n}\nif (!integrationResults.docCollection?.id) {\n errors.push('Documentation collection creation failed');\n}\n\nconst output = {\n success: errors.length === 0,\n error: errors.length > 0 ? errors.join('; ') : null,\n errors: errors,\n missionData: missionData,\n integrationResults: integrationResults,\n saveMissionResult: saveMissionResult,\n message: errors.length === 0 ?\n 'Mission integration complete: All systems updated successfully' :\n `Mission integration failed: ${errors.join('; ')}`\n};\n\nconsole.log('Process Results output:', JSON.stringify(output, null, 2));\nreturn output;" - }, - "name": "Process Results", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 860, - 560 - ], - "id": "aedea1af-dcfc-4361-bb66-f25dcce10b98" - }, - { - "parameters": { - "respondWith": "json", - "responseBody": "={{ $node[\"Process Results\"].json }}", - "options": {} - }, - "name": "Respond To Webhook", - "type": "n8n-nodes-base.respondToWebhook", - "typeVersion": 1, - "position": [ - 1060, - 560 - ], - "id": "fcecf6cc-52c3-4989-8fad-0cf6e3508601" - }, - { - "parameters": { - "httpMethod": "POST", - "path": "mission-created", - "options": { - "responseData": "lastNodeJson" - } - }, - "name": "Mission Created Webhook", - "type": "n8n-nodes-base.webhook", - "typeVersion": 1, - "position": [ - -160, - 560 - ], - "webhookId": "mission-created", - "id": "11274a5b-b20f-4180-8611-3690dc9a8722" - }, - { - "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.name }}" - }, - { - "name": "members", - "value": "={{ $node[\"Process Mission Data\"].json.missionProcessed.rocketChatUsernames }}" - }, - { - "name": "readOnly", - "value": "false" - } - ] - }, - "options": {} - }, - "name": "Create RocketChat Channel", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 660 - ], - "id": "90b1482e-6455-4c75-a7ae-297393c7aa63", - "continueOnFail": true - }, - { - "parameters": { - "method": "POST", - "url": "={{ $node['Process Mission Data'].json.config.MINIO_API_URL + '/api/upload/logo' }}", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { "name": "Content-Type", "value": "multipart/form-data" }, - { "name": "x-api-key", "value": "={{ $node['Process Mission Data'].json.config.N8N_API_KEY }}" } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { "name": "missionId", "value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}" }, - { "name": "file", "value": "={{ $node['Process Mission Data'].json.missionProcessed.logo }}", "type": "file" } - ] - }, - "options": { - "response": { "response": { "fullResponse": true } }, - "bodyContentType": "multipart-form-data" - } - }, - "name": "Upload Mission Logo to MinIO", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [260, 500], - "id": "upload-logo-to-minio", - "continueOnFail": false - }, - { - "parameters": { - "method": "POST", - "url": "={{ $node['Process Mission Data'].json.config.MINIO_API_URL + '/api/upload/attachments' }}", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { "name": "Content-Type", "value": "multipart/form-data" }, - { "name": "x-api-key", "value": "={{ $node['Process Mission Data'].json.config.N8N_API_KEY }}" } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { "name": "missionId", "value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}" }, - { "name": "files", "value": "={{ $node['Process Mission Data'].json.missionProcessed.attachments }}", "type": "file" } - ] - }, - "options": { - "response": { "response": { "fullResponse": true } }, - "bodyContentType": "multipart-form-data" - } - }, - "name": "Upload Mission Attachments to MinIO", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [260, 600], - "id": "upload-attachments-to-minio", - "continueOnFail": true - } - ], - "pinData": {}, - "connections": { - "Mission Created Webhook": { - "main": [ - [ - { - "node": "Process Mission Data", - "type": "main", - "index": 0 - } - ] - ] - }, - "Process Mission Data": { - "main": [ - [ - { - "node": "Get Keycloak Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Get Keycloak Token": { - "main": [ - [ - { - "node": "Process Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Process Token": { - "main": [ - [ - { - "node": "Create Git Repository", - "type": "main", - "index": 0 - }, - { - "node": "Create Leantime Project", - "type": "main", - "index": 0 - }, - { - "node": "Create Documentation Collection", - "type": "main", - "index": 0 - }, - { - "node": "Create RocketChat Channel", - "type": "main", - "index": 0 - } - ] - ] - }, - "Create Git Repository": { - "main": [ - [{ "node": "Combine Results", "type": "main", "index": 0 }] - ] - }, - "Create Leantime Project": { - "main": [ - [{ "node": "Combine Results", "type": "main", "index": 1 }] - ] - }, - "Create Documentation Collection": { - "main": [ - [{ "node": "Combine Results", "type": "main", "index": 2 }] - ] - }, - "Create RocketChat Channel": { - "main": [ - [{ "node": "Combine Results", "type": "main", "index": 3 }] - ] - }, - "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 - } - ] - ] - } - }, - "active": false, - "settings": { - "executionOrder": "v1" - }, - "versionId": "45981b34-113c-428c-85fd-fdc5e22afce4", - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "575d8de48bd511243817deebddae0cc97d73be64c6c4737e5d4e9caddec881d8" - }, - "id": "Mxg5cbQzEUgTEFNd", - "tags": [] -} \ No newline at end of file diff --git a/My_workflow_38.json b/My_workflow_38.json new file mode 100644 index 00000000..3b97e08c --- /dev/null +++ b/My_workflow_38.json @@ -0,0 +1,933 @@ +{ + "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": [] +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 40db5c51..10979ca5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,23 @@ services: volumes: - redis_data:/data + minio: + image: minio/minio + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + app: build: context: . @@ -31,11 +48,18 @@ services: depends_on: - db - redis + - minio environment: - DATABASE_URL=postgresql://postgres:postgres@db:5432/calendar_db - REDIS_URL=redis://:mySecretPassword@redis:6379 + - MINIO_S3_UPLOAD_BUCKET_URL=http://minio:9000 + - MINIO_AWS_REGION=us-east-1 + - MINIO_AWS_S3_UPLOAD_BUCKET_NAME=pages + - MINIO_ACCESS_KEY=minioadmin + - MINIO_SECRET_KEY=minioadmin volumes: db: driver: local - redis_data: \ No newline at end of file + redis_data: + minio_data: \ No newline at end of file diff --git a/reacct.json b/reacct.json deleted file mode 100644 index 217b08bf..00000000 --- a/reacct.json +++ /dev/null @@ -1,757 +0,0 @@ -{ - "name": "My workflow 10", - "nodes": [ - { - "parameters": { - "jsCode": "const missionData = $input.item.json;\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',\ - 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_SECRET_KEY: \"gbdrqJsXyU4IFxsfz9xdrnQeMRy2eZHeqQRrAeBR\"\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\nreturn output;" - }, - "name": "Process Mission Data", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - -140, - 560 - ], - "id": "4c53f8e1-285d-4b97-923a-9090ada513f2" - }, - { - "parameters": { - "functionCode": "const input = $input.item.json;\nconst logoData = input.missionProcessed.logo?.data;\nif (!logoData) return input;\n\n// Convert base64 to buffer\nconst base64Data = logoData.replace(/^data:image\\/\\w+;base64,/, '');\nconst buffer = Buffer.from(base64Data, 'base64');\n\n// Create a proper file object for MinIO\nconst fileData = {\n data: buffer,\n name: input.missionProcessed.logo.name || 'logo.png',\n type: input.missionProcessed.logo.type || 'image/png'\n};\n\nreturn {\n json: {\n ...input,\n missionProcessed: {\n ...input.missionProcessed,\n logo: fileData\n }\n }\n};" - }, - "name": "Decode Logo Data", - "type": "n8n-nodes-base.function", - "typeVersion": 1, - "position": [ - -20, - 500 - ], - "id": "decode-logo-data" - }, - { - "parameters": { - "method": "POST", - "url": "={{ $node['Decode Logo Data'].json.config.MINIO_API_URL + '/api/upload/logo' }}", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { - "name": "Content-Type", - "value": "multipart/form-data" - }, - { - "name": "x-api-key", - "value": "={{ $node['Decode Logo Data'].json.config.N8N_API_KEY }}" - } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { - "name": "missionId", - "value": "={{ $node['Decode Logo Data'].json.missionProcessed.sanitizedName }}" - }, - { - "name": "file", - "value": "={{ $node['Decode Logo Data'].json.missionProcessed.logo.data }}", - "type": "file" - } - ] - }, - "options": { - "bodyContentType": "multipart-form-data", - "response": { - "response": { - "fullResponse": true - } - } - } - }, - "name": "Upload Mission Logo to MinIO", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 60, - 500 - ], - "id": "6d76b33e-d25e-4c1f-b906-f47639facc42", - "continueOnFail": true, - "executeOnce": false, - "maxTries": 3, - "waitBetweenTries": 1000 - }, - { - "parameters": { - "method": "POST", - "url": "={{ $node['Process Mission Data'].json.config.MINIO_API_URL + '/api/upload/attachments' }}", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { - "name": "Content-Type", - "value": "multipart/form-data" - }, - { - "name": "x-api-key", - "value": "={{ $node['Process Mission Data'].json.config.N8N_API_KEY }}" - } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { - "name": "missionId", - "value": "={{ $node['Process Mission Data'].json.missionProcessed.sanitizedName }}" - }, - { - "name": "files", - "value": "={{ $node['Process Mission Data'].json.missionProcessed.attachments }}", - "type": "file" - } - ] - }, - "options": { - "bodyContentType": "multipart-form-data", - "response": { - "response": { - "fullResponse": true - } - } - } - }, - "name": "Upload Mission Attachments to MinIO", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 60, - 600 - ], - "id": "488895ea-024f-4946-90ec-f83f48f215ee", - "continueOnFail": true - }, - { - "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": "={{ $node['Process Mission Data'].json.config.KEYCLOAK_CLIENT_SECRET }}" - }, - { - "name": "scope", - "value": "openid profile email" - } - ] - }, - "options": { - "response": { - "response": { - "fullResponse": true - } - }, - "timeout": 30000 - } - }, - "name": "Get Keycloak Token", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 260, - 560 - ], - "id": "ed5999b3-f9af-45ae-86ce-f4522c69b5b7" - }, - { - "parameters": { - "functionCode": "const input = $input.item.json;\nconst access_token = input.body?.access_token;\nif (!access_token) throw new Error('No access token received from Keycloak');\nreturn { json: { access_token } };" - }, - "name": "Process Token", - "type": "n8n-nodes-base.function", - "typeVersion": 1, - "position": [ - 380, - 560 - ], - "id": "ef75d1c4-e3fd-4e4c-8447-023f2d9af420" - }, - { - "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": "={{ 'Bearer ' + $node['Process Mission Data'].json.config.GITEA_API_TOKEN }}" - } - ] - }, - "sendBody": true, - "bodyParameters": { - "parameters": [ - { - "name": "name", - "value": "={{ $node['Process Mission Data'].json.missionProcessed.name }}" - }, - { - "name": "private", - "value": "={{ true }}" - }, - { - "name": "auto_init", - "value": "={{ true }}" - }, - { - "name": "avatar_url", - "value": "={{ $node['Upload Mission Logo to MinIO'].json.publicUrl }}" - } - ] - }, - "options": {} - }, - "name": "Create Git Repository", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 460 - ], - "id": "a359928d-e372-4334-a91c-8d6d64dba92c", - "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['Upload Mission Logo to MinIO'].json.publicUrl } } }}" - } - ] - }, - "options": { - "response": { - "response": { - "fullResponse": true - } - } - } - }, - "name": "Create Leantime Project", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 560 - ], - "id": "c4533a11-692c-4d85-a0ab-b463c487b676", - "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.name }}" - }, - { - "name": "description", - "value": "={{ $node['Process Mission Data'].json.missionProcessed.description }}" - }, - { - "name": "color", - "value": "#4f46e5" - }, - { - "name": "permission", - "value": "read" - }, - { - "name": "private", - "value": "={{ true }}" - }, - { - "name": "avatarUrl", - "value": "={{ $node['Upload Mission Logo to MinIO'].json.publicUrl }}" - } - ] - }, - "options": {} - }, - "name": "Create Documentation Collection", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 760 - ], - "id": "9a20119a-e0ea-4e11-af63-c779bcc6ab59", - "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.name }}" - }, - { - "name": "members", - "value": "={{ $node['Process Mission Data'].json.missionProcessed.rocketChatUsernames }}" - }, - { - "name": "readOnly", - "value": "false" - }, - { - "name": "avatarUrl", - "value": "={{ $node['Upload Mission Logo to MinIO'].json.publicUrl }}" - } - ] - }, - "options": {} - }, - "name": "Create RocketChat Channel", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 3, - "position": [ - 460, - 660 - ], - "id": "af78b5b8-cc29-4f06-b25d-04e2e0edfa0c", - "continueOnFail": true - }, - { - "parameters": { - "functionCode": "// Combine results from all integrations\nconst inputs = $input.item.json;\nconst results = {\n gitRepo: inputs[0],\n leantimeProject: inputs[1],\n docCollection: inputs[2],\n rocketChatChannel: inputs[3]\n};\nreturn results;" - }, - "name": "Combine Results", - "type": "n8n-nodes-base.function", - "typeVersion": 1, - "position": [ - 660, - 560 - ], - "id": "f7529ca9-6507-4108-801e-9d01831a1d22" - }, - { - "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['Upload Mission Logo to MinIO'].json.publicUrl }}" - }, - { - "name": "attachments", - "value": "={{ $node['Upload Mission Attachments to MinIO'].json.files }}" - }, - { - "name": "gitRepoUrl", - "value": "={{ $node['Combine Results'].json.gitRepo?.html_url || '' }}" - }, - { - "name": "leantimeProjectId", - "value": "={{ $node['Combine Results'].json.leantimeProject?.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": [ - 860, - 460 - ], - "id": "8c3e264c-0cba-4d08-9eeb-b3efecef223e" - }, - { - "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?.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": [ - 960, - 560 - ], - "id": "b4fde353-fd64-4f29-9ade-0fbde9a09674" - }, - { - "parameters": { - "respondWith": "json", - "responseBody": "={{ $node[\"Process Results\"].json }}", - "options": {} - }, - "name": "Respond To Webhook", - "type": "n8n-nodes-base.respondToWebhook", - "typeVersion": 1, - "position": [ - 1060, - 560 - ], - "id": "5922f35a-d83e-4c9d-a3cf-9b2cf995a3f8" - }, - { - "parameters": { - "httpMethod": "POST", - "path": "mission-created", - "options": { - "responseData": "lastNodeJson", - "responseContentType": "application/json" - } - }, - "name": "Mission Created Webhook", - "type": "n8n-nodes-base.webhook", - "typeVersion": 1, - "position": [ - -360, - 560 - ], - "webhookId": "mission-created", - "id": "d5afe297-cf2b-4f33-ae11-fcbcf5643586" - } - ], - "connections": { - "Mission Created Webhook": { - "main": [ - [ - { - "node": "Process Mission Data", - "type": "main", - "index": 0 - } - ] - ] - }, - "Process Mission Data": { - "main": [ - [ - { - "node": "Decode Logo Data", - "type": "main", - "index": 0 - } - ] - ] - }, - "Decode Logo Data": { - "main": [ - [ - { - "node": "Upload Mission Logo to MinIO", - "type": "main", - "index": 0 - }, - { - "node": "Upload Mission Attachments to MinIO", - "type": "main", - "index": 0 - } - ] - ] - }, - "Upload Mission Logo to MinIO": { - "main": [ - [ - { - "node": "Get Keycloak Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Upload Mission Attachments to MinIO": { - "main": [ - [ - { - "node": "Get Keycloak Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Get Keycloak Token": { - "main": [ - [ - { - "node": "Process Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Process Token": { - "main": [ - [ - { - "node": "Create Git Repository", - "type": "main", - "index": 0 - }, - { - "node": "Create Leantime Project", - "type": "main", - "index": 0 - }, - { - "node": "Create Documentation Collection", - "type": "main", - "index": 0 - }, - { - "node": "Create RocketChat Channel", - "type": "main", - "index": 0 - } - ] - ] - }, - "Create Git Repository": { - "main": [ - [ - { - "node": "Combine Results", - "type": "main", - "index": 0 - } - ] - ] - }, - "Create Leantime Project": { - "main": [ - [ - { - "node": "Combine Results", - "type": "main", - "index": 1 - } - ] - ] - }, - "Create Documentation Collection": { - "main": [ - [ - { - "node": "Combine Results", - "type": "main", - "index": 2 - } - ] - ] - }, - "Create RocketChat Channel": { - "main": [ - [ - { - "node": "Combine Results", - "type": "main", - "index": 3 - } - ] - ] - }, - "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 - } - ] - ] - } - }, - "active": true, - "settings": { - "executionOrder": "v1" - }, - "versionId": "6f4f1b22-3e1d-4318-9e21-bc16b700b352", - "meta": { - "templateCredsSetupCompleted": true, - "instanceId": "575d8de48bd511243817deebddae0cc97d73be64c6c4737e5d4e9caddec881d8" - }, - "id": "ybWcDYBfbg8FBe0r", - "tags": [] -} \ No newline at end of file diff --git a/test-upload.js b/test-upload.js new file mode 100644 index 00000000..bc954158 --- /dev/null +++ b/test-upload.js @@ -0,0 +1,36 @@ +const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3'); +const fs = require('fs'); + +const s3Config = { + endpoint: 'https://dome-api.slm-lab.net', + region: 'us-east-1', + credentials: { + accessKeyId: 'LwgeE1ntADD20OuWC88S3pR0EaO7FtO4', + secretAccessKey: 'gbdrqJsXyU4IFxsfz9xdrnQeMRy2eZHeqQRrAeBR' + }, + forcePathStyle: true +}; + +const s3Client = new S3Client(s3Config); + +async function uploadFile() { + try { + const fileContent = fs.readFileSync('/Users/alma/Documents/test.png'); + + const command = new PutObjectCommand({ + Bucket: 'missions', + Key: 'test-mission/logo.png', + Body: fileContent, + ContentType: 'image/png', + ACL: 'public-read' + }); + + console.log('Uploading file...'); + const response = await s3Client.send(command); + console.log('Upload successful!', response); + } catch (error) { + console.error('Error uploading file:', error); + } +} + +uploadFile(); \ No newline at end of file