공통코드,REST API 회사별 분리
This commit is contained in:
parent
25f6217433
commit
c333a9fd9d
|
|
@ -461,12 +461,13 @@ export class CommonCodeController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 카테고리 중복 검사
|
||||
* 카테고리 중복 검사 (회사별)
|
||||
* GET /api/common-codes/categories/check-duplicate?field=categoryCode&value=USER_STATUS&excludeCode=OLD_CODE
|
||||
*/
|
||||
async checkCategoryDuplicate(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { field, value, excludeCode } = req.query;
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
// 입력값 검증
|
||||
if (!field || !value) {
|
||||
|
|
@ -488,7 +489,8 @@ export class CommonCodeController {
|
|||
const result = await this.commonCodeService.checkCategoryDuplicate(
|
||||
field as "categoryCode" | "categoryName" | "categoryNameEng",
|
||||
value as string,
|
||||
excludeCode as string
|
||||
excludeCode as string,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.json({
|
||||
|
|
@ -511,13 +513,14 @@ export class CommonCodeController {
|
|||
}
|
||||
|
||||
/**
|
||||
* 코드 중복 검사
|
||||
* 코드 중복 검사 (회사별)
|
||||
* GET /api/common-codes/categories/:categoryCode/codes/check-duplicate?field=codeValue&value=ACTIVE&excludeCode=OLD_CODE
|
||||
*/
|
||||
async checkCodeDuplicate(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { categoryCode } = req.params;
|
||||
const { field, value, excludeCode } = req.query;
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
// 입력값 검증
|
||||
if (!field || !value) {
|
||||
|
|
@ -540,7 +543,8 @@ export class CommonCodeController {
|
|||
categoryCode,
|
||||
field as "codeValue" | "codeName" | "codeNameEng",
|
||||
value as string,
|
||||
excludeCode as string
|
||||
excludeCode as string,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.json({
|
||||
|
|
|
|||
|
|
@ -87,7 +87,10 @@ router.get(
|
|||
filter,
|
||||
});
|
||||
|
||||
const result = await ExternalDbConnectionService.getConnections(filter);
|
||||
const result = await ExternalDbConnectionService.getConnections(
|
||||
filter,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
|
|
@ -319,7 +322,12 @@ router.delete(
|
|||
});
|
||||
}
|
||||
|
||||
const result = await ExternalDbConnectionService.deleteConnection(id);
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
const result = await ExternalDbConnectionService.deleteConnection(
|
||||
id,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
return res.status(200).json(result);
|
||||
|
|
@ -517,7 +525,10 @@ router.get(
|
|||
});
|
||||
|
||||
const externalConnections =
|
||||
await ExternalDbConnectionService.getConnections(filter);
|
||||
await ExternalDbConnectionService.getConnections(
|
||||
filter,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
if (!externalConnections.success) {
|
||||
return res.status(400).json(externalConnections);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,12 @@ router.get(
|
|||
company_code: req.query.company_code as string,
|
||||
};
|
||||
|
||||
const result =
|
||||
await ExternalRestApiConnectionService.getConnections(filter);
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
const result = await ExternalRestApiConnectionService.getConnections(
|
||||
filter,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.status(result.success ? 200 : 400).json(result);
|
||||
} catch (error) {
|
||||
|
|
@ -62,8 +66,12 @@ router.get(
|
|||
});
|
||||
}
|
||||
|
||||
const result =
|
||||
await ExternalRestApiConnectionService.getConnectionById(id);
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
const result = await ExternalRestApiConnectionService.getConnectionById(
|
||||
id,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.status(result.success ? 200 : 404).json(result);
|
||||
} catch (error) {
|
||||
|
|
@ -129,9 +137,12 @@ router.put(
|
|||
updated_by: req.user?.userId || "system",
|
||||
};
|
||||
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
const result = await ExternalRestApiConnectionService.updateConnection(
|
||||
id,
|
||||
data
|
||||
data,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.status(result.success ? 200 : 400).json(result);
|
||||
|
|
@ -164,8 +175,12 @@ router.delete(
|
|||
});
|
||||
}
|
||||
|
||||
const result =
|
||||
await ExternalRestApiConnectionService.deleteConnection(id);
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
|
||||
const result = await ExternalRestApiConnectionService.deleteConnection(
|
||||
id,
|
||||
userCompanyCode
|
||||
);
|
||||
|
||||
return res.status(result.success ? 200 : 404).json(result);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -604,12 +604,13 @@ export class CommonCodeService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 카테고리 중복 검사
|
||||
* 카테고리 중복 검사 (회사별)
|
||||
*/
|
||||
async checkCategoryDuplicate(
|
||||
field: "categoryCode" | "categoryName" | "categoryNameEng",
|
||||
value: string,
|
||||
excludeCategoryCode?: string
|
||||
excludeCategoryCode?: string,
|
||||
userCompanyCode?: string
|
||||
): Promise<{ isDuplicate: boolean; message: string }> {
|
||||
try {
|
||||
if (!value || !value.trim()) {
|
||||
|
|
@ -655,6 +656,12 @@ export class CommonCodeService {
|
|||
break;
|
||||
}
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
sql += ` AND company_code = $${paramIndex++}`;
|
||||
values.push(userCompanyCode);
|
||||
}
|
||||
|
||||
// 수정 시 자기 자신 제외
|
||||
if (excludeCategoryCode) {
|
||||
sql += ` AND category_code != $${paramIndex++}`;
|
||||
|
|
@ -675,6 +682,10 @@ export class CommonCodeService {
|
|||
categoryNameEng: "카테고리 영문명",
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`카테고리 중복 검사: ${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}`
|
||||
);
|
||||
|
||||
return {
|
||||
isDuplicate,
|
||||
message: isDuplicate
|
||||
|
|
@ -688,13 +699,14 @@ export class CommonCodeService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 코드 중복 검사
|
||||
* 코드 중복 검사 (회사별)
|
||||
*/
|
||||
async checkCodeDuplicate(
|
||||
categoryCode: string,
|
||||
field: "codeValue" | "codeName" | "codeNameEng",
|
||||
value: string,
|
||||
excludeCodeValue?: string
|
||||
excludeCodeValue?: string,
|
||||
userCompanyCode?: string
|
||||
): Promise<{ isDuplicate: boolean; message: string }> {
|
||||
try {
|
||||
if (!value || !value.trim()) {
|
||||
|
|
@ -743,6 +755,12 @@ export class CommonCodeService {
|
|||
break;
|
||||
}
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
sql += ` AND company_code = $${paramIndex++}`;
|
||||
values.push(userCompanyCode);
|
||||
}
|
||||
|
||||
// 수정 시 자기 자신 제외
|
||||
if (excludeCodeValue) {
|
||||
sql += ` AND code_value != $${paramIndex++}`;
|
||||
|
|
@ -760,6 +778,10 @@ export class CommonCodeService {
|
|||
codeNameEng: "코드 영문명",
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`코드 중복 검사: ${categoryCode}.${field}=${value}, 회사=${userCompanyCode}, 중복=${isDuplicate}`
|
||||
);
|
||||
|
||||
return {
|
||||
isDuplicate,
|
||||
message: isDuplicate
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ export class ExternalDbConnectionService {
|
|||
* 외부 DB 연결 목록 조회
|
||||
*/
|
||||
static async getConnections(
|
||||
filter: ExternalDbConnectionFilter
|
||||
filter: ExternalDbConnectionFilter,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalDbConnection[]>> {
|
||||
try {
|
||||
// WHERE 조건 동적 생성
|
||||
|
|
@ -25,6 +26,26 @@ export class ExternalDbConnectionService {
|
|||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우 필수)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
whereConditions.push(`company_code = $${paramIndex++}`);
|
||||
params.push(userCompanyCode);
|
||||
logger.info(`회사별 외부 DB 연결 필터링: ${userCompanyCode}`);
|
||||
} else if (userCompanyCode === "*") {
|
||||
logger.info(`최고 관리자: 모든 외부 DB 연결 조회`);
|
||||
// 필터가 있으면 적용
|
||||
if (filter.company_code) {
|
||||
whereConditions.push(`company_code = $${paramIndex++}`);
|
||||
params.push(filter.company_code);
|
||||
}
|
||||
} else {
|
||||
// userCompanyCode가 없는 경우 (하위 호환성)
|
||||
if (filter.company_code) {
|
||||
whereConditions.push(`company_code = $${paramIndex++}`);
|
||||
params.push(filter.company_code);
|
||||
}
|
||||
}
|
||||
|
||||
// 필터 조건 적용
|
||||
if (filter.db_type) {
|
||||
whereConditions.push(`db_type = $${paramIndex++}`);
|
||||
|
|
@ -36,11 +57,6 @@ export class ExternalDbConnectionService {
|
|||
params.push(filter.is_active);
|
||||
}
|
||||
|
||||
if (filter.company_code) {
|
||||
whereConditions.push(`company_code = $${paramIndex++}`);
|
||||
params.push(filter.company_code);
|
||||
}
|
||||
|
||||
// 검색 조건 적용 (연결명 또는 설명에서 검색)
|
||||
if (filter.search && filter.search.trim()) {
|
||||
whereConditions.push(
|
||||
|
|
@ -496,23 +512,36 @@ export class ExternalDbConnectionService {
|
|||
/**
|
||||
* 외부 DB 연결 삭제 (물리 삭제)
|
||||
*/
|
||||
static async deleteConnection(id: number): Promise<ApiResponse<void>> {
|
||||
static async deleteConnection(
|
||||
id: number,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
const existingConnection = await queryOne(
|
||||
`SELECT id FROM external_db_connections WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
let selectQuery = `SELECT id FROM external_db_connections WHERE id = $1`;
|
||||
const selectParams: any[] = [id];
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
selectQuery += ` AND company_code = $2`;
|
||||
selectParams.push(userCompanyCode);
|
||||
}
|
||||
|
||||
const existingConnection = await queryOne(selectQuery, selectParams);
|
||||
|
||||
if (!existingConnection) {
|
||||
return {
|
||||
success: false,
|
||||
message: "해당 연결 설정을 찾을 수 없습니다.",
|
||||
message: "해당 연결 설정을 찾을 수 없거나 권한이 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
// 물리 삭제 (실제 데이터 삭제)
|
||||
await query(`DELETE FROM external_db_connections WHERE id = $1`, [id]);
|
||||
|
||||
logger.info(
|
||||
`외부 DB 연결 삭제: ID ${id} (회사: ${userCompanyCode || "전체"})`
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "연결 설정이 삭제되었습니다.",
|
||||
|
|
@ -747,8 +776,11 @@ export class ExternalDbConnectionService {
|
|||
try {
|
||||
// 보안 검증: SELECT 쿼리만 허용
|
||||
const trimmedQuery = query.trim().toUpperCase();
|
||||
if (!trimmedQuery.startsWith('SELECT')) {
|
||||
console.log("보안 오류: SELECT가 아닌 쿼리 시도:", { id, query: query.substring(0, 100) });
|
||||
if (!trimmedQuery.startsWith("SELECT")) {
|
||||
console.log("보안 오류: SELECT가 아닌 쿼리 시도:", {
|
||||
id,
|
||||
query: query.substring(0, 100),
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다.",
|
||||
|
|
@ -756,16 +788,32 @@ export class ExternalDbConnectionService {
|
|||
}
|
||||
|
||||
// 위험한 키워드 검사
|
||||
const dangerousKeywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'TRUNCATE', 'EXEC', 'EXECUTE', 'CALL', 'MERGE'];
|
||||
const hasDangerousKeyword = dangerousKeywords.some(keyword =>
|
||||
const dangerousKeywords = [
|
||||
"INSERT",
|
||||
"UPDATE",
|
||||
"DELETE",
|
||||
"DROP",
|
||||
"CREATE",
|
||||
"ALTER",
|
||||
"TRUNCATE",
|
||||
"EXEC",
|
||||
"EXECUTE",
|
||||
"CALL",
|
||||
"MERGE",
|
||||
];
|
||||
const hasDangerousKeyword = dangerousKeywords.some((keyword) =>
|
||||
trimmedQuery.includes(keyword)
|
||||
);
|
||||
|
||||
|
||||
if (hasDangerousKeyword) {
|
||||
console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", { id, query: query.substring(0, 100) });
|
||||
console.log("보안 오류: 위험한 키워드 포함 쿼리 시도:", {
|
||||
id,
|
||||
query: query.substring(0, 100),
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: "데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.",
|
||||
message:
|
||||
"데이터를 변경하거나 삭제하는 쿼리는 허용되지 않습니다. SELECT 쿼리만 사용해주세요.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ export class ExternalRestApiConnectionService {
|
|||
* REST API 연결 목록 조회
|
||||
*/
|
||||
static async getConnections(
|
||||
filter: ExternalRestApiConnectionFilter = {}
|
||||
filter: ExternalRestApiConnectionFilter = {},
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalRestApiConnection[]>> {
|
||||
try {
|
||||
let query = `
|
||||
|
|
@ -39,11 +40,27 @@ export class ExternalRestApiConnectionService {
|
|||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 회사 코드 필터
|
||||
if (filter.company_code) {
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우 필수)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
query += ` AND company_code = $${paramIndex}`;
|
||||
params.push(filter.company_code);
|
||||
params.push(userCompanyCode);
|
||||
paramIndex++;
|
||||
logger.info(`회사별 REST API 연결 필터링: ${userCompanyCode}`);
|
||||
} else if (userCompanyCode === "*") {
|
||||
logger.info(`최고 관리자: 모든 REST API 연결 조회`);
|
||||
// 필터가 있으면 적용
|
||||
if (filter.company_code) {
|
||||
query += ` AND company_code = $${paramIndex}`;
|
||||
params.push(filter.company_code);
|
||||
paramIndex++;
|
||||
}
|
||||
} else {
|
||||
// userCompanyCode가 없는 경우 (하위 호환성)
|
||||
if (filter.company_code) {
|
||||
query += ` AND company_code = $${paramIndex}`;
|
||||
params.push(filter.company_code);
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 활성 상태 필터
|
||||
|
|
@ -105,10 +122,11 @@ export class ExternalRestApiConnectionService {
|
|||
* REST API 연결 상세 조회
|
||||
*/
|
||||
static async getConnectionById(
|
||||
id: number
|
||||
id: number,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalRestApiConnection>> {
|
||||
try {
|
||||
const query = `
|
||||
let query = `
|
||||
SELECT
|
||||
id, connection_name, description, base_url, default_headers,
|
||||
auth_type, auth_config, timeout, retry_count, retry_delay,
|
||||
|
|
@ -118,12 +136,20 @@ export class ExternalRestApiConnectionService {
|
|||
WHERE id = $1
|
||||
`;
|
||||
|
||||
const result: QueryResult<any> = await pool.query(query, [id]);
|
||||
const params: any[] = [id];
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
query += ` AND company_code = $2`;
|
||||
params.push(userCompanyCode);
|
||||
}
|
||||
|
||||
const result: QueryResult<any> = await pool.query(query, params);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: "연결을 찾을 수 없습니다.",
|
||||
message: "연결을 찾을 수 없거나 권한이 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -225,11 +251,12 @@ export class ExternalRestApiConnectionService {
|
|||
*/
|
||||
static async updateConnection(
|
||||
id: number,
|
||||
data: Partial<ExternalRestApiConnection>
|
||||
data: Partial<ExternalRestApiConnection>,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<ExternalRestApiConnection>> {
|
||||
try {
|
||||
// 기존 연결 확인
|
||||
const existing = await this.getConnectionById(id);
|
||||
// 기존 연결 확인 (회사 코드로 권한 체크)
|
||||
const existing = await this.getConnectionById(id, userCompanyCode);
|
||||
if (!existing.success) {
|
||||
return existing;
|
||||
}
|
||||
|
|
@ -353,24 +380,38 @@ export class ExternalRestApiConnectionService {
|
|||
/**
|
||||
* REST API 연결 삭제
|
||||
*/
|
||||
static async deleteConnection(id: number): Promise<ApiResponse<void>> {
|
||||
static async deleteConnection(
|
||||
id: number,
|
||||
userCompanyCode?: string
|
||||
): Promise<ApiResponse<void>> {
|
||||
try {
|
||||
const query = `
|
||||
let query = `
|
||||
DELETE FROM external_rest_api_connections
|
||||
WHERE id = $1
|
||||
RETURNING connection_name
|
||||
`;
|
||||
|
||||
const result: QueryResult<any> = await pool.query(query, [id]);
|
||||
const params: any[] = [id];
|
||||
|
||||
// 회사별 필터링 (최고 관리자가 아닌 경우)
|
||||
if (userCompanyCode && userCompanyCode !== "*") {
|
||||
query += ` AND company_code = $2`;
|
||||
params.push(userCompanyCode);
|
||||
}
|
||||
|
||||
query += ` RETURNING connection_name`;
|
||||
|
||||
const result: QueryResult<any> = await pool.query(query, params);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: "연결을 찾을 수 없습니다.",
|
||||
message: "연결을 찾을 수 없거나 권한이 없습니다.",
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(`REST API 연결 삭제 성공: ${result.rows[0].connection_name}`);
|
||||
logger.info(
|
||||
`REST API 연결 삭제 성공: ${result.rows[0].connection_name} (회사: ${userCompanyCode || "전체"})`
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue