ERP-node/backend-node/src/services/dataService.ts

319 lines
8.2 KiB
TypeScript
Raw Normal View History

import { query, queryOne } from "../database/db";
2025-09-12 16:47:02 +09:00
interface GetTableDataParams {
tableName: string;
limit?: number;
offset?: number;
orderBy?: string;
filters?: Record<string, string>;
userCompany?: string;
}
interface ServiceResponse<T> {
success: boolean;
data?: T;
message?: string;
error?: string;
}
/**
* ()
* SQL
*/
const ALLOWED_TABLES = [
"company_mng",
"user_info",
"dept_info",
"code_info",
"code_category",
"menu_info",
"approval",
"approval_kind",
"board",
"comm_code",
"product_mng",
"part_mng",
"material_mng",
"order_mng_master",
"inventory_mng",
"contract_mgmt",
"project_mgmt",
"screen_definitions",
"screen_layouts",
"layout_standards",
"component_standards",
"web_type_standards",
"button_action_standards",
"template_standards",
"grid_standards",
"style_templates",
"multi_lang_key_master",
"multi_lang_text",
"language_master",
"table_labels",
"column_labels",
"dynamic_form_data",
];
/**
*
*/
const COMPANY_FILTERED_TABLES = [
"company_mng",
"user_info",
"dept_info",
"approval",
"board",
"product_mng",
"part_mng",
"material_mng",
"order_mng_master",
"inventory_mng",
"contract_mgmt",
"project_mgmt",
];
class DataService {
/**
*
*/
async getTableData(
params: GetTableDataParams
): Promise<ServiceResponse<any[]>> {
const {
tableName,
limit = 10,
offset = 0,
orderBy,
filters = {},
userCompany,
} = params;
try {
// 테이블명 화이트리스트 검증
if (!ALLOWED_TABLES.includes(tableName)) {
return {
success: false,
message: `접근이 허용되지 않은 테이블입니다: ${tableName}`,
error: "TABLE_NOT_ALLOWED",
};
}
// 테이블 존재 여부 확인
const tableExists = await this.checkTableExists(tableName);
if (!tableExists) {
return {
success: false,
message: `테이블을 찾을 수 없습니다: ${tableName}`,
error: "TABLE_NOT_FOUND",
};
}
// 동적 SQL 쿼리 생성
let sql = `SELECT * FROM "${tableName}"`;
2025-09-12 16:47:02 +09:00
const queryParams: any[] = [];
let paramIndex = 1;
// WHERE 조건 생성
const whereConditions: string[] = [];
// 회사별 필터링 추가
if (COMPANY_FILTERED_TABLES.includes(tableName) && userCompany) {
// 슈퍼관리자(*)가 아닌 경우에만 회사 필터 적용
if (userCompany !== "*") {
whereConditions.push(`company_code = $${paramIndex}`);
queryParams.push(userCompany);
paramIndex++;
}
}
// 사용자 정의 필터 추가
for (const [key, value] of Object.entries(filters)) {
if (
value &&
key !== "limit" &&
key !== "offset" &&
key !== "orderBy" &&
key !== "userLang"
) {
// 컬럼명 검증 (SQL 인젝션 방지)
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
continue; // 유효하지 않은 컬럼명은 무시
}
whereConditions.push(`"${key}" ILIKE $${paramIndex}`);
queryParams.push(`%${value}%`);
paramIndex++;
}
}
// WHERE 절 추가
if (whereConditions.length > 0) {
sql += ` WHERE ${whereConditions.join(" AND ")}`;
2025-09-12 16:47:02 +09:00
}
// ORDER BY 절 추가
if (orderBy) {
// ORDER BY 검증 (SQL 인젝션 방지)
const orderParts = orderBy.split(" ");
const columnName = orderParts[0];
const direction = orderParts[1]?.toUpperCase();
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(columnName)) {
const validDirection = direction === "DESC" ? "DESC" : "ASC";
sql += ` ORDER BY "${columnName}" ${validDirection}`;
2025-09-12 16:47:02 +09:00
}
} else {
// 기본 정렬: 최신순 (가능한 컬럼 시도)
const dateColumns = [
"created_date",
"regdate",
"reg_date",
"updated_date",
"upd_date",
];
const tableColumns = await this.getTableColumnsSimple(tableName);
const availableDateColumn = dateColumns.find((col) =>
tableColumns.some((tableCol) => tableCol.column_name === col)
);
if (availableDateColumn) {
sql += ` ORDER BY "${availableDateColumn}" DESC`;
2025-09-12 16:47:02 +09:00
}
}
// LIMIT과 OFFSET 추가
sql += ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
2025-09-12 16:47:02 +09:00
queryParams.push(limit, offset);
console.log("🔍 실행할 쿼리:", sql);
2025-09-12 16:47:02 +09:00
console.log("📊 쿼리 파라미터:", queryParams);
// 쿼리 실행
const result = await query<any>(sql, queryParams);
2025-09-12 16:47:02 +09:00
return {
success: true,
data: result,
2025-09-12 16:47:02 +09:00
};
} catch (error) {
console.error(`데이터 조회 오류 (${tableName}):`, error);
return {
success: false,
message: "데이터 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
/**
*
*/
async getTableColumns(tableName: string): Promise<ServiceResponse<any[]>> {
try {
// 테이블명 화이트리스트 검증
if (!ALLOWED_TABLES.includes(tableName)) {
return {
success: false,
message: `접근이 허용되지 않은 테이블입니다: ${tableName}`,
error: "TABLE_NOT_ALLOWED",
};
}
const columns = await this.getTableColumnsSimple(tableName);
// 컬럼 라벨 정보 추가
const columnsWithLabels = await Promise.all(
columns.map(async (column) => {
const label = await this.getColumnLabel(
tableName,
column.column_name
);
return {
columnName: column.column_name,
columnLabel: label || column.column_name,
dataType: column.data_type,
isNullable: column.is_nullable === "YES",
defaultValue: column.column_default,
};
})
);
return {
success: true,
data: columnsWithLabels,
};
} catch (error) {
console.error(`컬럼 정보 조회 오류 (${tableName}):`, error);
return {
success: false,
message: "컬럼 정보 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
/**
*
*/
private async checkTableExists(tableName: string): Promise<boolean> {
try {
const result = await query<{ exists: boolean }>(
`SELECT EXISTS (
2025-09-12 16:47:02 +09:00
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = $1
)`,
[tableName]
2025-09-12 16:47:02 +09:00
);
return result[0]?.exists || false;
2025-09-12 16:47:02 +09:00
} catch (error) {
console.error("테이블 존재 확인 오류:", error);
return false;
}
}
/**
* ( )
*/
private async getTableColumnsSimple(tableName: string): Promise<any[]> {
const result = await query<any>(
`SELECT column_name, data_type, is_nullable, column_default
2025-09-12 16:47:02 +09:00
FROM information_schema.columns
WHERE table_name = $1
AND table_schema = 'public'
ORDER BY ordinal_position`,
[tableName]
2025-09-12 16:47:02 +09:00
);
return result;
2025-09-12 16:47:02 +09:00
}
/**
*
*/
private async getColumnLabel(
tableName: string,
columnName: string
): Promise<string | null> {
try {
// column_labels 테이블에서 라벨 조회
const result = await query<{ label_ko: string }>(
`SELECT label_ko
2025-09-12 16:47:02 +09:00
FROM column_labels
WHERE table_name = $1 AND column_name = $2
LIMIT 1`,
[tableName, columnName]
2025-09-12 16:47:02 +09:00
);
return result[0]?.label_ko || null;
2025-09-12 16:47:02 +09:00
} catch (error) {
// column_labels 테이블이 없거나 오류가 발생하면 null 반환
return null;
}
}
}
export const dataService = new DataService();