단순 키 연결 구현 시 증계 테입르에 레코드 생성 구현

This commit is contained in:
2025-09-08 18:18:47 +09:00
parent 6b6c62f3b7
commit ac03f311b0
7 changed files with 269 additions and 86 deletions

View File

@@ -5139,14 +5139,10 @@ model data_relationship_bridge {
// 소스 테이블 정보
from_table_name String @db.VarChar(100)
from_column_name String @db.VarChar(100)
from_key_value String? @db.VarChar(500) // 실제 연결 키 값
from_record_id String? @db.VarChar(100) // 소스 레코드의 Primary Key
// 타겟 테이블 정보
to_table_name String @db.VarChar(100)
to_column_name String @db.VarChar(100)
to_key_value String? @db.VarChar(500) // 실제 연결 키 값
to_record_id String? @db.VarChar(100) // 타겟 레코드의 Primary Key
// 메타데이터
connection_type String @db.VarChar(20) // 'simple-key', 'data-save', 'external-call'
@@ -5164,12 +5160,12 @@ model data_relationship_bridge {
relationship table_relationships @relation(fields: [relationship_id], references: [relationship_id], onDelete: Cascade)
@@index([relationship_id], map: "idx_data_bridge_relationship")
@@index([from_table_name, from_key_value], map: "idx_data_bridge_from_table")
@@index([to_table_name, to_key_value], map: "idx_data_bridge_to_table")
@@index([from_table_name], map: "idx_data_bridge_from_table")
@@index([to_table_name], map: "idx_data_bridge_to_table")
@@index([company_code], map: "idx_data_bridge_company")
@@index([is_active], map: "idx_data_bridge_active")
@@index([connection_type], map: "idx_data_bridge_connection_type")
@@index([from_table_name, from_column_name, from_key_value], map: "idx_data_bridge_from_lookup")
@@index([to_table_name, to_column_name, to_key_value], map: "idx_data_bridge_to_lookup")
@@index([from_table_name, from_column_name], map: "idx_data_bridge_from_lookup")
@@index([to_table_name, to_column_name], map: "idx_data_bridge_to_lookup")
@@index([company_code, is_active], map: "idx_data_bridge_company_active")
}

View File

@@ -366,12 +366,8 @@ export async function createDataLink(
relationshipId,
fromTableName,
fromColumnName,
fromKeyValue,
fromRecordId,
toTableName,
toColumnName,
toKeyValue,
toRecordId,
connectionType,
bridgeData,
} = req.body;
@@ -381,10 +377,8 @@ export async function createDataLink(
!relationshipId ||
!fromTableName ||
!fromColumnName ||
!fromKeyValue ||
!toTableName ||
!toColumnName ||
!toKeyValue ||
!connectionType
) {
const response: ApiResponse<null> = {
@@ -393,7 +387,7 @@ export async function createDataLink(
error: {
code: "MISSING_REQUIRED_FIELDS",
details:
"필수 필드: relationshipId, fromTableName, fromColumnName, fromKeyValue, toTableName, toColumnName, toKeyValue, connectionType",
"필수 필드: relationshipId, fromTableName, fromColumnName, toTableName, toColumnName, connectionType",
},
};
res.status(400).json(response);
@@ -409,12 +403,8 @@ export async function createDataLink(
relationshipId,
fromTableName,
fromColumnName,
fromKeyValue,
fromRecordId,
toTableName,
toColumnName,
toKeyValue,
toRecordId,
connectionType,
companyCode,
bridgeData,
@@ -551,3 +541,70 @@ export async function deleteDataLink(
res.status(500).json(response);
}
}
// ==================== 테이블 데이터 조회 ====================
/**
* 테이블 실제 데이터 조회 (페이징)
* GET /api/dataflow/table-data/:tableName
*/
export async function getTableData(req: Request, res: Response): Promise<void> {
try {
const { tableName } = req.params;
const {
page = "1",
limit = "10",
search = "",
searchColumn = "",
} = req.query;
if (!tableName) {
const response: ApiResponse<null> = {
success: false,
message: "테이블명이 필요합니다.",
error: {
code: "MISSING_TABLE_NAME",
details: "테이블명을 제공해주세요.",
},
};
res.status(400).json(response);
return;
}
const pageNum = parseInt(page as string) || 1;
const limitNum = parseInt(limit as string) || 10;
const userInfo = (req as any).user;
const companyCode = userInfo?.company_code || "*";
const dataflowService = new DataflowService();
const result = await dataflowService.getTableData(
tableName,
pageNum,
limitNum,
search as string,
searchColumn as string,
companyCode
);
const response: ApiResponse<typeof result> = {
success: true,
message: "테이블 데이터를 성공적으로 조회했습니다.",
data: result,
};
res.status(200).json(response);
} catch (error) {
logger.error("테이블 데이터 조회 중 오류 발생:", error);
const response: ApiResponse<null> = {
success: false,
message: "테이블 데이터 조회 중 오류가 발생했습니다.",
error: {
code: "TABLE_DATA_GET_ERROR",
details: error instanceof Error ? error.message : "Unknown error",
},
};
res.status(500).json(response);
}
}

View File

@@ -9,6 +9,7 @@ import {
createDataLink,
getLinkedDataByRelationship,
deleteDataLink,
getTableData,
} from "../controllers/dataflowController";
const router = express.Router();
@@ -69,4 +70,12 @@ router.get(
*/
router.delete("/data-links/:bridgeId", deleteDataLink);
// ==================== 테이블 데이터 조회 라우트 ====================
/**
* 테이블 실제 데이터 조회
* GET /api/dataflow/table-data/:tableName
*/
router.get("/table-data/:tableName", getTableData);
export default router;

View File

@@ -56,27 +56,63 @@ export class DataflowService {
);
}
// 새 관계 생성
const relationship = await prisma.table_relationships.create({
data: {
relationship_name: data.relationshipName,
from_table_name: data.fromTableName,
from_column_name: data.fromColumnName,
to_table_name: data.toTableName,
to_column_name: data.toColumnName,
relationship_type: data.relationshipType,
connection_type: data.connectionType,
company_code: data.companyCode,
settings: data.settings,
created_by: data.createdBy,
updated_by: data.createdBy,
},
// 트랜잭션으로 관계 생성과 단순 키값 연결 처리
const result = await prisma.$transaction(async (tx) => {
// 1. 새 관계 생성
const relationship = await tx.table_relationships.create({
data: {
relationship_name: data.relationshipName,
from_table_name: data.fromTableName,
from_column_name: data.fromColumnName,
to_table_name: data.toTableName,
to_column_name: data.toColumnName,
relationship_type: data.relationshipType,
connection_type: data.connectionType,
company_code: data.companyCode,
settings: data.settings,
created_by: data.createdBy,
updated_by: data.createdBy,
},
});
// 2. 단순 키값 연결인 경우 data_relationship_bridge에도 기본 레코드 생성
if (data.connectionType === "simple-key") {
logger.info(
`단순 키값 연결이므로 data_relationship_bridge에 기본 연결 레코드 생성 - 관계ID: ${relationship.relationship_id}`
);
await tx.data_relationship_bridge.create({
data: {
relationship_id: relationship.relationship_id,
from_table_name: data.fromTableName,
from_column_name: data.fromColumnName,
to_table_name: data.toTableName,
to_column_name: data.toColumnName,
connection_type: data.connectionType,
company_code: data.companyCode,
bridge_data: {
autoCreated: true,
createdAt: new Date().toISOString(),
notes: "단순 키값 연결 - 테이블과 컬럼 관계만 정의",
connectionInfo: `${data.fromTableName}.${data.fromColumnName}${data.toTableName}.${data.toColumnName}`,
settings: data.settings,
},
created_by: data.createdBy,
},
});
logger.info(
`단순 키값 연결 기본 레코드 생성 완료 - 관계ID: ${relationship.relationship_id}`
);
}
return relationship;
});
logger.info(
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}`
`DataflowService: 테이블 관계 생성 완료 - ID: ${result.relationship_id}`
);
return relationship;
return result;
} catch (error) {
logger.error("DataflowService: 테이블 관계 생성 실패", error);
throw error;
@@ -386,12 +422,8 @@ export class DataflowService {
relationshipId: number;
fromTableName: string;
fromColumnName: string;
fromKeyValue: string;
fromRecordId?: string;
toTableName: string;
toColumnName: string;
toKeyValue: string;
toRecordId?: string;
connectionType: string;
companyCode: string;
bridgeData?: any;
@@ -407,12 +439,8 @@ export class DataflowService {
relationship_id: linkData.relationshipId,
from_table_name: linkData.fromTableName,
from_column_name: linkData.fromColumnName,
from_key_value: linkData.fromKeyValue,
from_record_id: linkData.fromRecordId,
to_table_name: linkData.toTableName,
to_column_name: linkData.toColumnName,
to_key_value: linkData.toKeyValue,
to_record_id: linkData.toRecordId,
connection_type: linkData.connectionType,
company_code: linkData.companyCode,
bridge_data: linkData.bridgeData || {},
@@ -494,13 +522,7 @@ export class DataflowService {
is_active: "Y",
};
// 특정 키 값으로 필터링
if (keyValue) {
whereCondition.OR = [
{ from_table_name: tableName, from_key_value: keyValue },
{ to_table_name: tableName, to_key_value: keyValue },
];
}
// keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨)
// 회사코드 필터링
if (companyCode && companyCode !== "*") {
@@ -537,10 +559,6 @@ export class DataflowService {
async updateDataLink(
bridgeId: number,
updateData: {
fromKeyValue?: string;
fromRecordId?: string;
toKeyValue?: string;
toRecordId?: string;
bridgeData?: any;
updatedBy: string;
},
@@ -662,4 +680,89 @@ export class DataflowService {
throw error;
}
}
// ==================== 테이블 데이터 조회 ====================
/**
* 테이블 실제 데이터 조회 (페이징)
*/
async getTableData(
tableName: string,
page: number = 1,
limit: number = 10,
search: string = "",
searchColumn: string = "",
companyCode: string = "*"
) {
try {
logger.info(`DataflowService: 테이블 데이터 조회 시작 - ${tableName}`);
// 테이블 존재 여부 확인 (정보 스키마 사용)
const tableExists = await prisma.$queryRaw`
SELECT table_name
FROM information_schema.tables
WHERE table_name = ${tableName.toLowerCase()}
AND table_schema = 'public'
`;
if (
!tableExists ||
(Array.isArray(tableExists) && tableExists.length === 0)
) {
throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`);
}
// 전체 데이터 개수 조회
let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`;
let dataQuery = `SELECT * FROM "${tableName}"`;
// 검색 조건 추가
if (search && searchColumn) {
const whereCondition = `WHERE "${searchColumn}" ILIKE '%${search}%'`;
totalCountQuery += ` ${whereCondition}`;
dataQuery += ` ${whereCondition}`;
}
// 페이징 처리
const offset = (page - 1) * limit;
dataQuery += ` ORDER BY 1 LIMIT ${limit} OFFSET ${offset}`;
// 실제 쿼리 실행
const [totalResult, dataResult] = await Promise.all([
prisma.$queryRawUnsafe(totalCountQuery),
prisma.$queryRawUnsafe(dataQuery),
]);
const total =
Array.isArray(totalResult) && totalResult.length > 0
? Number((totalResult[0] as any).total)
: 0;
const data = Array.isArray(dataResult) ? dataResult : [];
const result = {
data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page < Math.ceil(total / limit),
hasPrev: page > 1,
},
};
logger.info(
`DataflowService: 테이블 데이터 조회 완료 - ${tableName}, 총 ${total}건 중 ${data.length}건 조회`
);
return result;
} catch (error) {
logger.error(
`DataflowService: 테이블 데이터 조회 실패 - ${tableName}`,
error
);
throw error;
}
}
}