feat: thêm chức năng upload ảnh

This commit is contained in:
2026-06-16 17:14:22 +07:00
parent 989d60643b
commit 88b2182789
6 changed files with 175 additions and 3 deletions
+74
View File
@@ -44,6 +44,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommentGateway = void 0;
const dotenv = __importStar(require("dotenv"));
@@ -51,8 +54,11 @@ const path = __importStar(require("path"));
const envPath = path.resolve(process.cwd(), '..', '.env');
dotenv.config({ path: envPath });
require("reflect-metadata");
const fs = __importStar(require("fs"));
const sharp_1 = __importDefault(require("sharp"));
const core_1 = require("@nestjs/core");
const common_1 = require("@nestjs/common");
const platform_express_1 = require("@nestjs/platform-express");
const websockets_1 = require("@nestjs/websockets");
const socket_io_1 = require("socket.io");
const prisma_service_1 = require("../prisma/prisma.service");
@@ -62,6 +68,7 @@ const jwt_1 = require("@nestjs/jwt");
const jwt_auth_guard_1 = require("./auth/jwt-auth.guard");
const jwt_strategy_1 = require("./auth/jwt.strategy");
const rbac_middleware_1 = require("./common/rbac.middleware");
const UPLOAD_ROOT = path.join(process.cwd(), 'uploads');
async function bootstrap() {
if (!process.env.DATABASE_URL) {
throw new Error('❌ DATABASE_URL không tồn tại trong biến môi trường! Kiểm tra file .env tại: ' + envPath);
@@ -72,6 +79,12 @@ async function bootstrap() {
const app = await core_1.NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.enableCors();
if (!fs.existsSync(UPLOAD_ROOT)) {
fs.mkdirSync(UPLOAD_ROOT, { recursive: true });
}
app.useStaticAssets(UPLOAD_ROOT, {
prefix: '/uploads/',
});
await app.listen(3001);
console.log(`🚀 Server is running on: http://localhost:3001`);
}
@@ -395,6 +408,15 @@ let TourController = class TourController {
});
}
async deleteTour(id) {
const photos = await this.prisma.photo.findMany({
where: { tourId: id }
});
for (const photo of photos) {
const displayFilePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
if (fs.existsSync(displayFilePath)) {
fs.unlinkSync(displayFilePath);
}
}
await this.prisma.tour.delete({
where: { id },
});
@@ -625,6 +647,42 @@ let TourController = class TourController {
});
return { message: 'Đã xóa thành viên khỏi tour' };
}
async uploadPhotos(tourId, files, req) {
if (!files || files.length === 0) {
throw new common_1.BadRequestException('Vui lòng chọn ít nhất một ảnh');
}
const uploaderId = req.user.id;
const memberOriginalDir = path.join(UPLOAD_ROOT, 'members', uploaderId, 'originals');
const tourDisplayPath = path.join(UPLOAD_ROOT, 'tours');
if (!fs.existsSync(memberOriginalDir))
fs.mkdirSync(memberOriginalDir, { recursive: true });
if (!fs.existsSync(tourDisplayPath))
fs.mkdirSync(tourDisplayPath, { recursive: true });
return Promise.all(files.map(async (file) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const extension = path.extname(file.originalname).toLowerCase() || '.jpg';
const filename = `${uniqueSuffix}${extension}`;
const originalFilePath = path.join(memberOriginalDir, filename);
const displayFilePath = path.join(tourDisplayPath, filename);
await fs.promises.writeFile(originalFilePath, file.buffer);
await (0, sharp_1.default)(file.buffer)
.resize(2560, 2560, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({ quality: 85 })
.toFile(displayFilePath);
return this.prisma.photo.create({
data: {
tourId: tourId,
uploaderId: uploaderId,
imageUrl: `/uploads/tours/${filename}`,
originalUrl: `/uploads/members/${uploaderId}/originals/${filename}`,
privacy: 'TOUR_ONLY',
}
});
}));
}
};
__decorate([
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
@@ -774,6 +832,17 @@ __decorate([
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", Promise)
], TourController.prototype, "removeMember", null);
__decorate([
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, rbac_middleware_1.TourRoleGuard),
(0, common_1.Post)(':tourId/photos'),
(0, common_1.UseInterceptors)((0, platform_express_1.FilesInterceptor)('images', 10)),
__param(0, (0, common_1.Param)('tourId', common_1.ParseUUIDPipe)),
__param(1, (0, common_1.UploadedFiles)()),
__param(2, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Array, Object]),
__metadata("design:returntype", Promise)
], TourController.prototype, "uploadPhotos", null);
TourController = __decorate([
(0, common_1.Controller)('tours'),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
@@ -1035,8 +1104,13 @@ let UserController = class UserController {
if (adminCount <= 1)
throw new common_1.BadRequestException('Không thể xóa Quản trị viên cuối cùng');
}
const memberDir = path.join(UPLOAD_ROOT, 'members', id);
await this.prisma.photo.deleteMany({ where: { uploaderId: id } });
await this.prisma.tourParticipant.deleteMany({ where: { userId: id } });
await this.prisma.user.delete({ where: { id } });
if (fs.existsSync(memberDir)) {
fs.rmSync(memberDir, { recursive: true, force: true });
}
return { message: 'Đã xóa người dùng' };
}
async toggleBlock(id) {