도메인 정리: invion.com → invyone.com 전체 일괄 치환 + 매핑 문서화
Build & Deploy to K8s / build-and-deploy (push) Has been cancelled

운영 도메인이 실제로는 v1.invyone.com / solution.invyone.com / api.invyone.com 인데
코드/문서 곳곳에 v1.invion.com / api.invion.com 등 미존재 도메인이 박혀 있어 정리.

변경 파일 (21):
- frontend lib/api/client.ts, lib/utils/apiUrl.ts: hostname 체크 endsWith(\".invyone.com\") 일반화
- frontend lib/api/dashboard.ts, file.ts, flow.ts, FileViewerModal*2.tsx: 도메인 치환
- frontend invion-layout-v5.html: 시안 내 placeholder 도메인 정리
- backend-spring SecurityConfig.java: CORS 주석 예시 정리
- docker/deploy/docker-compose.yml, k8s/traefik-dynamic.yaml: traefik Host 라벨 정리
- scripts/prod/deploy.sh: 안내 메시지 정리
- .cursor/rules/api-client-usage.mdc, project-conventions.mdc: AI 가이드 정리
- docs/* 4개: 아키텍처/플로우 문서 도메인 정리
- notes/gbpark/* 3개: 과거 메모 정리

신규:
- docs/DOMAIN_MAPPING.md: 운영/개발/폐기 도메인 영구 기록.
  AI 에이전트와 신규 개발자가 헷갈리지 않도록 단일 진실 출처.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Johngreen
2026-04-28 07:39:04 +09:00
parent eed70014c2
commit 52386efb83
48 changed files with 3533 additions and 49 deletions
@@ -39,7 +39,7 @@ public class SecurityConfig {
private final AiAgentApiKeyService aiAgentApiKeyService;
/**
* CORS 화이트리스트. 콤마 구분 문자열로 주입 (예: "http://localhost:3000,https://v1.invion.com").
* CORS 화이트리스트. 콤마 구분 문자열로 주입 (예: "http://localhost:3000,https://v1.invyone.com").
* application.yml 또는 환경변수 CORS_ALLOWED_ORIGINS 로 설정.
*/
@Value("${cors.allowed-origins}")
@@ -0,0 +1,23 @@
-- V001: ai_llm_providers
-- LLM 프로바이더 테이블 (anthropic/openai/google/deepseek/ollama)
CREATE TABLE ai_llm_providers (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
display_name VARCHAR(200) NOT NULL,
api_key_encrypted TEXT NOT NULL,
model_name VARCHAR(100) NOT NULL,
endpoint VARCHAR(500),
priority INTEGER NOT NULL DEFAULT 1,
max_tokens INTEGER NOT NULL DEFAULT 4096,
temperature NUMERIC(3,2) NOT NULL DEFAULT 0.70,
cost_per_1k_input NUMERIC(10,6) NOT NULL DEFAULT 0,
cost_per_1k_output NUMERIC(10,6) NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
config JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_providers_priority ON ai_llm_providers (priority);
CREATE INDEX idx_providers_active ON ai_llm_providers (is_active);
@@ -0,0 +1,23 @@
-- V002: ai_agents
-- AI 에이전트 테이블
CREATE TABLE ai_agents (
id BIGSERIAL PRIMARY KEY,
agent_id VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description TEXT,
model VARCHAR(100) NOT NULL DEFAULT 'claude-sonnet-4-20250514',
system_prompt TEXT,
tools JSONB NOT NULL DEFAULT '[]'::jsonb,
config JSONB NOT NULL DEFAULT '{}'::jsonb,
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active','inactive','archived')),
company_code VARCHAR(20),
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_ai_agents_company ON ai_agents (company_code);
CREATE INDEX idx_ai_agents_status ON ai_agents (status);
CREATE INDEX idx_ai_agents_created ON ai_agents (created_at DESC);
@@ -0,0 +1,20 @@
-- V003: ai_agent_groups
-- AI 에이전트 그룹 테이블
CREATE TABLE ai_agent_groups (
id BIGSERIAL PRIMARY KEY,
group_id VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description TEXT,
execution_mode VARCHAR(20) NOT NULL DEFAULT 'mixed'
CHECK (execution_mode IN ('parallel','sequential','mixed')),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active','inactive','archived')),
company_code VARCHAR(20),
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_ai_groups_company ON ai_agent_groups (company_code);
CREATE INDEX idx_ai_groups_status ON ai_agent_groups (status);
@@ -0,0 +1,16 @@
-- V004: ai_agent_group_members
-- AI 에이전트 그룹 멤버 테이블 (execution_order 로 mixed 모드 지원)
CREATE TABLE ai_agent_group_members (
id BIGSERIAL PRIMARY KEY,
group_id BIGINT NOT NULL REFERENCES ai_agent_groups(id) ON DELETE CASCADE,
agent_id BIGINT NOT NULL REFERENCES ai_agents(id) ON DELETE RESTRICT,
role_name VARCHAR(100) NOT NULL,
connectors JSONB NOT NULL DEFAULT '[]'::jsonb,
execution_order INTEGER NOT NULL DEFAULT 1,
config JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_group_members_group ON ai_agent_group_members (group_id, execution_order);
@@ -0,0 +1,27 @@
-- V005: ai_agent_api_keys
-- AI 에이전트 API 키 테이블 (sk-pipe-* 형식)
CREATE TABLE ai_agent_api_keys (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
key_hash CHAR(64) NOT NULL UNIQUE,
key_prefix VARCHAR(16) NOT NULL,
user_id VARCHAR(64) NOT NULL,
company_code VARCHAR(20),
agent_id BIGINT REFERENCES ai_agents(id) ON DELETE SET NULL,
permissions JSONB NOT NULL DEFAULT '["chat"]'::jsonb,
rate_limit INTEGER NOT NULL DEFAULT 60,
monthly_token_limit BIGINT NOT NULL DEFAULT 1000000,
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active','revoked')),
last_used_at TIMESTAMPTZ,
usage_count BIGINT NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_api_keys_hash ON ai_agent_api_keys (key_hash);
CREATE INDEX idx_api_keys_user ON ai_agent_api_keys (user_id);
CREATE INDEX idx_api_keys_company ON ai_agent_api_keys (company_code);
CREATE INDEX idx_api_keys_status ON ai_agent_api_keys (status);
@@ -0,0 +1,22 @@
-- V006: ai_agent_conversations
-- AI 에이전트 대화 세션 테이블
CREATE TABLE ai_agent_conversations (
id BIGSERIAL PRIMARY KEY,
conversation_id VARCHAR(64) NOT NULL UNIQUE,
agent_id BIGINT REFERENCES ai_agents(id) ON DELETE SET NULL,
user_id VARCHAR(64),
api_key_id BIGINT REFERENCES ai_agent_api_keys(id) ON DELETE SET NULL,
title VARCHAR(500),
message_count INTEGER NOT NULL DEFAULT 0,
total_tokens BIGINT NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'active',
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_conv_agent ON ai_agent_conversations (agent_id);
CREATE INDEX idx_conv_user ON ai_agent_conversations (user_id);
CREATE INDEX idx_conv_apikey ON ai_agent_conversations (api_key_id);
CREATE INDEX idx_conv_updated ON ai_agent_conversations (updated_at DESC);
@@ -0,0 +1,15 @@
-- V007: ai_agent_messages
-- AI 에이전트 대화 메시지 테이블
CREATE TABLE ai_agent_messages (
id BIGSERIAL PRIMARY KEY,
conversation_id BIGINT NOT NULL REFERENCES ai_agent_conversations(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL CHECK (role IN ('system','user','assistant','tool')),
content TEXT NOT NULL,
tool_calls JSONB,
token_count INTEGER NOT NULL DEFAULT 0,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_msg_conv ON ai_agent_messages (conversation_id, created_at);
@@ -0,0 +1,28 @@
-- V008: ai_agent_usage_logs
-- AI 에이전트 사용량 로그 테이블 (토큰/비용 추적)
CREATE TABLE ai_agent_usage_logs (
id BIGSERIAL PRIMARY KEY,
user_id VARCHAR(64),
api_key_id BIGINT REFERENCES ai_agent_api_keys(id) ON DELETE SET NULL,
agent_id BIGINT REFERENCES ai_agents(id) ON DELETE SET NULL,
conversation_id BIGINT REFERENCES ai_agent_conversations(id) ON DELETE SET NULL,
provider_name VARCHAR(50),
model_name VARCHAR(100),
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
total_tokens INTEGER NOT NULL DEFAULT 0,
cost_usd NUMERIC(12,6) NOT NULL DEFAULT 0,
response_time_ms INTEGER,
success BOOLEAN NOT NULL DEFAULT TRUE,
error_message TEXT,
request_path VARCHAR(500),
ip_address INET,
company_code VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_usage_created ON ai_agent_usage_logs (created_at DESC);
CREATE INDEX idx_usage_apikey ON ai_agent_usage_logs (api_key_id, created_at DESC);
CREATE INDEX idx_usage_agent ON ai_agent_usage_logs (agent_id, created_at DESC);
CREATE INDEX idx_usage_company ON ai_agent_usage_logs (company_code, created_at DESC);
@@ -0,0 +1,22 @@
-- V009: ai_agent_schedules
-- AI 에이전트 스케줄 테이블 (Quartz JDBC 연동)
CREATE TABLE ai_agent_schedules (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
group_id BIGINT NOT NULL REFERENCES ai_agent_groups(id) ON DELETE CASCADE,
cron_expression VARCHAR(100) NOT NULL,
timezone VARCHAR(50) NOT NULL DEFAULT 'Asia/Seoul',
input_message TEXT NOT NULL,
notification JSONB NOT NULL DEFAULT '{}'::jsonb,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
last_run_at TIMESTAMPTZ,
run_count BIGINT NOT NULL DEFAULT 0,
company_code VARCHAR(20),
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_sched_active ON ai_agent_schedules (is_active);
CREATE INDEX idx_sched_company ON ai_agent_schedules (company_code);
@@ -0,0 +1,24 @@
-- V010: ai_analysis_logs
-- AI 분석 실행 이력 테이블 (정확도 추적 포함)
CREATE TABLE ai_analysis_logs (
id BIGSERIAL PRIMARY KEY,
group_id BIGINT REFERENCES ai_agent_groups(id) ON DELETE SET NULL,
agent_id BIGINT REFERENCES ai_agents(id) ON DELETE SET NULL,
schedule_id BIGINT REFERENCES ai_agent_schedules(id) ON DELETE SET NULL,
execution_type VARCHAR(20) NOT NULL
CHECK (execution_type IN ('manual','api','schedule')),
input_message TEXT NOT NULL,
analysis_result TEXT NOT NULL,
prediction JSONB,
actual_result JSONB,
accuracy_score NUMERIC(5,2),
tokens_used INTEGER NOT NULL DEFAULT 0,
duration_ms INTEGER,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
company_code VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_analysis_group_created ON ai_analysis_logs (group_id, created_at DESC);
CREATE INDEX idx_analysis_company ON ai_analysis_logs (company_code, created_at DESC);
@@ -0,0 +1,20 @@
-- V011: ai_knowledge_files
-- AI 지식 파일 테이블 (RAG 문서 저장)
CREATE TABLE ai_knowledge_files (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(300) NOT NULL,
file_name VARCHAR(300),
category VARCHAR(100),
description TEXT,
content TEXT NOT NULL,
file_size BIGINT NOT NULL DEFAULT 0,
mime_type VARCHAR(100),
company_code VARCHAR(20),
created_by VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_knowledge_category ON ai_knowledge_files (category);
CREATE INDEX idx_knowledge_company ON ai_knowledge_files (company_code);
@@ -0,0 +1,156 @@
-- V012: Quartz JDBC JobStore 영구 테이블 (PostgreSQL)
-- 출처: spring-boot-starter-quartz / quartz tables_postgres.sql
CREATE TABLE QRTZ_JOB_DETAILS (
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250),
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE BOOLEAN NOT NULL,
IS_NONCONCURRENT BOOLEAN NOT NULL,
IS_UPDATE_DATA BOOLEAN NOT NULL,
REQUESTS_RECOVERY BOOLEAN NOT NULL,
JOB_DATA BYTEA,
CONSTRAINT PK_QRTZ_JOB_DETAILS PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250),
NEXT_FIRE_TIME BIGINT,
PREV_FIRE_TIME BIGINT,
PRIORITY INTEGER,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT NOT NULL,
END_TIME BIGINT,
CALENDAR_NAME VARCHAR(200),
MISFIRE_INSTR SMALLINT,
JOB_DATA BYTEA,
CONSTRAINT PK_QRTZ_TRIGGERS PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
CONSTRAINT FK_QRTZ_TRIGGERS_JOB_DETAILS FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT NOT NULL,
REPEAT_INTERVAL BIGINT NOT NULL,
TIMES_TRIGGERED BIGINT NOT NULL,
CONSTRAINT PK_QRTZ_SIMPLE_TRIGGERS PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
CONSTRAINT FK_QRTZ_SIMPLE_TRIGGERS FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
CONSTRAINT PK_QRTZ_CRON_TRIGGERS PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
CONSTRAINT FK_QRTZ_CRON_TRIGGERS FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512),
STR_PROP_2 VARCHAR(512),
STR_PROP_3 VARCHAR(512),
INT_PROP_1 INTEGER,
INT_PROP_2 INTEGER,
LONG_PROP_1 BIGINT,
LONG_PROP_2 BIGINT,
DEC_PROP_1 NUMERIC(13,4),
DEC_PROP_2 NUMERIC(13,4),
BOOL_PROP_1 BOOLEAN,
BOOL_PROP_2 BOOLEAN,
CONSTRAINT PK_QRTZ_SIMPROP_TRIGGERS PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
CONSTRAINT FK_QRTZ_SIMPROP_TRIGGERS FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BYTEA,
CONSTRAINT PK_QRTZ_BLOB_TRIGGERS PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
CONSTRAINT FK_QRTZ_BLOB_TRIGGERS FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS (
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BYTEA NOT NULL,
CONSTRAINT PK_QRTZ_CALENDARS PRIMARY KEY (SCHED_NAME, CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CONSTRAINT PK_QRTZ_PAUSED_TRIGGER_GRPS PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS (
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT NOT NULL,
SCHED_TIME BIGINT NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200),
JOB_GROUP VARCHAR(200),
IS_NONCONCURRENT BOOLEAN,
REQUESTS_RECOVERY BOOLEAN,
CONSTRAINT PK_QRTZ_FIRED_TRIGGERS PRIMARY KEY (SCHED_NAME, ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE (
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT NOT NULL,
CHECKIN_INTERVAL BIGINT NOT NULL,
CONSTRAINT PK_QRTZ_SCHEDULER_STATE PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
CONSTRAINT PK_QRTZ_LOCKS PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
@@ -0,0 +1,29 @@
-- V013: AI 테이블 추가 성능 인덱스
-- 복합 인덱스 및 자주 조회되는 컬럼에 대한 보조 인덱스
-- ai_agents: agent_id 문자열 조회 (ApiKey 검증 시)
CREATE INDEX idx_ai_agents_agent_id ON ai_agents (agent_id);
-- ai_agent_groups: group_id 문자열 조회
CREATE INDEX idx_ai_groups_group_id ON ai_agent_groups (group_id);
-- ai_agent_group_members: agent_id 역방향 조회 (에이전트 삭제 전 그룹 확인)
CREATE INDEX idx_group_members_agent ON ai_agent_group_members (agent_id);
-- ai_agent_conversations: conversation_id 문자열 조회
CREATE INDEX idx_conv_conv_id ON ai_agent_conversations (conversation_id);
-- ai_agent_conversations: company_code 조회 (멀티테넌시)
CREATE INDEX idx_conv_company ON ai_agent_conversations (user_id, updated_at DESC);
-- ai_analysis_logs: schedule_id 조회 (스케줄 실행 이력)
CREATE INDEX idx_analysis_schedule ON ai_analysis_logs (schedule_id, created_at DESC);
-- ai_analysis_logs: agent_id 조회
CREATE INDEX idx_analysis_agent ON ai_analysis_logs (agent_id, created_at DESC);
-- ai_agent_usage_logs: user_id + 기간 조회
CREATE INDEX idx_usage_user ON ai_agent_usage_logs (user_id, created_at DESC);
-- ai_knowledge_files: company_code + category 복합 조회
CREATE INDEX idx_knowledge_company_category ON ai_knowledge_files (company_code, category);
@@ -0,0 +1,200 @@
-- V014: AI 어시스턴트 메뉴 등록
-- 관리자 메뉴(MENU_TYPE='0')에 AI 어시스턴트 그룹 및 하위 7개 메뉴를 등록합니다.
-- SCREEN_GROUPS에 메뉴 그룹도 함께 등록하여 화면 그룹 트리와 연결합니다.
-- 멱등성: INSERT ... WHERE NOT EXISTS 사용 (unique constraint 없는 기존 테이블 대응).
-- ══════════════════════════════════════════════════════════════
-- 1. SCREEN_GROUPS: AI 어시스턴트 루트 그룹
-- ══════════════════════════════════════════════════════════════
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'AI 어시스턴트', 'AI_ASSISTANT', NULL, 0,
9900, '*', 'system', 'AI_ASSISTANT', 'AI 멀티 에이전트 관리 메뉴 그룹', 'Y', 'robot'
WHERE NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS
WHERE GROUP_CODE = 'AI_ASSISTANT' AND COMPANY_CODE = '*'
);
-- ══════════════════════════════════════════════════════════════
-- 2. MENU_INFO: AI 어시스턴트 부모 메뉴 (관리자 메뉴, 루트)
-- ══════════════════════════════════════════════════════════════
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT
'AI_ASSISTANT_ROOT', '0', '0', 'AI 어시스턴트',
'/admin/aiAssistant', 'AI 멀티 에이전트 관리',
9900, 'system', NOW(), 'active', '*', 'robot'
WHERE NOT EXISTS (
SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_ASSISTANT_ROOT'
);
-- ══════════════════════════════════════════════════════════════
-- 3. SCREEN_GROUPS: 하위 그룹 6개
-- PARENT_GROUP_ID는 위에서 삽입한 AI_ASSISTANT 그룹을 서브쿼리로 참조
-- ══════════════════════════════════════════════════════════════
-- 3-1. 에이전트 관리
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'에이전트 관리', 'AI_AGENTS', SG.ID, 1,
9901, '*', 'system', 'AI_ASSISTANT/AI_AGENTS', 'LLM 에이전트 CRUD', 'Y', 'cpu'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_AGENTS' AND COMPANY_CODE = '*'
);
-- 3-2. LLM 프로바이더
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'LLM 프로바이더', 'AI_PROVIDERS', SG.ID, 1,
9902, '*', 'system', 'AI_ASSISTANT/AI_PROVIDERS',
'Anthropic/OpenAI/Google/Ollama 프로바이더 관리', 'Y', 'cloud'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_PROVIDERS' AND COMPANY_CODE = '*'
);
-- 3-3. 멀티에이전트 워크스페이스
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'멀티에이전트 워크스페이스', 'AI_WORKSPACE', SG.ID, 1,
9903, '*', 'system', 'AI_ASSISTANT/AI_WORKSPACE', '에이전트 그룹 조립 및 실행', 'Y', 'diagram-3'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_WORKSPACE' AND COMPANY_CODE = '*'
);
-- 3-4. 대화 모니터링
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'대화 모니터링', 'AI_CONVERSATIONS', SG.ID, 1,
9904, '*', 'system', 'AI_ASSISTANT/AI_CONVERSATIONS', '에이전트 대화 메시지 열람', 'Y', 'chat-dots'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_CONVERSATIONS' AND COMPANY_CODE = '*'
);
-- 3-5. API 키 관리
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'API 키 관리', 'AI_API_KEYS', SG.ID, 1,
9905, '*', 'system', 'AI_ASSISTANT/AI_API_KEYS', 'sk-pipe-* API 키 발급 및 폐기', 'Y', 'key'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_API_KEYS' AND COMPANY_CODE = '*'
);
-- 3-6. 지식 라이브러리
INSERT INTO SCREEN_GROUPS (
GROUP_NAME, GROUP_CODE, PARENT_GROUP_ID, GROUP_LEVEL,
DISPLAY_ORDER, COMPANY_CODE, WRITER, HIERARCHY_PATH, DESCRIPTION, IS_ACTIVE, ICON
)
SELECT
'지식 라이브러리', 'AI_KNOWLEDGE', SG.ID, 1,
9906, '*', 'system', 'AI_ASSISTANT/AI_KNOWLEDGE', '지식 파일 업로드 및 관리', 'Y', 'book'
FROM SCREEN_GROUPS SG
WHERE SG.GROUP_CODE = 'AI_ASSISTANT' AND SG.COMPANY_CODE = '*'
AND NOT EXISTS (
SELECT 1 FROM SCREEN_GROUPS WHERE GROUP_CODE = 'AI_KNOWLEDGE' AND COMPANY_CODE = '*'
);
-- ══════════════════════════════════════════════════════════════
-- 4. MENU_INFO: 하위 메뉴 7개 (MENU_TYPE='0', 관리자 메뉴)
-- ══════════════════════════════════════════════════════════════
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_ASSISTANT', '0', 'AI_ASSISTANT_ROOT', 'AI 어시스턴트',
'/admin/aiAssistant', 'AI 어시스턴트 대시보드 (워크스페이스 리다이렉트)',
9901, 'system', NOW(), 'active', '*', 'robot'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_ASSISTANT');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_AGENTS', '0', 'AI_ASSISTANT_ROOT', '에이전트 관리',
'/admin/aiAssistant/agents', 'LLM 에이전트 CRUD',
9902, 'system', NOW(), 'active', '*', 'cpu'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_AGENTS');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_PROVIDERS', '0', 'AI_ASSISTANT_ROOT', 'LLM 프로바이더',
'/admin/aiAssistant/providers', 'Anthropic/OpenAI/Google/Ollama 프로바이더 설정',
9903, 'system', NOW(), 'active', '*', 'cloud'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_PROVIDERS');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_WORKSPACE', '0', 'AI_ASSISTANT_ROOT', '멀티에이전트 워크스페이스',
'/admin/aiAssistant/workspace', '에이전트 그룹 조립 및 실행',
9904, 'system', NOW(), 'active', '*', 'diagram-3'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_WORKSPACE');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_CONVERSATIONS', '0', 'AI_ASSISTANT_ROOT', '대화 모니터링',
'/admin/aiAssistant/conversations', '에이전트 대화 메시지 열람',
9905, 'system', NOW(), 'active', '*', 'chat-dots'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_CONVERSATIONS');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_API_KEYS', '0', 'AI_ASSISTANT_ROOT', 'API 키 관리',
'/admin/aiAssistant/api-keys-manage', 'sk-pipe-* API 키 발급 및 폐기',
9906, 'system', NOW(), 'active', '*', 'key'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_API_KEYS');
INSERT INTO MENU_INFO (
OBJID, MENU_TYPE, PARENT_OBJ_ID, MENU_NAME_KOR,
MENU_URL, MENU_DESC, SEQ, WRITER, CREATED_DATE, STATUS, COMPANY_CODE, MENU_ICON
)
SELECT 'AI_MENU_KNOWLEDGE', '0', 'AI_ASSISTANT_ROOT', '지식 라이브러리',
'/admin/aiAssistant/knowledge', '지식 파일 업로드 및 관리',
9907, 'system', NOW(), 'active', '*', 'book'
WHERE NOT EXISTS (SELECT 1 FROM MENU_INFO WHERE OBJID = 'AI_MENU_KNOWLEDGE');
-- ══════════════════════════════════════════════════════════════
-- 5. SCREEN_GROUPS.MENU_OBJID 연결 (루트 그룹 → 루트 메뉴)
-- ══════════════════════════════════════════════════════════════
UPDATE SCREEN_GROUPS
SET MENU_OBJID = 'AI_ASSISTANT_ROOT'
WHERE GROUP_CODE = 'AI_ASSISTANT'
AND COMPANY_CODE = '*'
AND MENU_OBJID IS NULL;