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 ว่าห้ามเป็นค่าว่างไหมครับ?
|
||||
|
||||
## ได้เลยครับ เรามาลุยงาน **T0.1 Secure Configuration Setup** ตามแผน Backend Plan กันต่อครับ
|
||||
## **T0.1 Secure Configuration Setup** ตามแผน Backend Plan กันต่อครับ
|
||||
|
||||
เป้าหมายของ Task นี้คือการทำให้ระบบอ่านค่า Environment Variables ได้อย่างถูกต้อง และ **"ต้องไม่ Start"** ถ้าค่า Config สำคัญๆ (เช่น Database Password, JWT Secret) หายไป หรือไม่ถูกต้อง เพื่อความปลอดภัยครับ
|
||||
|
||||
@@ -471,7 +471,7 @@ import { envValidationSchema } from './common/config/env.validation.js'; // ส
|
||||
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` หลัก
|
||||
|
||||
@@ -496,7 +496,7 @@ services:
|
||||
- MYSQL_PASSWORD=secret_password_here
|
||||
```
|
||||
|
||||
### 5\. ทดสอบความปลอดภัย (Validation Check)
|
||||
### 5. ทดสอบความปลอดภัย (Validation Check)
|
||||
|
||||
ลองทดสอบว่าระบบ Validation ทำงานถูกต้องหรือไม่ โดยการ **ลบ** หรือ **เปลี่ยนชื่อ** ตัวแปรสำคัญในไฟล์ `.env` (เช่น ลบ `JWT_SECRET`) แล้วลองรัน:
|
||||
|
||||
@@ -508,10 +508,230 @@ pnpm start:dev
|
||||
App **ต้อง Crash** และแสดง Error Message ชัดเจน เช่น:
|
||||
`Config validation error: "JWT_SECRET" is required`
|
||||
|
||||
ถ้าขึ้นแบบนี้แสดงว่าระบบ **Secure Configuration** ของเราทำงานถูกต้องตามแผน T0.1 แล้วครับ\! 🎉
|
||||
ถ้าขึ้นแบบนี้แสดงว่าระบบ **Secure Configuration** ของเราทำงานถูกต้องตามแผน T0.1 แล้วครับ! 🎉
|
||||
|
||||
-----
|
||||
|
||||
**ขั้นตอนถัดไป:**
|
||||
เราจะไปต่อที่ **T0.2 Redis & Queue Infrastructure** เพื่อเตรียมระบบ Caching และ Background Job ไหมครับ?
|
||||
## **T0.2 Redis & Queue Infrastructure**
|
||||
|
||||
เป้าหมายของ 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,
|
||||
UseGuards,
|
||||
Request,
|
||||
Param, // <--- ✅ 1. เพิ่ม Param
|
||||
ParseIntPipe, // <--- ✅ 2. เพิ่ม ParseIntPipe
|
||||
} from '@nestjs/common';
|
||||
import { CorrespondenceService } from './correspondence.service.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 { RbacGuard } from '../../common/auth/rbac.guard.js';
|
||||
import { RequirePermission } from '../../common/decorators/require-permission.decorator.js';
|
||||
@@ -28,4 +32,19 @@ export class CorrespondenceController {
|
||||
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