2025-11-21 03:50:45 +09:00
|
|
|
import { Response } from "express";
|
|
|
|
|
import { AuthenticatedRequest } from "../types/auth";
|
2025-11-14 14:43:53 +09:00
|
|
|
import { getPool } from "../database/db";
|
|
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 엔티티 검색 API
|
|
|
|
|
* GET /api/entity-search/:tableName
|
|
|
|
|
*/
|
2025-11-21 03:50:45 +09:00
|
|
|
export async function searchEntity(req: AuthenticatedRequest, res: Response) {
|
2025-11-14 14:43:53 +09:00
|
|
|
try {
|
|
|
|
|
const { tableName } = req.params;
|
|
|
|
|
const {
|
|
|
|
|
searchText = "",
|
|
|
|
|
searchFields = "",
|
|
|
|
|
filterCondition = "{}",
|
|
|
|
|
page = "1",
|
|
|
|
|
limit = "20",
|
|
|
|
|
} = req.query;
|
|
|
|
|
|
2025-11-17 16:48:42 +09:00
|
|
|
// tableName 유효성 검증
|
|
|
|
|
if (!tableName || tableName === "undefined" || tableName === "null") {
|
|
|
|
|
logger.warn("엔티티 검색 실패: 테이블명이 없음", { tableName });
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2025-11-21 03:50:45 +09:00
|
|
|
message:
|
|
|
|
|
"테이블명이 지정되지 않았습니다. 컴포넌트 설정에서 sourceTable을 확인해주세요.",
|
2025-11-17 16:48:42 +09:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 14:43:53 +09:00
|
|
|
// 멀티테넌시
|
|
|
|
|
const companyCode = req.user!.companyCode;
|
|
|
|
|
|
|
|
|
|
// 검색 필드 파싱
|
|
|
|
|
const fields = searchFields
|
|
|
|
|
? (searchFields as string).split(",").map((f) => f.trim())
|
|
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
// WHERE 조건 생성
|
|
|
|
|
const whereConditions: string[] = [];
|
|
|
|
|
const params: any[] = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
// 멀티테넌시 필터링
|
|
|
|
|
if (companyCode !== "*") {
|
|
|
|
|
whereConditions.push(`company_code = $${paramIndex}`);
|
|
|
|
|
params.push(companyCode);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 검색 조건
|
|
|
|
|
if (searchText && fields.length > 0) {
|
|
|
|
|
const searchConditions = fields.map((field) => {
|
|
|
|
|
const condition = `${field}::text ILIKE $${paramIndex}`;
|
|
|
|
|
paramIndex++;
|
|
|
|
|
return condition;
|
|
|
|
|
});
|
|
|
|
|
whereConditions.push(`(${searchConditions.join(" OR ")})`);
|
|
|
|
|
|
|
|
|
|
// 검색어 파라미터 추가
|
|
|
|
|
fields.forEach(() => {
|
|
|
|
|
params.push(`%${searchText}%`);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 추가 필터 조건
|
|
|
|
|
const additionalFilter = JSON.parse(filterCondition as string);
|
|
|
|
|
for (const [key, value] of Object.entries(additionalFilter)) {
|
|
|
|
|
whereConditions.push(`${key} = $${paramIndex}`);
|
|
|
|
|
params.push(value);
|
|
|
|
|
paramIndex++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 페이징
|
|
|
|
|
const offset = (parseInt(page as string) - 1) * parseInt(limit as string);
|
|
|
|
|
const whereClause =
|
|
|
|
|
whereConditions.length > 0
|
|
|
|
|
? `WHERE ${whereConditions.join(" AND ")}`
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
// 쿼리 실행
|
|
|
|
|
const pool = getPool();
|
|
|
|
|
const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
|
|
|
|
|
const dataQuery = `
|
|
|
|
|
SELECT * FROM ${tableName} ${whereClause}
|
|
|
|
|
ORDER BY id DESC
|
|
|
|
|
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
params.push(parseInt(limit as string));
|
|
|
|
|
params.push(offset);
|
|
|
|
|
|
|
|
|
|
const countResult = await pool.query(
|
|
|
|
|
countQuery,
|
|
|
|
|
params.slice(0, params.length - 2)
|
|
|
|
|
);
|
|
|
|
|
const dataResult = await pool.query(dataQuery, params);
|
|
|
|
|
|
|
|
|
|
logger.info("엔티티 검색 성공", {
|
|
|
|
|
tableName,
|
|
|
|
|
searchText,
|
|
|
|
|
companyCode,
|
|
|
|
|
rowCount: dataResult.rowCount,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: dataResult.rows,
|
|
|
|
|
pagination: {
|
|
|
|
|
total: parseInt(countResult.rows[0].count),
|
|
|
|
|
page: parseInt(page as string),
|
|
|
|
|
limit: parseInt(limit as string),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error: any) {
|
2025-11-21 03:50:45 +09:00
|
|
|
logger.error("엔티티 검색 오류", {
|
|
|
|
|
error: error.message,
|
|
|
|
|
stack: error.stack,
|
|
|
|
|
});
|
2025-11-14 14:43:53 +09:00
|
|
|
res.status(500).json({ success: false, message: error.message });
|
|
|
|
|
}
|
|
|
|
|
}
|