diff --git a/backend-node/src/routes/dataRoutes.ts b/backend-node/src/routes/dataRoutes.ts index c696d5de..f87aa5d6 100644 --- a/backend-node/src/routes/dataRoutes.ts +++ b/backend-node/src/routes/dataRoutes.ts @@ -14,8 +14,17 @@ router.get( authenticateToken, async (req: AuthenticatedRequest, res) => { try { - const { leftTable, rightTable, leftColumn, rightColumn, leftValue, dataFilter, enableEntityJoin, displayColumns, deduplication } = - req.query; + const { + leftTable, + rightTable, + leftColumn, + rightColumn, + leftValue, + dataFilter, + enableEntityJoin, + displayColumns, + deduplication, + } = req.query; // 입력값 검증 if (!leftTable || !rightTable || !leftColumn || !rightColumn) { @@ -38,7 +47,9 @@ router.get( } // 🆕 enableEntityJoin 파싱 - const enableEntityJoinFlag = enableEntityJoin === "true" || enableEntityJoin === true; + const enableEntityJoinFlag = + enableEntityJoin === "true" || + (typeof enableEntityJoin === "boolean" && enableEntityJoin); // SQL 인젝션 방지를 위한 검증 const tables = [leftTable as string, rightTable as string]; @@ -68,7 +79,9 @@ router.get( const userCompany = req.user?.companyCode; // displayColumns 파싱 (item_info.item_name 등) - let parsedDisplayColumns: Array<{ name: string; label?: string }> | undefined; + let parsedDisplayColumns: + | Array<{ name: string; label?: string }> + | undefined; if (displayColumns) { try { parsedDisplayColumns = JSON.parse(displayColumns as string); @@ -78,12 +91,14 @@ router.get( } // 🆕 deduplication 파싱 - let parsedDeduplication: { - enabled: boolean; - groupByColumn: string; - keepStrategy: "latest" | "earliest" | "base_price" | "current_date"; - sortColumn?: string; - } | undefined; + let parsedDeduplication: + | { + enabled: boolean; + groupByColumn: string; + keepStrategy: "latest" | "earliest" | "base_price" | "current_date"; + sortColumn?: string; + } + | undefined; if (deduplication) { try { parsedDeduplication = JSON.parse(deduplication as string); @@ -340,30 +355,37 @@ router.get( } const { enableEntityJoin, groupByColumns } = req.query; - const enableEntityJoinFlag = enableEntityJoin === "true" || enableEntityJoin === true; - + const enableEntityJoinFlag = + enableEntityJoin === "true" || + (typeof enableEntityJoin === "boolean" && enableEntityJoin); + // groupByColumns 파싱 (JSON 문자열 또는 쉼표 구분) let groupByColumnsArray: string[] = []; if (groupByColumns) { try { if (typeof groupByColumns === "string") { // JSON 형식이면 파싱, 아니면 쉼표로 분리 - groupByColumnsArray = groupByColumns.startsWith("[") - ? JSON.parse(groupByColumns) - : groupByColumns.split(",").map(c => c.trim()); + groupByColumnsArray = groupByColumns.startsWith("[") + ? JSON.parse(groupByColumns) + : groupByColumns.split(",").map((c) => c.trim()); } } catch (error) { console.warn("groupByColumns 파싱 실패:", error); } } - console.log(`🔍 레코드 상세 조회: ${tableName}/${id}`, { + console.log(`🔍 레코드 상세 조회: ${tableName}/${id}`, { enableEntityJoin: enableEntityJoinFlag, - groupByColumns: groupByColumnsArray + groupByColumns: groupByColumnsArray, }); // 레코드 상세 조회 (Entity Join 옵션 + 그룹핑 옵션 포함) - const result = await dataService.getRecordDetail(tableName, id, enableEntityJoinFlag, groupByColumnsArray); + const result = await dataService.getRecordDetail( + tableName, + id, + enableEntityJoinFlag, + groupByColumnsArray + ); if (!result.success) { return res.status(400).json(result); @@ -396,7 +418,7 @@ router.get( /** * 그룹화된 데이터 UPSERT API * POST /api/data/upsert-grouped - * + * * 요청 본문: * { * tableName: string, @@ -415,7 +437,8 @@ router.post( if (!tableName || !parentKeys || !records || !Array.isArray(records)) { return res.status(400).json({ success: false, - message: "필수 파라미터가 누락되었습니다 (tableName, parentKeys, records).", + message: + "필수 파라미터가 누락되었습니다 (tableName, parentKeys, records).", error: "MISSING_PARAMETERS", }); } @@ -450,17 +473,17 @@ router.post( } console.log(`✅ 그룹화된 데이터 UPSERT 성공: ${tableName}`, { - inserted: result.inserted, - updated: result.updated, - deleted: result.deleted, + inserted: result.data?.inserted || 0, + updated: result.data?.updated || 0, + deleted: result.data?.deleted || 0, }); return res.json({ success: true, message: "데이터가 저장되었습니다.", - inserted: result.inserted, - updated: result.updated, - deleted: result.deleted, + inserted: result.data?.inserted || 0, + updated: result.data?.updated || 0, + deleted: result.data?.deleted || 0, }); } catch (error) { console.error("그룹화된 데이터 UPSERT 오류:", error); @@ -506,16 +529,22 @@ router.post( // company_code와 company_name 자동 추가 (멀티테넌시) const enrichedData = { ...data }; - + // 테이블에 company_code 컬럼이 있는지 확인하고 자동으로 추가 - const hasCompanyCode = await dataService.checkColumnExists(tableName, "company_code"); + const hasCompanyCode = await dataService.checkColumnExists( + tableName, + "company_code" + ); if (hasCompanyCode && req.user?.companyCode) { enrichedData.company_code = req.user.companyCode; console.log(`🏢 company_code 자동 추가: ${req.user.companyCode}`); } - + // 테이블에 company_name 컬럼이 있는지 확인하고 자동으로 추가 - const hasCompanyName = await dataService.checkColumnExists(tableName, "company_name"); + const hasCompanyName = await dataService.checkColumnExists( + tableName, + "company_name" + ); if (hasCompanyName && req.user?.companyName) { enrichedData.company_name = req.user.companyName; console.log(`🏢 company_name 자동 추가: ${req.user.companyName}`); @@ -679,7 +708,10 @@ router.post( console.log(`🗑️ 그룹 삭제:`, { tableName, filterConditions }); - const result = await dataService.deleteGroupRecords(tableName, filterConditions); + const result = await dataService.deleteGroupRecords( + tableName, + filterConditions + ); if (!result.success) { return res.status(400).json(result); diff --git a/backend-node/src/types/express.d.ts b/backend-node/src/types/express.d.ts new file mode 100644 index 00000000..8f456992 --- /dev/null +++ b/backend-node/src/types/express.d.ts @@ -0,0 +1,13 @@ +import "express"; + +declare module "express-serve-static-core" { + interface Request { + user?: { + userId: string; + companyCode: string; + userType: string; + locale?: string; + }; + } +} +