diff --git a/app/api/missions/mission-created/route.ts b/app/api/missions/mission-created/route.ts
index c5403be..aec97c4 100644
--- a/app/api/missions/mission-created/route.ts
+++ b/app/api/missions/mission-created/route.ts
@@ -54,7 +54,13 @@ export async function POST(request: Request) {
logger.debug('Received mission-created data', {
hasMissionId: !!body.missionId,
hasName: !!body.name,
- hasCreatorId: !!body.creatorId
+ hasCreatorId: !!body.creatorId,
+ gitRepoUrl: body.gitRepoUrl,
+ leantimeProjectId: body.leantimeProjectId,
+ documentationCollectionId: body.documentationCollectionId,
+ rocketchatChannelId: body.rocketchatChannelId,
+ gitRepoUrlType: typeof body.gitRepoUrl,
+ rocketchatChannelIdType: typeof body.rocketchatChannelId,
});
// Validation des champs requis
@@ -125,28 +131,71 @@ export async function POST(request: Request) {
rocketChatChannelId?: string | null;
} = {};
+ // Helper function to check if a value is valid
+ const isValidValue = (value: any, type?: 'gitea' | 'outline' | 'rocketchat' | 'leantime'): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ if (trimmed === '' || trimmed === '0' || trimmed === 'null' || trimmed === 'undefined') return false;
+
+ // Additional validation based on type
+ if (type === 'gitea') {
+ // Reject Outline collection paths (they start with /collection/)
+ if (trimmed.startsWith('/collection/')) return false;
+ // Reject if it doesn't look like a URL or repo path
+ if (!trimmed.includes('http') && !trimmed.includes('/') && trimmed.length < 3) return false;
+ }
+ if (type === 'outline') {
+ // Outline IDs should be UUIDs or paths
+ if (trimmed.startsWith('/collection/')) return true; // This is valid for Outline
+ }
+ if (type === 'rocketchat') {
+ // RocketChat channel IDs should be alphanumeric strings
+ if (trimmed.length < 3) return false;
+ }
+ return true;
+ }
+ if (typeof value === 'number') return value !== 0;
+ return true;
+ };
+
// Mapper les champs N8N vers notre schéma Prisma
- // Vérifier que les valeurs ne sont pas des chaînes vides
+ // Vérifier que les valeurs ne sont pas des chaînes vides, "0", "null", "undefined", etc.
+ // ET vérifier qu'elles correspondent au bon type (pas de mélange Gitea/Outline)
if (body.gitRepoUrl !== undefined) {
- updateData.giteaRepositoryUrl = (body.gitRepoUrl && body.gitRepoUrl.trim() !== '') ? body.gitRepoUrl : null;
- logger.debug('Updating giteaRepositoryUrl', { hasUrl: !!updateData.giteaRepositoryUrl, value: body.gitRepoUrl });
+ const isValid = isValidValue(body.gitRepoUrl, 'gitea');
+ updateData.giteaRepositoryUrl = isValid ? body.gitRepoUrl : null;
+ logger.debug('Updating giteaRepositoryUrl', {
+ hasUrl: !!updateData.giteaRepositoryUrl,
+ value: body.gitRepoUrl,
+ isValid,
+ isOutlinePath: typeof body.gitRepoUrl === 'string' && body.gitRepoUrl.trim().startsWith('/collection/')
+ });
}
if (body.leantimeProjectId !== undefined) {
// N8N peut retourner un number, on le convertit en string
const projectId = body.leantimeProjectId ? String(body.leantimeProjectId).trim() : '';
- updateData.leantimeProjectId = projectId !== '' ? projectId : null;
- logger.debug('Updating leantimeProjectId', { hasId: !!updateData.leantimeProjectId, value: body.leantimeProjectId });
+ const isValid = isValidValue(projectId, 'leantime');
+ updateData.leantimeProjectId = isValid ? projectId : null;
+ logger.debug('Updating leantimeProjectId', { hasId: !!updateData.leantimeProjectId, value: body.leantimeProjectId, isValid });
}
if (body.documentationCollectionId !== undefined) {
- updateData.outlineCollectionId = (body.documentationCollectionId && body.documentationCollectionId.trim() !== '') ? body.documentationCollectionId : null;
- logger.debug('Updating outlineCollectionId', { hasId: !!updateData.outlineCollectionId, value: body.documentationCollectionId });
+ const isValid = isValidValue(body.documentationCollectionId, 'outline');
+ updateData.outlineCollectionId = isValid ? body.documentationCollectionId : null;
+ logger.debug('Updating outlineCollectionId', { hasId: !!updateData.outlineCollectionId, value: body.documentationCollectionId, isValid });
}
if (body.rocketchatChannelId !== undefined) {
- updateData.rocketChatChannelId = (body.rocketchatChannelId && body.rocketchatChannelId.trim() !== '') ? body.rocketchatChannelId : null;
- logger.debug('Updating rocketChatChannelId', { hasId: !!updateData.rocketChatChannelId, value: body.rocketchatChannelId });
+ const isValid = isValidValue(body.rocketchatChannelId, 'rocketchat');
+ updateData.rocketChatChannelId = isValid ? body.rocketchatChannelId : null;
+ logger.debug('Updating rocketChatChannelId', {
+ hasId: !!updateData.rocketChatChannelId,
+ value: body.rocketchatChannelId,
+ isValid,
+ valueType: typeof body.rocketchatChannelId
+ });
}
// Vérifier qu'il y a au moins un champ à mettre à jour
diff --git a/app/mission-tab/[missionId]/page.tsx b/app/mission-tab/[missionId]/page.tsx
index 8e854b3..d8eeb80 100644
--- a/app/mission-tab/[missionId]/page.tsx
+++ b/app/mission-tab/[missionId]/page.tsx
@@ -384,14 +384,50 @@ export default function MissionTabDetailPage() {
)}
{/* Ressources Métiers */}
- {((mission.rocketChatChannelId && mission.rocketChatChannelId.trim() !== '') ||
- (mission.outlineCollectionId && mission.outlineCollectionId.trim() !== '') ||
- (mission.giteaRepositoryUrl && mission.giteaRepositoryUrl.trim() !== '') ||
- (mission.leantimeProjectId && mission.leantimeProjectId.toString().trim() !== '')) && (
+ {(() => {
+ // Helper function to check if a value is valid (not null, not undefined, not empty string, not "0", not "null", not "undefined")
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ if (typeof value === 'number') return value !== 0;
+ return true;
+ };
+
+ const hasRocketChat = isValid(mission.rocketChatChannelId);
+ const hasOutline = isValid(mission.outlineCollectionId);
+ const hasGitea = isValid(mission.giteaRepositoryUrl);
+ const hasLeantime = isValid(mission.leantimeProjectId);
+
+ console.log('Ressources Métiers check (mission-tab):', {
+ hasRocketChat,
+ hasOutline,
+ hasGitea,
+ hasLeantime,
+ rocketChatValue: mission.rocketChatChannelId,
+ outlineValue: mission.outlineCollectionId,
+ giteaValue: mission.giteaRepositoryUrl,
+ leantimeValue: mission.leantimeProjectId,
+ });
+
+ return hasRocketChat || hasOutline || hasGitea || hasLeantime;
+ })() && (
Ressources Métiers
- {mission.rocketChatChannelId && mission.rocketChatChannelId.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.rocketChatChannelId);
+ })() && (() => {
// Build RocketChat URL: parole.slm-lab.net/channel/[channelId]
// If it's already a full URL, use it; otherwise build from ID
const rocketChatUrl = mission.rocketChatChannelId.startsWith('http')
@@ -416,7 +452,17 @@ export default function MissionTabDetailPage() {
);
})()}
- {mission.outlineCollectionId && mission.outlineCollectionId.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.outlineCollectionId);
+ })() && (() => {
// Build Outline URL: chapitre.slm-lab.net/collection/[collectionId]
// If it's already a full URL, use it; otherwise build from ID
const outlineUrl = mission.outlineCollectionId.startsWith('http')
@@ -441,7 +487,17 @@ export default function MissionTabDetailPage() {
);
})()}
- {mission.giteaRepositoryUrl && mission.giteaRepositoryUrl.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.giteaRepositoryUrl);
+ })() && (() => {
// Use mission name (sanitized) as repo name
const repoName = sanitizeMissionName(mission.name);
@@ -472,7 +528,18 @@ export default function MissionTabDetailPage() {
);
})()}
- {mission.leantimeProjectId && mission.leantimeProjectId.toString().trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ if (typeof value === 'number') return value !== 0;
+ return true;
+ };
+ return isValid(mission.leantimeProjectId);
+ })() && (() => {
// Build Leantime URL: agilite.slm-lab.net/projects/showProject/[projectId]
// If it's already a full URL, use it; otherwise build from ID
const leantimeUrl = mission.leantimeProjectId.startsWith('http')
diff --git a/app/missions/[missionId]/page.tsx b/app/missions/[missionId]/page.tsx
index 7286988..e12327d 100644
--- a/app/missions/[missionId]/page.tsx
+++ b/app/missions/[missionId]/page.tsx
@@ -94,6 +94,16 @@ export default function MissionDetailPage() {
}
const data = await response.json();
console.log("Mission details:", data);
+ console.log("Integration IDs:", {
+ rocketChatChannelId: data.rocketChatChannelId,
+ outlineCollectionId: data.outlineCollectionId,
+ giteaRepositoryUrl: data.giteaRepositoryUrl,
+ leantimeProjectId: data.leantimeProjectId,
+ rocketChatType: typeof data.rocketChatChannelId,
+ outlineType: typeof data.outlineCollectionId,
+ giteaType: typeof data.giteaRepositoryUrl,
+ leantimeType: typeof data.leantimeProjectId,
+ });
setMission(data);
setEditedPlan(data.actionPlan || "");
} catch (error) {
@@ -604,14 +614,50 @@ export default function MissionDetailPage() {
)}
{/* Ressources Métiers */}
- {((mission.rocketChatChannelId && mission.rocketChatChannelId.trim() !== '') ||
- (mission.outlineCollectionId && mission.outlineCollectionId.trim() !== '') ||
- (mission.giteaRepositoryUrl && mission.giteaRepositoryUrl.trim() !== '') ||
- (mission.leantimeProjectId && mission.leantimeProjectId.toString().trim() !== '')) && (
+ {(() => {
+ // Helper function to check if a value is valid (not null, not undefined, not empty string, not "0", not "null", not "undefined")
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ if (typeof value === 'number') return value !== 0;
+ return true;
+ };
+
+ const hasRocketChat = isValid(mission.rocketChatChannelId);
+ const hasOutline = isValid(mission.outlineCollectionId);
+ const hasGitea = isValid(mission.giteaRepositoryUrl);
+ const hasLeantime = isValid(mission.leantimeProjectId);
+
+ console.log('Ressources Métiers check:', {
+ hasRocketChat,
+ hasOutline,
+ hasGitea,
+ hasLeantime,
+ rocketChatValue: mission.rocketChatChannelId,
+ outlineValue: mission.outlineCollectionId,
+ giteaValue: mission.giteaRepositoryUrl,
+ leantimeValue: mission.leantimeProjectId,
+ });
+
+ return hasRocketChat || hasOutline || hasGitea || hasLeantime;
+ })() && (
Ressources Métiers
- {mission.rocketChatChannelId && mission.rocketChatChannelId.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.rocketChatChannelId);
+ })() && (() => {
// Build RocketChat URL: parole.slm-lab.net/channel/[channelId]
// If it's already a full URL, use it; otherwise build from ID
const rocketChatUrl = mission.rocketChatChannelId.startsWith('http')
@@ -636,7 +682,17 @@ export default function MissionDetailPage() {
);
})()}
- {mission.outlineCollectionId && mission.outlineCollectionId.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.outlineCollectionId);
+ })() && (() => {
// Build Outline URL: chapitre.slm-lab.net/collection/[collectionId]
// If it's already a full URL, use it; otherwise build from ID
const outlineUrl = mission.outlineCollectionId.startsWith('http')
@@ -661,7 +717,17 @@ export default function MissionDetailPage() {
);
})()}
- {mission.giteaRepositoryUrl && mission.giteaRepositoryUrl.trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ return true;
+ };
+ return isValid(mission.giteaRepositoryUrl);
+ })() && (() => {
// Use mission name (sanitized) as repo name
const repoName = sanitizeMissionName(mission.name);
@@ -692,7 +758,18 @@ export default function MissionDetailPage() {
);
})()}
- {mission.leantimeProjectId && mission.leantimeProjectId.toString().trim() !== '' && (() => {
+ {(() => {
+ const isValid = (value: any): boolean => {
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ const trimmed = value.trim();
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'null' && trimmed !== 'undefined';
+ }
+ if (typeof value === 'number') return value !== 0;
+ return true;
+ };
+ return isValid(mission.leantimeProjectId);
+ })() && (() => {
// Build Leantime URL: agilite.slm-lab.net/projects/showProject/[projectId]
// If it's already a full URL, use it; otherwise build from ID
const leantimeUrl = mission.leantimeProjectId.startsWith('http')