Compare commits

11 Commits

14 changed files with 308 additions and 179 deletions

View File

@@ -130,6 +130,69 @@ begin
)');
create_if_missing('create sequence sg_permission_seq start with 1 increment by 1 nocache');
create_if_missing('create sequence sg_permission_rule_seq start with 1 increment by 1 nocache');
create_if_missing('create table sg_vpd_policy_note (
object_owner varchar2(128) not null,
object_name varchar2(128) not null,
policy_name varchar2(128) not null,
description varchar2(2000),
updated_at timestamp default systimestamp not null,
constraint sg_vpd_policy_note_pk primary key (object_owner, object_name, policy_name)
)');
create_if_missing('create table sg_vpd_filter_note (
function_owner varchar2(128) not null,
function_name varchar2(128) not null,
description varchar2(2000),
updated_at timestamp default systimestamp not null,
constraint sg_vpd_filter_note_pk primary key (function_owner, function_name)
)');
create_if_missing('create table sg_masking_rule (
rule_id number primary key,
rule_code varchar2(100) not null unique,
rule_name varchar2(200) not null,
template_code varchar2(100) not null,
description varchar2(2000),
enabled_yn char(1) default ''Y'' not null,
created_at timestamp default systimestamp not null,
updated_at timestamp default systimestamp not null,
constraint sg_masking_rule_enabled_ck check (enabled_yn in (''Y'', ''N''))
)');
create_if_missing('create table sg_access_bearer_token (
key_id number primary key, user_id number not null, key_prefix varchar2(100) not null,
key_hash varchar2(256) not null unique, expires_at timestamp not null, revoked_at timestamp,
description varchar2(500), created_at timestamp default systimestamp not null,
constraint sg_access_bearer_token_user_fk foreign key (user_id) references sg_app_user(user_id)
)');
create_if_missing('create table sg_backoffice_setting (
setting_key varchar2(200) primary key, setting_value varchar2(4000),
updated_at timestamp default systimestamp not null
)');
create_if_missing('create table sg_vector_document_chunk (
chunk_id number primary key, document_id varchar2(200) not null, chunk_no number not null,
content clob not null, embedding vector(1536, float32), created_at timestamp default systimestamp not null
)');
create_if_missing('create table sg_vector_document_tag (
chunk_id number not null, tech_tag varchar2(200) not null,
constraint sg_vector_document_tag_pk primary key (chunk_id, tech_tag),
constraint sg_vector_document_tag_chunk_fk foreign key (chunk_id) references sg_vector_document_chunk(chunk_id)
)');
create_if_missing('create sequence sg_vector_chunk_seq start with 1 increment by 1 nocache');
create_if_missing('create table sg_column_masking_rule (
column_id number primary key,
rule_id number not null,
updated_at timestamp default systimestamp not null,
constraint sg_column_masking_rule_column_fk foreign key (column_id) references sg_protected_column(column_id),
constraint sg_column_masking_rule_rule_fk foreign key (rule_id) references sg_masking_rule(rule_id)
)');
create_if_missing('create table sg_user_masking_rule (
user_id number not null,
column_id number not null,
decision varchar2(30) not null,
active_yn char(1) default ''Y'' not null,
updated_at timestamp default systimestamp not null,
constraint sg_user_masking_rule_pk primary key (user_id, column_id),
constraint sg_user_masking_rule_user_fk foreign key (user_id) references sg_app_user(user_id),
constraint sg_user_masking_rule_column_fk foreign key (column_id) references sg_protected_column(column_id)
)');
end;
/
@@ -168,3 +231,99 @@ merge into sg_group_role t using (select 2001 group_id, 3002 role_id from dual)
on (t.group_id=s.group_id and t.role_id=s.role_id) when not matched then insert (group_id, role_id) values (s.group_id, s.role_id);
commit;
-- Compatibility layer for remaining backoffice modules. The data is stored
-- only in SG_* tables; these views prevent older controller paths from
-- querying non-existent CB_* physical tables during the Smilegate transition.
create or replace view cb_app_user as
select user_id, user_name, employee_no, dept_code, can_read_contents, active from sg_app_user;
create or replace view cb_app_group as
select group_id, group_code, group_name, description, active_yn from sg_app_group;
create or replace view cb_app_role as
select role_id, role_name, max_sensitivity_level from sg_app_role;
create or replace view cb_user_role as select user_id, role_id from sg_user_role;
create or replace view cb_user_group as select group_id, user_id from sg_user_group;
create or replace view cb_group_role as select group_id, role_id from sg_group_role;
create or replace view cb_protected_object as
select object_id, owner, object_name, ords_path, enabled_yn, description from sg_protected_object;
create or replace view cb_protected_column as
select column_id, object_id, column_name, sensitive_yn, visible_role_id, sensitivity_level, redaction_method from sg_protected_column;
create or replace view cb_permission as
select perm_id, role_id, target_name, action_name, permission_effect from sg_permission;
create or replace view cb_permission_rule as
select rule_id, perm_id, rule_column, rule_type, rule_value from sg_permission_rule;
create or replace view cb_permission_column as
select permission_id, column_name from sg_permission_column;
create or replace view cb_vpd_policy_note as
select object_owner, object_name, policy_name, description, updated_at from sg_vpd_policy_note;
create or replace view cb_vpd_filter_note as
select function_owner, function_name, description, updated_at from sg_vpd_filter_note;
create or replace view cb_masking_rule as
select rule_id, rule_code, rule_name, template_code, description, enabled_yn from sg_masking_rule;
create or replace view cb_column_masking_rule as
select column_id, rule_id, updated_at from sg_column_masking_rule;
create or replace view cb_user_masking_rule as
select user_id, column_id, decision, active_yn, updated_at from sg_user_masking_rule;
create or replace view cb_ords_probe_audit as
select audit_id, event_type, key_id, object_id, status, row_count, error_code, message, created_at from sg_audit_event;
create or replace view cb_backoffice_setting as
select setting_key, setting_value, updated_at from sg_backoffice_setting;
-- PoC administrator access: both demo operators can manage and query every
-- Smilegate game-data object registered in SGMP_POC.
merge into sg_app_role t
using (select 3099 role_id, 'DATA_AI_POC_ADMIN' role_name,
'Full access to all Smilegate PoC game-data objects' description,
'RESTRICTED' max_sensitivity_level from dual) s
on (t.role_id = s.role_id)
when matched then update set t.role_name=s.role_name, t.description=s.description,
t.max_sensitivity_level=s.max_sensitivity_level, t.updated_at=systimestamp
when not matched then insert (role_id, role_name, description, max_sensitivity_level)
values (s.role_id, s.role_name, s.description, s.max_sensitivity_level);
merge into sg_user_role t
using (select 1001 user_id, 3099 role_id from dual union all select 1002, 3099 from dual) s
on (t.user_id=s.user_id and t.role_id=s.role_id)
when not matched then insert (user_id, role_id) values (s.user_id, s.role_id);
declare
l_object_id number;
l_permission_id number;
begin
for source_object in (
select table_name
from all_tables
where owner = 'SGMP_POC'
and table_name not in ('SEMANTIC_METADATA_CHANGE_LOG', 'SGMP_TERM_CONTEXT_CACHE',
'SGMP_TERM_DICTIONARY', 'SGMP_TERM_SEARCH_LOG', 'SGMP_TERM_SYNONYM')
order by table_name
) loop
begin
select object_id into l_object_id
from sg_protected_object
where owner = 'SGMP_POC' and object_name = source_object.table_name;
exception
when no_data_found then
select nvl(max(object_id), 0) + 1 into l_object_id from sg_protected_object;
insert into sg_protected_object (object_id, owner, object_name, ords_path, enabled_yn, description)
values (l_object_id, 'SGMP_POC', source_object.table_name,
'/sgmp-poc/' || lower(source_object.table_name), 'Y',
'Smilegate PoC game-data object');
end;
begin
select perm_id into l_permission_id
from sg_permission
where role_id = 3099 and target_name = source_object.table_name;
exception
when no_data_found then
l_permission_id := sg_permission_seq.nextval;
insert into sg_permission (perm_id, role_id, target_name, action_name, permission_effect)
values (l_permission_id, 3099, source_object.table_name, 'SELECT', 'ALLOW');
insert into sg_permission_rule (rule_id, perm_id, rule_column, rule_type, rule_value)
values (sg_permission_rule_seq.nextval, l_permission_id, null, 'ALL', null);
end;
end loop;
commit;
end;
/

