260316:1117 20260316:1100 Refactor UUID
Build and Deploy / deploy (push) Successful in 9m24s

This commit is contained in:
admin
2026-03-16 11:17:15 +07:00
parent b93cd91325
commit c5c3ed9016
92 changed files with 1726 additions and 620 deletions
@@ -0,0 +1,28 @@
import { Column, BeforeInsert } from 'typeorm';
import { v7 as uuidv7 } from 'uuid';
/**
* Abstract base entity providing a UUID public identifier column.
* Uses MariaDB native UUID type (stored as BINARY(16) internally,
* auto-converts to string format — no transformer needed).
*
* App generates UUIDv7 via @BeforeInsert(); DB DEFAULT UUID() is fallback.
*
* @see ADR-019 Hybrid Identifier Strategy
*/
export abstract class UuidBaseEntity {
@Column({
type: 'uuid',
unique: true,
nullable: false,
comment: 'UUID Public Identifier (ADR-019)',
})
uuid!: string;
@BeforeInsert()
generateUuid(): void {
if (!this.uuid) {
this.uuid = uuidv7();
}
}
}
@@ -7,10 +7,13 @@ import {
JoinColumn,
} from 'typeorm';
import { User } from '../../../modules/user/entities/user.entity';
import { UuidBaseEntity } from '../../entities/uuid-base.entity';
import { Exclude } from 'class-transformer';
@Entity('attachments')
export class Attachment {
export class Attachment extends UuidBaseEntity {
@PrimaryGeneratedColumn()
@Exclude()
id!: number;
@Column({ name: 'original_filename', length: 255 })
@@ -9,6 +9,7 @@ import {
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { instanceToPlain } from 'class-transformer';
/** Metadata สำหรับ Paginated Response */
export interface ResponseMeta {
@@ -53,24 +54,31 @@ export class TransformInterceptor<T>
): Observable<ApiResponse<T>> {
return next.handle().pipe(
map((data: T) => {
const response = context.switchToHttp().getResponse<{ statusCode: number }>();
const response = context
.switchToHttp()
.getResponse<{ statusCode: number }>();
// ADR-019: Serialize entities via class-transformer
// This applies @Exclude() decorators to strip internal INT ids from responses
const serialized = instanceToPlain(data) as T;
// Handle Pagination Response (Standardize)
// ถ้า data มี structure { data: [], meta: {} } ให้ unzip ออกมา
if (isPaginatedPayload(data)) {
if (isPaginatedPayload(serialized)) {
return {
statusCode: response.statusCode,
message: data.message ?? 'Success',
data: data.data as unknown as T,
meta: data.meta,
message: serialized.message ?? 'Success',
data: serialized.data as unknown as T,
meta: serialized.meta,
};
}
const dataAsRecord = data as Record<string, unknown>;
const dataAsRecord = serialized as Record<string, unknown>;
return {
statusCode: response.statusCode,
message: (dataAsRecord?.['message'] as string | undefined) ?? 'Success',
data: (dataAsRecord?.['result'] as T | undefined) ?? data,
message:
(dataAsRecord?.['message'] as string | undefined) ?? 'Success',
data: (dataAsRecord?.['result'] as T | undefined) ?? serialized,
};
})
);
@@ -0,0 +1,20 @@
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
import { validate as uuidValidate } from 'uuid';
/**
* Validates that a route parameter is a valid UUID string.
* Accepts any UUID version (v1 from DB DEFAULT, v7 from app generation).
*
* Usage: @Param('uuid', ParseUuidPipe) uuid: string
*
* @see ADR-019 Hybrid Identifier Strategy
*/
@Injectable()
export class ParseUuidPipe implements PipeTransform<string> {
transform(value: string): string {
if (!uuidValidate(value)) {
throw new BadRequestException(`Invalid UUID format: ${value}`);
}
return value.toLowerCase();
}
}