Files
QuantEngineByItz/src/dotnet/QuantEngine.Infrastructure/Data/DbMigrator.cs
T
kjh2064 90bbb1860d
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Snapshot Admin Deployment / build-and-deploy (push) Failing after 2m30s
Deploy to Production / Build & Deploy to Production (push) Failing after 3m49s
feat(web): add auth and fix deployment checks
2026-07-01 13:02:10 +09:00

279 lines
12 KiB
C#

using System.Data;
using Dapper;
namespace QuantEngine.Infrastructure.Data
{
public class DbMigrator
{
private readonly IDbConnectionFactory _connectionFactory;
public DbMigrator(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public void Migrate()
{
using var conn = _connectionFactory.CreateConnection();
conn.Open();
// Create schema if not exists
conn.Execute("CREATE SCHEMA IF NOT EXISTS quantengine;");
// 0. kis_tokens
conn.Execute(@"
CREATE TABLE IF NOT EXISTS kis_tokens (
account TEXT PRIMARY KEY,
access_token TEXT NOT NULL,
expires_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
");
// 0b. workspace_account
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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 workspace_account(is_active, username);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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 workspace_session(username, expires_at DESC);
");
// 1. collection_runs
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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
);
");
// 2. collection_snapshots
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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 collection_snapshots(ticker, created_at DESC);
");
// 3. collection_source_errors
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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 collection_source_errors(run_id, source_name);
");
// 4. settings
conn.Execute(@"
CREATE TABLE IF NOT EXISTS settings (
ordinal INT NOT NULL,
key TEXT PRIMARY KEY,
value_json TEXT NOT NULL,
note TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL
);
");
// 5. account_snapshot
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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 account_snapshot(captured_at);
CREATE INDEX IF NOT EXISTS idx_account_snapshot_ticker ON account_snapshot(ticker);
");
// 6. workspace_meta
conn.Execute(@"
CREATE TABLE IF NOT EXISTS workspace_meta (
key TEXT PRIMARY KEY,
value_json TEXT NOT NULL
);
");
// 7. workspace_change_log
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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
);
");
// 8. workspace_approval_v2
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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)
);
");
// 9. workspace_lock
conn.Execute(@"
CREATE TABLE IF NOT EXISTS 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)
);
");
conn.Execute(@"
INSERT INTO quantengine.workspace_account (
ordinal, username, password_hash, role, is_active, created_at, updated_at
)
SELECT 1, 'admin', '8C6976E5B5410415BDE908BD4DEE15DFB167A9C873FC4BB8A81F6F2AB448A918', 'Admin', 'true', NOW()::text, NOW()::text
WHERE NOT EXISTS (
SELECT 1 FROM quantengine.workspace_account WHERE username = 'admin'
);
");
// 10. engine_history schema and tables
conn.Execute(@"
CREATE SCHEMA IF NOT EXISTS engine_history;
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS engine_history.market_raw_history (
id BIGSERIAL PRIMARY KEY,
source_id TEXT NOT NULL,
observed_at TEXT NOT NULL,
source_name TEXT NOT NULL,
instrument_id TEXT NOT NULL,
field_name TEXT NOT NULL,
field_value TEXT NOT NULL,
unit TEXT NOT NULL,
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_market_raw_history_created_at ON engine_history.market_raw_history (created_at DESC);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS engine_history.factor_version_history (
id BIGSERIAL PRIMARY KEY,
factor_id TEXT NOT NULL,
factor_version TEXT NOT NULL,
effective_from TEXT NOT NULL,
effective_to TEXT NOT NULL,
formula_id TEXT NOT NULL,
source_version TEXT NOT NULL,
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_factor_version_history_created_at ON engine_history.factor_version_history (created_at DESC);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS engine_history.factor_output_history (
id BIGSERIAL PRIMARY KEY,
factor_output_id TEXT NOT NULL,
observed_at TEXT NOT NULL,
factor_id TEXT NOT NULL,
factor_version TEXT NOT NULL,
output_value TEXT NOT NULL,
output_gate TEXT NOT NULL,
source_version TEXT NOT NULL,
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_factor_output_history_created_at ON engine_history.factor_output_history (created_at DESC);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS engine_history.decision_result_history (
id BIGSERIAL PRIMARY KEY,
decision_id TEXT NOT NULL,
decided_at TEXT NOT NULL,
instrument_id TEXT NOT NULL,
action TEXT NOT NULL,
gate TEXT NOT NULL,
score TEXT NOT NULL,
source_version TEXT NOT NULL,
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_decision_result_history_created_at ON engine_history.decision_result_history (created_at DESC);
");
conn.Execute(@"
CREATE TABLE IF NOT EXISTS engine_history.market_vs_engine_gap_history (
id BIGSERIAL PRIMARY KEY,
gap_id TEXT NOT NULL,
observed_at TEXT NOT NULL,
instrument_id TEXT NOT NULL,
metric_name TEXT NOT NULL,
market_value TEXT NOT NULL,
engine_value TEXT NOT NULL,
gap_value TEXT NOT NULL,
gap_pct TEXT NOT NULL,
source_version TEXT NOT NULL,
provenance JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_market_vs_engine_gap_history_created_at ON engine_history.market_vs_engine_gap_history (created_at DESC);
");
}
}
}