diff --git a/src/server/features/dashboard/services/DashboardService.ts b/src/server/features/dashboard/services/DashboardService.ts index 2acff01..05ede7d 100644 --- a/src/server/features/dashboard/services/DashboardService.ts +++ b/src/server/features/dashboard/services/DashboardService.ts @@ -12,6 +12,8 @@ import { createDataforseoClient, normalizeBacklinksTarget, } from "@/server/lib/dataforseo"; +import { asAppError } from "@/server/lib/errors"; +import { shouldCaptureAppErrorCode } from "@/shared/error-codes"; // Daily cadence: fresh numbers each visit without per-visit spend; a dormant // project costs nothing because refreshes are visit-triggered. @@ -269,11 +271,24 @@ async function ensureBacklinkSnapshot(input: { capturedAt: new Date().toISOString(), }); } catch (error) { - if (latestMatchesDomain) { - console.error("dashboard: backlink snapshot refresh failed", error); - return getBacklinkSummary(projectId, domain); + if (!latestMatchesDomain) throw error; + // Visit-triggered refresh, so an out-of-credits org re-hits this on every + // dashboard load. Expected refusals are not failures: log them at info with + // the code, and keep error for anything the taxonomy says is reportable. + const code = asAppError(error)?.code; + if (shouldCaptureAppErrorCode(code)) { + console.error( + "dashboard: backlink snapshot refresh failed", + { projectId }, + error, + ); + } else { + console.info("dashboard: backlink snapshot refresh skipped", { + projectId, + code, + }); } - throw error; + return getBacklinkSummary(projectId, domain); } return getBacklinkSummary(projectId, domain);