fix(테이블관리): 블로커 5건 (PR-A) (#28)
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m26s
Build & Deploy to K8s / build-and-deploy (push) Successful in 8m26s
PR-A: 블로커 5건 일괄 수정
This commit was merged in pull request #28.
This commit is contained in:
@@ -29,10 +29,11 @@ public class DdlController {
|
||||
@PostMapping("/tables")
|
||||
public ResponseEntity<ApiResponse<?>> createTable(
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestAttribute("user_id") String userId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -65,10 +66,11 @@ public class DdlController {
|
||||
public ResponseEntity<ApiResponse<?>> addColumn(
|
||||
@PathVariable String tableName,
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestAttribute("user_id") String userId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -99,9 +101,10 @@ public class DdlController {
|
||||
@PathVariable String tableName,
|
||||
@PathVariable String columnName,
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestAttribute("user_id") String userId) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -124,9 +127,10 @@ public class DdlController {
|
||||
public ResponseEntity<ApiResponse<?>> dropTable(
|
||||
@PathVariable String tableName,
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestAttribute("user_id") String userId) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -147,9 +151,10 @@ public class DdlController {
|
||||
@PostMapping("/validate/table")
|
||||
public ResponseEntity<ApiResponse<?>> validateTableCreation(
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -176,11 +181,12 @@ public class DdlController {
|
||||
@GetMapping("/logs")
|
||||
public ResponseEntity<ApiResponse<?>> getDdlLogs(
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestParam(required = false, defaultValue = "50") int limit,
|
||||
@RequestParam(required = false) String userId,
|
||||
@RequestParam(required = false) String ddlType) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -195,10 +201,11 @@ public class DdlController {
|
||||
@GetMapping("/statistics")
|
||||
public ResponseEntity<ApiResponse<?>> getDdlStatistics(
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestParam(required = false) String fromDate,
|
||||
@RequestParam(required = false) String toDate) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -212,9 +219,10 @@ public class DdlController {
|
||||
@GetMapping("/tables/{tableName}/history")
|
||||
public ResponseEntity<ApiResponse<?>> getTableDdlHistory(
|
||||
@PathVariable String tableName,
|
||||
@RequestAttribute("company_code") String companyCode) {
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -230,9 +238,10 @@ public class DdlController {
|
||||
@GetMapping("/tables/{tableName}/info")
|
||||
public ResponseEntity<ApiResponse<?>> getTableInfo(
|
||||
@PathVariable String tableName,
|
||||
@RequestAttribute("company_code") String companyCode) {
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -255,9 +264,10 @@ public class DdlController {
|
||||
@DeleteMapping("/logs/cleanup")
|
||||
public ResponseEntity<ApiResponse<?>> cleanupOldLogs(
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role,
|
||||
@RequestParam(required = false, defaultValue = "90") int retentionDays) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -272,9 +282,10 @@ public class DdlController {
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public ResponseEntity<ApiResponse<?>> getInfo(
|
||||
@RequestAttribute("company_code") String companyCode) {
|
||||
@RequestAttribute("company_code") String companyCode,
|
||||
@RequestAttribute(value = "role", required = false) String role) {
|
||||
|
||||
if (!isSuperAdmin(companyCode)) {
|
||||
if (!isSuperAdmin(companyCode, role)) {
|
||||
return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다."));
|
||||
}
|
||||
|
||||
@@ -318,7 +329,9 @@ public class DdlController {
|
||||
// 내부 유틸
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private boolean isSuperAdmin(String companyCode) {
|
||||
return "*".equals(companyCode);
|
||||
private boolean isSuperAdmin(String companyCode, String role) {
|
||||
// company_code 가 '*' 이고 role 이 SUPER_ADMIN 둘 다 충족해야 통과 (이중 체크).
|
||||
// 토큰 변조 또는 회사코드만으로 super 권한이 발급되는 사고 방지.
|
||||
return "*".equals(companyCode) && "SUPER_ADMIN".equals(role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,21 @@ public class TableManagementService extends BaseService {
|
||||
params.put("display_column", "entity".equals(inputType) ? settings.get("display_column") : null);
|
||||
params.put("display_order", settings.getOrDefault("display_order", 0));
|
||||
params.put("is_visible", settings.getOrDefault("is_visible", true));
|
||||
// is_nullable: 'Y'/'N' 또는 null. null 이면 mapper 의 COALESCE 로 기존 값 유지.
|
||||
Object rawIsNullable = settings.get("is_nullable");
|
||||
if (rawIsNullable != null) {
|
||||
String s = rawIsNullable.toString();
|
||||
// 프론트가 'YES'/'NO' 또는 'Y'/'N' 어느 쪽이든 보낼 수 있어 정규화
|
||||
if ("NO".equalsIgnoreCase(s) || "N".equalsIgnoreCase(s) || "FALSE".equalsIgnoreCase(s)) {
|
||||
params.put("is_nullable", "N");
|
||||
} else if ("YES".equalsIgnoreCase(s) || "Y".equalsIgnoreCase(s) || "TRUE".equalsIgnoreCase(s)) {
|
||||
params.put("is_nullable", "Y");
|
||||
} else {
|
||||
params.put("is_nullable", null);
|
||||
}
|
||||
} else {
|
||||
params.put("is_nullable", null);
|
||||
}
|
||||
params.put("company_code", companyCode);
|
||||
params.put("category_ref", "category".equals(inputType) ? settings.get("category_ref") : null);
|
||||
sqlSession.update(NS + "upsertColumnSettings", params);
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
, #{display_column}
|
||||
, #{display_order}
|
||||
, #{is_visible}
|
||||
, 'Y'
|
||||
, COALESCE(#{is_nullable}, 'Y')
|
||||
, #{company_code}
|
||||
, #{category_ref}
|
||||
, NOW()
|
||||
@@ -318,6 +318,7 @@
|
||||
, DISPLAY_COLUMN = EXCLUDED.DISPLAY_COLUMN
|
||||
, DISPLAY_ORDER = COALESCE(EXCLUDED.DISPLAY_ORDER, TABLE_TYPE_COLUMNS.DISPLAY_ORDER)
|
||||
, IS_VISIBLE = COALESCE(EXCLUDED.IS_VISIBLE, TABLE_TYPE_COLUMNS.IS_VISIBLE)
|
||||
, IS_NULLABLE = COALESCE(EXCLUDED.IS_NULLABLE, TABLE_TYPE_COLUMNS.IS_NULLABLE)
|
||||
, CATEGORY_REF = EXCLUDED.CATEGORY_REF
|
||||
, UPDATED_DATE = NOW()
|
||||
</insert>
|
||||
|
||||
@@ -399,9 +399,28 @@ export default function TableManagementPage() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 저장 안 한 변경 사항이 있는지 — columns 와 originalColumns 의 reference 비교 (immutable update 패턴 의존)
|
||||
const hasUnsavedChanges = useMemo(() => {
|
||||
if (columns.length === 0 || originalColumns.length === 0) return false;
|
||||
if (columns.length !== originalColumns.length) return true;
|
||||
// 직렬화 비교 (얕은 ref 만으론 부족 — handleColumnChange 가 새 객체를 만들지만 다른 필드는 같은 ref 일 수 있어서)
|
||||
try {
|
||||
return JSON.stringify(columns) !== JSON.stringify(originalColumns);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [columns, originalColumns]);
|
||||
|
||||
// 테이블 선택
|
||||
const handleTableSelect = useCallback(
|
||||
(tableName: string) => {
|
||||
if (tableName === selectedTable) return;
|
||||
if (hasUnsavedChanges) {
|
||||
const ok = typeof window !== "undefined"
|
||||
? window.confirm("저장하지 않은 컬럼 변경 사항이 있습니다. 이동하면 변경 내용이 사라집니다. 계속할까요?")
|
||||
: true;
|
||||
if (!ok) return;
|
||||
}
|
||||
setSelectedTable(tableName);
|
||||
setCurrentPage(1);
|
||||
setColumns([]);
|
||||
@@ -416,7 +435,7 @@ export default function TableManagementPage() {
|
||||
loadColumnTypes(tableName, 1, pageSize);
|
||||
loadConstraints(tableName);
|
||||
},
|
||||
[loadColumnTypes, loadConstraints, pageSize, tables],
|
||||
[hasUnsavedChanges, loadColumnTypes, loadConstraints, pageSize, selectedTable, tables],
|
||||
);
|
||||
|
||||
// 입력 타입 변경 - 이전 타입의 설정값 초기화 포함
|
||||
@@ -1808,6 +1827,21 @@ export default function TableManagementPage() {
|
||||
handleInputTypeChange(selectedColumn, value as string);
|
||||
return;
|
||||
}
|
||||
// 그리드 칩과 동일하게 is_nullable/is_unique 는 즉시 저장
|
||||
if (field === "is_nullable") {
|
||||
const currentColumn = columns.find((c) => c.column_name === selectedColumn);
|
||||
if (currentColumn) {
|
||||
handleNullableToggle(selectedColumn, currentColumn.is_nullable || "YES");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (field === "is_unique") {
|
||||
const currentColumn = columns.find((c) => c.column_name === selectedColumn);
|
||||
if (currentColumn) {
|
||||
handleUniqueToggle(selectedColumn, currentColumn.is_unique || "NO");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (field === "reference_table" && value) {
|
||||
loadReferenceTableColumns(value as string);
|
||||
}
|
||||
@@ -1857,13 +1891,13 @@ export default function TableManagementPage() {
|
||||
setDuplicateSourceTable(null);
|
||||
}}
|
||||
mode={duplicateModalMode}
|
||||
sourceTableName={duplicateSourceTable || undefined}
|
||||
source_table_name={duplicateSourceTable || undefined}
|
||||
/>
|
||||
|
||||
<AddColumnModal
|
||||
isOpen={addColumnModalOpen}
|
||||
onClose={() => setAddColumnModalOpen(false)}
|
||||
tableName={selectedTable || ""}
|
||||
table_name={selectedTable || ""}
|
||||
onSuccess={async (result) => {
|
||||
toast.success("컬럼이 성공적으로 추가되었습니다!");
|
||||
// 테이블 목록 새로고침 (컬럼 수 업데이트)
|
||||
|
||||
Reference in New Issue
Block a user