Merge remote-tracking branch 'upstream/main'
All checks were successful
Build and Push Images / build-and-push (push) Successful in 10m13s
All checks were successful
Build and Push Images / build-and-push (push) Successful in 10m13s
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import "dotenv/config";
|
||||
process.env.TZ = "Asia/Seoul";
|
||||
import "express-async-errors"; // async 라우트 핸들러의 에러를 Express 에러 핸들러로 자동 전달
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
|
||||
@@ -10,7 +10,7 @@ export const getAuditLogs = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
const isSuperAdmin = userCompanyCode === "*";
|
||||
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
|
||||
|
||||
const {
|
||||
companyCode,
|
||||
@@ -63,7 +63,7 @@ export const getAuditLogStats = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
const isSuperAdmin = userCompanyCode === "*";
|
||||
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
|
||||
const { companyCode, days } = req.query;
|
||||
|
||||
const targetCompany = isSuperAdmin
|
||||
@@ -91,7 +91,7 @@ export const getAuditLogUsers = async (
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const userCompanyCode = req.user?.companyCode;
|
||||
const isSuperAdmin = userCompanyCode === "*";
|
||||
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
|
||||
const { companyCode } = req.query;
|
||||
|
||||
const conditions: string[] = ["LOWER(u.status) = 'active'"];
|
||||
|
||||
@@ -224,6 +224,31 @@ export async function updateColumnSettings(
|
||||
`컬럼 설정 업데이트 완료: ${tableName}.${columnName}, company: ${companyCode}`
|
||||
);
|
||||
|
||||
auditLogService.log({
|
||||
companyCode: companyCode || "",
|
||||
userId: req.user?.userId || "",
|
||||
userName: req.user?.userName || "",
|
||||
action: "UPDATE",
|
||||
resourceType: "TABLE",
|
||||
resourceId: `${tableName}.${columnName}`,
|
||||
resourceName: settings.columnLabel || columnName,
|
||||
tableName: "table_type_columns",
|
||||
summary: `테이블 타입관리: ${tableName}.${columnName} 컬럼 설정 변경`,
|
||||
changes: {
|
||||
after: {
|
||||
columnLabel: settings.columnLabel,
|
||||
inputType: settings.inputType,
|
||||
referenceTable: settings.referenceTable,
|
||||
referenceColumn: settings.referenceColumn,
|
||||
displayColumn: settings.displayColumn,
|
||||
codeCategory: settings.codeCategory,
|
||||
},
|
||||
fields: ["columnLabel", "inputType", "referenceTable", "referenceColumn", "displayColumn", "codeCategory"],
|
||||
},
|
||||
ipAddress: getClientIp(req),
|
||||
requestPath: req.originalUrl,
|
||||
});
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
message: "컬럼 설정을 성공적으로 저장했습니다.",
|
||||
@@ -339,6 +364,29 @@ export async function updateAllColumnSettings(
|
||||
`전체 컬럼 설정 일괄 업데이트 완료: ${tableName}, ${columnSettings.length}개, company: ${companyCode}`
|
||||
);
|
||||
|
||||
const changedColumns = columnSettings
|
||||
.filter((c) => c.columnName)
|
||||
.map((c) => c.columnName)
|
||||
.join(", ");
|
||||
|
||||
auditLogService.log({
|
||||
companyCode: companyCode || "",
|
||||
userId: req.user?.userId || "",
|
||||
userName: req.user?.userName || "",
|
||||
action: "BATCH_UPDATE",
|
||||
resourceType: "TABLE",
|
||||
resourceId: tableName,
|
||||
resourceName: tableName,
|
||||
tableName: "table_type_columns",
|
||||
summary: `테이블 타입관리: ${tableName} 전체 컬럼 설정 일괄 변경 (${columnSettings.length}개)`,
|
||||
changes: {
|
||||
after: { columns: changedColumns, count: columnSettings.length },
|
||||
fields: columnSettings.filter((c) => c.columnName).map((c) => c.columnName!),
|
||||
},
|
||||
ipAddress: getClientIp(req),
|
||||
requestPath: req.originalUrl,
|
||||
});
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
message: "모든 컬럼 설정을 성공적으로 저장했습니다.",
|
||||
|
||||
@@ -66,8 +66,9 @@ export const initializePool = (): Pool => {
|
||||
|
||||
// 연결 풀 이벤트 핸들러
|
||||
pool.on("connect", (client) => {
|
||||
client.query("SET timezone = 'Asia/Seoul'");
|
||||
if (config.debug) {
|
||||
console.log("✅ PostgreSQL 클라이언트 연결 생성");
|
||||
console.log("✅ PostgreSQL 클라이언트 연결 생성 (timezone: Asia/Seoul)");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -251,6 +251,28 @@ class AuditLogService {
|
||||
[...params, limit, offset]
|
||||
);
|
||||
|
||||
const SECURITY_MASK = "(보안 항목 - 값 비공개)";
|
||||
const securedTables = ["table_type_columns"];
|
||||
|
||||
if (!isSuperAdmin) {
|
||||
for (const entry of data) {
|
||||
if (entry.table_name && securedTables.includes(entry.table_name) && entry.changes) {
|
||||
const changes = typeof entry.changes === "string" ? JSON.parse(entry.changes) : entry.changes;
|
||||
if (changes.before) {
|
||||
for (const key of Object.keys(changes.before)) {
|
||||
changes.before[key] = SECURITY_MASK;
|
||||
}
|
||||
}
|
||||
if (changes.after) {
|
||||
for (const key of Object.keys(changes.after)) {
|
||||
changes.after[key] = SECURITY_MASK;
|
||||
}
|
||||
}
|
||||
entry.changes = changes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { data, total };
|
||||
}
|
||||
|
||||
|
||||
@@ -1707,71 +1707,66 @@ export class DynamicFormService {
|
||||
try {
|
||||
console.log(`🎯 제어관리 설정 확인 중... (screenId: ${screenId})`);
|
||||
|
||||
// 화면의 저장 버튼에서 제어관리 설정 조회
|
||||
const screenLayouts = await query<{
|
||||
component_id: string;
|
||||
properties: any;
|
||||
// V2 레이아웃에서 layout_data jsonb 조회
|
||||
const v2Layouts = await query<{
|
||||
layout_id: number;
|
||||
layout_data: any;
|
||||
}>(
|
||||
`SELECT component_id, properties
|
||||
FROM screen_layouts
|
||||
WHERE screen_id = $1
|
||||
AND component_type IN ('component', 'v2-button-primary')`,
|
||||
[screenId]
|
||||
`SELECT layout_id, layout_data
|
||||
FROM screen_layouts_v2
|
||||
WHERE screen_id = $1 AND company_code = $2`,
|
||||
[screenId, companyCode]
|
||||
);
|
||||
|
||||
console.log(`📋 화면 컴포넌트 조회 결과:`, screenLayouts.length);
|
||||
if (v2Layouts.length === 0) {
|
||||
console.log(`ℹ️ V2 레이아웃이 없습니다. (화면 ID: ${screenId}, company: ${companyCode})`);
|
||||
return;
|
||||
}
|
||||
|
||||
// layout_data.components 배열에서 버튼 컴포넌트 추출
|
||||
const layoutData = v2Layouts[0].layout_data;
|
||||
const components: any[] = layoutData?.components || [];
|
||||
|
||||
console.log(`📋 V2 컴포넌트 조회 결과: ${components.length}개`);
|
||||
|
||||
// 저장 버튼 중에서 제어관리가 활성화된 것 찾기
|
||||
let controlConfigFound = false;
|
||||
for (const layout of screenLayouts) {
|
||||
const properties = layout.properties as any;
|
||||
for (const comp of components) {
|
||||
const overrides = comp?.overrides || {};
|
||||
|
||||
// 디버깅: 모든 컴포넌트 정보 출력
|
||||
console.log(`🔍 컴포넌트 검사:`, {
|
||||
componentId: layout.component_id,
|
||||
componentType: properties?.componentType,
|
||||
actionType: properties?.componentConfig?.action?.type,
|
||||
enableDataflowControl:
|
||||
properties?.webTypeConfig?.enableDataflowControl,
|
||||
hasDataflowConfig: !!properties?.webTypeConfig?.dataflowConfig,
|
||||
hasDiagramId:
|
||||
!!properties?.webTypeConfig?.dataflowConfig?.selectedDiagramId,
|
||||
hasFlowControls:
|
||||
!!properties?.webTypeConfig?.dataflowConfig?.flowControls,
|
||||
});
|
||||
const isButtonComponent =
|
||||
overrides?.type === "v2-button-primary" ||
|
||||
(comp?.url || "").includes("v2-button-primary");
|
||||
|
||||
// 버튼 컴포넌트이고 제어관리가 활성화된 경우
|
||||
// triggerType에 맞는 액션 타입 매칭: insert/update -> save, delete -> delete
|
||||
const buttonActionType = properties?.componentConfig?.action?.type;
|
||||
const buttonActionType = overrides?.action?.type;
|
||||
const isMatchingAction =
|
||||
(triggerType === "delete" && buttonActionType === "delete") ||
|
||||
((triggerType === "insert" || triggerType === "update") && buttonActionType === "save");
|
||||
|
||||
const isButtonComponent =
|
||||
properties?.componentType === "button-primary" ||
|
||||
properties?.componentType === "v2-button-primary";
|
||||
|
||||
|
||||
console.log(`🔍 V2 컴포넌트 검사:`, {
|
||||
componentId: comp?.id,
|
||||
type: overrides?.type,
|
||||
actionType: buttonActionType,
|
||||
enableDataflowControl: overrides?.enableDataflowControl,
|
||||
hasDataflowConfig: !!overrides?.dataflowConfig,
|
||||
});
|
||||
|
||||
if (
|
||||
isButtonComponent &&
|
||||
isMatchingAction &&
|
||||
properties?.webTypeConfig?.enableDataflowControl === true
|
||||
overrides?.enableDataflowControl === true
|
||||
) {
|
||||
const dataflowConfig = properties?.webTypeConfig?.dataflowConfig;
|
||||
|
||||
// 다중 제어 설정 확인 (flowControls 배열)
|
||||
const dataflowConfig = overrides?.dataflowConfig;
|
||||
const flowControls = dataflowConfig?.flowControls || [];
|
||||
|
||||
// flowControls가 있으면 다중 제어 실행, 없으면 기존 단일 제어 실행
|
||||
if (flowControls.length > 0) {
|
||||
controlConfigFound = true;
|
||||
console.log(`🎯 다중 제어관리 설정 발견: ${flowControls.length}개`);
|
||||
|
||||
// 순서대로 정렬
|
||||
const sortedControls = [...flowControls].sort(
|
||||
(a: any, b: any) => (a.order || 0) - (b.order || 0)
|
||||
);
|
||||
|
||||
// 다중 제어 순차 실행
|
||||
await this.executeMultipleFlowControls(
|
||||
sortedControls,
|
||||
savedData,
|
||||
@@ -1782,13 +1777,12 @@ export class DynamicFormService {
|
||||
companyCode
|
||||
);
|
||||
} else if (dataflowConfig?.selectedDiagramId) {
|
||||
// 기존 단일 제어 실행 (하위 호환성)
|
||||
controlConfigFound = true;
|
||||
const diagramId = dataflowConfig.selectedDiagramId;
|
||||
const relationshipId = dataflowConfig.selectedRelationshipId;
|
||||
|
||||
console.log(`🎯 단일 제어관리 설정 발견:`, {
|
||||
componentId: layout.component_id,
|
||||
componentId: comp?.id,
|
||||
diagramId,
|
||||
relationshipId,
|
||||
triggerType,
|
||||
@@ -1806,7 +1800,6 @@ export class DynamicFormService {
|
||||
);
|
||||
}
|
||||
|
||||
// 첫 번째 설정된 버튼의 제어관리만 실행
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user