Merge branch 'main' into feature/screen-management
This commit is contained in:
@@ -178,21 +178,24 @@ export class DashboardService {
|
||||
let params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// 회사 코드 필터링 (최우선)
|
||||
// 회사 코드 필터링 - company_code가 일치하면 해당 회사 사용자는 모두 조회 가능
|
||||
if (companyCode) {
|
||||
whereConditions.push(`d.company_code = $${paramIndex}`);
|
||||
params.push(companyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 권한 필터링
|
||||
if (userId) {
|
||||
if (companyCode === '*') {
|
||||
// 최고 관리자는 모든 대시보드 조회 가능
|
||||
} else {
|
||||
whereConditions.push(`d.company_code = $${paramIndex}`);
|
||||
params.push(companyCode);
|
||||
paramIndex++;
|
||||
}
|
||||
} else if (userId) {
|
||||
// 회사 코드 없이 userId만 있는 경우 (본인 생성 또는 공개)
|
||||
whereConditions.push(
|
||||
`(d.created_by = $${paramIndex} OR d.is_public = true)`
|
||||
);
|
||||
params.push(userId);
|
||||
paramIndex++;
|
||||
} else {
|
||||
// 비로그인 사용자는 공개 대시보드만
|
||||
whereConditions.push("d.is_public = true");
|
||||
}
|
||||
|
||||
@@ -228,7 +231,7 @@ export class DashboardService {
|
||||
|
||||
const whereClause = whereConditions.join(" AND ");
|
||||
|
||||
// 대시보드 목록 조회 (users 테이블 조인 제거)
|
||||
// 대시보드 목록 조회 (user_info 조인하여 생성자 이름 포함)
|
||||
const dashboardQuery = `
|
||||
SELECT
|
||||
d.id,
|
||||
@@ -242,13 +245,16 @@ export class DashboardService {
|
||||
d.tags,
|
||||
d.category,
|
||||
d.view_count,
|
||||
d.company_code,
|
||||
u.user_name as created_by_name,
|
||||
COUNT(de.id) as elements_count
|
||||
FROM dashboards d
|
||||
LEFT JOIN dashboard_elements de ON d.id = de.dashboard_id
|
||||
LEFT JOIN user_info u ON d.created_by = u.user_id
|
||||
WHERE ${whereClause}
|
||||
GROUP BY d.id, d.title, d.description, d.thumbnail_url, d.is_public,
|
||||
d.created_by, d.created_at, d.updated_at, d.tags, d.category,
|
||||
d.view_count
|
||||
d.view_count, d.company_code, u.user_name
|
||||
ORDER BY d.updated_at DESC
|
||||
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
||||
`;
|
||||
@@ -277,12 +283,14 @@ export class DashboardService {
|
||||
thumbnailUrl: row.thumbnail_url,
|
||||
isPublic: row.is_public,
|
||||
createdBy: row.created_by,
|
||||
createdByName: row.created_by_name || row.created_by,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
tags: JSON.parse(row.tags || "[]"),
|
||||
category: row.category,
|
||||
viewCount: parseInt(row.view_count || "0"),
|
||||
elementsCount: parseInt(row.elements_count || "0"),
|
||||
companyCode: row.company_code,
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
|
||||
@@ -124,6 +124,14 @@ export class BatchSchedulerService {
|
||||
try {
|
||||
logger.info(`배치 실행 시작: ${config.batch_name} (ID: ${config.id})`);
|
||||
|
||||
// 매핑 정보가 없으면 상세 조회로 다시 가져오기
|
||||
if (!config.batch_mappings || config.batch_mappings.length === 0) {
|
||||
const fullConfig = await BatchService.getBatchConfigById(config.id);
|
||||
if (fullConfig.success && fullConfig.data) {
|
||||
config = fullConfig.data;
|
||||
}
|
||||
}
|
||||
|
||||
// 실행 로그 생성
|
||||
const executionLogResponse =
|
||||
await BatchExecutionLogService.createExecutionLog({
|
||||
|
||||
@@ -474,6 +474,105 @@ export class ExternalRestApiConnectionService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증 헤더 생성
|
||||
*/
|
||||
static async getAuthHeaders(
|
||||
authType: AuthType,
|
||||
authConfig: any,
|
||||
companyCode?: string
|
||||
): Promise<Record<string, string>> {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (authType === "db-token") {
|
||||
const cfg = authConfig || {};
|
||||
const {
|
||||
dbTableName,
|
||||
dbValueColumn,
|
||||
dbWhereColumn,
|
||||
dbWhereValue,
|
||||
dbHeaderName,
|
||||
dbHeaderTemplate,
|
||||
} = cfg;
|
||||
|
||||
if (!dbTableName || !dbValueColumn) {
|
||||
throw new Error("DB 토큰 설정이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
if (!companyCode) {
|
||||
throw new Error("DB 토큰 모드에서는 회사 코드가 필요합니다.");
|
||||
}
|
||||
|
||||
const hasWhereColumn = !!dbWhereColumn;
|
||||
const hasWhereValue =
|
||||
dbWhereValue !== undefined &&
|
||||
dbWhereValue !== null &&
|
||||
dbWhereValue !== "";
|
||||
|
||||
// where 컬럼/값은 둘 다 비우거나 둘 다 채워야 함
|
||||
if (hasWhereColumn !== hasWhereValue) {
|
||||
throw new Error(
|
||||
"DB 토큰 설정에서 조건 컬럼과 조건 값은 둘 다 비우거나 둘 다 입력해야 합니다."
|
||||
);
|
||||
}
|
||||
|
||||
// 식별자 검증 (간단한 화이트리스트)
|
||||
const identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
if (
|
||||
!identifierRegex.test(dbTableName) ||
|
||||
!identifierRegex.test(dbValueColumn) ||
|
||||
(hasWhereColumn && !identifierRegex.test(dbWhereColumn as string))
|
||||
) {
|
||||
throw new Error(
|
||||
"DB 토큰 설정에 유효하지 않은 테이블 또는 컬럼명이 포함되어 있습니다."
|
||||
);
|
||||
}
|
||||
|
||||
let sql = `
|
||||
SELECT ${dbValueColumn} AS token_value
|
||||
FROM ${dbTableName}
|
||||
WHERE company_code = $1
|
||||
`;
|
||||
|
||||
const params: any[] = [companyCode];
|
||||
|
||||
if (hasWhereColumn && hasWhereValue) {
|
||||
sql += ` AND ${dbWhereColumn} = $2`;
|
||||
params.push(dbWhereValue);
|
||||
}
|
||||
|
||||
sql += `
|
||||
ORDER BY updated_date DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const tokenResult: QueryResult<any> = await pool.query(sql, params);
|
||||
|
||||
if (tokenResult.rowCount === 0) {
|
||||
throw new Error("DB에서 토큰을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
const tokenValue = tokenResult.rows[0]["token_value"];
|
||||
const headerName = dbHeaderName || "Authorization";
|
||||
const template = dbHeaderTemplate || "Bearer {{value}}";
|
||||
|
||||
headers[headerName] = template.replace("{{value}}", tokenValue);
|
||||
} else if (authType === "bearer" && authConfig?.token) {
|
||||
headers["Authorization"] = `Bearer ${authConfig.token}`;
|
||||
} else if (authType === "basic" && authConfig) {
|
||||
const credentials = Buffer.from(
|
||||
`${authConfig.username}:${authConfig.password}`
|
||||
).toString("base64");
|
||||
headers["Authorization"] = `Basic ${credentials}`;
|
||||
} else if (authType === "api-key" && authConfig) {
|
||||
if (authConfig.keyLocation === "header") {
|
||||
headers[authConfig.keyName] = authConfig.keyValue;
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API 연결 테스트 (테스트 요청 데이터 기반)
|
||||
*/
|
||||
@@ -485,99 +584,15 @@ export class ExternalRestApiConnectionService {
|
||||
|
||||
try {
|
||||
// 헤더 구성
|
||||
const headers = { ...testRequest.headers };
|
||||
let headers = { ...testRequest.headers };
|
||||
|
||||
// 인증 헤더 추가
|
||||
if (testRequest.auth_type === "db-token") {
|
||||
const cfg = testRequest.auth_config || {};
|
||||
const {
|
||||
dbTableName,
|
||||
dbValueColumn,
|
||||
dbWhereColumn,
|
||||
dbWhereValue,
|
||||
dbHeaderName,
|
||||
dbHeaderTemplate,
|
||||
} = cfg;
|
||||
|
||||
if (!dbTableName || !dbValueColumn) {
|
||||
throw new Error("DB 토큰 설정이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
if (!userCompanyCode) {
|
||||
throw new Error("DB 토큰 모드에서는 회사 코드가 필요합니다.");
|
||||
}
|
||||
|
||||
const hasWhereColumn = !!dbWhereColumn;
|
||||
const hasWhereValue =
|
||||
dbWhereValue !== undefined && dbWhereValue !== null && dbWhereValue !== "";
|
||||
|
||||
// where 컬럼/값은 둘 다 비우거나 둘 다 채워야 함
|
||||
if (hasWhereColumn !== hasWhereValue) {
|
||||
throw new Error(
|
||||
"DB 토큰 설정에서 조건 컬럼과 조건 값은 둘 다 비우거나 둘 다 입력해야 합니다."
|
||||
);
|
||||
}
|
||||
|
||||
// 식별자 검증 (간단한 화이트리스트)
|
||||
const identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
||||
if (
|
||||
!identifierRegex.test(dbTableName) ||
|
||||
!identifierRegex.test(dbValueColumn) ||
|
||||
(hasWhereColumn && !identifierRegex.test(dbWhereColumn as string))
|
||||
) {
|
||||
throw new Error(
|
||||
"DB 토큰 설정에 유효하지 않은 테이블 또는 컬럼명이 포함되어 있습니다."
|
||||
);
|
||||
}
|
||||
|
||||
let sql = `
|
||||
SELECT ${dbValueColumn} AS token_value
|
||||
FROM ${dbTableName}
|
||||
WHERE company_code = $1
|
||||
`;
|
||||
|
||||
const params: any[] = [userCompanyCode];
|
||||
|
||||
if (hasWhereColumn && hasWhereValue) {
|
||||
sql += ` AND ${dbWhereColumn} = $2`;
|
||||
params.push(dbWhereValue);
|
||||
}
|
||||
|
||||
sql += `
|
||||
ORDER BY updated_date DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const tokenResult: QueryResult<any> = await pool.query(sql, params);
|
||||
|
||||
if (tokenResult.rowCount === 0) {
|
||||
throw new Error("DB에서 토큰을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
const tokenValue = tokenResult.rows[0]["token_value"];
|
||||
const headerName = dbHeaderName || "Authorization";
|
||||
const template = dbHeaderTemplate || "Bearer {{value}}";
|
||||
|
||||
headers[headerName] = template.replace("{{value}}", tokenValue);
|
||||
} else if (
|
||||
testRequest.auth_type === "bearer" &&
|
||||
testRequest.auth_config?.token
|
||||
) {
|
||||
headers["Authorization"] = `Bearer ${testRequest.auth_config.token}`;
|
||||
} else if (testRequest.auth_type === "basic" && testRequest.auth_config) {
|
||||
const credentials = Buffer.from(
|
||||
`${testRequest.auth_config.username}:${testRequest.auth_config.password}`
|
||||
).toString("base64");
|
||||
headers["Authorization"] = `Basic ${credentials}`;
|
||||
} else if (
|
||||
testRequest.auth_type === "api-key" &&
|
||||
testRequest.auth_config
|
||||
) {
|
||||
if (testRequest.auth_config.keyLocation === "header") {
|
||||
headers[testRequest.auth_config.keyName] =
|
||||
testRequest.auth_config.keyValue;
|
||||
}
|
||||
}
|
||||
// 인증 헤더 생성 및 병합
|
||||
const authHeaders = await this.getAuthHeaders(
|
||||
testRequest.auth_type,
|
||||
testRequest.auth_config,
|
||||
userCompanyCode
|
||||
);
|
||||
headers = { ...headers, ...authHeaders };
|
||||
|
||||
// URL 구성
|
||||
let url = testRequest.base_url;
|
||||
|
||||
Reference in New Issue
Block a user