Compare commits

...

2 Commits

Author SHA1 Message Date
dohyeons
53bf3877ac Merge remote-tracking branch 'upstream/main' 2025-11-21 03:41:02 +09:00
dohyeons
fda7614d48 빌드에러해결 2025-11-21 03:40:41 +09:00
2 changed files with 76 additions and 31 deletions

View File

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

13
backend-node/src/types/express.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import "express";
declare module "express-serve-static-core" {
interface Request {
user?: {
userId: string;
companyCode: string;
userType: string;
locale?: string;
};
}
}