View File

@@ -37,7 +37,7 @@ public class SecurityConfig {
.rememberMeCookieName("VPD_REMEMBER_ME")
.tokenValiditySeconds(security.rememberMeValiditySeconds())
.useSecureCookie(true)
.alwaysRemember(false));
.alwaysRemember(true));
}
return http

View File

@@ -18,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class SchemaMetadataService {
private static final String OWNER = "POC_2";
private static final String OWNER = "SGMP_POC";
private static final int MAX_COMMENT_LENGTH = 4000;
private static final int MAX_ANNOTATION_VALUE_LENGTH = 4000;
private static final Pattern ORACLE_SIMPLE_NAME = Pattern.compile("[A-Z][A-Z0-9_$#]{0,127}");

View File

@@ -11,16 +11,16 @@ import org.springframework.stereotype.Service;
@Service
public class StructuredDataService {
private static final String OWNER = "POC_2";
private static final String OWNER = "SGMP_POC";
private static final int ROW_LIMIT = 50;
private static final List<StructuredDataTable> TABLES = List.of(
new StructuredDataTable("customers", "KB_CUSTOMERS", "고객원장", "고객 기본정보"),
new StructuredDataTable("products", "KB_PRODUCTS", "상품원장", "보험상품 마스터"),
new StructuredDataTable("contracts", "KB_CONTRACTS", "계약원장", "보험계약 정보"),
new StructuredDataTable("coverages", "KB_COVERAGES", "담보원장", "보장·특약 정보"),
new StructuredDataTable("claims", "KB_CLAIMS", "청구원장", "보험금 청구·지급 정보"),
new StructuredDataTable("external-holdings", "KB_EXTERNAL_HOLDINGS", "외부보유정보 원장", "타사·외부 가입·보유 정보"),
new StructuredDataTable("stakeholders", "KB_STAKEHOLDERS", "이해관계자 원장", "역할·담당자 매핑"));
new StructuredDataTable("game-users", "CZN_COMN_USER_MST", "게임 사용자", "카제나 게임 사용자 마스터"),
new StructuredDataTable("characters", "CZN_COMN_CHARACTER_MST", "캐릭터", "카제나 캐릭터 마스터"),
new StructuredDataTable("sales", "COMN_SALES_TXN", "판매 거래", "게임 상품 판매 거래"),
new StructuredDataTable("refunds", "COMN_REFUND_TXN", "환불 거래", "게임 상품 환불 거래"),
new StructuredDataTable("products", "COMN_SALES_PRODUCT_DISP_BAS", "상품", "판매 상품 전시 기준"),
new StructuredDataTable("game-servers", "COMN_GAME_SERVER_BAS", "게임 서버", "게임 서버 기준 정보"),
new StructuredDataTable("game-aliases", "COMN_GAME_ALIAS_BAS", "게임 별칭", "게임명·별칭·prefix 매핑"));
private final JdbcTemplate jdbcTemplate;
@@ -63,7 +63,7 @@ public class StructuredDataService {
previewSql(table), ROW_LIMIT);
return new StructuredDataPreview(table, columns, rows, ROW_LIMIT);
} catch (DataAccessException exception) {
throw new AppException("정형 데이터를 조회할 수 없습니다. POC_2 조회 권한과 대상 테이블 상태를 확인하세요.");
throw new AppException("게임 데이터를 조회할 수 없습니다. SGMP_POC 조회 권한과 대상 테이블 상태를 확인하세요.");
}
}
@@ -73,13 +73,13 @@ public class StructuredDataService {
*/
private String previewSql(StructuredDataTable table) {
return switch (table.key()) {
case "customers" -> "SELECT * FROM POC_2.KB_CUSTOMERS WHERE ROWNUM <= ?";
case "products" -> "SELECT * FROM POC_2.KB_PRODUCTS WHERE ROWNUM <= ?";
case "contracts" -> "SELECT * FROM POC_2.KB_CONTRACTS WHERE ROWNUM <= ?";
case "coverages" -> "SELECT * FROM POC_2.KB_COVERAGES WHERE ROWNUM <= ?";
case "claims" -> "SELECT * FROM POC_2.KB_CLAIMS WHERE ROWNUM <= ?";
case "external-holdings" -> "SELECT * FROM POC_2.KB_EXTERNAL_HOLDINGS WHERE ROWNUM <= ?";
case "stakeholders" -> "SELECT * FROM POC_2.KB_STAKEHOLDERS WHERE ROWNUM <= ?";
case "game-users" -> "SELECT * FROM SGMP_POC.CZN_COMN_USER_MST WHERE ROWNUM <= ?";
case "characters" -> "SELECT * FROM SGMP_POC.CZN_COMN_CHARACTER_MST WHERE ROWNUM <= ?";
case "sales" -> "SELECT * FROM SGMP_POC.COMN_SALES_TXN WHERE ROWNUM <= ?";
case "refunds" -> "SELECT * FROM SGMP_POC.COMN_REFUND_TXN WHERE ROWNUM <= ?";
case "products" -> "SELECT * FROM SGMP_POC.COMN_SALES_PRODUCT_DISP_BAS WHERE ROWNUM <= ?";
case "game-servers" -> "SELECT * FROM SGMP_POC.COMN_GAME_SERVER_BAS WHERE ROWNUM <= ?";
case "game-aliases" -> "SELECT * FROM SGMP_POC.COMN_GAME_ALIAS_BAS WHERE ROWNUM <= ?";
default -> throw new AppException("선택할 수 없는 정형 데이터 테이블입니다.");
};
}

View File

@@ -56,9 +56,9 @@ public class VectorKnowledgeService {
}
public VectorKnowledgeSummary summary() {
int documents = count("SELECT COUNT(DISTINCT document_id) FROM cb_vector_document_chunk");
int chunks = count("SELECT COUNT(*) FROM cb_vector_document_chunk");
int tags = count("SELECT COUNT(*) FROM cb_vector_document_tag");
int documents = count("SELECT COUNT(DISTINCT document_id) FROM sg_vector_document_chunk");
int chunks = count("SELECT COUNT(*) FROM sg_vector_document_chunk");
int tags = count("SELECT COUNT(*) FROM sg_vector_document_tag");
boolean registered = protectedObjectService.findEnabled().stream()
.anyMatch(object -> VECTOR_OBJECT.equalsIgnoreCase(object.objectName()));
return new VectorKnowledgeSummary(
@@ -107,13 +107,13 @@ public class VectorKnowledgeService {
String embeddingJson = json(embedding);
long chunkId = nextChunkId++;
jdbcTemplate.update("""
INSERT INTO cb_vector_document_chunk
INSERT INTO sg_vector_document_chunk
(chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding)
VALUES (?, ?, ?, ?, ?, ?, TO_VECTOR(?))
""", chunkId, documentId, chunk.chunkNo(), title, chunk.text(), sourceUri, embeddingJson);
for (String tag : tags) {
jdbcTemplate.update("""
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
INSERT INTO sg_vector_document_tag (chunk_id, tech_tag)
VALUES (?, ?)
""", chunkId, tag);
tagCount++;
@@ -143,18 +143,18 @@ public class VectorKnowledgeService {
private void replaceDocument(String documentId) {
jdbcTemplate.update("""
DELETE FROM cb_vector_document_tag
DELETE FROM sg_vector_document_tag
WHERE chunk_id IN (
SELECT chunk_id FROM cb_vector_document_chunk WHERE document_id = ?
SELECT chunk_id FROM sg_vector_document_chunk WHERE document_id = ?
)
""", documentId);
jdbcTemplate.update("DELETE FROM cb_vector_document_chunk WHERE document_id = ?", documentId);
jdbcTemplate.update("DELETE FROM sg_vector_document_chunk WHERE document_id = ?", documentId);
}
private long nextChunkId(int chunkCount) {
try {
Long sequenceValue = jdbcTemplate.queryForObject(
"SELECT cb_vector_chunk_seq.NEXTVAL FROM dual", Long.class);
"SELECT sg_vector_chunk_seq.NEXTVAL FROM dual", Long.class);
if (sequenceValue != null) {
return sequenceValue;
}
@@ -163,7 +163,7 @@ public class VectorKnowledgeService {
// fallback keeps the demonstration usable; the setup SQL creates it.
}
Long max = jdbcTemplate.queryForObject(
"SELECT NVL(MAX(chunk_id), 28000) FROM cb_vector_document_chunk", Long.class);
"SELECT NVL(MAX(chunk_id), 28000) FROM sg_vector_document_chunk", Long.class);
return (max == null ? 28000 : max) + 1;
}

View File

@@ -3,47 +3,44 @@
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.BearerTokenMapper">
<select id="findAll" resultType="com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord">
SELECT k.key_id, k.employee_id AS user_id, u.full_name AS username,
SELECT k.key_id, k.user_id, u.user_name AS username,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_user_id,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_role, CAST(NULL AS VARCHAR2(100)) AS stakeholder_channel,
k.key_prefix, k.key_hash,
k.expires_at, k.revoked_at, k.description
FROM hmm_access_bearer_tokens k
JOIN hmm_hr_employees u ON u.employee_id = k.employee_id
FROM sg_access_bearer_token k JOIN sg_app_user u ON u.user_id = k.user_id
WHERE #{includeInactive} = 1
OR (k.revoked_at IS NULL AND k.expires_at > SYSTIMESTAMP)
ORDER BY k.key_id DESC
</select>
<select id="findById" resultType="com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord">
SELECT k.key_id, k.employee_id AS user_id, u.full_name AS username,
SELECT k.key_id, k.user_id, u.user_name AS username,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_user_id,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_role, CAST(NULL AS VARCHAR2(100)) AS stakeholder_channel,
k.key_prefix, k.key_hash,
k.expires_at, k.revoked_at, k.description
FROM hmm_access_bearer_tokens k
JOIN hmm_hr_employees u ON u.employee_id = k.employee_id
FROM sg_access_bearer_token k JOIN sg_app_user u ON u.user_id = k.user_id
WHERE k.key_id = #{keyId}
</select>
<select id="findByHash" resultType="com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord">
SELECT k.key_id, k.employee_id AS user_id, u.full_name AS username,
SELECT k.key_id, k.user_id, u.user_name AS username,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_user_id,
CAST(NULL AS VARCHAR2(100)) AS stakeholder_role, CAST(NULL AS VARCHAR2(100)) AS stakeholder_channel,
k.key_prefix, k.key_hash,
k.expires_at, k.revoked_at, k.description
FROM hmm_access_bearer_tokens k
JOIN hmm_hr_employees u ON u.employee_id = k.employee_id
FROM sg_access_bearer_token k JOIN sg_app_user u ON u.user_id = k.user_id
WHERE k.key_hash = #{keyHash,jdbcType=VARCHAR}
</select>
<select id="nextKeyId" resultType="long">
SELECT NVL(MAX(key_id), 0) + 1 FROM hmm_access_bearer_tokens
SELECT NVL(MAX(key_id), 0) + 1 FROM sg_access_bearer_token
</select>
<insert id="insertToken" parameterType="com.cloudhandson.vpdbackoffice.domain.token.BearerTokenRecord">
INSERT INTO hmm_access_bearer_tokens (
key_id, employee_id, key_prefix, key_hash, expires_at, revoked_at, description
INSERT INTO sg_access_bearer_token (
key_id, user_id, key_prefix, key_hash, expires_at, revoked_at, description
) VALUES (
#{keyId,jdbcType=NUMERIC},
#{userId,jdbcType=NUMERIC},
@@ -56,7 +53,7 @@
</insert>
<update id="revokeToken">
UPDATE hmm_access_bearer_tokens
UPDATE sg_access_bearer_token
SET revoked_at = #{revokedAt,jdbcType=TIMESTAMP},
description = CASE
WHEN #{reason,jdbcType=VARCHAR} IS NULL THEN description

View File

@@ -8,7 +8,7 @@
group_name,
description,
active_yn
FROM hmm_access_groups
FROM sg_app_group
ORDER BY group_code
</select>
@@ -16,12 +16,12 @@
SELECT g.group_id,
g.group_code,
g.group_name,
u.employee_id AS user_id,
u.full_name AS username
FROM hmm_access_group_members ug
JOIN hmm_access_groups g ON g.group_id = ug.group_id
JOIN hmm_hr_employees u ON u.employee_id = ug.employee_id
ORDER BY g.group_code, u.full_name
u.user_id,
u.user_name AS username
FROM sg_user_group ug
JOIN sg_app_group g ON g.group_id = ug.group_id
JOIN sg_app_user u ON u.user_id = ug.user_id
ORDER BY g.group_code, u.user_name
</select>
<select id="findGroupRoles" resultType="com.cloudhandson.vpdbackoffice.domain.group.GroupRoleView">
@@ -30,18 +30,18 @@
g.group_name,
r.role_id,
r.role_name
FROM hmm_group_access_roles gr
JOIN hmm_access_groups g ON g.group_id = gr.group_id
JOIN hmm_access_roles r ON r.role_id = gr.role_id
FROM sg_group_role gr
JOIN sg_app_group g ON g.group_id = gr.group_id
JOIN sg_app_role r ON r.role_id = gr.role_id
ORDER BY g.group_code, r.role_name
</select>
<select id="nextGroupId" resultType="long">
SELECT NVL(MAX(group_id), 0) + 1 FROM hmm_access_groups
SELECT NVL(MAX(group_id), 0) + 1 FROM sg_app_group
</select>
<insert id="insertGroup">
INSERT INTO hmm_access_groups (
INSERT INTO sg_app_group (
group_id, group_code, group_name, description, active_yn
) VALUES (
#{groupId,jdbcType=NUMERIC},
@@ -53,29 +53,29 @@
</insert>
<update id="updateActive">
UPDATE hmm_access_groups
UPDATE sg_app_group
SET active_yn = #{activeYn,jdbcType=VARCHAR}
WHERE group_id = #{groupId,jdbcType=NUMERIC}
</update>
<insert id="insertGroupUser">
INSERT INTO hmm_access_group_members (group_id, employee_id)
INSERT INTO sg_user_group (group_id, user_id)
VALUES (#{groupId,jdbcType=NUMERIC}, #{userId,jdbcType=NUMERIC})
</insert>
<delete id="deleteGroupUser">
DELETE FROM hmm_access_group_members
DELETE FROM sg_user_group
WHERE group_id = #{groupId,jdbcType=NUMERIC}
AND employee_id = #{userId,jdbcType=NUMERIC}
AND user_id = #{userId,jdbcType=NUMERIC}
</delete>
<insert id="insertGroupRole">
INSERT INTO hmm_group_access_roles (group_id, role_id)
INSERT INTO sg_group_role (group_id, role_id)
VALUES (#{groupId,jdbcType=NUMERIC}, #{roleId,jdbcType=NUMERIC})
</insert>
<delete id="deleteGroupRole">
DELETE FROM hmm_group_access_roles
DELETE FROM sg_group_role
WHERE group_id = #{groupId,jdbcType=NUMERIC}
AND role_id = #{roleId,jdbcType=NUMERIC}
</delete>

View File

@@ -4,41 +4,41 @@
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.MaskingRuleMapper">
<select id="findAllRules" resultType="com.cloudhandson.vpdbackoffice.domain.masking.MaskingRule">
SELECT rule_id, rule_code, rule_name, template_code, description, enabled_yn
FROM cb_masking_rule
FROM sg_masking_rule
ORDER BY enabled_yn DESC, rule_name, rule_id
</select>
<select id="findEnabledRules" resultType="com.cloudhandson.vpdbackoffice.domain.masking.MaskingRule">
SELECT rule_id, rule_code, rule_name, template_code, description, enabled_yn
FROM cb_masking_rule
FROM sg_masking_rule
WHERE enabled_yn = 'Y'
ORDER BY rule_name, rule_id
</select>
<select id="findRuleById" resultType="com.cloudhandson.vpdbackoffice.domain.masking.MaskingRule">
SELECT rule_id, rule_code, rule_name, template_code, description, enabled_yn
FROM cb_masking_rule
FROM sg_masking_rule
WHERE rule_id = #{ruleId}
</select>
<select id="findRuleByCode" resultType="com.cloudhandson.vpdbackoffice.domain.masking.MaskingRule">
SELECT rule_id, rule_code, rule_name, template_code, description, enabled_yn
FROM cb_masking_rule
FROM sg_masking_rule
WHERE rule_code = #{ruleCode}
</select>
<select id="nextRuleId" resultType="long">
SELECT NVL(MAX(rule_id), 0) + 1 FROM cb_masking_rule
SELECT NVL(MAX(rule_id), 0) + 1 FROM sg_masking_rule
</select>
<insert id="insertRule">
INSERT INTO cb_masking_rule (rule_id, rule_code, rule_name, template_code, description, enabled_yn)
INSERT INTO sg_masking_rule (rule_id, rule_code, rule_name, template_code, description, enabled_yn)
VALUES (#{ruleId}, #{command.ruleCode}, #{command.ruleName}, #{command.templateCode},
#{command.description}, 'Y')
</insert>
<update id="updateRuleActive">
UPDATE cb_masking_rule
UPDATE sg_masking_rule
SET enabled_yn = #{enabledYn}
WHERE rule_id = #{ruleId}
</update>
@@ -54,10 +54,10 @@
r.rule_name,
r.template_code,
r.enabled_yn AS rule_enabled_yn
FROM cb_column_masking_rule link
JOIN cb_protected_column c ON c.column_id = link.column_id
JOIN cb_protected_object o ON o.object_id = c.object_id
JOIN cb_masking_rule r ON r.rule_id = link.rule_id
FROM sg_column_masking_rule link
JOIN sg_protected_column c ON c.column_id = link.column_id
JOIN sg_protected_object o ON o.object_id = c.object_id
JOIN sg_masking_rule r ON r.rule_id = link.rule_id
ORDER BY o.owner, o.object_name, c.column_name
</select>
@@ -76,10 +76,10 @@
configured AS (
SELECT protected_object.object_name,
COUNT(*) AS configured_column_count
FROM cb_column_masking_rule link
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
FROM sg_column_masking_rule link
JOIN sg_masking_rule rule ON rule.rule_id = link.rule_id
JOIN sg_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN sg_protected_object protected_object ON protected_object.object_id = protected_column.object_id
WHERE protected_object.owner = 'POC_2'
AND rule.enabled_yn = 'Y'
GROUP BY protected_object.object_name
@@ -102,10 +102,10 @@
missing_columns AS (
SELECT protected_object.object_name,
COUNT(*) AS missing_column_count
FROM cb_column_masking_rule link
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
FROM sg_column_masking_rule link
JOIN sg_masking_rule rule ON rule.rule_id = link.rule_id
JOIN sg_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN sg_protected_object protected_object ON protected_object.object_id = protected_column.object_id
LEFT JOIN redaction_columns policy_column
ON policy_column.object_owner = protected_object.owner
AND policy_column.object_name = protected_object.object_name
@@ -123,10 +123,10 @@
WHERE policy_column.object_owner = 'POC_2'
AND NOT EXISTS (
SELECT 1
FROM cb_column_masking_rule link
JOIN cb_masking_rule rule ON rule.rule_id = link.rule_id
JOIN cb_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN cb_protected_object protected_object ON protected_object.object_id = protected_column.object_id
FROM sg_column_masking_rule link
JOIN sg_masking_rule rule ON rule.rule_id = link.rule_id
JOIN sg_protected_column protected_column ON protected_column.column_id = link.column_id
JOIN sg_protected_object protected_object ON protected_object.object_id = protected_column.object_id
WHERE protected_object.owner = 'POC_2'
AND protected_object.object_name = policy_column.object_name
AND protected_column.column_name = policy_column.column_name
@@ -180,15 +180,15 @@
r.rule_name,
r.template_code,
r.enabled_yn AS rule_enabled_yn
FROM cb_column_masking_rule link
JOIN cb_protected_column c ON c.column_id = link.column_id
JOIN cb_protected_object o ON o.object_id = c.object_id
JOIN cb_masking_rule r ON r.rule_id = link.rule_id
FROM sg_column_masking_rule link
JOIN sg_protected_column c ON c.column_id = link.column_id
JOIN sg_protected_object o ON o.object_id = c.object_id
JOIN sg_masking_rule r ON r.rule_id = link.rule_id
WHERE c.column_id = #{columnId}
</select>
<insert id="upsertColumnRule">
MERGE INTO cb_column_masking_rule dst
MERGE INTO sg_column_masking_rule dst
USING (SELECT #{columnId} column_id, #{ruleId} rule_id FROM dual) src
ON (dst.column_id = src.column_id)
WHEN MATCHED THEN UPDATE SET dst.rule_id = src.rule_id, dst.updated_at = SYSTIMESTAMP
@@ -197,12 +197,12 @@
</insert>
<delete id="deleteColumnRule">
DELETE FROM cb_column_masking_rule
DELETE FROM sg_column_masking_rule
WHERE column_id = #{columnId}
</delete>
<delete id="deleteUserRulesForColumn">
DELETE FROM cb_user_masking_rule
DELETE FROM sg_user_masking_rule
WHERE column_id = #{columnId}
</delete>
@@ -217,12 +217,12 @@
r.template_code,
assignment.decision,
assignment.active_yn
FROM cb_user_masking_rule assignment
JOIN cb_app_user u ON u.user_id = assignment.user_id
JOIN cb_protected_column c ON c.column_id = assignment.column_id
JOIN cb_protected_object o ON o.object_id = c.object_id
JOIN cb_column_masking_rule link ON link.column_id = c.column_id
JOIN cb_masking_rule r ON r.rule_id = link.rule_id
FROM sg_user_masking_rule assignment
JOIN sg_app_user u ON u.user_id = assignment.user_id
JOIN sg_protected_column c ON c.column_id = assignment.column_id
JOIN sg_protected_object o ON o.object_id = c.object_id
JOIN sg_column_masking_rule link ON link.column_id = c.column_id
JOIN sg_masking_rule r ON r.rule_id = link.rule_id
ORDER BY u.user_name, o.owner, o.object_name, c.column_name
</select>
@@ -237,18 +237,18 @@
r.template_code,
assignment.decision,
assignment.active_yn
FROM cb_user_masking_rule assignment
JOIN cb_app_user u ON u.user_id = assignment.user_id
JOIN cb_protected_column c ON c.column_id = assignment.column_id
JOIN cb_protected_object o ON o.object_id = c.object_id
JOIN cb_column_masking_rule link ON link.column_id = c.column_id
JOIN cb_masking_rule r ON r.rule_id = link.rule_id
FROM sg_user_masking_rule assignment
JOIN sg_app_user u ON u.user_id = assignment.user_id
JOIN sg_protected_column c ON c.column_id = assignment.column_id
JOIN sg_protected_object o ON o.object_id = c.object_id
JOIN sg_column_masking_rule link ON link.column_id = c.column_id
JOIN sg_masking_rule r ON r.rule_id = link.rule_id
WHERE assignment.user_id = #{userId}
AND assignment.column_id = #{columnId}
</select>
<insert id="upsertUserRule">
MERGE INTO cb_user_masking_rule dst
MERGE INTO sg_user_masking_rule dst
USING (SELECT #{userId} user_id, #{columnId} column_id, #{decision} decision FROM dual) src
ON (dst.user_id = src.user_id AND dst.column_id = src.column_id)
WHEN MATCHED THEN UPDATE SET dst.decision = src.decision, dst.active_yn = 'Y', dst.updated_at = SYSTIMESTAMP
@@ -257,7 +257,7 @@
</insert>
<delete id="deleteUserRule">
DELETE FROM cb_user_masking_rule
DELETE FROM sg_user_masking_rule
WHERE user_id = #{userId}
AND column_id = #{columnId}
</delete>

View File

@@ -7,8 +7,7 @@
role_name,
description,
max_sensitivity_level
FROM hmm_access_roles
WHERE active_yn = 'Y'
FROM sg_app_role
ORDER BY role_name
</select>
@@ -17,45 +16,44 @@
role_name,
description,
max_sensitivity_level
FROM hmm_access_roles
FROM sg_app_role
WHERE role_id = #{roleId}
</select>
<select id="nextRoleId" resultType="long">
SELECT NVL(MAX(role_id), 0) + 1 FROM hmm_access_roles
SELECT NVL(MAX(role_id), 0) + 1 FROM sg_app_role
</select>
<insert id="insertRole">
INSERT INTO hmm_access_roles (role_id, role_name, description, max_sensitivity_level, active_yn)
INSERT INTO sg_app_role (role_id, role_name, description, max_sensitivity_level)
VALUES (
#{roleId,jdbcType=NUMERIC},
UPPER(#{roleName,jdbcType=VARCHAR}),
#{description,jdbcType=VARCHAR},
#{maxSensitivityLevel,jdbcType=VARCHAR},
'Y'
#{maxSensitivityLevel,jdbcType=VARCHAR}
)
</insert>
<update id="updateRoleMaxSensitivity">
UPDATE hmm_access_roles
UPDATE sg_app_role
SET max_sensitivity_level = #{maxSensitivityLevel,jdbcType=VARCHAR}
WHERE role_id = #{roleId,jdbcType=NUMERIC}
</update>
<delete id="deleteRole">
DELETE FROM hmm_access_roles
DELETE FROM sg_app_role
WHERE role_id = #{roleId,jdbcType=NUMERIC}
</delete>
<select id="countUserRolesByRoleId" resultType="int">
SELECT COUNT(*)
FROM hmm_employee_access_roles
FROM sg_user_role
WHERE role_id = #{roleId,jdbcType=NUMERIC}
</select>
<select id="countGroupRolesByRoleId" resultType="int">
SELECT COUNT(*)
FROM hmm_group_access_roles
FROM sg_group_role
WHERE role_id = #{roleId,jdbcType=NUMERIC}
</select>

View File

@@ -4,12 +4,12 @@
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.SettingMapper">
<select id="findByKey" resultType="com.cloudhandson.vpdbackoffice.domain.setting.BackofficeSetting">
SELECT setting_key, setting_value
FROM cb_backoffice_setting
FROM sg_backoffice_setting
WHERE setting_key = #{settingKey,jdbcType=VARCHAR}
</select>
<update id="upsert">
MERGE INTO cb_backoffice_setting dst
MERGE INTO sg_backoffice_setting dst
USING (
SELECT #{settingKey,jdbcType=VARCHAR} setting_key,
#{settingValue,jdbcType=VARCHAR} setting_value

View File

@@ -3,27 +3,16 @@
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cloudhandson.vpdbackoffice.mapper.UserMapper">
<select id="findAll" resultType="com.cloudhandson.vpdbackoffice.domain.user.AppUser">
SELECT e.employee_id AS user_id,
e.full_name AS username,
e.employee_code AS emp_no,
t.team_code AS dept_code,
'N' AS can_read_contents,
CASE WHEN e.employment_status = 'ACTIVE' THEN 'Y' ELSE 'N' END AS active_yn
FROM hmm_hr_employees e
JOIN hmm_org_teams t ON t.team_id = e.team_id
SELECT user_id, user_name AS username, employee_no AS emp_no, dept_code,
can_read_contents, active AS active_yn
FROM sg_app_user
ORDER BY username
</select>
<select id="findById" resultType="com.cloudhandson.vpdbackoffice.domain.user.AppUser">
SELECT e.employee_id AS user_id,
e.full_name AS username,
e.employee_code AS emp_no,
t.team_code AS dept_code,
'N' AS can_read_contents,
CASE WHEN e.employment_status = 'ACTIVE' THEN 'Y' ELSE 'N' END AS active_yn
FROM hmm_hr_employees e
JOIN hmm_org_teams t ON t.team_id = e.team_id
WHERE e.employee_id = #{userId}
SELECT user_id, user_name AS username, employee_no AS emp_no, dept_code,
can_read_contents, active AS active_yn
FROM sg_app_user WHERE user_id = #{userId}
</select>
<select id="findUserRoles" resultType="com.cloudhandson.vpdbackoffice.domain.user.UserRoleView">
@@ -31,46 +20,32 @@
u.user_name AS username,
r.role_id,
r.role_name
FROM hmm_employee_access_roles ur
JOIN hmm_hr_employees u ON u.employee_id = ur.employee_id
JOIN hmm_access_roles r ON r.role_id = ur.role_id
ORDER BY u.full_name, r.role_name
FROM sg_user_role ur
JOIN sg_app_user u ON u.user_id = ur.user_id
JOIN sg_app_role r ON r.role_id = ur.role_id
ORDER BY u.user_name, r.role_name
</select>
<select id="nextUserId" resultType="long">
SELECT NVL(MAX(employee_id), 0) + 1 FROM hmm_hr_employees
SELECT NVL(MAX(user_id), 0) + 1 FROM sg_app_user
</select>
<insert id="insertUser">
INSERT INTO hmm_hr_employees (
employee_id, employee_code, full_name, email, job_title, team_id, hire_date, employment_status
)
SELECT #{userId},
#{command.empNo},
#{command.username},
LOWER(#{command.empNo}) || '@example.test',
'HMM Demo Employee',
t.team_id,
TRUNC(SYSDATE),
'ACTIVE'
FROM hmm_org_teams t
WHERE t.team_code = #{command.deptCode}
INSERT INTO sg_app_user (user_id, login_id, user_name, employee_no, dept_code, can_read_contents, active)
VALUES (#{userId}, LOWER(#{command.empNo}), #{command.username}, #{command.empNo},
#{command.deptCode}, 'N', 'Y')
</insert>
<update id="updateActive">
UPDATE hmm_hr_employees
SET employment_status = CASE WHEN #{activeYn} = 'Y' THEN 'ACTIVE' ELSE 'INACTIVE' END
WHERE employee_id = #{userId}
UPDATE sg_app_user SET active = #{activeYn} WHERE user_id = #{userId}
</update>
<insert id="insertUserRole">
INSERT INTO hmm_employee_access_roles (employee_id, role_id)
VALUES (#{userId}, #{roleId})
INSERT INTO sg_user_role (user_id, role_id) VALUES (#{userId}, #{roleId})
</insert>
<delete id="deleteUserRole">
DELETE FROM hmm_employee_access_roles
WHERE employee_id = #{userId}
DELETE FROM sg_user_role WHERE user_id = #{userId}
AND role_id = #{roleId}
</delete>
</mapper>

View File

@@ -11,7 +11,7 @@
<p>업무 사용자와 데이터 접근 기준을 관리하고, DB가 적용한 결과까지 확인합니다.</p>
</div>
<div class="dashboard-hero-actions" aria-label="주요 작업">
<a class="btn rw-btn-primary" href="/probe">접근 검증</a>
<a class="btn rw-btn-primary" href="/users">사용자 관리</a>
<a class="btn rw-btn-secondary" href="/permissions">행 접근 규칙</a>
</div>
<details class="explanation-details dashboard-explanation">
@@ -152,10 +152,10 @@
<h2 id="menu-flow-title">설정은 접근 제어에서, 적용 확인은 접근 검증에서</h2>
</div>
<div class="dashboard-flow-grid">
<a class="dashboard-flow-card" href="/permissions"><strong>행 접근 규칙</strong><span>역할별 객체·행 조건</span></a>
<a class="dashboard-flow-card" href="/vpd-policies"><strong>보호·검증</strong><span>보호 연결·검증 세션·접근 확인</span></a>
<a class="dashboard-flow-card" href="/objects"><strong>연동 도구</strong><span>조회 대상·지식 검색·MCP 연동</span></a>
<a class="dashboard-flow-card" href="/operation-status"><strong>운영</strong><span>상태와 실행 결과 확인</span></a>
<a class="dashboard-flow-card" href="/users"><strong>사용자 관리</strong><span>PoC 운영 사용자를 관리</span></a>
<a class="dashboard-flow-card" href="/groups"><strong>그룹 관리</strong><span>Data &amp; AI TF 구성</span></a>
<a class="dashboard-flow-card" href="/roles"><strong>역할 관리</strong><span>운영 역할을 관리</span></a>
<a class="dashboard-flow-card" href="/permissions"><strong>행 접근 규칙</strong><span>게임 데이터 객체별 접근 규칙</span></a>
</div>
</section>

View File

@@ -10,7 +10,7 @@
<details class="explanation-details">
<summary>도움말</summary>
<p>
<code>POC_2</code> KB 업무 테이블의 table/column comment와 Oracle annotation을 조회·수정합니다.
<code>SGMP_POC</code> 게임 데이터 테이블의 table/column comment와 Oracle annotation을 조회·수정합니다.
Select AI profile의 <code>comments=true</code>, <code>annotations=true</code> 설정에서는 이 값들이 SQL 생성 근거로 들어갑니다.
</p>
<p class="mb-0">임의 스키마나 임의 테이블은 수정하지 않고, 백오피스가 승인한 7개 업무 테이블만 대상으로 합니다.</p>
@@ -24,7 +24,7 @@
<div class="section-heading">
<div>
<h2>테이블 선택</h2>
<p class="section-subtitle">정형 MCP/Select AI가 참조하는 KB 업무 원장 7개만 표시합니다.</p>
<p class="section-subtitle">정형 MCP/Select AI가 참조하는 게임 데이터 테이블만 표시합니다.</p>
</div>
<span class="badge text-bg-secondary" th:text="${#lists.size(tables)}">7</span>
</div>
@@ -33,9 +33,9 @@
class="structured-table-card"
th:classappend="${entry.key() == selectedKey} ? ' is-selected'"
th:href="@{/schema-metadata(table=${entry.key()})}">
<strong th:text="${entry.businessName()}">고객원장</strong>
<code th:text="${entry.tableName()}">KB_CUSTOMERS</code>
<small th:text="${entry.description()}">고객 기본정보</small>
<strong th:text="${entry.businessName()}">게임 사용자</strong>
<code th:text="${entry.tableName()}">CZN_COMN_USER_MST</code>
<small th:text="${entry.description()}">게임 사용자 마스터</small>
</a>
</div>
</section>
@@ -44,9 +44,9 @@
<div class="section-heading">
<div>
<span class="badge text-bg-secondary">TABLE</span>
<h2 class="mt-2" th:text="${metadata.table().businessName()}">계약원장</h2>
<h2 class="mt-2" th:text="${metadata.table().businessName()}">게임 데이터</h2>
<p class="section-subtitle">
<code th:text="${'POC_2.' + metadata.table().tableName()}">POC_2.KB_CONTRACTS</code>
<code th:text="${'SGMP_POC.' + metadata.table().tableName()}">SGMP_POC.CZN_COMN_USER_MST</code>
<span th:text="${' · ' + metadata.table().description()}"> · 설명</span>
</p>
</div>

View File

@@ -8,19 +8,19 @@
<h1>정형 데이터 조회</h1>
<details class="explanation-details">
<summary>도움말</summary>
<p>보험 원장 7개만 읽기 전용으로 조회합니다. 임의 SQL이나 수정 기능은 제공하지 않으며, 한 번에 최대 50건까지만 표시합니다.</p>
<p>스마일게이트 게임 데이터 테이블을 읽기 전용으로 조회합니다. 임의 SQL이나 수정 기능은 제공하지 않으며, 한 번에 최대 50건까지만 표시합니다.</p>
</details>
</div>
<div class="alert alert-warning">
이 화면은 관리자용 원장 미리보기입니다. 사용자별 행 접근 적용 결과는 <a href="/probe">접근 검증</a>에서 확인하세요.
이 화면은 관리자용 게임 데이터 미리보기입니다. 사용자별 행 접근 적용 결과는 <a href="/probe">접근 검증</a>에서 확인하세요.
</div>
<section class="content-band">
<div class="section-heading">
<div>
<h2>조회할 원장 선택</h2>
<p class="section-subtitle">KBAIPOC의 <code>POC_2</code> 스키마에서 승인된 정형 테이블만 표시합니다.</p>
<h2>조회할 게임 데이터 선택</h2>
<p class="section-subtitle"><code>SGMP_POC</code> 스키마에서 PoC 대상으로 등록한 게임 데이터 테이블만 표시합니다.</p>
</div>
</div>
<div class="structured-table-grid">
@@ -28,9 +28,9 @@
class="structured-table-card"
th:classappend="${entry.key() == selectedKey} ? ' is-selected'"
th:href="@{/structured-data(table=${entry.key()})}">
<strong th:text="${entry.businessName()}">고객원장</strong>
<code th:text="${entry.tableName()}">KB_CUSTOMERS</code>
<small th:text="${entry.description()}">고객 기본정보</small>
<strong th:text="${entry.businessName()}">게임 사용자</strong>
<code th:text="${entry.tableName()}">CZN_COMN_USER_MST</code>
<small th:text="${entry.description()}">게임 사용자 마스터</small>
</a>
</div>
</section>
@@ -40,9 +40,9 @@
<section class="content-band" th:if="${preview}">
<div class="section-heading">
<div>
<h2 th:text="${preview.table().businessName()}">고객원장</h2>
<h2 th:text="${preview.table().businessName()}">게임 사용자</h2>
<p class="section-subtitle">
<code th:text="${'POC_2.' + preview.table().tableName()}">POC_2.KB_CUSTOMERS</code>
<code th:text="${'SGMP_POC.' + preview.table().tableName()}">SGMP_POC.CZN_COMN_USER_MST</code>
<span th:text="${' · 최대 ' + preview.rowLimit() + '건'}"> · 최대 50건</span>
</p>
</div>