Files
QuantEngineByItz/src/dotnet/QuantEngine.Infrastructure/Migrations/V1__Initial_Schema.sql
T
kjh2064 c6a5e93773
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 8s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 14s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 1m45s
feat: DbUp 마이그레이션 및 Razor Pages 어드민 UI 완성 (Phase 1-3)
## Summary
-  DbUp 기반 SQL 마이그레이션 시스템 구현
  * V1: 기본 스키마 및 테이블 (quantengine, kis_tokens, workspace_account 등)
  * V2: KIS 데이터 수집 테이블 (kis_collection_runs, kis_collection_snapshots, kis_collection_errors)
  * V3: 엔진 히스토리 스키마 (market_raw_history, factor_version_history 등)
  * V4: 초기 관리자 계정 생성

-  Razor Pages 어드민 UI 완성
  * Users: Create, Edit 페이지 + Deactivate 기능
  * Collection: Errors, Snapshots 상세 페이지
  * Monitoring: 실시간 모니터링 대시보드
  * Operations: 작업 관리 및 스케줄 상태 조회

-  E2E 테스트 업데이트
  * login.spec.ts: Blazor WASM → Razor Pages 기반 로그인 테스트 (3개 통과)
  * admin-pages.spec.ts: 관리자 페이지 플로우 테스트 신규 작성

-  보안 업그레이드
  * Newtonsoft.Json 13.0.3 (GHSA-5crp-9r3c-p9vr 취약성 해결)
  * BCrypt 비밀번호 해싱 (SHA-256 자동 마이그레이션)

## Build Status
- 빌드: 성공 (0 errors, 1 warning - Newtonsoft.Json)
- 마이그레이션: 성공 (원격 서버 검증됨)
- E2E 테스트: 3개 통과 (DB 의존 3개는 로컬 환경 제약)

## Remote Verification
원격 서버 (Hetzner 178.104.200.7)에서:
- 2026-07-11 17:04:23.474: Database migration and initialization successful
- Hangfire SQL objects 설치됨
- 애플리케이션 정상 실행 중

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-07-11 17:55:01 +09:00

150 lines
4.5 KiB
SQL

-- V1__Initial_Schema.sql
-- Create quantengine schema and core tables
CREATE SCHEMA IF NOT EXISTS quantengine;
-- KIS API Token Cache
CREATE TABLE IF NOT EXISTS quantengine.kis_tokens (
account TEXT PRIMARY KEY,
access_token TEXT NOT NULL,
expires_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- User Account Management
CREATE TABLE IF NOT EXISTS quantengine.workspace_account (
ordinal INT NOT NULL,
username TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'Admin',
is_active TEXT NOT NULL DEFAULT 'true',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_workspace_account_active ON quantengine.workspace_account(is_active, username);
-- Session Management
CREATE TABLE IF NOT EXISTS quantengine.workspace_session (
session_token_hash TEXT PRIMARY KEY,
username TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'Admin',
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_workspace_session_username ON quantengine.workspace_session(username, expires_at DESC);
-- Collection Runs
CREATE TABLE IF NOT EXISTS quantengine.collection_runs (
run_id TEXT PRIMARY KEY,
collector_name TEXT NOT NULL,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL,
input_source TEXT,
output_json_path TEXT,
output_db_path TEXT,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Collection Snapshots
CREATE TABLE IF NOT EXISTS quantengine.collection_snapshots (
run_id TEXT NOT NULL,
dataset_name TEXT NOT NULL,
ticker TEXT NOT NULL,
name TEXT,
sector TEXT,
as_of_date TEXT,
source_priority TEXT,
source_status TEXT,
payload_json TEXT NOT NULL,
provenance_json TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (run_id, dataset_name, ticker)
);
CREATE INDEX IF NOT EXISTS idx_collection_snapshots_ticker_time ON quantengine.collection_snapshots(ticker, created_at DESC);
-- Collection Source Errors
CREATE TABLE IF NOT EXISTS quantengine.collection_source_errors (
run_id TEXT NOT NULL,
ticker TEXT,
source_name TEXT NOT NULL,
error_kind TEXT NOT NULL,
error_message TEXT NOT NULL,
payload_json TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_collection_source_errors_run ON quantengine.collection_source_errors(run_id, source_name);
-- Settings
CREATE TABLE IF NOT EXISTS quantengine.settings (
ordinal INT NOT NULL,
key TEXT PRIMARY KEY,
value_json TEXT NOT NULL,
note TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL
);
-- Account Snapshots
CREATE TABLE IF NOT EXISTS quantengine.account_snapshot (
ordinal INT NOT NULL,
row_json TEXT NOT NULL,
captured_at TEXT NOT NULL DEFAULT '',
account TEXT NOT NULL DEFAULT '',
account_type TEXT NOT NULL DEFAULT '',
ticker TEXT NOT NULL DEFAULT '',
name TEXT NOT NULL DEFAULT '',
parse_status TEXT NOT NULL DEFAULT '',
user_confirmed TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_account_snapshot_captured_at ON quantengine.account_snapshot(captured_at);
CREATE INDEX IF NOT EXISTS idx_account_snapshot_ticker ON quantengine.account_snapshot(ticker);
-- Workspace Metadata
CREATE TABLE IF NOT EXISTS quantengine.workspace_meta (
key TEXT PRIMARY KEY,
value_json TEXT NOT NULL
);
-- Workspace Change Log
CREATE TABLE IF NOT EXISTS quantengine.workspace_change_log (
id SERIAL PRIMARY KEY,
domain TEXT NOT NULL,
action TEXT NOT NULL,
target_ref TEXT NOT NULL DEFAULT '',
actor TEXT NOT NULL DEFAULT 'system',
note TEXT NOT NULL DEFAULT '',
before_json TEXT NOT NULL DEFAULT 'null',
after_json TEXT NOT NULL DEFAULT 'null',
created_at TEXT NOT NULL
);
-- Workspace Approval
CREATE TABLE IF NOT EXISTS quantengine.workspace_approval_v2 (
domain TEXT NOT NULL,
target_ref TEXT NOT NULL DEFAULT '*',
status TEXT NOT NULL,
approved_by TEXT NOT NULL DEFAULT '',
approved_at TEXT NOT NULL DEFAULT '',
note TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL,
PRIMARY KEY (domain, target_ref)
);
-- Workspace Lock
CREATE TABLE IF NOT EXISTS quantengine.workspace_lock (
domain TEXT NOT NULL,
target_ref TEXT NOT NULL DEFAULT '',
locked_by TEXT NOT NULL DEFAULT '',
reason TEXT NOT NULL DEFAULT '',
locked_at TEXT NOT NULL,
PRIMARY KEY (domain, target_ref)
);