feat: DISTINCT 값 조회 API 추가 및 라우터 설정

- 테이블 컬럼의 DISTINCT 값을 조회하는 API를 추가하였습니다. 이 API는 특정 테이블과 컬럼에서 DISTINCT 값을 반환하여 선택박스 옵션으로 사용할 수 있도록 합니다.
- API 호출 시 멀티테넌시를 고려하여 회사 코드에 따라 필터링을 적용하였습니다.
- 관련된 라우터 설정을 추가하여 API 접근을 가능하게 하였습니다.
- 프론트엔드에서 DISTINCT 값을 조회할 수 있도록 UnifiedSelect 컴포넌트를 업데이트하였습니다.
This commit is contained in:
kjs
2026-01-27 23:02:03 +09:00
parent cc742b27f1
commit a06f2eb52c
9 changed files with 624 additions and 93 deletions

View File

@@ -3,6 +3,107 @@ import { AuthenticatedRequest } from "../types/auth";
import { getPool } from "../database/db";
import { logger } from "../utils/logger";
/**
* 테이블 컬럼의 DISTINCT 값 조회 API (inputType: select 용)
* GET /api/entity/:tableName/distinct/:columnName
*
* 해당 테이블의 해당 컬럼에서 DISTINCT 값을 조회하여 선택박스 옵션으로 반환
*/
export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Response) {
try {
const { tableName, columnName } = req.params;
const { labelColumn } = req.query; // 선택적: 별도의 라벨 컬럼
// 유효성 검증
if (!tableName || tableName === "undefined" || tableName === "null") {
return res.status(400).json({
success: false,
message: "테이블명이 지정되지 않았습니다.",
});
}
if (!columnName || columnName === "undefined" || columnName === "null") {
return res.status(400).json({
success: false,
message: "컬럼명이 지정되지 않았습니다.",
});
}
const companyCode = req.user!.companyCode;
const pool = getPool();
// 테이블의 실제 컬럼 목록 조회
const columnsResult = await pool.query(
`SELECT column_name FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = $1`,
[tableName]
);
const existingColumns = new Set(columnsResult.rows.map((r: any) => r.column_name));
// 요청된 컬럼 검증
if (!existingColumns.has(columnName)) {
return res.status(400).json({
success: false,
message: `테이블 "${tableName}"에 컬럼 "${columnName}"이 존재하지 않습니다.`,
});
}
// 라벨 컬럼 결정 (지정되지 않으면 값 컬럼과 동일)
const effectiveLabelColumn = labelColumn && existingColumns.has(labelColumn as string)
? labelColumn as string
: columnName;
// WHERE 조건 (멀티테넌시)
const whereConditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;
if (companyCode !== "*" && existingColumns.has("company_code")) {
whereConditions.push(`company_code = $${paramIndex}`);
params.push(companyCode);
paramIndex++;
}
// NULL 제외
whereConditions.push(`"${columnName}" IS NOT NULL`);
whereConditions.push(`"${columnName}" != ''`);
const whereClause = whereConditions.length > 0
? `WHERE ${whereConditions.join(" AND ")}`
: "";
// DISTINCT 쿼리 실행
const query = `
SELECT DISTINCT "${columnName}" as value, "${effectiveLabelColumn}" as label
FROM "${tableName}"
${whereClause}
ORDER BY "${effectiveLabelColumn}" ASC
LIMIT 500
`;
const result = await pool.query(query, params);
logger.info("컬럼 DISTINCT 값 조회 성공", {
tableName,
columnName,
labelColumn: effectiveLabelColumn,
companyCode,
rowCount: result.rowCount,
});
res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("컬럼 DISTINCT 값 조회 오류", {
error: error.message,
stack: error.stack,
});
res.status(500).json({ success: false, message: error.message });
}
}
/**
* 엔티티 옵션 조회 API (UnifiedSelect용)
* GET /api/entity/:tableName/options

View File

@@ -1,6 +1,6 @@
import { Router } from "express";
import { authenticateToken } from "../middleware/authMiddleware";
import { searchEntity, getEntityOptions } from "../controllers/entitySearchController";
import { searchEntity, getEntityOptions, getDistinctColumnValues } from "../controllers/entitySearchController";
const router = Router();
@@ -21,3 +21,9 @@ export const entityOptionsRouter = Router();
*/
entityOptionsRouter.get("/:tableName/options", authenticateToken, getEntityOptions);
/**
* 테이블 컬럼의 DISTINCT 값 조회 API (inputType: select 용)
* GET /api/entity/:tableName/distinct/:columnName
*/
entityOptionsRouter.get("/:tableName/distinct/:columnName", authenticateToken, getDistinctColumnValues);