fix: admin empty thùng rác

This commit is contained in:
2026-06-22 12:28:21 +07:00
parent 860395cb14
commit 01d6d7439f
54 changed files with 992 additions and 361 deletions
+4
View File
@@ -1,4 +1,8 @@
declare const JwtAuthGuard_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
export declare class JwtAuthGuard extends JwtAuthGuard_base {
}
declare const JwtAuthGuardNoAnonymous_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
export declare class JwtAuthGuardNoAnonymous extends JwtAuthGuardNoAnonymous_base {
handleRequest(err: any, user: any, info: any): any;
}
export {};
+17 -1
View File
@@ -6,13 +6,29 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JwtAuthGuard = void 0;
exports.JwtAuthGuardNoAnonymous = exports.JwtAuthGuard = void 0;
const common_1 = require("@nestjs/common");
const passport_1 = require("@nestjs/passport");
const common_2 = require("@nestjs/common");
let JwtAuthGuard = class JwtAuthGuard extends (0, passport_1.AuthGuard)('jwt') {
};
exports.JwtAuthGuard = JwtAuthGuard;
exports.JwtAuthGuard = JwtAuthGuard = __decorate([
(0, common_1.Injectable)()
], JwtAuthGuard);
let JwtAuthGuardNoAnonymous = class JwtAuthGuardNoAnonymous extends (0, passport_1.AuthGuard)('jwt') {
handleRequest(err, user, info) {
if (err || !user) {
throw err || new common_2.UnauthorizedException('Phiên làm việc không hợp lệ hoặc đã hết hạn');
}
if (user.isAnonymous) {
throw new common_2.UnauthorizedException('Tài khoản khách không có quyền truy cập tính năng này. Vui lòng đăng ký tài khoản để tiếp tục.');
}
return user;
}
};
exports.JwtAuthGuardNoAnonymous = JwtAuthGuardNoAnonymous;
exports.JwtAuthGuardNoAnonymous = JwtAuthGuardNoAnonymous = __decorate([
(0, common_1.Injectable)()
], JwtAuthGuardNoAnonymous);
//# sourceMappingURL=jwt-auth.guard.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../../src/auth/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAC5C,+CAA6C;AAGtC,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,IAAA,oBAAS,EAAC,KAAK,CAAC;CAAG,CAAA;AAAxC,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;GACA,YAAY,CAA4B"}
{"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../../src/auth/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAC5C,+CAA6C;AAC7C,2CAAuD;AAGhD,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,IAAA,oBAAS,EAAC,KAAK,CAAC;CAAG,CAAA;AAAxC,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;GACA,YAAY,CAA4B;AAI9C,IAAM,uBAAuB,GAA7B,MAAM,uBAAwB,SAAQ,IAAA,oBAAS,EAAC,KAAK,CAAC;IAC3D,aAAa,CAAC,GAAQ,EAAE,IAAS,EAAE,IAAS;QAC1C,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,IAAI,IAAI,8BAAqB,CAAC,6CAA6C,CAAC,CAAC;QACxF,CAAC;QAGD,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,8BAAqB,CAAC,gGAAgG,CAAC,CAAC;QACpI,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAbY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;GACA,uBAAuB,CAanC"}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAmE;AACnE,+CAAoD;AACpD,+CAAoD;AACpD,gEAA4D;AAGrD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD,YAAoB,MAAqB;QACvC,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,cAAc;SACtD,CAAC,CAAC;QALe,WAAM,GAAN,MAAM,CAAe;IAMzC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAY;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC7C,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,8BAAqB,CAAC,sDAAsD,CAAC,CAAC;QAC1F,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AApBY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,WAAW,CAoBvB"}
{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAmE;AACnE,+CAAoD;AACpD,+CAAoD;AACpD,gEAA4D;AAGrD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD,YAAoB,MAAqB;QACvC,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,cAAc;SACtD,CAAC,CAAC;QALe,WAAM,GAAN,MAAM,CAAe;IAMzC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAY;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC7C,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE;SAC3B,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,8BAAqB,CAAC,sDAAsD,CAAC,CAAC;QAC1F,CAAC;QAID,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAtBY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,WAAW,CAsBvB"}
+377 -39
View File
@@ -71,6 +71,7 @@ const admin_guard_1 = require("./auth/admin.guard");
const nodemailer = __importStar(require("nodemailer"));
const jwt_1 = require("@nestjs/jwt");
const jwt_auth_guard_1 = require("./auth/jwt-auth.guard");
const jwt_auth_guard_2 = require("./auth/jwt-auth.guard");
const jwt_strategy_1 = require("./auth/jwt.strategy");
const core_2 = require("@nestjs/core");
const common_2 = require("@nestjs/common");
@@ -823,6 +824,34 @@ let TourController = class TourController {
}
}
});
const noteContent = `<h2>${filteredTitle} - Initial Planning</h2>
<p><strong>Start Date:</strong> ${startDate ? new Date(startDate).toLocaleDateString() : 'TBD'}</p>
<p><strong>End Date:</strong> ${endDate ? new Date(endDate).toLocaleDateString() : 'TBD'}</p>
<p><strong>Adult Participants:</strong> ${adultCount || 1}</p>
<p><strong>Child Participants:</strong> ${childCount || 0}</p>
<h3>Key Items to Plan:</h3>
<ul>
<li>Accommodations</li>
<li>Transportation</li>
<li>Activities & Attractions</li>
<li>Budget & Expenses</li>
<li>Important Contact Numbers</li>
<li>Special Requirements & Notes</li>
</ul>
<p><em>Add your planning notes here...</em></p>`;
try {
await this.prisma.tourNote.create({
data: {
tourId: tour.id,
userId: req.user.id,
title: `[${filteredTitle}] - Initial Planning`,
content: noteContent
}
});
}
catch (err) {
console.warn(`Failed to auto-create note for tour ${tour.id}:`, err.message);
}
await this.cacheManager.del(`/api/v1/tours/explore`);
return tour;
}
@@ -1464,15 +1493,37 @@ let TourController = class TourController {
console.warn('[HEIC] Failed to convert HEIC to JPEG, attempting to process anyway:', e.message);
}
}
await (0, sharp_1.default)(processBuffer)
.rotate()
.resize(2560, 2560, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({ quality: 85 })
.toFile(displayFilePath);
return this.prisma.photo.create({
const uploadsDir = path.dirname(displayFilePath);
if (!fs.existsSync(uploadsDir)) {
try {
fs.mkdirSync(uploadsDir, { recursive: true });
console.log(`[Photo] Created directory: ${uploadsDir}`);
}
catch (e) {
console.error(`[Photo] Failed to create directory ${uploadsDir}:`, e);
throw new common_1.BadRequestException(`Lỗi tạo thư mục: ${e.message}`);
}
}
try {
await (0, sharp_1.default)(processBuffer)
.rotate()
.resize(2560, 2560, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({ quality: 85 })
.toFile(displayFilePath);
console.log(`[Photo] Display image saved successfully: ${displayFilePath}`);
}
catch (e) {
console.error(`[Photo] Failed to save display image to ${displayFilePath}:`, e);
throw new common_1.BadRequestException(`Lỗi lưu hình ảnh: ${e.message}`);
}
if (!fs.existsSync(displayFilePath)) {
console.error(`[Photo] File verification failed at ${displayFilePath}`);
throw new common_1.BadRequestException('Lỗi: Hình ảnh không được lưu thành công. Vui lòng kiểm tra quyền lưu trữ.');
}
const photoRecord = await this.prisma.photo.create({
data: {
tourId: tourId,
uploaderId: uploaderId,
@@ -1485,6 +1536,8 @@ let TourController = class TourController {
}
}
});
console.log(`[Photo] Database record created: ID=${photoRecord.id}, imageUrl=${photoRecord.imageUrl}`);
return photoRecord;
}));
}
async createInvitation(tourId, body, req) {
@@ -1528,17 +1581,31 @@ let TourController = class TourController {
}
async joinByToken(body, req) {
const { token } = body;
console.log('[joinByToken] Request received, token length:', token ? token.length : 0);
if (!token) {
console.error('[joinByToken] No token provided');
throw new common_1.BadRequestException('Vui lòng cung cấp token lời mời.');
}
console.log('[joinByToken] Searching for invitation with token:', token.substring(0, 20) + '...');
const invitation = await this.prisma.tourInvitation.findUnique({
where: { token },
include: { tour: { select: { title: true } } }
include: { tour: { select: { id: true, title: true } } }
});
if (!invitation) {
console.error('[joinByToken] Invitation not found for token:', token.substring(0, 20) + '...');
const totalInvitations = await this.prisma.tourInvitation.count();
console.log('[joinByToken] Total invitations in database:', totalInvitations);
throw new common_1.NotFoundException('Lời mời không hợp lệ hoặc đã bị hủy.');
}
const userEmail = req.user.email;
console.log('[joinByToken] Checking email - User:', userEmail, 'Invitation:', invitation.email);
if (invitation.email && userEmail && invitation.email !== userEmail) {
console.error('[joinByToken] Email mismatch - User email:', userEmail, 'Invitation email:', invitation.email);
throw new common_1.BadRequestException('Lời mời này được gửi tới một địa chỉ email khác. Vui lòng đăng nhập bằng tài khoản đúng.');
}
console.log('[joinByToken] Invitation found, tourId:', invitation.tourId, 'expired:', invitation.expiredAt < new Date());
if (invitation.expiredAt < new Date()) {
console.error('[joinByToken] Invitation expired at:', invitation.expiredAt);
try {
await this.prisma.tourInvitation.delete({ where: { id: invitation.id } });
}
@@ -1546,10 +1613,12 @@ let TourController = class TourController {
throw new common_1.BadRequestException('Lời mời đã hết hạn.');
}
const userId = req.user.id;
console.log('[joinByToken] User attempting to join - userId:', userId, 'tourId:', invitation.tourId);
const existingParticipation = await this.prisma.tourParticipant.findUnique({
where: { tourId_userId: { tourId: invitation.tourId, userId } }
});
if (existingParticipation) {
console.log('[joinByToken] User is already a participant');
try {
await this.prisma.tourInvitation.delete({ where: { id: invitation.id } });
}
@@ -1582,6 +1651,7 @@ let TourController = class TourController {
where: { id: invitation.id }
})
]);
console.log('[joinByToken] Successfully joined tour:', invitation.tourId, 'userId:', userId);
return { success: true, tourId: invitation.tourId, message: `Bạn đã gia nhập hành trình "${invitation.tour.title}"!`, shouldRefresh: true };
}
async mergeMember(tourId, body, req) {
@@ -1709,7 +1779,7 @@ __decorate([
], TourController.prototype, "deleteTour", null);
__decorate([
(0, common_1.UseInterceptors)(compress_cache_interceptor_1.CompressCacheInterceptor),
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
(0, common_1.UseGuards)(jwt_auth_guard_2.JwtAuthGuardNoAnonymous),
(0, common_1.Get)('explore'),
__param(0, (0, common_1.Req)()),
__metadata("design:type", Function),
@@ -2155,11 +2225,15 @@ let PhotoController = class PhotoController {
const originalFilename = `${uniqueSuffix}${originalExtension}`;
const displayFilename = `${uniqueSuffix}.jpg`;
const memberOriginalDir = path.join(UPLOAD_ROOT, 'members', uploaderId, 'originals');
const displayFilePath = path.join(UPLOAD_ROOT, 'tours', displayFilename);
const tourDisplayPath = path.join(UPLOAD_ROOT, 'tours');
const displayFilePath = path.join(tourDisplayPath, displayFilename);
const originalFilePath = path.join(memberOriginalDir, originalFilename);
if (!fs.existsSync(memberOriginalDir)) {
fs.mkdirSync(memberOriginalDir, { recursive: true });
}
if (!fs.existsSync(tourDisplayPath)) {
fs.mkdirSync(tourDisplayPath, { recursive: true });
}
await fs.promises.writeFile(originalFilePath, file.buffer);
let lat;
let lng;
@@ -3037,6 +3111,23 @@ let PublicPhotoController = class PublicPhotoController {
</html>`;
res.type('text/html').send(htmlContent);
}
async flagPhoto(photoId, body, req) {
const photo = await this.prisma.photo.findUnique({
where: { id: photoId }
});
if (!photo) {
throw new common_1.NotFoundException('Ảnh không tồn tại');
}
const updatedPhoto = await this.prisma.photo.update({
where: { id: photoId },
data: {
isFlagged: true,
flaggedReason: body.reason || 'Không xác định',
flaggedAt: new Date()
}
});
return { success: true, message: 'Đã báo cáo ảnh thành công', photo: updatedPhoto };
}
};
__decorate([
(0, common_1.Get)(),
@@ -3079,6 +3170,16 @@ __decorate([
__metadata("design:paramtypes", [String, Object, Object]),
__metadata("design:returntype", Promise)
], PublicPhotoController.prototype, "sharePhoto", null);
__decorate([
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
(0, common_1.Post)(':photoId/flag'),
__param(0, (0, common_1.Param)('photoId', common_1.ParseUUIDPipe)),
__param(1, (0, common_1.Body)()),
__param(2, (0, common_1.Req)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object, Object]),
__metadata("design:returntype", Promise)
], PublicPhotoController.prototype, "flagPhoto", null);
PublicPhotoController = __decorate([
(0, common_1.Controller)('public-photos'),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
@@ -3631,6 +3732,79 @@ AdminModerationController = __decorate([
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, admin_guard_1.AdminGuard),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], AdminModerationController);
let AdminPhotosController = class AdminPhotosController {
constructor(prisma) {
this.prisma = prisma;
}
async getFlaggedPhotos() {
return this.prisma.photo.findMany({
where: { isFlagged: true },
include: {
uploader: { select: { id: true, name: true, email: true } },
tour: { select: { id: true, title: true } }
},
orderBy: { flaggedAt: 'desc' }
});
}
async approvePhoto(photoId) {
const photo = await this.prisma.photo.findUnique({
where: { id: photoId }
});
if (!photo) {
throw new common_1.NotFoundException('Ảnh không tồn tại');
}
const updatedPhoto = await this.prisma.photo.update({
where: { id: photoId },
data: {
isFlagged: false,
flaggedReason: null,
flaggedAt: null
}
});
return { success: true, message: 'Đã duyệt ảnh thành công', photo: updatedPhoto };
}
async deletePhoto(photoId) {
const photo = await this.prisma.photo.findUnique({
where: { id: photoId }
});
if (!photo) {
throw new common_1.NotFoundException('Ảnh không tồn tại');
}
const deletedPhoto = await this.prisma.photo.update({
where: { id: photoId },
data: {
isDeleted: true,
deletedAt: new Date()
}
});
return { success: true, message: 'Đã xóa ảnh thành công', photo: deletedPhoto };
}
};
__decorate([
(0, common_1.Get)('flagged'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], AdminPhotosController.prototype, "getFlaggedPhotos", null);
__decorate([
(0, common_1.Post)(':photoId/approve'),
__param(0, (0, common_1.Param)('photoId', common_1.ParseUUIDPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], AdminPhotosController.prototype, "approvePhoto", null);
__decorate([
(0, common_1.Delete)(':photoId'),
__param(0, (0, common_1.Param)('photoId', common_1.ParseUUIDPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], AdminPhotosController.prototype, "deletePhoto", null);
AdminPhotosController = __decorate([
(0, common_1.Controller)('admin/photos'),
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, admin_guard_1.AdminGuard),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], AdminPhotosController);
let ReportsController = class ReportsController {
constructor(prisma) {
this.prisma = prisma;
@@ -4347,67 +4521,225 @@ let AdminTrashController = class AdminTrashController {
}
async deletePermanentItems(body) {
const { type, ids } = body;
console.log('[deletePermanent] Request received - type:', type, 'ids count:', ids.length);
if (!type || !ids || !Array.isArray(ids)) {
throw new common_1.BadRequestException('Tham số không hợp lệ.');
}
const deleteResults = { success: 0, failed: 0, errors: [] };
if (type === 'tour') {
for (const tourId of ids) {
const photos = await this.prisma.photo.findMany({ where: { tourId } });
for (const p of photos) {
if (p.imageUrl) {
const displayFilePath = path.join(process.cwd(), p.imageUrl.replace(/^\//, ''));
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
try {
console.log('[deletePermanent] Deleting tour:', tourId);
const photos = await this.prisma.photo.findMany({ where: { tourId } });
console.log('[deletePermanent] Found', photos.length, 'photos for tour');
for (const p of photos) {
if (p.imageUrl) {
const displayFilePath = path.join(process.cwd(), p.imageUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking display file:', displayFilePath);
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
console.log('[deletePermanent] Deleted display file:', displayFilePath);
}
catch (e) {
console.error('[deletePermanent] Error deleting display file:', e);
}
}
}
if (p.originalUrl) {
const originalFilePath = path.join(process.cwd(), p.originalUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking original file:', originalFilePath);
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
console.log('[deletePermanent] Deleted original file:', originalFilePath);
}
catch (e) {
console.error('[deletePermanent] Error deleting original file:', e);
}
}
catch (e) { }
}
}
if (p.originalUrl) {
const originalFilePath = path.join(process.cwd(), p.originalUrl.replace(/^\//, ''));
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
}
catch (e) { }
}
}
await this.prisma.tour.delete({ where: { id: tourId } });
console.log('[deletePermanent] Deleted tour from database:', tourId);
deleteResults.success++;
}
catch (e) {
console.error('[deletePermanent] Error deleting tour:', tourId, e);
deleteResults.failed++;
deleteResults.errors.push(`Tour ${tourId}: ${e?.message || 'Unknown error'}`);
}
await this.prisma.tour.delete({ where: { id: tourId } });
}
}
else if (type === 'photo') {
console.log('[deletePermanent] Deleting', ids.length, 'photos');
console.log('[deletePermanent] Photo IDs:', JSON.stringify(ids));
if (!ids || ids.length === 0) {
console.warn('[deletePermanent] No photo IDs provided');
return { success: true, deleted: 0, failed: 0 };
}
for (const photoId of ids) {
const photo = await this.prisma.photo.findUnique({ where: { id: photoId } });
if (photo) {
try {
console.log('[deletePermanent] ========== START Processing photo:', photoId);
const photo = await this.prisma.photo.findUnique({ where: { id: photoId } });
if (!photo) {
console.warn('[deletePermanent] Photo not found in DB:', photoId);
deleteResults.failed++;
deleteResults.errors.push(`Photo ${photoId}: not found in database`);
console.log('[deletePermanent] ========== SKIP (not found):', photoId);
continue;
}
console.log('[deletePermanent] Found photo:', { id: photo.id, imageUrl: photo.imageUrl, isDeleted: photo.isDeleted, tourId: photo.tourId });
console.log('[deletePermanent] Deleting comments for photo:', photoId);
try {
const commentResult = await this.prisma.comment.deleteMany({
where: { photoId }
});
console.log('[deletePermanent] Deleted', commentResult.count, 'comments for photo');
}
catch (e) {
console.warn('[deletePermanent] Error deleting comments (continuing):', e?.message);
}
if (photo.imageUrl) {
const displayFilePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking display file:', displayFilePath);
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
console.log('[deletePermanent] ✓ Deleted display file');
}
catch (e) {
console.error('[deletePermanent] Error deleting display file:', e);
}
catch (e) { }
}
}
if (photo.originalUrl) {
const originalFilePath = path.join(process.cwd(), photo.originalUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking original file:', originalFilePath);
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
console.log('[deletePermanent] ✓ Deleted original file');
}
catch (e) {
console.error('[deletePermanent] Error deleting original file:', e);
}
catch (e) { }
}
}
await this.prisma.photo.delete({ where: { id: photoId } });
console.log('[deletePermanent] Attempting database delete for photo:', photoId);
const result = await this.prisma.photo.delete({ where: { id: photoId } });
console.log('[deletePermanent] ✓ Successfully deleted photo from database:', photoId);
deleteResults.success++;
console.log('[deletePermanent] ========== SUCCESS:', photoId);
}
catch (e) {
console.error('[deletePermanent] ✗ Error deleting photo:', photoId);
console.error('[deletePermanent] Error details:', e?.message || String(e));
deleteResults.failed++;
deleteResults.errors.push(`Photo ${photoId}: ${e?.message || 'Unknown error'}`);
console.log('[deletePermanent] ========== FAILED:', photoId);
}
}
console.log('[deletePermanent] Photos deletion complete - success:', deleteResults.success, 'failed:', deleteResults.failed);
}
else if (type === 'note') {
await this.prisma.tourNote.deleteMany({
where: { id: { in: ids } }
});
console.log('[deletePermanent] Deleting', ids.length, 'notes');
try {
const result = await this.prisma.tourNote.deleteMany({
where: { id: { in: ids } }
});
console.log('[deletePermanent] Deleted notes from database, count:', result.count);
deleteResults.success = result.count;
}
catch (e) {
console.error('[deletePermanent] Error deleting notes:', e);
deleteResults.failed = ids.length;
deleteResults.errors.push(`Notes deletion: ${e?.message || 'Unknown error'}`);
}
}
return { success: true };
console.log('[deletePermanent] Deletion complete - success:', deleteResults.success, 'failed:', deleteResults.failed);
if (deleteResults.failed > 0) {
console.warn('[deletePermanent] Errors:', deleteResults.errors);
}
return {
success: true,
deleted: deleteResults.success,
failed: deleteResults.failed,
errors: deleteResults.errors.length > 0 ? deleteResults.errors : undefined
};
}
async emptyAllTrash() {
console.log('[emptyAllTrash] Starting to empty all trash');
const deleteResults = { success: 0, failed: 0, errors: [] };
try {
console.log('[emptyAllTrash] Finding all deleted photos');
const deletedPhotos = await this.prisma.photo.findMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Found', deletedPhotos.length, 'deleted photos');
console.log('[emptyAllTrash] Deleting all comments from deleted photos');
const commentCount = await this.prisma.comment.deleteMany({
where: {
photoId: { in: deletedPhotos.map(p => p.id) }
}
});
console.log('[emptyAllTrash] Deleted', commentCount.count, 'comments');
for (const photo of deletedPhotos) {
if (photo.imageUrl) {
const filePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
console.log('[emptyAllTrash] Deleted display file:', filePath);
}
catch (e) {
console.error('[emptyAllTrash] Error deleting display file:', e);
}
}
}
if (photo.originalUrl) {
const filePath = path.join(process.cwd(), photo.originalUrl.replace(/^\//, ''));
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
console.log('[emptyAllTrash] Deleted original file:', filePath);
}
catch (e) {
console.error('[emptyAllTrash] Error deleting original file:', e);
}
}
}
}
console.log('[emptyAllTrash] Deleting all', deletedPhotos.length, 'photos from database');
const photoResult = await this.prisma.photo.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', photoResult.count, 'photos');
deleteResults.success += photoResult.count;
console.log('[emptyAllTrash] Deleting all deleted tours');
const tourResult = await this.prisma.tour.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', tourResult.count, 'tours');
deleteResults.success += tourResult.count;
console.log('[emptyAllTrash] Deleting all deleted notes');
const noteResult = await this.prisma.tourNote.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', noteResult.count, 'notes');
deleteResults.success += noteResult.count;
console.log('[emptyAllTrash] Successfully emptied all trash - total deleted:', deleteResults.success);
}
catch (e) {
console.error('[emptyAllTrash] Error emptying trash:', e?.message || e);
deleteResults.failed++;
deleteResults.errors.push(`Emptying trash: ${e?.message || 'Unknown error'}`);
}
return {
success: true,
totalDeleted: deleteResults.success,
errors: deleteResults.errors.length > 0 ? deleteResults.errors : undefined
};
}
};
__decorate([
@@ -4437,6 +4769,12 @@ __decorate([
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminTrashController.prototype, "deletePermanentItems", null);
__decorate([
(0, common_1.Post)('empty-all'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], AdminTrashController.prototype, "emptyAllTrash", null);
AdminTrashController = __decorate([
(0, common_1.Controller)('admin/trash'),
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, admin_guard_1.AdminGuard),
+1 -1
View File
File diff suppressed because one or more lines are too long
+213 -68
View File
@@ -4319,100 +4319,245 @@ class AdminTrashController {
throw new BadRequestException('Tham số không hợp lệ.');
}
const deleteResults = { success: 0, failed: 0, errors: [] as string[] };
if (type === 'tour') {
for (const tourId of ids) {
console.log('[deletePermanent] Deleting tour:', tourId);
const photos = await this.prisma.photo.findMany({ where: { tourId } });
console.log('[deletePermanent] Found', photos.length, 'photos for tour');
for (const p of photos) {
if (p.imageUrl) {
const displayFilePath = path.join(process.cwd(), p.imageUrl.replace(/^\//, ''));
try {
console.log('[deletePermanent] Deleting tour:', tourId);
// First, delete all files from disk
const photos = await this.prisma.photo.findMany({ where: { tourId } });
console.log('[deletePermanent] Found', photos.length, 'photos for tour');
for (const p of photos) {
if (p.imageUrl) {
const displayFilePath = path.join(process.cwd(), p.imageUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking display file:', displayFilePath);
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
console.log('[deletePermanent] Deleted display file:', displayFilePath);
} catch (e) {
console.error('[deletePermanent] Error deleting display file:', e);
}
}
}
if (p.originalUrl) {
const originalFilePath = path.join(process.cwd(), p.originalUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking original file:', originalFilePath);
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
console.log('[deletePermanent] Deleted original file:', originalFilePath);
} catch (e) {
console.error('[deletePermanent] Error deleting original file:', e);
}
}
}
}
// Delete the tour - cascade will handle related records
await this.prisma.tour.delete({ where: { id: tourId } });
console.log('[deletePermanent] Deleted tour from database:', tourId);
deleteResults.success++;
} catch (e: any) {
console.error('[deletePermanent] Error deleting tour:', tourId, e);
deleteResults.failed++;
deleteResults.errors.push(`Tour ${tourId}: ${e?.message || 'Unknown error'}`);
}
}
} else if (type === 'photo') {
console.log('[deletePermanent] Deleting', ids.length, 'photos');
console.log('[deletePermanent] Photo IDs:', JSON.stringify(ids));
if (!ids || ids.length === 0) {
console.warn('[deletePermanent] No photo IDs provided');
return { success: true, deleted: 0, failed: 0 };
}
for (const photoId of ids) {
try {
console.log('[deletePermanent] ========== START Processing photo:', photoId);
const photo = await this.prisma.photo.findUnique({ where: { id: photoId } });
if (!photo) {
console.warn('[deletePermanent] Photo not found in DB:', photoId);
deleteResults.failed++;
deleteResults.errors.push(`Photo ${photoId}: not found in database`);
console.log('[deletePermanent] ========== SKIP (not found):', photoId);
continue;
}
console.log('[deletePermanent] Found photo:', { id: photo.id, imageUrl: photo.imageUrl, isDeleted: photo.isDeleted, tourId: photo.tourId });
// First, delete all comments for this photo (because of foreign key)
console.log('[deletePermanent] Deleting comments for photo:', photoId);
try {
const commentResult = await this.prisma.comment.deleteMany({
where: { photoId }
});
console.log('[deletePermanent] Deleted', commentResult.count, 'comments for photo');
} catch (e: any) {
console.warn('[deletePermanent] Error deleting comments (continuing):', e?.message);
}
// Delete files from disk
if (photo.imageUrl) {
const displayFilePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking display file:', displayFilePath);
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
console.log('[deletePermanent] Deleted display file:', displayFilePath);
console.log('[deletePermanent] Deleted display file');
} catch (e) {
console.error('[deletePermanent] Error deleting display file:', e);
}
} else {
console.log('[deletePermanent] Display file not found (OK):', displayFilePath);
}
}
if (p.originalUrl) {
const originalFilePath = path.join(process.cwd(), p.originalUrl.replace(/^\//, ''));
if (photo.originalUrl) {
const originalFilePath = path.join(process.cwd(), photo.originalUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking original file:', originalFilePath);
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
console.log('[deletePermanent] Deleted original file:', originalFilePath);
console.log('[deletePermanent] Deleted original file');
} catch (e) {
console.error('[deletePermanent] Error deleting original file:', e);
}
} else {
console.log('[deletePermanent] Original file not found (OK):', originalFilePath);
}
}
// Delete from database
console.log('[deletePermanent] Attempting database delete for photo:', photoId);
const result = await this.prisma.photo.delete({ where: { id: photoId } });
console.log('[deletePermanent] ✓ Successfully deleted photo from database:', photoId);
deleteResults.success++;
console.log('[deletePermanent] ========== SUCCESS:', photoId);
} catch (e: any) {
console.error('[deletePermanent] ✗ Error deleting photo:', photoId);
console.error('[deletePermanent] Error details:', e?.message || String(e));
deleteResults.failed++;
deleteResults.errors.push(`Photo ${photoId}: ${e?.message || 'Unknown error'}`);
console.log('[deletePermanent] ========== FAILED:', photoId);
}
await this.prisma.tour.delete({ where: { id: tourId } });
console.log('[deletePermanent] Deleted tour from database:', tourId);
}
} else if (type === 'photo') {
console.log('[deletePermanent] Deleting', ids.length, 'photos');
for (const photoId of ids) {
console.log('[deletePermanent] Processing photo:', photoId);
const photo = await this.prisma.photo.findUnique({ where: { id: photoId } });
if (!photo) {
console.warn('[deletePermanent] Photo not found:', photoId);
continue;
}
console.log('[deletePermanent] Photo found, imageUrl:', photo.imageUrl);
if (photo.imageUrl) {
const displayFilePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking display file:', displayFilePath);
if (fs.existsSync(displayFilePath)) {
try {
fs.unlinkSync(displayFilePath);
console.log('[deletePermanent] Deleted display file');
} catch (e) {
console.error('[deletePermanent] Error deleting display file:', e);
}
} else {
console.log('[deletePermanent] Display file not found (OK)');
}
}
if (photo.originalUrl) {
const originalFilePath = path.join(process.cwd(), photo.originalUrl.replace(/^\//, ''));
console.log('[deletePermanent] Checking original file:', originalFilePath);
if (fs.existsSync(originalFilePath)) {
try {
fs.unlinkSync(originalFilePath);
console.log('[deletePermanent] Deleted original file');
} catch (e) {
console.error('[deletePermanent] Error deleting original file:', e);
}
} else {
console.log('[deletePermanent] Original file not found (OK)');
}
}
const result = await this.prisma.photo.delete({ where: { id: photoId } });
console.log('[deletePermanent] Deleted photo from database:', photoId);
}
console.log('[deletePermanent] Photos deletion complete - success:', deleteResults.success, 'failed:', deleteResults.failed);
} else if (type === 'note') {
console.log('[deletePermanent] Deleting', ids.length, 'notes');
const result = await this.prisma.tourNote.deleteMany({
where: { id: { in: ids } }
});
console.log('[deletePermanent] Deleted notes from database, count:', result.count);
try {
const result = await this.prisma.tourNote.deleteMany({
where: { id: { in: ids } }
});
console.log('[deletePermanent] Deleted notes from database, count:', result.count);
deleteResults.success = result.count;
} catch (e: any) {
console.error('[deletePermanent] Error deleting notes:', e);
deleteResults.failed = ids.length;
deleteResults.errors.push(`Notes deletion: ${e?.message || 'Unknown error'}`);
}
}
console.log('[deletePermanent] Deletion complete, type:', type);
return { success: true };
console.log('[deletePermanent] Deletion complete - success:', deleteResults.success, 'failed:', deleteResults.failed);
if (deleteResults.failed > 0) {
console.warn('[deletePermanent] Errors:', deleteResults.errors);
}
return {
success: true,
deleted: deleteResults.success,
failed: deleteResults.failed,
errors: deleteResults.errors.length > 0 ? deleteResults.errors : undefined
};
}
@Post('empty-all')
async emptyAllTrash() {
console.log('[emptyAllTrash] Starting to empty all trash');
const deleteResults = { success: 0, failed: 0, errors: [] as string[] };
try {
// Get all deleted photos
console.log('[emptyAllTrash] Finding all deleted photos');
const deletedPhotos = await this.prisma.photo.findMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Found', deletedPhotos.length, 'deleted photos');
// Delete all comments from deleted photos
console.log('[emptyAllTrash] Deleting all comments from deleted photos');
const commentCount = await this.prisma.comment.deleteMany({
where: {
photoId: { in: deletedPhotos.map(p => p.id) }
}
});
console.log('[emptyAllTrash] Deleted', commentCount.count, 'comments');
// Delete files from disk
for (const photo of deletedPhotos) {
if (photo.imageUrl) {
const filePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
console.log('[emptyAllTrash] Deleted display file:', filePath);
} catch (e) {
console.error('[emptyAllTrash] Error deleting display file:', e);
}
}
}
if (photo.originalUrl) {
const filePath = path.join(process.cwd(), photo.originalUrl.replace(/^\//, ''));
if (fs.existsSync(filePath)) {
try {
fs.unlinkSync(filePath);
console.log('[emptyAllTrash] Deleted original file:', filePath);
} catch (e) {
console.error('[emptyAllTrash] Error deleting original file:', e);
}
}
}
}
// Delete all photos
console.log('[emptyAllTrash] Deleting all', deletedPhotos.length, 'photos from database');
const photoResult = await this.prisma.photo.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', photoResult.count, 'photos');
deleteResults.success += photoResult.count;
// Delete all deleted tours (cascade will handle related)
console.log('[emptyAllTrash] Deleting all deleted tours');
const tourResult = await this.prisma.tour.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', tourResult.count, 'tours');
deleteResults.success += tourResult.count;
// Delete all deleted notes
console.log('[emptyAllTrash] Deleting all deleted notes');
const noteResult = await this.prisma.tourNote.deleteMany({
where: { isDeleted: true }
});
console.log('[emptyAllTrash] Deleted', noteResult.count, 'notes');
deleteResults.success += noteResult.count;
console.log('[emptyAllTrash] Successfully emptied all trash - total deleted:', deleteResults.success);
} catch (e: any) {
console.error('[emptyAllTrash] Error emptying trash:', e?.message || e);
deleteResults.failed++;
deleteResults.errors.push(`Emptying trash: ${e?.message || 'Unknown error'}`);
}
return {
success: true,
totalDeleted: deleteResults.success,
errors: deleteResults.errors.length > 0 ? deleteResults.errors : undefined
};
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1003 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB