fix: pool export 추가로 buttonActionStandardController 컴파일 에러 해결

문제:
- buttonActionStandardController에서 pool을 import하려 했으나
- db.ts에서 pool이 export되지 않아 컴파일 에러 발생

해결:
- db.ts에 'export { pool }' 추가
- pool 직접 접근이 필요한 경우를 위해 명시적 export

영향받는 파일:
- backend-node/src/database/db.ts
- backend-node/src/controllers/buttonActionStandardController.ts (사용)
This commit is contained in:
kjs 2025-10-01 14:37:33 +09:00
parent f2f0c33bad
commit fcf887ae76
2 changed files with 107 additions and 53 deletions

View File

@ -66,16 +66,19 @@ const storage = multer.diskStorage({
console.log("📁 파일명 처리:", {
originalname: file.originalname,
encoding: file.encoding,
mimetype: file.mimetype
mimetype: file.mimetype,
});
// UTF-8 인코딩 문제 해결: Buffer를 통한 올바른 디코딩
let decodedName;
try {
// 파일명이 깨진 경우 Buffer를 통해 올바르게 디코딩
const buffer = Buffer.from(file.originalname, 'latin1');
decodedName = buffer.toString('utf8');
console.log("📁 파일명 디코딩:", { original: file.originalname, decoded: decodedName });
const buffer = Buffer.from(file.originalname, "latin1");
decodedName = buffer.toString("utf8");
console.log("📁 파일명 디코딩:", {
original: file.originalname,
decoded: decodedName,
});
} catch (error) {
// 디코딩 실패 시 원본 사용
decodedName = file.originalname;
@ -85,16 +88,16 @@ const storage = multer.diskStorage({
// 한국어를 포함한 유니코드 문자 보존하면서 안전한 파일명 생성
// 위험한 문자만 제거: / \ : * ? " < > |
const sanitizedName = decodedName
.replace(/[\/\\:*?"<>|]/g, "_") // 파일시스템에서 금지된 문자만 치환
.replace(/\s+/g, "_") // 공백을 언더스코어로 치환
.replace(/_{2,}/g, "_"); // 연속된 언더스코어를 하나로 축약
.replace(/[\/\\:*?"<>|]/g, "_") // 파일시스템에서 금지된 문자만 치환
.replace(/\s+/g, "_") // 공백을 언더스코어로 치환
.replace(/_{2,}/g, "_"); // 연속된 언더스코어를 하나로 축약
const savedFileName = `${timestamp}_${sanitizedName}`;
console.log("📁 파일명 변환:", {
original: file.originalname,
sanitized: sanitizedName,
saved: savedFileName
saved: savedFileName,
});
cb(null, savedFileName);
@ -244,12 +247,18 @@ export const uploadFiles = async (
// 파일명 디코딩 (파일 저장 시와 동일한 로직)
let decodedOriginalName;
try {
const buffer = Buffer.from(file.originalname, 'latin1');
decodedOriginalName = buffer.toString('utf8');
console.log("💾 DB 저장용 파일명 디코딩:", { original: file.originalname, decoded: decodedOriginalName });
const buffer = Buffer.from(file.originalname, "latin1");
decodedOriginalName = buffer.toString("utf8");
console.log("💾 DB 저장용 파일명 디코딩:", {
original: file.originalname,
decoded: decodedOriginalName,
});
} catch (error) {
decodedOriginalName = file.originalname;
console.log("💾 DB 저장용 파일명 디코딩 실패, 원본 사용:", file.originalname);
console.log(
"💾 DB 저장용 파일명 디코딩 실패, 원본 사용:",
file.originalname
);
}
// 파일 확장자 추출
@ -293,8 +302,20 @@ export const uploadFiles = async (
) 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
objidValue,
finalTargetObjid,
file.filename,
decodedOriginalName,
docType,
docTypeName,
file.size,
fileExt,
fullFilePath,
companyCode,
writer,
new Date(),
"ACTIVE",
parentTargetObjid,
]
);
@ -486,7 +507,8 @@ export const getComponentFiles = async (
res: Response
): Promise<void> => {
try {
const { screenId, componentId, tableName, recordId, columnName } = req.query;
const { screenId, componentId, tableName, recordId, columnName } =
req.query;
console.log("📂 [getComponentFiles] API 호출:", {
screenId,
@ -494,7 +516,7 @@ export const getComponentFiles = async (
tableName,
recordId,
columnName,
user: req.user?.userId
user: req.user?.userId,
});
if (!screenId || !componentId) {
@ -507,8 +529,10 @@ export const getComponentFiles = async (
}
// 1. 템플릿 파일 조회 (화면 설계 시 업로드한 파일들)
const templateTargetObjid = `screen_files:${screenId}:${componentId}:${columnName || 'field_1'}`;
console.log("🔍 [getComponentFiles] 템플릿 파일 조회:", { templateTargetObjid });
const templateTargetObjid = `screen_files:${screenId}:${componentId}:${columnName || "field_1"}`;
console.log("🔍 [getComponentFiles] 템플릿 파일 조회:", {
templateTargetObjid,
});
// 모든 파일 조회해서 실제 저장된 target_objid 패턴 확인
const allFiles = await query<any>(
@ -519,7 +543,13 @@ export const getComponentFiles = async (
LIMIT 10`,
["ACTIVE"]
);
console.log("🗂️ [getComponentFiles] 최근 저장된 파일들의 target_objid:", allFiles.map(f => ({ target_objid: f.target_objid, name: f.real_file_name })));
console.log(
"🗂️ [getComponentFiles] 최근 저장된 파일들의 target_objid:",
allFiles.map((f) => ({
target_objid: f.target_objid,
name: f.real_file_name,
}))
);
const templateFiles = await query<any>(
`SELECT * FROM attach_file_info
@ -528,7 +558,10 @@ export const getComponentFiles = async (
[templateTargetObjid, "ACTIVE"]
);
console.log("📁 [getComponentFiles] 템플릿 파일 결과:", templateFiles.length);
console.log(
"📁 [getComponentFiles] 템플릿 파일 결과:",
templateFiles.length
);
// 2. 데이터 파일 조회 (실제 레코드와 연결된 파일들)
let dataFiles: any[] = [];
@ -560,13 +593,18 @@ export const getComponentFiles = async (
isTemplate, // 템플릿 파일 여부 표시
});
const formattedTemplateFiles = templateFiles.map(file => formatFileInfo(file, true));
const formattedDataFiles = dataFiles.map(file => formatFileInfo(file, false));
const formattedTemplateFiles = templateFiles.map((file) =>
formatFileInfo(file, true)
);
const formattedDataFiles = dataFiles.map((file) =>
formatFileInfo(file, false)
);
// 3. 전체 파일 목록 (데이터 파일 우선, 없으면 템플릿 파일 표시)
const totalFiles = formattedDataFiles.length > 0
? formattedDataFiles
: formattedTemplateFiles;
const totalFiles =
formattedDataFiles.length > 0
? formattedDataFiles
: formattedTemplateFiles;
res.json({
success: true,
@ -578,9 +616,10 @@ export const getComponentFiles = async (
dataCount: formattedDataFiles.length,
totalCount: totalFiles.length,
templateTargetObjid,
dataTargetObjid: tableName && recordId && columnName
? `${tableName}:${recordId}:${columnName}`
: null,
dataTargetObjid:
tableName && recordId && columnName
? `${tableName}:${recordId}:${columnName}`
: null,
},
});
} catch (error) {
@ -648,7 +687,7 @@ export const previewFile = async (
fileName: fileName,
companyUploadDir: companyUploadDir,
finalFilePath: filePath,
fileExists: fs.existsSync(filePath)
fileExists: fs.existsSync(filePath),
});
if (!fs.existsSync(filePath)) {
@ -768,7 +807,7 @@ export const downloadFile = async (
fileName: fileName,
companyUploadDir: companyUploadDir,
finalFilePath: filePath,
fileExists: fs.existsSync(filePath)
fileExists: fs.existsSync(filePath),
});
if (!fs.existsSync(filePath)) {
@ -803,7 +842,10 @@ export const downloadFile = async (
/**
* Google Docs Viewer용
*/
export const generateTempToken = async (req: AuthenticatedRequest, res: Response) => {
export const generateTempToken = async (
req: AuthenticatedRequest,
res: Response
) => {
try {
const { objid } = req.params;
@ -923,7 +965,10 @@ export const getFileByToken = async (req: Request, res: Response) => {
if (filePathParts.length >= 6) {
dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
}
const companyUploadDir = getCompanyUploadDir(companyCode, dateFolder || undefined);
const companyUploadDir = getCompanyUploadDir(
companyCode,
dateFolder || undefined
);
const filePath = path.join(companyUploadDir, fileName);
// 파일 존재 확인
@ -942,11 +987,14 @@ export const getFileByToken = async (req: Request, res: Response) => {
const mimeTypes: { [key: string]: string } = {
".pdf": "application/pdf",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".docx":
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlsx":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".pptx":
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
@ -960,7 +1008,10 @@ export const getFileByToken = async (req: Request, res: Response) => {
// 파일 헤더 설정
res.setHeader("Content-Type", contentType);
res.setHeader("Content-Disposition", `inline; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`);
res.setHeader(
"Content-Disposition",
`inline; filename="${encodeURIComponent(fileRecord.real_file_name!)}"`
);
res.setHeader("Cache-Control", "public, max-age=300"); // 5분 캐시
// 파일 스트림 전송

View File

@ -259,6 +259,9 @@ export function getPoolStatus() {
};
}
// Pool 직접 접근 (필요한 경우)
export { pool };
// 기본 익스포트 (편의성)
export default {
query,