This commit is contained in:
MarkLo127 2026-03-11 20:26:37 +08:00
parent 199315327b
commit 62b82788b8
3 changed files with 61 additions and 36 deletions

View File

@ -183,18 +183,32 @@ async def get_reports(
result = await db.execute(query) result = await db.execute(query)
reports = result.scalars().all() reports = result.scalars().all()
return [ # Process reports to strip large payloads for the list view
ReportResponse( optimized_reports = []
id=str(r.id), for r in reports:
ticker=r.ticker, # Create a copy of the result to avoid modifying SQLAlchemy objects directly
market_type=r.market_type, if r.result and isinstance(r.result, dict):
analysis_date=r.analysis_date, # Shallow copy the dictionary
result=r.result, optimized_result = dict(r.result)
language=r.language, # Remove the massive reports field if it exists
created_at=r.created_at.isoformat() + "Z" if "reports" in optimized_result:
optimized_result["reports"] = None
else:
optimized_result = r.result
optimized_reports.append(
ReportResponse(
id=str(r.id),
ticker=r.ticker,
market_type=r.market_type,
analysis_date=r.analysis_date,
result=optimized_result,
language=r.language,
created_at=r.created_at.isoformat() + "Z"
)
) )
for r in reports
] return optimized_reports
@router.post("/reports") @router.post("/reports")

View File

@ -401,13 +401,13 @@ export default function HistoryPage() {
// Auto-sync tracking ref // Auto-sync tracking ref
const hasAutoSyncedRef = useRef(false); const hasAutoSyncedRef = useRef(false);
const cloudReportsPromiseRef = useRef<Promise<any[]> | null>(null); const cloudReportsPromiseRef = useRef<Promise<any[] | null> | null>(null);
const fetchCloudReportsCached = async (forceRefresh = false) => { const fetchCloudReportsCached = async (forceRefresh = false) => {
if (forceRefresh || !cloudReportsPromiseRef.current) { if (forceRefresh || !cloudReportsPromiseRef.current) {
cloudReportsPromiseRef.current = getCloudReports().catch(() => { cloudReportsPromiseRef.current = getCloudReports().catch(() => {
cloudReportsPromiseRef.current = null; cloudReportsPromiseRef.current = null;
return []; return null;
}); });
} }
return cloudReportsPromiseRef.current; return cloudReportsPromiseRef.current;
@ -483,6 +483,10 @@ export default function HistoryPage() {
// Get cloud reports // Get cloud reports
const cloudReports = await fetchCloudReportsCached(true); // Force refresh const cloudReports = await fetchCloudReportsCached(true); // Force refresh
if (!cloudReports) {
console.warn("☁️ Sync: Failed to fetch cloud reports. Aborting sync to prevent data loss.");
return; // Abort sync if fetching fails
}
const cloudKeys = new Set(cloudReports.map((r) => getReportSignature(r))); const cloudKeys = new Set(cloudReports.map((r) => getReportSignature(r)));
const localKeys = new Set(allLocal.map((r) => getReportSignature(r))); const localKeys = new Set(allLocal.map((r) => getReportSignature(r)));
@ -609,9 +613,10 @@ export default function HistoryPage() {
if (isAuthenticated && isCloudSyncEnabled()) { if (isAuthenticated && isCloudSyncEnabled()) {
const cloudReports = await fetchCloudReportsCached(); const cloudReports = await fetchCloudReportsCached();
// Convert cloud reports to SavedReport format and filter by market type if (cloudReports) {
const cloudFiltered = cloudReports // Convert cloud reports to SavedReport format and filter by market type
.filter((r) => r.market_type === activeTab) const cloudFiltered = cloudReports
.filter((r) => r.market_type === activeTab)
.map((r) => ({ .map((r) => ({
id: parseInt(r.id.replace(/-/g, "").slice(0, 8), 16), // Convert UUID to number id: parseInt(r.id.replace(/-/g, "").slice(0, 8), 16), // Convert UUID to number
cloudId: r.id, // Keep cloud ID for deletion cloudId: r.id, // Keep cloud ID for deletion
@ -649,6 +654,7 @@ export default function HistoryPage() {
setIsCloudData(true); setIsCloudData(true);
return; return;
} }
} // Added missing closing brace for if (cloudReports)
} }
// Filter local data by language before display // Filter local data by language before display
@ -684,7 +690,7 @@ export default function HistoryPage() {
if (isAuthenticated && isCloudSyncEnabled()) { if (isAuthenticated && isCloudSyncEnabled()) {
const cloudReports = await fetchCloudReportsCached(); const cloudReports = await fetchCloudReportsCached();
if (cloudReports.length > 0) { if (cloudReports && cloudReports.length > 0) {
// Get local reports to check for duplicates // Get local reports to check for duplicates
const [usLocal, twseLocal, tpexLocal] = await Promise.all([ const [usLocal, twseLocal, tpexLocal] = await Promise.all([
getReportsByMarketType("us"), getReportsByMarketType("us"),
@ -794,24 +800,29 @@ export default function HistoryPage() {
// 1. Delete from cloud: delete the specific report AND any other duplicates with the same key // 1. Delete from cloud: delete the specific report AND any other duplicates with the same key
try { try {
const allCloudReports = await fetchCloudReportsCached(true); const allCloudReports = await fetchCloudReportsCached(true);
const matchingCloudIds = allCloudReports if (allCloudReports) {
.filter((r) => { const matchingCloudIds = allCloudReports
const lang = r.language || "zh-TW"; .filter((r) => {
return ( const lang = r.language || "zh-TW";
r.ticker === reportToDelete.ticker && return (
r.analysis_date === reportToDelete.analysis_date && r.ticker === reportToDelete.ticker &&
r.market_type === reportToDelete.market_type && r.analysis_date === reportToDelete.analysis_date &&
lang === (targetLang || "zh-TW") r.market_type === reportToDelete.market_type &&
); lang === (targetLang || "zh-TW")
}) );
.map((r) => r.id); })
.map((r) => r.id);
if (matchingCloudIds.length > 0) { if (matchingCloudIds.length > 0) {
console.log(`🗑️ Deleting ${matchingCloudIds.length} cloud report(s):`, matchingCloudIds); console.log(`🗑️ Deleting ${matchingCloudIds.length} cloud report(s):`, matchingCloudIds);
await Promise.all(matchingCloudIds.map((id) => deleteCloudReport(id))); await Promise.all(matchingCloudIds.map((id) => deleteCloudReport(id)));
} else if (cloudId) {
// Fallback: delete by cloudId if no match found by key
console.log("🗑️ Deleting from cloud by ID:", cloudId);
await deleteCloudReport(cloudId);
}
} else if (cloudId) { } else if (cloudId) {
// Fallback: delete by cloudId if no match found by key console.log("🗑️ Deleting from cloud by original ID because fetch failed:", cloudId);
console.log("🗑️ Deleting from cloud by ID:", cloudId);
await deleteCloudReport(cloudId); await deleteCloudReport(cloudId);
} }
} catch (cloudErr) { } catch (cloudErr) {

View File

@ -85,7 +85,7 @@ interface GetCloudReportsOptions {
/** /**
* Fetch reports from cloud with optional filtering and pagination * Fetch reports from cloud with optional filtering and pagination
*/ */
export async function getCloudReports(options?: GetCloudReportsOptions): Promise<CloudReport[]> { export async function getCloudReports(options?: GetCloudReportsOptions): Promise<CloudReport[] | null> {
if (!isCloudSyncEnabled()) return []; if (!isCloudSyncEnabled()) return [];
try { try {
@ -111,7 +111,7 @@ export async function getCloudReports(options?: GetCloudReportsOptions): Promise
return await response.json(); return await response.json();
} catch (error) { } catch (error) {
console.error("Failed to fetch cloud reports:", error); console.error("Failed to fetch cloud reports:", error);
return []; return null; // Return null instead of [] to indicate fetch failure
} }
} }