From 5e3289f0e14cb3dc895244e3827a1e9b2bb026af Mon Sep 17 00:00:00 2001 From: alma Date: Tue, 13 Jan 2026 23:20:18 +0100 Subject: [PATCH] Missions Corrections --- app/api/missions/mission-created/route.ts | 69 ++++++++++++++--- app/mission-tab/[missionId]/page.tsx | 83 ++++++++++++++++++-- app/missions/[missionId]/page.tsx | 93 +++++++++++++++++++++-- 3 files changed, 219 insertions(+), 26 deletions(-) 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')