fix: sửa lỗi hiển thị thumbnail khi chia sẻ trên facebook
This commit is contained in:
Vendored
+99
@@ -2434,6 +2434,96 @@ let PublicPhotoController = class PublicPhotoController {
|
||||
this.commentGateway.notifyNewPhotoComment(photoId, comment);
|
||||
return comment;
|
||||
}
|
||||
async sharePhoto(photoId, req, res) {
|
||||
const photo = await this.prisma.photo.findUnique({
|
||||
where: { id: photoId }
|
||||
});
|
||||
if (!photo) {
|
||||
throw new common_1.NotFoundException('Không tìm thấy ảnh.');
|
||||
}
|
||||
let host = req.headers['x-forwarded-host'] || req.headers.host || '';
|
||||
const isProxied = !!(req.headers['x-forwarded-proto'] || req.headers['x-forwarded-for']);
|
||||
const isLocalhost = host.includes('localhost') || host.includes('127.0.0.1');
|
||||
if (isLocalhost && isProxied) {
|
||||
host = 'yotrip.labz.io.vn';
|
||||
}
|
||||
const isLocal = host.includes('localhost') || host.includes('127.0.0.1') || host.startsWith('192.168.') || host.startsWith('10.');
|
||||
const protocol = isLocal ? (req.headers['x-forwarded-proto'] || 'http') : 'https';
|
||||
const baseUrl = `${protocol}://${host}`;
|
||||
let title = 'YoTrip - Xem ảnh công khai';
|
||||
let description = 'Xem hình ảnh chia sẻ công khai trên bản đồ hành trình YoTrip.';
|
||||
if (photo.metadata && typeof photo.metadata === 'object') {
|
||||
const meta = photo.metadata;
|
||||
if (meta.title && meta.title.trim()) {
|
||||
title = meta.title;
|
||||
}
|
||||
if (meta.description && meta.description.trim()) {
|
||||
description = meta.description;
|
||||
}
|
||||
}
|
||||
const fullImageUrl = photo.imageUrl
|
||||
? (photo.imageUrl.startsWith('http') ? photo.imageUrl : `${baseUrl}${photo.imageUrl}`)
|
||||
: '';
|
||||
const shareUrl = `${baseUrl}/api/v1/public-photos/${photoId}/share`;
|
||||
const redirectUrl = `${baseUrl}/?photoId=${photoId}`;
|
||||
let imageWidth = 1200;
|
||||
let imageHeight = 630;
|
||||
try {
|
||||
if (photo.imageUrl) {
|
||||
const localImagePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
|
||||
if (fs.existsSync(localImagePath)) {
|
||||
const imageMeta = await (0, sharp_1.default)(localImagePath).metadata();
|
||||
if (imageMeta.width && imageMeta.height) {
|
||||
imageWidth = imageMeta.width;
|
||||
imageHeight = imageMeta.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.warn('[Share] Không thể lấy kích thước ảnh:', err.message);
|
||||
}
|
||||
const htmlContent = `<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>${title}</title>
|
||||
<meta name="description" content="${description}">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:site_name" content="YoTrip">
|
||||
<meta property="og:locale" content="vi_VN">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="${shareUrl}">
|
||||
<meta property="og:title" content="${title}">
|
||||
<meta property="og:description" content="${description}">
|
||||
<meta property="og:image" content="${fullImageUrl}">
|
||||
<meta property="og:image:secure_url" content="${fullImageUrl}">
|
||||
<meta property="og:image:type" content="image/jpeg">
|
||||
<meta property="og:image:width" content="${imageWidth}">
|
||||
<meta property="og:image:height" content="${imageHeight}">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="${shareUrl}">
|
||||
<meta property="twitter:title" content="${title}">
|
||||
<meta property="twitter:description" content="${description}">
|
||||
<meta property="twitter:image" content="${fullImageUrl}">
|
||||
|
||||
<!-- Tự động chuyển hướng người dùng sang frontend chi tiết -->
|
||||
<script>
|
||||
window.location.href = "${redirectUrl}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-family: sans-serif; text-align: center; margin-top: 100px; color: #334155;">
|
||||
<h2>Đang chuyển hướng bạn đến YoTrip...</h2>
|
||||
<p>Nếu trang không tự động tải, <a href="${redirectUrl}">nhấn vào đây</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
res.type('text/html').send(htmlContent);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
@@ -2458,6 +2548,15 @@ __decorate([
|
||||
__metadata("design:paramtypes", [String, Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], PublicPhotoController.prototype, "addPhotoComment", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)(':photoId/share'),
|
||||
__param(0, (0, common_1.Param)('photoId', common_1.ParseUUIDPipe)),
|
||||
__param(1, (0, common_1.Req)()),
|
||||
__param(2, (0, common_1.Res)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], PublicPhotoController.prototype, "sharePhoto", null);
|
||||
PublicPhotoController = __decorate([
|
||||
(0, common_1.Controller)('public-photos'),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+110
-1
@@ -9,7 +9,7 @@ import sharp from 'sharp';
|
||||
import exifr from 'exifr';
|
||||
import heicConvert from 'heic-convert';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Query, ForbiddenException, Injectable, UseInterceptors, UploadedFiles, Inject, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Res, Query, ForbiddenException, Injectable, UseInterceptors, UploadedFiles, Inject, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { diskStorage } from 'multer';
|
||||
@@ -2374,6 +2374,115 @@ class PublicPhotoController {
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Get(':photoId/share')
|
||||
async sharePhoto(
|
||||
@Param('photoId', ParseUUIDPipe) photoId: string,
|
||||
@Req() req: any,
|
||||
@Res() res: any
|
||||
) {
|
||||
const photo = await this.prisma.photo.findUnique({
|
||||
where: { id: photoId }
|
||||
});
|
||||
|
||||
if (!photo) {
|
||||
throw new NotFoundException('Không tìm thấy ảnh.');
|
||||
}
|
||||
|
||||
let host = req.headers['x-forwarded-host'] || req.headers.host || '';
|
||||
|
||||
// Nếu host là localhost/127.0.0.1 nhưng request có header proxy (nghĩa là đang chạy qua Nginx proxy ở production)
|
||||
const isProxied = !!(req.headers['x-forwarded-proto'] || req.headers['x-forwarded-for']);
|
||||
const isLocalhost = host.includes('localhost') || host.includes('127.0.0.1');
|
||||
if (isLocalhost && isProxied) {
|
||||
host = 'yotrip.labz.io.vn';
|
||||
}
|
||||
|
||||
const isLocal = host.includes('localhost') || host.includes('127.0.0.1') || host.startsWith('192.168.') || host.startsWith('10.');
|
||||
const protocol = isLocal ? (req.headers['x-forwarded-proto'] || 'http') : 'https';
|
||||
const baseUrl = `${protocol}://${host}`;
|
||||
|
||||
// Lấy thông tin metadata
|
||||
let title = 'YoTrip - Xem ảnh công khai';
|
||||
let description = 'Xem hình ảnh chia sẻ công khai trên bản đồ hành trình YoTrip.';
|
||||
if (photo.metadata && typeof photo.metadata === 'object') {
|
||||
const meta = photo.metadata as any;
|
||||
if (meta.title && meta.title.trim()) {
|
||||
title = meta.title;
|
||||
}
|
||||
if (meta.description && meta.description.trim()) {
|
||||
description = meta.description;
|
||||
}
|
||||
}
|
||||
|
||||
// Xây dựng đường dẫn ảnh tuyệt đối để Facebook hiển thị thumbnail
|
||||
const fullImageUrl = photo.imageUrl
|
||||
? (photo.imageUrl.startsWith('http') ? photo.imageUrl : `${baseUrl}${photo.imageUrl}`)
|
||||
: '';
|
||||
|
||||
const shareUrl = `${baseUrl}/api/v1/public-photos/${photoId}/share`;
|
||||
const redirectUrl = `${baseUrl}/?photoId=${photoId}`;
|
||||
|
||||
// Lấy kích thước thực tế của ảnh bằng sharp để Facebook hiển thị chuẩn xác không cần chờ xử lý bất đồng bộ
|
||||
let imageWidth = 1200;
|
||||
let imageHeight = 630;
|
||||
try {
|
||||
if (photo.imageUrl) {
|
||||
const localImagePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
|
||||
if (fs.existsSync(localImagePath)) {
|
||||
const imageMeta = await sharp(localImagePath).metadata();
|
||||
if (imageMeta.width && imageMeta.height) {
|
||||
imageWidth = imageMeta.width;
|
||||
imageHeight = imageMeta.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[Share] Không thể lấy kích thước ảnh:', err.message);
|
||||
}
|
||||
|
||||
const htmlContent = `<!DOCTYPE html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>${title}</title>
|
||||
<meta name="description" content="${description}">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:site_name" content="YoTrip">
|
||||
<meta property="og:locale" content="vi_VN">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="${shareUrl}">
|
||||
<meta property="og:title" content="${title}">
|
||||
<meta property="og:description" content="${description}">
|
||||
<meta property="og:image" content="${fullImageUrl}">
|
||||
<meta property="og:image:secure_url" content="${fullImageUrl}">
|
||||
<meta property="og:image:type" content="image/jpeg">
|
||||
<meta property="og:image:width" content="${imageWidth}">
|
||||
<meta property="og:image:height" content="${imageHeight}">
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="${shareUrl}">
|
||||
<meta property="twitter:title" content="${title}">
|
||||
<meta property="twitter:description" content="${description}">
|
||||
<meta property="twitter:image" content="${fullImageUrl}">
|
||||
|
||||
<!-- Tự động chuyển hướng người dùng sang frontend chi tiết -->
|
||||
<script>
|
||||
window.location.href = "${redirectUrl}";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="font-family: sans-serif; text-align: center; margin-top: 100px; color: #334155;">
|
||||
<h2>Đang chuyển hướng bạn đến YoTrip...</h2>
|
||||
<p>Nếu trang không tự động tải, <a href="${redirectUrl}">nhấn vào đây</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
res.type('text/html').send(htmlContent);
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
|
||||
Reference in New Issue
Block a user