fix: sửa tính năng hiển thị quãng đường tại điểm đến kế tiếp

This commit is contained in:
2026-06-17 15:37:18 +07:00
parent 7ad785fed9
commit c5474f1ff6
7 changed files with 450 additions and 107 deletions
+112 -11
View File
@@ -178,6 +178,11 @@ export class TourRoleGuard implements CanActivate {
if (!rolesToCheck.some(r => role === r)) {
throw new ForbiddenException('Bạn không có quyền thực hiện hành động này trong tour này.');
}
// Ensure tourId is attached to the request object for controllers to use
if (tourId) {
(request as any).tourId = tourId;
}
return true;
}
}
@@ -350,6 +355,7 @@ class TourController {
if (!leg) throw new NotFoundException('Không tìm thấy chặng phù hợp để thêm địa điểm');
return this.prisma.location.create({
// ...
data: {
name: body.name,
address: body.address,
@@ -374,7 +380,13 @@ class TourController {
}
});
}
return loc;
// Xóa triệt để các loại cache của Tour (cả key UUID và key URL của Interceptor)
await Promise.all([
this.cacheManager.del(tourId),
this.cacheManager.del(`/api/v1/tours/${tourId}`),
this.cacheManager.del(`/api/v1/tours/${tourId}/public`),
]);
return loc; // Return the created location
});
}
@@ -413,6 +425,13 @@ class TourController {
legId: firstLeg.id,
plannedStart: new Date(0),
}
}).then(async (loc) => {
await Promise.all([
this.cacheManager.del(tourId),
this.cacheManager.del(`/api/v1/tours/${tourId}`),
this.cacheManager.del(`/api/v1/tours/${tourId}/public`),
]);
return loc;
});
}
@@ -449,6 +468,13 @@ class TourController {
legId: lastLeg.id,
plannedEnd: new Date(0), // Đánh dấu đây là điểm kết thúc đặc biệt
}
}).then(async (loc) => {
await Promise.all([
this.cacheManager.del(tourId),
this.cacheManager.del(`/api/v1/tours/${tourId}`),
this.cacheManager.del(`/api/v1/tours/${tourId}/public`),
]);
return loc;
});
}
@@ -495,7 +521,13 @@ class TourController {
});
}
return allLegs;
// Invalidate cache for the tour after initializing legs
await Promise.all([
this.cacheManager.del(tourId),
this.cacheManager.del(`/api/v1/tours/${tourId}`),
this.cacheManager.del(`/api/v1/tours/${tourId}/public`),
]);
return allLegs; // Return all legs
}
@Roles(ParticipantRole.OWNER, ParticipantRole.MANAGER) // Only owner/manager can add legs
@@ -514,6 +546,13 @@ class TourController {
sequence: tour.legs.length + 1,
note: body.note || `Chặng ${tour.legs.length + 1}`
}
}).then(async (leg) => {
await Promise.all([
this.cacheManager.del(tourId),
this.cacheManager.del(`/api/v1/tours/${tourId}`),
this.cacheManager.del(`/api/v1/tours/${tourId}/public`),
]);
return leg;
});
}
@@ -534,6 +573,13 @@ class TourController {
childCount: body.childCount !== undefined ? Number(body.childCount) : undefined,
childDiscount: body.childDiscount !== undefined ? Number(body.childDiscount) : undefined,
},
}).then(async (tour) => {
await Promise.all([
this.cacheManager.del(id),
this.cacheManager.del(`/api/v1/tours/${id}`),
this.cacheManager.del(`/api/v1/tours/${id}/public`),
]);
return tour;
});
}
@@ -938,10 +984,11 @@ class TourController {
@Controller('locations')
@UseGuards(JwtAuthGuard)
class LocationController {
constructor(private prisma: PrismaService) {}
constructor(private prisma: PrismaService, @Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Patch(':id')
async updateLocation(@Param('id', ParseUUIDPipe) id: string, @Body() body: any) {
@UseGuards(TourRoleGuard) // Apply TourRoleGuard to get tourId
async updateLocation(@Param('id', ParseUUIDPipe) id: string, @Body() body: any, @Req() req: any) {
const { expenseAmount, expenseCategory, ...data } = body;
const location = await this.prisma.location.update({
@@ -982,12 +1029,35 @@ class LocationController {
}
}
if (req.tourId) {
await Promise.all([
this.cacheManager.del(req.tourId),
this.cacheManager.del(`/api/v1/tours/${req.tourId}`),
this.cacheManager.del(`/api/v1/tours/${req.tourId}/public`),
]);
}
return location;
}
@Delete(':id')
async deleteLocation(@Param('id', ParseUUIDPipe) id: string) {
await this.prisma.location.delete({ where: { id } });
@UseGuards(TourRoleGuard) // Apply TourRoleGuard to get tourId
async deleteLocation(@Param('id', ParseUUIDPipe) id: string, @Req() req: any) {
try {
await this.prisma.location.delete({ where: { id } });
} catch (e) {
// Nếu bản ghi đã bị xóa trước đó, không ném lỗi 500 để đảm bảo tính an toàn (idempotency)
}
// Xóa mapping cache để Guard không bị đánh lừa ở lần truy cập sau
await this.cacheManager.del(`res-to-tour:${id}`);
if (req.tourId) {
await Promise.all([
this.cacheManager.del(req.tourId),
this.cacheManager.del(`/api/v1/tours/${req.tourId}`),
this.cacheManager.del(`/api/v1/tours/${req.tourId}/public`),
]);
}
return { success: true };
}
}
@@ -996,10 +1066,11 @@ class LocationController {
@Roles(ParticipantRole.OWNER, ParticipantRole.MANAGER) // Default roles for Leg operations
@UseGuards(JwtAuthGuard, TourRoleGuard)
class LegController {
constructor(private prisma: PrismaService) {}
constructor(private prisma: PrismaService, @Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Patch(':id')
async updateLeg(@Param('id', ParseUUIDPipe) id: string, @Body() body: any) {
@UseGuards(TourRoleGuard) // Apply TourRoleGuard to get tourId
async updateLeg(@Param('id', ParseUUIDPipe) id: string, @Body() body: any, @Req() req: any) {
return this.prisma.leg.update({
where: { id },
data: {
@@ -1009,10 +1080,18 @@ class LegController {
endDate: body.endDate ? new Date(body.endDate) : undefined,
description: body.description,
}
}).then(async (leg) => {
await Promise.all([
this.cacheManager.del(req.tourId),
this.cacheManager.del(`/api/v1/tours/${req.tourId}`),
this.cacheManager.del(`/api/v1/tours/${req.tourId}/public`),
]);
return leg;
});
}
@Delete(':id')
@UseGuards(TourRoleGuard) // Apply TourRoleGuard to get tourId
async deleteLeg(@Param('id', ParseUUIDPipe) id: string) {
const leg = await this.prisma.leg.findUnique({
where: { id },
@@ -1023,7 +1102,21 @@ class LegController {
throw new BadRequestException('Không thể xóa chặng đang có địa điểm. Hãy xóa địa điểm trước.');
}
await this.prisma.leg.delete({ where: { id } });
try {
const deletedLeg = await this.prisma.leg.delete({ where: { id } });
await this.cacheManager.del(`res-to-tour:${id}`);
if (deletedLeg.tourId) {
await Promise.all([
this.cacheManager.del(deletedLeg.tourId),
this.cacheManager.del(`/api/v1/tours/${deletedLeg.tourId}`),
this.cacheManager.del(`/api/v1/tours/${deletedLeg.tourId}/public`),
]);
}
} catch (e) {
// Idempotency
}
return { success: true };
}
}
@@ -1044,10 +1137,11 @@ function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: numbe
@Roles(ParticipantRole.OWNER, ParticipantRole.MANAGER) // Default roles for Routing operations
@UseGuards(JwtAuthGuard, TourRoleGuard)
class RoutingController {
constructor(private prisma: PrismaService) {}
constructor(private prisma: PrismaService, @Inject(CACHE_MANAGER) private cacheManager: Cache) {}
@Post('optimize/:legId')
async optimize(@Param('legId', ParseUUIDPipe) legId: string) {
@UseGuards(TourRoleGuard) // Apply TourRoleGuard to get tourId
async optimize(@Param('legId', ParseUUIDPipe) legId: string, @Req() req: any) {
const currentLeg = await this.prisma.leg.findUnique({
where: { id: legId },
});
@@ -1148,6 +1242,13 @@ class RoutingController {
orderBy: { plannedStart: 'asc' }
});
if (req.tourId) {
await Promise.all([
this.cacheManager.del(req.tourId),
this.cacheManager.del(`/api/v1/tours/${req.tourId}`),
this.cacheManager.del(`/api/v1/tours/${req.tourId}/public`),
]);
}
return {
locations: updatedLocations,
totalDistance: parseFloat(totalDistance.toFixed(2))