feature/prisma-to-raw-query-phase1-complete #82

Merged
kjs merged 29 commits from feature/prisma-to-raw-query-phase1-complete into main 2025-10-01 15:07:17 +09:00
3 changed files with 254 additions and 119 deletions
Showing only changes of commit f2f0c33bad - Show all commits

View File

@ -2,14 +2,14 @@
## 📊 현재 상황
| 항목 | 내용 |
| --------------- | --------------------------------------- |
| 총 Prisma 호출 | 29개 |
| 대상 파일 | 7개 |
| **현재 진행률** | **17/29 (58.6%)** 🔄 **진행 중** |
| 복잡도 | 중간 |
| 우선순위 | 🔴 높음 (Phase 4) |
| **상태** | ⏳ **진행 중** |
| 항목 | 내용 |
| --------------- | -------------------------------- |
| 총 Prisma 호출 | 29개 |
| 대상 파일 | 7개 |
| **현재 진행률** | **17/29 (58.6%)** 🔄 **진행 중** |
| 복잡도 | 중간 |
| 우선순위 | 🔴 높음 (Phase 4) |
| **상태** | ⏳ **진행 중** |
---
@ -18,6 +18,7 @@
### ✅ 완료된 파일 (2개)
1. **adminController.ts** - ✅ **28개 완료**
- 사용자 관리: getUserList, getUserInfo, updateUserStatus, deleteUser
- 프로필 관리: getMyProfile, updateMyProfile, resetPassword
- 사용자 생성/수정: createOrUpdateUser (UPSERT)
@ -41,27 +42,33 @@
#### Prisma 호출 목록:
1. **라인 33**: `getWebTypeStandards()` - findMany
```typescript
const webTypes = await prisma.web_type_standards.findMany({
where, orderBy, select
where,
orderBy,
select,
});
```
2. **라인 58**: `getWebTypeStandard()` - findUnique
```typescript
const webTypeData = await prisma.web_type_standards.findUnique({
where: { id }
where: { id },
});
```
3. **라인 112**: `createWebTypeStandard()` - findUnique (중복 체크)
```typescript
const existingWebType = await prisma.web_type_standards.findUnique({
where: { web_type: webType }
where: { web_type: webType },
});
```
4. **라인 123**: `createWebTypeStandard()` - create
```typescript
const newWebType = await prisma.web_type_standards.create({
data: { ... }
@ -69,13 +76,15 @@
```
5. **라인 178**: `updateWebTypeStandard()` - findUnique (존재 확인)
```typescript
const existingWebType = await prisma.web_type_standards.findUnique({
where: { id }
where: { id },
});
```
6. **라인 189**: `updateWebTypeStandard()` - update
```typescript
const updatedWebType = await prisma.web_type_standards.update({
where: { id }, data: { ... }
@ -83,20 +92,23 @@
```
7. **라인 230**: `deleteWebTypeStandard()` - findUnique (존재 확인)
```typescript
const existingWebType = await prisma.web_type_standards.findUnique({
where: { id }
where: { id },
});
```
8. **라인 241**: `deleteWebTypeStandard()` - delete
```typescript
await prisma.web_type_standards.delete({
where: { id }
where: { id },
});
```
9. **라인 275**: `updateSortOrder()` - $transaction
```typescript
await prisma.$transaction(
updates.map((item) =>
@ -110,11 +122,14 @@
11. **라인 305**: `getCategories()` - groupBy
```typescript
const categories = await prisma.web_type_standards.groupBy({
by: ['category'], where, _count: true
by: ["category"],
where,
_count: true,
});
```
**전환 전략**:
- findMany → `query<WebTypeStandard>` with dynamic WHERE
- findUnique → `queryOne<WebTypeStandard>`
- create → `queryOne` with INSERT RETURNING
@ -134,11 +149,12 @@
1. **라인 726**: `downloadFile()` - findUnique
```typescript
const fileRecord = await prisma.attach_file_info.findUnique({
where: { objid: BigInt(objid) }
where: { objid: BigInt(objid) },
});
```
**전환 전략**:
- findUnique → `queryOne<AttachFileInfo>`
---
@ -150,16 +166,19 @@
#### Prisma 호출 목록:
1. **라인 1005**: `executeSelect()` - $queryRawUnsafe
```typescript
return await prisma.$queryRawUnsafe(query, ...queryParams);
```
2. **라인 1022**: `executeInsert()` - $queryRawUnsafe
```typescript
const insertResult = await prisma.$queryRawUnsafe(...);
```
3. **라인 1055**: `executeUpdate()` - $queryRawUnsafe
```typescript
return await prisma.$queryRawUnsafe(updateQuery, ...updateParams);
```
@ -170,6 +189,7 @@
```
**전환 전략**:
- $queryRawUnsafe → `query<any>` (이미 Raw SQL 사용 중)
---
@ -186,6 +206,7 @@
4. **라인 31, 35, 40**: `await prisma.$disconnect()`
**전환 전략**:
- 이 파일은 데이터베이스 설정 파일이므로 완전히 제거
- 기존 `db.ts`의 connection pool로 대체
- 모든 import 경로를 `database``database/db`로 변경
@ -199,6 +220,7 @@
#### Prisma 호출:
1. **라인 183-184**: 동적 PrismaClient import
```typescript
const { PrismaClient } = await import("@prisma/client");
const prisma = new PrismaClient();
@ -211,6 +233,7 @@
```
**전환 전략**:
- 동적 import 제거
- `query('SELECT 1')` 사용
@ -223,20 +246,23 @@
#### Prisma 호출:
1. **라인 32**: findUnique (중복 체크)
```typescript
const existingCompany = await prisma.company_mng.findUnique({
where: { company_code }
where: { company_code },
});
```
2. **라인 61**: update (회사명 업데이트)
```typescript
await prisma.company_mng.update({
where: { company_code }, data: { company_name }
where: { company_code },
data: { company_name },
});
```
**전환 전략**:
- findUnique → `queryOne`
- update → `query`
@ -253,24 +279,30 @@
## 🎯 전환 우선순위
### Phase 4.1: 컨트롤러 (완료)
- [x] screenFileController.ts (2개)
- [x] adminController.ts (28개)
### Phase 4.2: 남은 컨트롤러 (진행 예정)
- [ ] webTypeStandardController.ts (11개) - 🔴 최우선
- [ ] fileController.ts (1개)
### Phase 4.3: Routes (진행 예정)
- [ ] ddlRoutes.ts (2개)
- [ ] companyManagementRoutes.ts (2개)
### Phase 4.4: Services (진행 예정)
- [ ] multiConnectionQueryService.ts (4개)
### Phase 4.5: Config (진행 예정)
- [ ] database.ts (4개) - 전체 파일 제거
### Phase 4.6: Tests (Phase 5)
- [ ] authService.test.ts (2개) - 별도 처리
---
@ -278,6 +310,7 @@
## 📋 체크리스트
### webTypeStandardController.ts
- [ ] Prisma import 제거
- [ ] query, queryOne import 추가
- [ ] getWebTypeStandards (findMany → query)
@ -292,18 +325,21 @@
- [ ] 동작 테스트
### fileController.ts
- [ ] Prisma import 제거
- [ ] queryOne import 추가
- [ ] downloadFile (findUnique → queryOne)
- [ ] TypeScript 컴파일 확인
### routes/ddlRoutes.ts
- [ ] 동적 PrismaClient import 제거
- [ ] query import 추가
- [ ] 연결 테스트 로직 변경
- [ ] TypeScript 컴파일 확인
### routes/companyManagementRoutes.ts
- [ ] Prisma import 제거
- [ ] query, queryOne import 추가
- [ ] findUnique → queryOne
@ -311,12 +347,14 @@
- [ ] TypeScript 컴파일 확인
### services/multiConnectionQueryService.ts
- [ ] Prisma import 제거
- [ ] query import 추가
- [ ] $queryRawUnsafe → query (4곳)
- [ ] TypeScript 컴파일 확인
### config/database.ts
- [ ] 파일 전체 분석
- [ ] 의존성 확인
- [ ] 대체 방안 구현
@ -328,15 +366,20 @@
## 🔧 전환 패턴 요약
### 1. findMany → query
```typescript
// Before
const items = await prisma.table.findMany({ where, orderBy });
// After
const items = await query<T>(`SELECT * FROM table WHERE ... ORDER BY ...`, params);
const items = await query<T>(
`SELECT * FROM table WHERE ... ORDER BY ...`,
params
);
```
### 2. findUnique → queryOne
```typescript
// Before
const item = await prisma.table.findUnique({ where: { id } });
@ -346,6 +389,7 @@ const item = await queryOne<T>(`SELECT * FROM table WHERE id = $1`, [id]);
```
### 3. create → queryOne with RETURNING
```typescript
// Before
const newItem = await prisma.table.create({ data });
@ -358,6 +402,7 @@ const [newItem] = await query<T>(
```
### 4. update → query with RETURNING
```typescript
// Before
const updated = await prisma.table.update({ where, data });
@ -370,6 +415,7 @@ const [updated] = await query<T>(
```
### 5. delete → query
```typescript
// Before
await prisma.table.delete({ where: { id } });
@ -379,6 +425,7 @@ await query(`DELETE FROM table WHERE id = $1`, [id]);
```
### 6. $transaction → transaction
```typescript
// Before
await prisma.$transaction([
@ -394,11 +441,12 @@ await transaction(async (client) => {
```
### 7. groupBy → query with GROUP BY
```typescript
// Before
const result = await prisma.table.groupBy({
by: ['category'],
_count: true
by: ["category"],
_count: true,
});
// After
@ -422,30 +470,34 @@ Phase 4.2: 남은 파일 ███████░░░░░░░░░
### 상세 진행 상황
| 카테고리 | 완료 | 남음 | 진행률 |
| -------------- | ---- | ---- | ------ |
| Services | 415 | 0 | 100% |
| Controllers | 30 | 11 | 73% |
| Routes | 0 | 4 | 0% |
| Config | 0 | 4 | 0% |
| **총계** | 445 | 19 | 95.9% |
| 카테고리 | 완료 | 남음 | 진행률 |
| ----------- | ---- | ---- | ------ |
| Services | 415 | 0 | 100% |
| Controllers | 30 | 11 | 73% |
| Routes | 0 | 4 | 0% |
| Config | 0 | 4 | 0% |
| **총계** | 445 | 19 | 95.9% |
---
## 🎬 다음 단계
1. **webTypeStandardController.ts 전환** (11개)
- 가장 많은 Prisma 호출을 가진 남은 컨트롤러
- 웹 타입 표준 관리 핵심 기능
2. **fileController.ts 전환** (1개)
- 단순 findUnique만 있어 빠르게 처리 가능
3. **Routes 전환** (4개)
- ddlRoutes.ts
- companyManagementRoutes.ts
4. **Service 전환** (4개)
- multiConnectionQueryService.ts
5. **Config 제거** (4개)
@ -457,14 +509,17 @@ Phase 4.2: 남은 파일 ███████░░░░░░░░░
## ⚠️ 주의사항
1. **database.ts 처리**
- 현재 많은 파일이 `import prisma from '../config/database'` 사용
- 모든 import를 `import { query, queryOne } from '../database/db'`로 변경 필요
- 단계적으로 진행하여 빌드 오류 방지
2. **BigInt 처리**
- fileController의 `objid: BigInt(objid)``objid::bigint` 또는 `CAST(objid AS BIGINT)`
3. **트랜잭션 처리**
- webTypeStandardController의 `updateSortOrder`는 복잡한 트랜잭션
- `transaction` 함수 사용 필요
@ -489,4 +544,3 @@ Phase 4.2: 남은 파일 ███████░░░░░░░░░
**작성일**: 2025-10-01
**최종 업데이트**: 2025-10-01
**상태**: 🔄 진행 중 (58.6% 완료)

View File

@ -723,11 +723,10 @@ export const downloadFile = async (
try {
const { objid } = req.params;
const fileRecord = await prisma.attach_file_info.findUnique({
where: {
objid: parseInt(objid),
},
});
const fileRecord = await queryOne<any>(
`SELECT * FROM attach_file_info WHERE objid = $1`,
[parseInt(objid)]
);
if (!fileRecord || fileRecord.status !== "ACTIVE") {
res.status(404).json({

View File

@ -1,39 +1,51 @@
import { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
import { query, queryOne, transaction } from "../database/db";
import { AuthenticatedRequest } from "../types/auth";
const prisma = new PrismaClient();
export class WebTypeStandardController {
// 웹타입 목록 조회
static async getWebTypes(req: Request, res: Response) {
try {
const { active, category, search } = req.query;
const where: any = {};
// 동적 WHERE 절 생성
const whereConditions: string[] = [];
const queryParams: any[] = [];
let paramIndex = 1;
if (active) {
where.is_active = active as string;
whereConditions.push(`is_active = $${paramIndex}`);
queryParams.push(active);
paramIndex++;
}
if (category) {
where.category = category as string;
whereConditions.push(`category = $${paramIndex}`);
queryParams.push(category);
paramIndex++;
}
if (search) {
where.OR = [
{ type_name: { contains: search as string, mode: "insensitive" } },
{
type_name_eng: { contains: search as string, mode: "insensitive" },
},
{ description: { contains: search as string, mode: "insensitive" } },
];
if (search && typeof search === "string") {
whereConditions.push(`(
type_name ILIKE $${paramIndex} OR
type_name_eng ILIKE $${paramIndex} OR
description ILIKE $${paramIndex}
)`);
queryParams.push(`%${search}%`);
paramIndex++;
}
const webTypes = await prisma.web_type_standards.findMany({
where,
orderBy: [{ sort_order: "asc" }, { web_type: "asc" }],
});
const whereClause =
whereConditions.length > 0
? `WHERE ${whereConditions.join(" AND ")}`
: "";
const webTypes = await query<any>(
`SELECT * FROM web_type_standards
${whereClause}
ORDER BY sort_order ASC, web_type ASC`,
queryParams
);
return res.json({
success: true,
@ -55,9 +67,10 @@ export class WebTypeStandardController {
try {
const { webType } = req.params;
const webTypeData = await prisma.web_type_standards.findUnique({
where: { web_type: webType },
});
const webTypeData = await queryOne<any>(
`SELECT * FROM web_type_standards WHERE web_type = $1`,
[webType]
);
if (!webTypeData) {
return res.status(404).json({
@ -109,9 +122,10 @@ export class WebTypeStandardController {
}
// 중복 체크
const existingWebType = await prisma.web_type_standards.findUnique({
where: { web_type },
});
const existingWebType = await queryOne<any>(
`SELECT web_type FROM web_type_standards WHERE web_type = $1`,
[web_type]
);
if (existingWebType) {
return res.status(409).json({
@ -120,8 +134,15 @@ export class WebTypeStandardController {
});
}
const newWebType = await prisma.web_type_standards.create({
data: {
const [newWebType] = await query<any>(
`INSERT INTO web_type_standards (
web_type, type_name, type_name_eng, description, category,
component_name, config_panel, default_config, validation_rules,
default_style, input_properties, sort_order, is_active,
created_by, created_date, updated_by, updated_date
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW(), $15, NOW())
RETURNING *`,
[
web_type,
type_name,
type_name_eng,
@ -135,10 +156,10 @@ export class WebTypeStandardController {
input_properties,
sort_order,
is_active,
created_by: req.user?.userId || "system",
updated_by: req.user?.userId || "system",
},
});
req.user?.userId || "system",
req.user?.userId || "system",
]
);
return res.status(201).json({
success: true,
@ -174,37 +195,106 @@ export class WebTypeStandardController {
is_active,
} = req.body;
// 존재 여부 확인
const existingWebType = await prisma.web_type_standards.findUnique({
where: { web_type: webType },
});
// 동적 UPDATE 쿼리 생성
const updateFields: string[] = [];
const updateValues: any[] = [];
let paramIndex = 1;
if (!existingWebType) {
if (type_name !== undefined) {
updateFields.push(`type_name = $${paramIndex}`);
updateValues.push(type_name);
paramIndex++;
}
if (type_name_eng !== undefined) {
updateFields.push(`type_name_eng = $${paramIndex}`);
updateValues.push(type_name_eng);
paramIndex++;
}
if (description !== undefined) {
updateFields.push(`description = $${paramIndex}`);
updateValues.push(description);
paramIndex++;
}
if (category !== undefined) {
updateFields.push(`category = $${paramIndex}`);
updateValues.push(category);
paramIndex++;
}
if (component_name !== undefined) {
updateFields.push(`component_name = $${paramIndex}`);
updateValues.push(component_name);
paramIndex++;
}
if (config_panel !== undefined) {
updateFields.push(`config_panel = $${paramIndex}`);
updateValues.push(config_panel);
paramIndex++;
}
if (default_config !== undefined) {
updateFields.push(`default_config = $${paramIndex}`);
updateValues.push(default_config);
paramIndex++;
}
if (validation_rules !== undefined) {
updateFields.push(`validation_rules = $${paramIndex}`);
updateValues.push(validation_rules);
paramIndex++;
}
if (default_style !== undefined) {
updateFields.push(`default_style = $${paramIndex}`);
updateValues.push(default_style);
paramIndex++;
}
if (input_properties !== undefined) {
updateFields.push(`input_properties = $${paramIndex}`);
updateValues.push(input_properties);
paramIndex++;
}
if (sort_order !== undefined) {
updateFields.push(`sort_order = $${paramIndex}`);
updateValues.push(sort_order);
paramIndex++;
}
if (is_active !== undefined) {
updateFields.push(`is_active = $${paramIndex}`);
updateValues.push(is_active);
paramIndex++;
}
// updated_by, updated_date는 항상 추가
updateFields.push(`updated_by = $${paramIndex}`);
updateValues.push(req.user?.userId || "system");
paramIndex++;
updateFields.push(`updated_date = NOW()`);
if (updateFields.length === 2) {
// updated_by, updated_date만 있는 경우 = 수정할 내용이 없음
return res.status(400).json({
success: false,
message: "수정할 내용이 없습니다.",
});
}
// WHERE 조건용 파라미터 추가
updateValues.push(webType);
const result = await query<any>(
`UPDATE web_type_standards
SET ${updateFields.join(", ")}
WHERE web_type = $${paramIndex}
RETURNING *`,
updateValues
);
if (result.length === 0) {
return res.status(404).json({
success: false,
message: "해당 웹타입을 찾을 수 없습니다.",
});
}
const updatedWebType = await prisma.web_type_standards.update({
where: { web_type: webType },
data: {
type_name,
type_name_eng,
description,
category,
component_name,
config_panel,
default_config,
validation_rules,
default_style,
input_properties,
sort_order,
is_active,
updated_by: req.user?.userId || "system",
updated_date: new Date(),
},
});
const updatedWebType = result[0];
return res.json({
success: true,
@ -226,22 +316,18 @@ export class WebTypeStandardController {
try {
const { webType } = req.params;
// 존재 여부 확인
const existingWebType = await prisma.web_type_standards.findUnique({
where: { web_type: webType },
});
const result = await query<any>(
`DELETE FROM web_type_standards WHERE web_type = $1 RETURNING *`,
[webType]
);
if (!existingWebType) {
if (result.length === 0) {
return res.status(404).json({
success: false,
message: "해당 웹타입을 찾을 수 없습니다.",
});
}
await prisma.web_type_standards.delete({
where: { web_type: webType },
});
return res.json({
success: true,
message: "웹타입이 성공적으로 삭제되었습니다.",
@ -272,18 +358,16 @@ export class WebTypeStandardController {
}
// 트랜잭션으로 일괄 업데이트
await prisma.$transaction(
webTypes.map((item) =>
prisma.web_type_standards.update({
where: { web_type: item.web_type },
data: {
sort_order: item.sort_order,
updated_by: req.user?.userId || "system",
updated_date: new Date(),
},
})
)
);
await transaction(async (client) => {
for (const item of webTypes) {
await client.query(
`UPDATE web_type_standards
SET sort_order = $1, updated_by = $2, updated_date = NOW()
WHERE web_type = $3`,
[item.sort_order, req.user?.userId || "system", item.web_type]
);
}
});
return res.json({
success: true,
@ -302,19 +386,17 @@ export class WebTypeStandardController {
// 웹타입 카테고리 목록 조회
static async getWebTypeCategories(req: Request, res: Response) {
try {
const categories = await prisma.web_type_standards.groupBy({
by: ["category"],
where: {
is_active: "Y",
},
_count: {
category: true,
},
});
const categories = await query<{ category: string; count: string }>(
`SELECT category, COUNT(*) as count
FROM web_type_standards
WHERE is_active = 'Y'
GROUP BY category`,
[]
);
const categoryList = categories.map((item) => ({
category: item.category,
count: item._count.category,
count: parseInt(item.count, 10),
}));
return res.json({