690328:1106 Fixing Refactor uuid by Kimi #01
This commit is contained in:
@@ -89,7 +89,7 @@ Reference these guidelines when:
|
||||
- `db-hybrid-identifier` - **CRITICAL** ADR-019: INT PK + UUID public API
|
||||
- `db-avoid-n-plus-one` - HIGH N+1 query prevention
|
||||
- `db-use-transactions` - HIGH Transaction management
|
||||
- `db-use-migrations` - N/A **ADR-009**: No TypeORM migrations - use SQL files
|
||||
- `db-no-typeorm-migrations` - **CRITICAL** ADR-009: No TypeORM migrations - use SQL files
|
||||
|
||||
### 8. API Design (MEDIUM)
|
||||
|
||||
@@ -110,7 +110,86 @@ Reference these guidelines when:
|
||||
- `devops-use-logging` - Structured logging
|
||||
- `devops-graceful-shutdown` - Zero-downtime deployments
|
||||
|
||||
## How to Use
|
||||
## NAP-DMS Project-Specific Rules (MUST FOLLOW)
|
||||
|
||||
These rules override general NestJS best practices for the NAP-DMS project:
|
||||
|
||||
### ADR-009: No TypeORM Migrations
|
||||
|
||||
- **ห้ามสร้างไฟล์ migration ของ TypeORM**
|
||||
- แก้ไข schema โดยตรงที่: `specs/03-Data-and-Storage/lcbp3-v1.8.0-schema-02-tables.sql`
|
||||
- ใช้ n8n workflow สำหรับ data migration ถ้าจำเป็น
|
||||
|
||||
### ADR-019: Hybrid Identifier Strategy (CRITICAL)
|
||||
|
||||
```typescript
|
||||
@Entity()
|
||||
export class Project {
|
||||
@PrimaryGeneratedColumn()
|
||||
@Exclude() // ห้ามส่งออกทาง API
|
||||
id: number; // INT AUTO_INCREMENT - internal only
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
@Expose({ name: 'id' }) // ส่งออกเป็น 'id' ทาง API
|
||||
publicId: string; // UUIDv7 - public API identifier
|
||||
}
|
||||
```
|
||||
|
||||
### Two-Phase File Upload
|
||||
|
||||
```typescript
|
||||
// Phase 1: Upload to temp
|
||||
@Post('upload')
|
||||
async uploadFile(@UploadedFile() file: Express.Multer.File) {
|
||||
await this.virusScan(file);
|
||||
const tempId = await this.fileStorage.saveToTemp(file);
|
||||
return { temp_id: tempId, expires_at: addHours(new Date(), 24) };
|
||||
}
|
||||
|
||||
// Phase 2: Commit in transaction
|
||||
async createEntity(dto: CreateDto, tempIds: string[]) {
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const entity = await manager.save(Entity, dto);
|
||||
await this.fileStorage.commitFiles(tempIds, entity.id, manager);
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Idempotency Requirement
|
||||
|
||||
- ทุก POST/PUT/PATCH ที่สำคัญต้องมี `Idempotency-Key` header
|
||||
- ใช้ `IdempotencyInterceptor` ที่มีอยู่แล้ว
|
||||
|
||||
### Document Numbering (Double-Lock)
|
||||
|
||||
```typescript
|
||||
async generateNextNumber(context: NumberingContext): Promise<string> {
|
||||
const lockKey = `doc_num:${context.projectId}:${context.typeId}`;
|
||||
const lock = await this.redisLock.acquire(lockKey, 3000);
|
||||
|
||||
try {
|
||||
const counter = await this.counterRepo.findOne({
|
||||
where: context,
|
||||
lock: { mode: 'optimistic' },
|
||||
});
|
||||
counter.last_number++;
|
||||
return this.formatNumber(await this.counterRepo.save(counter));
|
||||
} finally {
|
||||
await lock.release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Anti-Patterns (ห้ามทำ)
|
||||
|
||||
- ❌ ใช้ SQL Triggers สำหรับ business logic
|
||||
- ❌ ใช้ `.env` ใน production (ใช้ Docker ENV)
|
||||
- ❌ ใช้ `any` type (strict mode enforced)
|
||||
- ❌ ใช้ `console.log` (ใช้ NestJS Logger)
|
||||
- ❌ สร้างตาราง routing แยก (ใช้ Workflow Engine)
|
||||
|
||||
---
|
||||
|
||||
Read individual rule files for detailed explanations and code examples:
|
||||
|
||||
|
||||
@@ -165,7 +165,162 @@ See [self-hosting.md](./self-hosting.md) for:
|
||||
- Cache handlers for multi-instance ISR
|
||||
- What works vs needs extra setup
|
||||
|
||||
## Debug Tricks
|
||||
## NAP-DMS Project-Specific Rules (MUST FOLLOW)
|
||||
|
||||
These rules are mandatory for the NAP-DMS LCBP3 frontend project:
|
||||
|
||||
### State Management (บังคับใช้)
|
||||
|
||||
**Server State - TanStack Query (React Query)**
|
||||
|
||||
```tsx
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
// ❌ ห้ามใช้ useEffect โดยตรง
|
||||
// ✅ ใช้ TanStack Query
|
||||
export function useCorrespondences(projectId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['correspondences', projectId],
|
||||
queryFn: () => correspondenceService.getAll(projectId),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Form State - React Hook Form + Zod**
|
||||
|
||||
```tsx
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
title: z.string().min(1, 'กรุณาระบุหัวเรื่อง'),
|
||||
projectUuid: z.string().uuid('กรุณาเลือกโปรเจกต์'),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
```
|
||||
|
||||
### ADR-019 UUID Handling (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// Interface ต้องมีทั้ง id และ publicId
|
||||
interface Contract {
|
||||
id?: number; // Internal (อาจ undefined)
|
||||
publicId?: string; // UUID - ใช้ตัวนี้
|
||||
contractCode: string;
|
||||
}
|
||||
|
||||
// Select options - ใช้ pattern นี้เสมอ
|
||||
const options = contracts.map((c) => ({
|
||||
label: `${c.contractName} (${c.contractCode})`,
|
||||
value: String(c.publicId ?? c.id ?? ''), // fallback pattern
|
||||
key: String(c.publicId ?? c.id ?? ''),
|
||||
}));
|
||||
|
||||
// ❌ ห้ามใช้ parseInt บน UUID
|
||||
// const id = parseInt(projectId); // WRONG!
|
||||
|
||||
// ✅ ส่ง UUID string ตรงๆ
|
||||
apiClient.get(`/projects/${projectId}`); // projectId is UUID string
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
**Code Identifiers - ภาษาอังกฤษ**
|
||||
|
||||
```tsx
|
||||
// ✅ Correct
|
||||
interface Correspondence {
|
||||
documentNumber: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// ❌ Wrong
|
||||
interface เอกสาร {
|
||||
เลขที่: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Comments - ภาษาไทย**
|
||||
|
||||
```tsx
|
||||
// ✅ Correct - อธิบาย logic เป็นภาษาไทย
|
||||
// ตรวจสอบว่ามีการระบุ projectUuid หรือไม่
|
||||
if (!data.projectUuid) {
|
||||
throw new Error('กรุณาเลือกโปรเจกต์');
|
||||
}
|
||||
|
||||
// ❌ Wrong - ห้ามใช้ภาษาอังกฤษใน comments
|
||||
// Check if projectUuid is provided
|
||||
```
|
||||
|
||||
### UI Components
|
||||
|
||||
**บังคับใช้ shadcn/ui**
|
||||
|
||||
```tsx
|
||||
// ✅ Correct
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
|
||||
// ❌ Wrong - ไม่สร้าง component เองถ้ามีใน shadcn
|
||||
const MyButton = () => <button className="...">Click</button>;
|
||||
```
|
||||
|
||||
### File Upload Pattern
|
||||
|
||||
```tsx
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
|
||||
// Two-phase upload
|
||||
const onDrop = useCallback(async (files: File[]) => {
|
||||
// Phase 1: Upload to temp
|
||||
const tempFiles = await Promise.all(files.map((file) => uploadService.uploadTemp(file)));
|
||||
setTempIds(tempFiles.map((f) => f.tempId));
|
||||
}, []);
|
||||
|
||||
// Phase 2: Commit on form submit
|
||||
const onSubmit = async (data: FormData) => {
|
||||
await correspondenceService.create({
|
||||
...data,
|
||||
tempFileIds,
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### API Client Setup
|
||||
|
||||
```typescript
|
||||
// lib/api/client.ts
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Auto-add Idempotency-Key
|
||||
apiClient.interceptors.request.use((config) => {
|
||||
if (['post', 'put', 'patch'].includes(config.method?.toLowerCase() || '')) {
|
||||
config.headers['Idempotency-Key'] = uuidv4();
|
||||
}
|
||||
return config;
|
||||
});
|
||||
```
|
||||
|
||||
### Anti-Patterns (ห้ามทำ)
|
||||
|
||||
- ❌ Fetch data ใน useEffect โดยตรง
|
||||
- ❌ Props drilling ลึกเกิน 3 levels
|
||||
- ❌ Inline styles (ใช้ Tailwind)
|
||||
- ❌ console.log ใน production
|
||||
- ❌ parseInt() บน UUID values
|
||||
- ❌ ใช้ index เป็น key ใน list
|
||||
- ❌ Snake_case ใน form field names (ใช้ camelCase)
|
||||
|
||||
---
|
||||
|
||||
See [debug-tricks.md](./debug-tricks.md) for:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user