diff --git a/.gitignore b/.gitignore index fcb282a9..cb76d9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,12 @@ src/dotnet/QuantEngine.Web/wwwroot/_framework/ # 런타임 감사 로그 (append-only, 매 DAG 실행마다 증가) runtime/lineage_events.jsonl +# .NET 런타임 로그 (Serilog 등, 실행마다 재생성) +**/logs/*.log + +# Playwright 테스트 산출물 (스크린샷/트레이스, 실행마다 재생성) +test-results/ + # OS / 에디터 ... .DS_Store diff --git a/CLAUDE.md b/CLAUDE.md index b1664300..50569cbe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co │ ├─ Emergency password reset, auth endpoints │ │ └─ Portfolio/settings endpoints │ ├─────────────────────────────────────────────────────────────┤ +│ Application Layer (QuantEngine.Application) │ +│ ├─ ~22 orchestrators/services between Web and Infrastructure│ +│ ├─ KisDataCollectionOrchestrator, PipelineOrchestrator, │ +│ │ WorkspaceService, etc. │ +│ └─ Owns cross-cutting workflows; Web/Pages call these, not │ +│ Infrastructure directly │ +├─────────────────────────────────────────────────────────────┤ │ Repository Layer (Dapper ORM, SQL-driven) │ │ ├─ IWorkspaceRepository: workspace/settings CRUD │ │ ├─ ICollectionRepository: KIS runs, snapshots, errors │ @@ -56,7 +63,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co │ ├─ Unified schema: quantenginedb / quantengine schema │ │ ├─ Key tables: kis_tokens, kis_collection_runs/snapshots │ │ │ workspaces, account_snapshots, settings │ -│ ├─ Auto-migrations via DbUp (V001.sql, V002.sql, ...) │ +│ ├─ Auto-migrations via DbUp (V1__Name.sql, V2__Name.sql...) │ │ └─ Documented in: docs/db/quantengine.dbml (DBML sync) │ ├─────────────────────────────────────────────────────────────┤ │ Validation & Quality Gates (Python) │ @@ -96,366 +103,54 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Architecture**: .NET 9 + C# (web UI + APIs, primary); Python (legacy data collection, gradual deprecation) - **Critical Path**: KIS API → PostgreSQL → Admin Dashboard (read-only quotations only, no trading APIs) - **Validation**: 30+ contract validators + 9 parallel CI jobs + manual release gates -- **Web UI**: Blazor Interactive WebAssembly (MudBlazor) + ASP.NET Core Web API (API-First) +- **Web UI**: Razor Pages (Server-Rendered) + Bootstrap 5/Tabler + Cookie auth (Blazor/MudBlazor deprecated 2026-07-11 — see UI Design Principles below) - **Database**: PostgreSQL (Npgsql 8.0), single unified database - **Data Source**: KIS Open API (quotations/ranking read-only), with fallbacks - **Key Runtimes**: .NET 9, Python 3.9+, Node.js 16+ -### Migration Phases Status (2026-07-11) +### Migration Status Summary +- **Phase 1** (Web UI → Razor Pages): ✅ Complete (2026-07-11) +- **Phase 2** (KIS Data Collection Pipeline): 95% complete — pipeline orchestration (Python→.NET) pending +- **Phase 3** (Node.js→.NET CLI Tools): Planned +- **Phase 4** (CI/CD Pipeline Hardening): 80% complete — Gitea Actions runner has network limitations, manual-trigger release workflow active (see Deployment & Operations) +- **Phase 5** (Admin UI & Deployment Optimization): ✅ Complete (2026-07-11) -**Phase 1: Web UI Migration** ✅ 완료 (2026-07-11) -- **새로운 표준**: Razor Pages (Server-Rendered) + Cookie Authentication + Tabler UI -- **폐기 대상**: Blazor Interactive WebAssembly, MudBlazor, SmartAdmin -- **완료 기준 — Phase 1 Success Criteria**: - - ✅ Cookie 인증 구현 (AuthService + IpLockoutService + BCrypt) - - ✅ Razor Pages 렌더링 (Admin 레이아웃 + 3개 이상 기본 페이지) - - ✅ 공용 UI 컴포넌트 (4개 이상 shared partials) - - ✅ 보안: 백도어 제거, 무솔트 해시 마이그레이션, IP 잠금 - - ✅ 빌드 성공: 0 errors, 0 warnings - - ✅ CLAUDE.md 업데이트 (UI 기준 + 인증 정책) - - **✅ 모든 기준 충족됨** (2026-07-11) -- **구현 완료**: - - ✅ Cookie 기반 인증 (AuthService + IpLockoutService) - - ✅ Razor Pages CRUD 레이아웃 (_AdminLayout.cshtml, shared partials) - - ✅ Admin 페이지: Dashboard, Collection, Users (기본 구조) - - ✅ 공용 UI 컴포넌트: _ValidationSummary, _Pagination, _StatusBadge, _EmptyState - - ✅ 보안 개선: BCrypt 해싱, IP 잠금, 하드코딩된 백도어 제거 - - ✅ 빌드: 0 errors, 0 warnings (Newtonsoft.Json 보안 경고 제외) - - ✅ CLAUDE.md 완전 업데이트 (UI 기준, 인증, 상태 정의) -- **구현 미완료 (향후 작업)**: - - 🔄 Users 페이지: Create/Edit 폼 완성 - - 🔄 Collection 페이지: 스냅샷/에러 조회 상세화 - - 🔄 E2E 테스트: Playwright 스펙 업데이트 +Full phase-by-phase history and detail: [docs/MIGRATION_STATUS.md](docs/MIGRATION_STATUS.md) -**Phase 2: KIS Data Collection Pipeline** ✅ 95% COMPLETE -- ✅ KIS API Client: Full implementation complete - - IKisApiClient interface (5 quotation methods) - - KisApiClient with real HTTP implementation + token caching - - All governance rules enforced (no trading APIs) - - Windows env var + registry fallback for credentials - - Build: 0 errors, 0 warnings -- ✅ PostgreSQL Infrastructure: Complete - - PostgresTokenCache (token management, 10-min skew) - - CollectionRepository (full CRUD + dashboard aggregations) - - Auto-creates kis_tokens, kis_collection_runs, kis_collection_snapshots, kis_collection_errors - - Dapper ORM + parameterized SQL (injection-proof) -- ✅ Web API Endpoints: Complete - - CollectionEndpoints (6 endpoints: state, runs, snapshots, errors, latest, start) - - ApiClient for Blazor consumption -- ✅ Blazor UI: Complete - - Collection.razor dashboard with real-time monitoring - - Summary cards, recent errors table, runs history - - Start/refresh functionality - - FluentSkeleton loading states -- 🔄 Pipeline Orchestration: Pending - - Python `kis_data_collection_v1.py` → .NET (data fetching + validation) - - Real KIS API data collection workflow integration - - E2E test: API → DB → UI validation +## Deployment & Operations -**Phase 3: Node.js→.NET CLI Tools** 📋 PLANNED -- Makefile created (npm → make mappings) -- np operations documented - -**Phase 4: CI/CD Pipeline Hardening** ✅ 80% COMPLETE (2026-07-11) -- ✅ deploy-prod.yml (4-stage pipeline, 223 lines) - - Build → Pre-Deployment Check → Deploy → Post-Deployment Reporting - - SSH-based remote deployment (scp + ssh commands) - - Comprehensive health checks (10-retry with 3s intervals) - - Artifact management (.tar.gz) -- ✅ Workflow consolidation (2 active files) - - ci.yml: PR validation only (maintains 29 validators) - - deploy-prod.yml: Production deployment - - Deleted: merge-to-main.yml (non-functional), fast-validation.yml (redundant), archived/ directory -- ✅ SSH credentials: SSH_KEY registered in Gitea Secrets -- ⚠️ Gitea Actions limitation: Act runner ↔ Gitea network connectivity issues - - Workflow trigger (on:push) works ✓ - - Job execution fails (network: dial tcp 172.18.0.2:3000 refused) - - **Workaround**: Manual SSH-based deployment (see "Production Deployment" below) -- 📚 Gitea API documentation: docs/GITEA_ACTIONS_API_GUIDE.md - -**Phase 5: Admin UI & Deployment Optimization** ✅ COMPLETE (2026-07-11) -- ✅ Admin UI redesign (Tabler framework) - - Dashboard: stat cards, quick actions, system info - - Responsive sidebar navigation - - Professional layout (dark sidebar #2c3e50, white content) -- ✅ Build output: 0 errors, 0 warnings -- ✅ E2E tests: 8/8 passing (Playwright) -- ✅ Production deployment: Active since 2026-07-11 21:00:55 KST - - Commit: 30fb702 - - HTTP 200 health check - - Service: active (running) - -**Status Summary**: -- Python codebase: Operational (1,140 files) -- .NET 9 coverage: Core (✅), Infrastructure (✅), API (✅), Web UI (✅) -- Database: PostgreSQL fully migrated -- CI/CD: Manual SSH deployment (fully operational), Gitea Actions (limited by infrastructure) -- Release gates: Python gates remain authority until Phase 2 integration testing complete - -## Deployment & Operations (Phase 4-5, 2026-07-11) - -**Production Server**: Hetzner Cloud `178.104.200.7` (kjh2064@178.104.200.7) - -Projects on server: -1. **TaxBaik** (홈페이지) — Nginx location `/taxbaik` -2. **QuantEngine** (데이터 수집/분석) — Nginx location `/quantengine` +**Production Server**: Hetzner Cloud `178.104.200.7` (kjh2064@178.104.200.7). Full runbook +(release/deploy workflow, SSH key setup, health checks, rollback, troubleshooting): +[docs/DEPLOYMENT_RUNBOOK.md](docs/DEPLOYMENT_RUNBOOK.md) ### ⚠️ CRITICAL: CI/CD-Only Deployment Mandate -**Rule**: ALL production deployments MUST go through Gitea Actions CI/CD. Manual SSH deployments are **FORBIDDEN**. - -**Why**: -- Automatic validation (build, health checks, version verification) -- Audit trail (all deployments logged in Gitea Actions) -- Consistent process (no manual errors) -- Rollback safety (deployment history retained) -- Release traceability (version control via git tags) +**Rule**: ALL production deployments MUST go through Gitea Actions CI/CD +(`prepare-release.yml` → `deploy-prod.yml`, both manual `workflow_dispatch`). Manual SSH +deployments are **FORBIDDEN** (audit trail, consistent health checks, rollback safety). ### ⚠️ CRITICAL: DB Secret Management (Incident 2026-07-12) -**Incident**: `quant.taxbaik.com/login`이 `28P01 password authentication failed`로 장애 발생. -원인: `appsettings.Production.json`에 하드코딩되어 배포된 DB 비밀번호가, 실제 DB 비밀번호가 -로테이션된 이후에도 계속 옛날 값(심지어 이전 세션에서 검증 없이 넣은 placeholder였던 적도 있음) +**Incident**: `quant.taxbaik.com/login`이 `28P01 password authentication failed`로 장애 발생 — +`appsettings.Production.json`에 하드코딩되어 배포된 DB 비밀번호가 로테이션 이후에도 옛날 값 그대로 배포되고 있었음. **Rule**: **DB 접속 문자열(`ConnectionStrings`)은 절대 `appsettings.Production.json`이나 -워크플로우 파일에 하드코딩하지 않는다.** `prepare-release.yml`이 생성하는 -`appsettings.Production.json`에는 `Logging` 설정만 있고 `ConnectionStrings`는 없다 — -이는 의도된 설계다 (Gitea Release는 누구나 다운로드 가능한 아티팩트이므로 시크릿을 -담으면 안 됨). +워크플로우 파일에 하드코딩하지 않는다.** `prepare-release.yml`이 생성하는 아티팩트에는 +`Logging` 설정만 있고 `ConnectionStrings`는 없다 (의도된 설계 — Gitea Release는 누구나 +다운로드 가능하므로 시크릿을 담으면 안 됨). -**실제 DB 비밀번호의 출처**: 프로덕션 서버의 `/home/kjh2064/.config/quantengine.env` -파일 (`ConnectionStrings__DefaultConnection=...` 형식) 하나뿐이며, -`quantengine.service.d/env.conf` drop-in의 `EnvironmentFile=` 지시자로 systemd가 -이 값을 환경변수로 주입한다. ASP.NET Core 설정 우선순위상 **환경변수가 -`appsettings.Production.json`을 오버라이드**하므로, 배포되는 아티팩트 자체에는 -DB 정보가 없어도 서비스는 정상 동작한다. +실제 DB 비밀번호는 프로덕션 서버의 `/home/kjh2064/.config/quantengine.env` 파일에만 존재하며, +`quantengine.service.d/env.conf`의 `EnvironmentFile=` 지시자로 systemd가 환경변수로 주입한다 +(환경변수가 `appsettings.Production.json`을 오버라이드). -**DB 비밀번호가 바뀌면** (로테이션 등): `/home/kjh2064/.config/quantengine.env` 파일만 -갱신하고 `sudo systemctl restart quantengine`. 워크플로우 파일이나 Gitea Secrets는 -건드릴 필요 없음 (배포 파이프라인은 DB 비밀번호를 모른 채로 동작해야 정상). +**DB 비밀번호 로테이션 시**: `/home/kjh2064/.config/quantengine.env`만 갱신 + +`sudo systemctl restart quantengine`. 워크플로우 파일/Gitea Secrets는 건드리지 않음. -**배포 전 체크리스트에 추가**: -- ✅ 새 릴리즈 배포 후 반드시 `/Account/Login` 실제 HTTP 응답 + `journalctl -u quantengine`에서 - `28P01`/`password authentication failed` 부재 확인 (단순 프로세스 `active` 상태만으로는 - DB 연결 실패를 못 잡음 — ASP.NET Core는 DB 없이도 기동은 되고 로그인 요청 시점에야 실패함) -- ✅ `.config/quantengine.env`의 존재와 `quantengine.service.d/env.conf`의 - `EnvironmentFile=` 배선이 서버에 유지되고 있는지 (systemd unit 자체를 재생성/덮어쓰는 - 배포 방식으로 전환할 경우 이 drop-in이 날아가지 않는지 확인 필요) - -### Production Deployment Strategy (Release-Based) - -**Architecture**: Two-Workflow System (Release Creation → Deployment) - -#### Workflow 1: prepare-release.yml (Release Creation) - -**Purpose**: Create a release with built artifact - -**Trigger**: Manual (`workflow_dispatch`) -```bash -# Visit Gitea Actions and select prepare-release.yml -# Input version: v0.1.20260711 (or any semantic version) -``` - -**What it does**: -1. ✓ Build (restore, build, publish) -2. ✓ Generate `appsettings.Production.json` -3. ✓ Package artifact: `.tar.gz` -4. ✓ Create git tag: `v0.1.20260711` -5. ✓ Create Gitea Release with artifact attached -6. ✓ Notify: Release ready for deployment - -**Output**: Gitea Release with downloadable artifact - -#### Workflow 2: deploy-prod.yml (Deployment) - -**Purpose**: Deploy a release to production - -**Trigger**: Manual (`workflow_dispatch`) -```bash -# Visit Gitea Actions and select deploy-prod.yml -# Input release: v0.1.20260711 (optional — uses latest if empty) -``` - -**What it does**: -1. ✓ Fetch Release (from Gitea Releases) -2. ✓ Download artifact -3. ✓ Verify SSH credentials -4. ✓ Upload to production server -5. ✓ Extract and symlink -6. ✓ Restart service -7. ✓ 6-point health checks -8. ✓ Report deployment status - -**Deployment Pipeline (5 Stages)**: - -| Stage | Purpose | Timeout | -|-------|---------|---------| -| 1. Fetch Release | Query Gitea Releases, download artifact | 10min | -| 2. Pre-Check | Verify SSH keys, secrets, release | 5min | -| 3. Deploy | Upload, extract, symlink, restart service | 30min | -| 4. Health Check | 6-point verification (HTTP, CSS, login, service, release, DB auth) | 10min | -| 5. Report | Final deployment status | Auto | - -**Health Checks (Automatic)**: -- ✓ HTTP 200 on `/Account/Login` -- ✓ Login page content verification -- ✓ CSS file loads (`/css/admin.css`) -- ✓ Service status (systemctl active) -- ✓ Release verification (deployed release tag matches) -- ✓ **DB authentication check** (`journalctl`에서 `28P01`/`password authentication failed` - 부재 확인 — GET `/Account/Login`은 DB가 끊겨도 200을 반환하므로 이 체크가 없으면 - DB 장애를 배포 파이프라인이 놓친다. 2026-07-12 사고 이후 추가됨) - -**Complete Deployment Flow**: -``` -1. Code committed to main branch -2. Create release: prepare-release.yml workflow_dispatch (manual) - → Builds code - → Creates Gitea Release with artifact - → Tags repository -3. Deploy release: deploy-prod.yml workflow_dispatch (manual) - → Selects release version - → Downloads artifact from Gitea Release - → Deploys to production server - → Runs health checks - → Reports status -``` - -### Pre-Deployment Checklist - -**Before creating a release**, verify: -1. ✅ Local build: `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release` (0 errors, 0 warnings) -2. ✅ E2E tests pass: `npx playwright test` -3. ✅ Admin pages verified (200 status, no 500 errors) -4. ✅ All changes committed and pushed to main branch -5. ✅ No uncommitted changes: `git status` - -### Release & Deployment Workflow - -**Step 1: Create Release (prepare-release.yml)** -```bash -# Visit Gitea Actions -# https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions - -# Run prepare-release.yml workflow -# Input: version = v0.1.20260711 - -# Workflow will: -# - Build and publish -# - Package artifact -# - Create git tag -# - Create Gitea Release -# - Attach artifact -``` - -**Step 2: Deploy Release (deploy-prod.yml)** -```bash -# Visit Gitea Actions (same page) -# Run deploy-prod.yml workflow -# Input: release = v0.1.20260711 (leave empty for latest) - -# Workflow will: -# - Download artifact from release -# - Deploy to production server -# - Run health checks -# - Report status -``` - -### SSH Key Configuration (Required) - -**Setup (One-time)**: -1. Generate ED25519 key locally (or reuse existing): - ```bash - ssh-keygen -t ed25519 -f ~/.ssh/quantengine_deploy -C "QuantEngine CI/CD" - ``` - -2. Add public key to production server: - ```bash - ssh-copy-id -i ~/.ssh/quantengine_deploy.pub kjh2064@178.104.200.7 - ``` - -3. Get private key in base64 format: - ```bash - # macOS/Linux - base64 -w 0 ~/.ssh/quantengine_deploy > /tmp/key_b64.txt - cat /tmp/key_b64.txt | pbcopy - - # Or Windows PowerShell - $key = Get-Content ~/.ssh/quantengine_deploy -Raw - [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($key)) | Set-Clipboard - ``` - -4. Configure in Gitea: - - URL: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/settings/secrets - - Add secret: `DEPLOY_SSH_KEY_B64` (base64-encoded private key) - - Or: `DEPLOY_SSH_KEY` (raw PEM format) - - Also add: `GITEA_TOKEN` (for release API access) - - Generate at: https://gitea.taxbaik.com/user/settings/applications - - Required permissions: `repo` + `read:actions` - -### Deployment Monitoring - -**During Deployment**: -- Watch live in Gitea Actions UI -- Jobs complete in order: Build → Pre-Check → Deploy → Health Check → Report - -**After Deployment**: -```bash -# SSH into server -ssh kjh2064@178.104.200.7 - -# Check active deployment -readlink ~/quantengine_active - -# View service status -systemctl status quantengine - -# Tail live logs -journalctl -u quantengine -f - -# Health check -curl -I http://127.0.0.1:5000/Account/Login -``` - -### Automatic Rollback (if health check fails) - -If health check fails, deployment stops automatically: -1. Service restart may fail -2. Symlink update reverts to previous deployment -3. Gitea Actions marks deployment as FAILED -4. Logs include failure details - -Manual rollback (if needed): -```bash -# List deployments -ls -lht ~/deployments/quantengine_* - -# Revert symlink to previous version -ln -sfn /home/kjh2064/deployments/quantengine_YYYYMMDD_HHMMSS_COMMIT ~/quantengine_active - -# Restart service -sudo systemctl restart quantengine - -# Verify -curl http://127.0.0.1:5000/Account/Login -``` - -### Troubleshooting Deployment Failures - -**Issue**: Build fails -- Check: `dotnet build` locally first -- Ensure: No compilation errors, 0 warnings - -**Issue**: Health check timeout -- Check: Service logs: `journalctl -u quantengine -n 50` -- Check: Port 5000 listening: `ss -tlnp | grep 5000` -- Check: DB connectivity in appsettings.Production.json - -**Issue**: SSH key error -- Verify: `DEPLOY_SSH_KEY_B64` or `DEPLOY_SSH_KEY` in Gitea Secrets -- Check: Public key added to `~/.ssh/authorized_keys` on server -- Test: `ssh -i ~/.ssh/key_file kjh2064@178.104.200.7 echo OK` +**배포 후 필수 확인**: `/Account/Login` 실제 HTTP 응답 + `journalctl -u quantengine`에서 +`28P01`/`password authentication failed` 부재 확인 (단순 `active` 상태만으로는 DB 연결 실패를 +못 잡음 — ASP.NET Core는 DB 없이도 기동은 되고 로그인 요청 시점에야 실패함). 6-point 헬스체크에 +DB 인증 확인이 포함되어 있음. 상세: [docs/DEPLOYMENT_RUNBOOK.md](docs/DEPLOYMENT_RUNBOOK.md) ### Git Repository @@ -463,149 +158,109 @@ curl http://127.0.0.1:5000/Account/Login - **HTTP**: `https://gitea.taxbaik.com/kjh2064/QuantEngineByItz.git` - **SSH**: `ssh://git@gitea.taxbaik.com:2222/kjh2064/QuantEngineByItz.git` -## UI Design Principles (2026-07-11 — Migrated to Razor Pages) +## UI Design Principles -### Framework & Design System (NEW — 2026-07-11) +- **Primary Framework**: ASP.NET Core Razor Pages (server-rendered, no WASM) + Bootstrap 5 + + Tabler UI. Blazor Interactive WebAssembly, MudBlazor, SmartAdmin are **deprecated** + (2026-07-11). The `.sln` has 6 projects: Core, Application, Infrastructure, Web, Tools, + Core.Tests — there is no `QuantEngine.Web.Client` in the repo (fully removed, not just + excluded from the solution; verified 2026-07-30). +- **Authentication**: Cookie (`QuantEngine.Admin.Auth`, HttpOnly, SameSite=Lax, 12h sliding) + + BCrypt + IP lockout (3 strikes/15min). Folder-level `[Authorize]` via + `AuthorizeFolder("/Admin")` — no per-page repetition. Login: `/Account/Login`. +- **Component rules**: PageModel constructor DI (repos/services directly, no API hop); + `OnPostAsync()`/`OnPostDeleteAsync()` multi-handler pattern; validation failures return + `Page()`; DataAnnotations + FluentValidation double-check. +- Full component-to-Bootstrap/Tabler mapping table (buttons, inputs, tables, badges, modals, + validation messages): [docs/UI_GUIDELINES.md](docs/UI_GUIDELINES.md) -- **Primary Framework**: ASP.NET Core Razor Pages + Bootstrap 5 + Tabler UI -- **Design System**: Tabler (Bootstrap 5 기반), 밀집 레이아웃 + 전통 서버 렌더링 -- **Render Mode**: **Server-side Razor Pages** — 모든 Admin UI는 서버에서 렌더링, Cookie 기반 인증 (API-First WASM 폐기) -- **Authentication**: Cookie Authentication (HttpOnly) + BCrypt password hashing + IP lockout (3 strikes, 15-min) -- **Deprecation**: **Blazor Interactive WebAssembly 폐기**, **MudBlazor 컴포넌트 폐기** (2026-07-11), **SmartAdmin 폐기**. 기존 WASM 코드는 `/QuantEngine.Web.Client` 폴더에 참고용으로만 보관 (`.sln`에서 제외) +## Development Commands -### Component Development Rules (NEW) +### .NET +```powershell +cd src/dotnet +dotnet restore +dotnet build # Debug (0 errors, 0 warnings) +dotnet build -c Release +dotnet watch run --project QuantEngine.Web # Hot-reload → http://localhost:5265 +dotnet test src/dotnet/QuantEngine.Core.Tests -c Release +dotnet test src/dotnet/QuantEngine.sln --configuration Release +``` -1. **All Admin UI Development** (New + Refactored): - - Use **Razor Pages** (.cshtml + .cshtml.cs PageModel) exclusively for admin - - UI는 Repository/Service를 생성자 DI로 직접 호출 (API 홉 없음) - - Bootstrap 5 + Tabler UI CSS classes for styling - - **Form Validation**: DataAnnotations DTO + FluentValidation IValidator 이중 검증 - - HTML `
` + tag helpers (`asp-for`, `asp-action`, `asp-page`) - -2. **Authentication & Authorization**: - - Cookie name: `QuantEngine.Admin.Auth` (HttpOnly, SameSite=Lax) - - Session duration: 12 hours (sliding expiration) - - Folder-level `[Authorize]` via `AuthorizeFolder("/Admin")` convention (per-page 반복 금지) - - Login: `/Account/Login` (Razor Page, NO WASM) - - Password: BCrypt-hashed (auto-migrates existing SHA-256 hashes on first login) - - IP Lockout: 3 failed attempts → 15-minute lockout - -3. **Data & Form Patterns**: - - PageModel constructor: `public IndexModel(IWorkspaceRepository repo, ILogger logger)` - - Form submission: `OnPostAsync()` / `OnPostDeleteAsync()` (multi-handler pattern) - - Validation failures: return `Page()` (re-render with ModelState errors) - - Pagination: `PaginationModel` record (Page, TotalPages, Func BuildPageUrl) - - Empty states: `` - -4. **Component Mapping** (Bootstrap 5 + Tabler): - -| UI Element | Component | Notes | -|-----------|-----------|-------| -| Button | `