diff --git a/backend/assets/static/360-badge.png b/backend/assets/static/360-badge.png new file mode 100644 index 0000000..4729284 Binary files /dev/null and b/backend/assets/static/360-badge.png differ diff --git a/backend/routes/apiRoutes.js b/backend/routes/apiRoutes.js index c4a16e1..fdb9e85 100644 --- a/backend/routes/apiRoutes.js +++ b/backend/routes/apiRoutes.js @@ -3,6 +3,7 @@ const multer = require('multer'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); +const sharp = require('sharp'); const User = require('../models/User'); const Asset = require('../models/Asset'); @@ -96,20 +97,13 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => { // Lấy tọa độ GPS gốc từ ảnh vừa upload trước khi nén/xử lý const originalGPS = await getGPSCoordinates(tempFilePath); - // BACKGROUND PROCESSING: Thực hiện song song không chặn response - setImmediate(async () => { - try { - // 1. Resize to 8K - await resizeTo8K(tempFilePath, processedFilePath); - // 2. Inject GPS - await injectGPSCoordinates(processedFilePath, latitude, longitude); - // 3. Cleanup temp file - if (fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath); - console.log(`Background processing finished for: ${processedFileName}`); - } catch (err) { - console.error(`Background Image processing failed: ${err.message}`); - } - }); + // Thực hiện xử lý ảnh tuần tự để đảm bảo file được tạo thành công trước khi lưu DB + // 1. Resize to 8K (Sẽ convert từ DNG sang JPG nếu sharp hỗ trợ libraw, nếu không sẽ báo lỗi) + await resizeTo8K(tempFilePath, processedFilePath); + // 2. Inject GPS vào file JPG vừa tạo + await injectGPSCoordinates(processedFilePath, latitude, longitude); + // 3. Cleanup temp file (file gốc dng) + if (fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath); // 5. Save Asset to DB const asset = new Asset({ @@ -151,17 +145,17 @@ router.post('/scenes', protect, uploadSinglePanorama, async (req, res) => { }); await scene.save(); - res.status(202).json({ + res.status(201).json({ message: 'Scene created successfully', scene }); } catch (error) { - // Cleanup file if error occurs + // Dọn dẹp các file rác nếu quá trình xử lý thất bại if (req.file && fs.existsSync(req.file.path)) { try { fs.unlinkSync(req.file.path); } catch (e) {} } - res.status(500).json({ message: error.message }); + res.status(500).json({ message: "Xử lý ảnh 360 thất bại. Vui lòng đảm bảo bạn upload ảnh Panorama đã được stitch (JPEG). Chi tiết: " + error.message }); } }); @@ -267,8 +261,9 @@ router.get('/share/:sceneId', optionalAuth, async (req, res) => { + - + @@ -496,6 +491,41 @@ router.get('/assets/view/:assetId', verifyReferer, optionalAuth, async (req, res const resolvedPath = path.resolve(asset.filePath); + // Kiểm tra xem có cần chèn watermark 360° hay không (dành cho Social Bot hoặc yêu cầu thủ công) + const userAgent = req.headers['user-agent'] || ''; + const isSocialBot = /facebookexternalhit|Facebot|ZaloBot|Twitterbot|Slackbot|LinkedInBot|Embedly/i.test(userAgent); + const wantWatermark = isSocialBot || req.query.watermark === 'true'; + + if (wantWatermark) { + const iconPath = path.join(__dirname, '../assets/static/360-badge.png'); + if (fs.existsSync(iconPath)) { + try { + // Resize ảnh về kích thước vuông (1200x1200) trước khi chèn icon + // Việc này giúp icon 360 hiển thị rõ ràng và tỷ lệ hợp lý hơn + const imageBuffer = await sharp(resolvedPath) + .resize(1200, 1200, { + fit: 'cover' + }) + .composite([{ + input: iconPath, + gravity: 'center' + }]) + .jpeg({ quality: 90 }) + .toBuffer(); + + res.set({ + 'Content-Type': 'image/jpeg', + 'Cache-Control': 'public, max-age=2592000', + 'Content-Disposition': 'inline; filename="preview_360.jpg"' + }); + return res.send(imageBuffer); + } catch (sharpError) { + console.error("[Sharp Error]:", sharpError.message); + // Nếu lỗi sharp, fallback xuống sendFile bình thường ở dưới + } + } + } + // Sử dụng res.sendFile để tối ưu hóa việc truyền tải file lớn và hỗ trợ Caching (ETag) res.sendFile(resolvedPath, { maxAge: 2592000000, // 30 ngày (tính bằng ms) diff --git a/backend/uploads/processed_1780970998028_047bd312.JPG.jpg b/backend/uploads/processed_1780970998028_047bd312.JPG.jpg new file mode 100644 index 0000000..646787a Binary files /dev/null and b/backend/uploads/processed_1780970998028_047bd312.JPG.jpg differ diff --git a/backend/uploads/temp/1780971557798_da18c0af.dng b/backend/uploads/temp/1780971557798_da18c0af.dng new file mode 100644 index 0000000..50f3be0 Binary files /dev/null and b/backend/uploads/temp/1780971557798_da18c0af.dng differ