feat(collection): PostgreSQL 백킹 활성화 (CollectionRepository + TokenCache)
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 7s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 13s
Snapshot Admin Deployment / build-and-deploy (push) Failing after 1m21s
Deploy to Production / Build & Deploy to Production (push) Successful in 1m55s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 7s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 13s
Snapshot Admin Deployment / build-and-deploy (push) Failing after 1m21s
Deploy to Production / Build & Deploy to Production (push) Successful in 1m55s
- Program.cs: PlaceholderCollectionRepository/TokenCache/KisApiClient → 실제 구현체로 변경 - 데이터베이스 초기화: EnsureTablesAsync() 호출 (시작 시 테이블 자동 생성) - kis_tokens, kis_collection_runs, kis_collection_snapshots, kis_collection_errors 테이블 - Dapper 기반 SQL 쿼리 (파라미터화, SQL 주입 방지) - 인덱스: started_at, ticker, captured_at, run_id - PlaceholderImplementations.cs 제거 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,164 +1,2 @@
|
||||
using QuantEngine.Core.Interfaces;
|
||||
|
||||
namespace QuantEngine.Web.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder implementations for Collection services.
|
||||
/// Temporary: to be replaced with actual PostgreSQL implementations.
|
||||
/// </summary>
|
||||
|
||||
public class PlaceholderCollectionRepository : ICollectionRepository
|
||||
{
|
||||
private static readonly List<CollectionRunRecord> MockRuns = new();
|
||||
private static readonly List<CollectionSnapshotRecord> MockSnapshots = new();
|
||||
private static readonly List<CollectionErrorRecord> MockErrors = new();
|
||||
|
||||
public Task SaveRunAsync(CollectionRunRecord run)
|
||||
{
|
||||
MockRuns.Add(run);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UpdateRunStatusAsync(string runId, string status, string? finishedAt = null, int? totalSnapshots = null, int? totalErrors = null)
|
||||
{
|
||||
var run = MockRuns.FirstOrDefault(r => r.RunId == runId);
|
||||
if (run != null)
|
||||
{
|
||||
var idx = MockRuns.IndexOf(run);
|
||||
MockRuns[idx] = new CollectionRunRecord(runId, status, run.StartedAt, finishedAt, totalSnapshots, totalErrors, DateTime.UtcNow.ToString("o"));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SaveSnapshotAsync(CollectionSnapshotRecord snapshot)
|
||||
{
|
||||
MockSnapshots.Add(snapshot);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SaveErrorAsync(CollectionErrorRecord error)
|
||||
{
|
||||
MockErrors.Add(error);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<List<CollectionRunRecord>> GetRecentRunsAsync(int limit = 20)
|
||||
{
|
||||
return Task.FromResult(MockRuns.OrderByDescending(r => r.StartedAt).Take(limit).ToList());
|
||||
}
|
||||
|
||||
public Task<List<CollectionSnapshotRecord>> GetRunSnapshotsAsync(string runId)
|
||||
{
|
||||
return Task.FromResult(MockSnapshots.Where(s => s.RunId == runId).ToList());
|
||||
}
|
||||
|
||||
public Task<List<CollectionErrorRecord>> GetRunErrorsAsync(string runId, int limit = 50)
|
||||
{
|
||||
return Task.FromResult(MockErrors.Where(e => e.RunId == runId).Take(limit).ToList());
|
||||
}
|
||||
|
||||
public Task<CollectionDashboardStateRecord> GetDashboardStateAsync()
|
||||
{
|
||||
var lastRun = MockRuns.OrderByDescending(r => r.StartedAt).FirstOrDefault();
|
||||
var recentErrors = MockErrors.OrderByDescending(e => e.CreatedAt).Take(5).ToList();
|
||||
|
||||
return Task.FromResult(new CollectionDashboardStateRecord(
|
||||
LastRunId: lastRun?.RunId,
|
||||
LastRunStatus: lastRun?.Status,
|
||||
LastFinishedAt: lastRun?.FinishedAt,
|
||||
TotalSnapshots: MockSnapshots.Count,
|
||||
TotalErrors: MockErrors.Count,
|
||||
RecentErrors: recentErrors
|
||||
));
|
||||
}
|
||||
|
||||
public Task<List<CollectionSnapshotRecord>> GetLatestSnapshotsForTickerAsync(string ticker, int limit = 10)
|
||||
{
|
||||
return Task.FromResult(MockSnapshots.Where(s => s.Ticker == ticker).OrderByDescending(s => s.CapturedAt).Take(limit).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaceholderTokenCache : ITokenCache
|
||||
{
|
||||
private static readonly Dictionary<string, (string Token, DateTime ExpiresAt)> Cache = new();
|
||||
|
||||
public Task<string?> GetCachedTokenAsync(string account)
|
||||
{
|
||||
if (Cache.TryGetValue(account, out var entry) && entry.ExpiresAt > DateTime.UtcNow.AddMinutes(10))
|
||||
{
|
||||
return Task.FromResult<string?>(entry.Token);
|
||||
}
|
||||
return Task.FromResult<string?>(null);
|
||||
}
|
||||
|
||||
public Task SaveTokenAsync(string account, string token, DateTime expiresAt)
|
||||
{
|
||||
Cache[account] = (token, expiresAt);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ClearExpiredTokensAsync()
|
||||
{
|
||||
var expired = Cache.Where(kv => kv.Value.ExpiresAt <= DateTime.UtcNow).Select(kv => kv.Key).ToList();
|
||||
foreach (var key in expired) Cache.Remove(key);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaceholderKisApiClient : IKisApiClient
|
||||
{
|
||||
public Task<Dictionary<string, object>> GetCurrentPriceAsync(string code, string account = "mock")
|
||||
{
|
||||
return Task.FromResult(new Dictionary<string, object>
|
||||
{
|
||||
{ "code", code },
|
||||
{ "price", 0 },
|
||||
{ "change", 0 },
|
||||
{ "changeRate", 0 }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetAskingPrice10LevelAsync(string code, string account = "mock")
|
||||
{
|
||||
return Task.FromResult(new Dictionary<string, object>
|
||||
{
|
||||
{ "code", code },
|
||||
{ "askLevels", new List<object>() },
|
||||
{ "bidLevels", new List<object>() }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetDailyShortSaleAsync(string code, string startDate, string endDate, string account = "mock")
|
||||
{
|
||||
return Task.FromResult(new Dictionary<string, object>
|
||||
{
|
||||
{ "code", code },
|
||||
{ "startDate", startDate },
|
||||
{ "endDate", endDate },
|
||||
{ "data", new List<object>() }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetDailyItemChartPriceAsync(string code, string startDate, string endDate, string period = "D", string account = "mock")
|
||||
{
|
||||
return Task.FromResult(new Dictionary<string, object>
|
||||
{
|
||||
{ "code", code },
|
||||
{ "startDate", startDate },
|
||||
{ "endDate", endDate },
|
||||
{ "period", period },
|
||||
{ "candles", new List<object>() }
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Dictionary<string, object>> GetInvestorTrendAsync(string code, string account = "mock")
|
||||
{
|
||||
return Task.FromResult(new Dictionary<string, object>
|
||||
{
|
||||
{ "code", code },
|
||||
{ "individual", 0 },
|
||||
{ "foreign", 0 },
|
||||
{ "institution", 0 }
|
||||
});
|
||||
}
|
||||
}
|
||||
// Placeholder implementations removed — using PostgreSQL-backed real implementations instead.
|
||||
// See Program.cs for service registration.
|
||||
|
||||
Reference in New Issue
Block a user