Vision Refactor

This commit is contained in:
alma 2026-01-14 11:15:51 +01:00
parent 5e3289f0e1
commit 1257f8cff3
14 changed files with 1244 additions and 14 deletions

98
N8N_ROCKETCHAT_FIX.md Normal file
View File

@ -0,0 +1,98 @@
# Correction N8N - RocketChat Channel ID
## 🔍 Problème identifié
D'après la réponse du webhook N8N, la structure de `rocketChatChannel` est :
```json
"rocketChatChannel": {
"id": "6966cbb6c8e9627bcb87daad",
"name": "seffirouuuuuu",
"exists": true,
"error": null
}
```
Mais dans le node **"Save Mission To API"**, le code cherche :
```
$node['Combine Results'].json.rocketChatChannel?.channel?._id
```
Ce qui ne correspond pas à la structure réelle qui est `rocketChatChannel.id` (pas `rocketChatChannel.channel._id`).
---
## ✅ Solution : Modifier dans N8N
### Étape 1 : Ouvrir le workflow N8N
1. Connectez-vous à N8N
2. Ouvrez le workflow qui gère la création de missions
3. Trouvez le node **"Save Mission To API"**
### Étape 2 : Modifier le paramètre `rocketchatChannelId`
Dans le node **"Save Mission To API"**, dans la section **Body Parameters**, trouvez le paramètre :
**Nom** : `rocketchatChannelId`
**Valeur actuelle (INCORRECTE)** :
```
={{ $node['Combine Results'].json.rocketChatChannel?.channel?._id || '' }}
```
**Valeur à mettre (CORRECTE)** :
```
={{ $node['Combine Results'].json.rocketChatChannel?.id || $node['Combine Results'].json.rocketChatChannel?.channel?._id || '' }}
```
Cette expression essaie d'abord `rocketChatChannel.id` (la structure réelle), puis fait un fallback sur `rocketChatChannel.channel._id` (ancienne structure) si la première n'existe pas.
---
## 🔍 Vérification dans "Combine Results"
Vérifiez aussi que le node **"Combine Results"** structure correctement les données.
Dans le code JavaScript de "Combine Results", la ligne qui traite RocketChat devrait extraire l'ID correctement. Vérifiez que cette partie du code gère bien les deux structures :
```javascript
// Process RocketChat channel result
if (rocketChatResult.error?.includes('error-duplicate-channel-name')) {
console.log('RocketChat channel already exists');
rocketChatResult = { exists: true };
} else if (rocketChatResult.body?.channel?._id) {
resourceStatus.rocketChatChannel = true;
}
// ...
// Dans la construction du résultat
rocketChatChannel: rocketChatResult.error ? { error: ... } : (rocketChatResult.body || rocketChatResult || {})
```
Si la réponse de RocketChat est directement dans `rocketChatResult.body.channel._id`, alors `rocketChatResult.body` contiendra `{ channel: { _id: "..." } }`.
Mais d'après votre réponse, il semble que "Combine Results" transforme déjà la structure en `{ id: "...", name: "...", exists: true }`.
---
## 🧪 Test après modification
Après avoir modifié le node "Save Mission To API", testez avec :
```bash
./test-n8n-python.py [MISSION_ID] [PROJECT_NAME]
```
Vérifiez dans les logs Next.js que `rocketchatChannelId` est bien reçu avec la valeur `"6966cbb6c8e9627bcb87daad"` (ou l'ID réel du channel).
---
## 📝 Résumé des changements
**Node à modifier** : "Save Mission To API"
**Paramètre** : `rocketchatChannelId`
**Ancienne valeur** : `={{ $node['Combine Results'].json.rocketChatChannel?.channel?._id || '' }}`
**Nouvelle valeur** : `={{ $node['Combine Results'].json.rocketChatChannel?.id || $node['Combine Results'].json.rocketChatChannel?.channel?._id || '' }}`
Cette modification permet de supporter les deux structures (ancienne et nouvelle) pour plus de robustesse.

View File

@ -1,21 +1,160 @@
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/options";
"use client";
import { useState, useEffect } from "react";
import { useSession } from "next-auth/react";
import { redirect } from "next/navigation";
import { ResponsiveIframe } from "@/app/components/responsive-iframe";
import { Users, FolderKanban, Video, ArrowLeft, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
export default async function Page() {
const session = await getServerSession(authOptions);
if (!session) {
redirect("/signin");
interface Group {
id: string;
name: string;
path: string;
}
// URL Jitsi avec salle par défaut
const jitsiUrl = process.env.NEXT_PUBLIC_IFRAME_CONFERENCE_URL || 'https://vision.slm-lab.net/MonMeeting';
interface Mission {
id: string;
name: string;
logoUrl?: string | null;
}
type ConferenceType = "group" | "mission" | null;
export default function VisionPage() {
const { data: session, status } = useSession();
const { toast } = useToast();
const [groups, setGroups] = useState<Group[]>([]);
const [missions, setMissions] = useState<Mission[]>([]);
const [loading, setLoading] = useState(true);
const [selectedConference, setSelectedConference] = useState<{
type: ConferenceType;
id: string;
name: string;
} | null>(null);
const [jitsiUrl, setJitsiUrl] = useState<string | null>(null);
// Redirect if not authenticated
useEffect(() => {
if (status === "unauthenticated") {
redirect("/signin");
}
}, [status]);
// Fetch user groups and missions
useEffect(() => {
const fetchData = async () => {
if (status !== "authenticated" || !session?.user?.id) {
return;
}
try {
setLoading(true);
const userId = session.user.id;
// Fetch user groups
const groupsRes = await fetch(`/api/users/${userId}/groups`);
if (groupsRes.ok) {
const groupsData = await groupsRes.json();
setGroups(Array.isArray(groupsData) ? groupsData : []);
} else {
console.error("Failed to fetch groups");
}
// Fetch all missions and filter for user's missions
const missionsRes = await fetch("/api/missions?limit=1000");
if (missionsRes.ok) {
const missionsData = await missionsRes.json();
const allMissions = missionsData.missions || [];
// Filter missions where user is creator or member
const userMissions = allMissions.filter((mission: any) => {
const isCreator = mission.creator?.id === userId;
const isMember = mission.missionUsers?.some(
(mu: any) => mu.user?.id === userId
);
return isCreator || isMember;
});
setMissions(userMissions);
} else {
console.error("Failed to fetch missions");
}
} catch (error) {
console.error("Error fetching data:", error);
toast({
title: "Erreur",
description: "Impossible de charger les données",
variant: "destructive",
});
} finally {
setLoading(false);
}
};
fetchData();
}, [session, status, toast]);
// Handle conference selection
const handleConferenceClick = (type: ConferenceType, id: string, name: string) => {
const baseUrl = process.env.NEXT_PUBLIC_IFRAME_CONFERENCE_URL || 'https://vision.slm-lab.net';
let url: string;
if (type === "group") {
// URL format: https://vision.slm-lab.net/Groupe-{groupId}
url = `${baseUrl}/Groupe-${id}`;
} else {
// URL format: https://vision.slm-lab.net/{missionId}
url = `${baseUrl}/${id}`;
}
setSelectedConference({ type, id, name });
setJitsiUrl(url);
};
// Handle back to list
const handleBack = () => {
setSelectedConference(null);
setJitsiUrl(null);
};
// Show loading state
if (status === "loading" || loading) {
return (
<main className="w-full h-screen bg-black">
<div className="w-full h-full px-4 pt-12 pb-4">
<main className="w-full h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="h-12 w-12 animate-spin text-blue-600 mx-auto mb-4" />
<p className="text-gray-600">Chargement...</p>
</div>
</main>
);
}
// Show Jitsi iframe if conference is selected
if (selectedConference && jitsiUrl) {
return (
<main className="w-full h-screen bg-black flex flex-col">
{/* Header with back button */}
<div className="bg-white border-b border-gray-200 px-4 py-3 flex items-center gap-4">
<Button
variant="ghost"
size="sm"
onClick={handleBack}
className="flex items-center gap-2"
>
<ArrowLeft className="h-4 w-4" />
Retour
</Button>
<div className="flex-1">
<h1 className="text-sm font-medium text-gray-900">
{selectedConference.type === "group" ? "Groupe" : "Mission"}: {selectedConference.name}
</h1>
</div>
</div>
{/* Jitsi iframe */}
<div className="flex-1 overflow-hidden">
<ResponsiveIframe
src={jitsiUrl}
allow="camera; microphone; fullscreen; display-capture; autoplay; clipboard-write; encrypted-media"
@ -24,3 +163,143 @@ export default async function Page() {
</main>
);
}
// Show list of groups and missions
return (
<main className="w-full h-screen bg-gray-50">
<div className="w-full h-full px-4 pt-12 pb-4">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 mb-2">
Espaces de réunion
</h1>
<p className="text-gray-600">
Sélectionnez un groupe ou une mission pour accéder à son espace de réunion Jitsi
</p>
</div>
{/* Groups Section */}
<div className="mb-8">
<div className="flex items-center gap-2 mb-4">
<Users className="h-5 w-5 text-blue-600" />
<h2 className="text-xl font-semibold text-gray-900">
Groupes ({groups.length})
</h2>
</div>
{groups.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-8 text-center">
<Users className="h-12 w-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500">Aucun groupe disponible</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{groups.map((group) => (
<div
key={group.id}
className="bg-white rounded-lg border border-gray-200 p-4 hover:shadow-md transition-shadow cursor-pointer"
onClick={() => handleConferenceClick("group", group.id, group.name)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center">
<Users className="h-5 w-5 text-blue-600" />
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-900 truncate">
{group.name}
</h3>
<p className="text-sm text-gray-500 truncate">
{group.path}
</p>
</div>
</div>
<Button
variant="outline"
size="sm"
className="ml-2 flex items-center gap-2"
onClick={(e) => {
e.stopPropagation();
handleConferenceClick("group", group.id, group.name);
}}
>
<Video className="h-4 w-4" />
Réunion
</Button>
</div>
</div>
))}
</div>
)}
</div>
{/* Missions Section */}
<div>
<div className="flex items-center gap-2 mb-4">
<FolderKanban className="h-5 w-5 text-purple-600" />
<h2 className="text-xl font-semibold text-gray-900">
Missions ({missions.length})
</h2>
</div>
{missions.length === 0 ? (
<div className="bg-white rounded-lg border border-gray-200 p-8 text-center">
<FolderKanban className="h-12 w-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500">Aucune mission disponible</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{missions.map((mission) => (
<div
key={mission.id}
className="bg-white rounded-lg border border-gray-200 p-4 hover:shadow-md transition-shadow cursor-pointer"
onClick={() => handleConferenceClick("mission", mission.id, mission.name)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1">
{mission.logoUrl ? (
<div className="h-10 w-10 rounded-md overflow-hidden flex-shrink-0">
<img
src={mission.logoUrl}
alt={mission.name}
className="h-full w-full object-cover"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
</div>
) : (
<div className="h-10 w-10 rounded-md bg-purple-100 flex items-center justify-center flex-shrink-0">
<FolderKanban className="h-5 w-5 text-purple-600" />
</div>
)}
<div className="flex-1 min-w-0">
<h3 className="font-medium text-gray-900 truncate">
{mission.name}
</h3>
</div>
</div>
<Button
variant="outline"
size="sm"
className="ml-2 flex items-center gap-2"
onClick={(e) => {
e.stopPropagation();
handleConferenceClick("mission", mission.id, mission.name);
}}
>
<Video className="h-4 w-4" />
Réunion
</Button>
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
</main>
);
}

52
test-n8n-curl-env.sh Normal file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# Curl simple utilisant les variables d'environnement
# Charge automatiquement .env.local si présent
# Charger .env.local
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
# Utiliser les variables d'environnement
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY}"
# Vérifier que l'API key est définie
if [ -z "$API_KEY" ]; then
echo "❌ Erreur: N8N_API_KEY n'est pas définie"
echo " Vérifiez votre fichier .env.local ou exportez N8N_API_KEY"
exit 1
fi
# Paramètres optionnels
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
echo "🧪 Test webhook N8N"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "API URL: $API_URL"
echo "Mission ID: $MISSION_ID"
echo "RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo ""
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"name\": \"SEFFIR\",
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"gitRepoUrl\": \"\",
\"leantimeProjectId\": \"517\",
\"documentationCollectionId\": \"08919836-435a-466f-a38a-014991759da2\",
\"rocketchatChannelId\": \"${ROCKETCHAT_CHANNEL_ID}\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"missionType\": \"remote\",
\"niveau\": \"s\"
}" \
-s | jq '.'
echo ""
echo "✅ Test terminé"

37
test-n8n-curl-precise.sh Normal file
View File

@ -0,0 +1,37 @@
#!/bin/bash
# Version curl simple et précise
# Charge .env.local automatiquement
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
curl -X POST "https://brain.slm-lab.net/webhook-test/mission-created" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"SEFFIR\",
\"oddScope\": [\"odd-4\"],
\"niveau\": \"s\",
\"intention\": \"\",
\"missionType\": \"remote\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"services\": [],
\"participation\": \"ouvert\",
\"profils\": [],
\"guardians\": {},
\"volunteers\": [],
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"missionId\": \"${MISSION_ID}\",
\"logoPath\": \"missions/${MISSION_ID}/logo.png\",
\"logoUrl\": \"https://hub.slm-lab.net/api/missions/image/missions/${MISSION_ID}/logo.png\",
\"config\": {
\"N8N_API_KEY\": \"${N8N_API_KEY}\",
\"MISSION_API_URL\": \"${API_URL}\"
}
}" \
-s | jq '.'

22
test-n8n-curl.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# Curl simple pour tester la sortie N8N
# Remplacez les valeurs entre <...> par vos vraies valeurs
curl -X POST "https://hub.slm-lab.net/api/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: <VOTRE_N8N_API_KEY>" \
-d '{
"missionId": "3103ec1a-acde-4025-9ead-4e1a0ddc047c",
"name": "SEFFIR",
"creatorId": "203cbc91-61ab-47a2-95d2-b5e1159327d7",
"gitRepoUrl": "",
"leantimeProjectId": "517",
"documentationCollectionId": "08919836-435a-466f-a38a-014991759da2",
"rocketchatChannelId": "ByehQjC44FwMeiLbX",
"donneurDOrdre": "group",
"projection": "long",
"missionType": "remote",
"niveau": "s"
}' \
-v

86
test-n8n-inline.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
# Version inline avec paramètres
# Usage: ./test-n8n-inline.sh [MISSION_ID] [PROJECT_NAME]
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
PROJECT_NAME="${2:-SEFFIR}"
python3 << EOF
import urllib.request
import json
import os
import sys
# Charger .env.local
env_vars = {}
if os.path.exists('.env.local'):
with open('.env.local') as f:
for line in f:
if '=' in line and not line.strip().startswith('#'):
key, value = line.strip().split('=', 1)
env_vars[key] = value
webhook_url = "https://brain.slm-lab.net/webhook-test/mission-created"
mission_id = "${MISSION_ID}"
project_name = "${PROJECT_NAME}"
api_key = env_vars.get('N8N_API_KEY', os.environ.get('N8N_API_KEY'))
api_url = env_vars.get('NEXT_PUBLIC_API_URL', os.environ.get('NEXT_PUBLIC_API_URL', 'https://hub.slm-lab.net/api'))
if not api_key:
print("❌ Erreur: N8N_API_KEY n'est pas définie")
sys.exit(1)
print(f"🧪 Test du webhook N8N")
print(f"Mission ID: {mission_id}")
print(f"Project Name: {project_name}")
print("")
data = {
"name": project_name,
"oddScope": ["odd-4"],
"niveau": "s",
"intention": "",
"missionType": "remote",
"donneurDOrdre": "group",
"projection": "long",
"services": [],
"participation": "ouvert",
"profils": [],
"guardians": {},
"volunteers": [],
"creatorId": "203cbc91-61ab-47a2-95d2-b5e1159327d7",
"missionId": mission_id,
"logoPath": f"missions/{mission_id}/logo.png",
"logoUrl": f"https://hub.slm-lab.net/api/missions/image/missions/{mission_id}/logo.png",
"config": {
"N8N_API_KEY": api_key,
"MISSION_API_URL": api_url
}
}
req = urllib.request.Request(
webhook_url,
data=json.dumps(data).encode('utf-8'),
headers={'Content-Type': 'application/json'}
)
try:
with urllib.request.urlopen(req) as response:
print(f"✅ Status: {response.status} {response.reason}")
print(f"📄 Réponse:")
response_data = json.loads(response.read().decode('utf-8'))
print(json.dumps(response_data, indent=2))
except urllib.error.HTTPError as e:
print(f"❌ HTTP Error {e.code}: {e.reason}")
try:
error_body = e.read().decode('utf-8')
print(f"📄 Corps de l'erreur:")
print(json.dumps(json.loads(error_body), indent=2))
except:
print(error_body)
sys.exit(1)
except Exception as e:
print(f"❌ Erreur: {e}")
sys.exit(1)
EOF

87
test-n8n-python.py Normal file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""
Script pour tester le webhook N8N
Usage: python3 test-n8n-python.py [MISSION_ID] [PROJECT_NAME]
"""
import urllib.request
import json
import os
import sys
# Charger .env.local
env_vars = {}
if os.path.exists('.env.local'):
with open('.env.local') as f:
for line in f:
if '=' in line and not line.strip().startswith('#'):
key, value = line.strip().split('=', 1)
env_vars[key] = value
# Paramètres depuis la ligne de commande ou variables d'environnement
mission_id = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('MISSION_ID', '3103ec1a-acde-4025-9ead-4e1a0ddc047c')
project_name = sys.argv[2] if len(sys.argv) > 2 else os.environ.get('PROJECT_NAME', 'SEFFIR')
webhook_url = "https://brain.slm-lab.net/webhook-test/mission-created"
api_key = env_vars.get('N8N_API_KEY', os.environ.get('N8N_API_KEY'))
api_url = env_vars.get('NEXT_PUBLIC_API_URL', os.environ.get('NEXT_PUBLIC_API_URL', 'https://hub.slm-lab.net/api'))
if not api_key:
print("❌ Erreur: N8N_API_KEY n'est pas définie")
print(" Vérifiez votre fichier .env.local ou exportez N8N_API_KEY")
sys.exit(1)
print(f"🧪 Test du webhook N8N")
print(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
print(f"Webhook URL: {webhook_url}")
print(f"Mission ID: {mission_id}")
print(f"Project Name: {project_name}")
print(f"")
data = {
"name": project_name,
"oddScope": ["odd-4"],
"niveau": "s",
"intention": "",
"missionType": "remote",
"donneurDOrdre": "group",
"projection": "long",
"services": [],
"participation": "ouvert",
"profils": [],
"guardians": {},
"volunteers": [],
"creatorId": "203cbc91-61ab-47a2-95d2-b5e1159327d7",
"missionId": mission_id,
"logoPath": f"missions/{mission_id}/logo.png",
"logoUrl": f"https://hub.slm-lab.net/api/missions/image/missions/{mission_id}/logo.png",
"config": {
"N8N_API_KEY": api_key,
"MISSION_API_URL": api_url
}
}
req = urllib.request.Request(
webhook_url,
data=json.dumps(data).encode('utf-8'),
headers={'Content-Type': 'application/json'}
)
try:
with urllib.request.urlopen(req) as response:
print(f"✅ Status: {response.status} {response.reason}")
print(f"📄 Réponse:")
response_data = json.loads(response.read().decode('utf-8'))
print(json.dumps(response_data, indent=2))
except urllib.error.HTTPError as e:
print(f"❌ HTTP Error {e.code}: {e.reason}")
try:
error_body = e.read().decode('utf-8')
print(f"📄 Corps de l'erreur:")
print(json.dumps(json.loads(error_body), indent=2))
except:
print(error_body)
sys.exit(1)
except Exception as e:
print(f"❌ Erreur: {e}")
sys.exit(1)

45
test-n8n-simple-vm.sh Normal file
View File

@ -0,0 +1,45 @@
#!/bin/bash
# Version simple pour la VM - utilise curl si disponible
# Usage: ./test-n8n-simple-vm.sh [MISSION_ID]
# Charger .env.local
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
if [ -z "$N8N_API_KEY" ]; then
echo "❌ N8N_API_KEY non définie"
exit 1
fi
echo "🧪 Test webhook N8N: https://brain.slm-lab.net/webhook-test/mission-created"
echo ""
curl -X POST "https://brain.slm-lab.net/webhook-test/mission-created" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"SEFFIR\",
\"oddScope\": [\"odd-4\"],
\"niveau\": \"s\",
\"missionType\": \"remote\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"services\": [],
\"participation\": \"ouvert\",
\"profils\": [],
\"guardians\": {},
\"volunteers\": [],
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"missionId\": \"${MISSION_ID}\",
\"logoPath\": \"missions/${MISSION_ID}/logo.png\",
\"logoUrl\": \"https://hub.slm-lab.net/api/missions/image/missions/${MISSION_ID}/logo.png\",
\"config\": {
\"N8N_API_KEY\": \"${N8N_API_KEY}\",
\"MISSION_API_URL\": \"${API_URL}\"
}
}" \
-s | python3 -m json.tool 2>/dev/null || cat

39
test-n8n-simple.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
# Version simple - charge .env.local automatiquement
# Usage: ./test-n8n-simple.sh [MISSION_ID] [ROCKETCHAT_CHANNEL_ID]
# Charger .env.local
if [ -f .env.local ]; then
set -a
source .env.local
set +a
fi
# Variables
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY}"
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
if [ -z "$API_KEY" ]; then
echo "❌ N8N_API_KEY non définie"
exit 1
fi
echo "🧪 Test avec RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo ""
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"name\": \"SEFFIR\",
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"gitRepoUrl\": \"\",
\"leantimeProjectId\": \"517\",
\"documentationCollectionId\": \"08919836-435a-466f-a38a-014991759da2\",
\"rocketchatChannelId\": \"${ROCKETCHAT_CHANNEL_ID}\"
}" \
-s | jq '.'

158
test-n8n-vm.sh Normal file
View File

@ -0,0 +1,158 @@
#!/bin/bash
# Script pour tester le webhook N8N depuis la VM
# Utilise les outils disponibles: curl, wget, python3, node
# Charger .env.local si présent
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
# Variables
WEBHOOK_URL="https://brain.slm-lab.net/webhook-test/mission-created"
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY}"
# Vérifier que l'API key est définie
if [ -z "$API_KEY" ]; then
echo "❌ Erreur: N8N_API_KEY n'est pas définie"
echo " Vérifiez votre fichier .env.local ou exportez N8N_API_KEY"
exit 1
fi
echo "🧪 Test du webhook N8N depuis la VM"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Webhook URL: $WEBHOOK_URL"
echo "Mission ID: $MISSION_ID"
echo "API URL: $API_URL"
echo ""
# Préparer le JSON
JSON_DATA=$(cat <<EOF
{
"name": "SEFFIR",
"oddScope": ["odd-4"],
"niveau": "s",
"intention": "",
"missionType": "remote",
"donneurDOrdre": "group",
"projection": "long",
"services": [],
"participation": "ouvert",
"profils": [],
"guardians": {},
"volunteers": [],
"creatorId": "203cbc91-61ab-47a2-95d2-b5e1159327d7",
"missionId": "${MISSION_ID}",
"logoPath": "missions/${MISSION_ID}/logo.png",
"logoUrl": "https://hub.slm-lab.net/api/missions/image/missions/${MISSION_ID}/logo.png",
"config": {
"N8N_API_KEY": "${API_KEY}",
"MISSION_API_URL": "${API_URL}"
}
}
EOF
)
# Essayer curl d'abord, puis wget, puis python3
if command -v curl &> /dev/null; then
echo "📤 Utilisation de curl..."
echo ""
curl -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "$JSON_DATA" \
-v
elif command -v wget &> /dev/null; then
echo "📤 Utilisation de wget..."
echo ""
echo "$JSON_DATA" | wget --method=POST \
--header="Content-Type: application/json" \
--body-data=- \
--output-document=- \
--server-response \
"${WEBHOOK_URL}" 2>&1
elif command -v python3 &> /dev/null; then
echo "📤 Utilisation de python3..."
echo ""
python3 <<PYTHON_SCRIPT
import urllib.request
import urllib.parse
import json
import sys
url = "${WEBHOOK_URL}"
data = ${JSON_DATA}
json_data = json.dumps(data).encode('utf-8')
req = urllib.request.Request(url, data=json_data, headers={'Content-Type': 'application/json'})
try:
with urllib.request.urlopen(req) as response:
print(f"Status: {response.status} {response.reason}")
print(f"Headers: {dict(response.headers)}")
print(f"\nResponse Body:")
print(response.read().decode('utf-8'))
except urllib.error.HTTPError as e:
print(f"HTTP Error {e.code}: {e.reason}")
print(e.read().decode('utf-8'))
sys.exit(1)
PYTHON_SCRIPT
elif command -v node &> /dev/null; then
echo "📤 Utilisation de node..."
echo ""
node <<NODE_SCRIPT
const https = require('https');
const http = require('http');
const url = new URL('${WEBHOOK_URL}');
const data = ${JSON_DATA};
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(data))
}
};
const client = url.protocol === 'https:' ? https : http;
const req = client.request(options, (res) => {
console.log(\`Status: \${res.statusCode} \${res.statusMessage}\`);
console.log(\`Headers:\`, res.headers);
console.log(\`\nResponse Body:\`);
let body = '';
res.on('data', (chunk) => { body += chunk; });
res.on('end', () => {
try {
console.log(JSON.stringify(JSON.parse(body), null, 2));
} catch (e) {
console.log(body);
}
});
});
req.on('error', (e) => {
console.error(\`Erreur: \${e.message}\`);
process.exit(1);
});
req.write(JSON.stringify(data));
req.end();
NODE_SCRIPT
else
echo "❌ Aucun outil disponible (curl, wget, python3, node)"
exit 1
fi
echo ""
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Test terminé"
echo ""
echo "💡 Vérifiez les logs N8N pour voir la structure de la réponse RocketChat"

View File

@ -0,0 +1,62 @@
#!/bin/bash
# Test direct du webhook N8N
# Usage: ./test-n8n-webhook-direct.sh [MISSION_ID] [ROCKETCHAT_CHANNEL_ID]
# Charger .env.local si présent
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
# URL du webhook N8N
WEBHOOK_URL="https://brain.slm-lab.net/webhook-test/mission-created"
# Paramètres optionnels
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
echo "🧪 Test du webhook N8N directement"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Webhook URL: $WEBHOOK_URL"
echo "Mission ID: $MISSION_ID"
echo "RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo ""
# Test avec tous les champs (simule ce que Next.js envoie à N8N)
echo "📤 Envoi de la requête complète au webhook N8N..."
echo ""
curl -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"name\": \"SEFFIR\",
\"oddScope\": [\"odd-4\"],
\"niveau\": \"s\",
\"missionType\": \"remote\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"services\": [],
\"participation\": \"ouvert\",
\"profils\": [],
\"hasGuardians\": true,
\"volunteersCount\": 0,
\"hasLogo\": true,
\"config\": {
\"MISSION_API_URL\": \"${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}\",
\"N8N_API_KEY\": \"${N8N_API_KEY}\",
\"ROCKETCHAT_API_URL\": \"${ROCKET_CHAT_API_URL:-https://parole.slm-lab.net}\",
\"ROCKETCHAT_AUTH_TOKEN\": \"${ROCKET_CHAT_TOKEN}\",
\"ROCKETCHAT_USER_ID\": \"${ROCKET_CHAT_USER_ID}\"
},
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\"
}" \
-v
echo ""
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Test terminé"
echo ""
echo "💡 Note: Ce test déclenche le workflow N8N complet."
echo " Vérifiez les logs N8N pour voir la structure exacte de la réponse RocketChat."

116
test-n8n-webhook-env.sh Normal file
View File

@ -0,0 +1,116 @@
#!/bin/bash
# Script pour tester le webhook N8N en utilisant les variables d'environnement
# Usage: source .env.local && ./test-n8n-webhook-env.sh [MISSION_ID] [ROCKETCHAT_CHANNEL_ID]
# Charger les variables d'environnement depuis .env.local si elles existent
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
# Configuration depuis les variables d'environnement
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY}"
# Vérifier que l'API key est définie
if [ -z "$API_KEY" ]; then
echo "❌ Erreur: N8N_API_KEY n'est pas définie dans les variables d'environnement"
echo " Assurez-vous d'avoir chargé .env.local ou défini N8N_API_KEY"
exit 1
fi
# Paramètres (utiliser ceux fournis ou des valeurs par défaut pour test)
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
echo "🧪 Test du webhook N8N mission-created"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "API URL: $API_URL"
echo "Mission ID: $MISSION_ID"
echo "RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo "API Key: ${API_KEY:0:10}... (masquée)"
echo ""
# Test principal avec tous les champs
echo "📤 Envoi de la requête avec RocketChat Channel ID..."
echo ""
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"name\": \"SEFFIR\",
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"gitRepoUrl\": \"\",
\"leantimeProjectId\": \"517\",
\"documentationCollectionId\": \"08919836-435a-466f-a38a-014991759da2\",
\"rocketchatChannelId\": \"${ROCKETCHAT_CHANNEL_ID}\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"missionType\": \"remote\",
\"niveau\": \"s\"
}")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
echo "📥 Réponse HTTP: $HTTP_CODE"
echo "📄 Corps de la réponse:"
echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY"
echo ""
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ Succès! La mission a été mise à jour."
else
echo "❌ Erreur HTTP $HTTP_CODE"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "🔍 Tests supplémentaires avec différents formats de rocketchatChannelId:"
echo ""
# Test 1: ID valide
echo "=== Test 1: ID RocketChat valide ==="
curl -s -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": \"ByehQjC44FwMeiLbX\"
}" | jq '.' || echo "Erreur de parsing JSON"
echo ""
# Test 2: null
echo "=== Test 2: ID RocketChat null ==="
curl -s -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": null
}" | jq '.' || echo "Erreur de parsing JSON"
echo ""
# Test 3: chaîne vide
echo "=== Test 3: ID RocketChat chaîne vide ==="
curl -s -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": \"\"
}" | jq '.' || echo "Erreur de parsing JSON"
echo ""
# Test 4: non fourni
echo "=== Test 4: ID RocketChat non fourni ==="
curl -s -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\"
}" | jq '.' || echo "Erreur de parsing JSON"
echo ""

View File

@ -0,0 +1,65 @@
#!/bin/bash
# Test précis du webhook N8N avec la structure exacte envoyée par Next.js
# Usage: ./test-n8n-webhook-precise.sh [MISSION_ID] [ROCKETCHAT_CHANNEL_ID]
# Charger .env.local
if [ -f .env.local ]; then
export $(grep -v '^#' .env.local | xargs)
fi
# URL du webhook N8N
WEBHOOK_URL="https://brain.slm-lab.net/webhook-test/mission-created"
# Variables d'environnement
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY}"
# Paramètres optionnels
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
echo "🧪 Test précis du webhook N8N"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Webhook URL: $WEBHOOK_URL"
echo "Mission ID: $MISSION_ID"
echo "RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo ""
# Structure exacte envoyée par Next.js (après nettoyage dans n8n-service.ts)
curl -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"SEFFIR\",
\"oddScope\": [\"odd-4\"],
\"niveau\": \"s\",
\"intention\": \"\",
\"missionType\": \"remote\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"services\": [],
\"participation\": \"ouvert\",
\"profils\": [],
\"guardians\": {},
\"volunteers\": [],
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"missionId\": \"${MISSION_ID}\",
\"logoPath\": \"missions/${MISSION_ID}/logo.png\",
\"logoUrl\": \"https://hub.slm-lab.net/api/missions/image/missions/${MISSION_ID}/logo.png\",
\"config\": {
\"N8N_API_KEY\": \"${API_KEY}\",
\"MISSION_API_URL\": \"${API_URL}\"
}
}" \
-v
echo ""
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Test terminé"
echo ""
echo "💡 Ce test déclenche le workflow N8N complet."
echo " Vérifiez les logs N8N pour voir:"
echo " 1. La structure de la réponse RocketChat"
echo " 2. Le chemin exact pour accéder à channel._id"
echo " 3. La valeur exacte de rocketchatChannelId dans 'Save Mission To API'"

84
test-n8n-webhook.sh Normal file
View File

@ -0,0 +1,84 @@
#!/bin/bash
# Script pour tester le webhook N8N mission-created
# Usage: ./test-n8n-webhook.sh [MISSION_ID] [ROCKETCHAT_CHANNEL_ID]
# Configuration
API_URL="${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net/api}"
API_KEY="${N8N_API_KEY:-your-api-key-here}"
# Paramètres (utiliser ceux fournis ou des valeurs par défaut pour test)
MISSION_ID="${1:-3103ec1a-acde-4025-9ead-4e1a0ddc047c}"
ROCKETCHAT_CHANNEL_ID="${2:-ByehQjC44FwMeiLbX}"
echo "Testing N8N webhook output..."
echo "API URL: $API_URL"
echo "Mission ID: $MISSION_ID"
echo "RocketChat Channel ID: $ROCKETCHAT_CHANNEL_ID"
echo ""
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"name\": \"SEFFIR\",
\"creatorId\": \"203cbc91-61ab-47a2-95d2-b5e1159327d7\",
\"gitRepoUrl\": \"\",
\"leantimeProjectId\": \"517\",
\"documentationCollectionId\": \"08919836-435a-466f-a38a-014991759da2\",
\"rocketchatChannelId\": \"${ROCKETCHAT_CHANNEL_ID}\",
\"donneurDOrdre\": \"group\",
\"projection\": \"long\",
\"missionType\": \"remote\",
\"niveau\": \"s\"
}" \
-v
echo ""
echo ""
echo "Test avec différents formats de rocketchatChannelId:"
echo ""
# Test 1: ID valide
echo "=== Test 1: ID RocketChat valide ==="
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": \"ByehQjC44FwMeiLbX\"
}" \
-s | jq '.'
echo ""
echo "=== Test 2: ID RocketChat null ==="
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": null
}" \
-s | jq '.'
echo ""
echo "=== Test 3: ID RocketChat chaîne vide ==="
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\",
\"rocketchatChannelId\": \"\"
}" \
-s | jq '.'
echo ""
echo "=== Test 4: ID RocketChat non fourni ==="
curl -X POST "${API_URL}/missions/mission-created" \
-H "Content-Type: application/json" \
-H "x-api-key: ${API_KEY}" \
-d "{
\"missionId\": \"${MISSION_ID}\"
}" \
-s | jq '.'