feat: Phase 3.8 DbTypeCategoryService Raw Query 전환 완료
10개 Prisma 호출을 모두 Raw Query로 전환 - 카테고리 목록 조회 (getAllCategories) - 카테고리 단건 조회 (getCategoryByTypeCode) - 카테고리 생성 (createCategory - 중복 검사) - 카테고리 수정 (updateCategory - 동적 UPDATE) - 카테고리 삭제 (deleteCategory - 연결 확인 후 비활성화) - 연결 통계 조회 (getConnectionStatsByType - LEFT JOIN + GROUP BY) - 기본 카테고리 초기화 (initializeDefaultCategories - UPSERT) 주요 기술적 해결: - ApiResponse 래퍼 패턴 유지 - 동적 UPDATE 쿼리 (5개 필드 조건부 업데이트) - ON CONFLICT를 사용한 UPSERT (기본 카테고리 초기화) - 연결 확인 (external_db_connections COUNT) - LEFT JOIN + GROUP BY 통계 쿼리 최적화 (타입별 연결 수) - 중복 검사 (카테고리 생성 시) - try-catch 에러 처리 및 ApiResponse 반환 TypeScript 컴파일 성공 Prisma import 완전 제거 Phase 3 진행률: 107/162 (66.0%) 전체 진행률: 358/444 (80.6%)
This commit is contained in:
parent
c973cb674d
commit
a8c4f9ec45
|
|
@ -129,8 +129,8 @@ backend-node/ (루트)
|
|||
- `dataflowDiagramService.ts` (0개) - ✅ **전환 완료** (Phase 3.5)
|
||||
- `collectionService.ts` (0개) - ✅ **전환 완료** (Phase 3.6)
|
||||
- `layoutService.ts` (0개) - ✅ **전환 완료** (Phase 3.7)
|
||||
- `dbTypeCategoryService.ts` (10개) - DB 타입 분류 ⭐ 신규 발견
|
||||
- `templateStandardService.ts` (9개) - 템플릿 표준
|
||||
- `dbTypeCategoryService.ts` (0개) - ✅ **전환 완료** (Phase 3.8)
|
||||
- `templateStandardService.ts` (6개) - 템플릿 표준
|
||||
- `eventTriggerService.ts` (6개) - JSON 검색 쿼리
|
||||
|
||||
#### 🟡 **중간 (단순 CRUD) - 3순위**
|
||||
|
|
@ -1194,6 +1194,16 @@ describe("Performance Benchmarks", () => {
|
|||
- [x] Promise.all 병렬 쿼리 (목록 + 개수)
|
||||
- [x] TypeScript 컴파일 성공
|
||||
- [x] Prisma import 완전 제거
|
||||
- [x] **DbTypeCategoryService 전환 (10개)** ✅ **완료** (Phase 3.8)
|
||||
- [x] 10개 Prisma 호출 전환 완료 (DB 타입 카테고리 CRUD, 통계)
|
||||
- [x] ApiResponse 래퍼 패턴 유지
|
||||
- [x] 동적 UPDATE 쿼리 (5개 필드 조건부 업데이트)
|
||||
- [x] ON CONFLICT를 사용한 UPSERT (기본 카테고리 초기화)
|
||||
- [x] 연결 확인 (external_db_connections COUNT)
|
||||
- [x] LEFT JOIN + GROUP BY 통계 쿼리 (타입별 연결 수)
|
||||
- [x] 중복 검사 (카테고리 생성 시)
|
||||
- [x] TypeScript 컴파일 성공
|
||||
- [x] Prisma import 완전 제거
|
||||
- [ ] 배치 관련 서비스 전환 (26개) ⭐ 대규모 신규 발견
|
||||
- [ ] BatchExternalDbService (8개)
|
||||
- [ ] BatchExecutionLogService (7개), BatchManagementService (5개)
|
||||
|
|
@ -1202,8 +1212,7 @@ describe("Performance Benchmarks", () => {
|
|||
- [ ] TemplateStandardService (6개) - [계획서](PHASE3.9_TEMPLATE_STANDARD_SERVICE_MIGRATION.md)
|
||||
- [ ] 데이터플로우 관련 서비스 (6개) ⭐ 신규 발견
|
||||
- [ ] DataflowControlService (6개)
|
||||
- [ ] 기타 중요 서비스 (18개) ⭐ 신규 발견
|
||||
- [ ] DbTypeCategoryService (10개) - [계획서](PHASE3.8_DB_TYPE_CATEGORY_SERVICE_MIGRATION.md)
|
||||
- [ ] 기타 중요 서비스 (8개) ⭐ 신규 발견
|
||||
- [ ] DDLAuditLogger (8개)
|
||||
- [ ] 기능별 테스트 완료
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { query, queryOne } from "../database/db";
|
||||
|
||||
export interface DbTypeCategory {
|
||||
type_code: string;
|
||||
|
|
@ -42,13 +40,12 @@ export class DbTypeCategoryService {
|
|||
*/
|
||||
static async getAllCategories(): Promise<ApiResponse<DbTypeCategory[]>> {
|
||||
try {
|
||||
const categories = await prisma.db_type_categories.findMany({
|
||||
where: { is_active: true },
|
||||
orderBy: [
|
||||
{ sort_order: 'asc' },
|
||||
{ display_name: 'asc' }
|
||||
]
|
||||
});
|
||||
const categories = await query<DbTypeCategory>(
|
||||
`SELECT * FROM db_type_categories
|
||||
WHERE is_active = $1
|
||||
ORDER BY sort_order ASC, display_name ASC`,
|
||||
[true]
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
@ -70,9 +67,10 @@ export class DbTypeCategoryService {
|
|||
*/
|
||||
static async getCategoryByTypeCode(typeCode: string): Promise<ApiResponse<DbTypeCategory>> {
|
||||
try {
|
||||
const category = await prisma.db_type_categories.findUnique({
|
||||
where: { type_code: typeCode }
|
||||
});
|
||||
const category = await queryOne<DbTypeCategory>(
|
||||
`SELECT * FROM db_type_categories WHERE type_code = $1`,
|
||||
[typeCode]
|
||||
);
|
||||
|
||||
if (!category) {
|
||||
return {
|
||||
|
|
@ -102,9 +100,10 @@ export class DbTypeCategoryService {
|
|||
static async createCategory(data: CreateDbTypeCategoryRequest): Promise<ApiResponse<DbTypeCategory>> {
|
||||
try {
|
||||
// 중복 체크
|
||||
const existing = await prisma.db_type_categories.findUnique({
|
||||
where: { type_code: data.type_code }
|
||||
});
|
||||
const existing = await queryOne<DbTypeCategory>(
|
||||
`SELECT * FROM db_type_categories WHERE type_code = $1`,
|
||||
[data.type_code]
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
return {
|
||||
|
|
@ -113,15 +112,20 @@ export class DbTypeCategoryService {
|
|||
};
|
||||
}
|
||||
|
||||
const category = await prisma.db_type_categories.create({
|
||||
data: {
|
||||
type_code: data.type_code,
|
||||
display_name: data.display_name,
|
||||
icon: data.icon,
|
||||
color: data.color,
|
||||
sort_order: data.sort_order || 0
|
||||
}
|
||||
});
|
||||
const category = await queryOne<DbTypeCategory>(
|
||||
`INSERT INTO db_type_categories
|
||||
(type_code, display_name, icon, color, sort_order, is_active, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[
|
||||
data.type_code,
|
||||
data.display_name,
|
||||
data.icon || null,
|
||||
data.color || null,
|
||||
data.sort_order || 0,
|
||||
true,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
@ -143,17 +147,39 @@ export class DbTypeCategoryService {
|
|||
*/
|
||||
static async updateCategory(typeCode: string, data: UpdateDbTypeCategoryRequest): Promise<ApiResponse<DbTypeCategory>> {
|
||||
try {
|
||||
const category = await prisma.db_type_categories.update({
|
||||
where: { type_code: typeCode },
|
||||
data: {
|
||||
display_name: data.display_name,
|
||||
icon: data.icon,
|
||||
color: data.color,
|
||||
sort_order: data.sort_order,
|
||||
is_active: data.is_active,
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
// 동적 UPDATE 쿼리 생성
|
||||
const updateFields: string[] = ["updated_at = NOW()"];
|
||||
const values: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (data.display_name !== undefined) {
|
||||
updateFields.push(`display_name = $${paramIndex++}`);
|
||||
values.push(data.display_name);
|
||||
}
|
||||
if (data.icon !== undefined) {
|
||||
updateFields.push(`icon = $${paramIndex++}`);
|
||||
values.push(data.icon);
|
||||
}
|
||||
if (data.color !== undefined) {
|
||||
updateFields.push(`color = $${paramIndex++}`);
|
||||
values.push(data.color);
|
||||
}
|
||||
if (data.sort_order !== undefined) {
|
||||
updateFields.push(`sort_order = $${paramIndex++}`);
|
||||
values.push(data.sort_order);
|
||||
}
|
||||
if (data.is_active !== undefined) {
|
||||
updateFields.push(`is_active = $${paramIndex++}`);
|
||||
values.push(data.is_active);
|
||||
}
|
||||
|
||||
const category = await queryOne<DbTypeCategory>(
|
||||
`UPDATE db_type_categories
|
||||
SET ${updateFields.join(", ")}
|
||||
WHERE type_code = $${paramIndex}
|
||||
RETURNING *`,
|
||||
[...values, typeCode]
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
@ -176,12 +202,12 @@ export class DbTypeCategoryService {
|
|||
static async deleteCategory(typeCode: string): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
// 해당 타입을 사용하는 연결이 있는지 확인
|
||||
const connectionsCount = await prisma.external_db_connections.count({
|
||||
where: {
|
||||
db_type: typeCode,
|
||||
is_active: "Y"
|
||||
}
|
||||
});
|
||||
const countResult = await queryOne<{ count: string }>(
|
||||
`SELECT COUNT(*) as count FROM external_db_connections
|
||||
WHERE db_type = $1 AND is_active = $2`,
|
||||
[typeCode, "Y"]
|
||||
);
|
||||
const connectionsCount = parseInt(countResult?.count || "0");
|
||||
|
||||
if (connectionsCount > 0) {
|
||||
return {
|
||||
|
|
@ -190,13 +216,12 @@ export class DbTypeCategoryService {
|
|||
};
|
||||
}
|
||||
|
||||
await prisma.db_type_categories.update({
|
||||
where: { type_code: typeCode },
|
||||
data: {
|
||||
is_active: false,
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
await query(
|
||||
`UPDATE db_type_categories
|
||||
SET is_active = $1, updated_at = NOW()
|
||||
WHERE type_code = $2`,
|
||||
[false, typeCode]
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
@ -217,30 +242,28 @@ export class DbTypeCategoryService {
|
|||
*/
|
||||
static async getConnectionStatsByType(): Promise<ApiResponse<any[]>> {
|
||||
try {
|
||||
const stats = await prisma.external_db_connections.groupBy({
|
||||
by: ['db_type'],
|
||||
where: { is_active: "Y" },
|
||||
_count: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
// LEFT JOIN으로 한 번에 조회
|
||||
const result = await query<any>(
|
||||
`SELECT
|
||||
c.*,
|
||||
COUNT(e.id) as connection_count
|
||||
FROM db_type_categories c
|
||||
LEFT JOIN external_db_connections e ON c.type_code = e.db_type AND e.is_active = $1
|
||||
WHERE c.is_active = $2
|
||||
GROUP BY c.type_code, c.display_name, c.icon, c.color, c.sort_order, c.is_active, c.created_at, c.updated_at
|
||||
ORDER BY c.sort_order ASC`,
|
||||
["Y", true]
|
||||
);
|
||||
|
||||
// 카테고리 정보와 함께 반환
|
||||
const categories = await prisma.db_type_categories.findMany({
|
||||
where: { is_active: true }
|
||||
});
|
||||
|
||||
const result = categories.map(category => {
|
||||
const stat = stats.find(s => s.db_type === category.type_code);
|
||||
return {
|
||||
...category,
|
||||
connection_count: stat?._count.id || 0
|
||||
};
|
||||
});
|
||||
// connection_count를 숫자로 변환
|
||||
const formattedResult = result.map(row => ({
|
||||
...row,
|
||||
connection_count: parseInt(row.connection_count)
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
data: formattedResult,
|
||||
message: "DB 타입별 연결 통계를 조회했습니다."
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
@ -297,11 +320,20 @@ export class DbTypeCategoryService {
|
|||
];
|
||||
|
||||
for (const category of defaultCategories) {
|
||||
await prisma.db_type_categories.upsert({
|
||||
where: { type_code: category.type_code },
|
||||
update: {},
|
||||
create: category
|
||||
});
|
||||
await query(
|
||||
`INSERT INTO db_type_categories
|
||||
(type_code, display_name, icon, color, sort_order, is_active, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
|
||||
ON CONFLICT (type_code) DO NOTHING`,
|
||||
[
|
||||
category.type_code,
|
||||
category.display_name,
|
||||
category.icon,
|
||||
category.color,
|
||||
category.sort_order,
|
||||
true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue