diff --git a/app/api/missions/[missionId]/files/folder/route.ts b/app/api/missions/[missionId]/files/folder/route.ts index 867fa66..3a6ef92 100644 --- a/app/api/missions/[missionId]/files/folder/route.ts +++ b/app/api/missions/[missionId]/files/folder/route.ts @@ -4,19 +4,36 @@ import { authOptions } from "@/app/api/auth/options"; import { prisma } from '@/lib/prisma'; import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +// S3 Configuration for missions - uses environment variables +const MISSIONS_S3_CONFIG = { + endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''), + region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1', + accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '', + secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '', + bucket: 'missions' // Missions bucket is always 'missions' +}; + +// Validate required S3 configuration +if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) { + const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.'; + console.error(errorMsg); + if (process.env.NODE_ENV === 'production') { + throw new Error('S3 credentials are required in production environment'); + } +} + // Use the exact same S3 client configuration as mission-uploads.ts -// Use hardcoded credentials as primary (same as mission-uploads.ts) const missionsS3Client = new S3Client({ - region: 'us-east-1', - endpoint: 'https://dome-api.slm-lab.net', + region: MISSIONS_S3_CONFIG.region, + endpoint: MISSIONS_S3_CONFIG.endpoint, credentials: { - accessKeyId: '4aBT4CMb7JIMMyUtp4Pl', // Primary: hardcoded (same as mission-uploads.ts) - secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg' // Primary: hardcoded (same as mission-uploads.ts) + accessKeyId: MISSIONS_S3_CONFIG.accessKey, + secretAccessKey: MISSIONS_S3_CONFIG.secretKey }, forcePathStyle: true // Required for MinIO }); -const MISSIONS_BUCKET = 'missions'; +const MISSIONS_BUCKET = MISSIONS_S3_CONFIG.bucket; // Helper function to check if user can manage files (creator or gardien) // Also checks if mission is closed (closed missions cannot be modified) diff --git a/app/api/missions/[missionId]/files/route.ts b/app/api/missions/[missionId]/files/route.ts index 60ce9ff..2c17d13 100644 --- a/app/api/missions/[missionId]/files/route.ts +++ b/app/api/missions/[missionId]/files/route.ts @@ -5,19 +5,37 @@ import { prisma } from '@/lib/prisma'; import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'; import { Readable } from 'stream'; +// S3 Configuration for missions - uses environment variables +const MISSIONS_S3_CONFIG = { + endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''), + region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1', + accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '', + secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '', + bucket: 'missions' // Missions bucket is always 'missions' +}; + +// Validate required S3 configuration +if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) { + const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.'; + console.error(errorMsg); + if (process.env.NODE_ENV === 'production') { + throw new Error('S3 credentials are required in production environment'); + } +} + // Use the exact same S3 client configuration as mission-uploads.ts and image route // This ensures we use the same credentials and settings that work for other mission operations const missionsS3Client = new S3Client({ - region: 'us-east-1', - endpoint: 'https://dome-api.slm-lab.net', + region: MISSIONS_S3_CONFIG.region, + endpoint: MISSIONS_S3_CONFIG.endpoint, credentials: { - accessKeyId: '4aBT4CMb7JIMMyUtp4Pl', - secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg' + accessKeyId: MISSIONS_S3_CONFIG.accessKey, + secretAccessKey: MISSIONS_S3_CONFIG.secretKey }, forcePathStyle: true // Required for MinIO }); -const MISSIONS_BUCKET = 'missions'; +const MISSIONS_BUCKET = MISSIONS_S3_CONFIG.bucket; // Helper function to check if user has access to mission async function checkMissionAccess(userId: string, missionId: string): Promise { diff --git a/app/api/missions/image/[...path]/route.ts b/app/api/missions/image/[...path]/route.ts index 69f35a1..05ea9da 100644 --- a/app/api/missions/image/[...path]/route.ts +++ b/app/api/missions/image/[...path]/route.ts @@ -5,13 +5,31 @@ import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; import { NoSuchKey } from '@aws-sdk/client-s3'; import { logger } from '@/lib/logger'; +// S3 Configuration for missions - uses environment variables +const MISSIONS_S3_CONFIG = { + endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''), + region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1', + accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '', + secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '', + bucket: 'missions' // Missions bucket is always 'missions' +}; + +// Validate required S3 configuration +if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) { + const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.'; + console.error(errorMsg); + if (process.env.NODE_ENV === 'production') { + throw new Error('S3 credentials are required in production environment'); + } +} + // Initialize S3 client const s3Client = new S3Client({ - region: 'us-east-1', - endpoint: 'https://dome-api.slm-lab.net', + region: MISSIONS_S3_CONFIG.region, + endpoint: MISSIONS_S3_CONFIG.endpoint, credentials: { - accessKeyId: '4aBT4CMb7JIMMyUtp4Pl', - secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg' + accessKeyId: MISSIONS_S3_CONFIG.accessKey, + secretAccessKey: MISSIONS_S3_CONFIG.secretKey }, forcePathStyle: true // Required for MinIO }); @@ -43,7 +61,7 @@ export async function GET( }); const command = new GetObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, }); diff --git a/app/api/missions/route.ts b/app/api/missions/route.ts index d121c6e..f56b80f 100644 --- a/app/api/missions/route.ts +++ b/app/api/missions/route.ts @@ -9,6 +9,9 @@ import { CopyObjectCommand, DeleteObjectCommand, HeadObjectCommand } from '@aws- import { uploadMissionLogo, uploadMissionAttachment, getMissionFileUrl } from '@/lib/mission-uploads'; import { logger } from '@/lib/logger'; +// Missions bucket name - consistent across all mission file operations +const MISSIONS_BUCKET = 'missions'; + // Types interface MissionCreateInput { name: string; @@ -194,7 +197,7 @@ export async function GET(request: Request) { async function verifyFileExists(filePath: string): Promise { try { await s3Client.send(new HeadObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_BUCKET, Key: filePath.replace('missions/', '') })); return true; @@ -530,7 +533,7 @@ export async function POST(request: Request) { for (const file of uploadedFiles) { try { await s3Client.send(new DeleteObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_BUCKET, Key: file.path.replace('missions/', '') })); logger.debug('Cleaned up file', { path: file.path }); diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f443556..85f8d70 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -148,10 +148,21 @@ services: NODE_ENV: production NEXT_TELEMETRY_DISABLED: 1 + # MinIO / S3 Configuration (required for mission file uploads) + MINIO_S3_UPLOAD_BUCKET_URL: ${MINIO_S3_UPLOAD_BUCKET_URL:-https://dome-api.slm-lab.net} + MINIO_AWS_REGION: ${MINIO_AWS_REGION:-us-east-1} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + + # API URLs (required for mission creation) + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://hub.slm-lab.net} + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-https://hub.slm-lab.net} + + # N8N Integration (required for mission creation workflow) + N8N_API_KEY: ${N8N_API_KEY} + N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL:-https://brain.slm-lab.net/webhook/mission-created} + # Autres variables d'environnement (ajoutez les vôtres) - # MINIO_S3_UPLOAD_BUCKET_URL: ${MINIO_S3_UPLOAD_BUCKET_URL} - # MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} - # MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} # volumes: # Optionnel: monter des fichiers de configuration ou des uploads # - ./uploads:/app/uploads diff --git a/env.production.example b/env.production.example index 860e29c..059e356 100644 --- a/env.production.example +++ b/env.production.example @@ -42,13 +42,27 @@ NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 # ============================================ -# MinIO / S3 (si utilisé) +# MinIO / S3 (REQUIS pour les missions) # ============================================ -# MINIO_S3_UPLOAD_BUCKET_URL=http://minio:9000 -# MINIO_AWS_REGION=us-east-1 -# MINIO_AWS_S3_UPLOAD_BUCKET_NAME=missions -# MINIO_ACCESS_KEY=minioadmin -# MINIO_SECRET_KEY=CHANGEZ_CE_MOT_DE_PASSE +# Ces variables sont OBLIGATOIRES pour créer des missions avec logos et pièces jointes +MINIO_S3_UPLOAD_BUCKET_URL=https://dome-api.slm-lab.net +MINIO_AWS_REGION=us-east-1 +MINIO_AWS_S3_UPLOAD_BUCKET_NAME=missions +MINIO_ACCESS_KEY=VOTRE_ACCESS_KEY +MINIO_SECRET_KEY=VOTRE_SECRET_KEY + +# ============================================ +# URLs de l'application (REQUIS pour les missions) +# ============================================ +NEXT_PUBLIC_API_URL=https://hub.slm-lab.net +NEXT_PUBLIC_APP_URL=https://hub.slm-lab.net + +# ============================================ +# N8N Integration (REQUIS pour les missions) +# ============================================ +# N8N est utilisé pour créer automatiquement les projets Leantime, repos Git, etc. +N8N_API_KEY=VOTRE_N8N_API_KEY +N8N_WEBHOOK_URL=https://brain.slm-lab.net/webhook/mission-created # ============================================ # Autres services (ajoutez selon vos besoins) diff --git a/lib/mission-uploads.ts b/lib/mission-uploads.ts index 9e8615c..eafdb92 100644 --- a/lib/mission-uploads.ts +++ b/lib/mission-uploads.ts @@ -5,13 +5,31 @@ import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; import { logger } from '@/lib/logger'; +// S3 Configuration for missions - uses environment variables +const MISSIONS_S3_CONFIG = { + endpoint: (process.env.MINIO_S3_UPLOAD_BUCKET_URL || process.env.S3_ENDPOINT || 'https://dome-api.slm-lab.net').replace(/\/$/, ''), + region: process.env.MINIO_AWS_REGION || process.env.S3_REGION || 'us-east-1', + accessKey: process.env.MINIO_ACCESS_KEY || process.env.S3_ACCESS_KEY || '', + secretKey: process.env.MINIO_SECRET_KEY || process.env.S3_SECRET_KEY || '', + bucket: 'missions' // Missions bucket is always 'missions' +}; + +// Validate required S3 configuration +if (!MISSIONS_S3_CONFIG.accessKey || !MISSIONS_S3_CONFIG.secretKey) { + const errorMsg = '⚠️ S3 credentials are missing! Please set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables.'; + console.error(errorMsg); + if (process.env.NODE_ENV === 'production') { + throw new Error('S3 credentials are required in production environment'); + } +} + // Initialize S3 client for Minio const s3Client = new S3Client({ - region: 'us-east-1', - endpoint: 'https://dome-api.slm-lab.net', + region: MISSIONS_S3_CONFIG.region, + endpoint: MISSIONS_S3_CONFIG.endpoint, credentials: { - accessKeyId: '4aBT4CMb7JIMMyUtp4Pl', - secretAccessKey: 'HGn39XhCIlqOjmDVzRK9MED2Fci2rYvDDgbLFElg' + accessKeyId: MISSIONS_S3_CONFIG.accessKey, + secretAccessKey: MISSIONS_S3_CONFIG.secretKey }, forcePathStyle: true // Required for MinIO }); @@ -53,7 +71,7 @@ export async function deleteMissionLogo(missionId: string, logoPath: string): Pr }); const command = new DeleteObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, }); @@ -83,7 +101,7 @@ export async function deleteMissionAttachment(filePath: string): Promise { }); const command = new DeleteObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, }); @@ -118,13 +136,13 @@ export async function uploadMissionLogo(userId: string, missionId: string, file: const buffer = Buffer.from(arrayBuffer); logger.debug('Uploading to Minio', { - bucket: 'missions', + bucket: MISSIONS_S3_CONFIG.bucket, key: minioPath, contentType: file.type }); await s3Client.send(new PutObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, Body: buffer, ContentType: file.type, @@ -174,13 +192,13 @@ export async function uploadMissionAttachment( const buffer = Buffer.from(arrayBuffer); logger.debug('Uploading to Minio', { - bucket: 'missions', + bucket: MISSIONS_S3_CONFIG.bucket, key: minioPath, contentType: file.type }); await s3Client.send(new PutObjectCommand({ - Bucket: 'missions', + Bucket: MISSIONS_S3_CONFIG.bucket, Key: minioPath, Body: buffer, ContentType: file.type,