1173 lines
34 KiB
TypeScript
1173 lines
34 KiB
TypeScript
import { query, queryOne, transaction } from "../database/db";
|
|
import { logger } from "../utils/logger";
|
|
|
|
// 테이블 관계 생성 데이터 타입
|
|
interface CreateTableRelationshipData {
|
|
diagramId?: number; // 기존 관계도에 추가하는 경우
|
|
relationshipName: string;
|
|
fromTableName: string;
|
|
fromColumnName: string;
|
|
toTableName: string;
|
|
toColumnName: string;
|
|
relationshipType: string;
|
|
connectionType: string;
|
|
companyCode: string;
|
|
settings: any;
|
|
createdBy: string;
|
|
}
|
|
|
|
// 테이블 관계 수정 데이터 타입
|
|
interface UpdateTableRelationshipData {
|
|
relationshipName?: string;
|
|
fromTableName?: string;
|
|
fromColumnName?: string;
|
|
toTableName?: string;
|
|
toColumnName?: string;
|
|
relationshipType?: string;
|
|
connectionType?: string;
|
|
settings?: any;
|
|
updatedBy: string;
|
|
}
|
|
|
|
export class DataflowService {
|
|
/**
|
|
* 테이블 관계 생성
|
|
*/
|
|
async createTableRelationship(data: CreateTableRelationshipData) {
|
|
try {
|
|
logger.info("DataflowService: 테이블 관계 생성 시작", data);
|
|
|
|
// diagram_id 결정 로직
|
|
let diagramId = data.diagramId;
|
|
|
|
if (!diagramId) {
|
|
// 새로운 관계도인 경우, 새로운 diagram_id 생성
|
|
// 현재 최대 diagram_id + 1
|
|
const maxDiagramId = await queryOne<{ diagram_id: number }>(
|
|
`SELECT diagram_id FROM table_relationships
|
|
WHERE company_code = $1
|
|
ORDER BY diagram_id DESC
|
|
LIMIT 1`,
|
|
[data.companyCode]
|
|
);
|
|
|
|
diagramId = (maxDiagramId?.diagram_id || 0) + 1;
|
|
}
|
|
|
|
// 중복 관계 확인 (같은 diagram_id 내에서)
|
|
const existingRelationship = await queryOne(
|
|
`SELECT * FROM table_relationships
|
|
WHERE diagram_id = $1
|
|
AND from_table_name = $2
|
|
AND from_column_name = $3
|
|
AND to_table_name = $4
|
|
AND to_column_name = $5
|
|
AND company_code = $6
|
|
AND is_active = 'Y'`,
|
|
[
|
|
diagramId,
|
|
data.fromTableName,
|
|
data.fromColumnName,
|
|
data.toTableName,
|
|
data.toColumnName,
|
|
data.companyCode,
|
|
]
|
|
);
|
|
|
|
if (existingRelationship) {
|
|
throw new Error(
|
|
`이미 존재하는 관계입니다: ${data.fromTableName}.${data.fromColumnName} → ${data.toTableName}.${data.toColumnName}`
|
|
);
|
|
}
|
|
|
|
// 새 관계 생성
|
|
const relationship = await queryOne(
|
|
`INSERT INTO table_relationships (
|
|
diagram_id, relationship_name, from_table_name, from_column_name,
|
|
to_table_name, to_column_name, relationship_type, connection_type,
|
|
company_code, settings, created_by, updated_by, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), now())
|
|
RETURNING *`,
|
|
[
|
|
diagramId,
|
|
data.relationshipName,
|
|
data.fromTableName,
|
|
data.fromColumnName,
|
|
data.toTableName,
|
|
data.toColumnName,
|
|
data.relationshipType,
|
|
data.connectionType,
|
|
data.companyCode,
|
|
JSON.stringify(data.settings),
|
|
data.createdBy,
|
|
data.createdBy,
|
|
]
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}, Diagram ID: ${relationship.diagram_id}`
|
|
);
|
|
return relationship;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블 관계 생성 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 회사별 테이블 관계 목록 조회
|
|
*/
|
|
async getTableRelationships(companyCode: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 목록 조회 시작 - 회사코드: ${companyCode}`
|
|
);
|
|
|
|
// 관리자는 모든 회사의 관계를 볼 수 있음
|
|
let queryText = `SELECT * FROM table_relationships WHERE is_active = 'Y'`;
|
|
const params: any[] = [];
|
|
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $1`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` ORDER BY created_date DESC`;
|
|
const relationships = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 목록 조회 완료 - ${relationships.length}개`
|
|
);
|
|
return relationships;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블 관계 목록 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 테이블 관계 조회
|
|
*/
|
|
async getTableRelationship(relationshipId: number, companyCode: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 조회 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}`
|
|
);
|
|
|
|
let queryText = `SELECT * FROM table_relationships WHERE relationship_id = $1 AND is_active = 'Y'`;
|
|
const params: any[] = [relationshipId];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $2`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
const relationship = await queryOne(queryText, params);
|
|
|
|
if (relationship) {
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 조회 완료 - ID: ${relationshipId}`
|
|
);
|
|
} else {
|
|
logger.warn(
|
|
`DataflowService: 테이블 관계를 찾을 수 없음 - ID: ${relationshipId}`
|
|
);
|
|
}
|
|
|
|
return relationship;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블 관계 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블 관계 수정
|
|
*/
|
|
async updateTableRelationship(
|
|
relationshipId: number,
|
|
updateData: UpdateTableRelationshipData,
|
|
companyCode: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 수정 시작 - ID: ${relationshipId}`,
|
|
updateData
|
|
);
|
|
|
|
// 기존 관계 확인
|
|
const existingRelationship = await this.getTableRelationship(
|
|
relationshipId,
|
|
companyCode
|
|
);
|
|
if (!existingRelationship) {
|
|
return null;
|
|
}
|
|
|
|
// 관계 수정
|
|
const updates: string[] = [];
|
|
const params: any[] = [];
|
|
let paramIndex = 1;
|
|
|
|
if (updateData.relationshipName !== undefined) {
|
|
updates.push(`relationship_name = $${paramIndex++}`);
|
|
params.push(updateData.relationshipName);
|
|
}
|
|
if (updateData.fromTableName !== undefined) {
|
|
updates.push(`from_table_name = $${paramIndex++}`);
|
|
params.push(updateData.fromTableName);
|
|
}
|
|
if (updateData.fromColumnName !== undefined) {
|
|
updates.push(`from_column_name = $${paramIndex++}`);
|
|
params.push(updateData.fromColumnName);
|
|
}
|
|
if (updateData.toTableName !== undefined) {
|
|
updates.push(`to_table_name = $${paramIndex++}`);
|
|
params.push(updateData.toTableName);
|
|
}
|
|
if (updateData.toColumnName !== undefined) {
|
|
updates.push(`to_column_name = $${paramIndex++}`);
|
|
params.push(updateData.toColumnName);
|
|
}
|
|
if (updateData.relationshipType !== undefined) {
|
|
updates.push(`relationship_type = $${paramIndex++}`);
|
|
params.push(updateData.relationshipType);
|
|
}
|
|
if (updateData.connectionType !== undefined) {
|
|
updates.push(`connection_type = $${paramIndex++}`);
|
|
params.push(updateData.connectionType);
|
|
}
|
|
if (updateData.settings !== undefined) {
|
|
updates.push(`settings = $${paramIndex++}`);
|
|
params.push(JSON.stringify(updateData.settings));
|
|
}
|
|
updates.push(`updated_by = $${paramIndex++}`);
|
|
params.push(updateData.updatedBy);
|
|
updates.push(`updated_date = now()`);
|
|
|
|
params.push(relationshipId);
|
|
|
|
const relationship = await queryOne(
|
|
`UPDATE table_relationships
|
|
SET ${updates.join(", ")}
|
|
WHERE relationship_id = $${paramIndex}
|
|
RETURNING *`,
|
|
params
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 수정 완료 - ID: ${relationshipId}`
|
|
);
|
|
return relationship;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블 관계 수정 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블 관계 삭제 (소프트 삭제)
|
|
*/
|
|
async deleteTableRelationship(relationshipId: number, companyCode: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 삭제 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}`
|
|
);
|
|
|
|
// 기존 관계 확인
|
|
const existingRelationship = await this.getTableRelationship(
|
|
relationshipId,
|
|
companyCode
|
|
);
|
|
if (!existingRelationship) {
|
|
return false;
|
|
}
|
|
|
|
// 소프트 삭제 (is_active = 'N')
|
|
await query(
|
|
`UPDATE table_relationships
|
|
SET is_active = 'N', updated_date = now()
|
|
WHERE relationship_id = $1`,
|
|
[relationshipId]
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블 관계 삭제 완료 - ID: ${relationshipId}`
|
|
);
|
|
return true;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블 관계 삭제 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 테이블과 관련된 모든 관계 조회
|
|
*/
|
|
async getRelationshipsByTable(tableName: string, companyCode: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블별 관계 조회 시작 - 테이블: ${tableName}, 회사코드: ${companyCode}`
|
|
);
|
|
|
|
let queryText = `
|
|
SELECT * FROM table_relationships
|
|
WHERE (from_table_name = $1 OR to_table_name = $1)
|
|
AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [tableName];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $2`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` ORDER BY created_date DESC`;
|
|
const relationships = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블별 관계 조회 완료 - ${relationships.length}개`
|
|
);
|
|
return relationships;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블별 관계 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 연결 타입별 관계 조회
|
|
*/
|
|
async getRelationshipsByConnectionType(
|
|
connectionType: string,
|
|
companyCode: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 연결타입별 관계 조회 시작 - 타입: ${connectionType}, 회사코드: ${companyCode}`
|
|
);
|
|
|
|
let queryText = `
|
|
SELECT * FROM table_relationships
|
|
WHERE connection_type = $1 AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [connectionType];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $2`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` ORDER BY created_date DESC`;
|
|
const relationships = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 연결타입별 관계 조회 완료 - ${relationships.length}개`
|
|
);
|
|
return relationships;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 연결타입별 관계 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계 통계 조회
|
|
*/
|
|
async getRelationshipStats(companyCode: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 관계 통계 조회 시작 - 회사코드: ${companyCode}`
|
|
);
|
|
|
|
let whereClause = `WHERE is_active = 'Y'`;
|
|
const params: any[] = [];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
whereClause += ` AND company_code = $1`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
// 전체 관계 수
|
|
const totalCountResult = await queryOne<{ count: string }>(
|
|
`SELECT COUNT(*) as count FROM table_relationships ${whereClause}`,
|
|
params
|
|
);
|
|
const totalCount = parseInt(totalCountResult?.count || "0", 10);
|
|
|
|
// 관계 타입별 통계
|
|
const relationshipTypeStats = await query<{
|
|
relationship_type: string;
|
|
count: string;
|
|
}>(
|
|
`SELECT relationship_type, COUNT(*) as count
|
|
FROM table_relationships ${whereClause}
|
|
GROUP BY relationship_type`,
|
|
params
|
|
);
|
|
|
|
// 연결 타입별 통계
|
|
const connectionTypeStats = await query<{
|
|
connection_type: string;
|
|
count: string;
|
|
}>(
|
|
`SELECT connection_type, COUNT(*) as count
|
|
FROM table_relationships ${whereClause}
|
|
GROUP BY connection_type`,
|
|
params
|
|
);
|
|
|
|
const stats = {
|
|
totalCount,
|
|
relationshipTypeStats: relationshipTypeStats.map((stat) => ({
|
|
type: stat.relationship_type,
|
|
count: parseInt(stat.count, 10),
|
|
})),
|
|
connectionTypeStats: connectionTypeStats.map((stat) => ({
|
|
type: stat.connection_type,
|
|
count: parseInt(stat.count, 10),
|
|
})),
|
|
};
|
|
|
|
logger.info(`DataflowService: 관계 통계 조회 완료`, stats);
|
|
return stats;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 관계 통계 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// ==================== 데이터 중계 관리 ====================
|
|
|
|
/**
|
|
* 데이터 관계 연결 생성
|
|
*/
|
|
async createDataLink(linkData: {
|
|
relationshipId: number;
|
|
fromTableName: string;
|
|
fromColumnName: string;
|
|
toTableName: string;
|
|
toColumnName: string;
|
|
connectionType: string;
|
|
companyCode: string;
|
|
bridgeData?: any;
|
|
createdBy: string;
|
|
}) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 생성 시작 - 관계ID: ${linkData.relationshipId}`
|
|
);
|
|
|
|
const bridge = await queryOne(
|
|
`INSERT INTO data_relationship_bridge (
|
|
relationship_id, from_table_name, from_column_name, to_table_name,
|
|
to_column_name, connection_type, company_code, bridge_data,
|
|
created_by, created_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
|
|
RETURNING *`,
|
|
[
|
|
linkData.relationshipId,
|
|
linkData.fromTableName,
|
|
linkData.fromColumnName,
|
|
linkData.toTableName,
|
|
linkData.toColumnName,
|
|
linkData.connectionType,
|
|
linkData.companyCode,
|
|
JSON.stringify(linkData.bridgeData || {}),
|
|
linkData.createdBy,
|
|
]
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 생성 완료 - Bridge ID: ${bridge.bridge_id}`
|
|
);
|
|
return bridge;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 데이터 연결 생성 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계별 연결된 데이터 조회
|
|
*/
|
|
async getLinkedDataByRelationship(
|
|
relationshipId: number,
|
|
companyCode: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 관계별 연결 데이터 조회 시작 - 관계ID: ${relationshipId}`
|
|
);
|
|
|
|
let queryText = `
|
|
SELECT * FROM data_relationship_bridge
|
|
WHERE relationship_id = $1 AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [relationshipId];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $2`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` ORDER BY created_at DESC`;
|
|
const linkedData = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 관계별 연결 데이터 조회 완료 - ${linkedData.length}건`
|
|
);
|
|
return linkedData;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 관계별 연결 데이터 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 테이블의 연결된 데이터 조회
|
|
*/
|
|
async getLinkedDataByTable(
|
|
tableName: string,
|
|
keyValue?: string,
|
|
companyCode?: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 테이블별 연결 데이터 조회 시작 - 테이블: ${tableName}`
|
|
);
|
|
|
|
let queryText = `
|
|
SELECT * FROM data_relationship_bridge
|
|
WHERE (from_table_name = $1 OR to_table_name = $1) AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [tableName];
|
|
|
|
// keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨)
|
|
|
|
// 회사코드 필터링
|
|
if (companyCode && companyCode !== "*") {
|
|
queryText += ` AND company_code = $2`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` ORDER BY created_at DESC`;
|
|
const linkedData = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 테이블별 연결 데이터 조회 완료 - ${linkedData.length}건`
|
|
);
|
|
return linkedData;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 테이블별 연결 데이터 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 데이터 연결 수정
|
|
*/
|
|
async updateDataLink(
|
|
bridgeId: number,
|
|
updateData: {
|
|
bridgeData?: any;
|
|
updatedBy: string;
|
|
},
|
|
companyCode: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 수정 시작 - Bridge ID: ${bridgeId}`
|
|
);
|
|
|
|
let queryText = `
|
|
UPDATE data_relationship_bridge
|
|
SET bridge_data = $1, updated_by = $2, updated_at = now()
|
|
WHERE bridge_id = $3 AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [
|
|
JSON.stringify(updateData.bridgeData),
|
|
updateData.updatedBy,
|
|
bridgeId,
|
|
];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $4`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
queryText += ` RETURNING *`;
|
|
const updatedBridge = await queryOne(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 수정 완료 - Bridge ID: ${bridgeId}`
|
|
);
|
|
return updatedBridge;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 데이터 연결 수정 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 데이터 연결 삭제 (소프트 삭제)
|
|
*/
|
|
async deleteDataLink(
|
|
bridgeId: number,
|
|
companyCode: string,
|
|
deletedBy: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 삭제 시작 - Bridge ID: ${bridgeId}`
|
|
);
|
|
|
|
let queryText = `
|
|
UPDATE data_relationship_bridge
|
|
SET is_active = 'N', updated_at = now(), updated_by = $1
|
|
WHERE bridge_id = $2 AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [deletedBy, bridgeId];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $3`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 데이터 연결 삭제 완료 - Bridge ID: ${bridgeId}`
|
|
);
|
|
return true;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 데이터 연결 삭제 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계 삭제 시 연결된 모든 데이터도 삭제
|
|
*/
|
|
async deleteAllLinkedDataByRelationship(
|
|
relationshipId: number,
|
|
companyCode: string,
|
|
deletedBy: string
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 관계별 모든 데이터 연결 삭제 시작 - 관계ID: ${relationshipId}`
|
|
);
|
|
|
|
let queryText = `
|
|
UPDATE data_relationship_bridge
|
|
SET is_active = 'N', updated_at = now(), updated_by = $1
|
|
WHERE relationship_id = $2 AND is_active = 'Y'
|
|
`;
|
|
const params: any[] = [deletedBy, relationshipId];
|
|
|
|
// 관리자가 아닌 경우 회사코드 제한
|
|
if (companyCode !== "*") {
|
|
queryText += ` AND company_code = $3`;
|
|
params.push(companyCode);
|
|
}
|
|
|
|
const result = await query(queryText, params);
|
|
|
|
logger.info(
|
|
`DataflowService: 관계별 모든 데이터 연결 삭제 완료 - ${result.length}건`
|
|
);
|
|
return result.length;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 관계별 모든 데이터 연결 삭제 실패", error);
|
|
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 query(
|
|
`SELECT table_name
|
|
FROM information_schema.tables
|
|
WHERE table_name = $1 AND table_schema = 'public'`,
|
|
[tableName.toLowerCase()]
|
|
);
|
|
|
|
if (!tableExists || tableExists.length === 0) {
|
|
throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`);
|
|
}
|
|
|
|
// 전체 데이터 개수 조회 및 데이터 조회
|
|
let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`;
|
|
let dataQuery = `SELECT * FROM "${tableName}"`;
|
|
const queryParams: any[] = [];
|
|
|
|
// 검색 조건 추가 (SQL Injection 방지를 위해 파라미터 바인딩 사용)
|
|
if (search && searchColumn) {
|
|
const paramIndex = queryParams.length + 1;
|
|
const whereCondition = `WHERE "${searchColumn}" ILIKE $${paramIndex}`;
|
|
totalCountQuery += ` ${whereCondition}`;
|
|
dataQuery += ` ${whereCondition}`;
|
|
queryParams.push(`%${search}%`);
|
|
}
|
|
|
|
// 페이징 처리
|
|
const offset = (page - 1) * limit;
|
|
const limitIndex = queryParams.length + 1;
|
|
const offsetIndex = queryParams.length + 2;
|
|
dataQuery += ` ORDER BY 1 LIMIT $${limitIndex} OFFSET $${offsetIndex}`;
|
|
|
|
const dataQueryParams = [...queryParams, limit, offset];
|
|
|
|
// 실제 쿼리 실행
|
|
const [totalResult, dataResult] = await Promise.all([
|
|
query(totalCountQuery, queryParams.length > 0 ? queryParams : []),
|
|
query(dataQuery, dataQueryParams),
|
|
]);
|
|
|
|
const total =
|
|
totalResult && totalResult.length > 0
|
|
? Number((totalResult[0] as any).total)
|
|
: 0;
|
|
|
|
const data = 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계도 그룹 목록 조회 (diagram_id별로 그룹화)
|
|
*/
|
|
async getDataFlowDiagrams(
|
|
companyCode: string,
|
|
page: number = 1,
|
|
size: number = 20,
|
|
searchTerm: string = ""
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 관계도 목록 조회 시작 - ${companyCode}, page: ${page}, size: ${size}, search: ${searchTerm}`
|
|
);
|
|
|
|
// WHERE 조건 구성
|
|
const params: any[] = [companyCode];
|
|
let whereClause = `WHERE company_code = $1 AND is_active = 'Y'`;
|
|
|
|
if (searchTerm) {
|
|
whereClause += ` AND (
|
|
relationship_name ILIKE $2 OR
|
|
from_table_name ILIKE $2 OR
|
|
to_table_name ILIKE $2
|
|
)`;
|
|
params.push(`%${searchTerm}%`);
|
|
}
|
|
|
|
// diagram_id별로 그룹화된 데이터 조회
|
|
const relationships = await query<{
|
|
relationship_id: number;
|
|
diagram_id: number;
|
|
relationship_name: string;
|
|
from_table_name: string;
|
|
to_table_name: string;
|
|
connection_type: string;
|
|
relationship_type: string;
|
|
created_date: Date;
|
|
created_by: string;
|
|
updated_date: Date;
|
|
updated_by: string;
|
|
}>(
|
|
`SELECT
|
|
relationship_id, diagram_id, relationship_name,
|
|
from_table_name, to_table_name, connection_type,
|
|
relationship_type, created_date, created_by,
|
|
updated_date, updated_by
|
|
FROM table_relationships
|
|
${whereClause}
|
|
ORDER BY diagram_id ASC, created_date DESC`,
|
|
params
|
|
);
|
|
|
|
// diagram_id별로 그룹화
|
|
const diagramMap = new Map<number, any>();
|
|
|
|
relationships.forEach((rel) => {
|
|
const diagramId = rel.diagram_id;
|
|
|
|
if (diagramId && !diagramMap.has(diagramId)) {
|
|
diagramMap.set(diagramId, {
|
|
diagramId: diagramId,
|
|
diagramName: rel.relationship_name, // 첫 번째 관계의 이름을 사용
|
|
connectionType: rel.connection_type,
|
|
relationshipType: rel.relationship_type,
|
|
tableCount: new Set<string>(),
|
|
relationshipCount: 0,
|
|
createdAt: rel.created_date,
|
|
createdBy: rel.created_by,
|
|
updatedAt: rel.updated_date,
|
|
updatedBy: rel.updated_by,
|
|
tables: [],
|
|
});
|
|
}
|
|
|
|
if (diagramId) {
|
|
const diagram = diagramMap.get(diagramId);
|
|
if (diagram) {
|
|
diagram.tableCount.add(rel.from_table_name || "");
|
|
diagram.tableCount.add(rel.to_table_name || "");
|
|
}
|
|
diagram.relationshipCount++;
|
|
|
|
// 최신 업데이트 시간 유지
|
|
if (rel.updated_date && rel.updated_date > diagram.updatedAt) {
|
|
diagram.updatedAt = rel.updated_date;
|
|
diagram.updatedBy = rel.updated_by;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Set을 배열로 변환하고 테이블 개수 계산
|
|
const diagrams = Array.from(diagramMap.values()).map((diagram) => ({
|
|
...diagram,
|
|
tableCount: diagram.tableCount.size,
|
|
tables: Array.from(diagram.tableCount),
|
|
}));
|
|
|
|
// 페이징 처리
|
|
const total = diagrams.length;
|
|
const startIndex = (page - 1) * size;
|
|
const endIndex = startIndex + size;
|
|
const paginatedDiagrams = diagrams.slice(startIndex, endIndex);
|
|
|
|
const result = {
|
|
diagrams: paginatedDiagrams,
|
|
total,
|
|
page,
|
|
size,
|
|
totalPages: Math.ceil(total / size),
|
|
hasNext: page < Math.ceil(total / size),
|
|
hasPrev: page > 1,
|
|
};
|
|
|
|
logger.info(
|
|
`DataflowService: 관계도 목록 조회 완료 - 총 ${total}개 관계도 중 ${paginatedDiagrams.length}개 조회`
|
|
);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
logger.error("DataflowService: 관계도 목록 조회 실패", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 특정 관계도의 모든 관계 조회
|
|
*/
|
|
async getDiagramRelationships(companyCode: string, diagramName: string) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: 관계도 관계 조회 시작 - ${companyCode}, diagram: ${diagramName}`
|
|
);
|
|
|
|
const relationships = await query(
|
|
`SELECT * FROM table_relationships
|
|
WHERE company_code = $1
|
|
AND relationship_name = $2
|
|
AND is_active = 'Y'
|
|
ORDER BY created_date ASC`,
|
|
[companyCode, diagramName]
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: 관계도 관계 조회 완료 - ${diagramName}, ${relationships.length}개 관계`
|
|
);
|
|
|
|
return relationships;
|
|
} catch (error) {
|
|
logger.error(
|
|
`DataflowService: 관계도 관계 조회 실패 - ${diagramName}`,
|
|
error
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계도 복사 (diagram_id 기반)
|
|
*/
|
|
async copyDiagram(
|
|
companyCode: string,
|
|
originalDiagramName: string
|
|
): Promise<string> {
|
|
try {
|
|
logger.info(`DataflowService: 관계도 복사 시작 - ${originalDiagramName}`);
|
|
|
|
// 원본 관계도의 모든 관계 조회
|
|
const originalRelationships = await query<{
|
|
relationship_id: number;
|
|
diagram_id: number;
|
|
relationship_name: string;
|
|
from_table_name: string;
|
|
from_column_name: string;
|
|
to_table_name: string;
|
|
to_column_name: string;
|
|
relationship_type: string;
|
|
connection_type: string;
|
|
settings: any;
|
|
company_code: string;
|
|
created_by: string;
|
|
updated_by: string;
|
|
}>(
|
|
`SELECT * FROM table_relationships
|
|
WHERE company_code = $1
|
|
AND relationship_name = $2
|
|
AND is_active = 'Y'`,
|
|
[companyCode, originalDiagramName]
|
|
);
|
|
|
|
if (originalRelationships.length === 0) {
|
|
throw new Error("복사할 관계도를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// 새로운 관계도 이름 생성 (중복 검사)
|
|
let newDiagramName = `${originalDiagramName} (1)`;
|
|
let counter = 1;
|
|
|
|
while (true) {
|
|
const existingDiagram = await queryOne(
|
|
`SELECT relationship_id FROM table_relationships
|
|
WHERE company_code = $1
|
|
AND relationship_name = $2
|
|
AND is_active = 'Y'
|
|
LIMIT 1`,
|
|
[companyCode, newDiagramName]
|
|
);
|
|
|
|
if (!existingDiagram) {
|
|
break;
|
|
}
|
|
|
|
counter++;
|
|
newDiagramName = `${originalDiagramName} (${counter})`;
|
|
}
|
|
|
|
// 새로운 diagram_id 생성
|
|
const maxDiagramId = await queryOne<{ diagram_id: number }>(
|
|
`SELECT diagram_id FROM table_relationships
|
|
WHERE company_code = $1
|
|
ORDER BY diagram_id DESC
|
|
LIMIT 1`,
|
|
[companyCode]
|
|
);
|
|
|
|
const newDiagramId = (maxDiagramId?.diagram_id || 0) + 1;
|
|
|
|
// 트랜잭션으로 모든 관계 복사
|
|
const copiedRelationships = await transaction(async (client) => {
|
|
const results: any[] = [];
|
|
|
|
for (const rel of originalRelationships) {
|
|
const result = await client.query(
|
|
`INSERT INTO table_relationships (
|
|
diagram_id, relationship_name, from_table_name, from_column_name,
|
|
to_table_name, to_column_name, relationship_type, connection_type,
|
|
settings, company_code, is_active, created_by, updated_by, created_at, updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'Y', $11, $12, now(), now())
|
|
RETURNING *`,
|
|
[
|
|
newDiagramId,
|
|
newDiagramName,
|
|
rel.from_table_name,
|
|
rel.from_column_name,
|
|
rel.to_table_name,
|
|
rel.to_column_name,
|
|
rel.relationship_type,
|
|
rel.connection_type,
|
|
rel.settings,
|
|
rel.company_code,
|
|
rel.created_by,
|
|
rel.updated_by,
|
|
]
|
|
);
|
|
|
|
if (result.rows && result.rows.length > 0) {
|
|
results.push(result.rows[0]);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
});
|
|
|
|
logger.info(
|
|
`DataflowService: 관계도 복사 완료 - ${originalDiagramName} → ${newDiagramName} (diagram_id: ${newDiagramId}), ${copiedRelationships.length}개 관계 복사`
|
|
);
|
|
|
|
return newDiagramName;
|
|
} catch (error) {
|
|
logger.error(
|
|
`DataflowService: 관계도 복사 실패 - ${originalDiagramName}`,
|
|
error
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 관계도 삭제
|
|
*/
|
|
async deleteDiagram(
|
|
companyCode: string,
|
|
diagramName: string
|
|
): Promise<number> {
|
|
try {
|
|
logger.info(`DataflowService: 관계도 삭제 시작 - ${diagramName}`);
|
|
|
|
// 관계도의 모든 관계 삭제 (하드 삭제)
|
|
const deleteResult = await query<{ count: number }>(
|
|
`DELETE FROM table_relationships
|
|
WHERE company_code = $1 AND relationship_name = $2
|
|
RETURNING relationship_id`,
|
|
[companyCode, diagramName]
|
|
);
|
|
|
|
const count = deleteResult.length;
|
|
|
|
logger.info(
|
|
`DataflowService: 관계도 삭제 완료 - ${diagramName}, ${count}개 관계 삭제`
|
|
);
|
|
|
|
return count;
|
|
} catch (error) {
|
|
logger.error(`DataflowService: 관계도 삭제 실패 - ${diagramName}`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* diagram_id로 해당 관계도의 모든 관계 조회
|
|
*/
|
|
async getDiagramRelationshipsByDiagramId(
|
|
companyCode: string,
|
|
diagramId: number
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: diagram_id로 관계도 관계 조회 - ${diagramId}`
|
|
);
|
|
|
|
// diagram_id로 모든 관계 조회
|
|
const relationships = await query(
|
|
`SELECT * FROM table_relationships
|
|
WHERE diagram_id = $1
|
|
AND company_code = $2
|
|
AND is_active = 'Y'
|
|
ORDER BY relationship_id ASC`,
|
|
[diagramId, companyCode]
|
|
);
|
|
|
|
logger.info(
|
|
`DataflowService: diagram_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계`
|
|
);
|
|
|
|
return relationships.map((rel: any) => ({
|
|
...rel,
|
|
settings: rel.settings as any,
|
|
}));
|
|
} catch (error) {
|
|
logger.error(
|
|
`DataflowService: diagram_id로 관계도 관계 조회 실패 - ${diagramId}`,
|
|
error
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* relationship_id로 해당 관계도의 모든 관계 조회 (하위 호환성 유지)
|
|
*/
|
|
async getDiagramRelationshipsByRelationshipId(
|
|
companyCode: string,
|
|
relationshipId: number
|
|
) {
|
|
try {
|
|
logger.info(
|
|
`DataflowService: relationship_id로 관계도 관계 조회 - ${relationshipId}`
|
|
);
|
|
|
|
// 먼저 해당 relationship_id의 diagram_id를 찾음
|
|
const targetRelationship = await queryOne<{ diagram_id: number }>(
|
|
`SELECT diagram_id FROM table_relationships
|
|
WHERE relationship_id = $1
|
|
AND company_code = $2
|
|
AND is_active = 'Y'
|
|
LIMIT 1`,
|
|
[relationshipId, companyCode]
|
|
);
|
|
|
|
if (!targetRelationship) {
|
|
throw new Error("해당 관계 ID를 찾을 수 없습니다.");
|
|
}
|
|
|
|
// diagram_id로 모든 관계 조회
|
|
if (targetRelationship.diagram_id) {
|
|
return this.getDiagramRelationshipsByDiagramId(
|
|
companyCode,
|
|
targetRelationship.diagram_id
|
|
);
|
|
} else {
|
|
throw new Error("관계에 diagram_id가 없습니다.");
|
|
}
|
|
} catch (error) {
|
|
logger.error(
|
|
`DataflowService: relationship_id로 관계도 관계 조회 실패 - ${relationshipId}`,
|
|
error
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|