Thêm tính năng bình luận ở mỗi điểm của chặng

This commit is contained in:
2026-06-16 08:27:54 +07:00
parent c5530f36df
commit 5464d90948
15 changed files with 1427 additions and 97 deletions
+85 -6
View File
@@ -7,7 +7,9 @@ dotenv.config({ path: envPath });
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Query, ForbiddenException } from '@nestjs/common';
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Query, ForbiddenException, Injectable } from '@nestjs/common';
import { WebSocketGateway, WebSocketServer, SubscribeMessage, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';
import { AdminGuard } from './auth/admin.guard';
@@ -360,7 +362,10 @@ class TourController {
legs: {
orderBy: { sequence: 'asc' },
include: {
locations: { orderBy: { plannedStart: 'asc' } }
locations: {
orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } }
}
}
}
}
@@ -384,7 +389,10 @@ class TourController {
legs: {
orderBy: { sequence: 'asc' },
include: {
locations: { orderBy: { plannedStart: 'asc' } },
locations: {
orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } }
},
},
},
},
@@ -871,15 +879,86 @@ class UserController {
}
}
@WebSocketGateway({ cors: { origin: '*' } })
export class CommentGateway implements OnGatewayConnection {
@WebSocketServer() server: Server;
handleConnection(client: Socket) {
console.log(`[WS] Client connected: ${client.id}`);
}
@SubscribeMessage('joinTour')
handleJoinTour(client: Socket, tourId: string) {
client.join(`tour_${tourId}`);
console.log(`[WS] Client ${client.id} joined room: tour_${tourId}`);
}
notifyNewComment(tourId: string, data: any) {
// Gửi thông báo tới tất cả client trong phòng của Tour này
this.server.to(`tour_${tourId}`).emit('commentAdded', data);
}
}
@Controller('locations')
@UseGuards(JwtAuthGuard)
class CommentController {
constructor(
private prisma: PrismaService,
private commentGateway: CommentGateway
) {}
@Get(':locationId/comments')
async getComments(@Param('locationId', ParseUUIDPipe) locationId: string) {
return this.prisma.comment.findMany({
where: { locationId },
include: {
user: { select: { name: true } }
},
orderBy: { createdAt: 'asc' }
});
}
@Post(':locationId/comments')
async addComment(
@Param('locationId', ParseUUIDPipe) locationId: string,
@Body() body: { content: string },
@Req() req: any
) {
const comment = await this.prisma.comment.create({
data: {
content: body.content,
locationId,
userId: req.user.id
},
include: { user: { select: { name: true } } }
});
// Tìm tourId để gửi thông báo vào đúng phòng
const location = await this.prisma.location.findUnique({
where: { id: locationId },
include: { leg: { select: { tourId: true } } }
});
if (location?.leg?.tourId) {
this.commentGateway.notifyNewComment(location.leg.tourId, {
...comment,
locationId // Gửi kèm locationId để UI biết điểm nào cần tăng số lượng
});
}
return comment;
}
}
@Module({
imports: [
JwtModule.register({
secret: process.env.JWT_SECRET || 'super-secret',
signOptions: { expiresIn: '1d' },
}),
}) as any,
],
controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController, LocationController],
providers: [PrismaService, JwtStrategy, TourRoleGuard, JwtAuthGuard, AdminGuard],
controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController, LocationController, CommentController],
providers: [PrismaService, JwtStrategy, TourRoleGuard, JwtAuthGuard, AdminGuard, CommentGateway],
exports: [PrismaService]
})
class AppModule {}