260316:1255 Refactor to NestJS 11
Build and Deploy / deploy (push) Successful in 9m13s

This commit is contained in:
admin
2026-03-16 12:55:36 +07:00
parent c5c3ed9016
commit 25c50792e7
13 changed files with 1078 additions and 65 deletions
-1
View File
@@ -34,7 +34,6 @@
"@nestjs/core": "^11.0.1",
"@nestjs/elasticsearch": "^11.1.0",
"@nestjs/jwt": "^11.0.1",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/platform-socket.io": "^11.1.9",
+3 -8
View File
@@ -27,12 +27,7 @@ import {
ApiResponse,
ApiBody,
} from '@nestjs/swagger';
import { Request } from 'express';
// สร้าง Interface สำหรับ Request ที่มี User
interface RequestWithUser extends Request {
user: any;
}
import type { RequestWithUser, RequestWithRefreshUser } from '../interfaces/request-with-user.interface';
@ApiTags('Authentication')
@Controller('auth')
@@ -95,7 +90,7 @@ export class AuthController {
},
},
})
async refresh(@Req() req: RequestWithUser) {
async refresh(@Req() req: RequestWithRefreshUser) {
return this.authService.refreshToken(req.user.sub, req.user.refreshToken);
}
@@ -121,7 +116,7 @@ export class AuthController {
}
// ส่ง refresh token ไปด้วยถ้ามี (ใน header หรือ body)
// สำหรับตอนนี้ส่งแค่ access token ไป blacklist
return this.authService.logout(req.user.sub, token);
return this.authService.logout(req.user.user_id, token);
}
@UseGuards(JwtAuthGuard)
@@ -17,6 +17,7 @@ import {
import { AuthService } from './auth.service';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { User } from '../../modules/user/entities/user.entity';
import type { RequestWithUser } from '../interfaces/request-with-user.interface';
@ApiTags('Authentication')
@Controller('auth/sessions')
@@ -28,7 +29,7 @@ export class SessionController {
@Get()
@ApiOperation({ summary: 'List all active sessions (Admin/DC Only)' })
@ApiResponse({ status: 200, description: 'List of active sessions' })
async getActiveSessions(@Req() req: any) {
async getActiveSessions(@Req() req: RequestWithUser) {
this.checkAdminRole(req.user);
return this.authService.getActiveSessions();
}
@@ -36,7 +37,10 @@ export class SessionController {
@Delete(':id')
@ApiOperation({ summary: 'Revoke a session by ID (Admin/DC Only)' })
@ApiResponse({ status: 200, description: 'Session revoked' })
async revokeSession(@Param('id', ParseIntPipe) id: number, @Req() req: any) {
async revokeSession(
@Param('id', ParseIntPipe) id: number,
@Req() req: RequestWithUser
) {
this.checkAdminRole(req.user);
await this.authService.revokeSession(id);
return { message: 'Session revoked successfully' };
@@ -7,11 +7,12 @@ import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import type { JwtPayload } from './jwt.strategy';
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
'jwt-refresh'
) {
constructor(configService: ConfigService) {
super({
@@ -23,7 +24,7 @@ export class JwtRefreshStrategy extends PassportStrategy(
});
}
async validate(req: Request, payload: any) {
async validate(req: Request, payload: JwtPayload) {
const refreshToken = ExtractJwt.fromAuthHeaderAsBearerToken()(req);
return {
...payload,
@@ -20,14 +20,7 @@ import type { Response } from 'express';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileStorageService } from './file-storage.service';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
// Interface เพื่อระบุ Type ของ Request ที่ผ่าน JwtAuthGuard มาแล้ว
interface RequestWithUser {
user: {
userId: number;
username: string;
};
}
import type { RequestWithUser } from '../interfaces/request-with-user.interface';
@Controller('files')
@UseGuards(JwtAuthGuard)
@@ -53,7 +46,7 @@ export class FileStorageController {
@Request() req: RequestWithUser
) {
// ส่ง userId จาก Token ไปด้วย
return this.fileStorageService.upload(file, req.user.userId);
return this.fileStorageService.upload(file, req.user.user_id);
}
/**
@@ -90,7 +83,7 @@ export class FileStorageController {
@Request() req: RequestWithUser
) {
// ส่ง userId ไปด้วยเพื่อตรวจสอบความเป็นเจ้าของ
await this.fileStorageService.delete(id, req.user.userId);
await this.fileStorageService.delete(id, req.user.user_id);
return { message: 'File deleted successfully', id };
}
}
@@ -0,0 +1,30 @@
// File: src/common/interfaces/request-with-user.interface.ts
// NestJS 11: Shared typed Request interfaces (replaces scattered `req: any` patterns)
import { Request } from 'express';
import { User } from '../../modules/user/entities/user.entity';
/**
* Request object after JwtAuthGuard has validated the token.
* Passport attaches the User entity returned by JwtStrategy.validate() to `req.user`.
*/
export interface RequestWithUser extends Request {
user: User;
}
/**
* Payload shape returned by JwtRefreshStrategy.validate().
* Contains JWT claims + the raw refresh token for rotation.
*/
export interface JwtRefreshPayload {
sub: number;
username: string;
refreshToken: string;
}
/**
* Request object after JwtRefreshGuard has validated the refresh token.
*/
export interface RequestWithRefreshUser extends Request {
user: JwtRefreshPayload;
}
+1 -1
View File
@@ -73,7 +73,7 @@ async function bootstrap() {
const swaggerConfig = new DocumentBuilder()
.setTitle('LCBP3 DMS API')
.setDescription('Document Management System API Documentation')
.setVersion('1.4.3')
.setVersion('1.8.1')
.addBearerAuth() // เพิ่มปุ่มใส่ Token (รูปกุญแจ)
.build();
@@ -31,6 +31,7 @@ import { RbacGuard } from '../../common/guards/rbac.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { Audit } from '../../common/decorators/audit.decorator';
import { ParseUuidPipe } from '../../common/pipes/parse-uuid.pipe';
import type { RequestWithUser } from '../../common/interfaces/request-with-user.interface';
@ApiTags('Correspondences')
@Controller('correspondences')
@@ -48,13 +49,7 @@ export class CorrespondenceController {
@RequirePermission('workflow.action_review')
processAction(
@Body() actionDto: WorkflowActionDto,
@Request()
req: Request & {
user: {
user_id: number;
assignments?: Array<{ role: { roleName: string } }>;
};
}
@Request() req: RequestWithUser
) {
// Extract roles from user assignments for DSL requirements check
const userRoles =
@@ -87,12 +82,9 @@ export class CorrespondenceController {
@Audit('correspondence.create', 'correspondence')
create(
@Body() createDto: CreateCorrespondenceDto,
@Request() req: Request & { user: unknown }
@Request() req: RequestWithUser
) {
return this.correspondenceService.create(
createDto,
req.user as Parameters<typeof this.correspondenceService.create>[1]
);
return this.correspondenceService.create(createDto, req.user);
}
@Post('preview-number')
@@ -104,11 +96,11 @@ export class CorrespondenceController {
@RequirePermission('correspondence.create')
previewNumber(
@Body() createDto: CreateCorrespondenceDto,
@Request() req: Request & { user: unknown }
@Request() req: RequestWithUser
) {
return this.correspondenceService.previewDocumentNumber(
createDto,
req.user as Parameters<typeof this.correspondenceService.create>[1]
req.user
);
}
@@ -131,13 +123,7 @@ export class CorrespondenceController {
async submit(
@Param('uuid', ParseUuidPipe) uuid: string,
@Body() submitDto: SubmitCorrespondenceDto,
@Request()
req: Request & {
user: {
user_id: number;
assignments?: Array<{ role: { roleName: string } }>;
};
}
@Request() req: RequestWithUser
) {
const corr = await this.correspondenceService.findOneByUuid(uuid);
// Extract roles from user assignments
@@ -172,14 +158,10 @@ export class CorrespondenceController {
async update(
@Param('uuid', ParseUuidPipe) uuid: string,
@Body() updateDto: UpdateCorrespondenceDto,
@Request() req: Request & { user: unknown }
@Request() req: RequestWithUser
) {
const corr = await this.correspondenceService.findOneByUuid(uuid);
return this.correspondenceService.update(
corr.id,
updateDto,
req.user as Parameters<typeof this.correspondenceService.create>[1]
);
return this.correspondenceService.update(corr.id, updateDto, req.user);
}
@Get(':uuid/references')
@@ -31,6 +31,7 @@ import { WorkflowTransitionDto } from './dto/workflow-transition.dto';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RbacGuard } from '../../common/guards/rbac.guard';
import type { RequestWithUser } from '../../common/interfaces/request-with-user.interface';
@ApiTags('Workflow Engine')
@ApiBearerAuth() // ระบุว่าต้องใช้ Token ใน Swagger
@@ -95,10 +96,10 @@ export class WorkflowEngineController {
async processTransition(
@Param('id') instanceId: string,
@Body() dto: WorkflowTransitionDto,
@Request() req: any
@Request() req: RequestWithUser
) {
// ดึง User ID จาก Token (req.user มาจาก JwtStrategy)
const userId = req.user?.userId;
const userId = req.user?.user_id;
return this.workflowService.processTransition(
instanceId,