Fundamental_Analysis/services/api-gateway/src/persistence.rs
Lv, Qi 0c975bb8f1 Refactor: Remove legacy analysis results and implement workflow history
- **Common Contracts**: Updated DTOs and models to support workflow history; removed legacy analysis result DTOs.
- **Data Persistence Service**:
    - Removed `analysis_results` table logic and API endpoints.
    - Implemented `workflow_history` API and DB access (`history.rs`).
    - Fixed compilation errors and updated tests.
    - Exposed Postgres port in `docker-compose.yml` for easier debugging/offline checks.
- **API Gateway**:
    - Implemented `history` endpoints (get history list, get by ID).
    - Removed legacy `analysis-results` endpoints.
    - Fixed routing and handler logic in `api.rs`.
- **Report Generator Service**:
    - Removed dependency on legacy `analysis-results` persistence calls.
    - Fixed compilation errors.
- **Workflow Orchestrator**: Fixed warnings and minor logic issues.
- **Providers**: Updated provider services (alphavantage, tushare, finnhub, yfinance, mock) to align with contract changes.
- **Frontend**:
    - Updated `ReportPage` and stores to use new workflow history.
    - Added `RecentReportsDropdown` component.
    - Cleaned up `RealtimeLogs` component.
- **Documentation**: Moved completed design tasks to `completed/` and added refactoring context docs.

Confirmed all services pass `cargo check`.
2025-11-29 14:46:44 +08:00

204 lines
5.8 KiB
Rust

//!
//! 数据持久化服务客户端
//!
use crate::error::Result;
use common_contracts::config_models::{
AnalysisTemplateSets, DataSourcesConfig, LlmProvidersConfig,
};
use common_contracts::dtos::{CompanyProfileDto, TimeSeriesFinancialDto, WorkflowHistoryDto, WorkflowHistorySummaryDto};
use uuid::Uuid;
#[derive(Clone)]
pub struct PersistenceClient {
client: reqwest::Client,
base_url: String,
}
impl PersistenceClient {
pub fn new(base_url: String) -> Self {
Self {
client: reqwest::Client::new(),
base_url,
}
}
pub async fn get_company_profile(&self, symbol: &str) -> Result<CompanyProfileDto> {
let url = format!("{}/companies/{}", self.base_url, symbol);
let profile = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<CompanyProfileDto>()
.await?;
Ok(profile)
}
pub async fn get_financials(&self, symbol: &str) -> Result<Vec<TimeSeriesFinancialDto>> {
let url = format!(
"{}/market-data/financial-statements/{}",
self.base_url, symbol
);
let financials = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<Vec<TimeSeriesFinancialDto>>()
.await?;
Ok(financials)
}
#[allow(dead_code)]
pub async fn get_session_data(
&self,
request_id: Uuid,
provider: Option<&str>,
data_type: Option<&str>,
) -> Result<Vec<common_contracts::dtos::SessionDataDto>> {
let url = format!("{}/session-data/{}", self.base_url, request_id);
let mut req = self.client.get(&url);
if let Some(p) = provider {
req = req.query(&[("provider", p)]);
}
if let Some(d) = data_type {
req = req.query(&[("data_type", d)]);
}
let data = req
.send()
.await?
.error_for_status()?
.json::<Vec<common_contracts::dtos::SessionDataDto>>()
.await?;
Ok(data)
}
pub async fn get_workflow_histories(&self, symbol: Option<&str>, limit: Option<i64>) -> Result<Vec<WorkflowHistorySummaryDto>> {
let url = format!("{}/history", self.base_url);
let mut req = self.client.get(&url);
if let Some(s) = symbol {
req = req.query(&[("symbol", s)]);
}
if let Some(l) = limit {
req = req.query(&[("limit", l)]);
}
let resp = req.send().await?.error_for_status()?;
let results = resp.json().await?;
Ok(results)
}
pub async fn get_workflow_history_by_id(&self, request_id: Uuid) -> Result<WorkflowHistoryDto> {
let url = format!("{}/history/{}", self.base_url, request_id);
let resp = self.client.get(&url).send().await?.error_for_status()?;
let result = resp.json().await?;
Ok(result)
}
pub async fn clear_history(&self) -> Result<()> {
let url = format!("{}/system/history", self.base_url);
self.client
.delete(&url)
.send()
.await?
.error_for_status()?;
Ok(())
}
// --- Config Methods ---
pub async fn get_llm_providers_config(&self) -> Result<LlmProvidersConfig> {
let url = format!("{}/configs/llm_providers", self.base_url);
let config = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<LlmProvidersConfig>()
.await?;
Ok(config)
}
pub async fn update_llm_providers_config(
&self,
payload: &LlmProvidersConfig,
) -> Result<LlmProvidersConfig> {
let url = format!("{}/configs/llm_providers", self.base_url);
let updated_config = self
.client
.put(&url)
.json(payload)
.send()
.await?
.error_for_status()?
.json::<LlmProvidersConfig>()
.await?;
Ok(updated_config)
}
pub async fn get_analysis_template_sets(&self) -> Result<AnalysisTemplateSets> {
let url = format!("{}/configs/analysis_template_sets", self.base_url);
let config = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<AnalysisTemplateSets>()
.await?;
Ok(config)
}
pub async fn update_analysis_template_sets(
&self,
payload: &AnalysisTemplateSets,
) -> Result<AnalysisTemplateSets> {
let url = format!("{}/configs/analysis_template_sets", self.base_url);
let updated_config = self
.client
.put(&url)
.json(payload)
.send()
.await?
.error_for_status()?
.json::<AnalysisTemplateSets>()
.await?;
Ok(updated_config)
}
pub async fn get_data_sources_config(&self) -> Result<DataSourcesConfig> {
let url = format!("{}/configs/data_sources", self.base_url);
let config = self
.client
.get(&url)
.send()
.await?
.error_for_status()?
.json::<DataSourcesConfig>()
.await?;
Ok(config)
}
pub async fn update_data_sources_config(
&self,
payload: &DataSourcesConfig,
) -> Result<DataSourcesConfig> {
let url = format!("{}/configs/data_sources", self.base_url);
let updated_config = self
.client
.put(&url)
.json(payload)
.send()
.await?
.error_for_status()?
.json::<DataSourcesConfig>()
.await?;
Ok(updated_config)
}
}