From 62b82788b8fa2cf79c1b6b45f1a938735aad555b Mon Sep 17 00:00:00 2001 From: MarkLo127 Date: Wed, 11 Mar 2026 20:26:37 +0800 Subject: [PATCH] --- backend/app/api/user.py | 38 ++++++++++++++++-------- frontend/app/history/page.tsx | 55 +++++++++++++++++++++-------------- frontend/lib/user-api.ts | 4 +-- 3 files changed, 61 insertions(+), 36 deletions(-) diff --git a/backend/app/api/user.py b/backend/app/api/user.py index 01fb1c5c..f14e9bf4 100644 --- a/backend/app/api/user.py +++ b/backend/app/api/user.py @@ -182,19 +182,33 @@ async def get_reports( result = await db.execute(query) reports = result.scalars().all() - - return [ - ReportResponse( - id=str(r.id), - ticker=r.ticker, - market_type=r.market_type, - analysis_date=r.analysis_date, - result=r.result, - language=r.language, - created_at=r.created_at.isoformat() + "Z" + + # Process reports to strip large payloads for the list view + optimized_reports = [] + for r in reports: + # Create a copy of the result to avoid modifying SQLAlchemy objects directly + if r.result and isinstance(r.result, dict): + # Shallow copy the dictionary + optimized_result = dict(r.result) + # Remove the massive reports field if it exists + 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") diff --git a/frontend/app/history/page.tsx b/frontend/app/history/page.tsx index e11465c9..2d125de3 100644 --- a/frontend/app/history/page.tsx +++ b/frontend/app/history/page.tsx @@ -401,13 +401,13 @@ export default function HistoryPage() { // Auto-sync tracking ref const hasAutoSyncedRef = useRef(false); - const cloudReportsPromiseRef = useRef | null>(null); + const cloudReportsPromiseRef = useRef | null>(null); const fetchCloudReportsCached = async (forceRefresh = false) => { if (forceRefresh || !cloudReportsPromiseRef.current) { cloudReportsPromiseRef.current = getCloudReports().catch(() => { cloudReportsPromiseRef.current = null; - return []; + return null; }); } return cloudReportsPromiseRef.current; @@ -483,6 +483,10 @@ export default function HistoryPage() { // Get cloud reports 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 localKeys = new Set(allLocal.map((r) => getReportSignature(r))); @@ -609,9 +613,10 @@ export default function HistoryPage() { if (isAuthenticated && isCloudSyncEnabled()) { const cloudReports = await fetchCloudReportsCached(); - // Convert cloud reports to SavedReport format and filter by market type - const cloudFiltered = cloudReports - .filter((r) => r.market_type === activeTab) + if (cloudReports) { + // Convert cloud reports to SavedReport format and filter by market type + const cloudFiltered = cloudReports + .filter((r) => r.market_type === activeTab) .map((r) => ({ id: parseInt(r.id.replace(/-/g, "").slice(0, 8), 16), // Convert UUID to number cloudId: r.id, // Keep cloud ID for deletion @@ -649,6 +654,7 @@ export default function HistoryPage() { setIsCloudData(true); return; } + } // Added missing closing brace for if (cloudReports) } // Filter local data by language before display @@ -684,7 +690,7 @@ export default function HistoryPage() { if (isAuthenticated && isCloudSyncEnabled()) { const cloudReports = await fetchCloudReportsCached(); - if (cloudReports.length > 0) { + if (cloudReports && cloudReports.length > 0) { // Get local reports to check for duplicates const [usLocal, twseLocal, tpexLocal] = await Promise.all([ 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 try { const allCloudReports = await fetchCloudReportsCached(true); - const matchingCloudIds = allCloudReports - .filter((r) => { - const lang = r.language || "zh-TW"; - return ( - r.ticker === reportToDelete.ticker && - r.analysis_date === reportToDelete.analysis_date && - r.market_type === reportToDelete.market_type && - lang === (targetLang || "zh-TW") - ); - }) - .map((r) => r.id); + if (allCloudReports) { + const matchingCloudIds = allCloudReports + .filter((r) => { + const lang = r.language || "zh-TW"; + return ( + r.ticker === reportToDelete.ticker && + r.analysis_date === reportToDelete.analysis_date && + r.market_type === reportToDelete.market_type && + lang === (targetLang || "zh-TW") + ); + }) + .map((r) => r.id); - if (matchingCloudIds.length > 0) { - console.log(`🗑️ Deleting ${matchingCloudIds.length} cloud report(s):`, matchingCloudIds); - await Promise.all(matchingCloudIds.map((id) => deleteCloudReport(id))); + if (matchingCloudIds.length > 0) { + console.log(`🗑️ Deleting ${matchingCloudIds.length} cloud report(s):`, matchingCloudIds); + 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) { - // Fallback: delete by cloudId if no match found by key - console.log("🗑️ Deleting from cloud by ID:", cloudId); + console.log("🗑️ Deleting from cloud by original ID because fetch failed:", cloudId); await deleteCloudReport(cloudId); } } catch (cloudErr) { diff --git a/frontend/lib/user-api.ts b/frontend/lib/user-api.ts index 2ec25d97..0a16292f 100644 --- a/frontend/lib/user-api.ts +++ b/frontend/lib/user-api.ts @@ -85,7 +85,7 @@ interface GetCloudReportsOptions { /** * Fetch reports from cloud with optional filtering and pagination */ -export async function getCloudReports(options?: GetCloudReportsOptions): Promise { +export async function getCloudReports(options?: GetCloudReportsOptions): Promise { if (!isCloudSyncEnabled()) return []; try { @@ -111,7 +111,7 @@ export async function getCloudReports(options?: GetCloudReportsOptions): Promise return await response.json(); } catch (error) { console.error("Failed to fetch cloud reports:", error); - return []; + return null; // Return null instead of [] to indicate fetch failure } }