This commit is contained in:
parent
cccf02b8dc
commit
a45bd56ad0
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,16 @@ async def init_db():
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
# 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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue