@canonry/canonry 7.66.0 → 7.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/references/seo-analysis.md +2 -2
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -2
- package/assets/assets/{AgentSetupPage-DXFxOJ1W.js → AgentSetupPage-ORsy3QEa.js} +3 -3
- package/assets/assets/{AuditHistoryPanel-CS8UfTHc.js → AuditHistoryPanel-vTHZDbfs.js} +1 -1
- package/assets/assets/{BacklinksPage-D5IA2tNY.js → BacklinksPage-FqADB5sO.js} +1 -1
- package/assets/assets/{HistoryPage-DsQAUkgR.js → HistoryPage-C4l5Toqa.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-x8f7xanT.js +4 -0
- package/assets/assets/ProjectPage-CG9I2-B4.js +9 -0
- package/assets/assets/{RunsPage-BgjN1N3t.js → RunsPage-oyhhwAPq.js} +1 -1
- package/assets/assets/{SettingsPage-BQuqnEvm.js → SettingsPage-DJc4Di1M.js} +1 -1
- package/assets/assets/SiteHealthSection-BvqFg3rG.js +2 -0
- package/assets/assets/{ToneBadge-DNva5_AM.js → ToneBadge-CTWpeKu8.js} +1 -1
- package/assets/assets/{TrafficPage-BBqpvwl5.js → TrafficPage-DAD0FFkL.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-BBEzbawh.js +1 -0
- package/assets/assets/{account-context-wRthfm6K.js → account-context-DMHOefRt.js} +1 -1
- package/assets/assets/{card-Bq64-UB5.js → card-Brp6fFNG.js} +1 -1
- package/assets/assets/{extract-error-message-BRDc8As2.js → extract-error-message-5QpQCsGb.js} +1 -1
- package/assets/assets/{format-helpers-D5wFtgg2.js → format-helpers-BFM0FRjT.js} +1 -1
- package/assets/assets/index-BBNs-RhI.js +60 -0
- package/assets/assets/{query-client-IopmAOx0.js → query-client-BMTW-xss.js} +1 -1
- package/assets/assets/react-query-DGmY6hJa.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-d72rX0Uo.js → react-sigma_core.esm.min-C0VjhgDo.js} +1 -1
- package/assets/assets/service-DW9u_0aZ.js +1 -0
- package/assets/index.html +7 -7
- package/dist/{ads-IFKZU7SC.js → ads-BXGEL4WI.js} +3 -3
- package/dist/{agent-3KMENHL3.js → agent-LFX7GHWD.js} +7 -7
- package/dist/{agent-ask-7WVQLLD5.js → agent-ask-QJV225AT.js} +3 -3
- package/dist/{agent-conversations-5LR67TBD.js → agent-conversations-2TZDIGA6.js} +3 -3
- package/dist/{agent-memory-GKUCCKVX.js → agent-memory-XCY3ZG5C.js} +3 -3
- package/dist/{agent-providers-TVCRM3YZ.js → agent-providers-VS6PH55Y.js} +3 -3
- package/dist/{agent-transcript-MUTCOOWG.js → agent-transcript-BUUHOTZM.js} +3 -3
- package/dist/{analytics-2IXSG5ML.js → analytics-YWJWPUNF.js} +3 -3
- package/dist/{apply-7RIYK4VC.js → apply-BMCYFBEJ.js} +3 -3
- package/dist/{backfill-VU3FV6DQ.js → backfill-YBOKT5LT.js} +9 -9
- package/dist/{backlinks-DLSRYEX2.js → backlinks-QNNABB4N.js} +3 -3
- package/dist/{bing-4CXTZUH4.js → bing-KEMXBABP.js} +3 -3
- package/dist/{bootstrap-OOHSBSDQ.js → bootstrap-X2AADK4M.js} +10 -10
- package/dist/{cdp-Y53SRSRV.js → cdp-WVYUEV4U.js} +7 -7
- package/dist/{chunk-J5YZXRD5.js → chunk-2X7DFLMD.js} +8 -8
- package/dist/{chunk-FG7KMVA6.js → chunk-32BTTG5N.js} +5 -5
- package/dist/{chunk-ALQEGGYM.js → chunk-37R6GTU7.js} +4 -4
- package/dist/{chunk-OSF27DCY.js → chunk-5DMESZDF.js} +2 -2
- package/dist/{chunk-JALKQREN.js → chunk-6DJN7HCK.js} +83 -11
- package/dist/{chunk-ZRSNMEB3.js → chunk-AFRCTLUL.js} +342 -337
- package/dist/{chunk-JYOMHNRP.js → chunk-AVISYPUU.js} +702 -465
- package/dist/{chunk-743NYXIK.js → chunk-CBYKQRDH.js} +2 -2
- package/dist/{chunk-QRBZBJ4U.js → chunk-EKSXR7RJ.js} +1 -1
- package/dist/{chunk-K2HDTMRI.js → chunk-G6PZEDTH.js} +3 -3
- package/dist/{chunk-DE2UI37M.js → chunk-GTPGYFZ4.js} +2 -2
- package/dist/{chunk-BCRRAH5C.js → chunk-HX3WLTZU.js} +1 -1
- package/dist/{chunk-AVQQRTMD.js → chunk-IEBMODUI.js} +72 -33
- package/dist/{chunk-L7DQYID4.js → chunk-KJLICF53.js} +4 -4
- package/dist/{chunk-CKTA5374.js → chunk-LU3M54IY.js} +1 -1
- package/dist/{chunk-LBRG6RKI.js → chunk-MD5PHJBX.js} +1 -1
- package/dist/{chunk-6ASEI24F.js → chunk-MLOGEXIO.js} +1 -1
- package/dist/{chunk-XC44Q5XR.js → chunk-MOJ2XZXK.js} +1 -1
- package/dist/{chunk-YJYLFIVT.js → chunk-NHHZQWMI.js} +1 -1
- package/dist/{chunk-H37CDAZX.js → chunk-P74TPIWZ.js} +2 -2
- package/dist/{chunk-IPF2S5G2.js → chunk-RRS7U6HQ.js} +4 -4
- package/dist/{chunk-SGZWOUIZ.js → chunk-UHLWNU6I.js} +5 -5
- package/dist/{chunk-EYWC24VC.js → chunk-UNCOK6DA.js} +1 -1
- package/dist/{chunk-YRX5UOM3.js → chunk-UXTNDZKM.js} +1 -1
- package/dist/{chunk-VGUZPC3I.js → chunk-VR4MYSEH.js} +3 -3
- package/dist/{chunk-2YBEI6LE.js → chunk-WSYLDPYF.js} +1 -1
- package/dist/{chunk-TJZLICMB.js → chunk-WZ4LUM26.js} +3 -3
- package/dist/{chunk-WNCOBSPN.js → chunk-XEGYSZEV.js} +19 -3
- package/dist/{chunk-EIMXB6UH.js → chunk-XEPEMON2.js} +2 -2
- package/dist/{chunk-DVYS2RMM.js → chunk-XIA6NWIF.js} +4 -4
- package/dist/{chunk-PFKBQTYD.js → chunk-XMIKSCAO.js} +1 -1
- package/dist/{chunk-DH5QRKFW.js → chunk-Y54PMWBH.js} +3 -3
- package/dist/{chunk-FNI3QSVD.js → chunk-YP743QX5.js} +1 -1
- package/dist/{chunk-IHOVPHUW.js → chunk-ZGUR4AZM.js} +2 -2
- package/dist/{citations-ZTQIYYDB.js → citations-ZSI5BOGD.js} +3 -3
- package/dist/{cli-catalog-IZJXAHFQ.js → cli-catalog-J2HUCGJL.js} +3 -3
- package/dist/{cli-commands-XYM2VMAK.js → cli-commands-XFMVFREG.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-SIXAPHOM.js → competitor-WJDN3DLP.js} +3 -3
- package/dist/{content-XPS3D3Y6.js → content-K52YV4BK.js} +3 -3
- package/dist/{dashboard-FMJDM5PA.js → dashboard-L6RKTP35.js} +6 -6
- package/dist/{demo-QQKSHTPO.js → demo-Y7XRD4X4.js} +1 -1
- package/dist/{demo-server-LQ3QXUAS.js → demo-server-QNQX5CNP.js} +13 -11
- package/dist/{discover-K5HFEU2T.js → discover-UO5M7HQZ.js} +3 -3
- package/dist/{discover-eval-6536Z2YN.js → discover-eval-DN4PS6VN.js} +3 -3
- package/dist/{doctor-STNH3PHH.js → doctor-7UYUXG7J.js} +3 -3
- package/dist/{evidence-W2EWKBAV.js → evidence-DGKN5SGQ.js} +3 -3
- package/dist/{export-cmd-EGHMY3M6.js → export-cmd-QAQWWIRL.js} +3 -3
- package/dist/{ga-DVIG3PVW.js → ga-4LGTEA6J.js} +3 -3
- package/dist/{gbp-SNS4XZBO.js → gbp-XOLYZ4XC.js} +3 -3
- package/dist/{google-KGPHZG6E.js → google-CAQDCFA6.js} +4 -4
- package/dist/{google-marketing-VZR6W3L6.js → google-marketing-HU5I3XPP.js} +1 -1
- package/dist/{health-cmd-2KWO2GOM.js → health-cmd-3PEB3M45.js} +3 -3
- package/dist/{history-ZY27B7JE.js → history-UGUWYPOY.js} +3 -3
- package/dist/index.d.ts +109 -3
- package/dist/index.js +24 -24
- package/dist/{init-5TXIKII2.js → init-DEW65BLN.js} +11 -11
- package/dist/{insights-MUYEOSOG.js → insights-ZC3R4ZYG.js} +3 -3
- package/dist/intelligence-service-XRJVXZYI.js +15 -0
- package/dist/{journeys-YO54IRVC.js → journeys-W6F4BCXF.js} +3 -3
- package/dist/{keys-M4QZGYVJ.js → keys-ME7VRF73.js} +3 -3
- package/dist/{keyword-MU62MGWT.js → keyword-R3PLGSO5.js} +3 -3
- package/dist/{logs-BAQM334Q.js → logs-UM7CWHYB.js} +3 -3
- package/dist/{mcp-LYS5P3PB.js → mcp-BUPP5HC6.js} +2 -2
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-HVSTZWTW.js → measurement-plan-R4KZHJII.js} +5 -5
- package/dist/{notify-XSP5JXU3.js → notify-4C26D2MY.js} +3 -3
- package/dist/{organic-evidence-4HYW4PVO.js → organic-evidence-SLIISYHW.js} +3 -3
- package/dist/{overview-XH72WTZT.js → overview-JJZZOYQU.js} +4 -4
- package/dist/{project-UKH7FTQX.js → project-XJH2OECP.js} +3 -3
- package/dist/{query-RRD5M4QV.js → query-ZWAIWIYB.js} +3 -3
- package/dist/{research-V2IGDUKL.js → research-GTVWVZBI.js} +3 -3
- package/dist/{results-export-5FC7ZT6O.js → results-export-CPAWO5AV.js} +3 -3
- package/dist/{run-B2A4LMWU.js → run-RI2BMD7U.js} +4 -4
- package/dist/{schedule-PLY6CHQT.js → schedule-HDDWULBX.js} +3 -3
- package/dist/{search-ACVK3WH5.js → search-TT3Q2BBP.js} +3 -3
- package/dist/{serve-2LTQZPEK.js → serve-6JYPUU24.js} +25 -25
- package/dist/{session-IHXMPETX.js → session-DDESFV3N.js} +12 -12
- package/dist/{settings-ZHBNX2I5.js → settings-4AYXYJVA.js} +3 -3
- package/dist/{skills-KWRTCXTF.js → skills-UFMLMMY5.js} +2 -2
- package/dist/{snapshot-GBDCCV6L.js → snapshot-KLWQGZB6.js} +3 -3
- package/dist/{src-NOTQWGYV.js → src-6IHGEMT2.js} +6 -2
- package/dist/{status-FGDDGBMJ.js → status-HRIUPGUM.js} +4 -4
- package/dist/{technical-aeo-AK4QI63Q.js → technical-aeo-HKJQHMMS.js} +3 -3
- package/dist/{telemetry-6QPWYMCR.js → telemetry-IS6KZK32.js} +4 -4
- package/dist/{tools-PBNTVVN7.js → tools-ZNVQFEAX.js} +11 -11
- package/dist/{traffic-AS4PIOQL.js → traffic-PFHFGN44.js} +5 -5
- package/dist/{users-BNYKF35J.js → users-BSLUSU6W.js} +3 -3
- package/dist/{visibility-stats-TWL3GJMD.js → visibility-stats-MPRNHPZW.js} +3 -3
- package/dist/{wordpress-XBWXAACD.js → wordpress-U5XKHURL.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/MeasurementPropertyPage-CdCnjCaS.js +0 -4
- package/assets/assets/ProjectPage-D3oFKnBG.js +0 -9
- package/assets/assets/SiteHealthSection-C6QE9xNk.js +0 -2
- package/assets/assets/TrafficSourceDetailPage-uxd0ojry.js +0 -1
- package/assets/assets/index-Ttz5_bL3.js +0 -60
- package/assets/assets/react-query-CISHjSch.js +0 -1
- package/assets/assets/service-DsDxSQuN.js +0 -1
- package/dist/intelligence-service-U4NSGZUH.js +0 -15
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
normalizeCloudflareWorkerEvent,
|
|
10
10
|
sessionizeJourneyRequests,
|
|
11
11
|
verifyRequestSignature
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HX3WLTZU.js";
|
|
13
13
|
import {
|
|
14
14
|
currentOutcomeAttribution,
|
|
15
15
|
getRequestContext,
|
|
16
16
|
registerRequestContext,
|
|
17
17
|
requestAuditIdentity,
|
|
18
18
|
sanitizeRequestContext
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UXTNDZKM.js";
|
|
20
20
|
import {
|
|
21
21
|
SentimentIdempotencyConflict,
|
|
22
22
|
SentimentRepository,
|
|
@@ -49,6 +49,8 @@ import {
|
|
|
49
49
|
conversionTrackingContracts,
|
|
50
50
|
crawlerEventsHourly,
|
|
51
51
|
crawlerVerificationManifestsHourly,
|
|
52
|
+
currentSeoTaskCondition,
|
|
53
|
+
currentSeoTasks,
|
|
52
54
|
dashboardSessions,
|
|
53
55
|
discoveryProbes,
|
|
54
56
|
discoverySessions,
|
|
@@ -154,7 +156,7 @@ import {
|
|
|
154
156
|
userSessions,
|
|
155
157
|
users,
|
|
156
158
|
writeNegativeReviewMaxStars
|
|
157
|
-
} from "./chunk-
|
|
159
|
+
} from "./chunk-6DJN7HCK.js";
|
|
158
160
|
import {
|
|
159
161
|
ADS_ACTIVATE_SCOPE,
|
|
160
162
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -366,6 +368,7 @@ import {
|
|
|
366
368
|
SeoAdapterOutcomes,
|
|
367
369
|
SeoAvailability,
|
|
368
370
|
SeoCapabilities,
|
|
371
|
+
SeoMarketRankingSearchStatuses,
|
|
369
372
|
SeoRunStatuses,
|
|
370
373
|
SeoScopeKinds,
|
|
371
374
|
SeoTargetKinds,
|
|
@@ -605,7 +608,6 @@ import {
|
|
|
605
608
|
effectiveDomains,
|
|
606
609
|
emptyCitationVisibility,
|
|
607
610
|
emptySentimentCounts,
|
|
608
|
-
emptySeoResults,
|
|
609
611
|
escapeLikePattern,
|
|
610
612
|
escapeRegExp,
|
|
611
613
|
evaluateModelPointerExposure,
|
|
@@ -1020,6 +1022,7 @@ import {
|
|
|
1020
1022
|
runtimeStateMissing,
|
|
1021
1023
|
schedulableRunKindSchema,
|
|
1022
1024
|
scheduleDtoSchema,
|
|
1025
|
+
scheduleEnabledAt,
|
|
1023
1026
|
scheduleExpectedUpdatedAtSchema,
|
|
1024
1027
|
scheduleUpsertRequestSchema,
|
|
1025
1028
|
scheduleVersionConflict,
|
|
@@ -1173,7 +1176,7 @@ import {
|
|
|
1173
1176
|
wordpressSchemaDeployResultDtoSchema,
|
|
1174
1177
|
wordpressSchemaStatusResultDtoSchema,
|
|
1175
1178
|
wordpressStatusDtoSchema
|
|
1176
|
-
} from "./chunk-
|
|
1179
|
+
} from "./chunk-XEGYSZEV.js";
|
|
1177
1180
|
|
|
1178
1181
|
// ../api-routes/src/user-session.ts
|
|
1179
1182
|
import crypto2 from "crypto";
|
|
@@ -7262,9 +7265,9 @@ var SentimentService = class {
|
|
|
7262
7265
|
const estimatedInputTokens = this.options.estimate ? inputs.reduce((sum, input) => sum + this.options.estimate(input), 0) : 0;
|
|
7263
7266
|
const expiresAt = new Date(Date.parse(this.now()) + 15 * 6e4).toISOString();
|
|
7264
7267
|
const previewToken = definitionId && settings.enabled && settings.ready && source.assessments.length ? this.sign({ projectId, selection: frozenSelection, definitionId, fingerprint: sentimentHash(source.assessments), expiresAt }) : null;
|
|
7265
|
-
const
|
|
7268
|
+
const skipped6 = source.runIds.flatMap((runId) => Object.entries(source.skippedByRun?.[runId] ?? {}).map(([reason, count10]) => ({ runId, reason, count: count10 })));
|
|
7266
7269
|
const estimateMethod = this.options.estimate ? `Adapter estimate of the full request including all questions and evidence candidates; final billed usage is recorded per attempt.${definitionId ? "" : " Sentiment is not configured for this project, so the estimate uses the current evaluator definition."}` : "Token and cost estimates unavailable on this host.";
|
|
7267
|
-
return { previewToken, expiresAt: previewToken ? expiresAt : null, selection: frozenSelection, evaluationDefinitionId: definitionId, eligibleAssessments: source.assessments.length, alreadyClassified, skipped:
|
|
7270
|
+
return { previewToken, expiresAt: previewToken ? expiresAt : null, selection: frozenSelection, evaluationDefinitionId: definitionId, eligibleAssessments: source.assessments.length, alreadyClassified, skipped: skipped6, estimatedInputTokens, estimatedCostUsd: this.options.estimate ? estimatedInputTokens * 0.042 / 1e6 : null, estimateMethod };
|
|
7268
7271
|
}
|
|
7269
7272
|
/** The receipt carries the first page of attempts; a replay of a long-running job never returns all of them. */
|
|
7270
7273
|
submit(projectId, previewToken, idempotencyKey, actor2) {
|
|
@@ -8419,6 +8422,7 @@ var HEALTH_CHECK_LABELS = {
|
|
|
8419
8422
|
"ga.auth.connection": "GA4 sign-in",
|
|
8420
8423
|
"ga.data.recent-data": "GA4 data stopped",
|
|
8421
8424
|
"gsc.data.recent-data": "Search Console data stopped",
|
|
8425
|
+
"seo.data.recent-refresh": "SEO refresh stalled",
|
|
8422
8426
|
"report.sweeps": "Monthly report sweeps",
|
|
8423
8427
|
"report.models": "Monthly report models",
|
|
8424
8428
|
"report.daily-data": "Monthly report data gaps",
|
|
@@ -8471,7 +8475,11 @@ var HEALTH_FIXED_LINES = {
|
|
|
8471
8475
|
"traffic.source.all-errored-recently": "Traffic syncs are working again.",
|
|
8472
8476
|
"traffic.queue-backlog.remaining": "The traffic backlog is cleared.",
|
|
8473
8477
|
"traffic.worker-version.stale": "The Cloudflare worker is up to date.",
|
|
8474
|
-
"gbp.auth.no-connection": "Business Profile is connected."
|
|
8478
|
+
"gbp.auth.no-connection": "Business Profile is connected.",
|
|
8479
|
+
"seo.data.refresh-failed": "SEO refreshes complete again.",
|
|
8480
|
+
"seo.data.refresh-partial": "SEO refreshes complete again.",
|
|
8481
|
+
"seo.data.overdue": "SEO data is current again.",
|
|
8482
|
+
"seo.data.schedule-invalid": "The SEO refresh schedule can run again."
|
|
8475
8483
|
};
|
|
8476
8484
|
function firstSentence(text, max2 = 180) {
|
|
8477
8485
|
if (!text) return null;
|
|
@@ -15220,7 +15228,37 @@ function runDto(job, counts2) {
|
|
|
15220
15228
|
return { id: job.id, projectId: job.projectKey, profileId: job.profileId, profileRevision: job.profileRevision, locationCode: job.profile.locationCode, languageCode: job.profile.languageCode, cancelRequested: job.cancelRequested, status: job.status, capabilities: job.profile.capabilities, scope: job.profile.scope, taskCount: counts2.reduce((sum, group) => sum + group.count, 0), completedTasks: counts2.filter((group) => group.status === SeoRunStatuses.completed || group.status === SeoRunStatuses.partial).reduce((sum, group) => sum + group.count, 0), failedTasks: counts2.filter((group) => group.status === SeoRunStatuses.failed).reduce((sum, group) => sum + group.count, 0), createdAt: job.createdAt, startedAt: job.startedAt, finishedAt: job.finishedAt, error: job.error };
|
|
15221
15229
|
}
|
|
15222
15230
|
function resultRows(tasks) {
|
|
15223
|
-
|
|
15231
|
+
const kept = tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed);
|
|
15232
|
+
return {
|
|
15233
|
+
keywordMetrics: kept.flatMap((task) => task.results.keywordMetrics),
|
|
15234
|
+
domainIntelligence: kept.flatMap((task) => task.results.domainIntelligence),
|
|
15235
|
+
rankedKeywords: kept.flatMap((task) => task.results.rankedKeywords),
|
|
15236
|
+
marketRankings: tasks.flatMap(marketRankingRows)
|
|
15237
|
+
};
|
|
15238
|
+
}
|
|
15239
|
+
function marketRankingRows(task) {
|
|
15240
|
+
const stored = task.reconciliation?.outcome === SeoAdapterOutcomes.failed ? [] : task.results.marketRankings ?? [];
|
|
15241
|
+
if (stored.length > 0) return stored.map((row) => ({ ...row, searchStatus: row.searchStatus ?? SeoMarketRankingSearchStatuses.completed }));
|
|
15242
|
+
const { search, target } = task.request;
|
|
15243
|
+
if (task.request.capability !== SeoCapabilities["market-rankings"] || !search || !target) return [];
|
|
15244
|
+
const searchStatus = task.status === SeoRunStatuses.cancelled ? SeoMarketRankingSearchStatuses.cancelled : task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"] ? SeoMarketRankingSearchStatuses.failed : null;
|
|
15245
|
+
if (!searchStatus) return [];
|
|
15246
|
+
return search.markets.map((market) => ({
|
|
15247
|
+
...market.context,
|
|
15248
|
+
searchText: task.request.keywords[0] ?? "",
|
|
15249
|
+
locationLabel: search.locationLabel,
|
|
15250
|
+
target,
|
|
15251
|
+
searchStatus,
|
|
15252
|
+
rankOrganic: null,
|
|
15253
|
+
rankAbsolute: null,
|
|
15254
|
+
url: null,
|
|
15255
|
+
depth: 0,
|
|
15256
|
+
competitors: [],
|
|
15257
|
+
provenance: null
|
|
15258
|
+
}));
|
|
15259
|
+
}
|
|
15260
|
+
function taskSearch(task) {
|
|
15261
|
+
return task.request.capability === SeoCapabilities["market-rankings"] ? { searchText: task.request.keywords[0] ?? null, locationLabel: task.request.search?.locationLabel ?? null } : { searchText: null, locationLabel: null };
|
|
15224
15262
|
}
|
|
15225
15263
|
function compareMarketRankings(a, b) {
|
|
15226
15264
|
return compareText(a.regionLabel ?? "", b.regionLabel ?? "") || compareText(a.marketLabel, b.marketLabel) || compareText(a.searchText, b.searchText) || compareText(a.queryText, b.queryText);
|
|
@@ -15434,14 +15472,20 @@ function budgetBuckets(account, projectId, policy, now) {
|
|
|
15434
15472
|
{ scope: `project-month:${projectId}`, period: now.slice(0, 7), limit: policy.projectMonthlyLimitMicros }
|
|
15435
15473
|
];
|
|
15436
15474
|
}
|
|
15475
|
+
function skippedRefreshReason(profileId, reason) {
|
|
15476
|
+
return `Scheduled SEO refresh for ${profileId ? `profile "${profileId}"` : "this project"} did not start: ${reason}`;
|
|
15477
|
+
}
|
|
15478
|
+
var SKIPPED_REFRESH_PROFILE = /^Scheduled SEO refresh for profile "([^"]+)" did not start: /;
|
|
15479
|
+
function skippedRefreshProfile(error) {
|
|
15480
|
+
return error ? SKIPPED_REFRESH_PROFILE.exec(error)?.[1] ?? null : null;
|
|
15481
|
+
}
|
|
15437
15482
|
function startScheduledSeoRefreshes(db, opts, input) {
|
|
15438
15483
|
const repository = new SeoRepository(db);
|
|
15439
15484
|
const principalId = `schedule:${input.scheduleId}`;
|
|
15440
15485
|
const recordSkipped = (profileId, reason, detail) => {
|
|
15441
15486
|
const runId = crypto17.randomUUID();
|
|
15442
15487
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15443
|
-
|
|
15444
|
-
db.insert(runs).values({ id: runId, projectId: input.projectId, kind: RunKinds["seo-sync"], status: RunStatuses.failed, trigger: RunTriggers.scheduled, createdAt: now, finishedAt: now, error: `Scheduled SEO refresh for ${subject} did not start: ${reason}` }).run();
|
|
15488
|
+
db.insert(runs).values({ id: runId, projectId: input.projectId, kind: RunKinds["seo-sync"], status: RunStatuses.failed, trigger: RunTriggers.scheduled, createdAt: now, finishedAt: now, error: skippedRefreshReason(profileId, reason) }).run();
|
|
15445
15489
|
return { profileId, status: "skipped", runId, reason, ...detail ? { detail } : {} };
|
|
15446
15490
|
};
|
|
15447
15491
|
const saved = db.select().from(seoProfiles).where(eq32(seoProfiles.projectId, input.projectId)).orderBy(asc6(seoProfiles.id)).all().map(profileDto);
|
|
@@ -15493,8 +15537,8 @@ async function seoRoutes(app, opts) {
|
|
|
15493
15537
|
if (!row) throw notFound("SEO profile", id);
|
|
15494
15538
|
return profileDto(row);
|
|
15495
15539
|
};
|
|
15496
|
-
const tasks = (id) => app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all();
|
|
15497
|
-
const taskCounts = (ids) => ids.length ? app.db.select({ jobId: seoTasks.jobId, status: seoTasks.status, count: sql8`count(*)` }).from(seoTasks).where(inArray16(seoTasks.jobId, ids)).groupBy(seoTasks.jobId, seoTasks.status).all() : [];
|
|
15540
|
+
const tasks = (id) => currentSeoTasks(app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all());
|
|
15541
|
+
const taskCounts = (ids) => ids.length ? app.db.select({ jobId: seoTasks.jobId, status: seoTasks.status, count: sql8`count(*)` }).from(seoTasks).where(and24(inArray16(seoTasks.jobId, ids), currentSeoTaskCondition())).groupBy(seoTasks.jobId, seoTasks.status).all() : [];
|
|
15498
15542
|
const runSummary = (row) => runDto(row, taskCounts([row.id]));
|
|
15499
15543
|
const job = (projectId, id) => {
|
|
15500
15544
|
const row = app.db.select().from(seoJobs).where(and24(eq32(seoJobs.projectId, projectId), isNotNull3(seoJobs.runId), eq32(seoJobs.id, id))).get();
|
|
@@ -15645,7 +15689,7 @@ async function seoRoutes(app, opts) {
|
|
|
15645
15689
|
const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
|
|
15646
15690
|
const rows = tasks(row.id);
|
|
15647
15691
|
const results = resultRows(rows);
|
|
15648
|
-
return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20), marketRankings: [...results.marketRankings].sort(compareMarketRankings).slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
|
|
15692
|
+
return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null, ...taskSearch(task), retried: task.retryOf !== null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20), marketRankings: [...results.marketRankings].sort(compareMarketRankings).slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
|
|
15649
15693
|
});
|
|
15650
15694
|
app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
|
|
15651
15695
|
requireScope(request, SEO_RUN_SCOPE);
|
|
@@ -27130,7 +27174,7 @@ async function applyRoutes(app, opts) {
|
|
|
27130
27174
|
timezone: resolvedSchedule.timezone,
|
|
27131
27175
|
nextRunAt,
|
|
27132
27176
|
providers: config.spec.schedule?.providers ?? [],
|
|
27133
|
-
...specEnabled === void 0 ? {} : { enabled: specEnabled },
|
|
27177
|
+
...specEnabled === void 0 ? {} : { enabled: specEnabled, enabledAt: scheduleEnabledAt(existingSched, specEnabled, now) },
|
|
27134
27178
|
updatedAt: nextScheduleUpdatedAt(existingSched.updatedAt, Date.parse(now))
|
|
27135
27179
|
}).where(eq44(schedules.id, existingSched.id)).run();
|
|
27136
27180
|
} else {
|
|
@@ -27144,6 +27188,7 @@ async function applyRoutes(app, opts) {
|
|
|
27144
27188
|
timezone: resolvedSchedule.timezone,
|
|
27145
27189
|
nextRunAt,
|
|
27146
27190
|
enabled: specEnabled ?? true,
|
|
27191
|
+
enabledAt: scheduleEnabledAt(void 0, specEnabled ?? true, now),
|
|
27147
27192
|
providers: config.spec.schedule?.providers ?? [],
|
|
27148
27193
|
createdAt: now,
|
|
27149
27194
|
updatedAt: now
|
|
@@ -35124,7 +35169,7 @@ var routeCatalog = [
|
|
|
35124
35169
|
path: "/api/v1/projects/{name}/seo/market-rankings",
|
|
35125
35170
|
summary: "Read stored market rankings",
|
|
35126
35171
|
tags: ["seo"],
|
|
35127
|
-
description: "Stored evidence only; never calls a provider. The project's position for each tracked non-brand query, searched on Google from its market's location, ordered by region, market and search text.
|
|
35172
|
+
description: "Stored evidence only; never calls a provider. The project's position for each tracked non-brand query, searched on Google from its market's location, ordered by region, market and search text. searchStatus says how each search ended: in a completed or partial row a null rank means the site was not in the checked results (depth, which is lower when only part of the results came back); a failed or cancelled search keeps its rows with no rank.",
|
|
35128
35173
|
parameters: [
|
|
35129
35174
|
nameParameter,
|
|
35130
35175
|
...seoEvidenceParameters,
|
|
@@ -43091,6 +43136,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
43091
43136
|
providers: providers ?? [],
|
|
43092
43137
|
sourceId: sourceId ?? null,
|
|
43093
43138
|
enabled: enabledBool,
|
|
43139
|
+
enabledAt: scheduleEnabledAt(existing, enabledBool, now),
|
|
43094
43140
|
updatedAt: now
|
|
43095
43141
|
};
|
|
43096
43142
|
if (existing) {
|
|
@@ -59835,11 +59881,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
59835
59881
|
reportMetaStep(metaWriteOutcome(result.results));
|
|
59836
59882
|
const applied = result.results.filter((r) => r.status === "applied").length;
|
|
59837
59883
|
const manual = result.results.filter((r) => r.status === "manual").length;
|
|
59838
|
-
const
|
|
59884
|
+
const skipped6 = result.results.filter((r) => r.status === "skipped").length;
|
|
59839
59885
|
steps.push({
|
|
59840
59886
|
name: "set-meta",
|
|
59841
59887
|
status: "completed",
|
|
59842
|
-
summary: `${applied} applied, ${manual} manual-assist, ${
|
|
59888
|
+
summary: `${applied} applied, ${manual} manual-assist, ${skipped6} skipped`
|
|
59843
59889
|
});
|
|
59844
59890
|
}
|
|
59845
59891
|
} catch (err) {
|
|
@@ -59869,11 +59915,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
59869
59915
|
reportSchemaStep(schemaDeployOutcome(result.results));
|
|
59870
59916
|
const deployed = result.results.filter((r) => r.status === "deployed").length;
|
|
59871
59917
|
const stripped = result.results.filter((r) => r.status === "stripped").length;
|
|
59872
|
-
const
|
|
59918
|
+
const skipped6 = result.results.filter((r) => r.status === "skipped").length;
|
|
59873
59919
|
steps.push({
|
|
59874
59920
|
name: "schema-deploy",
|
|
59875
59921
|
status: "completed",
|
|
59876
|
-
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${
|
|
59922
|
+
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped6} skipped`
|
|
59877
59923
|
});
|
|
59878
59924
|
}
|
|
59879
59925
|
} catch (err) {
|
|
@@ -60261,7 +60307,7 @@ async function queryBacklinks(opts) {
|
|
|
60261
60307
|
const reversed = opts.targets.map(reverseDomain);
|
|
60262
60308
|
const targetList = reversed.map(quote).join(", ");
|
|
60263
60309
|
const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
|
|
60264
|
-
const
|
|
60310
|
+
const sql38 = `
|
|
60265
60311
|
WITH vertices AS (
|
|
60266
60312
|
SELECT * FROM read_csv(
|
|
60267
60313
|
${quote(opts.vertexPath)},
|
|
@@ -60297,7 +60343,7 @@ async function queryBacklinks(opts) {
|
|
|
60297
60343
|
const conn = await instance.connect();
|
|
60298
60344
|
let rows;
|
|
60299
60345
|
try {
|
|
60300
|
-
const reader = await conn.runAndReadAll(
|
|
60346
|
+
const reader = await conn.runAndReadAll(sql38);
|
|
60301
60347
|
rows = reader.getRowObjects();
|
|
60302
60348
|
} finally {
|
|
60303
60349
|
conn.disconnectSync?.();
|
|
@@ -62904,6 +62950,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
|
|
|
62904
62950
|
preset: null,
|
|
62905
62951
|
timezone: "UTC",
|
|
62906
62952
|
enabled: true,
|
|
62953
|
+
enabledAt: now,
|
|
62907
62954
|
providers: [],
|
|
62908
62955
|
sourceId,
|
|
62909
62956
|
createdAt: now,
|
|
@@ -67413,6 +67460,195 @@ var gscRecentDataCheck = {
|
|
|
67413
67460
|
};
|
|
67414
67461
|
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
67415
67462
|
|
|
67463
|
+
// ../api-routes/src/doctor/checks/seo-refresh.ts
|
|
67464
|
+
import { and as and75, asc as asc20, desc as desc42, eq as eq89, gt as gt8, inArray as inArray39, sql as sql32 } from "drizzle-orm";
|
|
67465
|
+
var GRACE_DAYS = 2;
|
|
67466
|
+
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
67467
|
+
var FIRING_SPAN_MS = 6e4;
|
|
67468
|
+
var REASON_LIMIT = 300;
|
|
67469
|
+
function skipped2(code, summary3) {
|
|
67470
|
+
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67471
|
+
}
|
|
67472
|
+
function seoSyncRuns(projectId) {
|
|
67473
|
+
return and75(eq89(runs.projectId, projectId), eq89(runs.kind, RunKinds["seo-sync"]));
|
|
67474
|
+
}
|
|
67475
|
+
function scheduleTiming(schedule) {
|
|
67476
|
+
if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
|
|
67477
|
+
const recurrence = calendarRecurrenceSchema.safeParse(schedule.recurrence).data;
|
|
67478
|
+
return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
|
|
67479
|
+
}
|
|
67480
|
+
function latestFiring(ctx, projectId, enabledSince) {
|
|
67481
|
+
const scheduled = and75(seoSyncRuns(projectId), eq89(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
|
|
67482
|
+
const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc42(runs.createdAt)).limit(1).get();
|
|
67483
|
+
if (!newest) return [];
|
|
67484
|
+
const newestMs = Date.parse(newest.createdAt);
|
|
67485
|
+
const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
|
|
67486
|
+
return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and75(scheduled, gt8(runs.createdAt, after))).orderBy(asc20(runs.createdAt), asc20(runs.id)).all();
|
|
67487
|
+
}
|
|
67488
|
+
function lastCompleted(ctx, projectId) {
|
|
67489
|
+
const at = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
67490
|
+
return ctx.db.select({ id: runs.id, at }).from(runs).where(and75(seoSyncRuns(projectId), eq89(runs.status, RunStatuses.completed))).orderBy(desc42(at)).limit(1).get() ?? null;
|
|
67491
|
+
}
|
|
67492
|
+
function isIncomplete(run) {
|
|
67493
|
+
return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
|
|
67494
|
+
}
|
|
67495
|
+
function runProfiles(ctx, firing) {
|
|
67496
|
+
const jobs = firing.length === 0 ? [] : ctx.db.select({ runId: seoJobs.runId, profileId: seoJobs.profileId }).from(seoJobs).where(inArray39(seoJobs.runId, firing.map((run) => run.id))).all();
|
|
67497
|
+
const byRun = new Map(jobs.flatMap((job) => job.runId ? [[job.runId, job.profileId]] : []));
|
|
67498
|
+
return new Map(firing.map((run) => [run.id, byRun.get(run.id) ?? skippedRefreshProfile(run.error)]));
|
|
67499
|
+
}
|
|
67500
|
+
function unsettledRuns(ctx, projectId, firing, profiles) {
|
|
67501
|
+
const incomplete = firing.filter(isIncomplete);
|
|
67502
|
+
const firedAt = firing[firing.length - 1]?.createdAt;
|
|
67503
|
+
if (incomplete.length === 0 || !firedAt) return incomplete;
|
|
67504
|
+
const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq89(seoJobs.runId, runs.id)).where(and75(seoSyncRuns(projectId), eq89(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
|
|
67505
|
+
return incomplete.filter((run) => {
|
|
67506
|
+
const profile = profiles.get(run.id) ?? null;
|
|
67507
|
+
return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
|
|
67508
|
+
});
|
|
67509
|
+
}
|
|
67510
|
+
var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
|
|
67511
|
+
function refreshReason(ctx, run) {
|
|
67512
|
+
const stored = parseRunError(run.error);
|
|
67513
|
+
if (stored) return formatRunErrorOneLine(stored);
|
|
67514
|
+
const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs, eq89(seoTasks.jobId, seoJobs.id)).where(eq89(seoJobs.runId, run.id)).all());
|
|
67515
|
+
const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
|
|
67516
|
+
const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
|
|
67517
|
+
const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
|
|
67518
|
+
const errors = [...new Set(failed.flatMap((task) => task.error ? [task.error] : []))].join(" ");
|
|
67519
|
+
if (tasks.length <= 1) {
|
|
67520
|
+
if (errors) return errors;
|
|
67521
|
+
if (partial > 0) return "Some SEO results could not be collected.";
|
|
67522
|
+
return cancelled > 0 ? "The refresh was cancelled." : "No reason was recorded.";
|
|
67523
|
+
}
|
|
67524
|
+
const unit = tasks.every((task) => task.request.capability === SeoCapabilities["market-rankings"]) ? "searches" : "requests";
|
|
67525
|
+
const counts2 = [
|
|
67526
|
+
...failed.length > 0 ? [`${failed.length} failed`] : [],
|
|
67527
|
+
...partial > 0 ? [`${partial} returned only part of their results`] : [],
|
|
67528
|
+
...cancelled > 0 ? [`${cancelled} did not run`] : []
|
|
67529
|
+
];
|
|
67530
|
+
if (counts2.length === 0) return "No reason was recorded.";
|
|
67531
|
+
return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
|
|
67532
|
+
}
|
|
67533
|
+
function gradeSeoRefresh(ctx, project) {
|
|
67534
|
+
const schedule = ctx.db.select().from(schedules).where(and75(eq89(schedules.projectId, project.id), eq89(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
|
|
67535
|
+
if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
|
|
67536
|
+
if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
|
|
67537
|
+
const name = project.name;
|
|
67538
|
+
const day = (iso) => formatIsoDateInTimeZone(iso, schedule.timezone);
|
|
67539
|
+
const now = Date.now();
|
|
67540
|
+
const timing = scheduleTiming(schedule);
|
|
67541
|
+
const last = lastCompleted(ctx, project.id);
|
|
67542
|
+
const enabledSince = schedule.enabledAt ?? schedule.createdAt;
|
|
67543
|
+
const reference = last && Date.parse(last.at) > Date.parse(enabledSince) ? last.at : enabledSince;
|
|
67544
|
+
const dueAt = timing ? nextRunFromSchedule(timing, new Date(reference)) : null;
|
|
67545
|
+
const deadlineAt = dueAt ? new Date(Date.parse(dueAt) + GRACE_DAYS * DAY_MS5).toISOString() : null;
|
|
67546
|
+
const firing = latestFiring(ctx, project.id, enabledSince);
|
|
67547
|
+
const profiles = runProfiles(ctx, firing);
|
|
67548
|
+
const details = {
|
|
67549
|
+
scheduleId: schedule.id,
|
|
67550
|
+
cronExpr: schedule.cronExpr || null,
|
|
67551
|
+
recurrence: schedule.recurrence ?? null,
|
|
67552
|
+
timezone: schedule.timezone,
|
|
67553
|
+
enabledSince,
|
|
67554
|
+
graceDays: GRACE_DAYS,
|
|
67555
|
+
lastCompletedAt: last?.at ?? null,
|
|
67556
|
+
lastCompletedRunId: last?.id ?? null,
|
|
67557
|
+
dueAt,
|
|
67558
|
+
deadlineAt,
|
|
67559
|
+
latestScheduledRuns: firing.map((run) => ({
|
|
67560
|
+
id: run.id,
|
|
67561
|
+
profileId: profiles.get(run.id) ?? null,
|
|
67562
|
+
status: run.status,
|
|
67563
|
+
createdAt: run.createdAt,
|
|
67564
|
+
reason: isIncomplete(run) ? refreshReason(ctx, run) : null
|
|
67565
|
+
}))
|
|
67566
|
+
};
|
|
67567
|
+
if (!timing || !dueAt || !deadlineAt) {
|
|
67568
|
+
return {
|
|
67569
|
+
status: CheckStatuses.warn,
|
|
67570
|
+
code: "seo.data.schedule-invalid",
|
|
67571
|
+
summary: "The SEO refresh schedule has no time it can run, so no scheduled SEO refresh will start.",
|
|
67572
|
+
remediation: `Set a schedule that can run, for example \`canonry schedule set ${name} --kind seo-sync --cron '0 7 2 * *' --timezone UTC\`.`,
|
|
67573
|
+
details,
|
|
67574
|
+
alert: {
|
|
67575
|
+
label: "SEO schedule invalid",
|
|
67576
|
+
line: "The SEO refresh schedule has no time it can run, so SEO data never refreshes.",
|
|
67577
|
+
next: `Set it again with canonry schedule set ${name} --kind seo-sync.`
|
|
67578
|
+
}
|
|
67579
|
+
};
|
|
67580
|
+
}
|
|
67581
|
+
const nextAt = nextRunFromSchedule(timing, new Date(now));
|
|
67582
|
+
const unsettled = unsettledRuns(ctx, project.id, firing, profiles);
|
|
67583
|
+
const newestFired = firing[firing.length - 1];
|
|
67584
|
+
if (newestFired && unsettled.length > 0) {
|
|
67585
|
+
const failed = unsettled.some((run) => run.status === RunStatuses.failed);
|
|
67586
|
+
const firedOn = day(newestFired.createdAt);
|
|
67587
|
+
const reasons = [...new Set(unsettled.map((run) => refreshReason(ctx, run)))].join(" ");
|
|
67588
|
+
const reason = reasons.length > REASON_LIMIT ? `${truncateUtf16(reasons, REASON_LIMIT)}...` : reasons;
|
|
67589
|
+
const outcome2 = firing.length === 1 ? `The scheduled SEO refresh on ${firedOn} ${failed ? "failed" : "only partly completed"}` : `${unsettled.length} of ${firing.length} profile refreshes in the scheduled SEO refresh on ${firedOn} did not fully complete`;
|
|
67590
|
+
const line = firing.length > 1 ? `${outcome2}.` : unsettled[0].error ? reason : `${outcome2}: ${reason}`;
|
|
67591
|
+
const next = nextAt ? ` on ${day(nextAt)}` : "";
|
|
67592
|
+
return {
|
|
67593
|
+
status: CheckStatuses.warn,
|
|
67594
|
+
code: failed ? "seo.data.refresh-failed" : "seo.data.refresh-partial",
|
|
67595
|
+
summary: `${outcome2}. Reason: ${reason} ${last ? `The last completed SEO refresh was on ${day(last.at)}.` : "No SEO refresh has completed yet."}`,
|
|
67596
|
+
remediation: `Fix what the reason names, then refresh now with \`canonry seo refresh-intent ${name} <profile-id>\` and \`canonry seo sync\`, or wait for the next scheduled refresh${next}. A completed refresh of the same profile clears this warning.`,
|
|
67597
|
+
details,
|
|
67598
|
+
alert: {
|
|
67599
|
+
label: failed ? "SEO refresh failed" : "SEO refresh incomplete",
|
|
67600
|
+
line: firstSentence(line) ?? line,
|
|
67601
|
+
next: `Fix the cause, then refresh by hand or wait for the next scheduled refresh${next}.`
|
|
67602
|
+
}
|
|
67603
|
+
};
|
|
67604
|
+
}
|
|
67605
|
+
if (now >= Date.parse(deadlineAt)) {
|
|
67606
|
+
const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and75(seoSyncRuns(project.id), inArray39(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc20(runs.createdAt)).limit(1).get();
|
|
67607
|
+
const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
|
|
67608
|
+
const missed = last ? `The scheduled refresh due ${day(dueAt)} did not complete within ${GRACE_DAYS} days.` : `Its first refresh, due ${day(dueAt)}, did not complete within ${GRACE_DAYS} days.`;
|
|
67609
|
+
return {
|
|
67610
|
+
status: CheckStatuses.warn,
|
|
67611
|
+
code: "seo.data.overdue",
|
|
67612
|
+
summary: `${since} ${missed}${active ? ` A refresh started ${day(active.createdAt)} has not finished, and scheduled refreshes wait for it.` : ""}`,
|
|
67613
|
+
remediation: active ? `Check that refresh with \`canonry seo run show ${name} ${active.id}\`, and cancel it with \`canonry seo run cancel ${name} ${active.id}\` if it is stuck.` : `Check that the server was running on ${day(dueAt)} and what \`canonry runs ${name} --kind seo-sync\` shows for that day. To refresh now, run \`canonry seo refresh-intent ${name} <profile-id>\`, then \`canonry seo sync\`.`,
|
|
67614
|
+
details: { ...details, activeRunId: active?.id ?? null },
|
|
67615
|
+
alert: {
|
|
67616
|
+
label: "SEO refresh overdue",
|
|
67617
|
+
line: since,
|
|
67618
|
+
next: active ? "Check the refresh that has not finished, and cancel it if it is stuck." : `Check that the server was up on ${day(dueAt)}, then refresh by hand.`
|
|
67619
|
+
}
|
|
67620
|
+
};
|
|
67621
|
+
}
|
|
67622
|
+
const pending = Date.parse(dueAt) <= now;
|
|
67623
|
+
if (!last) {
|
|
67624
|
+
return {
|
|
67625
|
+
status: CheckStatuses.ok,
|
|
67626
|
+
code: "seo.data.awaiting-first-refresh",
|
|
67627
|
+
summary: pending ? `No SEO refresh has completed yet. The first scheduled refresh, due ${day(dueAt)}, has until ${day(deadlineAt)} to complete.` : `No SEO refresh has completed yet. The first scheduled refresh is due ${day(dueAt)}.`,
|
|
67628
|
+
remediation: null,
|
|
67629
|
+
details
|
|
67630
|
+
};
|
|
67631
|
+
}
|
|
67632
|
+
return {
|
|
67633
|
+
status: CheckStatuses.ok,
|
|
67634
|
+
code: "seo.data.fresh",
|
|
67635
|
+
summary: `The last SEO refresh completed on ${day(last.at)}. ${pending ? `The scheduled refresh due ${day(dueAt)} has until ${day(deadlineAt)} to complete.` : `The next scheduled refresh is due ${day(dueAt)}.`}`,
|
|
67636
|
+
remediation: null,
|
|
67637
|
+
details
|
|
67638
|
+
};
|
|
67639
|
+
}
|
|
67640
|
+
var seoRecentRefreshCheck = {
|
|
67641
|
+
id: "seo.data.recent-refresh",
|
|
67642
|
+
category: CheckCategories.integrations,
|
|
67643
|
+
scope: CheckScopes.project,
|
|
67644
|
+
title: "SEO data still refreshing",
|
|
67645
|
+
run: (ctx) => {
|
|
67646
|
+
if (!ctx.project) return skipped2("seo.data.no-project", "Project context required.");
|
|
67647
|
+
return gradeSeoRefresh(ctx, ctx.project);
|
|
67648
|
+
}
|
|
67649
|
+
};
|
|
67650
|
+
var SEO_REFRESH_CHECKS = [seoRecentRefreshCheck];
|
|
67651
|
+
|
|
67416
67652
|
// ../api-routes/src/site-reachability.ts
|
|
67417
67653
|
import dns3 from "dns/promises";
|
|
67418
67654
|
import http3 from "http";
|
|
@@ -67616,7 +67852,7 @@ function probeHostFromDomain(value) {
|
|
|
67616
67852
|
if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
|
|
67617
67853
|
return hostname;
|
|
67618
67854
|
}
|
|
67619
|
-
var
|
|
67855
|
+
var skipped3 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
|
|
67620
67856
|
var siteReachabilityCheck = {
|
|
67621
67857
|
id: SITE_REACHABILITY_CHECK_ID,
|
|
67622
67858
|
category: CheckCategories.integrations,
|
|
@@ -67627,16 +67863,16 @@ var siteReachabilityCheck = {
|
|
|
67627
67863
|
// `canonry doctor --project` must not exit 1 on a single dropped packet.
|
|
67628
67864
|
optIn: true,
|
|
67629
67865
|
run: async (ctx) => {
|
|
67630
|
-
if (!ctx.project) return
|
|
67866
|
+
if (!ctx.project) return skipped3("site.reachability.no-project", "Project context required.");
|
|
67631
67867
|
const host = probeHostFromDomain(ctx.project.canonicalDomain);
|
|
67632
67868
|
if (!host) {
|
|
67633
|
-
return
|
|
67869
|
+
return skipped3("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
|
|
67634
67870
|
}
|
|
67635
67871
|
const url = `https://${host}/`;
|
|
67636
67872
|
const probe = ctx.probeSiteReachability ?? probeSiteReachability;
|
|
67637
67873
|
const result = await probe(url);
|
|
67638
67874
|
if (result.state === "unavailable") {
|
|
67639
|
-
return
|
|
67875
|
+
return skipped3("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
|
|
67640
67876
|
}
|
|
67641
67877
|
if (result.state === "up") {
|
|
67642
67878
|
return {
|
|
@@ -67677,7 +67913,7 @@ var siteReachabilityCheck = {
|
|
|
67677
67913
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
67678
67914
|
|
|
67679
67915
|
// ../api-routes/src/doctor/checks/api-hosts.ts
|
|
67680
|
-
import { eq as
|
|
67916
|
+
import { eq as eq91 } from "drizzle-orm";
|
|
67681
67917
|
|
|
67682
67918
|
// ../api-routes/src/api-host-probe.ts
|
|
67683
67919
|
import dns4 from "dns/promises";
|
|
@@ -67783,7 +68019,7 @@ async function probeApiHost(host, options = {}) {
|
|
|
67783
68019
|
}
|
|
67784
68020
|
|
|
67785
68021
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
67786
|
-
import { and as
|
|
68022
|
+
import { and as and76, count as count7, desc as desc43, eq as eq90, gte as gte23, gt as gt9, inArray as inArray40, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
|
|
67787
68023
|
var SYNC_FAILURE_WINDOW = 3;
|
|
67788
68024
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
67789
68025
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -67804,7 +68040,7 @@ function gscConnection(ctx, project) {
|
|
|
67804
68040
|
function gbpConnection(ctx, project) {
|
|
67805
68041
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
67806
68042
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
67807
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
68043
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, project.id), eq90(gbpLocations.selected, true))).limit(1).get();
|
|
67808
68044
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
67809
68045
|
}
|
|
67810
68046
|
function gaConnection(ctx, project) {
|
|
@@ -67861,7 +68097,7 @@ var SPECS = [
|
|
|
67861
68097
|
causeCheckIds: ["google.auth.connection", "google.auth.property-access", "google.auth.scopes", "gsc.api.reachable"]
|
|
67862
68098
|
}
|
|
67863
68099
|
];
|
|
67864
|
-
function
|
|
68100
|
+
function skipped4(code, summary3) {
|
|
67865
68101
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67866
68102
|
}
|
|
67867
68103
|
function latestErrorText(raw) {
|
|
@@ -67869,16 +68105,16 @@ function latestErrorText(raw) {
|
|
|
67869
68105
|
return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
|
|
67870
68106
|
}
|
|
67871
68107
|
function readSyncRunState(db, projectId, kind, options = {}) {
|
|
67872
|
-
const scope =
|
|
67873
|
-
|
|
67874
|
-
|
|
67875
|
-
...options.sourceId ? [
|
|
68108
|
+
const scope = and76(
|
|
68109
|
+
eq90(runs.projectId, projectId),
|
|
68110
|
+
eq90(runs.kind, kind),
|
|
68111
|
+
...options.sourceId ? [eq90(runs.sourceId, options.sourceId)] : [],
|
|
67876
68112
|
...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
|
|
67877
68113
|
);
|
|
67878
|
-
const graded = options.since ?
|
|
67879
|
-
const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(
|
|
67880
|
-
const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
67881
|
-
const failureCount = db.select({ count: count7() }).from(runs).where(
|
|
68114
|
+
const graded = options.since ? and76(scope, gte23(runs.createdAt, options.since)) : scope;
|
|
68115
|
+
const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and76(graded, inArray40(runs.status, FINISHED_STATUSES))).orderBy(desc43(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
|
|
68116
|
+
const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and76(graded, inArray40(runs.status, SUCCESS_STATUSES))).orderBy(desc43(runs.createdAt)).limit(1).get();
|
|
68117
|
+
const failureCount = db.select({ count: count7() }).from(runs).where(and76(lastSuccess ? and76(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq90(runs.status, RunStatuses.failed))).get();
|
|
67882
68118
|
const failuresSinceSuccess = Number(failureCount?.count ?? 0);
|
|
67883
68119
|
const latest = recent[0];
|
|
67884
68120
|
return {
|
|
@@ -67895,21 +68131,21 @@ function readSyncRunState(db, projectId, kind, options = {}) {
|
|
|
67895
68131
|
function gradeSyncFailures(ctx, spec) {
|
|
67896
68132
|
const code = (suffix) => `${spec.prefix}.sync.${suffix}`;
|
|
67897
68133
|
const project = ctx.project;
|
|
67898
|
-
if (!project) return
|
|
68134
|
+
if (!project) return skipped4(code("no-project"), "Project context required.");
|
|
67899
68135
|
const connection = spec.connection(ctx, project);
|
|
67900
68136
|
if (connection.state === "store-unavailable") {
|
|
67901
|
-
return
|
|
68137
|
+
return skipped4(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
|
|
67902
68138
|
}
|
|
67903
68139
|
if (connection.state === "not-connected") {
|
|
67904
|
-
return
|
|
68140
|
+
return skipped4(code("not-connected"), `${spec.label} is not connected for this project.`);
|
|
67905
68141
|
}
|
|
67906
68142
|
if (connection.state === "no-selected-locations") {
|
|
67907
|
-
return
|
|
68143
|
+
return skipped4(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
|
|
67908
68144
|
}
|
|
67909
68145
|
const nothingToSync = spec.nothingToSyncErrors ?? [];
|
|
67910
68146
|
const state = readSyncRunState(ctx.db, project.id, spec.kind, { since: connection.since, nothingToSyncErrors: nothingToSync });
|
|
67911
68147
|
if (state.latestStatus === null) {
|
|
67912
|
-
return
|
|
68148
|
+
return skipped4(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
|
|
67913
68149
|
}
|
|
67914
68150
|
const latestFailed = state.latestStatus === RunStatuses.failed;
|
|
67915
68151
|
const details = {
|
|
@@ -67965,7 +68201,7 @@ var API_HOST_TABLE = {
|
|
|
67965
68201
|
ads: ["api.ads.openai.com"]
|
|
67966
68202
|
};
|
|
67967
68203
|
var API_HOST_EXCLUDED = { gsc: [new URL(INDEXING_API_BASE).hostname] };
|
|
67968
|
-
function
|
|
68204
|
+
function skipped5(code, summary3) {
|
|
67969
68205
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67970
68206
|
}
|
|
67971
68207
|
function connectionFor(prefix, ctx, project) {
|
|
@@ -67975,7 +68211,7 @@ function connectionFor(prefix, ctx, project) {
|
|
|
67975
68211
|
if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
|
|
67976
68212
|
if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
|
|
67977
68213
|
if (prefix === "bing") return ctx.bingConnectionStore?.getConnection(project.canonicalDomain) ? { state: "connected", since: null } : { state: "not-connected" };
|
|
67978
|
-
return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(
|
|
68214
|
+
return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq91(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
|
|
67979
68215
|
}
|
|
67980
68216
|
function syncCommand(prefix) {
|
|
67981
68217
|
if (prefix === "gsc") return "google sync";
|
|
@@ -67989,9 +68225,9 @@ function syncCommand(prefix) {
|
|
|
67989
68225
|
async function grade2(prefix, ctx) {
|
|
67990
68226
|
const id = `${prefix}.api`;
|
|
67991
68227
|
const checkId = `${id}.reachable`;
|
|
67992
|
-
if (!ctx.project) return
|
|
68228
|
+
if (!ctx.project) return skipped5(`${id}.no-project`, "Project context required.");
|
|
67993
68229
|
const connection = connectionFor(prefix, ctx, ctx.project);
|
|
67994
|
-
if (connection.state !== "connected") return
|
|
68230
|
+
if (connection.state !== "connected") return skipped5(`${id}.not-connected`, `${prefix} is not connected for this project.`);
|
|
67995
68231
|
const probe = ctx.probeApiHost ?? probeApiHost;
|
|
67996
68232
|
const results = await Promise.all(API_HOST_TABLE[prefix].map((host) => probe(host)));
|
|
67997
68233
|
const details = { hosts: results };
|
|
@@ -68020,7 +68256,7 @@ function definition(prefix) {
|
|
|
68020
68256
|
var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
|
|
68021
68257
|
|
|
68022
68258
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
68023
|
-
import { and as
|
|
68259
|
+
import { and as and77, eq as eq92 } from "drizzle-orm";
|
|
68024
68260
|
var RECENT_SYNC_WARN_DAYS = 4;
|
|
68025
68261
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
68026
68262
|
function skippedNoProject3() {
|
|
@@ -68040,7 +68276,7 @@ function storeUnavailable() {
|
|
|
68040
68276
|
};
|
|
68041
68277
|
}
|
|
68042
68278
|
function hasSelectedLocation(ctx, projectId) {
|
|
68043
|
-
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(
|
|
68279
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, projectId), eq92(gbpLocations.selected, true))).limit(1).get();
|
|
68044
68280
|
}
|
|
68045
68281
|
async function resolveGbpToken(ctx) {
|
|
68046
68282
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
@@ -68300,7 +68536,7 @@ var recentSyncCheck = {
|
|
|
68300
68536
|
title: "GBP recent sync",
|
|
68301
68537
|
run: (ctx) => {
|
|
68302
68538
|
if (!ctx.project) return skippedNoProject3();
|
|
68303
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
68539
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, ctx.project.id), eq92(gbpLocations.selected, true))).all();
|
|
68304
68540
|
if (selected.length === 0) {
|
|
68305
68541
|
return {
|
|
68306
68542
|
status: CheckStatuses.skipped,
|
|
@@ -68360,7 +68596,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
68360
68596
|
);
|
|
68361
68597
|
|
|
68362
68598
|
// ../api-routes/src/doctor/checks/places.ts
|
|
68363
|
-
import { eq as
|
|
68599
|
+
import { eq as eq93 } from "drizzle-orm";
|
|
68364
68600
|
var apiKeyCheck = {
|
|
68365
68601
|
id: "gbp.places.api-key",
|
|
68366
68602
|
category: CheckCategories.auth,
|
|
@@ -68405,7 +68641,7 @@ var apiKeyCheck = {
|
|
|
68405
68641
|
details: { tier: cfg.tier }
|
|
68406
68642
|
};
|
|
68407
68643
|
}
|
|
68408
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
68644
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq93(gbpLocations.projectId, ctx.project.id)).all();
|
|
68409
68645
|
const selected = rows.filter((r) => r.selected);
|
|
68410
68646
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
68411
68647
|
const details = {
|
|
@@ -68438,7 +68674,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
68438
68674
|
);
|
|
68439
68675
|
|
|
68440
68676
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
68441
|
-
import { and as
|
|
68677
|
+
import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray41, sql as sql33 } from "drizzle-orm";
|
|
68442
68678
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
68443
68679
|
var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
|
|
68444
68680
|
var reviewsAccessCheck = {
|
|
@@ -68466,7 +68702,7 @@ var reviewsAccessCheck = {
|
|
|
68466
68702
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
68467
68703
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
68468
68704
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
68469
|
-
}).from(gbpLocations).where(
|
|
68705
|
+
}).from(gbpLocations).where(and78(eq94(gbpLocations.projectId, project.id), eq94(gbpLocations.selected, true))).all();
|
|
68470
68706
|
if (locations.length === 0) {
|
|
68471
68707
|
return {
|
|
68472
68708
|
status: CheckStatuses.skipped,
|
|
@@ -68486,7 +68722,7 @@ var reviewsAccessCheck = {
|
|
|
68486
68722
|
const places = ctx.getPlacesConfig?.();
|
|
68487
68723
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
68488
68724
|
const v4Worked = new Set(
|
|
68489
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
68725
|
+
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and78(eq94(gbpReviewRatings.projectId, project.id), eq94(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
|
|
68490
68726
|
);
|
|
68491
68727
|
const coverageOf = (loc) => {
|
|
68492
68728
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -68520,7 +68756,7 @@ var reviewsAccessCheck = {
|
|
|
68520
68756
|
none > 0 ? `${none} have no review source` : null
|
|
68521
68757
|
].filter((part) => part !== null);
|
|
68522
68758
|
const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
|
|
68523
|
-
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(
|
|
68759
|
+
const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq94(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
|
|
68524
68760
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
68525
68761
|
if (none > 0 && subscribed) {
|
|
68526
68762
|
return {
|
|
@@ -68551,7 +68787,7 @@ var reviewDeliveryCheck = {
|
|
|
68551
68787
|
return { status: CheckStatuses.skipped, code: "gbp.reviews.delivery.no-project", summary: "Project context required.", remediation: null };
|
|
68552
68788
|
}
|
|
68553
68789
|
const since = new Date(Date.now() - UNDELIVERED_ATTENTION_DAYS * 864e5).toISOString();
|
|
68554
|
-
const lastTouched =
|
|
68790
|
+
const lastTouched = sql33`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
|
|
68555
68791
|
const rows = ctx.db.select({
|
|
68556
68792
|
id: reviewWebhookDeliveries.id,
|
|
68557
68793
|
notificationId: reviewWebhookDeliveries.notificationId,
|
|
@@ -68559,11 +68795,11 @@ var reviewDeliveryCheck = {
|
|
|
68559
68795
|
attempts: reviewWebhookDeliveries.attempts,
|
|
68560
68796
|
lastTouched,
|
|
68561
68797
|
payload: reviewWebhookDeliveries.payload
|
|
68562
|
-
}).from(reviewWebhookDeliveries).where(
|
|
68563
|
-
|
|
68564
|
-
|
|
68798
|
+
}).from(reviewWebhookDeliveries).where(and78(
|
|
68799
|
+
eq94(reviewWebhookDeliveries.projectId, ctx.project.id),
|
|
68800
|
+
inArray41(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
|
|
68565
68801
|
gte24(lastTouched, since)
|
|
68566
|
-
)).orderBy(
|
|
68802
|
+
)).orderBy(desc44(lastTouched)).all();
|
|
68567
68803
|
if (rows.length === 0) {
|
|
68568
68804
|
return {
|
|
68569
68805
|
status: CheckStatuses.ok,
|
|
@@ -68572,7 +68808,7 @@ var reviewDeliveryCheck = {
|
|
|
68572
68808
|
remediation: null
|
|
68573
68809
|
};
|
|
68574
68810
|
}
|
|
68575
|
-
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(
|
|
68811
|
+
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq94(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
|
|
68576
68812
|
const deliveries = rows.map((row) => {
|
|
68577
68813
|
const review = row.payload.reviews[0];
|
|
68578
68814
|
return {
|
|
@@ -69146,7 +69382,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
69146
69382
|
];
|
|
69147
69383
|
|
|
69148
69384
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
69149
|
-
import { and as
|
|
69385
|
+
import { and as and79, desc as desc45, eq as eq95, gte as gte25, inArray as inArray42, isNotNull as isNotNull9, ne as ne16, sql as sql34 } from "drizzle-orm";
|
|
69150
69386
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
69151
69387
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
69152
69388
|
function isCloudflareDirectPush(source) {
|
|
@@ -69185,8 +69421,8 @@ function skippedNoProject5() {
|
|
|
69185
69421
|
function loadProbes(ctx) {
|
|
69186
69422
|
if (!ctx.project) return [];
|
|
69187
69423
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
69188
|
-
|
|
69189
|
-
|
|
69424
|
+
and79(
|
|
69425
|
+
eq95(trafficSources.projectId, ctx.project.id),
|
|
69190
69426
|
ne16(trafficSources.status, TrafficSourceStatuses.archived)
|
|
69191
69427
|
)
|
|
69192
69428
|
).all();
|
|
@@ -69323,28 +69559,28 @@ var recentDataCheck = {
|
|
|
69323
69559
|
const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
|
|
69324
69560
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
69325
69561
|
const recentCrawlers = Number(
|
|
69326
|
-
ctx.db.select({ total:
|
|
69327
|
-
|
|
69328
|
-
|
|
69329
|
-
|
|
69562
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
69563
|
+
and79(
|
|
69564
|
+
eq95(crawlerEventsHourly.projectId, ctx.project.id),
|
|
69565
|
+
inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
69330
69566
|
gte25(crawlerEventsHourly.tsHour, warnCutoff)
|
|
69331
69567
|
)
|
|
69332
69568
|
).get()?.total ?? 0
|
|
69333
69569
|
);
|
|
69334
69570
|
const recentReferrals = Number(
|
|
69335
|
-
ctx.db.select({ total:
|
|
69336
|
-
|
|
69337
|
-
|
|
69338
|
-
|
|
69571
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
69572
|
+
and79(
|
|
69573
|
+
eq95(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
69574
|
+
inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
69339
69575
|
gte25(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
69340
69576
|
)
|
|
69341
69577
|
).get()?.total ?? 0
|
|
69342
69578
|
);
|
|
69343
69579
|
const recentUserFetches = Number(
|
|
69344
|
-
ctx.db.select({ total:
|
|
69345
|
-
|
|
69346
|
-
|
|
69347
|
-
|
|
69580
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
69581
|
+
and79(
|
|
69582
|
+
eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
69583
|
+
inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
69348
69584
|
gte25(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
69349
69585
|
)
|
|
69350
69586
|
).get()?.total ?? 0
|
|
@@ -69367,28 +69603,28 @@ var recentDataCheck = {
|
|
|
69367
69603
|
};
|
|
69368
69604
|
}
|
|
69369
69605
|
const olderCrawlers = Number(
|
|
69370
|
-
ctx.db.select({ total:
|
|
69371
|
-
|
|
69372
|
-
|
|
69373
|
-
|
|
69606
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
69607
|
+
and79(
|
|
69608
|
+
eq95(crawlerEventsHourly.projectId, ctx.project.id),
|
|
69609
|
+
inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
69374
69610
|
gte25(crawlerEventsHourly.tsHour, failCutoff)
|
|
69375
69611
|
)
|
|
69376
69612
|
).get()?.total ?? 0
|
|
69377
69613
|
);
|
|
69378
69614
|
const olderReferrals = Number(
|
|
69379
|
-
ctx.db.select({ total:
|
|
69380
|
-
|
|
69381
|
-
|
|
69382
|
-
|
|
69615
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
69616
|
+
and79(
|
|
69617
|
+
eq95(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
69618
|
+
inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
69383
69619
|
gte25(aiReferralEventsHourly.tsHour, failCutoff)
|
|
69384
69620
|
)
|
|
69385
69621
|
).get()?.total ?? 0
|
|
69386
69622
|
);
|
|
69387
69623
|
const olderUserFetches = Number(
|
|
69388
|
-
ctx.db.select({ total:
|
|
69389
|
-
|
|
69390
|
-
|
|
69391
|
-
|
|
69624
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
69625
|
+
and79(
|
|
69626
|
+
eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
69627
|
+
inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
69392
69628
|
gte25(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
69393
69629
|
)
|
|
69394
69630
|
).get()?.total ?? 0
|
|
@@ -69453,7 +69689,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
|
|
|
69453
69689
|
function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
69454
69690
|
const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
|
|
69455
69691
|
const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
|
|
69456
|
-
const
|
|
69692
|
+
const skipped6 = results.filter((r) => r.output.status === CheckStatuses.skipped);
|
|
69457
69693
|
const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
|
|
69458
69694
|
const detail = {
|
|
69459
69695
|
sources: results.map((r) => ({
|
|
@@ -69487,7 +69723,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
69487
69723
|
return {
|
|
69488
69724
|
status: CheckStatuses.ok,
|
|
69489
69725
|
code: `traffic.${fallbackId}.ok`,
|
|
69490
|
-
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${
|
|
69726
|
+
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped6.length > 0 ? ` (${skipped6.length} skipped)` : ""}.`,
|
|
69491
69727
|
details: detail
|
|
69492
69728
|
};
|
|
69493
69729
|
}
|
|
@@ -69495,7 +69731,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
69495
69731
|
status: CheckStatuses.skipped,
|
|
69496
69732
|
code: `traffic.${fallbackId}.all-skipped`,
|
|
69497
69733
|
summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
|
|
69498
|
-
remediation:
|
|
69734
|
+
remediation: skipped6.find((result) => result.output.remediation)?.output.remediation,
|
|
69499
69735
|
details: detail
|
|
69500
69736
|
};
|
|
69501
69737
|
}
|
|
@@ -69714,12 +69950,12 @@ var syncLagCheck = {
|
|
|
69714
69950
|
}
|
|
69715
69951
|
};
|
|
69716
69952
|
function lastQueueStopReason(ctx, sourceId) {
|
|
69717
|
-
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(
|
|
69718
|
-
|
|
69719
|
-
|
|
69720
|
-
|
|
69953
|
+
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and79(
|
|
69954
|
+
eq95(runs.projectId, ctx.project.id),
|
|
69955
|
+
eq95(runs.kind, RunKinds["traffic-sync"]),
|
|
69956
|
+
eq95(runs.sourceId, sourceId),
|
|
69721
69957
|
isNotNull9(runs.syncSummary)
|
|
69722
|
-
)).orderBy(
|
|
69958
|
+
)).orderBy(desc45(runs.createdAt)).limit(1).get();
|
|
69723
69959
|
return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
|
|
69724
69960
|
}
|
|
69725
69961
|
var QUEUE_LIMIT_STOPS = [
|
|
@@ -70009,7 +70245,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
|
|
|
70009
70245
|
];
|
|
70010
70246
|
var FRESH_SNAPSHOT_WARN_DAYS = 7;
|
|
70011
70247
|
var FRESH_SNAPSHOT_FAIL_DAYS = 30;
|
|
70012
|
-
var
|
|
70248
|
+
var DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
70013
70249
|
function unavailableStatus() {
|
|
70014
70250
|
return {
|
|
70015
70251
|
status: CheckStatuses.skipped,
|
|
@@ -70096,7 +70332,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
|
|
|
70096
70332
|
details: { latestSnapshotAt }
|
|
70097
70333
|
};
|
|
70098
70334
|
}
|
|
70099
|
-
const ageDays2 = (now.getTime() - snapshotMs) /
|
|
70335
|
+
const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS6;
|
|
70100
70336
|
const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays2)) };
|
|
70101
70337
|
if (ageDays2 < 0) {
|
|
70102
70338
|
return {
|
|
@@ -70319,6 +70555,7 @@ var ALL_CHECKS = [
|
|
|
70319
70555
|
...WORDPRESS_PUBLISH_CHECKS,
|
|
70320
70556
|
...GA_AUTH_CHECKS,
|
|
70321
70557
|
...DATA_FRESHNESS_CHECKS,
|
|
70558
|
+
...SEO_REFRESH_CHECKS,
|
|
70322
70559
|
reportSweepsCheck,
|
|
70323
70560
|
reportModelsCheck,
|
|
70324
70561
|
reportDailyDataCheck,
|
|
@@ -70516,7 +70753,7 @@ async function doctorRoutes(app, opts) {
|
|
|
70516
70753
|
}
|
|
70517
70754
|
|
|
70518
70755
|
// ../api-routes/src/status.ts
|
|
70519
|
-
import { and as
|
|
70756
|
+
import { and as and80, desc as desc46, eq as eq96, isNull as isNull15 } from "drizzle-orm";
|
|
70520
70757
|
function ageDays(iso) {
|
|
70521
70758
|
if (!iso) return null;
|
|
70522
70759
|
const ms = Date.now() - Date.parse(iso);
|
|
@@ -70566,7 +70803,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
|
|
|
70566
70803
|
};
|
|
70567
70804
|
}
|
|
70568
70805
|
function latestDate2(db, table, projectId) {
|
|
70569
|
-
const row = db.select({ date: table.date }).from(table).where(
|
|
70806
|
+
const row = db.select({ date: table.date }).from(table).where(eq96(table.projectId, projectId)).orderBy(desc46(table.date)).limit(1).get();
|
|
70570
70807
|
return row?.date ?? null;
|
|
70571
70808
|
}
|
|
70572
70809
|
function projectRows(opts, project) {
|
|
@@ -70581,7 +70818,7 @@ function projectRows(opts, project) {
|
|
|
70581
70818
|
const rows = [];
|
|
70582
70819
|
const gsc = gscConnection(ctx, info);
|
|
70583
70820
|
if (connected(gsc)) {
|
|
70584
|
-
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
70821
|
+
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq96(gscDataWatermarks.projectId, project.id)).limit(1).get();
|
|
70585
70822
|
const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
|
|
70586
70823
|
const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
|
|
70587
70824
|
rows.push(makeRow("gsc", "Search Console", RunKinds["gsc-sync"], state, SYNC_IDLE_DAYS, dataThrough, null, ["gsc.sync.recent-failures"], GSC_DATA_AGING_DAYS));
|
|
@@ -70594,12 +70831,12 @@ function projectRows(opts, project) {
|
|
|
70594
70831
|
const gbp = gbpConnection(ctx, info);
|
|
70595
70832
|
if (connected(gbp)) rows.push(makeRow("gbp", "Google Business Profile", RunKinds["gbp-sync"], readSyncRunState(opts.db, project.id, RunKinds["gbp-sync"], { nothingToSyncErrors: GBP_NOTHING_TO_SYNC_ERRORS }), RECENT_SYNC_WARN_DAYS, null, null, ["gbp.sync.recent-failures"]));
|
|
70596
70833
|
if (opts.bingConnectionStore?.getConnection(project.canonicalDomain)) rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
|
|
70597
|
-
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(
|
|
70834
|
+
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq96(adsConnections.projectId, project.id)).limit(1).get();
|
|
70598
70835
|
if (ads) rows.push(makeRow("ads", "OpenAI Ads", RunKinds["ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["ads-sync"]), ADS_RECENT_SYNC_WARN_DAYS, null, null, ["ads.data.recent-sync"], ADS_RECENT_SYNC_WARN_DAYS, ads.lastSyncedAt));
|
|
70599
70836
|
const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
|
|
70600
70837
|
if (marketing?.googleAds) rows.push(makeRow("google-ads", "Google Ads", RunKinds["google-ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["google-ads-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["google-ads.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.googleAds.latestSnapshotAt));
|
|
70601
70838
|
if (marketing?.gtm) rows.push(makeRow("gtm", "Google Tag Manager", RunKinds["gtm-sync"], readSyncRunState(opts.db, project.id, RunKinds["gtm-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["gtm.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.gtm.latestSnapshotAt));
|
|
70602
|
-
const sources = opts.db.select().from(trafficSources).where(
|
|
70839
|
+
const sources = opts.db.select().from(trafficSources).where(and80(eq96(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
|
|
70603
70840
|
for (const source of sources) {
|
|
70604
70841
|
const row = makeRow("traffic", source.displayName, RunKinds["traffic-sync"], readSyncRunState(opts.db, project.id, RunKinds["traffic-sync"], { sourceId: source.id }), null, null, source.id, ["traffic.source.sync-lag", "traffic.source.recent-data"]);
|
|
70605
70842
|
const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
|
|
@@ -70611,7 +70848,7 @@ function projectRows(opts, project) {
|
|
|
70611
70848
|
}
|
|
70612
70849
|
function projectStatus(opts, project) {
|
|
70613
70850
|
const integrations = projectRows(opts, project);
|
|
70614
|
-
const healthRow = opts.db.select().from(doctorHealthState).where(
|
|
70851
|
+
const healthRow = opts.db.select().from(doctorHealthState).where(eq96(doctorHealthState.projectId, project.id)).limit(1).get();
|
|
70615
70852
|
const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
|
|
70616
70853
|
const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
|
|
70617
70854
|
const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
|
|
@@ -70631,7 +70868,7 @@ async function statusRoutes(app, opts) {
|
|
|
70631
70868
|
if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
|
|
70632
70869
|
return instanceStatusDtoSchema.parse(result);
|
|
70633
70870
|
}
|
|
70634
|
-
const projectsRows = (scopedId ? opts.db.select().from(projects).where(
|
|
70871
|
+
const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq96(projects.id, scopedId)) : opts.db.select().from(projects)).all();
|
|
70635
70872
|
const output = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...opts.getMcpHealth ? { mcp: opts.getMcpHealth() } : {}, summary: { projects: projectsRows.length, ok: 0, failing: 0, warning: 0, stale: 0, neverSynced: 0 }, projects: projectsRows.map((project) => projectStatus(opts, project)) };
|
|
70636
70873
|
for (const project of output.projects) {
|
|
70637
70874
|
const key = project.state === "never-synced" ? "neverSynced" : project.state;
|
|
@@ -70644,7 +70881,7 @@ async function statusRoutes(app, opts) {
|
|
|
70644
70881
|
|
|
70645
70882
|
// ../api-routes/src/discovery/routes.ts
|
|
70646
70883
|
import crypto54 from "crypto";
|
|
70647
|
-
import { and as
|
|
70884
|
+
import { and as and81, desc as desc47, eq as eq97, gte as gte26, inArray as inArray43, isNull as isNull16, or as or20 } from "drizzle-orm";
|
|
70648
70885
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
70649
70886
|
async function discoveryRoutes(app, opts) {
|
|
70650
70887
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -70678,13 +70915,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
70678
70915
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
70679
70916
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
70680
70917
|
const decision = app.db.transaction((tx) => {
|
|
70681
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
70682
|
-
|
|
70683
|
-
|
|
70918
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and81(
|
|
70919
|
+
eq97(discoverySessions.projectId, project.id),
|
|
70920
|
+
eq97(discoverySessions.icpDescription, icpDescription),
|
|
70684
70921
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
70685
70922
|
// semantics, so a request with a different (or no) buyer must start
|
|
70686
70923
|
// its own session, never adopt another buyer's probes.
|
|
70687
|
-
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) :
|
|
70924
|
+
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq97(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
70688
70925
|
// Locations are identity too: a different service-area subset seeds
|
|
70689
70926
|
// and probes a different geo, so it must never reuse another geo's
|
|
70690
70927
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -70694,18 +70931,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
70694
70931
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
70695
70932
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
70696
70933
|
// upgrade, never a wrong reuse.
|
|
70697
|
-
locations.length === 0 ? or20(isNull16(discoverySessions.locations),
|
|
70934
|
+
locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq97(discoverySessions.locations, locations)) : eq97(discoverySessions.locations, locations),
|
|
70698
70935
|
// Seed provider set is identity: a different phrasing distribution
|
|
70699
70936
|
// must never reuse another set's session. Null (= Gemini-only
|
|
70700
70937
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
70701
|
-
seedProviders == null ? isNull16(discoverySessions.seedProviders) :
|
|
70702
|
-
|
|
70938
|
+
seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq97(discoverySessions.seedProviders, seedProviders),
|
|
70939
|
+
inArray43(discoverySessions.status, [
|
|
70703
70940
|
DiscoverySessionStatuses.queued,
|
|
70704
70941
|
DiscoverySessionStatuses.seeding,
|
|
70705
70942
|
DiscoverySessionStatuses.probing
|
|
70706
70943
|
]),
|
|
70707
70944
|
gte26(discoverySessions.createdAt, ageFloorIso)
|
|
70708
|
-
)).orderBy(
|
|
70945
|
+
)).orderBy(desc47(discoverySessions.createdAt)).get();
|
|
70709
70946
|
if (existing && existing.runId) {
|
|
70710
70947
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
70711
70948
|
}
|
|
@@ -70774,7 +71011,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70774
71011
|
const project = resolveProject(app.db, request.params.name);
|
|
70775
71012
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
70776
71013
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
70777
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
71014
|
+
const rows = app.db.select().from(discoverySessions).where(eq97(discoverySessions.projectId, project.id)).orderBy(desc47(discoverySessions.createdAt)).limit(limit).all();
|
|
70778
71015
|
return reply.send(rows.map(serializeSession));
|
|
70779
71016
|
}
|
|
70780
71017
|
);
|
|
@@ -70782,11 +71019,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
70782
71019
|
"/projects/:name/discover/sessions/:id",
|
|
70783
71020
|
async (request, reply) => {
|
|
70784
71021
|
const project = resolveProject(app.db, request.params.name);
|
|
70785
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71022
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70786
71023
|
if (!session || session.projectId !== project.id) {
|
|
70787
71024
|
throw notFound("Discovery session", request.params.id);
|
|
70788
71025
|
}
|
|
70789
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71026
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70790
71027
|
const detail = {
|
|
70791
71028
|
...serializeSession(session),
|
|
70792
71029
|
probes: probeRows.map(serializeProbe)
|
|
@@ -70798,7 +71035,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70798
71035
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
70799
71036
|
async (request, reply) => {
|
|
70800
71037
|
const project = resolveProject(app.db, request.params.name);
|
|
70801
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71038
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70802
71039
|
if (!session || session.projectId !== project.id) {
|
|
70803
71040
|
throw notFound("Discovery session", request.params.id);
|
|
70804
71041
|
}
|
|
@@ -70806,7 +71043,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70806
71043
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
70807
71044
|
const applyAnchor = request.query.anchor !== "false";
|
|
70808
71045
|
const provider = session.seedProvider ?? "gemini";
|
|
70809
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71046
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70810
71047
|
const extract = opts.harvestSearchQueries;
|
|
70811
71048
|
const probesWithQueries = probeRows.map((row) => {
|
|
70812
71049
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -70817,7 +71054,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70817
71054
|
return { searchQueries: [] };
|
|
70818
71055
|
}
|
|
70819
71056
|
});
|
|
70820
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
71057
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query);
|
|
70821
71058
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
70822
71059
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
70823
71060
|
effectiveDomains(project)
|
|
@@ -70864,11 +71101,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
70864
71101
|
"/projects/:name/discover/sessions/:id/promote",
|
|
70865
71102
|
async (request, reply) => {
|
|
70866
71103
|
const project = resolveProject(app.db, request.params.name);
|
|
70867
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71104
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70868
71105
|
if (!session || session.projectId !== project.id) {
|
|
70869
71106
|
throw notFound("Discovery session", request.params.id);
|
|
70870
71107
|
}
|
|
70871
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71108
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70872
71109
|
const cited = /* @__PURE__ */ new Set();
|
|
70873
71110
|
const aspirational = /* @__PURE__ */ new Set();
|
|
70874
71111
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -70903,7 +71140,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70903
71140
|
);
|
|
70904
71141
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
70905
71142
|
const project = resolveProject(app.db, request.params.name);
|
|
70906
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71143
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70907
71144
|
if (!session || session.projectId !== project.id) {
|
|
70908
71145
|
throw notFound("Discovery session", request.params.id);
|
|
70909
71146
|
}
|
|
@@ -70926,7 +71163,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70926
71163
|
const bucketSet = new Set(buckets);
|
|
70927
71164
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
70928
71165
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
70929
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71166
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70930
71167
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
70931
71168
|
for (const probe of probeRows) {
|
|
70932
71169
|
if (!probe.bucket) continue;
|
|
@@ -70934,7 +71171,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70934
71171
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
70935
71172
|
}
|
|
70936
71173
|
const existingQueries = new Set(
|
|
70937
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
71174
|
+
app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
70938
71175
|
);
|
|
70939
71176
|
const promotedQueries = [];
|
|
70940
71177
|
const skippedQueries = [];
|
|
@@ -71106,21 +71343,21 @@ function planCompetitorPromotion(input) {
|
|
|
71106
71343
|
const preferred = new Set(input.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES);
|
|
71107
71344
|
const eligible = groupCompetitorMap(input.competitorMap, input.probes, preferred).filter((group) => group.hits >= DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS).filter((group) => !typeFilter || group.sources.some((source) => typeFilter.has(source.competitorType))).sort((a, b) => b.hits - a.hits || a.domain.localeCompare(b.domain));
|
|
71108
71345
|
const tracked = new Set(input.tracked.map((row) => normalizeCompetitorDomain(row.domain)));
|
|
71109
|
-
const
|
|
71346
|
+
const skipped6 = [];
|
|
71110
71347
|
const candidates = [];
|
|
71111
71348
|
for (const group of eligible) {
|
|
71112
71349
|
const projectDomain = competitorDomainProjectClaim(group.domain, input.project.domains);
|
|
71113
71350
|
const subdomains = [...new Set(group.sources.map((source) => hostOf(source.domain) ?? source.domain))].filter((host) => host !== group.domain);
|
|
71114
71351
|
if (projectDomain) {
|
|
71115
|
-
|
|
71352
|
+
skipped6.push({
|
|
71116
71353
|
...group,
|
|
71117
71354
|
reason: DiscoveryPromoteCompetitorSkipReasons["project-domain"],
|
|
71118
71355
|
message: `${group.domain} overlaps the project domain ${projectDomain}, so every citation of the project would count for it`
|
|
71119
71356
|
});
|
|
71120
71357
|
} else if (tracked.has(group.domain)) {
|
|
71121
|
-
|
|
71358
|
+
skipped6.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
|
|
71122
71359
|
} else if (subdomains.length > 1) {
|
|
71123
|
-
|
|
71360
|
+
skipped6.push({
|
|
71124
71361
|
...group,
|
|
71125
71362
|
reason: DiscoveryPromoteCompetitorSkipReasons["shared-host"],
|
|
71126
71363
|
message: `${group.domain} was cited as ${subdomains.length} different subdomains (${subdomains.join(", ")}), which may be different sites on one shared host, so it is not merged into one competitor; if they are one competitor, add it with canonry competitor add <project> ${group.domain}`
|
|
@@ -71134,7 +71371,7 @@ function planCompetitorPromotion(input) {
|
|
|
71134
71371
|
for (const group of candidates) {
|
|
71135
71372
|
const rejections = claimed.get(group.domain);
|
|
71136
71373
|
if (rejections) {
|
|
71137
|
-
|
|
71374
|
+
skipped6.push({
|
|
71138
71375
|
...group,
|
|
71139
71376
|
reason: DiscoveryPromoteCompetitorSkipReasons["claimed-by-alias"],
|
|
71140
71377
|
message: rejections.map((rejection) => `${group.domain} ${describeCompetitorAliasRejection(rejection)}`).join("; ")
|
|
@@ -71143,12 +71380,12 @@ function planCompetitorPromotion(input) {
|
|
|
71143
71380
|
promote.push(group);
|
|
71144
71381
|
}
|
|
71145
71382
|
}
|
|
71146
|
-
return { promote, skipped:
|
|
71383
|
+
return { promote, skipped: skipped6 };
|
|
71147
71384
|
}
|
|
71148
71385
|
|
|
71149
71386
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
71150
71387
|
import crypto55 from "crypto";
|
|
71151
|
-
import { eq as
|
|
71388
|
+
import { eq as eq98 } from "drizzle-orm";
|
|
71152
71389
|
var DEFAULT_MAX_PROBES = 100;
|
|
71153
71390
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
71154
71391
|
function classifyProbeBucket(input) {
|
|
@@ -71234,7 +71471,7 @@ async function executeDiscovery(opts) {
|
|
|
71234
71471
|
status: DiscoverySessionStatuses.seeding,
|
|
71235
71472
|
dedupThreshold,
|
|
71236
71473
|
startedAt
|
|
71237
|
-
}).where(
|
|
71474
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71238
71475
|
const seedResult = await opts.deps.seed({
|
|
71239
71476
|
project: opts.project,
|
|
71240
71477
|
icpDescription: opts.icpDescription,
|
|
@@ -71295,7 +71532,7 @@ async function executeDiscovery(opts) {
|
|
|
71295
71532
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
71296
71533
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
71297
71534
|
warning
|
|
71298
|
-
}).where(
|
|
71535
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71299
71536
|
const probeLocation = opts.locations?.[0];
|
|
71300
71537
|
const probeResults = await mapWithConcurrency(
|
|
71301
71538
|
probedCanonicals,
|
|
@@ -71349,7 +71586,7 @@ async function executeDiscovery(opts) {
|
|
|
71349
71586
|
wastedCount: buckets["wasted-surface"],
|
|
71350
71587
|
competitorMap,
|
|
71351
71588
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71352
|
-
}).where(
|
|
71589
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71353
71590
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
71354
71591
|
return {
|
|
71355
71592
|
buckets,
|
|
@@ -71389,7 +71626,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
71389
71626
|
status: DiscoverySessionStatuses.failed,
|
|
71390
71627
|
error,
|
|
71391
71628
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71392
|
-
}).where(
|
|
71629
|
+
}).where(eq98(discoverySessions.id, sessionId)).run();
|
|
71393
71630
|
}
|
|
71394
71631
|
function dedupeStrings(input) {
|
|
71395
71632
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -71407,12 +71644,12 @@ function dedupeStrings(input) {
|
|
|
71407
71644
|
|
|
71408
71645
|
// ../api-routes/src/technical-aeo.ts
|
|
71409
71646
|
import crypto56 from "crypto";
|
|
71410
|
-
import { and as
|
|
71647
|
+
import { and as and83, asc as asc22, count as count8, desc as desc48, eq as eq100, gte as gte27, inArray as inArray44, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql36 } from "drizzle-orm";
|
|
71411
71648
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
71412
71649
|
|
|
71413
71650
|
// ../api-routes/src/technical-aeo-issues.ts
|
|
71414
71651
|
import { createHash as createHash10 } from "crypto";
|
|
71415
|
-
import { and as
|
|
71652
|
+
import { and as and82, asc as asc21, eq as eq99, gt as gt10, sql as sql35 } from "drizzle-orm";
|
|
71416
71653
|
var ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
71417
71654
|
"entity-consistency.title.short",
|
|
71418
71655
|
"site.duplicate-title",
|
|
@@ -71557,12 +71794,12 @@ function readSiteAuditIssues(input) {
|
|
|
71557
71794
|
}
|
|
71558
71795
|
}
|
|
71559
71796
|
if (snapshot.detailsAvailable && snapshot.attemptId) {
|
|
71560
|
-
const scope =
|
|
71561
|
-
|
|
71562
|
-
|
|
71563
|
-
|
|
71797
|
+
const scope = and82(
|
|
71798
|
+
eq99(siteCrawlPages.projectId, project.id),
|
|
71799
|
+
eq99(siteCrawlPages.runId, snapshot.runId),
|
|
71800
|
+
eq99(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
71564
71801
|
);
|
|
71565
|
-
const size = db.select({ bytes:
|
|
71802
|
+
const size = db.select({ bytes: sql35`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
|
|
71566
71803
|
if (size + crossBytes > MAX_EVIDENCE_BYTES) {
|
|
71567
71804
|
throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
|
|
71568
71805
|
}
|
|
@@ -71573,7 +71810,7 @@ function readSiteAuditIssues(input) {
|
|
|
71573
71810
|
url: siteCrawlPages.url,
|
|
71574
71811
|
auditScore: siteCrawlPages.auditScore,
|
|
71575
71812
|
auditFields: siteCrawlPages.auditFields
|
|
71576
|
-
}).from(siteCrawlPages).where(
|
|
71813
|
+
}).from(siteCrawlPages).where(and82(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc21(siteCrawlPages.nodeKey)).limit(200).all();
|
|
71577
71814
|
for (const page3 of pages) {
|
|
71578
71815
|
evidenceHash.update(JSON.stringify(page3));
|
|
71579
71816
|
const fields = record(page3.auditFields);
|
|
@@ -71935,13 +72172,13 @@ function parseLinkKind(value) {
|
|
|
71935
72172
|
return allowed.data;
|
|
71936
72173
|
}
|
|
71937
72174
|
function linkKindFilter(linkKind) {
|
|
71938
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
71939
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
72175
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq100(siteCrawlEdges.isTemplate, false);
|
|
72176
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq100(siteCrawlEdges.isTemplate, true);
|
|
71940
72177
|
return void 0;
|
|
71941
72178
|
}
|
|
71942
72179
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
71943
72180
|
var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
|
|
71944
|
-
var PAGE_CHANGE_PREDICATE =
|
|
72181
|
+
var PAGE_CHANGE_PREDICATE = sql36`(
|
|
71945
72182
|
current.url IS NOT previous.url
|
|
71946
72183
|
OR current.final_url IS NOT previous.final_url
|
|
71947
72184
|
OR current.path IS NOT previous.path
|
|
@@ -71963,7 +72200,7 @@ var PAGE_CHANGE_PREDICATE = sql35`(
|
|
|
71963
72200
|
OR current.link_score_raw IS NOT previous.link_score_raw
|
|
71964
72201
|
OR current.link_score_normalized IS NOT previous.link_score_normalized
|
|
71965
72202
|
)`;
|
|
71966
|
-
var LINK_CHANGE_PREDICATE =
|
|
72203
|
+
var LINK_CHANGE_PREDICATE = sql36`(
|
|
71967
72204
|
current.source_node_key IS NOT previous.source_node_key
|
|
71968
72205
|
OR current.source_url IS NOT previous.source_url
|
|
71969
72206
|
OR current.target_node_key IS NOT previous.target_node_key
|
|
@@ -72040,20 +72277,20 @@ function changedFields(before, after, fields) {
|
|
|
72040
72277
|
}
|
|
72041
72278
|
async function technicalAeoRoutes(app, opts) {
|
|
72042
72279
|
const visibleCrawlFilters = (projectId) => [
|
|
72043
|
-
|
|
72044
|
-
|
|
72045
|
-
|
|
72046
|
-
|
|
72280
|
+
eq100(siteCrawlSnapshots.projectId, projectId),
|
|
72281
|
+
eq100(runs.projectId, projectId),
|
|
72282
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72283
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72047
72284
|
notProbeRun()
|
|
72048
72285
|
];
|
|
72049
72286
|
const availableScanDatesFor = (projectId, requestedDate) => {
|
|
72050
72287
|
if (!requestedDate) return void 0;
|
|
72051
|
-
const date =
|
|
72288
|
+
const date = sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
72052
72289
|
const filters = visibleCrawlFilters(projectId);
|
|
72053
|
-
const allDates =
|
|
72054
|
-
const matchingDates =
|
|
72055
|
-
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72056
|
-
const countDates = (where) => app.db.select({ value:
|
|
72290
|
+
const allDates = and83(...filters);
|
|
72291
|
+
const matchingDates = and83(...filters, eq100(sql36`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
72292
|
+
const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc48(date)).limit(10).all().map((row) => row.date);
|
|
72293
|
+
const countDates = (where) => app.db.select({ value: sql36`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
|
|
72057
72294
|
return {
|
|
72058
72295
|
recentDates: readDates(allDates),
|
|
72059
72296
|
totalDates: countDates(allDates),
|
|
@@ -72069,34 +72306,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72069
72306
|
nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
|
|
72070
72307
|
filters.push(gte27(siteCrawlSnapshots.createdAt, requestedDate), lt14(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
72071
72308
|
}
|
|
72072
|
-
const selectionFilters = scope.scoredOnly ? [...filters,
|
|
72073
|
-
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72074
|
-
|
|
72075
|
-
|
|
72076
|
-
|
|
72077
|
-
|
|
72078
|
-
|
|
72309
|
+
const selectionFilters = scope.scoredOnly ? [...filters, sql36`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
72310
|
+
const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(...selectionFilters, runId ? eq100(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
|
|
72311
|
+
desc48(sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
72312
|
+
desc48(siteCrawlSnapshots.complete),
|
|
72313
|
+
desc48(siteCrawlSnapshots.pagesFetched),
|
|
72314
|
+
desc48(siteCrawlSnapshots.createdAt),
|
|
72315
|
+
desc48(siteCrawlSnapshots.runId)
|
|
72079
72316
|
).limit(1).get();
|
|
72080
72317
|
if (!selected) return void 0;
|
|
72081
72318
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
72082
72319
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
72083
72320
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
72084
|
-
const sameDate =
|
|
72321
|
+
const sameDate = and83(
|
|
72085
72322
|
...filters,
|
|
72086
72323
|
gte27(siteCrawlSnapshots.createdAt, date),
|
|
72087
72324
|
lt14(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
72088
72325
|
);
|
|
72089
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72326
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
72090
72327
|
const candidates = app.db.select({
|
|
72091
72328
|
runId: siteCrawlSnapshots.runId,
|
|
72092
72329
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
72093
72330
|
complete: siteCrawlSnapshots.complete,
|
|
72094
72331
|
pages: siteCrawlSnapshots.pagesFetched
|
|
72095
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72096
|
-
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72332
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
72333
|
+
desc48(siteCrawlSnapshots.complete),
|
|
72334
|
+
desc48(siteCrawlSnapshots.pagesFetched),
|
|
72335
|
+
desc48(siteCrawlSnapshots.createdAt),
|
|
72336
|
+
desc48(siteCrawlSnapshots.runId)
|
|
72100
72337
|
).limit(10).all();
|
|
72101
72338
|
const runSelection = {
|
|
72102
72339
|
reason: runId ? "explicit-run" : scope.scoredOnly ? "latest-scored-scan" : requestedDate ? selected.snapshot.complete ? "requested-date-complete" : "requested-date-most-pages" : selected.snapshot.complete ? "latest-date-complete" : "latest-date-most-pages",
|
|
@@ -72110,20 +72347,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72110
72347
|
};
|
|
72111
72348
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
72112
72349
|
const filters = [
|
|
72113
|
-
|
|
72114
|
-
|
|
72115
|
-
|
|
72116
|
-
|
|
72350
|
+
eq100(siteAuditSnapshots.projectId, projectId),
|
|
72351
|
+
eq100(runs.projectId, projectId),
|
|
72352
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72353
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72117
72354
|
notProbeRun(),
|
|
72118
72355
|
...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
72119
72356
|
];
|
|
72120
|
-
const complete =
|
|
72121
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72122
|
-
|
|
72123
|
-
|
|
72124
|
-
|
|
72125
|
-
|
|
72126
|
-
|
|
72357
|
+
const complete = sql36`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
72358
|
+
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(...filters, pinnedRunId ? eq100(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
|
|
72359
|
+
desc48(sql36`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
72360
|
+
desc48(complete),
|
|
72361
|
+
desc48(siteAuditSnapshots.pagesAudited),
|
|
72362
|
+
desc48(siteAuditSnapshots.createdAt),
|
|
72363
|
+
desc48(siteAuditSnapshots.runId)
|
|
72127
72364
|
).limit(1).get();
|
|
72128
72365
|
const auditOfCrawl = (target) => {
|
|
72129
72366
|
const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
|
|
@@ -72139,22 +72376,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72139
72376
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
72140
72377
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
72141
72378
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
72142
|
-
const sameDate =
|
|
72379
|
+
const sameDate = and83(
|
|
72143
72380
|
...filters,
|
|
72144
72381
|
gte27(siteAuditSnapshots.createdAt, date),
|
|
72145
72382
|
lt14(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
72146
72383
|
);
|
|
72147
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72384
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
72148
72385
|
const candidates = app.db.select({
|
|
72149
72386
|
runId: siteAuditSnapshots.runId,
|
|
72150
72387
|
createdAt: siteAuditSnapshots.createdAt,
|
|
72151
72388
|
complete,
|
|
72152
72389
|
pages: siteAuditSnapshots.pagesAudited
|
|
72153
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
72154
|
-
|
|
72155
|
-
|
|
72156
|
-
|
|
72157
|
-
|
|
72390
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
72391
|
+
desc48(complete),
|
|
72392
|
+
desc48(siteAuditSnapshots.pagesAudited),
|
|
72393
|
+
desc48(siteAuditSnapshots.createdAt),
|
|
72394
|
+
desc48(siteAuditSnapshots.runId)
|
|
72158
72395
|
).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
|
|
72159
72396
|
const runSelection = {
|
|
72160
72397
|
// A crawl exists here only when none stored a scorecard: this score is a fallback.
|
|
@@ -72169,9 +72406,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72169
72406
|
};
|
|
72170
72407
|
const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
|
|
72171
72408
|
const crawlReasonGroups = (where) => {
|
|
72172
|
-
const canonicalOther =
|
|
72409
|
+
const canonicalOther = sql36`case when ${siteCrawlPages.canonicalNodeKey} is not null
|
|
72173
72410
|
and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
|
|
72174
|
-
const canonicalReason =
|
|
72411
|
+
const canonicalReason = sql36`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
|
|
72175
72412
|
where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
|
|
72176
72413
|
const groups = app.db.select({
|
|
72177
72414
|
fetchState: siteCrawlPages.fetchState,
|
|
@@ -72180,7 +72417,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72180
72417
|
canonicalOther,
|
|
72181
72418
|
canonicalReason,
|
|
72182
72419
|
pages: count8(),
|
|
72183
|
-
exampleUrl:
|
|
72420
|
+
exampleUrl: sql36`min(${siteCrawlPages.url})`
|
|
72184
72421
|
}).from(siteCrawlPages).where(where).groupBy(
|
|
72185
72422
|
siteCrawlPages.fetchState,
|
|
72186
72423
|
siteCrawlPages.indexabilityState,
|
|
@@ -72216,10 +72453,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72216
72453
|
};
|
|
72217
72454
|
const inventorySummaryFor = (scope) => {
|
|
72218
72455
|
if (!scope) return null;
|
|
72219
|
-
const groups = crawlReasonGroups(
|
|
72220
|
-
|
|
72221
|
-
|
|
72222
|
-
|
|
72456
|
+
const groups = crawlReasonGroups(and83(
|
|
72457
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72458
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72459
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId)
|
|
72223
72460
|
));
|
|
72224
72461
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
72225
72462
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
@@ -72239,34 +72476,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72239
72476
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
72240
72477
|
const pageInScope = (scope, selector) => {
|
|
72241
72478
|
if (!selector.nodeKey && !selector.url) return null;
|
|
72242
|
-
return app.db.select().from(siteCrawlPages).where(
|
|
72243
|
-
|
|
72244
|
-
|
|
72245
|
-
|
|
72246
|
-
selector.nodeKey ?
|
|
72247
|
-
)).orderBy(
|
|
72248
|
-
};
|
|
72249
|
-
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(
|
|
72250
|
-
|
|
72251
|
-
|
|
72252
|
-
|
|
72253
|
-
|
|
72254
|
-
)).orderBy(
|
|
72255
|
-
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(
|
|
72256
|
-
|
|
72257
|
-
|
|
72258
|
-
|
|
72259
|
-
|
|
72479
|
+
return app.db.select().from(siteCrawlPages).where(and83(
|
|
72480
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72481
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72482
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
72483
|
+
selector.nodeKey ? eq100(siteCrawlPages.nodeKey, selector.nodeKey) : eq100(siteCrawlPages.url, selector.url)
|
|
72484
|
+
)).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
72485
|
+
};
|
|
72486
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and83(
|
|
72487
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72488
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72489
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
72490
|
+
eq100(siteCrawlPages.depth, 0)
|
|
72491
|
+
)).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
72492
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and83(
|
|
72493
|
+
eq100(runs.id, runId),
|
|
72494
|
+
eq100(runs.projectId, projectId),
|
|
72495
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72496
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72260
72497
|
notProbeRun()
|
|
72261
72498
|
)).limit(1).get());
|
|
72262
72499
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
72263
72500
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
72264
72501
|
};
|
|
72265
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72266
|
-
|
|
72267
|
-
|
|
72268
|
-
|
|
72269
|
-
|
|
72502
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
|
|
72503
|
+
eq100(siteAuditSnapshots.projectId, projectId),
|
|
72504
|
+
eq100(runs.projectId, projectId),
|
|
72505
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72506
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72270
72507
|
notProbeRun()
|
|
72271
72508
|
)).limit(1).get());
|
|
72272
72509
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -72367,14 +72604,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72367
72604
|
};
|
|
72368
72605
|
}
|
|
72369
72606
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
72370
|
-
const conds = [
|
|
72371
|
-
if (statusFilter) conds.push(
|
|
72372
|
-
const where =
|
|
72607
|
+
const conds = [eq100(siteAuditPages.projectId, project.id), eq100(siteAuditPages.runId, latest.snap.runId)];
|
|
72608
|
+
if (statusFilter) conds.push(eq100(siteAuditPages.status, statusFilter));
|
|
72609
|
+
const where = and83(...conds);
|
|
72373
72610
|
const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
|
|
72374
72611
|
const total = totalRow?.value ?? 0;
|
|
72375
72612
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
72376
72613
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
72377
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
72614
|
+
const orderBy = request.query.sort === "score-desc" ? desc48(siteAuditPages.overallScore) : request.query.sort === "url" ? asc22(siteAuditPages.url) : asc22(siteAuditPages.overallScore);
|
|
72378
72615
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
72379
72616
|
const pages = rows.map((row) => ({
|
|
72380
72617
|
url: row.url,
|
|
@@ -72393,13 +72630,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72393
72630
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
72394
72631
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
72395
72632
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
72396
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
72397
|
-
|
|
72398
|
-
|
|
72399
|
-
|
|
72400
|
-
|
|
72633
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
|
|
72634
|
+
eq100(siteAuditSnapshots.projectId, project.id),
|
|
72635
|
+
eq100(runs.projectId, project.id),
|
|
72636
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72637
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72401
72638
|
notProbeRun()
|
|
72402
|
-
)).orderBy(
|
|
72639
|
+
)).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
72403
72640
|
return { project: project.name, points: rows.reverse() };
|
|
72404
72641
|
});
|
|
72405
72642
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -72499,10 +72736,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72499
72736
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
72500
72737
|
snapshot.rootUrl
|
|
72501
72738
|
)?.nodeKey ?? null;
|
|
72502
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
72503
|
-
|
|
72504
|
-
|
|
72505
|
-
|
|
72739
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and83(
|
|
72740
|
+
eq100(siteCrawlGraphLayouts.projectId, project.id),
|
|
72741
|
+
eq100(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
72742
|
+
eq100(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
72506
72743
|
)).limit(1).get();
|
|
72507
72744
|
if (!persistedLayout) {
|
|
72508
72745
|
return {
|
|
@@ -72555,9 +72792,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72555
72792
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
72556
72793
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
72557
72794
|
const graphScope = [
|
|
72558
|
-
|
|
72559
|
-
|
|
72560
|
-
|
|
72795
|
+
eq100(siteCrawlGraphNodes.projectId, project.id),
|
|
72796
|
+
eq100(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
72797
|
+
eq100(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
72561
72798
|
];
|
|
72562
72799
|
const nodeRows = app.db.select({
|
|
72563
72800
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -72576,18 +72813,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72576
72813
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
72577
72814
|
x: siteCrawlGraphNodes.x,
|
|
72578
72815
|
y: siteCrawlGraphNodes.y
|
|
72579
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
72580
|
-
|
|
72581
|
-
|
|
72582
|
-
|
|
72583
|
-
|
|
72584
|
-
)).where(
|
|
72816
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and83(
|
|
72817
|
+
eq100(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
72818
|
+
eq100(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
72819
|
+
eq100(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
72820
|
+
eq100(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
72821
|
+
)).where(and83(...graphScope, lt14(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc22(siteCrawlGraphNodes.sampleRank)).all();
|
|
72585
72822
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
72586
72823
|
...row,
|
|
72587
72824
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
72588
72825
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
72589
72826
|
}));
|
|
72590
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
72827
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq100(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
72591
72828
|
const edges = app.db.select({
|
|
72592
72829
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
72593
72830
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -72595,25 +72832,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72595
72832
|
followable: siteCrawlGraphEdges.followable,
|
|
72596
72833
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
72597
72834
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
72598
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
72599
|
-
|
|
72600
|
-
|
|
72601
|
-
|
|
72602
|
-
|
|
72835
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and83(
|
|
72836
|
+
eq100(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
72837
|
+
eq100(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
72838
|
+
eq100(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
72839
|
+
eq100(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
72603
72840
|
lt14(graphSourceNode.sampleRank, maxNodes)
|
|
72604
|
-
)).innerJoin(graphTargetNode,
|
|
72605
|
-
|
|
72606
|
-
|
|
72607
|
-
|
|
72608
|
-
|
|
72841
|
+
)).innerJoin(graphTargetNode, and83(
|
|
72842
|
+
eq100(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
72843
|
+
eq100(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
72844
|
+
eq100(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
72845
|
+
eq100(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
72609
72846
|
lt14(graphTargetNode.sampleRank, maxNodes)
|
|
72610
|
-
)).where(
|
|
72611
|
-
|
|
72612
|
-
|
|
72613
|
-
|
|
72847
|
+
)).where(and83(
|
|
72848
|
+
eq100(siteCrawlGraphEdges.projectId, project.id),
|
|
72849
|
+
eq100(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
72850
|
+
eq100(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
72614
72851
|
lt14(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
72615
72852
|
graphLinkKindFilter
|
|
72616
|
-
)).orderBy(
|
|
72853
|
+
)).orderBy(asc22(siteCrawlGraphEdges.sampleRank)).all();
|
|
72617
72854
|
const totalNodes = persistedLayout.totalNodes;
|
|
72618
72855
|
const totalEdges = persistedLayout.totalEdges;
|
|
72619
72856
|
const totalTemplateEdges = persistedLayout.totalTemplateEdges;
|
|
@@ -72705,17 +72942,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72705
72942
|
let frontier = [focus.nodeKey];
|
|
72706
72943
|
let queryTruncated = false;
|
|
72707
72944
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
72708
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
72709
|
-
|
|
72710
|
-
|
|
72711
|
-
|
|
72712
|
-
|
|
72945
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and83(
|
|
72946
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
72947
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
72948
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
72949
|
+
eq100(siteCrawlEdges.internal, true),
|
|
72713
72950
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72714
72951
|
or21(
|
|
72715
|
-
|
|
72716
|
-
|
|
72952
|
+
inArray44(siteCrawlEdges.sourceNodeKey, frontier),
|
|
72953
|
+
inArray44(siteCrawlEdges.targetNodeKey, frontier)
|
|
72717
72954
|
)
|
|
72718
|
-
)).orderBy(
|
|
72955
|
+
)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
72719
72956
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
72720
72957
|
const next = /* @__PURE__ */ new Set();
|
|
72721
72958
|
for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
|
|
@@ -72746,22 +72983,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72746
72983
|
frontier = [...next].sort();
|
|
72747
72984
|
}
|
|
72748
72985
|
const nodeKeys = [...distances.keys()];
|
|
72749
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
72750
|
-
|
|
72751
|
-
|
|
72752
|
-
|
|
72753
|
-
|
|
72986
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
|
|
72987
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72988
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72989
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
72990
|
+
inArray44(siteCrawlPages.nodeKey, nodeKeys)
|
|
72754
72991
|
)).all();
|
|
72755
72992
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
72756
72993
|
const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
|
|
72757
|
-
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(
|
|
72758
|
-
|
|
72759
|
-
|
|
72760
|
-
|
|
72761
|
-
|
|
72994
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and83(
|
|
72995
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
72996
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
72997
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
72998
|
+
eq100(siteCrawlEdges.internal, true),
|
|
72762
72999
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72763
|
-
|
|
72764
|
-
|
|
73000
|
+
inArray44(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
73001
|
+
inArray44(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
72765
73002
|
)).limit(1).get();
|
|
72766
73003
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
72767
73004
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -72854,23 +73091,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72854
73091
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
72855
73092
|
let truncated = false;
|
|
72856
73093
|
for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
|
|
72857
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
72858
|
-
|
|
72859
|
-
|
|
72860
|
-
|
|
72861
|
-
|
|
72862
|
-
|
|
72863
|
-
|
|
73094
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and83(
|
|
73095
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73096
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73097
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73098
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73099
|
+
eq100(siteCrawlEdges.followable, true),
|
|
73100
|
+
eq100(siteCrawlEdges.relation, "anchor"),
|
|
72864
73101
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72865
|
-
|
|
72866
|
-
)).orderBy(
|
|
73102
|
+
inArray44(siteCrawlEdges.sourceNodeKey, frontier)
|
|
73103
|
+
)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
72867
73104
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
72868
73105
|
const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
|
|
72869
|
-
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(
|
|
72870
|
-
|
|
72871
|
-
|
|
72872
|
-
|
|
72873
|
-
|
|
73106
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and83(
|
|
73107
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73108
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73109
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73110
|
+
inArray44(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
72874
73111
|
)).all().map((row) => row.nodeKey));
|
|
72875
73112
|
const next = /* @__PURE__ */ new Set();
|
|
72876
73113
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -72911,11 +73148,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72911
73148
|
pathEdges.unshift(step.edge);
|
|
72912
73149
|
pathKeys.unshift(step.nodeKey);
|
|
72913
73150
|
}
|
|
72914
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
72915
|
-
|
|
72916
|
-
|
|
72917
|
-
|
|
72918
|
-
|
|
73151
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and83(
|
|
73152
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73153
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73154
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73155
|
+
inArray44(siteCrawlPages.nodeKey, pathKeys)
|
|
72919
73156
|
)).all();
|
|
72920
73157
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
72921
73158
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -72943,25 +73180,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72943
73180
|
throw validationError("change must be all, added, removed, or changed");
|
|
72944
73181
|
}
|
|
72945
73182
|
const completeSnapshotFilters = [
|
|
72946
|
-
|
|
72947
|
-
|
|
72948
|
-
|
|
72949
|
-
|
|
72950
|
-
|
|
73183
|
+
eq100(siteCrawlSnapshots.projectId, project.id),
|
|
73184
|
+
eq100(runs.projectId, project.id),
|
|
73185
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73186
|
+
eq100(runs.status, RunStatuses.completed),
|
|
73187
|
+
eq100(siteCrawlSnapshots.complete, true),
|
|
72951
73188
|
notProbeRun()
|
|
72952
73189
|
];
|
|
72953
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
73190
|
+
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(...completeSnapshotFilters)).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get();
|
|
72954
73191
|
if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
|
|
72955
|
-
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
73192
|
+
const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
|
|
72956
73193
|
...completeSnapshotFilters,
|
|
72957
73194
|
or21(
|
|
72958
73195
|
lt14(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
72959
|
-
|
|
72960
|
-
|
|
73196
|
+
and83(
|
|
73197
|
+
eq100(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
72961
73198
|
lt14(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
72962
73199
|
)
|
|
72963
73200
|
)
|
|
72964
|
-
)).orderBy(
|
|
73201
|
+
)).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
72965
73202
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
72966
73203
|
if (!afterTarget) {
|
|
72967
73204
|
const latestCrawl = resolveCrawl(project.id);
|
|
@@ -73045,9 +73282,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73045
73282
|
const summary4 = { pages: zero2(), links: zero2() };
|
|
73046
73283
|
if (scopeFilter !== "links") {
|
|
73047
73284
|
if (includeAdded || includeChanged) {
|
|
73048
|
-
const addedExpression = includeAdded ?
|
|
73049
|
-
const changedExpression = includeChanged ?
|
|
73050
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
73285
|
+
const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73286
|
+
const changedExpression = includeChanged ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73287
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
|
|
73051
73288
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
73052
73289
|
FROM site_crawl_pages AS current
|
|
73053
73290
|
LEFT JOIN site_crawl_pages AS previous
|
|
@@ -73063,7 +73300,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73063
73300
|
summary4.pages.changed = Number(counts2.changed);
|
|
73064
73301
|
}
|
|
73065
73302
|
if (includeRemoved) {
|
|
73066
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
73303
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql36`
|
|
73067
73304
|
SELECT COUNT(*) AS removed
|
|
73068
73305
|
FROM site_crawl_pages AS previous
|
|
73069
73306
|
LEFT JOIN site_crawl_pages AS current
|
|
@@ -73081,9 +73318,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73081
73318
|
}
|
|
73082
73319
|
if (scopeFilter !== "pages") {
|
|
73083
73320
|
if (includeAdded || includeChanged) {
|
|
73084
|
-
const addedExpression = includeAdded ?
|
|
73085
|
-
const changedExpression = includeChanged ?
|
|
73086
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
73321
|
+
const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73322
|
+
const changedExpression = includeChanged ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73323
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
|
|
73087
73324
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
73088
73325
|
FROM site_crawl_edges AS current
|
|
73089
73326
|
LEFT JOIN site_crawl_edges AS previous
|
|
@@ -73101,7 +73338,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73101
73338
|
summary4.links.changed = Number(counts2.changed);
|
|
73102
73339
|
}
|
|
73103
73340
|
if (includeRemoved) {
|
|
73104
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
73341
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql36`
|
|
73105
73342
|
SELECT COUNT(*) AS removed
|
|
73106
73343
|
FROM site_crawl_edges AS previous
|
|
73107
73344
|
LEFT JOIN site_crawl_edges AS current
|
|
@@ -73131,48 +73368,48 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73131
73368
|
const includeRemoved = changeFilter === "all" || changeFilter === "removed";
|
|
73132
73369
|
const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
|
|
73133
73370
|
const keyColumn = entity === "page" ? "node_key" : "edge_key";
|
|
73134
|
-
const internalCurrent = entity === "link" ?
|
|
73135
|
-
const internalPreviousJoin = entity === "link" ?
|
|
73136
|
-
const internalPrevious = entity === "link" ?
|
|
73137
|
-
const internalCurrentJoin = entity === "link" ?
|
|
73371
|
+
const internalCurrent = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
|
|
73372
|
+
const internalPreviousJoin = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
|
|
73373
|
+
const internalPrevious = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
|
|
73374
|
+
const internalCurrentJoin = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
|
|
73138
73375
|
const difference2 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
|
|
73139
|
-
const afterKind = changeFilter === "added" ?
|
|
73140
|
-
const afterRows = includeAfter ? app.db.all(
|
|
73376
|
+
const afterKind = changeFilter === "added" ? sql36`previous.id IS NULL` : changeFilter === "changed" ? sql36`previous.id IS NOT NULL AND ${difference2}` : sql36`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
|
|
73377
|
+
const afterRows = includeAfter ? app.db.all(sql36`
|
|
73141
73378
|
SELECT ${entity} AS entity,
|
|
73142
73379
|
CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
|
|
73143
|
-
current.${
|
|
73144
|
-
FROM ${
|
|
73145
|
-
LEFT JOIN ${
|
|
73380
|
+
current.${sql36.raw(keyColumn)} AS key
|
|
73381
|
+
FROM ${sql36.raw(table)} AS current
|
|
73382
|
+
LEFT JOIN ${sql36.raw(table)} AS previous
|
|
73146
73383
|
ON previous.project_id = ${beforeScope.projectId}
|
|
73147
73384
|
AND previous.run_id = ${beforeScope.runId}
|
|
73148
73385
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
73149
73386
|
${internalPreviousJoin}
|
|
73150
|
-
AND previous.${
|
|
73387
|
+
AND previous.${sql36.raw(keyColumn)} = current.${sql36.raw(keyColumn)}
|
|
73151
73388
|
WHERE current.project_id = ${afterScope.projectId}
|
|
73152
73389
|
AND current.run_id = ${afterScope.runId}
|
|
73153
73390
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
73154
73391
|
${internalCurrent}
|
|
73155
|
-
AND current.${
|
|
73392
|
+
AND current.${sql36.raw(keyColumn)} > ${afterKey}
|
|
73156
73393
|
AND (${afterKind})
|
|
73157
|
-
ORDER BY current.${
|
|
73394
|
+
ORDER BY current.${sql36.raw(keyColumn)} ASC
|
|
73158
73395
|
LIMIT ${wanted2}
|
|
73159
73396
|
`) : [];
|
|
73160
|
-
const removedRows = includeRemoved ? app.db.all(
|
|
73161
|
-
SELECT ${entity} AS entity, 'removed' AS change, previous.${
|
|
73162
|
-
FROM ${
|
|
73163
|
-
LEFT JOIN ${
|
|
73397
|
+
const removedRows = includeRemoved ? app.db.all(sql36`
|
|
73398
|
+
SELECT ${entity} AS entity, 'removed' AS change, previous.${sql36.raw(keyColumn)} AS key
|
|
73399
|
+
FROM ${sql36.raw(table)} AS previous
|
|
73400
|
+
LEFT JOIN ${sql36.raw(table)} AS current
|
|
73164
73401
|
ON current.project_id = ${afterScope.projectId}
|
|
73165
73402
|
AND current.run_id = ${afterScope.runId}
|
|
73166
73403
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
73167
73404
|
${internalCurrentJoin}
|
|
73168
|
-
AND current.${
|
|
73405
|
+
AND current.${sql36.raw(keyColumn)} = previous.${sql36.raw(keyColumn)}
|
|
73169
73406
|
WHERE previous.project_id = ${beforeScope.projectId}
|
|
73170
73407
|
AND previous.run_id = ${beforeScope.runId}
|
|
73171
73408
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
73172
73409
|
${internalPrevious}
|
|
73173
|
-
AND previous.${
|
|
73410
|
+
AND previous.${sql36.raw(keyColumn)} > ${afterKey}
|
|
73174
73411
|
AND current.id IS NULL
|
|
73175
|
-
ORDER BY previous.${
|
|
73412
|
+
ORDER BY previous.${sql36.raw(keyColumn)} ASC
|
|
73176
73413
|
LIMIT ${wanted2}
|
|
73177
73414
|
`) : [];
|
|
73178
73415
|
const merged = [];
|
|
@@ -73202,18 +73439,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73202
73439
|
const visibleKeys = keyRows.slice(0, limit);
|
|
73203
73440
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
73204
73441
|
const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
|
|
73205
|
-
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
73206
|
-
|
|
73207
|
-
|
|
73208
|
-
|
|
73209
|
-
|
|
73442
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
|
|
73443
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73444
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73445
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73446
|
+
inArray44(siteCrawlPages.nodeKey, pageKeys)
|
|
73210
73447
|
)).all();
|
|
73211
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
73212
|
-
|
|
73213
|
-
|
|
73214
|
-
|
|
73215
|
-
|
|
73216
|
-
|
|
73448
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and83(
|
|
73449
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73450
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73451
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73452
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73453
|
+
inArray44(siteCrawlEdges.edgeKey, linkKeys)
|
|
73217
73454
|
)).all();
|
|
73218
73455
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
73219
73456
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -73386,16 +73623,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73386
73623
|
}
|
|
73387
73624
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
73388
73625
|
const filters = [
|
|
73389
|
-
|
|
73390
|
-
|
|
73391
|
-
|
|
73626
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73627
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73628
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
73392
73629
|
];
|
|
73393
|
-
if (request.query.nodeKey) filters.push(
|
|
73630
|
+
if (request.query.nodeKey) filters.push(eq100(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
73394
73631
|
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
73395
|
-
if (inventoryEligible != null) filters.push(
|
|
73396
|
-
if (request.query.fetchState) filters.push(
|
|
73397
|
-
if (request.query.indexabilityState) filters.push(
|
|
73398
|
-
if (request.query.auditState) filters.push(
|
|
73632
|
+
if (inventoryEligible != null) filters.push(eq100(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
73633
|
+
if (request.query.fetchState) filters.push(eq100(siteCrawlPages.fetchState, request.query.fetchState));
|
|
73634
|
+
if (request.query.indexabilityState) filters.push(eq100(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
73635
|
+
if (request.query.auditState) filters.push(eq100(siteCrawlPages.auditState, request.query.auditState));
|
|
73399
73636
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
73400
73637
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73401
73638
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
@@ -73413,17 +73650,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73413
73650
|
sort
|
|
73414
73651
|
})).digest("base64url");
|
|
73415
73652
|
const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
|
|
73416
|
-
const orderBy = sort === "score-desc" ? [
|
|
73653
|
+
const orderBy = sort === "score-desc" ? [desc48(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)] : sort === "path" ? [asc22(siteCrawlPages.path), asc22(siteCrawlPages.nodeKey)] : [asc22(siteCrawlPages.url), asc22(siteCrawlPages.nodeKey)];
|
|
73417
73654
|
let healthStateFilter = null;
|
|
73418
73655
|
if (healthState) {
|
|
73419
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
73420
|
-
|
|
73421
|
-
|
|
73422
|
-
|
|
73656
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and83(
|
|
73657
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73658
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73659
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
73423
73660
|
isNull17(siteCrawlPages.healthState)
|
|
73424
73661
|
)).limit(1).get();
|
|
73425
73662
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
73426
|
-
if (healthStateFilter === "applied") filters.push(
|
|
73663
|
+
if (healthStateFilter === "applied") filters.push(eq100(siteCrawlPages.healthState, healthState));
|
|
73427
73664
|
}
|
|
73428
73665
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
73429
73666
|
return {
|
|
@@ -73440,7 +73677,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73440
73677
|
pages: []
|
|
73441
73678
|
};
|
|
73442
73679
|
}
|
|
73443
|
-
const where =
|
|
73680
|
+
const where = and83(...filters);
|
|
73444
73681
|
const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
73445
73682
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
73446
73683
|
const nextOffset = offset + rows.length;
|
|
@@ -73475,10 +73712,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73475
73712
|
url: siteCrawlPages.url,
|
|
73476
73713
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
73477
73714
|
fetchState: siteCrawlPages.fetchState
|
|
73478
|
-
}).from(siteCrawlPages).where(
|
|
73479
|
-
|
|
73480
|
-
|
|
73481
|
-
|
|
73715
|
+
}).from(siteCrawlPages).where(and83(
|
|
73716
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73717
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73718
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
73482
73719
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
73483
73720
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
73484
73721
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -73560,21 +73797,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73560
73797
|
};
|
|
73561
73798
|
}
|
|
73562
73799
|
const filters = [
|
|
73563
|
-
|
|
73564
|
-
|
|
73565
|
-
|
|
73566
|
-
|
|
73800
|
+
eq100(siteCrawlEdges.projectId, project.id),
|
|
73801
|
+
eq100(siteCrawlEdges.runId, snapshot.runId),
|
|
73802
|
+
eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
73803
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73567
73804
|
linkKindFilter(linkKind)
|
|
73568
73805
|
];
|
|
73569
|
-
if (request.query.sourceUrl) filters.push(
|
|
73570
|
-
if (request.query.targetUrl) filters.push(
|
|
73806
|
+
if (request.query.sourceUrl) filters.push(eq100(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
73807
|
+
if (request.query.targetUrl) filters.push(eq100(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
73571
73808
|
const followable = parseBoolean2(request.query.followable);
|
|
73572
|
-
if (followable != null) filters.push(
|
|
73573
|
-
const where =
|
|
73809
|
+
if (followable != null) filters.push(eq100(siteCrawlEdges.followable, followable));
|
|
73810
|
+
const where = and83(...filters);
|
|
73574
73811
|
const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
73575
73812
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73576
73813
|
const offset = decodeCursor(request.query.cursor);
|
|
73577
|
-
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(
|
|
73814
|
+
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
|
|
73578
73815
|
const nextOffset = offset + rows.length;
|
|
73579
73816
|
return {
|
|
73580
73817
|
project: project.name,
|
|
@@ -73631,17 +73868,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73631
73868
|
};
|
|
73632
73869
|
}
|
|
73633
73870
|
const scope = [
|
|
73634
|
-
|
|
73635
|
-
|
|
73636
|
-
|
|
73637
|
-
|
|
73871
|
+
eq100(siteCrawlEdges.projectId, project.id),
|
|
73872
|
+
eq100(siteCrawlEdges.runId, snapshot.runId),
|
|
73873
|
+
eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
73874
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73638
73875
|
linkKindFilter(linkKind)
|
|
73639
73876
|
];
|
|
73640
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
73641
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
73877
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq100(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq100(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq100(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq100(siteCrawlEdges.targetUrl, request.query.url);
|
|
73878
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq100(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq100(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq100(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq100(siteCrawlEdges.sourceUrl, request.query.url);
|
|
73642
73879
|
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
73643
|
-
const inboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
73644
|
-
const outboundRows = app.db.select().from(siteCrawlEdges).where(
|
|
73880
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, inboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
73881
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, outboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
73645
73882
|
return {
|
|
73646
73883
|
project: project.name,
|
|
73647
73884
|
hasCrawlData: true,
|
|
@@ -73675,11 +73912,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73675
73912
|
if (!snapshot.attemptId) {
|
|
73676
73913
|
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
|
|
73677
73914
|
}
|
|
73678
|
-
const where =
|
|
73679
|
-
|
|
73680
|
-
|
|
73681
|
-
|
|
73682
|
-
|
|
73915
|
+
const where = and83(
|
|
73916
|
+
eq100(siteCrawlFindings.projectId, project.id),
|
|
73917
|
+
eq100(siteCrawlFindings.runId, snapshot.runId),
|
|
73918
|
+
eq100(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
73919
|
+
eq100(siteCrawlFindings.findingType, "dead-link"),
|
|
73683
73920
|
// Belt and braces on the client-facing surface: a row without a status
|
|
73684
73921
|
// code is not evidence of a broken link, and this response is where such
|
|
73685
73922
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -73689,12 +73926,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73689
73926
|
// a fabricated broken link shown to a client is not worth the saved
|
|
73690
73927
|
// predicate. Applied to `total` as well as the rows, or the count would
|
|
73691
73928
|
// promise a page the list cannot fill.
|
|
73692
|
-
isNotNull10(
|
|
73929
|
+
isNotNull10(sql36`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
|
|
73693
73930
|
);
|
|
73694
73931
|
const total = app.db.select({ value: count8() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
73695
73932
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73696
73933
|
const offset = decodeCursor(request.query.cursor);
|
|
73697
|
-
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(
|
|
73934
|
+
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc22(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
|
|
73698
73935
|
const nextOffset = offset + rows.length;
|
|
73699
73936
|
const deadLinks = rows.map((row) => ({
|
|
73700
73937
|
findingKey: row.findingKey,
|
|
@@ -73729,19 +73966,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73729
73966
|
createdAt: runs.createdAt,
|
|
73730
73967
|
startedAt: runs.startedAt,
|
|
73731
73968
|
finishedAt: runs.finishedAt
|
|
73732
|
-
}).from(runs).where(
|
|
73733
|
-
|
|
73734
|
-
|
|
73969
|
+
}).from(runs).where(and83(
|
|
73970
|
+
eq100(runs.projectId, project.id),
|
|
73971
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73735
73972
|
notProbeRun()
|
|
73736
|
-
)).orderBy(
|
|
73973
|
+
)).orderBy(desc48(runs.createdAt), desc48(runs.id)).limit(limit).all();
|
|
73737
73974
|
const runIds = rows.map((row) => row.runId);
|
|
73738
|
-
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
73739
|
-
|
|
73740
|
-
|
|
73741
|
-
|
|
73742
|
-
|
|
73975
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
|
|
73976
|
+
eq100(siteCrawlSnapshots.projectId, project.id),
|
|
73977
|
+
eq100(runs.projectId, project.id),
|
|
73978
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73979
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
73743
73980
|
notProbeRun(),
|
|
73744
|
-
|
|
73981
|
+
inArray44(siteCrawlSnapshots.runId, runIds)
|
|
73745
73982
|
)).all().map((row) => row.runId));
|
|
73746
73983
|
return {
|
|
73747
73984
|
project: project.name,
|
|
@@ -73755,21 +73992,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73755
73992
|
});
|
|
73756
73993
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
73757
73994
|
const project = resolveProject(app.db, request.params.name);
|
|
73758
|
-
const run = app.db.select().from(runs).where(
|
|
73759
|
-
|
|
73760
|
-
|
|
73761
|
-
|
|
73995
|
+
const run = app.db.select().from(runs).where(and83(
|
|
73996
|
+
eq100(runs.id, request.params.runId),
|
|
73997
|
+
eq100(runs.projectId, project.id),
|
|
73998
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73762
73999
|
notProbeRun()
|
|
73763
74000
|
)).get();
|
|
73764
74001
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
73765
|
-
const attempt = app.db.select().from(siteCrawlAttempts).where(
|
|
73766
|
-
|
|
73767
|
-
|
|
73768
|
-
)).orderBy(
|
|
73769
|
-
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(
|
|
73770
|
-
|
|
73771
|
-
|
|
73772
|
-
|
|
74002
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and83(
|
|
74003
|
+
eq100(siteCrawlAttempts.projectId, project.id),
|
|
74004
|
+
eq100(siteCrawlAttempts.runId, run.id)
|
|
74005
|
+
)).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
74006
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and83(
|
|
74007
|
+
eq100(siteCrawlGraphLayouts.projectId, project.id),
|
|
74008
|
+
eq100(siteCrawlGraphLayouts.runId, run.id),
|
|
74009
|
+
eq100(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
73773
74010
|
)).get() : void 0;
|
|
73774
74011
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
73775
74012
|
return {
|
|
@@ -73800,19 +74037,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73800
74037
|
};
|
|
73801
74038
|
});
|
|
73802
74039
|
app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
|
|
73803
|
-
const project = tx.select().from(projects).where(
|
|
74040
|
+
const project = tx.select().from(projects).where(eq100(projects.name, request.params.name)).get();
|
|
73804
74041
|
if (!project) throw notFound("Project", request.params.name);
|
|
73805
|
-
const run = tx.select().from(runs).where(
|
|
73806
|
-
|
|
73807
|
-
|
|
73808
|
-
|
|
74042
|
+
const run = tx.select().from(runs).where(and83(
|
|
74043
|
+
eq100(runs.id, request.params.runId),
|
|
74044
|
+
eq100(runs.projectId, project.id),
|
|
74045
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73809
74046
|
notProbeRun()
|
|
73810
74047
|
)).get();
|
|
73811
74048
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
73812
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
73813
|
-
|
|
73814
|
-
|
|
73815
|
-
)).orderBy(
|
|
74049
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and83(
|
|
74050
|
+
eq100(siteCrawlAttempts.projectId, project.id),
|
|
74051
|
+
eq100(siteCrawlAttempts.runId, run.id)
|
|
74052
|
+
)).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
73816
74053
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
73817
74054
|
const base = {
|
|
73818
74055
|
project: project.name,
|
|
@@ -73825,11 +74062,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73825
74062
|
if (!attempt) {
|
|
73826
74063
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
73827
74064
|
}
|
|
73828
|
-
const auditedWhere =
|
|
73829
|
-
|
|
73830
|
-
|
|
73831
|
-
|
|
73832
|
-
|
|
74065
|
+
const auditedWhere = and83(
|
|
74066
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
74067
|
+
eq100(siteCrawlPages.runId, run.id),
|
|
74068
|
+
eq100(siteCrawlPages.attemptId, attempt.id),
|
|
74069
|
+
eq100(siteCrawlPages.auditState, "success")
|
|
73833
74070
|
);
|
|
73834
74071
|
const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
73835
74072
|
if (state !== "collecting") {
|
|
@@ -73840,11 +74077,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73840
74077
|
url: siteCrawlPages.url,
|
|
73841
74078
|
auditScore: siteCrawlPages.auditScore,
|
|
73842
74079
|
auditFields: siteCrawlPages.auditFields
|
|
73843
|
-
}).from(siteCrawlPages).where(
|
|
74080
|
+
}).from(siteCrawlPages).where(and83(
|
|
73844
74081
|
auditedWhere,
|
|
73845
74082
|
isNotNull10(siteCrawlPages.auditScore),
|
|
73846
74083
|
lt14(siteCrawlPages.auditScore, 70)
|
|
73847
|
-
)).orderBy(
|
|
74084
|
+
)).orderBy(asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
73848
74085
|
const examples = candidates.flatMap((candidate) => {
|
|
73849
74086
|
if (candidate.auditScore == null) return [];
|
|
73850
74087
|
const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
|
|
@@ -73876,13 +74113,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73876
74113
|
status: runs.status,
|
|
73877
74114
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
73878
74115
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
73879
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
73880
|
-
|
|
73881
|
-
|
|
73882
|
-
)).where(
|
|
73883
|
-
|
|
73884
|
-
|
|
73885
|
-
|
|
74116
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and83(
|
|
74117
|
+
eq100(siteCrawlRunRequests.projectId, runs.projectId),
|
|
74118
|
+
eq100(siteCrawlRunRequests.runId, runs.id)
|
|
74119
|
+
)).where(and83(
|
|
74120
|
+
eq100(runs.projectId, project.id),
|
|
74121
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
74122
|
+
inArray44(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
73886
74123
|
)).get();
|
|
73887
74124
|
if (existing) {
|
|
73888
74125
|
if (existing.identityKey === identityKey) {
|
|
@@ -73938,7 +74175,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73938
74175
|
// ../api-routes/src/research.ts
|
|
73939
74176
|
import crypto57 from "crypto";
|
|
73940
74177
|
import { z as z7 } from "zod";
|
|
73941
|
-
import { and as
|
|
74178
|
+
import { and as and84, count as count9, desc as desc49, eq as eq101, gte as gte28, lt as lt15, or as or22, sql as sql37 } from "drizzle-orm";
|
|
73942
74179
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
73943
74180
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
73944
74181
|
async function researchRoutes(app, opts) {
|
|
@@ -73957,7 +74194,7 @@ async function researchRoutes(app, opts) {
|
|
|
73957
74194
|
const input = parsed.data;
|
|
73958
74195
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
73959
74196
|
const requestHash2 = directResearchRequestHash(input);
|
|
73960
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
74197
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
73961
74198
|
if (existingReceipt) {
|
|
73962
74199
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
73963
74200
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -73992,7 +74229,7 @@ async function researchRoutes(app, opts) {
|
|
|
73992
74229
|
const initiatedBy = researchPrincipal(request);
|
|
73993
74230
|
const decision = app.db.transaction((tx) => {
|
|
73994
74231
|
if (input.idempotencyKey) {
|
|
73995
|
-
const existing = tx.select().from(researchRuns).where(
|
|
74232
|
+
const existing = tx.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
73996
74233
|
if (existing) {
|
|
73997
74234
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
73998
74235
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -74000,11 +74237,11 @@ async function researchRoutes(app, opts) {
|
|
|
74000
74237
|
}
|
|
74001
74238
|
if (initiatedBy?.limited) {
|
|
74002
74239
|
const { start, end, date } = utcDayBounds(now);
|
|
74003
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
74004
|
-
|
|
74240
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
|
|
74241
|
+
eq101(researchRuns.projectId, project.id),
|
|
74005
74242
|
gte28(researchRuns.createdAt, start),
|
|
74006
74243
|
lt15(researchRuns.createdAt, end),
|
|
74007
|
-
|
|
74244
|
+
sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
74008
74245
|
)).get()?.value ?? 0;
|
|
74009
74246
|
if (used >= viewerDailyRunLimit) {
|
|
74010
74247
|
throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
@@ -74032,15 +74269,15 @@ async function researchRoutes(app, opts) {
|
|
|
74032
74269
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
74033
74270
|
const requestHash2 = researchBatchRequestHash(input);
|
|
74034
74271
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
74035
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
74036
|
-
|
|
74037
|
-
|
|
74272
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and84(
|
|
74273
|
+
eq101(researchRuns.projectId, project.id),
|
|
74274
|
+
eq101(researchRuns.idempotencyKey, receiptKeys[0])
|
|
74038
74275
|
)).get();
|
|
74039
74276
|
if (firstReceipt) {
|
|
74040
74277
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
74041
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
74042
|
-
|
|
74043
|
-
|
|
74278
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and84(
|
|
74279
|
+
eq101(researchRuns.projectId, project.id),
|
|
74280
|
+
eq101(researchRuns.idempotencyKey, key)
|
|
74044
74281
|
)).get());
|
|
74045
74282
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
74046
74283
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -74056,11 +74293,11 @@ async function researchRoutes(app, opts) {
|
|
|
74056
74293
|
const decision = app.db.transaction((tx) => {
|
|
74057
74294
|
if (initiatedBy?.limited) {
|
|
74058
74295
|
const { start, end, date } = utcDayBounds(now);
|
|
74059
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
74060
|
-
|
|
74296
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
|
|
74297
|
+
eq101(researchRuns.projectId, project.id),
|
|
74061
74298
|
gte28(researchRuns.createdAt, start),
|
|
74062
74299
|
lt15(researchRuns.createdAt, end),
|
|
74063
|
-
|
|
74300
|
+
sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
74064
74301
|
)).get()?.value ?? 0;
|
|
74065
74302
|
if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
74066
74303
|
}
|
|
@@ -74111,10 +74348,10 @@ async function researchRoutes(app, opts) {
|
|
|
74111
74348
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
74112
74349
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
74113
74350
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
74114
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
74115
|
-
|
|
74116
|
-
cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt),
|
|
74117
|
-
)).orderBy(
|
|
74351
|
+
const rows = app.db.select().from(researchRuns).where(and84(
|
|
74352
|
+
eq101(researchRuns.projectId, project.id),
|
|
74353
|
+
cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and84(eq101(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
|
|
74354
|
+
)).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
|
|
74118
74355
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
74119
74356
|
const last = runs2.at(-1);
|
|
74120
74357
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -74263,9 +74500,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
74263
74500
|
return stored;
|
|
74264
74501
|
}
|
|
74265
74502
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
74266
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
74267
|
-
|
|
74268
|
-
|
|
74503
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and84(
|
|
74504
|
+
eq101(measurementQueryTemplates.projectId, projectId),
|
|
74505
|
+
eq101(measurementQueryTemplates.id, selection.templateId)
|
|
74269
74506
|
)).get();
|
|
74270
74507
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
74271
74508
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -74274,7 +74511,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
74274
74511
|
}
|
|
74275
74512
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
74276
74513
|
if (bindingLocation) {
|
|
74277
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
74514
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq101(projects.id, projectId)).get();
|
|
74278
74515
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
74279
74516
|
}
|
|
74280
74517
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -74287,9 +74524,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
74287
74524
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
74288
74525
|
}
|
|
74289
74526
|
function getDetail(app, projectId, id) {
|
|
74290
|
-
const row = app.db.select().from(researchRuns).where(
|
|
74527
|
+
const row = app.db.select().from(researchRuns).where(and84(eq101(researchRuns.id, id), eq101(researchRuns.projectId, projectId))).get();
|
|
74291
74528
|
if (!row) throw notFound("Research run", id);
|
|
74292
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
74529
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq101(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
74293
74530
|
return { ...serializeRun(row), queries: queries2 };
|
|
74294
74531
|
}
|
|
74295
74532
|
function serializeRun(row) {
|
|
@@ -74344,7 +74581,7 @@ function utcDayBounds(now) {
|
|
|
74344
74581
|
|
|
74345
74582
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
74346
74583
|
import crypto58 from "crypto";
|
|
74347
|
-
import { and as
|
|
74584
|
+
import { and as and85, eq as eq102 } from "drizzle-orm";
|
|
74348
74585
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
74349
74586
|
return db.transaction((tx) => {
|
|
74350
74587
|
const run = tx.select({
|
|
@@ -74352,7 +74589,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74352
74589
|
trigger: runs.trigger,
|
|
74353
74590
|
status: runs.status,
|
|
74354
74591
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
74355
|
-
}).from(runs).where(
|
|
74592
|
+
}).from(runs).where(and85(eq102(runs.projectId, input.projectId), eq102(runs.id, input.runId))).get();
|
|
74356
74593
|
if (!run) throw notFound("Run", input.runId);
|
|
74357
74594
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
74358
74595
|
return null;
|
|
@@ -74373,7 +74610,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74373
74610
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
74374
74611
|
}
|
|
74375
74612
|
}
|
|
74376
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
74613
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and85(eq102(simpleMeasurementDefinitions.projectId, input.projectId), eq102(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
74377
74614
|
if (existing) {
|
|
74378
74615
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
74379
74616
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -74399,15 +74636,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74399
74636
|
if (run.status !== RunStatuses.running) {
|
|
74400
74637
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
74401
74638
|
}
|
|
74402
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
74639
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq102(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
74403
74640
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
74404
74641
|
}
|
|
74405
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
74642
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq102(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
74406
74643
|
if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
74407
74644
|
throw validationError("Every captured query must belong to the run project.");
|
|
74408
74645
|
}
|
|
74409
74646
|
if (definition2.competitors !== void 0) {
|
|
74410
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
74647
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq102(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
|
|
74411
74648
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
74412
74649
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
74413
74650
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|