fix: MudBlazor v8 compatibility, static asset conflict, deploy host domain
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 5s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 9s
Deploy to Production / Build & Deploy to Production (push) Failing after 2m32s

- fix CS0542: rename Users/Assets private members to _users/_assets
- fix CS0246: MudDialogInstance -> IMudDialogInstance
- fix AppTheme: PaletteLight/PaletteDark, string[] FontFamily, string typography values
- fix DataCollectionMonitoring: @(ticker.DataPointCount)개 Korean char parsing
- fix SchedulerService: add missing Hangfire namespaces, fix GetJobStatus return type
- fix Program.cs: move PostgreSQL setup above Hangfire registration
- fix ConfirmDialog: BackdropClick, Canceled spelling for MudBlazor v8
- fix static asset conflict: remove wwwroot/_framework from git tracking
- chore: add wwwroot/_framework/ to .gitignore
- ci: change DEPLOY_HOST from IP to quant.taxbaik.com domain
This commit is contained in:
2026-07-05 17:43:36 +09:00
parent 7daedbff3c
commit 543b327d27
446 changed files with 237 additions and 1494 deletions
+166 -30
View File
@@ -11,7 +11,7 @@ concurrency:
cancel-in-progress: true
env:
DEPLOY_HOST: 178.104.200.7
DEPLOY_HOST: quant.taxbaik.com
DEPLOY_USER: kjh2064
SERVICE_NAME: quantengine
DOTNET_VERSION: '10.0.x'
@@ -89,28 +89,108 @@ jobs:
- name: Setup SSH
run: |
echo "🔑 Setting up SSH configuration..."
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# SSH 키 설정
if [ -z "${{ secrets.SSH_PRIVATE_KEY }}" ]; then
echo "❌ SSH_PRIVATE_KEY secret not configured"
exit 1
fi
if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
else
echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 2>/dev/null || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
fi
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Prepare QuantEngine DB Env
chmod 600 ~/.ssh/id_ed25519
# SSH 키 검증
if ! ssh-keygen -l -f ~/.ssh/id_ed25519 >/dev/null 2>&1; then
echo "❌ SSH key validation failed"
exit 1
fi
# 호스트 키 스캔 (재시도)
for i in 1 2 3; do
if ssh-keyscan -t ed25519,rsa -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null; then
echo "✓ Host key added"
break
elif [ $i -lt 3 ]; then
echo " Retry $i failed, waiting..."
sleep 2
else
echo "⚠️ Host key scan failed (continuing anyway)"
fi
done
# SSH 연결 테스트
echo "Testing SSH connection..."
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
"${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}" "echo ✓ SSH OK"; then
echo "✓ SSH connection verified"
else
echo "❌ SSH connection test failed"
exit 1
fi
- name: Prepare & Validate QuantEngine DB Env
run: |
echo "🔧 Preparing database environment..."
# 환경 변수 검증
if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then
echo "❌ QUANTENGINE_DB_PASSWORD secret not configured"
exit 1
fi
if [ -z "${{ env.QUANTENGINE_DB_NAME }}" ] || [ -z "${{ env.QUANTENGINE_DB_USER }}" ]; then
echo "❌ DB configuration environment variables not set"
exit 1
fi
# 환경 파일 생성
mkdir -p ./deploy
cat > ./deploy/quantengine.env <<EOF
ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=${QUANTENGINE_DB_NAME};Username=${QUANTENGINE_DB_USER};Password=${{ secrets.QUANTENGINE_DB_PASSWORD }};Search Path=quantengine;
ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=${{ env.QUANTENGINE_DB_NAME }};Username=${{ env.QUANTENGINE_DB_USER }};Password=${{ secrets.QUANTENGINE_DB_PASSWORD }};Search Path=quantengine;
EOF
chmod 600 ./deploy/quantengine.env
# 파일 검증
if [ ! -f ./deploy/quantengine.env ]; then
echo "❌ Failed to create database config file"
exit 1
fi
echo "✓ Database configuration prepared"
- name: Package Artifact
run: |
tar -czf quantengine.tar.gz -C ./publish .
echo "✓ Package size: $(du -sh quantengine.tar.gz | cut -f1)"
echo "📦 Creating deployment package..."
# 패키지 생성
if ! tar -czf quantengine.tar.gz -C ./publish .; then
echo "❌ Failed to create package"
exit 1
fi
# 패키지 검증
PACKAGE_SIZE=$(du -sh quantengine.tar.gz | cut -f1)
PACKAGE_BYTES=$(stat -f%z quantengine.tar.gz 2>/dev/null || stat -c%s quantengine.tar.gz 2>/dev/null)
if [ -z "$PACKAGE_BYTES" ] || [ "$PACKAGE_BYTES" -lt 1000000 ]; then
echo "⚠️ Warning: Package seems too small ($PACKAGE_SIZE)"
fi
if [ ! -f quantengine.tar.gz ]; then
echo "❌ Package file not created"
exit 1
fi
echo "✓ Package created: $PACKAGE_SIZE"
tar -tzf quantengine.tar.gz | head -5
- name: Deploy & Verify on Server
run: |
@@ -135,43 +215,99 @@ jobs:
notify_failure() {
local exit_code=$?
local error_msg="$1"
send_telegram "❌ <b>QuantEngine 배포 실패</b>
커밋: <code>${COMMIT}</code>
시간: <code>${TIMESTAMP}</code>
단계: deploy-to-prod (SSH Execution)"
단계: ${error_msg:-deploy-to-prod}
로그: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}"
exit "$exit_code"
}
trap notify_failure ERR
trap 'notify_failure "SSH/File Transfer"' ERR
echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ==="
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp"
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
quantengine.tar.gz "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.tar.gz"
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
tools/deploy_quantengine.sh "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/deploy.sh"
scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
deploy/quantengine.env "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.env"
# 원격 디렉토리 생성
echo "📁 Creating remote directories..."
if ! ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp"; then
echo "❌ Failed to create remote directories"
notify_failure "Remote directory creation"
fi
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh"
# 배포 파일 전송 (재시도)
for file in quantengine.tar.gz:quantengine.tar.gz tools/deploy_quantengine.sh:deploy.sh deploy/quantengine.env:quantengine.env; do
IFS=':' read -r SRC DST <<< "$file"
echo "📤 Transferring $SRC..."
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/.config && install -m 600 /home/kjh2064/tmp/quantengine.env /home/kjh2064/.config/quantengine.env && rm -f /home/kjh2064/tmp/quantengine.env"
for attempt in 1 2 3; do
if scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
"$SRC" "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/$DST" 2>&1; then
echo "✓ Transferred $SRC"
break
elif [ $attempt -lt 3 ]; then
echo " Retry $attempt failed, waiting 5s..."
sleep 5
else
echo "❌ Failed to transfer $SRC after 3 attempts"
notify_failure "File transfer ($SRC)"
fi
done
done
# 배포 스크립트 실행 (재시도)
echo "🚀 Running deployment script..."
for attempt in 1 2; do
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && CI_DEPLOY=1 /home/kjh2064/tmp/deploy.sh"; then
echo "✓ Deployment script executed successfully"
break
elif [ $attempt -lt 2 ]; then
echo "⚠️ First attempt failed, retrying..."
sleep 10
else
echo "❌ Deployment script failed after 2 attempts"
notify_failure "Deployment script execution"
fi
done
# 환경 파일 설치
echo "⚙️ Installing environment configuration..."
if ! ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 \
"$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/.config && install -m 600 /home/kjh2064/tmp/quantengine.env /home/kjh2064/.config/quantengine.env && rm -f /home/kjh2064/tmp/quantengine.env"; then
echo "❌ Failed to install configuration"
notify_failure "Configuration installation"
fi
# 서비스 안정화 대기
echo "⏳ Waiting for service stabilization (15s)..."
sleep 15
echo "=== Verifying Loopback Health ==="
loopback_headers=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -s -D - -o /dev/null http://127.0.0.1:5000/")
echo "$loopback_headers"
if ! printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] 30[12] '; then
echo "Loopback health check failed for quantengine" >&2
exit 1
loopback_headers=""
for i in 1 2 3; do
echo " Health check attempt $i..."
loopback_headers=$(ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "curl -s -D - -o /dev/null -m 5 http://127.0.0.1:5000/" 2>&1)
if printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] (200|30[12]) '; then
echo "✓ Loopback health check passed"
break
elif [ $i -lt 3 ]; then
echo " Waiting 5s for service..."
sleep 5
fi
done
if ! printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] '; then
echo "❌ Loopback health check failed"
echo "Response: $loopback_headers"
notify_failure "Health check (loopback)"
fi
if ! printf '%s' "$loopback_headers" | grep -qiE '^Location: /login'; then
echo "Loopback redirect target is unexpected" >&2
exit 1
if ! printf '%s' "$loopback_headers" | grep -qiE '(^Location: /login|^HTTP/1\.[01] 200 )'; then
echo "⚠️ Unexpected redirect, but service is responding"
fi
echo "=== Verifying Favicon Assets ==="
+3
View File
@@ -17,6 +17,9 @@ publish-output/
*.user
*.suo
# Blazor WASM 클라이언트 정적 자산 (빌드 시 자동 복사, 커밋 불필요)
src/dotnet/QuantEngine.Web/wwwroot/_framework/
# 런타임 감사 로그 (append-only, 매 DAG 실행마다 증가)
runtime/lineage_events.jsonl
@@ -9,10 +9,10 @@
CloseButton = false,
MaxWidth = MaxWidth.Small,
FullWidth = true,
DisableBackdropClick = true
BackdropClick = false
};
var parameters = new DialogParameters<ConfirmDialogContent>
var parameters = new DialogParameters<ConfirmDialog>
{
{ x => x.Title, title },
{ x => x.Message, message },
@@ -20,10 +20,10 @@
{ x => x.CancelText, cancelText }
};
var dialog = await dialogService.ShowAsync<ConfirmDialogContent>(title, parameters, options);
var dialog = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, options);
var result = await dialog.Result;
return !result.Cancelled && (bool?)result.Data == true;
return !result.Canceled && (bool?)result.Data == true;
}
}
@@ -42,7 +42,7 @@
@code {
[CascadingParameter]
private MudDialogInstance MudDialog { get; set; }
private IMudDialogInstance MudDialog { get; set; }
[Parameter]
public string Title { get; set; } = "확인";
@@ -5,7 +5,7 @@
</MudNavLink>
<!-- Admin Section -->
<MudNavGroup Title="관리" Icon="@Icons.Material.Filled.Admin4">
<MudNavGroup Title="관리" Icon="@Icons.Material.Filled.AdminPanelSettings">
<MudNavLink Href="/users" Icon="@Icons.Material.Filled.People">사용자 관리</MudNavLink>
<MudNavLink Href="/monitoring" Icon="@Icons.Material.Filled.Timeline">데이터 수집</MudNavLink>
<MudNavLink Href="/settings" Icon="@Icons.Material.Filled.Settings">설정</MudNavLink>
@@ -140,7 +140,7 @@
마지막 수집: @ticker.LastCollectionTime.ToString("yyyy-MM-dd HH:mm:ss")
</MudText>
<MudText Typo="Typo.caption" Class="text-muted">
데이터 포인트: @ticker.DataPointCount개
데이터 포인트: @(ticker.DataPointCount)
</MudText>
</div>
}
@@ -53,7 +53,7 @@
<MudPaper Class="pa-4" Elevation="1">
<MudText Typo="Typo.h6" Class="mb-4">자산 구성</MudText>
<MudTable Items="@Assets" Dense="true" Hover="true" Striped="true">
<MudTable Items="@_assets" Dense="true" Hover="true" Striped="true">
<HeaderContent>
<MudTh>종목/펀드명</MudTh>
<MudTh>수량</MudTh>
@@ -168,7 +168,7 @@
</MudPaper>
@code {
private List<AssetModel> Assets = new();
private List<AssetModel> _assets = new();
private List<CategoryModel> AssetCategories = new();
private List<TradeModel> TradingHistory = new();
@@ -179,14 +179,14 @@
private async Task LoadAssets()
{
Assets = new List<AssetModel>
_assets = new List<AssetModel>
{
new AssetModel { Name = "삼성전자", Ticker = "005930", Quantity = 50, CurrentPrice = 70000, Value = 3500000, ReturnRate = 5.2, Ratio = 28.0 },
new AssetModel { Name = "LG화학", Ticker = "051910", Quantity = 30, CurrentPrice = 820000, Value = 24600000, ReturnRate = -2.1, Ratio = 19.6 },
new AssetModel { Name = "현대차", Ticker = "005380", Quantity = 40, CurrentPrice = 245000, Value = 9800000, ReturnRate = 8.5, Ratio = 7.8 },
new AssetModel { Name = "SK하이닉스", Ticker = "000660", Quantity = 25, CurrentPrice = 105000, Value = 2625000, ReturnRate = 12.3, Ratio = 2.1 },
new AssetModel { Name = "삼성중공업", Ticker = "010140", Quantity = 60, CurrentPrice = 85000, Value = 5100000, ReturnRate = 3.7, Ratio = 4.1 },
new AssetModel { Name = "포스코", Ticker = "005490", Quantity = 20, CurrentPrice = 75000, Value = 1500000, ReturnRate = -5.2, Ratio = 1.2 },
new AssetModel { Name = "삼성전자", Ticker = "005930", Quantity = 50, CurrentPrice = 70000, Value = 3500000, ReturnRate = 5.2M, Ratio = 28.0M },
new AssetModel { Name = "LG화학", Ticker = "051910", Quantity = 30, CurrentPrice = 820000, Value = 24600000, ReturnRate = -2.1M, Ratio = 19.6M },
new AssetModel { Name = "현대차", Ticker = "005380", Quantity = 40, CurrentPrice = 245000, Value = 9800000, ReturnRate = 8.5M, Ratio = 7.8M },
new AssetModel { Name = "SK하이닉스", Ticker = "000660", Quantity = 25, CurrentPrice = 105000, Value = 2625000, ReturnRate = 12.3M, Ratio = 2.1M },
new AssetModel { Name = "삼성중공업", Ticker = "010140", Quantity = 60, CurrentPrice = 85000, Value = 5100000, ReturnRate = 3.7M, Ratio = 4.1M },
new AssetModel { Name = "포스코", Ticker = "005490", Quantity = 20, CurrentPrice = 75000, Value = 1500000, ReturnRate = -5.2M, Ratio = 1.2M },
};
AssetCategories = new List<CategoryModel>
@@ -23,7 +23,7 @@
<!-- Users Table -->
<MudPaper Class="pa-4" Elevation="1">
@if (Users.Count == 0)
@if (_users.Count == 0)
{
<MudAlert Severity="Severity.Info">사용자가 없습니다.</MudAlert>
}
@@ -75,14 +75,14 @@
</MudPaper>
@code {
private List<UserModel> Users = new();
private List<UserModel> _users = new();
private string SearchQuery = "";
private IEnumerable<UserModel> FilteredUsers
{
get => string.IsNullOrEmpty(SearchQuery)
? Users
: Users.Where(u => u.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ||
? _users
: _users.Where(u => u.Name.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase) ||
u.Email.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase));
}
@@ -95,7 +95,7 @@
{
try
{
Users = new List<UserModel>
_users = new List<UserModel>
{
new UserModel
{
@@ -6,7 +6,7 @@ public static class AppTheme
{
public static MudTheme LightTheme => new()
{
Palette = new PaletteLight
PaletteLight = new PaletteLight
{
Primary = "#3f51b5",
Secondary = "#f50057",
@@ -30,97 +30,87 @@ public static class AppTheme
DividerLight = "#f5f5f5",
TableLines = "#e0e0e0",
LinesDefault = "#e0e0e0",
LinesInputBorder = "#bdbdbd",
TextDisabled = "rgba(0,0,0,0.38)",
BorderRadius = "4px",
OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)",
Elevation = new Dictionary<int, string>
{
{ 0, "none" },
{ 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" },
{ 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" },
{ 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" },
{ 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" },
}
LinesInputs = "#bdbdbd",
TextDisabled = "rgba(0,0,0,0.38)"
},
Typography = new Typography
{
Default = new DefaultTypography
{
FontFamily = "Roboto, sans-serif",
FontFamily = new[] { "Roboto", "sans-serif" },
FontSize = "1rem",
FontWeight = 400,
LineHeight = 1.5,
FontWeight = "400",
LineHeight = "1.5",
LetterSpacing = "0.5px"
},
H1 = new H1Typography
{
FontSize = "6rem",
FontWeight = 300,
LineHeight = 1.167,
FontWeight = "300",
LineHeight = "1.167",
LetterSpacing = "-0.015625em"
},
H2 = new H2Typography
{
FontSize = "3.75rem",
FontWeight = 300,
LineHeight = 1.2,
FontWeight = "300",
LineHeight = "1.2",
LetterSpacing = "-0.0083333333em"
},
H3 = new H3Typography
{
FontSize = "3rem",
FontWeight = 400,
LineHeight = 1.167,
FontWeight = "400",
LineHeight = "1.167",
LetterSpacing = "0em"
},
H4 = new H4Typography
{
FontSize = "2.125rem",
FontWeight = 500,
LineHeight = 1.235,
FontWeight = "500",
LineHeight = "1.235",
LetterSpacing = "0.0125em"
},
H5 = new H5Typography
{
FontSize = "1.5rem",
FontWeight = 500,
LineHeight = 1.334,
FontWeight = "500",
LineHeight = "1.334",
LetterSpacing = "0em"
},
H6 = new H6Typography
{
FontSize = "1.25rem",
FontWeight = 600,
LineHeight = 1.6,
FontWeight = "600",
LineHeight = "1.6",
LetterSpacing = "0.0125em"
},
Body1 = new Body1Typography
{
FontSize = "1rem",
FontWeight = 500,
LineHeight = 1.5,
FontWeight = "500",
LineHeight = "1.5",
LetterSpacing = "0.03125em"
},
Body2 = new Body2Typography
{
FontSize = "0.875rem",
FontWeight = 400,
LineHeight = 1.43,
FontWeight = "400",
LineHeight = "1.43",
LetterSpacing = "0.0178571429em"
},
Button = new ButtonTypography
{
FontSize = "0.875rem",
FontWeight = 600,
LineHeight = 1.75,
FontWeight = "600",
LineHeight = "1.75",
LetterSpacing = "0.0892857143em"
},
Caption = new CaptionTypography
{
FontSize = "0.75rem",
FontWeight = 400,
LineHeight = 1.66,
FontWeight = "400",
LineHeight = "1.66",
LetterSpacing = "0.0333333333em"
}
},
@@ -135,7 +125,7 @@ public static class AppTheme
public static MudTheme DarkTheme => new()
{
Palette = new PaletteDark
PaletteDark = new PaletteDark
{
Primary = "#bb86fc",
Secondary = "#03dac6",
@@ -159,18 +149,8 @@ public static class AppTheme
DividerLight = "#2c3e50",
TableLines = "#37474f",
LinesDefault = "#37474f",
LinesInputBorder = "#555555",
TextDisabled = "rgba(255,255,255,0.38)",
BorderRadius = "4px",
OverlayShadow = "0 5px 5px -3px rgba(0,0,0,0.2), 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12)",
Elevation = new Dictionary<int, string>
{
{ 0, "none" },
{ 1, "0 2px 1px -1px rgba(0,0,0,0.2),0 1px 1px 0 rgba(0,0,0,0.14),0 1px 3px 0 rgba(0,0,0,0.12)" },
{ 2, "0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12)" },
{ 3, "0 3px 3px -2px rgba(0,0,0,0.2),0 3px 4px 0 rgba(0,0,0,0.14),0 1px 8px 0 rgba(0,0,0,0.12)" },
{ 4, "0 2px 4px -1px rgba(0,0,0,0.2),0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12)" },
}
LinesInputs = "#555555",
TextDisabled = "rgba(255,255,255,0.38)"
},
Typography = LightTheme.Typography,
LayoutProperties = LightTheme.LayoutProperties
+11 -11
View File
@@ -50,17 +50,6 @@ builder.Services.AddAuthorizationCore();
builder.Services.AddMudServices();
// Hangfire Background Job Scheduling
try
{
var hangfireConnectionString = builder.Configuration.GetConnectionString("HangfireConnection") ?? connectionString;
builder.Services.AddHangfireServices(hangfireConnectionString);
}
catch (Exception ex)
{
Log.Warning("Hangfire initialization failed: {Message}", ex.Message);
}
// PostgreSQL Dapper Setup
var configuredConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var fallbackConnectionString = "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=CHANGE_ME;Search Path=quantengine;";
@@ -79,6 +68,17 @@ builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
builder.Services.AddScoped<IPostgresqlHistorySnapshotReader, PostgresqlHistorySnapshotReader>();
builder.Services.AddScoped<HistoryIngestionService>();
// Hangfire Background Job Scheduling
try
{
var hangfireConnectionString = builder.Configuration.GetConnectionString("HangfireConnection") ?? connectionString;
builder.Services.AddHangfireServices(hangfireConnectionString);
}
catch (Exception ex)
{
Log.Warning("Hangfire initialization failed: {Message}", ex.Message);
}
// Collection Pipeline Services (PostgreSQL-backed implementations)
builder.Services.AddScoped<ICollectionRepository, CollectionRepository>();
builder.Services.AddScoped<ITokenCache, PostgresTokenCache>();
@@ -1,4 +1,8 @@
using Hangfire;
using Hangfire.States;
using Hangfire.Dashboard;
using Hangfire.SqlServer;
using System.Linq.Expressions;
using QuantEngine.Application.Services;
using QuantEngine.Infrastructure.Data;
@@ -12,18 +16,15 @@ public class SchedulerService
private readonly ILogger<SchedulerService> _logger;
private readonly IBackgroundJobClient _jobClient;
private readonly IRecurringJobManager _recurringJobManager;
private readonly IKisApiPriceSource _kisApi;
public SchedulerService(
ILogger<SchedulerService> logger,
IBackgroundJobClient jobClient,
IRecurringJobManager recurringJobManager,
IKisApiPriceSource kisApi)
IRecurringJobManager recurringJobManager)
{
_logger = logger;
_jobClient = jobClient;
_recurringJobManager = recurringJobManager;
_kisApi = kisApi;
}
/// <summary>
@@ -195,7 +196,7 @@ public class SchedulerService
/// <summary>
/// Enqueue one-time job
/// </summary>
public string EnqueueJob(string jobName, Func<Task> job)
public string EnqueueJob(string jobName, Expression<Func<Task>> job)
{
var jobId = _jobClient.Enqueue(job);
_logger.LogInformation("Enqueued job {JobName} with ID {JobId}", jobName, jobId);
@@ -205,7 +206,7 @@ public class SchedulerService
/// <summary>
/// Get job status
/// </summary>
public JobState GetJobStatus(string jobId)
public string? GetJobStatus(string jobId)
{
return JobStorage.Current.GetConnection().GetJobData(jobId)?.State;
}
@@ -242,7 +243,6 @@ public static class HangfireServiceExtensions
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.FromSeconds(15),
UsePageLocks = true,
DisableGlobalLocks = true
}));

Some files were not shown because too many files have changed in this diff Show More