Sửa lỗi frontend không kết nối với backend

This commit is contained in:
2026-06-15 18:25:37 +07:00
parent 4716b841dc
commit 34e2af8825
32 changed files with 5817 additions and 41 deletions
+7
View File
@@ -0,0 +1,7 @@
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
export declare class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor();
onModuleInit(): Promise<void>;
onModuleDestroy(): Promise<void>;
}
+73
View File
@@ -0,0 +1,73 @@
"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrismaService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const adapter_pg_1 = require("@prisma/adapter-pg");
const pg_1 = require("pg");
const dotenv = __importStar(require("dotenv"));
const path = __importStar(require("path"));
dotenv.config({ path: path.join(__dirname, '..', '.env') });
let PrismaService = class PrismaService extends client_1.PrismaClient {
constructor() {
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
}
console.log('--- [PRISMA CHECK] ---');
console.log('DATABASE_URL nhận được:', process.env.DATABASE_URL ? 'ĐÃ ĐỌC THÀNH CÔNG ✔️' : 'VẪN BỊ UNDEFINED ❌');
console.log('----------------------');
const pool = new pg_1.Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new adapter_pg_1.PrismaPg(pool);
super({ adapter });
}
async onModuleInit() { await this.$connect(); }
async onModuleDestroy() { await this.$disconnect(); }
};
exports.PrismaService = PrismaService;
exports.PrismaService = PrismaService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [])
], PrismaService);
//# sourceMappingURL=prisma.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"prisma.service.js","sourceRoot":"","sources":["../../prisma/prisma.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA2E;AAC3E,2CAA8C;AAC9C,mDAA8C;AAC9C,2BAA0B;AAC1B,+CAAiC;AACjC,2CAA6B;AAE7B,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;AAGrD,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAY;IAC7C;QACE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;YAC9B,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACjH,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,SAAI,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,qBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,YAAY,KAAK,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC/C,KAAK,CAAC,eAAe,KAAK,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;CACtD,CAAA;AAfY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;;GACA,aAAa,CAezB"}
+7
View File
@@ -0,0 +1,7 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
export declare class AdminGuard implements CanActivate {
private prisma;
constructor(prisma: PrismaService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
+40
View File
@@ -0,0 +1,40 @@
"use strict";
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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminGuard = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../../prisma/prisma.service");
let AdminGuard = class AdminGuard {
constructor(prisma) {
this.prisma = prisma;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user || !user.id) {
throw new common_1.ForbiddenException('Yêu cầu xác thực không hợp lệ. Vui lòng đăng nhập.');
}
const dbUser = await this.prisma.user.findUnique({
where: { id: user.id },
select: { isAdmin: true, isBlocked: true },
});
if (!dbUser || !dbUser.isAdmin || dbUser.isBlocked) {
throw new common_1.ForbiddenException('Truy cập bị từ chối. Bạn không có quyền quản trị viên hệ thống.');
}
return true;
}
};
exports.AdminGuard = AdminGuard;
exports.AdminGuard = AdminGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], AdminGuard);
//# sourceMappingURL=admin.guard.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.guard.js","sourceRoot":"","sources":["../../../src/auth/admin.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+F;AAC/F,gEAA4D;AAGrD,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1B,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,2BAAkB,CAAC,oDAAoD,CAAC,CAAC;QACrF,CAAC;QAGD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC/C,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;YACtB,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;SAC3C,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACnD,MAAM,IAAI,2BAAkB,CAAC,iEAAiE,CAAC,CAAC;QAClG,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAzBY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,UAAU,CAyBtB"}
+4
View File
@@ -0,0 +1,4 @@
declare const JwtAuthGuard_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
export declare class JwtAuthGuard extends JwtAuthGuard_base {
}
export {};
+18
View File
@@ -0,0 +1,18 @@
"use strict";
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JwtAuthGuard = void 0;
const common_1 = require("@nestjs/common");
const passport_1 = require("@nestjs/passport");
let JwtAuthGuard = class JwtAuthGuard extends (0, passport_1.AuthGuard)('jwt') {
};
exports.JwtAuthGuard = JwtAuthGuard;
exports.JwtAuthGuard = JwtAuthGuard = __decorate([
(0, common_1.Injectable)()
], JwtAuthGuard);
//# sourceMappingURL=jwt-auth.guard.js.map
+1
View File
@@ -0,0 +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"}
+19
View File
@@ -0,0 +1,19 @@
import { PrismaService } from '../../prisma/prisma.service';
declare const JwtStrategy_base: new (...args: any) => any;
export declare class JwtStrategy extends JwtStrategy_base {
private prisma;
constructor(prisma: PrismaService);
validate(payload: any): Promise<{
id: string;
email: string;
passwordHash: string;
name: string | null;
phone: string | null;
address: string | null;
avatar: string | null;
createdAt: Date;
isAdmin: boolean;
isBlocked: boolean;
}>;
}
export {};
+41
View File
@@ -0,0 +1,41 @@
"use strict";
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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JwtStrategy = void 0;
const common_1 = require("@nestjs/common");
const passport_1 = require("@nestjs/passport");
const passport_jwt_1 = require("passport-jwt");
const prisma_service_1 = require("../../prisma/prisma.service");
let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(passport_jwt_1.Strategy) {
constructor(prisma) {
super({
jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'super-secret',
});
this.prisma = prisma;
}
async validate(payload) {
const user = await this.prisma.user.findUnique({
where: { id: payload.sub },
});
if (!user) {
throw new common_1.UnauthorizedException('Người dùng không tồn tại hoặc phiên làm việc hết hạn');
}
return user;
}
};
exports.JwtStrategy = JwtStrategy;
exports.JwtStrategy = JwtStrategy = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], JwtStrategy);
//# sourceMappingURL=jwt.strategy.js.map
+1
View File
@@ -0,0 +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"}
+7
View File
@@ -0,0 +1,7 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
export declare class TourRoleGuard implements CanActivate {
private prisma;
constructor(prisma: PrismaService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
+58
View File
@@ -0,0 +1,58 @@
"use strict";
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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TourRoleGuard = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../../prisma/prisma.service");
let TourRoleGuard = class TourRoleGuard {
constructor(prisma) {
this.prisma = prisma;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const user = request.user;
const tourId = request.params.id || request.params.tourId;
const path = request.url;
if (!user || !tourId) {
throw new common_1.ForbiddenException("Thông tin xác thực hoặc mã Tour không hợp lệ.");
}
if (path.includes('/join-requests') && request.method === 'POST' && (!request.params.requestId || /\/join-requests\/[^/]+\/(accept|reject)$/.test(path))) {
request.tourParticipation = null;
return true;
}
const participation = await this.prisma.tourParticipant.findUnique({
where: {
tourId_userId: {
tourId: tourId,
userId: user.id,
},
},
});
if (!participation) {
throw new common_1.ForbiddenException("Bạn không phải là thành viên của tour này.");
}
request.tourParticipation = participation;
const role = participation.role;
const isPlanPath = path.includes('/plans');
const isExpensePath = path.includes('/expenses');
if ((role === 'MEMBER_NO_FINANCE' || role === 'VIEWER_ONLY') &&
(isPlanPath || isExpensePath)) {
throw new common_1.ForbiddenException("Bạn không có quyền xem kế hoạch và chi phí của tour này.");
}
return true;
}
};
exports.TourRoleGuard = TourRoleGuard;
exports.TourRoleGuard = TourRoleGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], TourRoleGuard);
//# sourceMappingURL=rbac.middleware.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"rbac.middleware.js","sourceRoot":"","sources":["../../../src/common/rbac.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+F;AAC/F,gEAA4D;AAGrD,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;QAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC;QAEzB,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,IAAI,2BAAkB,CAAC,+CAA+C,CAAC,CAAC;QAChF,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,0CAA0C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACzJ,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,UAAU,CAAC;YACjE,KAAK,EAAE;gBACL,aAAa,EAAE;oBACb,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,IAAI,CAAC,EAAE;iBAChB;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,2BAAkB,CAAC,4CAA4C,CAAC,CAAC;QAC7E,CAAC;QAED,OAAO,CAAC,iBAAiB,GAAG,aAAa,CAAC;QAE1C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAEjD,IACE,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,KAAK,aAAa,CAAC;YACxD,CAAC,UAAU,IAAI,aAAa,CAAC,EAC7B,CAAC;YACD,MAAM,IAAI,2BAAkB,CAAC,0DAA0D,CAAC,CAAC;QAC3F,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA/CY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,aAAa,CA+CzB"}
+1
View File
@@ -0,0 +1 @@
import 'reflect-metadata';
+1023
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -1,8 +1,10 @@
{ {
"$schema": "https://json.schemastore.org/nest-cli", "$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": ".", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true "deleteOutDir": true,
"assets": ["**/*.prisma", "schema.sql"],
"watchAssets": true
} }
} }
+34
View File
@@ -0,0 +1,34 @@
{
"name": "backend",
"version": "0.0.1",
"scripts": {
"start:dev": "nest start --watch",
"db:generate": "prisma generate --schema=prisma/schema.prisma",
"db:migrate": "prisma migrate dev --schema=prisma/schema.prisma",
"db:seed": "node --loader ts-node/esm seed.ts"
},
"devDependencies": {
"@nestjs/cli": "^11.0.23",
"@types/node": "^20.14.10",
"@types/pg": "^8.11.6",
"prisma": "^5.16.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
},
"dependencies": {
"@nestjs/common": "^11.1.27",
"@nestjs/core": "^11.1.27",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.27",
"@prisma/client": "^5.16.2",
"@prisma/adapter-pg": "^5.16.2",
"bcrypt": "^6.0.0",
"dotenv": "^17.4.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pg": "^8.12.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2"
}
}
+10
View File
@@ -2,10 +2,20 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg'; import { Pool } from 'pg';
import * as dotenv from 'dotenv';
import * as path from 'path';
dotenv.config({ path: path.join(__dirname, '..', '.env') });
@Injectable() @Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() { constructor() {
if (!process.env.DATABASE_URL) {
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
}
console.log('--- [PRISMA CHECK] ---');
console.log('DATABASE_URL nhận được:', process.env.DATABASE_URL ? 'ĐÃ ĐỌC THÀNH CÔNG ✔️' : 'VẪN BỊ UNDEFINED ❌');
console.log('----------------------');
const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaPg(pool); const adapter = new PrismaPg(pool);
super({ adapter }); super({ adapter });
+2
View File
@@ -3,10 +3,12 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
} }
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL")
} }
// --- Enums --- // --- Enums ---
+1 -1
View File
@@ -1,5 +1,5 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { PrismaService } from './prisma.service.js'; import { PrismaService } from '../../prisma/prisma.service';
@Injectable() @Injectable()
export class AdminGuard implements CanActivate { export class AdminGuard implements CanActivate {
+1 -1
View File
@@ -1,7 +1,7 @@
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt'; import { ExtractJwt, Strategy } from 'passport-jwt';
import { PrismaService } from './prisma.service.js'; import { PrismaService } from '../../prisma/prisma.service';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtStrategy extends PassportStrategy(Strategy) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { PrismaService } from './prisma.service.js'; import { PrismaService } from '../../prisma/prisma.service';
@Injectable() @Injectable()
export class TourRoleGuard implements CanActivate { export class TourRoleGuard implements CanActivate {
+35 -17
View File
@@ -1,14 +1,40 @@
import * as dotenv from 'dotenv';
import * as path from 'path';
// Sửa đường dẫn: lùi 2 cấp từ backend/src để ra root monorepo
const envPath = path.resolve(process.cwd(), '..', '.env');
dotenv.config({ path: envPath });
import 'reflect-metadata'; import 'reflect-metadata';
import { NestFactory } from '@nestjs/core'; 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 } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service.js'; import { PrismaService } from '../prisma/prisma.service';
import 'dotenv/config';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { AdminGuard } from './auth/admin.guard.js'; import { AdminGuard } from './auth/admin.guard';
import { JwtModule, JwtService } from '@nestjs/jwt'; import { JwtModule, JwtService } from '@nestjs/jwt';
import { JwtAuthGuard } from './auth/jwt-auth.guard.js'; import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { JwtStrategy } from './auth/jwt.strategy.js'; import { JwtStrategy } from './auth/jwt.strategy';
import { TourRoleGuard } from './common/rbac.middleware.js'; import { TourRoleGuard } from './common/rbac.middleware';
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);
}
// Kiểm tra log xem biến môi trường đã nhận đúng chưa
console.log('====================================');
console.log('DATABASE_URL:', process.env.DATABASE_URL);
console.log('====================================');
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
// Bật CORS để cho phép Frontend kết nối API không bị chặn
app.enableCors();
await app.listen(3001);
console.log(`🚀 Server is running on: http://localhost:3001`);
}
@Controller() @Controller()
class AppController { class AppController {
@@ -18,7 +44,7 @@ class AppController {
} }
} }
@Controller('v1/auth') @Controller('auth')
class AuthController { class AuthController {
constructor(private prisma: PrismaService, private jwtService: JwtService) {} constructor(private prisma: PrismaService, private jwtService: JwtService) {}
@@ -75,7 +101,7 @@ class AuthController {
} }
} }
@Controller('v1/tours') @Controller('tours')
class TourController { class TourController {
constructor(private prisma: PrismaService) {} constructor(private prisma: PrismaService) {}
@@ -843,18 +869,10 @@ class UserController {
}), }),
], ],
controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController, LocationController], controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController, LocationController],
providers: [PrismaService, JwtStrategy, TourRoleGuard], providers: [PrismaService, JwtStrategy, TourRoleGuard, JwtAuthGuard, AdminGuard],
exports: [PrismaService] exports: [PrismaService]
}) })
class AppModule {} class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors(); // Cho phép Frontend gọi API
app.setGlobalPrefix('api'); // Tất cả API sẽ bắt đầu bằng /api/...
const port = process.env.PORT || 3001;
await app.listen(port);
console.log(`🚀 Server is running on: http://localhost:${port}`);
}
bootstrap(); bootstrap();
+7 -7
View File
@@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "ESNext", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node", // Đm bo module resolution là 'node'
"declaration": true, "declaration": true,
"removeComments": true, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
@@ -14,13 +14,13 @@
"strictNullChecks": false, "strictNullChecks": false,
"noImplicitAny": false, "noImplicitAny": false,
"strictBindCallApply": false, "strictBindCallApply": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false, "noFallthroughCasesInSwitch": false,
"resolveJsonModule": true, "resolveJsonModule": true, // Cho phép import các file .json
"esModuleInterop": true, "esModuleInterop": true, // Cho phép cú pháp import/export ES Modules vi CommonJS
"baseUrl": ".", "baseUrl": "./",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["src/*"]
} }
}, },
"include": ["src/**/*"] "include": ["src/**/*"]
+1 -2
View File
@@ -22,8 +22,7 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitc
setIsLoading(true); setIsLoading(true);
try { try {
const API_BASE = `http://${window.location.hostname}:3001`; const response = await fetch(`/api/v1/auth/login`, {
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }), body: JSON.stringify({ email, password }),
+1 -1
View File
@@ -5,7 +5,7 @@ import { useTourStore } from '@/store/useTourStore';
import { AddLocationModal } from '@/components/AddLocationModal'; import { AddLocationModal } from '@/components/AddLocationModal';
import { AddMemberModal } from '../components/AddMemberModal'; import { AddMemberModal } from '../components/AddMemberModal';
import { ConfirmModal } from '../components/ConfirmModal'; import { ConfirmModal } from '../components/ConfirmModal';
import { NotificationModal, useNotificationModal } from '@/components/NotificationModal'; import { NotificationModal, useNotificationModal } from '../components/NotificationModal';
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap } from 'react-leaflet';
import _MarkerClusterGroup from 'react-leaflet-cluster'; import _MarkerClusterGroup from 'react-leaflet-cluster';
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup; const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
+4406 -2
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -9,7 +9,9 @@
], ],
"scripts": { "scripts": {
"build": "npm run build --workspace=backend && npm run build --workspace=frontend", "build": "npm run build --workspace=backend && npm run build --workspace=frontend",
"start:dev": "concurrently \"npm run start:dev --workspace=backend\" \"npm run dev --workspace=frontend\"", "start:backend": "npm run start:dev --workspace=backend",
"start:frontend": "npm run start:dev --workspace=frontend",
"start:dev": "concurrently -n \"BACKEND,FRONTEND\" -c \"magenta,cyan\" \"npm run start:dev --workspace=backend\" \"npm run dev --workspace=frontend\"",
"db:migrate": "npm run db:migrate --workspace=backend", "db:migrate": "npm run db:migrate --workspace=backend",
"db:generate": "npm run db:generate --workspace=backend", "db:generate": "npm run db:generate --workspace=backend",
"db:seed": "npm run db:seed --workspace=backend" "db:seed": "npm run db:seed --workspace=backend"
+5 -4
View File
@@ -11,12 +11,13 @@ echo "🚀 Đang quét và cập nhật các đường dẫn import cho cấu tr
# Các Guard/Middleware nay nằm trong auth/ và common/ # Các Guard/Middleware nay nằm trong auth/ và common/
sed -i -E "s|from\s+['\"](\./)(admin\.guard\|jwt-auth\.guard\|jwt\.strategy)['\"]|from './auth/\2'|g" backend/src/main.ts sed -i -E "s|from\s+['\"](\./)(admin\.guard\|jwt-auth\.guard\|jwt\.strategy)['\"]|from './auth/\2'|g" backend/src/main.ts
sed -i -E "s|from\s+['\"](\./)(rbac\.middleware)['\"]|from './common/\2'|g" backend/src/main.ts sed -i -E "s|from\s+['\"](\./)(rbac\.middleware)['\"]|from './common/\2'|g" backend/src/main.ts
# Prisma service nay nằm ở ../prisma/ (so với thư mục src/) # Đảm bảo Prisma service trỏ đúng về thư mục prisma ở root từ backend/src/
sed -i -E "s|from\s+['\"](\./prisma/prisma\.service)['\"]|from '../prisma/prisma.service'|g" backend/src/main.ts sed -i -E "s|from\s+['\"](\.\./)+(prisma/prisma\.service)['\"]|from '../../\2'|g" backend/src/main.ts
# Cập nhật các file trong backend/src/auth/ (Guards & Strategies) # Cập nhật các file trong backend/src/auth/ (Guards & Strategies)
# Nếu chúng import PrismaService từ ./prisma/prisma.service, nay cần lùi 2 cấp: ../../prisma/prisma.service # Từ backend/src/auth/ cần lùi 3 cấp để ra root prisma/
find backend/src/auth -name "*.ts" -exec sed -i -E "s|from\s+['\"](\./prisma/prisma\.service)['\"]|from '../../prisma/prisma.service'|g" {} + find backend/src/auth -name "*.ts" -exec sed -i -E "s|from\s+['\"](\.\./)+(prisma/prisma\.service)['\"]|from '../../../\2'|g" {} +
find backend/src/common -name "*.ts" -exec sed -i -E "s|from\s+['\"](\.\./)+(prisma/prisma\.service)['\"]|from '../../../\2'|g" {} +
# --- 2. CẬP NHẬT FRONTEND (React + Vite) --- # --- 2. CẬP NHẬT FRONTEND (React + Vite) ---