feat: 데이터 도메인 사전 + 데이터 사전 시스템 (V20)
3 agents 병렬 작업 (dba / api / frontend) 결과물 + SQL 버그 2건 fix. DB (V20__데이터사전.sql): - data_domain (domain_code PK, category, data_type, length/precision/scale, format_pattern, validation_regex, masking_type, encrypted, ...) - data_dictionary (dict_id PK, table_name, column_name, domain_code FK, column_label, is_pk/is_fk/fk_reference, business_rule, sample_value, ...) - VIEW v_data_dictionary_full: information_schema + pg_description + data_dictionary + data_domain 4-way LEFT JOIN → ga 스키마 모든 컬럼 자동 노출 (사전 미등록 컬럼도 회색 표시) - 도메인 30개 초기 데이터 (IDENTIFIER 7 / PII 6 / MONEY 5 / RATE 3 / DATE 4 / CODE 4 / TEXT 3 / JSON 2) - 사전 매핑 50개 (agent 10 / contract 7 / recruit_ledger 9 / settle_master 9 / payment 6) - 메뉴 SYSTEM_DATA_DOMAIN, SYSTEM_DATA_DICT 등록 + 권한 Backend: - ga-core VO 9개 + Mapper 2개 + XML 2개 - ga-api Service 2 (도메인 삭제 시 사전 참조 검증) + Controller 2 - 엔드포인트: · /api/data-domains (CRUD + /options) · /api/data-dictionary (CRUD + /tables + /full?tableName=) Frontend: - api/dict.ts (13 endpoints) - pages/system/DataDomainList.tsx — 카테고리 색상 Tag, 마스킹/암호화 표시, ModalForm 등록/수정/삭제 - pages/system/DataDictionary.tsx — 좌(테이블 목록 + prefix 그룹핑) + 우(v_data_dictionary_full 조회, 사전 미등록 컬럼은 회색+점선 추가 버튼) - App.tsx + MenuIcon.tsx 매핑 추가 Bug fix: - DataDomainMapper: WHERE is_active = true → 'Y' (CHAR vs boolean) - DataDictionaryMapper.selectFromView: ORDER BY sort_order 제거 (view 에 없는 컬럼) 검증: - ./gradlew :ga-api:bootJar 통과 - Flyway V20 운영 DB 적용 (17초) - 4 엔드포인트 모두 200 응답 - agent 테이블 15컬럼 사전 자동 노출 + 도메인 매핑 정상 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import api, { PageResponse, unwrap } from './request';
|
||||
|
||||
// ─── 타입 ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DataDomainResp {
|
||||
domainCode: string;
|
||||
domainName: string;
|
||||
domainCategory: string;
|
||||
dataType: string;
|
||||
length?: number;
|
||||
precision?: number;
|
||||
scale?: number;
|
||||
formatPattern?: string;
|
||||
validationRegex?: string;
|
||||
exampleValue?: string;
|
||||
maskingType?: string;
|
||||
encrypted: string; // 'Y'/'N'
|
||||
description?: string;
|
||||
isActive: string;
|
||||
}
|
||||
|
||||
export interface DataDomainOption {
|
||||
domainCode: string;
|
||||
domainName: string;
|
||||
domainCategory: string;
|
||||
}
|
||||
|
||||
export interface DataDomainSaveReq {
|
||||
domainCode: string;
|
||||
domainName: string;
|
||||
domainCategory: string;
|
||||
dataType: string;
|
||||
length?: number;
|
||||
precision?: number;
|
||||
scale?: number;
|
||||
formatPattern?: string;
|
||||
validationRegex?: string;
|
||||
exampleValue?: string;
|
||||
maskingType?: string;
|
||||
encrypted?: string;
|
||||
description?: string;
|
||||
isActive?: string;
|
||||
}
|
||||
|
||||
export interface DataDomainQuery {
|
||||
domainCategory?: string;
|
||||
isActive?: string;
|
||||
searchKeyword?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface DataDictionaryResp {
|
||||
dictId: number;
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
domainCode?: string;
|
||||
domainName?: string;
|
||||
domainCategory?: string;
|
||||
columnLabel?: string;
|
||||
description?: string;
|
||||
isPk: string;
|
||||
isFk: string;
|
||||
fkReference?: string;
|
||||
isRequired: string;
|
||||
defaultValue?: string;
|
||||
businessRule?: string;
|
||||
sampleValue?: string;
|
||||
maskingType?: string;
|
||||
encrypted?: string;
|
||||
}
|
||||
|
||||
export interface DataDictionaryFullResp {
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
ordinalPosition: number;
|
||||
dataType: string;
|
||||
maxLength?: number;
|
||||
numPrecision?: number;
|
||||
numScale?: number;
|
||||
isNullable: string;
|
||||
columnDefault?: string;
|
||||
tableComment?: string;
|
||||
columnComment?: string;
|
||||
columnLabel?: string;
|
||||
dictDescription?: string;
|
||||
domainCode?: string;
|
||||
domainName?: string;
|
||||
domainCategory?: string;
|
||||
formatPattern?: string;
|
||||
maskingType?: string;
|
||||
encrypted?: string;
|
||||
isPk?: string;
|
||||
isFk?: string;
|
||||
fkReference?: string;
|
||||
businessRule?: string;
|
||||
sampleValue?: string;
|
||||
}
|
||||
|
||||
export interface DataDictionarySaveReq {
|
||||
tableName: string;
|
||||
columnName: string;
|
||||
columnLabel?: string;
|
||||
description?: string;
|
||||
domainCode?: string;
|
||||
isPk?: string;
|
||||
isFk?: string;
|
||||
fkReference?: string;
|
||||
isRequired?: string;
|
||||
defaultValue?: string;
|
||||
businessRule?: string;
|
||||
sampleValue?: string;
|
||||
}
|
||||
|
||||
export interface DataDictionaryQuery {
|
||||
tableName?: string;
|
||||
domainCode?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
// ─── API ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const dataDomainApi = {
|
||||
list: (params: DataDomainQuery) =>
|
||||
unwrap<PageResponse<DataDomainResp>>(api.get('/api/data-domains', { params })),
|
||||
|
||||
options: () =>
|
||||
unwrap<DataDomainOption[]>(api.get('/api/data-domains/options')),
|
||||
|
||||
detail: (domainCode: string) =>
|
||||
unwrap<DataDomainResp>(api.get(`/api/data-domains/${domainCode}`)),
|
||||
|
||||
create: (req: DataDomainSaveReq) =>
|
||||
unwrap<void>(api.post('/api/data-domains', req)),
|
||||
|
||||
update: (domainCode: string, req: DataDomainSaveReq) =>
|
||||
unwrap<void>(api.put(`/api/data-domains/${domainCode}`, req)),
|
||||
|
||||
remove: (domainCode: string) =>
|
||||
unwrap<void>(api.delete(`/api/data-domains/${domainCode}`)),
|
||||
};
|
||||
|
||||
export const dataDictApi = {
|
||||
list: (params: DataDictionaryQuery) =>
|
||||
unwrap<PageResponse<DataDictionaryResp>>(api.get('/api/data-dictionary', { params })),
|
||||
|
||||
tables: () =>
|
||||
unwrap<string[]>(api.get('/api/data-dictionary/tables')),
|
||||
|
||||
full: (tableName: string) =>
|
||||
unwrap<DataDictionaryFullResp[]>(api.get('/api/data-dictionary/full', { params: { tableName } })),
|
||||
|
||||
detail: (dictId: number) =>
|
||||
unwrap<DataDictionaryResp>(api.get(`/api/data-dictionary/${dictId}`)),
|
||||
|
||||
create: (req: DataDictionarySaveReq) =>
|
||||
unwrap<void>(api.post('/api/data-dictionary', req)),
|
||||
|
||||
update: (dictId: number, req: DataDictionarySaveReq) =>
|
||||
unwrap<void>(api.put(`/api/data-dictionary/${dictId}`, req)),
|
||||
|
||||
remove: (dictId: number) =>
|
||||
unwrap<void>(api.delete(`/api/data-dictionary/${dictId}`)),
|
||||
};
|
||||
Reference in New Issue
Block a user