This commit is contained in:
MarkLo127 2026-03-11 17:46:07 +08:00
parent cccf02b8dc
commit a45bd56ad0
4 changed files with 25 additions and 37 deletions

View File

@ -38,6 +38,7 @@ class ReportCreate(BaseModel):
market_type: str # us, twse, tpex market_type: str # us, twse, tpex
analysis_date: str analysis_date: str
result: dict result: dict
language: Optional[str] = None
class ReportResponse(BaseModel): class ReportResponse(BaseModel):
@ -47,6 +48,7 @@ class ReportResponse(BaseModel):
market_type: str market_type: str
analysis_date: str analysis_date: str
result: dict result: dict
language: Optional[str] = None
created_at: str created_at: str
@ -166,6 +168,7 @@ async def get_reports(
market_type=r.market_type, market_type=r.market_type,
analysis_date=r.analysis_date, analysis_date=r.analysis_date,
result=r.result, result=r.result,
language=r.language,
created_at=r.created_at.isoformat() + "Z" created_at=r.created_at.isoformat() + "Z"
) )
for r in reports for r in reports
@ -184,7 +187,8 @@ async def create_report(
ticker=report_data.ticker, ticker=report_data.ticker,
market_type=report_data.market_type, market_type=report_data.market_type,
analysis_date=report_data.analysis_date, analysis_date=report_data.analysis_date,
result=report_data.result result=report_data.result,
language=report_data.language
) )
db.add(report) db.add(report)
await db.commit() await db.commit()
@ -223,6 +227,7 @@ async def get_report(
market_type=report.market_type, market_type=report.market_type,
analysis_date=report.analysis_date, analysis_date=report.analysis_date,
result=report.result, result=report.result,
language=report.language,
created_at=report.created_at.isoformat() + "Z" created_at=report.created_at.isoformat() + "Z"
) )

View File

@ -63,6 +63,16 @@ async def init_db():
# Create all tables # Create all tables
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
# Manual migrations for existing databases
try:
# Add language column if it doesn't exist
await conn.execute(text("ALTER TABLE reports ADD COLUMN IF NOT EXISTS language VARCHAR(10);"))
# Add indexes to optimize queries
await conn.execute(text("CREATE INDEX IF NOT EXISTS ix_reports_user_id ON reports (user_id);"))
await conn.execute(text("CREATE INDEX IF NOT EXISTS ix_reports_created_at ON reports (created_at);"))
except Exception as e:
print(f"Skipping manual migration (might be SQLite or syntax not supported): {e}")
print("Database tables initialized successfully") print("Database tables initialized successfully")

View File

@ -85,17 +85,20 @@ class Report(Base):
user_id: Mapped[uuid.UUID] = mapped_column( user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"), ForeignKey("users.id", ondelete="CASCADE"),
nullable=False nullable=False,
index=True
) )
ticker: Mapped[str] = mapped_column(String(20), nullable=False) ticker: Mapped[str] = mapped_column(String(20), nullable=False)
market_type: Mapped[str] = mapped_column(String(10), nullable=False) # us, twse, tpex market_type: Mapped[str] = mapped_column(String(10), nullable=False) # us, twse, tpex
analysis_date: Mapped[str] = mapped_column(String(10), nullable=False) # YYYY-MM-DD analysis_date: Mapped[str] = mapped_column(String(10), nullable=False) # YYYY-MM-DD
# Store full result as JSONB # Store full result as JSONB
result: Mapped[dict] = mapped_column(JSON, nullable=False) result: Mapped[dict] = mapped_column(JSON, nullable=False)
language: Mapped[Optional[str]] = mapped_column(String(10), nullable=True)
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime, DateTime,
default=datetime.utcnow, default=datetime.utcnow,
nullable=False nullable=False,
index=True
) )
# Relationship # Relationship

View File

@ -574,43 +574,18 @@ export default function HistoryPage() {
new Date(b.saved_at).getTime() - new Date(a.saved_at).getTime(), new Date(b.saved_at).getTime() - new Date(a.saved_at).getTime(),
); );
// Filter by current language setReports(merged);
const languageFiltered = merged.filter((report) => {
// Use stored language if available
if (report.language) {
return report.language === locale;
}
// Fallback: detect from content for old reports without language field
return detectReportLanguage(report.result?.reports) === locale;
});
setReports(languageFiltered);
setIsCloudData(true); setIsCloudData(true);
return; return;
} }
} }
// If no cloud data or not authenticated, use local only setReports(localData);
// Filter by current language
const languageFiltered = localData.filter((report) => {
if (report.language) {
return report.language === locale;
}
return detectReportLanguage(report.result?.reports) === locale;
});
setReports(languageFiltered);
setIsCloudData(false); setIsCloudData(false);
} catch (error) { } catch (error) {
console.error("Failed to load reports:", error); console.error("Failed to load reports:", error);
// Fall back to local on error
const data = await getReportsByMarketType(activeTab); const data = await getReportsByMarketType(activeTab);
const languageFiltered = data.filter((report) => { setReports(data as SavedReport[]);
if (report.language) {
return report.language === locale;
}
return detectReportLanguage(report.result?.reports) === locale;
});
setReports(languageFiltered);
setIsCloudData(false); setIsCloudData(false);
} finally { } finally {
setLoading(false); setLoading(false);
@ -621,12 +596,7 @@ export default function HistoryPage() {
try { try {
// Helper to filter reports by language // Helper to filter reports by language
const filterByLanguage = (reports: SavedReport[]) => { const filterByLanguage = (reports: SavedReport[]) => {
return reports.filter(report => { return reports;
if (report.language) {
return report.language === locale;
}
return detectReportLanguage(report.result?.reports) === locale;
});
}; };
if (isAuthenticated && isCloudSyncEnabled()) { if (isAuthenticated && isCloudSyncEnabled()) {