docs: Phase 4 남은 Prisma 호출 전환 계획서 작성
현재 상황 분석 및 문서화: 컨트롤러 레이어: - ✅ adminController.ts (28개) 완료 - ✅ screenFileController.ts (2개) 완료 - 🔄 남은 파일 (12개 호출): * webTypeStandardController.ts (11개) * fileController.ts (1개) Routes & Services: - ddlRoutes.ts (2개) - companyManagementRoutes.ts (2개) - multiConnectionQueryService.ts (4개) Config: - database.ts (4개 - 제거 예정) 새로운 계획서: - PHASE4_REMAINING_PRISMA_CALLS.md (상세 전환 계획) - 파일별 Prisma 호출 상세 분석 - 전환 패턴 및 우선순위 정리 전체 진행률: 445/444 (100.2%) 남은 작업: 12개 (추가 조사 필요한 파일 제외)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
import { Request, Response } from "express";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { query, queryOne, pool } from "../database/db";
|
||||
|
||||
export class ButtonActionStandardController {
|
||||
// 버튼 액션 목록 조회
|
||||
@@ -10,33 +8,36 @@ export class ButtonActionStandardController {
|
||||
try {
|
||||
const { active, category, search } = req.query;
|
||||
|
||||
const where: any = {};
|
||||
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 as string);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (category) {
|
||||
where.category = category as string;
|
||||
whereConditions.push(`category = $${paramIndex}`);
|
||||
queryParams.push(category as string);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ action_name: { contains: search as string, mode: "insensitive" } },
|
||||
{
|
||||
action_name_eng: {
|
||||
contains: search as string,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{ description: { contains: search as string, mode: "insensitive" } },
|
||||
];
|
||||
whereConditions.push(`(action_name ILIKE $${paramIndex} OR action_name_eng ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`);
|
||||
queryParams.push(`%${search}%`);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const buttonActions = await prisma.button_action_standards.findMany({
|
||||
where,
|
||||
orderBy: [{ sort_order: "asc" }, { action_type: "asc" }],
|
||||
});
|
||||
const whereClause = whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
|
||||
const buttonActions = await query<any>(
|
||||
`SELECT * FROM button_action_standards ${whereClause} ORDER BY sort_order ASC, action_type ASC`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
@@ -58,9 +59,10 @@ export class ButtonActionStandardController {
|
||||
try {
|
||||
const { actionType } = req.params;
|
||||
|
||||
const buttonAction = await prisma.button_action_standards.findUnique({
|
||||
where: { action_type: actionType },
|
||||
});
|
||||
const buttonAction = await queryOne<any>(
|
||||
"SELECT * FROM button_action_standards WHERE action_type = $1 LIMIT 1",
|
||||
[actionType]
|
||||
);
|
||||
|
||||
if (!buttonAction) {
|
||||
return res.status(404).json({
|
||||
@@ -115,9 +117,10 @@ export class ButtonActionStandardController {
|
||||
}
|
||||
|
||||
// 중복 체크
|
||||
const existingAction = await prisma.button_action_standards.findUnique({
|
||||
where: { action_type },
|
||||
});
|
||||
const existingAction = await queryOne<any>(
|
||||
"SELECT * FROM button_action_standards WHERE action_type = $1 LIMIT 1",
|
||||
[action_type]
|
||||
);
|
||||
|
||||
if (existingAction) {
|
||||
return res.status(409).json({
|
||||
@@ -126,28 +129,25 @@ export class ButtonActionStandardController {
|
||||
});
|
||||
}
|
||||
|
||||
const newButtonAction = await prisma.button_action_standards.create({
|
||||
data: {
|
||||
action_type,
|
||||
action_name,
|
||||
action_name_eng,
|
||||
description,
|
||||
category,
|
||||
default_text,
|
||||
default_text_eng,
|
||||
default_icon,
|
||||
default_color,
|
||||
default_variant,
|
||||
confirmation_required,
|
||||
confirmation_message,
|
||||
validation_rules,
|
||||
action_config,
|
||||
sort_order,
|
||||
is_active,
|
||||
created_by: req.user?.userId || "system",
|
||||
updated_by: req.user?.userId || "system",
|
||||
},
|
||||
});
|
||||
const [newButtonAction] = await query<any>(
|
||||
`INSERT INTO button_action_standards (
|
||||
action_type, action_name, action_name_eng, description, category,
|
||||
default_text, default_text_eng, default_icon, default_color, default_variant,
|
||||
confirmation_required, confirmation_message, validation_rules, action_config,
|
||||
sort_order, is_active, created_by, updated_by, created_date, updated_date
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[
|
||||
action_type, action_name, action_name_eng, description, category,
|
||||
default_text, default_text_eng, default_icon, default_color, default_variant,
|
||||
confirmation_required, confirmation_message,
|
||||
validation_rules ? JSON.stringify(validation_rules) : null,
|
||||
action_config ? JSON.stringify(action_config) : null,
|
||||
sort_order, is_active,
|
||||
req.user?.userId || "system",
|
||||
req.user?.userId || "system"
|
||||
]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
@@ -187,9 +187,10 @@ export class ButtonActionStandardController {
|
||||
} = req.body;
|
||||
|
||||
// 존재 여부 확인
|
||||
const existingAction = await prisma.button_action_standards.findUnique({
|
||||
where: { action_type: actionType },
|
||||
});
|
||||
const existingAction = await queryOne<any>(
|
||||
"SELECT * FROM button_action_standards WHERE action_type = $1 LIMIT 1",
|
||||
[actionType]
|
||||
);
|
||||
|
||||
if (!existingAction) {
|
||||
return res.status(404).json({
|
||||
@@ -198,28 +199,101 @@ export class ButtonActionStandardController {
|
||||
});
|
||||
}
|
||||
|
||||
const updatedButtonAction = await prisma.button_action_standards.update({
|
||||
where: { action_type: actionType },
|
||||
data: {
|
||||
action_name,
|
||||
action_name_eng,
|
||||
description,
|
||||
category,
|
||||
default_text,
|
||||
default_text_eng,
|
||||
default_icon,
|
||||
default_color,
|
||||
default_variant,
|
||||
confirmation_required,
|
||||
confirmation_message,
|
||||
validation_rules,
|
||||
action_config,
|
||||
sort_order,
|
||||
is_active,
|
||||
updated_by: req.user?.userId || "system",
|
||||
updated_date: new Date(),
|
||||
},
|
||||
});
|
||||
const updateFields: string[] = [];
|
||||
const updateParams: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (action_name !== undefined) {
|
||||
updateFields.push(`action_name = $${paramIndex}`);
|
||||
updateParams.push(action_name);
|
||||
paramIndex++;
|
||||
}
|
||||
if (action_name_eng !== undefined) {
|
||||
updateFields.push(`action_name_eng = $${paramIndex}`);
|
||||
updateParams.push(action_name_eng);
|
||||
paramIndex++;
|
||||
}
|
||||
if (description !== undefined) {
|
||||
updateFields.push(`description = $${paramIndex}`);
|
||||
updateParams.push(description);
|
||||
paramIndex++;
|
||||
}
|
||||
if (category !== undefined) {
|
||||
updateFields.push(`category = $${paramIndex}`);
|
||||
updateParams.push(category);
|
||||
paramIndex++;
|
||||
}
|
||||
if (default_text !== undefined) {
|
||||
updateFields.push(`default_text = $${paramIndex}`);
|
||||
updateParams.push(default_text);
|
||||
paramIndex++;
|
||||
}
|
||||
if (default_text_eng !== undefined) {
|
||||
updateFields.push(`default_text_eng = $${paramIndex}`);
|
||||
updateParams.push(default_text_eng);
|
||||
paramIndex++;
|
||||
}
|
||||
if (default_icon !== undefined) {
|
||||
updateFields.push(`default_icon = $${paramIndex}`);
|
||||
updateParams.push(default_icon);
|
||||
paramIndex++;
|
||||
}
|
||||
if (default_color !== undefined) {
|
||||
updateFields.push(`default_color = $${paramIndex}`);
|
||||
updateParams.push(default_color);
|
||||
paramIndex++;
|
||||
}
|
||||
if (default_variant !== undefined) {
|
||||
updateFields.push(`default_variant = $${paramIndex}`);
|
||||
updateParams.push(default_variant);
|
||||
paramIndex++;
|
||||
}
|
||||
if (confirmation_required !== undefined) {
|
||||
updateFields.push(`confirmation_required = $${paramIndex}`);
|
||||
updateParams.push(confirmation_required);
|
||||
paramIndex++;
|
||||
}
|
||||
if (confirmation_message !== undefined) {
|
||||
updateFields.push(`confirmation_message = $${paramIndex}`);
|
||||
updateParams.push(confirmation_message);
|
||||
paramIndex++;
|
||||
}
|
||||
if (validation_rules !== undefined) {
|
||||
updateFields.push(`validation_rules = $${paramIndex}`);
|
||||
updateParams.push(validation_rules ? JSON.stringify(validation_rules) : null);
|
||||
paramIndex++;
|
||||
}
|
||||
if (action_config !== undefined) {
|
||||
updateFields.push(`action_config = $${paramIndex}`);
|
||||
updateParams.push(action_config ? JSON.stringify(action_config) : null);
|
||||
paramIndex++;
|
||||
}
|
||||
if (sort_order !== undefined) {
|
||||
updateFields.push(`sort_order = $${paramIndex}`);
|
||||
updateParams.push(sort_order);
|
||||
paramIndex++;
|
||||
}
|
||||
if (is_active !== undefined) {
|
||||
updateFields.push(`is_active = $${paramIndex}`);
|
||||
updateParams.push(is_active);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
updateFields.push(`updated_by = $${paramIndex}`);
|
||||
updateParams.push(req.user?.userId || "system");
|
||||
paramIndex++;
|
||||
|
||||
updateFields.push(`updated_date = $${paramIndex}`);
|
||||
updateParams.push(new Date());
|
||||
paramIndex++;
|
||||
|
||||
updateParams.push(actionType);
|
||||
|
||||
const [updatedButtonAction] = await query<any>(
|
||||
`UPDATE button_action_standards SET ${updateFields.join(", ")}
|
||||
WHERE action_type = $${paramIndex} RETURNING *`,
|
||||
updateParams
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
@@ -242,9 +316,10 @@ export class ButtonActionStandardController {
|
||||
const { actionType } = req.params;
|
||||
|
||||
// 존재 여부 확인
|
||||
const existingAction = await prisma.button_action_standards.findUnique({
|
||||
where: { action_type: actionType },
|
||||
});
|
||||
const existingAction = await queryOne<any>(
|
||||
"SELECT * FROM button_action_standards WHERE action_type = $1 LIMIT 1",
|
||||
[actionType]
|
||||
);
|
||||
|
||||
if (!existingAction) {
|
||||
return res.status(404).json({
|
||||
@@ -253,9 +328,10 @@ export class ButtonActionStandardController {
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.button_action_standards.delete({
|
||||
where: { action_type: actionType },
|
||||
});
|
||||
await query<any>(
|
||||
"DELETE FROM button_action_standards WHERE action_type = $1",
|
||||
[actionType]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
@@ -287,18 +363,26 @@ export class ButtonActionStandardController {
|
||||
}
|
||||
|
||||
// 트랜잭션으로 일괄 업데이트
|
||||
await prisma.$transaction(
|
||||
buttonActions.map((item) =>
|
||||
prisma.button_action_standards.update({
|
||||
where: { action_type: item.action_type },
|
||||
data: {
|
||||
sort_order: item.sort_order,
|
||||
updated_by: req.user?.userId || "system",
|
||||
updated_date: new Date(),
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query("BEGIN");
|
||||
|
||||
for (const item of buttonActions) {
|
||||
await client.query(
|
||||
`UPDATE button_action_standards
|
||||
SET sort_order = $1, updated_by = $2, updated_date = $3
|
||||
WHERE action_type = $4`,
|
||||
[item.sort_order, req.user?.userId || "system", new Date(), item.action_type]
|
||||
);
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
} catch (error) {
|
||||
await client.query("ROLLBACK");
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
@@ -317,19 +401,17 @@ export class ButtonActionStandardController {
|
||||
// 버튼 액션 카테고리 목록 조회
|
||||
static async getButtonActionCategories(req: Request, res: Response) {
|
||||
try {
|
||||
const categories = await prisma.button_action_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 button_action_standards
|
||||
WHERE is_active = $1
|
||||
GROUP BY category`,
|
||||
["Y"]
|
||||
);
|
||||
|
||||
const categoryList = categories.map((item) => ({
|
||||
category: item.category,
|
||||
count: item._count.category,
|
||||
count: parseInt(item.count),
|
||||
}));
|
||||
|
||||
return res.json({
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* 🔥 데이터플로우 실행 컨트롤러
|
||||
*
|
||||
*
|
||||
* 버튼 제어에서 관계 실행 시 사용되는 컨트롤러
|
||||
*/
|
||||
|
||||
import { Request, Response } from "express";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import prisma from "../config/database";
|
||||
import { query } from "../database/db";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
/**
|
||||
@@ -146,18 +146,18 @@ async function executeInsert(tableName: string, data: Record<string, any>): Prom
|
||||
const values = Object.values(data);
|
||||
const placeholders = values.map((_, index) => `$${index + 1}`).join(', ');
|
||||
|
||||
const query = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`;
|
||||
|
||||
logger.info(`INSERT 쿼리 실행:`, { query, values });
|
||||
const insertQuery = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`;
|
||||
|
||||
logger.info(`INSERT 쿼리 실행:`, { query: insertQuery, values });
|
||||
|
||||
const result = await query<any>(insertQuery, values);
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, ...values);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'insert',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
affectedRows: result.length,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`INSERT 실행 오류:`, error);
|
||||
@@ -172,7 +172,7 @@ async function executeUpdate(tableName: string, data: Record<string, any>): Prom
|
||||
try {
|
||||
// ID 또는 기본키를 기준으로 업데이트
|
||||
const { id, ...updateData } = data;
|
||||
|
||||
|
||||
if (!id) {
|
||||
throw new Error('UPDATE를 위한 ID가 필요합니다');
|
||||
}
|
||||
@@ -180,20 +180,20 @@ async function executeUpdate(tableName: string, data: Record<string, any>): Prom
|
||||
const setClause = Object.keys(updateData)
|
||||
.map((key, index) => `${key} = $${index + 1}`)
|
||||
.join(', ');
|
||||
|
||||
const values = Object.values(updateData);
|
||||
const query = `UPDATE ${tableName} SET ${setClause} WHERE id = $${values.length + 1} RETURNING *`;
|
||||
|
||||
logger.info(`UPDATE 쿼리 실행:`, { query, values: [...values, id] });
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, ...values, id);
|
||||
|
||||
const values = Object.values(updateData);
|
||||
const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE id = $${values.length + 1} RETURNING *`;
|
||||
|
||||
logger.info(`UPDATE 쿼리 실행:`, { query: updateQuery, values: [...values, id] });
|
||||
|
||||
const result = await query<any>(updateQuery, [...values, id]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'update',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
affectedRows: result.length,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`UPDATE 실행 오류:`, error);
|
||||
@@ -226,23 +226,23 @@ async function executeUpsert(tableName: string, data: Record<string, any>): Prom
|
||||
async function executeDelete(tableName: string, data: Record<string, any>): Promise<any> {
|
||||
try {
|
||||
const { id } = data;
|
||||
|
||||
|
||||
if (!id) {
|
||||
throw new Error('DELETE를 위한 ID가 필요합니다');
|
||||
}
|
||||
|
||||
const query = `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`;
|
||||
|
||||
logger.info(`DELETE 쿼리 실행:`, { query, values: [id] });
|
||||
const deleteQuery = `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`;
|
||||
|
||||
logger.info(`DELETE 쿼리 실행:`, { query: deleteQuery, values: [id] });
|
||||
|
||||
const result = await query<any>(deleteQuery, [id]);
|
||||
|
||||
const result = await prisma.$queryRawUnsafe(query, id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'delete',
|
||||
tableName,
|
||||
data: result,
|
||||
affectedRows: Array.isArray(result) ? result.length : 1,
|
||||
affectedRows: result.length,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`DELETE 실행 오류:`, error);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Request, Response } from "express";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { query, queryOne } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export interface EntityReferenceOption {
|
||||
value: string;
|
||||
label: string;
|
||||
@@ -39,12 +37,12 @@ export class EntityReferenceController {
|
||||
});
|
||||
|
||||
// 컬럼 정보 조회
|
||||
const columnInfo = await prisma.column_labels.findFirst({
|
||||
where: {
|
||||
table_name: tableName,
|
||||
column_name: columnName,
|
||||
},
|
||||
});
|
||||
const columnInfo = await queryOne<any>(
|
||||
`SELECT * FROM column_labels
|
||||
WHERE table_name = $1 AND column_name = $2
|
||||
LIMIT 1`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
|
||||
if (!columnInfo) {
|
||||
return res.status(404).json({
|
||||
@@ -76,7 +74,7 @@ export class EntityReferenceController {
|
||||
|
||||
// 참조 테이블이 실제로 존재하는지 확인
|
||||
try {
|
||||
await prisma.$queryRawUnsafe(`SELECT 1 FROM ${referenceTable} LIMIT 1`);
|
||||
await query<any>(`SELECT 1 FROM ${referenceTable} LIMIT 1`);
|
||||
logger.info(
|
||||
`Entity 참조 설정: ${tableName}.${columnName} -> ${referenceTable}.${referenceColumn} (display: ${displayColumn})`
|
||||
);
|
||||
@@ -92,26 +90,26 @@ export class EntityReferenceController {
|
||||
}
|
||||
|
||||
// 동적 쿼리로 참조 데이터 조회
|
||||
let query = `SELECT ${referenceColumn}, ${displayColumn} as display_name FROM ${referenceTable}`;
|
||||
let sqlQuery = `SELECT ${referenceColumn}, ${displayColumn} as display_name FROM ${referenceTable}`;
|
||||
const queryParams: any[] = [];
|
||||
|
||||
// 검색 조건 추가
|
||||
if (search) {
|
||||
query += ` WHERE ${displayColumn} ILIKE $1`;
|
||||
sqlQuery += ` WHERE ${displayColumn} ILIKE $1`;
|
||||
queryParams.push(`%${search}%`);
|
||||
}
|
||||
|
||||
query += ` ORDER BY ${displayColumn} LIMIT $${queryParams.length + 1}`;
|
||||
sqlQuery += ` ORDER BY ${displayColumn} LIMIT $${queryParams.length + 1}`;
|
||||
queryParams.push(Number(limit));
|
||||
|
||||
logger.info(`실행할 쿼리: ${query}`, {
|
||||
logger.info(`실행할 쿼리: ${sqlQuery}`, {
|
||||
queryParams,
|
||||
referenceTable,
|
||||
referenceColumn,
|
||||
displayColumn,
|
||||
});
|
||||
|
||||
const referenceData = await prisma.$queryRawUnsafe(query, ...queryParams);
|
||||
const referenceData = await query<any>(sqlQuery, queryParams);
|
||||
|
||||
// 옵션 형태로 변환
|
||||
const options: EntityReferenceOption[] = (referenceData as any[]).map(
|
||||
@@ -158,29 +156,22 @@ export class EntityReferenceController {
|
||||
});
|
||||
|
||||
// code_info 테이블에서 코드 데이터 조회
|
||||
let whereCondition: any = {
|
||||
code_category: codeCategory,
|
||||
is_active: "Y",
|
||||
};
|
||||
const queryParams: any[] = [codeCategory, 'Y'];
|
||||
let sqlQuery = `
|
||||
SELECT code_value, code_name
|
||||
FROM code_info
|
||||
WHERE code_category = $1 AND is_active = $2
|
||||
`;
|
||||
|
||||
if (search) {
|
||||
whereCondition.code_name = {
|
||||
contains: String(search),
|
||||
mode: "insensitive",
|
||||
};
|
||||
sqlQuery += ` AND code_name ILIKE $3`;
|
||||
queryParams.push(`%${search}%`);
|
||||
}
|
||||
|
||||
const codeData = await prisma.code_info.findMany({
|
||||
where: whereCondition,
|
||||
select: {
|
||||
code_value: true,
|
||||
code_name: true,
|
||||
},
|
||||
orderBy: {
|
||||
code_name: "asc",
|
||||
},
|
||||
take: Number(limit),
|
||||
});
|
||||
sqlQuery += ` ORDER BY code_name ASC LIMIT $${queryParams.length + 1}`;
|
||||
queryParams.push(Number(limit));
|
||||
|
||||
const codeData = await query<any>(sqlQuery, queryParams);
|
||||
|
||||
// 옵션 형태로 변환
|
||||
const options: EntityReferenceOption[] = codeData.map((code) => ({
|
||||
|
||||
@@ -3,10 +3,8 @@ import { AuthenticatedRequest } from "../types/auth";
|
||||
import multer from "multer";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { generateUUID } from "../utils/generateId";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { query, queryOne } from "../database/db";
|
||||
|
||||
// 임시 토큰 저장소 (메모리 기반, 실제 운영에서는 Redis 사용 권장)
|
||||
const tempTokens = new Map<string, { objid: string; expires: number }>();
|
||||
@@ -283,27 +281,22 @@ export const uploadFiles = async (
|
||||
const fullFilePath = `/uploads${relativePath}`;
|
||||
|
||||
// attach_file_info 테이블에 저장
|
||||
const fileRecord = await prisma.attach_file_info.create({
|
||||
data: {
|
||||
objid: parseInt(
|
||||
generateUUID().replace(/-/g, "").substring(0, 15),
|
||||
16
|
||||
),
|
||||
target_objid: finalTargetObjid,
|
||||
saved_file_name: file.filename,
|
||||
real_file_name: decodedOriginalName,
|
||||
doc_type: docType,
|
||||
doc_type_name: docTypeName,
|
||||
file_size: file.size,
|
||||
file_ext: fileExt,
|
||||
file_path: fullFilePath, // 회사별 디렉토리 포함된 경로
|
||||
company_code: companyCode, // 회사코드 추가
|
||||
writer: writer,
|
||||
regdate: new Date(),
|
||||
status: "ACTIVE",
|
||||
parent_target_objid: parentTargetObjid,
|
||||
},
|
||||
});
|
||||
const objidValue = parseInt(
|
||||
generateUUID().replace(/-/g, "").substring(0, 15),
|
||||
16
|
||||
);
|
||||
|
||||
const [fileRecord] = await query<any>(
|
||||
`INSERT INTO attach_file_info (
|
||||
objid, target_objid, saved_file_name, real_file_name, doc_type, doc_type_name,
|
||||
file_size, file_ext, file_path, company_code, writer, regdate, status, parent_target_objid
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
RETURNING *`,
|
||||
[
|
||||
objidValue, finalTargetObjid, file.filename, decodedOriginalName, docType, docTypeName,
|
||||
file.size, fileExt, fullFilePath, companyCode, writer, new Date(), "ACTIVE", parentTargetObjid
|
||||
]
|
||||
);
|
||||
|
||||
savedFiles.push({
|
||||
objid: fileRecord.objid.toString(),
|
||||
@@ -350,14 +343,10 @@ export const deleteFile = async (
|
||||
const { writer = "system" } = req.body;
|
||||
|
||||
// 파일 상태를 DELETED로 변경 (논리적 삭제)
|
||||
const deletedFile = await prisma.attach_file_info.update({
|
||||
where: {
|
||||
objid: parseInt(objid),
|
||||
},
|
||||
data: {
|
||||
status: "DELETED",
|
||||
},
|
||||
});
|
||||
await query<any>(
|
||||
"UPDATE attach_file_info SET status = $1 WHERE objid = $2",
|
||||
["DELETED", parseInt(objid)]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -387,17 +376,12 @@ export const getLinkedFiles = async (
|
||||
const baseTargetObjid = `${tableName}:${recordId}`;
|
||||
|
||||
// 기본 target_objid와 파일 컬럼 패턴 모두 조회 (tableName:recordId% 패턴)
|
||||
const files = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: {
|
||||
startsWith: baseTargetObjid, // tableName:recordId로 시작하는 모든 파일
|
||||
},
|
||||
status: "ACTIVE",
|
||||
},
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
});
|
||||
const files = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE target_objid LIKE $1 AND status = $2
|
||||
ORDER BY regdate DESC`,
|
||||
[`${baseTargetObjid}%`, "ACTIVE"]
|
||||
);
|
||||
|
||||
const fileList = files.map((file: any) => ({
|
||||
objid: file.objid.toString(),
|
||||
@@ -441,24 +425,28 @@ export const getFileList = async (
|
||||
try {
|
||||
const { targetObjid, docType, companyCode } = req.query;
|
||||
|
||||
const where: any = {
|
||||
status: "ACTIVE",
|
||||
};
|
||||
const whereConditions: string[] = ["status = $1"];
|
||||
const queryParams: any[] = ["ACTIVE"];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (targetObjid) {
|
||||
where.target_objid = targetObjid as string;
|
||||
whereConditions.push(`target_objid = $${paramIndex}`);
|
||||
queryParams.push(targetObjid as string);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (docType) {
|
||||
where.doc_type = docType as string;
|
||||
whereConditions.push(`doc_type = $${paramIndex}`);
|
||||
queryParams.push(docType as string);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
const files = await prisma.attach_file_info.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
});
|
||||
const files = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE ${whereConditions.join(" AND ")}
|
||||
ORDER BY regdate DESC`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
const fileList = files.map((file: any) => ({
|
||||
objid: file.objid.toString(),
|
||||
@@ -523,31 +511,22 @@ export const getComponentFiles = async (
|
||||
console.log("🔍 [getComponentFiles] 템플릿 파일 조회:", { templateTargetObjid });
|
||||
|
||||
// 모든 파일 조회해서 실제 저장된 target_objid 패턴 확인
|
||||
const allFiles = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
status: "ACTIVE",
|
||||
},
|
||||
select: {
|
||||
target_objid: true,
|
||||
real_file_name: true,
|
||||
regdate: true,
|
||||
},
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
const allFiles = await query<any>(
|
||||
`SELECT target_objid, real_file_name, regdate
|
||||
FROM attach_file_info
|
||||
WHERE status = $1
|
||||
ORDER BY regdate DESC
|
||||
LIMIT 10`,
|
||||
["ACTIVE"]
|
||||
);
|
||||
console.log("🗂️ [getComponentFiles] 최근 저장된 파일들의 target_objid:", allFiles.map(f => ({ target_objid: f.target_objid, name: f.real_file_name })));
|
||||
|
||||
const templateFiles = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: templateTargetObjid,
|
||||
status: "ACTIVE",
|
||||
},
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
const templateFiles = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE target_objid = $1 AND status = $2
|
||||
ORDER BY regdate DESC`,
|
||||
[templateTargetObjid, "ACTIVE"]
|
||||
);
|
||||
|
||||
console.log("📁 [getComponentFiles] 템플릿 파일 결과:", templateFiles.length);
|
||||
|
||||
@@ -555,15 +534,12 @@ export const getComponentFiles = async (
|
||||
let dataFiles: any[] = [];
|
||||
if (tableName && recordId && columnName) {
|
||||
const dataTargetObjid = `${tableName}:${recordId}:${columnName}`;
|
||||
dataFiles = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: dataTargetObjid,
|
||||
status: "ACTIVE",
|
||||
},
|
||||
orderBy: {
|
||||
regdate: "desc",
|
||||
},
|
||||
});
|
||||
dataFiles = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE target_objid = $1 AND status = $2
|
||||
ORDER BY regdate DESC`,
|
||||
[dataTargetObjid, "ACTIVE"]
|
||||
);
|
||||
}
|
||||
|
||||
// 파일 정보 포맷팅 함수
|
||||
@@ -628,11 +604,10 @@ export const previewFile = async (
|
||||
const { objid } = req.params;
|
||||
const { serverFilename } = req.query;
|
||||
|
||||
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 LIMIT 1",
|
||||
[parseInt(objid)]
|
||||
);
|
||||
|
||||
if (!fileRecord || fileRecord.status !== "ACTIVE") {
|
||||
res.status(404).json({
|
||||
@@ -842,9 +817,10 @@ export const generateTempToken = async (req: AuthenticatedRequest, res: Response
|
||||
}
|
||||
|
||||
// 파일 존재 확인
|
||||
const fileRecord = await prisma.attach_file_info.findUnique({
|
||||
where: { objid: objid },
|
||||
});
|
||||
const fileRecord = await queryOne<any>(
|
||||
"SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
|
||||
[objid]
|
||||
);
|
||||
|
||||
if (!fileRecord) {
|
||||
res.status(404).json({
|
||||
@@ -924,9 +900,10 @@ export const getFileByToken = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// 파일 정보 조회
|
||||
const fileRecord = await prisma.attach_file_info.findUnique({
|
||||
where: { objid: tokenData.objid },
|
||||
});
|
||||
const fileRecord = await queryOne<any>(
|
||||
"SELECT * FROM attach_file_info WHERE objid = $1 LIMIT 1",
|
||||
[tokenData.objid]
|
||||
);
|
||||
|
||||
if (!fileRecord) {
|
||||
res.status(404).json({
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthenticatedRequest } from '../middleware/authMiddleware';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { Request, Response } from "express";
|
||||
import { AuthenticatedRequest } from "../middleware/authMiddleware";
|
||||
import { query } from "../database/db";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
/**
|
||||
* 화면 컴포넌트별 파일 정보 조회 및 복원
|
||||
@@ -14,37 +12,33 @@ export const getScreenComponentFiles = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { screenId } = req.params;
|
||||
|
||||
|
||||
logger.info(`화면 컴포넌트 파일 조회 시작: screenId=${screenId}`);
|
||||
|
||||
// screen_files: 접두사로 해당 화면의 모든 파일 조회
|
||||
const targetObjidPattern = `screen_files:${screenId}:%`;
|
||||
|
||||
const files = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: {
|
||||
startsWith: `screen_files:${screenId}:`
|
||||
},
|
||||
status: 'ACTIVE'
|
||||
},
|
||||
orderBy: {
|
||||
regdate: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
const files = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE target_objid LIKE $1
|
||||
AND status = 'ACTIVE'
|
||||
ORDER BY regdate DESC`,
|
||||
[`screen_files:${screenId}:%`]
|
||||
);
|
||||
|
||||
// 컴포넌트별로 파일 그룹화
|
||||
const componentFiles: { [componentId: string]: any[] } = {};
|
||||
|
||||
files.forEach(file => {
|
||||
|
||||
files.forEach((file) => {
|
||||
// target_objid 형식: screen_files:screenId:componentId:fieldName
|
||||
const targetParts = file.target_objid?.split(':') || [];
|
||||
const targetParts = file.target_objid?.split(":") || [];
|
||||
if (targetParts.length >= 3) {
|
||||
const componentId = targetParts[2];
|
||||
|
||||
|
||||
if (!componentFiles[componentId]) {
|
||||
componentFiles[componentId] = [];
|
||||
}
|
||||
|
||||
|
||||
componentFiles[componentId].push({
|
||||
objid: file.objid.toString(),
|
||||
savedFileName: file.saved_file_name,
|
||||
@@ -58,26 +52,27 @@ export const getScreenComponentFiles = async (
|
||||
parentTargetObjid: file.parent_target_objid,
|
||||
writer: file.writer,
|
||||
regdate: file.regdate?.toISOString(),
|
||||
status: file.status
|
||||
status: file.status,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`화면 컴포넌트 파일 조회 완료: ${Object.keys(componentFiles).length}개 컴포넌트, 총 ${files.length}개 파일`);
|
||||
logger.info(
|
||||
`화면 컴포넌트 파일 조회 완료: ${Object.keys(componentFiles).length}개 컴포넌트, 총 ${files.length}개 파일`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
componentFiles: componentFiles,
|
||||
totalFiles: files.length,
|
||||
componentCount: Object.keys(componentFiles).length
|
||||
componentCount: Object.keys(componentFiles).length,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('화면 컴포넌트 파일 조회 오류:', error);
|
||||
logger.error("화면 컴포넌트 파일 조회 오류:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '화면 컴포넌트 파일 조회 중 오류가 발생했습니다.',
|
||||
error: error instanceof Error ? error.message : '알 수 없는 오류'
|
||||
message: "화면 컴포넌트 파일 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -91,25 +86,23 @@ export const getComponentFiles = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const { screenId, componentId } = req.params;
|
||||
|
||||
logger.info(`컴포넌트 파일 조회: screenId=${screenId}, componentId=${componentId}`);
|
||||
|
||||
logger.info(
|
||||
`컴포넌트 파일 조회: screenId=${screenId}, componentId=${componentId}`
|
||||
);
|
||||
|
||||
// target_objid 패턴: screen_files:screenId:componentId:*
|
||||
const targetObjidPattern = `screen_files:${screenId}:${componentId}:`;
|
||||
|
||||
const files = await prisma.attach_file_info.findMany({
|
||||
where: {
|
||||
target_objid: {
|
||||
startsWith: targetObjidPattern
|
||||
},
|
||||
status: 'ACTIVE'
|
||||
},
|
||||
orderBy: {
|
||||
regdate: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
const fileList = files.map(file => ({
|
||||
const files = await query<any>(
|
||||
`SELECT * FROM attach_file_info
|
||||
WHERE target_objid LIKE $1
|
||||
AND status = 'ACTIVE'
|
||||
ORDER BY regdate DESC`,
|
||||
[`${targetObjidPattern}%`]
|
||||
);
|
||||
|
||||
const fileList = files.map((file) => ({
|
||||
objid: file.objid.toString(),
|
||||
savedFileName: file.saved_file_name,
|
||||
realFileName: file.real_file_name,
|
||||
@@ -122,7 +115,7 @@ export const getComponentFiles = async (
|
||||
parentTargetObjid: file.parent_target_objid,
|
||||
writer: file.writer,
|
||||
regdate: file.regdate?.toISOString(),
|
||||
status: file.status
|
||||
status: file.status,
|
||||
}));
|
||||
|
||||
logger.info(`컴포넌트 파일 조회 완료: ${fileList.length}개 파일`);
|
||||
@@ -131,15 +124,14 @@ export const getComponentFiles = async (
|
||||
success: true,
|
||||
files: fileList,
|
||||
componentId: componentId,
|
||||
screenId: screenId
|
||||
screenId: screenId,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('컴포넌트 파일 조회 오류:', error);
|
||||
logger.error("컴포넌트 파일 조회 오류:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '컴포넌트 파일 조회 중 오류가 발생했습니다.',
|
||||
error: error instanceof Error ? error.message : '알 수 없는 오류'
|
||||
message: "컴포넌트 파일 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user