NeahNew/My_workflow_41.json
2025-05-23 10:24:40 +02:00

275 lines
19 KiB
JSON

{
"name": "Simplified Mission Creation Workflow",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "mission-created",
"responseMode": "responseNode",
"responseData": "allEntries",
"options": {
"response": {
"response": {
"fullResponse": true
}
},
"idempotency": {
"enabled": true,
"key": "={{ $json.name }}-{{ $now }}"
}
}
},
"name": "Mission Created Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-1320,
600
],
"webhookId": "mission-created",
"id": "01fde23a-f4da-48f6-b1a4-c0ac1210dbb6"
},
{
"parameters": {
"jsCode": "// Process all mission data in one node\nconst missionData = $input.item.json;\nconst binaryData = $input.item.binary;\n\n// Generate a unique request ID based on mission data\nconst generateRequestId = (data) => {\n const missionName = data?.body?.name || data?.name || '';\n const timestamp = new Date().toISOString().split('T')[0];\n const randomSuffix = Math.random().toString(36).substring(2, 8);\n return `${missionName}-${timestamp}-${randomSuffix}`;\n};\n\n// Check for existing mission with same name and request ID\nconst checkExistingMission = async () => {\n try {\n const requestId = generateRequestId(missionData);\n const response = await fetch(`${missionData?.config?.MISSION_API_URL || 'https://hub.slm-lab.net'}/api/missions?name=${encodeURIComponent(missionData?.body?.name || missionData?.name || '')}&requestId=${requestId}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': missionData?.config?.N8N_API_KEY || 'LwgeE1ntADD20OuWC88S3pR0EaO7FtO4',\n 'x-request-id': requestId,\n 'x-idempotency-key': requestId\n }\n });\n \n if (!response.ok) throw new Error('Failed to check existing mission');\n \n const data = await response.json();\n return {\n exists: Array.isArray(data) && data.length > 0,\n requestId,\n data\n };\n } catch (error) {\n console.error('Error checking existing mission:', error);\n return { exists: false, requestId: null, data: null };\n }\n};\n\n// Check if mission already exists\nconst { exists: missionExists, requestId, data: existingMission } = await checkExistingMission();\nif (missionExists) {\n return {\n missionExists: true,\n message: 'Mission with this name already exists',\n originalData: missionData,\n requestId,\n existingMission\n };\n}\n\n// Add request ID to mission data\nmissionData.requestId = requestId;\n\n// Helper functions\nconst sanitizeName = (name) => {\n if (!name || typeof name !== \"string\") return \"unnamed-mission\";\n return name.toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n};\n\nconst sanitizeDisplayName = (name) => {\n if (!name || typeof name !== \"string\") return \"Unnamed Mission\";\n return name.trim();\n};\n\nconst processFile = (file) => {\n if (!file) return null;\n \n // Handle different file formats\n if (typeof file === \"string\") {\n return {\n data: file,\n name: \"file.png\",\n type: \"image/png\"\n };\n }\n \n if (typeof file === \"object\") {\n if (file.data && typeof file.data === \"object\" && file.data.data) {\n return {\n data: file.data.data,\n name: file.name || \"file.png\",\n type: file.type || \"image/png\"\n };\n }\n \n if (file.data && typeof file.data === \"string\") {\n return {\n data: file.data,\n name: file.name || \"file.png\",\n type: file.type || \"image/png\"\n };\n }\n }\n \n return null;\n};\n\n// Process mission data\nconst missionName = missionData?.body?.name || missionData?.name || \"Unnamed Mission\";\nconst processedData = {\n missionProcessed: {\n name: missionName,\n sanitizedName: sanitizeName(missionName),\n displayName: sanitizeDisplayName(missionName),\n intention: missionData?.body?.intention || missionData?.intention || \"\",\n description: missionData?.body?.intention || missionData?.intention || \"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: missionData?.body?.missionType || missionData?.missionType || \"default\",\n guardians: missionData?.body?.guardians || missionData?.guardians || {},\n volunteers: missionData?.body?.volunteers || missionData?.volunteers || [],\n profils: missionData?.body?.profils || missionData?.profils || [],\n services: missionData?.body?.services || missionData?.services || [],\n clientId: (missionData?.body?.missionType === \"interne\" || missionData?.missionType === \"interne\") ? 1 : 2,\n rocketChatUsernames: [],\n logo: processFile(missionData?.logo),\n attachments: Array.isArray(missionData?.attachments) ? missionData.attachments.map(processFile).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// Process guardians and volunteers for RocketChat\nconst guardians = processedData.missionProcessed.guardians;\nif (guardians) {\n for (const role in guardians) {\n const user = guardians[role];\n if (user) processedData.missionProcessed.rocketChatUsernames.push(user);\n }\n}\n\nconst volunteers = processedData.missionProcessed.volunteers;\nif (Array.isArray(volunteers)) {\n processedData.missionProcessed.rocketChatUsernames.push(...volunteers);\n}\n\n// Remove duplicates\nprocessedData.missionProcessed.rocketChatUsernames = [...new Set(processedData.missionProcessed.rocketChatUsernames)];\n\nreturn processedData;"
},
"name": "Process Mission Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1040,
600
],
"id": "45f11013-1264-444c-8380-cc201cdae7f7"
},
{
"parameters": {
"jsCode": "// Process and upload all files in one node\nconst input = $input.item.json;\n\n// Helper function to upload file to S3\nconst uploadToS3 = async (file, type) => {\n if (!file || !file.data) return null;\n \n try {\n const response = await fetch(`${input.config.MINIO_API_URL}/upload`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Access-Key': input.config.MINIO_ACCESS_KEY,\n 'X-Secret-Key': input.config.MINIO_SECRET_KEY\n },\n body: JSON.stringify({\n bucket: 'missions',\n path: `${input.missionProcessed.sanitizedName}/${type}/${file.name}`,\n data: file.data,\n contentType: file.type\n })\n });\n \n if (!response.ok) throw new Error('Upload failed');\n \n const result = await response.json();\n return result.url;\n } catch (error) {\n console.error(`Error uploading ${type}:`, error);\n return null;\n }\n};\n\n// Upload logo\nlet logoUrl = null;\nif (input.missionProcessed.logo) {\n logoUrl = await uploadToS3(input.missionProcessed.logo, 'logo');\n}\n\n// Upload attachments\nconst attachmentUrls = [];\nif (Array.isArray(input.missionProcessed.attachments)) {\n for (const attachment of input.missionProcessed.attachments) {\n const url = await uploadToS3(attachment, 'attachments');\n if (url) attachmentUrls.push(url);\n }\n}\n\nreturn {\n json: {\n ...input,\n logoUrl,\n attachmentUrls,\n filesProcessed: true\n }\n};"
},
"name": "Process Files",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-840,
600
],
"id": "812883bf-385c-4c85-bdf7-69d7a36a2d3a"
},
{
"parameters": {
"jsCode": "// Integrate with all services in parallel\nconst input = $input.item.json;\n\n// Helper function for service integration\nconst integrateService = async (service, data) => {\n try {\n const response = await fetch(service.url, {\n method: 'POST',\n headers: service.headers,\n body: JSON.stringify(service.body)\n });\n \n if (!response.ok) throw new Error(`Service integration failed: ${response.statusText}`);\n \n return await response.json();\n } catch (error) {\n console.error(`Error in ${service.name}:`, error);\n return { error: error.message };\n }\n};\n\n// Prepare service integrations\nconst services = [];\n\n// Git Repository (if needed)\nif (input.missionProcessed.services.includes('Gite') || input.missionProcessed.services.includes('Calcul')) {\n services.push({\n name: 'Git Repository',\n url: `${input.config.GITEA_API_URL}/user/repos`,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `token ${input.config.GITEA_API_TOKEN}`\n },\n body: {\n name: input.missionProcessed.sanitizedName,\n private: true,\n auto_init: true,\n avatar_url: input.logoUrl\n }\n });\n}\n\n// Leantime Project\nservices.push({\n name: 'Leantime Project',\n url: `${input.config.LEANTIME_API_URL}/api/jsonrpc`,\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': input.config.LEANTIME_API_TOKEN\n },\n body: {\n method: 'leantime.rpc.Projects.Projects.addProject',\n jsonrpc: '2.0',\n id: 1,\n params: {\n values: {\n name: input.missionProcessed.displayName,\n clientId: input.missionProcessed.clientId,\n details: input.missionProcessed.intention,\n type: 'project',\n start: input.missionProcessed.startDate,\n end: input.missionProcessed.endDate,\n status: 'open',\n psettings: 'restricted',\n avatar: input.logoUrl\n }\n }\n }\n});\n\n// Documentation Collection\nservices.push({\n name: 'Documentation Collection',\n url: `${input.config.OUTLINE_API_URL}/collections.create`,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${input.config.OUTLINE_API_TOKEN}`\n },\n body: {\n name: input.missionProcessed.displayName,\n description: input.missionProcessed.description,\n color: '#4f46e5',\n permission: 'read',\n private: true,\n avatarUrl: input.logoUrl\n }\n});\n\n// RocketChat Channel\nservices.push({\n name: 'RocketChat Channel',\n url: `${input.config.ROCKETCHAT_API_URL}/api/v1/channels.create`,\n headers: {\n 'Content-Type': 'application/json',\n 'X-Auth-Token': input.config.ROCKETCHAT_AUTH_TOKEN,\n 'X-User-Id': input.config.ROCKETCHAT_USER_ID\n },\n body: {\n name: input.missionProcessed.displayName,\n members: input.missionProcessed.rocketChatUsernames,\n readOnly: false,\n avatarUrl: input.logoUrl\n }\n});\n\n// Execute all service integrations in parallel\nconst results = await Promise.all(services.map(service => integrateService(service, input)));\n\n// Map results to their respective services\nconst serviceResults = {};\nservices.forEach((service, index) => {\n serviceResults[service.name.toLowerCase().replace(/\\s+/g, '')] = results[index];\n});\n\nreturn {\n json: {\n ...input,\n serviceResults,\n servicesIntegrated: true\n }\n};"
},
"name": "Integrate Services",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-640,
600
],
"id": "2cd1602d-60ee-4049-a834-4b6c37cbb4cd"
},
{
"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 }}"
},
{
"name": "x-request-id",
"value": "={{ $node['Process Mission Data'].json.requestId }}"
}
]
},
"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 Files'].json.logoUrl }}"
},
{
"name": "attachments",
"value": "={{ $node['Process Files'].json.attachmentUrls }}"
},
{
"name": "gitRepoUrl",
"value": "={{ $node['Integrate Services'].json.serviceResults.gitrepository?.html_url || '' }}"
},
{
"name": "leantimeProjectId",
"value": "={{ $node['Integrate Services'].json.serviceResults.leantimeproject?.result?.id || '' }}"
},
{
"name": "documentationCollectionId",
"value": "={{ $node['Integrate Services'].json.serviceResults.documentationcollection?.id || '' }}"
},
{
"name": "rocketchatChannelId",
"value": "={{ $node['Integrate Services'].json.serviceResults.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": [
-440,
600
],
"id": "cac1aab1-1f4d-4c67-8edf-5d32a39c7ff2"
},
{
"parameters": {
"jsCode": "// Process final results\nconst input = $input.item.json;\n\n// Handle case when mission already exists\nif (input.missionExists) {\n return {\n success: true,\n message: 'Mission already exists',\n data: input.existingMission,\n requestId: input.requestId,\n isIdempotent: true\n };\n}\n\nconst serviceResults = input.serviceResults || {};\nconst saveMissionResult = input.body || {};\n\n// Check for errors\nconst errors = [];\n\nif (saveMissionResult.error) {\n errors.push(`Failed to save mission: ${saveMissionResult.error.message || 'Unknown error'}`);\n}\n\nif (serviceResults.gitrepository?.error) {\n errors.push(`Git repository creation failed: ${serviceResults.gitrepository.error}`);\n}\n\nif (serviceResults.leantimeproject?.error) {\n errors.push(`Leantime project creation failed: ${serviceResults.leantimeproject.error}`);\n}\n\nif (serviceResults.documentationcollection?.error) {\n errors.push(`Documentation collection creation failed: ${serviceResults.documentationcollection.error}`);\n}\n\nif (serviceResults.rocketchatchannel?.error) {\n errors.push(`RocketChat channel creation failed: ${serviceResults.rocketchatchannel.error}`);\n}\n\n// Prepare response\nconst response = {\n success: errors.length === 0,\n error: errors.length > 0 ? errors.join('; ') : null,\n errors: errors,\n missionData: input.missionProcessed,\n serviceResults: {\n gitRepo: serviceResults.gitrepository,\n leantimeProject: serviceResults.leantimeproject,\n documentation: serviceResults.documentationcollection,\n rocketChat: serviceResults.rocketchatchannel\n },\n saveMissionResult,\n requestId: input.requestId,\n message: errors.length === 0 ? 'Mission integration complete: All systems updated successfully' : `Mission integration failed: ${errors.join('; ')}`\n};\n\nreturn response;"
},
"name": "Process Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-240,
600
],
"id": "428eb077-6f23-447e-804a-803148f0b7e8"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $node[\"Process Results\"].json }}",
"options": {}
},
"name": "Respond To Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
-40,
600
],
"id": "47b5609f-06d7-4d1a-89a3-55a093b82e31"
}
],
"connections": {
"Mission Created Webhook": {
"main": [
[
{
"node": "Process Mission Data",
"type": "main",
"index": 0
}
]
]
},
"Process Mission Data": {
"main": [
[
{
"node": "Process Files",
"type": "main",
"index": 0
}
]
]
},
"Process Files": {
"main": [
[
{
"node": "Integrate Services",
"type": "main",
"index": 0
}
]
]
},
"Integrate Services": {
"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": "678f7cfc-e4f1-4c80-abae-bb23335efe9c",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "575d8de48bd511243817deebddae0cc97d73be64c6c4737e5d4e9caddec881d8"
},
"id": "jtAwgFTRapzRi0OB",
"tags": []
}