/** * Room analysis API routes * Handles image upload and OpenAI Vision analysis */ import multer from 'multer'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { analyzeRoomPhoto, analysisTo3DConfig } from '../services/roomAnalyzer.js'; import pool, { run } from '../db/index.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Configure multer for image uploads const storage = multer.memoryStorage(); const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 // 10MB max }, fileFilter: (req, file, cb) => { const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('Type de fichier non supporté. Utilisez JPEG, PNG ou WebP.')); } } }); // Directory for storing uploaded room photos const UPLOADS_DIR = path.join(__dirname, '..', '..', 'client', 'public', 'rooms'); if (!fs.existsSync(UPLOADS_DIR)) { fs.mkdirSync(UPLOADS_DIR, { recursive: true }); } /** * Middleware for single image upload */ export const uploadMiddleware = upload.single('image'); /** * POST /api/room/analyze * Upload a room photo and analyze it with GPT-4 Vision */ export async function analyzeRoom(req, res) { try { if (!req.file) { return res.status(400).json({ error: 'Aucune image fournie' }); } // Convert buffer to base64 const imageBase64 = req.file.buffer.toString('base64'); const mimeType = req.file.mimetype; // Analyze with OpenAI console.log('Analyzing room photo with GPT-4 Vision...'); const result = await analyzeRoomPhoto(imageBase64, mimeType); if (!result.success) { return res.status(500).json({ error: 'Échec de l\'analyse' }); } // Save the image for future reference const filename = `room_${Date.now()}.${mimeType.split('/')[1]}`; const filepath = path.join(UPLOADS_DIR, filename); fs.writeFileSync(filepath, req.file.buffer); // Convert to 3D config const room3DConfig = analysisTo3DConfig(result.analysis); res.json({ success: true, imagePath: `/rooms/${filename}`, analysis: result.analysis, room3DConfig, tokensUsed: result.tokensUsed }); } catch (error) { console.error('Room analysis error:', error); res.status(500).json({ error: error.message || 'Erreur lors de l\'analyse de la pièce' }); } } /** * POST /api/room/save * Save room configuration to an order */ export async function saveRoomConfig(req, res) { try { const { orderId, imagePath, analysis, room3DConfig } = req.body; if (!analysis) { return res.status(400).json({ error: 'Configuration de pièce requise' }); } const analysisJson = JSON.stringify({ analysis, room3DConfig }); if (orderId) { // Update existing order await run( `INSERT INTO room_configs (order_id, image_path, analysis_json) VALUES (?, ?, ?)`, [orderId, imagePath || null, analysisJson] ); } // Return the config ID for reference const { rows } = await pool.query( 'SELECT last_insert_rowid() as id' ); res.json({ success: true, roomConfigId: rows[0]?.id || null }); } catch (error) { console.error('Save room config error:', error); res.status(500).json({ error: error.message }); } } /** * GET /api/room/config/:orderId * Get room configuration for an order */ export async function getRoomConfig(req, res) { try { const { orderId } = req.params; const { rows } = await pool.query( 'SELECT * FROM room_configs WHERE order_id = ? ORDER BY created_at DESC LIMIT 1', [orderId] ); if (rows.length === 0) { return res.status(404).json({ error: 'Configuration non trouvée' }); } const config = rows[0]; const parsed = JSON.parse(config.analysis_json || '{}'); res.json({ id: config.id, orderId: config.order_id, imagePath: config.image_path, analysis: parsed.analysis, room3DConfig: parsed.room3DConfig, createdAt: config.created_at }); } catch (error) { console.error('Get room config error:', error); res.status(500).json({ error: error.message }); } } /** * Check if OpenAI API is configured */ export function checkApiStatus(req, res) { const isConfigured = !!process.env.OPENAI_API_KEY; res.json({ openaiConfigured: isConfigured, features: { roomAnalysis: isConfigured } }); }