251120:2340 Setting Project แก้ Correspondence
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
alwaysApply: true
|
|
||||||
---
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
description: Documents Management Sytem Version 1.1.0
|
|
||||||
globs:
|
|
||||||
- docs/LCBP3-DMS_V1_1_0_*.md
|
|
||||||
alwaysApply: true
|
|
||||||
---
|
|
||||||
@@ -375,7 +375,7 @@ JWT_EXPIRATION=8h
|
|||||||
|
|
||||||
คุณต้องการให้ผมช่วยตั้งค่า `ConfigModule` ใน NestJS เพื่ออ่านค่า `JWT_SECRET` นี้อย่างปลอดภัยพร้อม Validate ว่าห้ามเป็นค่าว่างไหมครับ?
|
คุณต้องการให้ผมช่วยตั้งค่า `ConfigModule` ใน NestJS เพื่ออ่านค่า `JWT_SECRET` นี้อย่างปลอดภัยพร้อม Validate ว่าห้ามเป็นค่าว่างไหมครับ?
|
||||||
|
|
||||||
## ได้เลยครับ เรามาลุยงาน **T0.1 Secure Configuration Setup** ตามแผน Backend Plan กันต่อครับ
|
## **T0.1 Secure Configuration Setup** ตามแผน Backend Plan กันต่อครับ
|
||||||
|
|
||||||
เป้าหมายของ Task นี้คือการทำให้ระบบอ่านค่า Environment Variables ได้อย่างถูกต้อง และ **"ต้องไม่ Start"** ถ้าค่า Config สำคัญๆ (เช่น Database Password, JWT Secret) หายไป หรือไม่ถูกต้อง เพื่อความปลอดภัยครับ
|
เป้าหมายของ Task นี้คือการทำให้ระบบอ่านค่า Environment Variables ได้อย่างถูกต้อง และ **"ต้องไม่ Start"** ถ้าค่า Config สำคัญๆ (เช่น Database Password, JWT Secret) หายไป หรือไม่ถูกต้อง เพื่อความปลอดภัยครับ
|
||||||
|
|
||||||
@@ -444,7 +444,7 @@ import { envValidationSchema } from './common/config/env.validation.js'; // ส
|
|||||||
validationSchema: envValidationSchema, // ใช้ Schema ที่เราสร้างเพื่อตรวจสอบ
|
validationSchema: envValidationSchema, // ใช้ Schema ที่เราสร้างเพื่อตรวจสอบ
|
||||||
validationOptions: {
|
validationOptions: {
|
||||||
// ถ้ามีค่าไหนไม่ผ่าน Validation ให้ Error และหยุดทำงานทันที
|
// ถ้ามีค่าไหนไม่ผ่าน Validation ให้ Error และหยุดทำงานทันที
|
||||||
abortEarly: true,
|
abortEarly: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -471,7 +471,7 @@ import { envValidationSchema } from './common/config/env.validation.js'; // ส
|
|||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4\. สร้างไฟล์ Template สำหรับ Docker Override (ตามแผน T0.1)
|
### 4. สร้างไฟล์ Template สำหรับ Docker Override (ตามแผน T0.1)
|
||||||
|
|
||||||
ตามแผนงาน เราต้องเตรียมไฟล์ `docker-compose.override.yml.example` เพื่อให้นักพัฒนาคนอื่นรู้ว่าต้อง Inject Secrets อย่างไร โดยไม่เปิดเผยความลับใน `docker-compose.yml` หลัก
|
ตามแผนงาน เราต้องเตรียมไฟล์ `docker-compose.override.yml.example` เพื่อให้นักพัฒนาคนอื่นรู้ว่าต้อง Inject Secrets อย่างไร โดยไม่เปิดเผยความลับใน `docker-compose.yml` หลัก
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ services:
|
|||||||
- MYSQL_PASSWORD=secret_password_here
|
- MYSQL_PASSWORD=secret_password_here
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5\. ทดสอบความปลอดภัย (Validation Check)
|
### 5. ทดสอบความปลอดภัย (Validation Check)
|
||||||
|
|
||||||
ลองทดสอบว่าระบบ Validation ทำงานถูกต้องหรือไม่ โดยการ **ลบ** หรือ **เปลี่ยนชื่อ** ตัวแปรสำคัญในไฟล์ `.env` (เช่น ลบ `JWT_SECRET`) แล้วลองรัน:
|
ลองทดสอบว่าระบบ Validation ทำงานถูกต้องหรือไม่ โดยการ **ลบ** หรือ **เปลี่ยนชื่อ** ตัวแปรสำคัญในไฟล์ `.env` (เช่น ลบ `JWT_SECRET`) แล้วลองรัน:
|
||||||
|
|
||||||
@@ -508,10 +508,230 @@ pnpm start:dev
|
|||||||
App **ต้อง Crash** และแสดง Error Message ชัดเจน เช่น:
|
App **ต้อง Crash** และแสดง Error Message ชัดเจน เช่น:
|
||||||
`Config validation error: "JWT_SECRET" is required`
|
`Config validation error: "JWT_SECRET" is required`
|
||||||
|
|
||||||
ถ้าขึ้นแบบนี้แสดงว่าระบบ **Secure Configuration** ของเราทำงานถูกต้องตามแผน T0.1 แล้วครับ\! 🎉
|
ถ้าขึ้นแบบนี้แสดงว่าระบบ **Secure Configuration** ของเราทำงานถูกต้องตามแผน T0.1 แล้วครับ! 🎉
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
**ขั้นตอนถัดไป:**
|
## **T0.2 Redis & Queue Infrastructure**
|
||||||
เราจะไปต่อที่ **T0.2 Redis & Queue Infrastructure** เพื่อเตรียมระบบ Caching และ Background Job ไหมครับ?
|
|
||||||
|
|
||||||
|
เป้าหมายของ Task นี้คือเตรียม **Redis** สำหรับทำ 2 เรื่องสำคัญ:
|
||||||
|
|
||||||
|
1. **Distributed Locking (Redlock):** ป้องกัน Race Condition เวลาออกเลขที่เอกสาร
|
||||||
|
2. **Message Queue (BullMQ):** สำหรับจัดการงานเบื้องหลัง (Background Jobs) เช่น การรวมอีเมลแจ้งเตือน (Digest Notification)
|
||||||
|
|
||||||
|
มาเริ่มกันเลยครับ
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### 1. เพิ่ม Redis ใน Docker Compose
|
||||||
|
|
||||||
|
แก้ไขไฟล์ `docker-compose.yml` เพื่อเพิ่ม Service Redis ครับ โดยเราจะตั้ง Password เพื่อความปลอดภัย
|
||||||
|
|
||||||
|
**ไฟล์: `docker-compose.yml`**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ... (mariadb & pma เดิม) ...
|
||||||
|
|
||||||
|
# เพิ่ม Redis Service
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: lcbp3-redis-local
|
||||||
|
restart: always
|
||||||
|
# ใช้ Command นี้เพื่อตั้ง Password
|
||||||
|
command: redis-server --requirepass "redis_password_secure"
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- lcbp3-net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
redis_data: # เพิ่ม Volume
|
||||||
|
|
||||||
|
networks:
|
||||||
|
lcbp3-net:
|
||||||
|
driver: bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
จากนั้นรันคำสั่งเพื่อ Start Redis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. อัปเดต Environment Config
|
||||||
|
|
||||||
|
เพิ่มค่า Config ของ Redis ลงในไฟล์ `.env` และไฟล์ Validation ครับ
|
||||||
|
|
||||||
|
**ไฟล์: `.env`**
|
||||||
|
|
||||||
|
```env
|
||||||
|
# ... (ค่าเดิม) ...
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_PASSWORD=redis_password_secure
|
||||||
|
```
|
||||||
|
|
||||||
|
**ไฟล์: `src/common/config/env.validation.ts`**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ... (import Joi) ...
|
||||||
|
|
||||||
|
export const envValidationSchema = Joi.object({
|
||||||
|
// ... (App & Database Config เดิม) ...
|
||||||
|
|
||||||
|
// 4. Redis Configuration (เพิ่มส่วนนี้)
|
||||||
|
REDIS_HOST: Joi.string().required(),
|
||||||
|
REDIS_PORT: Joi.number().default(6379),
|
||||||
|
REDIS_PASSWORD: Joi.string().required(),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. ติดตั้ง Library สำหรับ BullMQ
|
||||||
|
|
||||||
|
เราจะใช้ `@nestjs/bullmq` ซึ่งเป็น Wrapper ของ `bullmq` (Redis Queue ที่ทันสมัยกว่า Bull ธรรมดา)
|
||||||
|
|
||||||
|
รันคำสั่ง:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add @nestjs/bullmq bullmq
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. ตั้งค่า BullModule ใน AppModule
|
||||||
|
|
||||||
|
แก้ไข `src/app.module.ts` เพื่อเชื่อมต่อ Redis เข้ากับระบบ Queue ครับ
|
||||||
|
|
||||||
|
**ไฟล์: `src/app.module.ts`**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { BullModule } from '@nestjs/bullmq'; // Import BullModule
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { envValidationSchema } from './common/config/env.validation.js';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
// 1. Config (เดิม)
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
envFilePath: '.env',
|
||||||
|
validationSchema: envValidationSchema,
|
||||||
|
validationOptions: { abortEarly: true },
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 2. TypeORM (เดิม)
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
type: 'mariadb',
|
||||||
|
host: configService.get<string>('DB_HOST'),
|
||||||
|
port: configService.get<number>('DB_PORT'),
|
||||||
|
username: configService.get<string>('DB_USERNAME'),
|
||||||
|
password: configService.get<string>('DB_PASSWORD'),
|
||||||
|
database: configService.get<string>('DB_DATABASE'),
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: configService.get<string>('NODE_ENV') === 'development',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 3. BullMQ (Redis) Setup [NEW]
|
||||||
|
BullModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
connection: {
|
||||||
|
host: configService.get<string>('REDIS_HOST'),
|
||||||
|
port: configService.get<number>('REDIS_PORT'),
|
||||||
|
password: configService.get<string>('REDIS_PASSWORD'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. ทดสอบการเชื่อมต่อ Redis
|
||||||
|
|
||||||
|
ลอง Start App ดูอีกครั้งครับ:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
ถ้าไม่มี Error ขึ้น และ App Start ได้ปกติ แสดงว่า **T0.2 เสร็จสมบูรณ์** ครับ\! ตอนนี้เรามีทั้ง Database และ Redis พร้อมสำหรับการพัฒนา Business Logic แล้ว
|
||||||
|
|
||||||
|
-----
|
||||||
|
### 6. **แก้ไข Error**
|
||||||
|
ปัญหานี้เกิดจากยังไม่ได้ **Import** `BullModule` เข้ามาในไฟล์ `src/app.module.ts` ครับ (TypeScript เลยไม่รู้จักว่า `BullModule` คืออะไร)
|
||||||
|
|
||||||
|
ให้เพิ่มบรรทัดนี้ที่ส่วนบนสุดของไฟล์ `src/app.module.ts` ครับ:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
|
```
|
||||||
|
|
||||||
|
หรือลองตรวจสอบไฟล์ `src/app.module.ts` ให้ครบถ้วนตามนี้ครับ:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { BullModule } from '@nestjs/bullmq'; // <--- ต้องมีบรรทัดนี้ครับ
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { envValidationSchema } from './common/config/env.validation.js';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
envFilePath: '.env',
|
||||||
|
validationSchema: envValidationSchema,
|
||||||
|
validationOptions: { abortEarly: true },
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
type: 'mariadb',
|
||||||
|
host: configService.get<string>('DB_HOST'),
|
||||||
|
port: configService.get<number>('DB_PORT'),
|
||||||
|
username: configService.get<string>('DB_USERNAME'),
|
||||||
|
password: configService.get<string>('DB_PASSWORD'),
|
||||||
|
database: configService.get<string>('DB_DATABASE'),
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: configService.get<string>('NODE_ENV') === 'development',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
// Queue Configuration
|
||||||
|
BullModule.forRootAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (configService: ConfigService) => ({
|
||||||
|
connection: {
|
||||||
|
host: configService.get<string>('REDIS_HOST'),
|
||||||
|
port: configService.get<number>('REDIS_PORT'),
|
||||||
|
password: configService.get<string>('REDIS_PASSWORD'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
```
|
||||||
2195
Setting Project T1.md
Normal file
2195
Setting Project T1.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,13 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Request,
|
Request,
|
||||||
|
Param, // <--- ✅ 1. เพิ่ม Param
|
||||||
|
ParseIntPipe, // <--- ✅ 2. เพิ่ม ParseIntPipe
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { CorrespondenceService } from './correspondence.service.js';
|
import { CorrespondenceService } from './correspondence.service.js';
|
||||||
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto.js';
|
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto.js';
|
||||||
|
import { SubmitCorrespondenceDto } from './dto/submit-correspondence.dto.js'; // <--- ✅ 3. เพิ่ม Import DTO นี้
|
||||||
|
|
||||||
import { JwtAuthGuard } from '../../common/auth/jwt-auth.guard.js';
|
import { JwtAuthGuard } from '../../common/auth/jwt-auth.guard.js';
|
||||||
import { RbacGuard } from '../../common/auth/rbac.guard.js';
|
import { RbacGuard } from '../../common/auth/rbac.guard.js';
|
||||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
|
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
|
||||||
@@ -28,4 +32,19 @@ export class CorrespondenceController {
|
|||||||
findAll() {
|
findAll() {
|
||||||
return this.correspondenceService.findAll();
|
return this.correspondenceService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ เพิ่ม Endpoint นี้ครับ
|
||||||
|
@Post(':id/submit')
|
||||||
|
@RequirePermission('correspondence.create') // หรือจะสร้าง Permission ใหม่ 'workflow.submit' ก็ได้
|
||||||
|
submit(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() submitDto: SubmitCorrespondenceDto,
|
||||||
|
@Request() req: any,
|
||||||
|
) {
|
||||||
|
return this.correspondenceService.submit(
|
||||||
|
id,
|
||||||
|
submitDto.templateId,
|
||||||
|
req.user,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { IsInt, IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class SubmitCorrespondenceDto {
|
||||||
|
@IsInt()
|
||||||
|
@IsNotEmpty()
|
||||||
|
templateId!: number;
|
||||||
|
}
|
||||||
BIN
extensions_list.txt
Normal file
BIN
extensions_list.txt
Normal file
Binary file not shown.
19
test.sql
Normal file
19
test.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- 1. สร้าง Template ชื่อ "General Approval"
|
||||||
|
INSERT INTO correspondence_routing_templates (id, template_name, description, is_active)
|
||||||
|
VALUES (
|
||||||
|
1,
|
||||||
|
'General Approval',
|
||||||
|
'Template สำหรับการอนุมัติทั่วไป',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
-- 2. สร้าง Steps (ส่งไป Org ID 1 ก่อน แล้วส่งไป Org ID 2)
|
||||||
|
-- (สมมติว่า Org ID 1 = Owner, Org ID 2 = Consultant ตาม Seed Data เดิม)
|
||||||
|
INSERT INTO correspondence_routing_template_steps (
|
||||||
|
template_id,
|
||||||
|
sequence,
|
||||||
|
to_organization_id,
|
||||||
|
step_purpose,
|
||||||
|
expected_days
|
||||||
|
)
|
||||||
|
VALUES (1, 1, 22, 'FOR_REVIEW', 3),
|
||||||
|
(1, 2, 1, 'FOR_APPROVAL', 5);
|
||||||
Reference in New Issue
Block a user