diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java b/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java index 0a4a17d..7922dbd 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/domain/protectedobject/DatabaseObjectOption.java @@ -13,4 +13,8 @@ public record DatabaseObjectOption( public String label() { return owner + "." + objectName + " (" + objectType + ")"; } + + public String defaultOrdsPath() { + return owner.toLowerCase() + "/" + objectName.toLowerCase(); + } } diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java index f49d95e..e1cfcf6 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java @@ -51,14 +51,15 @@ public class ProtectedObjectService { @Transactional public void createObject(ProtectedObjectCreateCommand command) { + ProtectedObjectCreateCommand normalized = normalizeCreateCommand(command); long objectId = mapper.nextObjectId(); - mapper.insertObject(objectId, command); - Set sensitive = splitCsv(command.sensitiveColumns()); - for (String column : splitCsv(command.columns())) { + mapper.insertObject(objectId, normalized); + Set sensitive = splitCsv(normalized.sensitiveColumns()); + for (String column : splitCsv(normalized.columns())) { mapper.insertColumn(mapper.nextColumnId(), objectId, column, sensitive.contains(column) ? "Y" : "N"); } auditService.record(new AuditEvent("PROTECTED_OBJECT_CREATED", null, objectId, "SUCCESS", null, null, - command.objectName())); + normalized.objectName())); } @Transactional @@ -92,6 +93,20 @@ public class ProtectedObjectService { return schemaPath + "/" + objectName.toLowerCase(Locale.ROOT); } + private ProtectedObjectCreateCommand normalizeCreateCommand(ProtectedObjectCreateCommand command) { + String owner = command.owner().trim().toUpperCase(Locale.ROOT); + String objectName = command.objectName().trim().toUpperCase(Locale.ROOT); + String ordsPath = command.ordsPath(); + if (ordsPath == null || ordsPath.isBlank()) { + ordsPath = defaultOrdsPath(owner, objectName); + } + String columns = command.columns(); + if (columns == null || columns.isBlank()) { + columns = String.join(",", mapper.findDatabaseColumns(owner, objectName)); + } + return new ProtectedObjectCreateCommand(owner, objectName, ordsPath.trim(), columns, command.sensitiveColumns()); + } + @Transactional public void disableObject(long objectId) { int updated = mapper.disableObject(objectId); diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java b/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java index fb45332..447e492 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/web/ProtectedObjectController.java @@ -21,6 +21,7 @@ public class ProtectedObjectController { @GetMapping("/objects") public String objects(Model model) { model.addAttribute("objects", protectedObjectService.findEnabled()); + model.addAttribute("dbObjects", protectedObjectService.findDatabaseObjects()); return "objects"; } diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 85ed874..747c62d 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -60,6 +60,43 @@ async function syncRuleColumnOptions() { } } +async function syncObjectCatalogSelection() { + const catalog = document.getElementById('objectCatalogSelect'); + if (!catalog || !catalog.value) { + return; + } + const selected = catalog.options[catalog.selectedIndex]; + const ownerInput = document.querySelector('input[name="owner"]'); + const objectInput = document.querySelector('input[name="objectName"]'); + const ordsPathInput = document.querySelector('input[name="ordsPath"]'); + const columnsInput = document.querySelector('input[name="columns"]'); + if (ownerInput) { + ownerInput.value = selected.dataset.owner || ''; + } + if (objectInput) { + objectInput.value = selected.dataset.objectName || ''; + } + if (ordsPathInput) { + ordsPathInput.value = selected.dataset.defaultPath || ''; + } + if (!columnsInput) { + return; + } + columnsInput.value = ''; + try { + const response = await fetch(`/permissions/object-columns?objectRef=${encodeURIComponent(`db:${catalog.value}`)}`); + if (!response.ok) { + return; + } + const columns = await response.json(); + if (Array.isArray(columns)) { + columnsInput.value = columns.join(','); + } + } catch (error) { + columnsInput.value = ''; + } +} + document.addEventListener('DOMContentLoaded', () => { const master = document.getElementById('userRoleMaster'); if (master) { @@ -71,6 +108,10 @@ document.addEventListener('DOMContentLoaded', () => { objectSelect.addEventListener('change', syncRuleColumnOptions); syncRuleColumnOptions(); } + const catalog = document.getElementById('objectCatalogSelect'); + if (catalog) { + catalog.addEventListener('change', syncObjectCatalogSelection); + } document.querySelectorAll('[data-rule-add]').forEach((button) => { button.addEventListener('click', () => { const row = button.closest('.rule-row'); diff --git a/src/main/resources/templates/objects.html b/src/main/resources/templates/objects.html index 1f40dc8..d4ca353 100644 --- a/src/main/resources/templates/objects.html +++ b/src/main/resources/templates/objects.html @@ -6,7 +6,7 @@

테이블/뷰 관리

-

권한에 사용할 보호 객체와 ORDS 경로를 등록합니다.

+

권한에 사용할 보호 객체와 이미 구성된 ORDS 조회 경로를 매핑합니다.

@@ -15,17 +15,29 @@

테이블/뷰 추가

+