optimize: sử dụng redis để cache cho các response từ server
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Cache } from 'cache-manager';
|
||||
import { HttpAdapterHost } from '@nestjs/core';
|
||||
export declare class CompressCacheInterceptor implements NestInterceptor {
|
||||
private cacheManager;
|
||||
private readonly httpAdapterHost;
|
||||
constructor(cacheManager: Cache, httpAdapterHost: HttpAdapterHost);
|
||||
intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CompressCacheInterceptor = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const rxjs_1 = require("rxjs");
|
||||
const operators_1 = require("rxjs/operators");
|
||||
const cache_manager_1 = require("@nestjs/cache-manager");
|
||||
const zlib = __importStar(require("zlib"));
|
||||
const util_1 = require("util");
|
||||
const core_1 = require("@nestjs/core");
|
||||
const gzip = (0, util_1.promisify)(zlib.gzip);
|
||||
const gunzip = (0, util_1.promisify)(zlib.gunzip);
|
||||
const COMPRESSION_THRESHOLD = 100;
|
||||
let CompressCacheInterceptor = class CompressCacheInterceptor {
|
||||
constructor(cacheManager, httpAdapterHost) {
|
||||
this.cacheManager = cacheManager;
|
||||
this.httpAdapterHost = httpAdapterHost;
|
||||
}
|
||||
async intercept(context, next) {
|
||||
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
||||
const request = context.getArgByIndex(0);
|
||||
const response = context.getArgByIndex(1);
|
||||
if (httpAdapter.getRequestMethod(request) !== 'GET') {
|
||||
return next.handle();
|
||||
}
|
||||
const cacheKey = httpAdapter.getRequestUrl(request);
|
||||
let cachedData = await this.cacheManager.get(cacheKey);
|
||||
if (cachedData) {
|
||||
try {
|
||||
if (Buffer.isBuffer(cachedData) && cachedData.length > 2 && cachedData[0] === 0x1f && cachedData[1] === 0x8b) {
|
||||
const decompressed = await gunzip(cachedData);
|
||||
const jsonString = decompressed.toString('utf8');
|
||||
httpAdapter.setHeader(response, 'Content-Type', 'application/json');
|
||||
httpAdapter.setHeader(response, 'X-Cache', 'HIT (Compressed)');
|
||||
return (0, rxjs_1.of)(JSON.parse(jsonString));
|
||||
}
|
||||
else {
|
||||
const jsonString = cachedData.toString();
|
||||
httpAdapter.setHeader(response, 'Content-Type', 'application/json');
|
||||
httpAdapter.setHeader(response, 'X-Cache', 'HIT (Uncompressed)');
|
||||
return (0, rxjs_1.of)(JSON.parse(jsonString));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`[Cache] Lỗi giải nén dữ liệu cache cho key ${cacheKey}:`, e);
|
||||
await this.cacheManager.del(cacheKey);
|
||||
}
|
||||
}
|
||||
httpAdapter.setHeader(response, 'X-Cache', 'MISS');
|
||||
return next.handle().pipe((0, operators_1.tap)(async (data) => {
|
||||
if (!data)
|
||||
return;
|
||||
const jsonString = JSON.stringify(data);
|
||||
const ttl = 60000;
|
||||
if (jsonString.length > COMPRESSION_THRESHOLD) {
|
||||
try {
|
||||
const compressed = await gzip(Buffer.from(jsonString, 'utf8'));
|
||||
await this.cacheManager.set(cacheKey, compressed, ttl);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`[Cache] Lỗi nén dữ liệu cho key ${cacheKey}:`, e);
|
||||
await this.cacheManager.set(cacheKey, jsonString, ttl);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await this.cacheManager.set(cacheKey, jsonString, ttl);
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
exports.CompressCacheInterceptor = CompressCacheInterceptor;
|
||||
exports.CompressCacheInterceptor = CompressCacheInterceptor = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(0, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
|
||||
__metadata("design:paramtypes", [Object, core_1.HttpAdapterHost])
|
||||
], CompressCacheInterceptor);
|
||||
//# sourceMappingURL=compress-cache.interceptor.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"compress-cache.interceptor.js","sourceRoot":"","sources":["../../../src/common/compress-cache.interceptor.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+3
-1
@@ -5,12 +5,14 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { ParticipantRole } from '@prisma/client';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Cache } from 'cache-manager';
|
||||
export declare const ROLES_KEY = "roles";
|
||||
export declare const Roles: (...roles: ParticipantRole[]) => import("@nestjs/common").CustomDecorator<string>;
|
||||
export declare class TourRoleGuard implements CanActivate {
|
||||
private reflector;
|
||||
private prisma;
|
||||
constructor(reflector: Reflector, prisma: PrismaService);
|
||||
private cacheManager;
|
||||
constructor(reflector: Reflector, prisma: PrismaService, cacheManager: Cache);
|
||||
canActivate(context: ExecutionContext): Promise<boolean>;
|
||||
}
|
||||
export declare class CommentGateway implements OnGatewayConnection {
|
||||
|
||||
Vendored
+139
-17
@@ -55,6 +55,8 @@ const envPath = path.resolve(process.cwd(), '..', '.env');
|
||||
dotenv.config({ path: envPath });
|
||||
require("reflect-metadata");
|
||||
const fs = __importStar(require("fs"));
|
||||
const zlib = __importStar(require("zlib"));
|
||||
const util_1 = require("util");
|
||||
const sharp_1 = __importDefault(require("sharp"));
|
||||
const core_1 = require("@nestjs/core");
|
||||
const common_1 = require("@nestjs/common");
|
||||
@@ -70,7 +72,17 @@ const jwt_auth_guard_1 = 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");
|
||||
const cache_manager_1 = require("@nestjs/cache-manager");
|
||||
const cache_manager_redis_yet_1 = require("cache-manager-redis-yet");
|
||||
const compress_cache_interceptor_1 = require("./common/compress-cache.interceptor");
|
||||
const gzip = (0, util_1.promisify)(zlib.gzip);
|
||||
const gunzip = (0, util_1.promisify)(zlib.gunzip);
|
||||
const UPLOAD_ROOT = path.join(process.cwd(), 'uploads');
|
||||
const CACHE_TTL = {
|
||||
DEFAULT: 600000,
|
||||
RESOURCE_TO_TOUR: 3600000,
|
||||
USER_ROLE: 300000,
|
||||
};
|
||||
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);
|
||||
@@ -94,9 +106,10 @@ exports.ROLES_KEY = 'roles';
|
||||
const Roles = (...roles) => (0, common_2.SetMetadata)(exports.ROLES_KEY, roles);
|
||||
exports.Roles = Roles;
|
||||
let TourRoleGuard = class TourRoleGuard {
|
||||
constructor(reflector, prisma) {
|
||||
constructor(reflector, prisma, cacheManager) {
|
||||
this.reflector = reflector;
|
||||
this.prisma = prisma;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
async canActivate(context) {
|
||||
const requiredRoles = this.reflector.getAllAndOverride(exports.ROLES_KEY, [
|
||||
@@ -107,14 +120,73 @@ let TourRoleGuard = class TourRoleGuard {
|
||||
const rolesToCheck = requiredRoles && requiredRoles.length > 0 ? requiredRoles : defaultRoles;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user = request.user;
|
||||
const tourId = request.params.tourId || request.params.id;
|
||||
let tourId = request.params.tourId;
|
||||
const resourceId = request.params.id || request.params.legId || request.params.locationId;
|
||||
if (!tourId && resourceId) {
|
||||
const resCacheKey = `res-to-tour:${resourceId}`;
|
||||
const compressedData = await this.cacheManager.get(resCacheKey);
|
||||
if (compressedData) {
|
||||
try {
|
||||
const decompressed = await gunzip(compressedData);
|
||||
tourId = decompressed.toString();
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Lỗi giải nén cache:', e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const isTour = await this.prisma.tour.findUnique({ where: { id: resourceId }, select: { id: true } });
|
||||
if (isTour) {
|
||||
tourId = resourceId;
|
||||
}
|
||||
else {
|
||||
const leg = await this.prisma.leg.findUnique({ where: { id: resourceId }, select: { tourId: true } });
|
||||
if (leg) {
|
||||
tourId = leg.tourId;
|
||||
}
|
||||
else {
|
||||
const loc = await this.prisma.location.findUnique({
|
||||
where: { id: resourceId },
|
||||
include: { leg: { select: { tourId: true } } }
|
||||
});
|
||||
if (loc) {
|
||||
tourId = loc.leg.tourId;
|
||||
}
|
||||
else {
|
||||
const photo = await this.prisma.photo.findUnique({ where: { id: resourceId }, select: { tourId: true } });
|
||||
if (photo)
|
||||
tourId = photo.tourId;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tourId) {
|
||||
try {
|
||||
const compressed = await gzip(Buffer.from(tourId));
|
||||
await this.cacheManager.set(resCacheKey, compressed, CACHE_TTL.RESOURCE_TO_TOUR);
|
||||
}
|
||||
catch (e) {
|
||||
await this.cacheManager.set(resCacheKey, tourId, CACHE_TTL.RESOURCE_TO_TOUR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!user || !tourId) {
|
||||
if (!resourceId && !request.params.tourId)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
const participation = await this.prisma.tourParticipant.findUnique({
|
||||
where: { tourId_userId: { tourId, userId: user.id } },
|
||||
});
|
||||
if (!participation || !rolesToCheck.some(role => participation.role === role)) {
|
||||
const roleCacheKey = `user-role:${user.id}:${tourId}`;
|
||||
let role = await this.cacheManager.get(roleCacheKey);
|
||||
if (!role) {
|
||||
const participation = await this.prisma.tourParticipant.findUnique({
|
||||
where: { tourId_userId: { tourId, userId: user.id } },
|
||||
});
|
||||
if (!participation)
|
||||
return false;
|
||||
role = participation.role;
|
||||
await this.cacheManager.set(roleCacheKey, role, CACHE_TTL.USER_ROLE);
|
||||
}
|
||||
if (!rolesToCheck.some(r => role === r)) {
|
||||
throw new common_1.ForbiddenException('Bạn không có quyền thực hiện hành động này trong tour này.');
|
||||
}
|
||||
return true;
|
||||
@@ -123,7 +195,9 @@ let TourRoleGuard = class TourRoleGuard {
|
||||
exports.TourRoleGuard = TourRoleGuard;
|
||||
exports.TourRoleGuard = TourRoleGuard = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [core_2.Reflector, prisma_service_1.PrismaService])
|
||||
__param(2, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
|
||||
__metadata("design:paramtypes", [core_2.Reflector,
|
||||
prisma_service_1.PrismaService, Object])
|
||||
], TourRoleGuard);
|
||||
let AppController = class AppController {
|
||||
getHello() {
|
||||
@@ -212,6 +286,7 @@ let PublicTourController = class PublicTourController {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
async getPublicTourDetails(id) {
|
||||
console.log(`[PublicTour] Đang truy vấn chi tiết Tour ID: ${id}`);
|
||||
const tour = await this.prisma.tour.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
@@ -240,12 +315,15 @@ let PublicTourController = class PublicTourController {
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!tour)
|
||||
if (!tour) {
|
||||
console.warn(`[PublicTour] Không tìm thấy Tour với ID: ${id} trong Database`);
|
||||
throw new common_1.NotFoundException(`Không tìm thấy Tour`);
|
||||
}
|
||||
return tour;
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
(0, common_1.UseInterceptors)(compress_cache_interceptor_1.CompressCacheInterceptor),
|
||||
(0, common_1.Get)(':id/public'),
|
||||
__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
||||
__metadata("design:type", Function),
|
||||
@@ -257,8 +335,9 @@ PublicTourController = __decorate([
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
], PublicTourController);
|
||||
let TourController = class TourController {
|
||||
constructor(prisma) {
|
||||
constructor(prisma, cacheManager) {
|
||||
this.prisma = prisma;
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
async createTour(body, req) {
|
||||
const { title, description, startDate, endDate, adultCount, childCount, childDiscount, tags } = body;
|
||||
@@ -445,10 +524,32 @@ let TourController = class TourController {
|
||||
});
|
||||
}
|
||||
async deleteTour(id) {
|
||||
const photos = await this.prisma.photo.findMany({
|
||||
where: { tourId: id }
|
||||
const tour = await this.prisma.tour.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
participants: true,
|
||||
photos: true,
|
||||
legs: {
|
||||
include: { locations: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
for (const photo of photos) {
|
||||
if (!tour)
|
||||
throw new common_1.NotFoundException('Không tìm thấy tour');
|
||||
for (const participant of tour.participants) {
|
||||
await this.cacheManager.del(`user-role:${participant.userId}:${id}`);
|
||||
}
|
||||
await this.cacheManager.del(`res-to-tour:${id}`);
|
||||
for (const leg of tour.legs) {
|
||||
await this.cacheManager.del(`res-to-tour:${leg.id}`);
|
||||
for (const loc of leg.locations) {
|
||||
await this.cacheManager.del(`res-to-tour:${loc.id}`);
|
||||
}
|
||||
}
|
||||
for (const photo of tour.photos) {
|
||||
await this.cacheManager.del(`res-to-tour:${photo.id}`);
|
||||
}
|
||||
for (const photo of tour.photos) {
|
||||
if (photo.imageUrl) {
|
||||
const displayFilePath = path.join(process.cwd(), photo.imageUrl.replace(/^\//, ''));
|
||||
if (fs.existsSync(displayFilePath)) {
|
||||
@@ -527,6 +628,7 @@ let TourController = class TourController {
|
||||
async addMember(tourId, body, req) {
|
||||
const validRoles = ['OWNER', 'MANAGER', 'MEMBER', 'MEMBER_NO_FINANCE', 'VIEWER_ONLY'];
|
||||
const role = validRoles.includes(body.role) ? body.role : 'MEMBER';
|
||||
await this.cacheManager.del(`user-role:${body.userId}:${tourId}`);
|
||||
const participation = await this.prisma.tourParticipant.findUnique({
|
||||
where: { tourId_userId: { tourId, userId: body.userId } },
|
||||
});
|
||||
@@ -627,6 +729,7 @@ let TourController = class TourController {
|
||||
if (joinRequest.status !== 'PENDING') {
|
||||
throw new common_1.BadRequestException('Yêu cầu này đã được xử lý trước đó.');
|
||||
}
|
||||
await this.cacheManager.del(`user-role:${joinRequest.userId}:${tourId}`);
|
||||
const existing = await this.prisma.tourParticipant.findUnique({
|
||||
where: { tourId_userId: { tourId, userId: joinRequest.userId } },
|
||||
});
|
||||
@@ -685,6 +788,7 @@ let TourController = class TourController {
|
||||
if (!participation) {
|
||||
throw new common_1.NotFoundException('Thành viên này không có trong tour');
|
||||
}
|
||||
await this.cacheManager.del(`user-role:${userId}:${tourId}`);
|
||||
await this.prisma.tourParticipant.delete({
|
||||
where: { tourId_userId: { tourId, userId } },
|
||||
});
|
||||
@@ -809,6 +913,7 @@ __decorate([
|
||||
__metadata("design:returntype", Promise)
|
||||
], 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.Get)('explore'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
@@ -817,6 +922,7 @@ __decorate([
|
||||
__metadata("design:returntype", Promise)
|
||||
], TourController.prototype, "getPublicTours", null);
|
||||
__decorate([
|
||||
(0, common_1.UseInterceptors)(compress_cache_interceptor_1.CompressCacheInterceptor),
|
||||
(0, exports.Roles)(client_1.ParticipantRole.OWNER, client_1.ParticipantRole.MANAGER, client_1.ParticipantRole.MEMBER, client_1.ParticipantRole.MEMBER_NO_FINANCE, client_1.ParticipantRole.VIEWER_ONLY),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, TourRoleGuard),
|
||||
(0, common_1.Get)(':id'),
|
||||
@@ -903,7 +1009,8 @@ __decorate([
|
||||
], TourController.prototype, "uploadPhotos", null);
|
||||
TourController = __decorate([
|
||||
(0, common_1.Controller)('tours'),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
__param(1, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService, Object])
|
||||
], TourController);
|
||||
let LocationController = class LocationController {
|
||||
constructor(prisma) {
|
||||
@@ -1020,7 +1127,7 @@ __decorate([
|
||||
LegController = __decorate([
|
||||
(0, common_1.Controller)('legs'),
|
||||
(0, exports.Roles)(client_1.ParticipantRole.OWNER, client_1.ParticipantRole.MANAGER),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, TourRoleGuard),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
], LegController);
|
||||
function calculateDistance(lat1, lon1, lat2, lon2) {
|
||||
@@ -1123,6 +1230,7 @@ __decorate([
|
||||
RoutingController = __decorate([
|
||||
(0, common_1.Controller)('routing'),
|
||||
(0, exports.Roles)(client_1.ParticipantRole.OWNER, client_1.ParticipantRole.MANAGER),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, TourRoleGuard),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
], RoutingController);
|
||||
let PhotoController = class PhotoController {
|
||||
@@ -1166,7 +1274,7 @@ __decorate([
|
||||
PhotoController = __decorate([
|
||||
(0, common_1.Controller)('photos'),
|
||||
(0, exports.Roles)(client_1.ParticipantRole.OWNER, client_1.ParticipantRole.MANAGER, client_1.ParticipantRole.MEMBER, client_1.ParticipantRole.MEMBER_NO_FINANCE),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, TourRoleGuard),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
], PhotoController);
|
||||
let UserController = class UserController {
|
||||
@@ -1364,7 +1472,8 @@ __decorate([
|
||||
__metadata("design:returntype", Promise)
|
||||
], CommentController.prototype, "getComments", null);
|
||||
__decorate([
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard),
|
||||
(0, exports.Roles)(client_1.ParticipantRole.OWNER, client_1.ParticipantRole.MANAGER, client_1.ParticipantRole.MEMBER, client_1.ParticipantRole.MEMBER_NO_FINANCE, client_1.ParticipantRole.VIEWER_ONLY),
|
||||
(0, common_1.UseGuards)(jwt_auth_guard_1.JwtAuthGuard, TourRoleGuard),
|
||||
(0, common_1.Post)(':locationId/comments'),
|
||||
__param(0, (0, common_1.Param)('locationId', common_1.ParseUUIDPipe)),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
@@ -1383,6 +1492,15 @@ let AppModule = class AppModule {
|
||||
AppModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [
|
||||
cache_manager_1.CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
useFactory: async () => ({
|
||||
store: await (0, cache_manager_redis_yet_1.redisStore)({
|
||||
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||
ttl: CACHE_TTL.DEFAULT,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
jwt_1.JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'super-secret',
|
||||
signOptions: { expiresIn: '1d' },
|
||||
@@ -1393,5 +1511,9 @@ AppModule = __decorate([
|
||||
exports: [prisma_service_1.PrismaService]
|
||||
})
|
||||
], AppModule);
|
||||
bootstrap();
|
||||
bootstrap().catch(err => {
|
||||
console.error('💥 Lỗi khởi động Server:');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=main.js.map
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user