251120:1700 Backend T3.4
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity('json_schemas')
|
||||
export class JsonSchema {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column({ name: 'schema_code', unique: true, length: 100 })
|
||||
schemaCode!: string; // เช่น 'RFA_DWG_V1'
|
||||
|
||||
@Column({ default: 1 })
|
||||
version!: number;
|
||||
|
||||
@Column({ name: 'schema_definition', type: 'json' })
|
||||
schemaDefinition!: any; // เก็บ JSON Schema มาตรฐาน (Draft 7/2019-09)
|
||||
|
||||
@Column({ name: 'is_active', default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt!: Date;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { JsonSchemaController } from './json-schema.controller';
|
||||
|
||||
describe('JsonSchemaController', () => {
|
||||
let controller: JsonSchemaController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [JsonSchemaController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<JsonSchemaController>(JsonSchemaController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
25
backend/src/modules/json-schema/json-schema.controller.ts
Normal file
25
backend/src/modules/json-schema/json-schema.controller.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Controller, Post, Body, Param, UseGuards } from '@nestjs/common';
|
||||
import { JsonSchemaService } from './json-schema.service.js';
|
||||
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';
|
||||
|
||||
@Controller('json-schemas')
|
||||
@UseGuards(JwtAuthGuard, RbacGuard)
|
||||
export class JsonSchemaController {
|
||||
constructor(private readonly schemaService: JsonSchemaService) {}
|
||||
|
||||
@Post(':code')
|
||||
@RequirePermission('system.manage_all') // เฉพาะ Superadmin หรือผู้มีสิทธิ์จัดการ System
|
||||
create(@Param('code') code: string, @Body() definition: any) {
|
||||
return this.schemaService.createOrUpdate(code, definition);
|
||||
}
|
||||
|
||||
// Endpoint สำหรับ Test Validate (Optional)
|
||||
@Post(':code/validate')
|
||||
@RequirePermission('document.view')
|
||||
async validate(@Param('code') code: string, @Body() data: any) {
|
||||
const isValid = await this.schemaService.validate(code, data);
|
||||
return { valid: isValid };
|
||||
}
|
||||
}
|
||||
17
backend/src/modules/json-schema/json-schema.module.ts
Normal file
17
backend/src/modules/json-schema/json-schema.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { JsonSchemaService } from './json-schema.service.js';
|
||||
import { JsonSchemaController } from './json-schema.controller.js';
|
||||
import { JsonSchema } from './entities/json-schema.entity.js';
|
||||
import { UserModule } from '../user/user.module.js'; // <--- 1. Import UserModule
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([JsonSchema]),
|
||||
UserModule, // <--- 2. ใส่ UserModule ใน imports
|
||||
],
|
||||
controllers: [JsonSchemaController],
|
||||
providers: [JsonSchemaService],
|
||||
exports: [JsonSchemaService], // Export ให้ Module อื่นเรียกใช้ .validate()
|
||||
})
|
||||
export class JsonSchemaModule {}
|
||||
18
backend/src/modules/json-schema/json-schema.service.spec.ts
Normal file
18
backend/src/modules/json-schema/json-schema.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { JsonSchemaService } from './json-schema.service';
|
||||
|
||||
describe('JsonSchemaService', () => {
|
||||
let service: JsonSchemaService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [JsonSchemaService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<JsonSchemaService>(JsonSchemaService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
101
backend/src/modules/json-schema/json-schema.service.ts
Normal file
101
backend/src/modules/json-schema/json-schema.service.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
Injectable,
|
||||
OnModuleInit,
|
||||
BadRequestException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import Ajv from 'ajv';
|
||||
import addFormats from 'ajv-formats';
|
||||
import { JsonSchema } from './entities/json-schema.entity.js';
|
||||
|
||||
@Injectable()
|
||||
export class JsonSchemaService implements OnModuleInit {
|
||||
private ajv: Ajv;
|
||||
// Cache ตัว Validator ที่ Compile แล้ว เพื่อประสิทธิภาพ
|
||||
private validators = new Map<string, any>();
|
||||
|
||||
constructor(
|
||||
@InjectRepository(JsonSchema)
|
||||
private schemaRepo: Repository<JsonSchema>,
|
||||
) {
|
||||
// ตั้งค่า AJV
|
||||
this.ajv = new Ajv({ allErrors: true, strict: false }); // strict: false เพื่อยืดหยุ่นกับ custom keywords
|
||||
addFormats(this.ajv); // รองรับ format เช่น email, date-time
|
||||
}
|
||||
|
||||
onModuleInit() {
|
||||
// (Optional) โหลด Schema ทั้งหมดมา Cache ตอนเริ่ม App ก็ได้
|
||||
// แต่ตอนนี้ใช้วิธี Lazy Load (โหลดเมื่อใช้) ไปก่อน
|
||||
}
|
||||
|
||||
/**
|
||||
* ตรวจสอบข้อมูล JSON ว่าถูกต้องตาม Schema หรือไม่
|
||||
*/
|
||||
async validate(schemaCode: string, data: any): Promise<boolean> {
|
||||
let validate = this.validators.get(schemaCode);
|
||||
|
||||
// ถ้ายังไม่มีใน Cache หรือต้องการตัวล่าสุด ให้ดึงจาก DB
|
||||
if (!validate) {
|
||||
const schema = await this.schemaRepo.findOne({
|
||||
where: { schemaCode, isActive: true },
|
||||
});
|
||||
|
||||
if (!schema) {
|
||||
throw new NotFoundException(`JSON Schema '${schemaCode}' not found`);
|
||||
}
|
||||
|
||||
try {
|
||||
validate = this.ajv.compile(schema.schemaDefinition);
|
||||
this.validators.set(schemaCode, validate);
|
||||
} catch (error: any) {
|
||||
throw new BadRequestException(
|
||||
`Invalid Schema Definition for '${schemaCode}': ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const valid = validate(data);
|
||||
|
||||
if (!valid) {
|
||||
// รวบรวม Error ทั้งหมดส่งกลับไป
|
||||
const errors = validate.errors
|
||||
?.map((e: any) => `${e.instancePath} ${e.message}`)
|
||||
.join(', ');
|
||||
throw new BadRequestException(`JSON Validation Failed: ${errors}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ฟังก์ชันสำหรับสร้าง/อัปเดต Schema (สำหรับ Admin)
|
||||
async createOrUpdate(schemaCode: string, definition: any) {
|
||||
// ตรวจสอบก่อนว่า Definition เป็น JSON Schema ที่ถูกต้องไหม
|
||||
try {
|
||||
this.ajv.compile(definition);
|
||||
} catch (error: any) {
|
||||
throw new BadRequestException(
|
||||
`Invalid JSON Schema format: ${error.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
let schema = await this.schemaRepo.findOne({ where: { schemaCode } });
|
||||
|
||||
if (schema) {
|
||||
schema.schemaDefinition = definition;
|
||||
schema.version += 1;
|
||||
} else {
|
||||
schema = this.schemaRepo.create({
|
||||
schemaCode,
|
||||
schemaDefinition: definition,
|
||||
version: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Clear Cache เก่า
|
||||
this.validators.delete(schemaCode);
|
||||
|
||||
return this.schemaRepo.save(schema);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user