@canonry/canonry 7.66.0 → 7.68.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 +12 -4
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -2
- package/assets/assets/{AgentSetupPage-DXFxOJ1W.js → AgentSetupPage-DyssdZ1J.js} +3 -3
- package/assets/assets/{AuditHistoryPanel-CS8UfTHc.js → AuditHistoryPanel-Bma4Q6p0.js} +1 -1
- package/assets/assets/{BacklinksPage-D5IA2tNY.js → BacklinksPage-C3GoO2Wf.js} +1 -1
- package/assets/assets/{HistoryPage-DsQAUkgR.js → HistoryPage-6iYvYCL7.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-CjI5eB3b.js +4 -0
- package/assets/assets/ProjectPage-GYIgNPxn.js +9 -0
- package/assets/assets/{RunsPage-BgjN1N3t.js → RunsPage-Cw0GTxgx.js} +1 -1
- package/assets/assets/{SettingsPage-BQuqnEvm.js → SettingsPage-Cvg8BvZn.js} +1 -1
- package/assets/assets/SiteHealthSection-vejp2Igj.js +2 -0
- package/assets/assets/ToneBadge-c2bgjqqz.js +60 -0
- package/assets/assets/{TrafficPage-BBqpvwl5.js → TrafficPage-BUb0xr-b.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-DmrtEYs8.js +1 -0
- package/assets/assets/{account-context-wRthfm6K.js → account-context-qhg8esXw.js} +1 -1
- package/assets/assets/{card-Bq64-UB5.js → card-BNthgfAY.js} +1 -1
- package/assets/assets/{extract-error-message-BRDc8As2.js → extract-error-message-DuYEL09s.js} +1 -1
- package/assets/assets/{format-helpers-D5wFtgg2.js → format-helpers-6v-a8B3S.js} +1 -1
- package/assets/assets/index-BUigRGam.js +60 -0
- package/assets/assets/{index-C6U4u18w.css → index-CqNwsqjm.css} +1 -1
- package/assets/assets/{query-client-IopmAOx0.js → query-client-COFiuVJB.js} +1 -1
- package/assets/assets/react-query-ZdpN7cFt.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-d72rX0Uo.js → react-sigma_core.esm.min-EbeNATny.js} +1 -1
- package/assets/assets/service-DGFjEsYR.js +1 -0
- package/assets/index.html +8 -8
- package/dist/{ads-IFKZU7SC.js → ads-AHM5LMVT.js} +3 -3
- package/dist/{agent-3KMENHL3.js → agent-2FSGY7SD.js} +7 -7
- package/dist/{agent-ask-7WVQLLD5.js → agent-ask-GIK7T2HD.js} +3 -3
- package/dist/{agent-conversations-5LR67TBD.js → agent-conversations-JEIFZHT3.js} +3 -3
- package/dist/{agent-memory-GKUCCKVX.js → agent-memory-JC3JJXON.js} +3 -3
- package/dist/{agent-providers-TVCRM3YZ.js → agent-providers-JRL3VZA7.js} +3 -3
- package/dist/{agent-transcript-MUTCOOWG.js → agent-transcript-ZQDNW3IE.js} +3 -3
- package/dist/{analytics-2IXSG5ML.js → analytics-HMYE6EIX.js} +3 -3
- package/dist/{apply-7RIYK4VC.js → apply-F5CHVFC3.js} +3 -3
- package/dist/{backfill-VU3FV6DQ.js → backfill-JZDQKZHY.js} +9 -9
- package/dist/{backlinks-DLSRYEX2.js → backlinks-L7XEHBAP.js} +3 -3
- package/dist/{bing-4CXTZUH4.js → bing-XTMKLVRJ.js} +3 -3
- package/dist/{bootstrap-OOHSBSDQ.js → bootstrap-AEHBYC77.js} +10 -10
- package/dist/{cdp-Y53SRSRV.js → cdp-SSIHU3PU.js} +7 -7
- package/dist/{chunk-6ASEI24F.js → chunk-2D7O5UHJ.js} +1 -1
- package/dist/{chunk-LBRG6RKI.js → chunk-33LH4ZGF.js} +2 -1
- package/dist/{chunk-VGUZPC3I.js → chunk-4KMUJOYL.js} +4 -4
- package/dist/{chunk-2YBEI6LE.js → chunk-5SK27UE7.js} +1 -1
- package/dist/{chunk-JYOMHNRP.js → chunk-6W3ENB2X.js} +758 -467
- package/dist/{chunk-IHOVPHUW.js → chunk-7AXVACK6.js} +2 -2
- package/dist/{chunk-TJZLICMB.js → chunk-7NMCRHQL.js} +3 -3
- package/dist/{chunk-YRX5UOM3.js → chunk-7SGZHGZ2.js} +1 -1
- package/dist/{chunk-JALKQREN.js → chunk-ARIYTYOM.js} +83 -11
- package/dist/{chunk-PFKBQTYD.js → chunk-ARYBII44.js} +1 -1
- package/dist/{chunk-743NYXIK.js → chunk-CZUPNWLH.js} +2 -2
- package/dist/{chunk-EIMXB6UH.js → chunk-FDRZQH3Y.js} +14 -2
- package/dist/{chunk-OSF27DCY.js → chunk-IVBJEVHS.js} +2 -2
- package/dist/{chunk-BCRRAH5C.js → chunk-J4CNNYRQ.js} +1 -1
- package/dist/{chunk-DH5QRKFW.js → chunk-JPVG5LIE.js} +3 -3
- package/dist/{chunk-QRBZBJ4U.js → chunk-K2P2XO6P.js} +1 -1
- package/dist/{chunk-ZRSNMEB3.js → chunk-K54WGCZV.js} +409 -340
- package/dist/{chunk-L7DQYID4.js → chunk-LABVR6K7.js} +4 -4
- package/dist/{chunk-FNI3QSVD.js → chunk-LTMQ4MHF.js} +1 -1
- package/dist/{chunk-EYWC24VC.js → chunk-MVRPFLEL.js} +1 -1
- package/dist/{chunk-CKTA5374.js → chunk-PAVSKSKA.js} +1 -1
- package/dist/{chunk-AVQQRTMD.js → chunk-QXCQSRHL.js} +72 -33
- package/dist/{chunk-K2HDTMRI.js → chunk-R42VEDCB.js} +3 -3
- package/dist/{chunk-H37CDAZX.js → chunk-RNA6ZCM6.js} +2 -2
- package/dist/{chunk-FG7KMVA6.js → chunk-U3EJ5RW7.js} +12 -6
- package/dist/{chunk-DE2UI37M.js → chunk-V6ALEP33.js} +2 -2
- package/dist/{chunk-WNCOBSPN.js → chunk-VPZMSV3Q.js} +254 -9
- package/dist/{chunk-DVYS2RMM.js → chunk-W7PA35VJ.js} +4 -4
- package/dist/{chunk-YJYLFIVT.js → chunk-XMXYUP7X.js} +1 -1
- package/dist/{chunk-J5YZXRD5.js → chunk-Y42G2YKT.js} +8 -8
- package/dist/{chunk-ALQEGGYM.js → chunk-YIIIYNPW.js} +22 -4
- package/dist/{chunk-XC44Q5XR.js → chunk-ZD5G5QEV.js} +1 -1
- package/dist/{chunk-SGZWOUIZ.js → chunk-ZU6X4SN2.js} +5 -5
- package/dist/{chunk-IPF2S5G2.js → chunk-ZUXFHO4T.js} +4 -4
- package/dist/{citations-ZTQIYYDB.js → citations-NIFKIG6Y.js} +3 -3
- package/dist/{cli-catalog-IZJXAHFQ.js → cli-catalog-ONYERYPV.js} +3 -3
- package/dist/{cli-commands-XYM2VMAK.js → cli-commands-LWI2NEWW.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-SIXAPHOM.js → competitor-NSUSTLNW.js} +3 -3
- package/dist/{content-XPS3D3Y6.js → content-W5VYYMAW.js} +3 -3
- package/dist/{dashboard-FMJDM5PA.js → dashboard-NNA3P645.js} +6 -6
- package/dist/{demo-QQKSHTPO.js → demo-5M4YGVV4.js} +1 -1
- package/dist/{demo-server-LQ3QXUAS.js → demo-server-FDR4RKLM.js} +14 -11
- package/dist/{discover-K5HFEU2T.js → discover-VCKMXFOR.js} +3 -3
- package/dist/{discover-eval-6536Z2YN.js → discover-eval-CPVP7EK5.js} +3 -3
- package/dist/{doctor-STNH3PHH.js → doctor-EVCP6PDM.js} +3 -3
- package/dist/{evidence-W2EWKBAV.js → evidence-ZWRGNZWN.js} +3 -3
- package/dist/{export-cmd-EGHMY3M6.js → export-cmd-AM6SIRUW.js} +3 -3
- package/dist/{ga-DVIG3PVW.js → ga-6J67NDMZ.js} +3 -3
- package/dist/{gbp-SNS4XZBO.js → gbp-ERTWK26L.js} +3 -3
- package/dist/{google-KGPHZG6E.js → google-O6VQFK4L.js} +4 -4
- package/dist/{google-marketing-VZR6W3L6.js → google-marketing-OEW2YGUR.js} +1 -1
- package/dist/{health-cmd-2KWO2GOM.js → health-cmd-OPGK37MK.js} +3 -3
- package/dist/{history-ZY27B7JE.js → history-WTU4ETCU.js} +3 -3
- package/dist/index.d.ts +114 -8
- package/dist/index.js +24 -24
- package/dist/{init-5TXIKII2.js → init-WTAVKFGR.js} +11 -11
- package/dist/{insights-MUYEOSOG.js → insights-LPEDLSEV.js} +3 -3
- package/dist/intelligence-service-4OO2VQ2I.js +15 -0
- package/dist/{journeys-YO54IRVC.js → journeys-TWVQNRLT.js} +3 -3
- package/dist/{keys-M4QZGYVJ.js → keys-C6FPREBZ.js} +3 -3
- package/dist/{keyword-MU62MGWT.js → keyword-PBSE65DK.js} +3 -3
- package/dist/{logs-BAQM334Q.js → logs-62RUOAKT.js} +3 -3
- package/dist/{mcp-LYS5P3PB.js → mcp-P4LNENKX.js} +2 -2
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-HVSTZWTW.js → measurement-plan-HKW3IVIE.js} +5 -5
- package/dist/{notify-XSP5JXU3.js → notify-A3I3G7NK.js} +3 -3
- package/dist/{organic-evidence-4HYW4PVO.js → organic-evidence-OFUJBDCK.js} +3 -3
- package/dist/{overview-XH72WTZT.js → overview-NSRODN2X.js} +4 -4
- package/dist/{project-UKH7FTQX.js → project-B5G2HEAB.js} +3 -3
- package/dist/{query-RRD5M4QV.js → query-UQHJVYUJ.js} +3 -3
- package/dist/{research-V2IGDUKL.js → research-ZWBJDLJY.js} +3 -3
- package/dist/{results-export-5FC7ZT6O.js → results-export-FOJYT4MK.js} +3 -3
- package/dist/{run-B2A4LMWU.js → run-ZIUQ2TNN.js} +4 -4
- package/dist/{schedule-PLY6CHQT.js → schedule-B5CORKGT.js} +3 -3
- package/dist/{search-ACVK3WH5.js → search-2XCZKP6A.js} +3 -3
- package/dist/{serve-2LTQZPEK.js → serve-3BSLRICO.js} +25 -25
- package/dist/{session-IHXMPETX.js → session-PILSYOTX.js} +12 -12
- package/dist/{settings-ZHBNX2I5.js → settings-Y3LVGZH4.js} +3 -3
- package/dist/{skills-KWRTCXTF.js → skills-6E3RVQAQ.js} +2 -2
- package/dist/{snapshot-GBDCCV6L.js → snapshot-RAV2HCL7.js} +3 -3
- package/dist/{src-NOTQWGYV.js → src-VUD6WO5N.js} +6 -2
- package/dist/{status-FGDDGBMJ.js → status-2WCUV6C2.js} +4 -4
- package/dist/{technical-aeo-AK4QI63Q.js → technical-aeo-2TJEHHBR.js} +3 -3
- package/dist/{telemetry-6QPWYMCR.js → telemetry-LQAD26UJ.js} +4 -4
- package/dist/{tools-PBNTVVN7.js → tools-U6VGYZV2.js} +11 -11
- package/dist/{traffic-AS4PIOQL.js → traffic-3TBXSHPV.js} +5 -5
- package/dist/{users-BNYKF35J.js → users-2HDDA5GT.js} +3 -3
- package/dist/{visibility-stats-TWL3GJMD.js → visibility-stats-HSGT3OL3.js} +3 -3
- package/dist/{wordpress-XBWXAACD.js → wordpress-ALODIEFK.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/ToneBadge-DNva5_AM.js +0 -60
- 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-J4CNNYRQ.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-7SGZHGZ2.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-ARIYTYOM.js";
|
|
158
160
|
import {
|
|
159
161
|
ADS_ACTIVATE_SCOPE,
|
|
160
162
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -366,6 +368,8 @@ import {
|
|
|
366
368
|
SeoAdapterOutcomes,
|
|
367
369
|
SeoAvailability,
|
|
368
370
|
SeoCapabilities,
|
|
371
|
+
SeoMarketRankingSearchStatuses,
|
|
372
|
+
SeoMarketRankingSorts,
|
|
369
373
|
SeoRunStatuses,
|
|
370
374
|
SeoScopeKinds,
|
|
371
375
|
SeoTargetKinds,
|
|
@@ -524,6 +528,7 @@ import {
|
|
|
524
528
|
clusterByCosine,
|
|
525
529
|
coerceSkillManifest,
|
|
526
530
|
compactDateToIso,
|
|
531
|
+
compareMarketRankingsBy,
|
|
527
532
|
compareSemver,
|
|
528
533
|
compareText,
|
|
529
534
|
competitorAliasBlockRequestSchema,
|
|
@@ -567,6 +572,7 @@ import {
|
|
|
567
572
|
conversionTrackingOptionsDtoSchema,
|
|
568
573
|
cosineSimilarity,
|
|
569
574
|
countAdsActivationManifestEntities,
|
|
575
|
+
countMarketRankings,
|
|
570
576
|
countedLeadEventNames,
|
|
571
577
|
countsAsMention,
|
|
572
578
|
countsKeyEvents,
|
|
@@ -605,7 +611,6 @@ import {
|
|
|
605
611
|
effectiveDomains,
|
|
606
612
|
emptyCitationVisibility,
|
|
607
613
|
emptySentimentCounts,
|
|
608
|
-
emptySeoResults,
|
|
609
614
|
escapeLikePattern,
|
|
610
615
|
escapeRegExp,
|
|
611
616
|
evaluateModelPointerExposure,
|
|
@@ -697,6 +702,7 @@ import {
|
|
|
697
702
|
googleOAuthStartResponseSchema,
|
|
698
703
|
gradedReportMonths,
|
|
699
704
|
groupIsoDateRanges,
|
|
705
|
+
groupMarketRankings,
|
|
700
706
|
gscCoverageSnapshotDtoSchema,
|
|
701
707
|
gscCoverageSummaryDtoSchema,
|
|
702
708
|
gscDeindexedRowSchema,
|
|
@@ -771,9 +777,12 @@ import {
|
|
|
771
777
|
makesInstallMeasurable,
|
|
772
778
|
mapWithConcurrency,
|
|
773
779
|
marketPinAliasClaims,
|
|
780
|
+
marketRankingRow,
|
|
774
781
|
matchOperationTemplate,
|
|
775
782
|
matchedAliasKeys,
|
|
776
783
|
matcherMatchesText,
|
|
784
|
+
matchesMarketRankingFilter,
|
|
785
|
+
matchesMarketRankingText,
|
|
777
786
|
matchesMeasurementTargetUrl,
|
|
778
787
|
measurementChangesQuerySchema,
|
|
779
788
|
measurementChangesResponseSchema,
|
|
@@ -1020,6 +1029,7 @@ import {
|
|
|
1020
1029
|
runtimeStateMissing,
|
|
1021
1030
|
schedulableRunKindSchema,
|
|
1022
1031
|
scheduleDtoSchema,
|
|
1032
|
+
scheduleEnabledAt,
|
|
1023
1033
|
scheduleExpectedUpdatedAtSchema,
|
|
1024
1034
|
scheduleUpsertRequestSchema,
|
|
1025
1035
|
scheduleVersionConflict,
|
|
@@ -1045,8 +1055,12 @@ import {
|
|
|
1045
1055
|
seoDomainIntelligenceListSchema,
|
|
1046
1056
|
seoEvidenceQuerySchema,
|
|
1047
1057
|
seoKeywordMetricsListSchema,
|
|
1058
|
+
seoMarketRankingFilterSchema,
|
|
1048
1059
|
seoMarketRankingListSchema,
|
|
1049
1060
|
seoMarketRankingQuerySchema,
|
|
1061
|
+
seoMarketRankingSortSchema,
|
|
1062
|
+
seoMarketRankingSummaryQuerySchema,
|
|
1063
|
+
seoMarketRankingSummarySchema,
|
|
1050
1064
|
seoOperatorTaskSchema,
|
|
1051
1065
|
seoOverviewSchema,
|
|
1052
1066
|
seoProfileInputSchema,
|
|
@@ -1173,7 +1187,7 @@ import {
|
|
|
1173
1187
|
wordpressSchemaDeployResultDtoSchema,
|
|
1174
1188
|
wordpressSchemaStatusResultDtoSchema,
|
|
1175
1189
|
wordpressStatusDtoSchema
|
|
1176
|
-
} from "./chunk-
|
|
1190
|
+
} from "./chunk-VPZMSV3Q.js";
|
|
1177
1191
|
|
|
1178
1192
|
// ../api-routes/src/user-session.ts
|
|
1179
1193
|
import crypto2 from "crypto";
|
|
@@ -7262,9 +7276,9 @@ var SentimentService = class {
|
|
|
7262
7276
|
const estimatedInputTokens = this.options.estimate ? inputs.reduce((sum, input) => sum + this.options.estimate(input), 0) : 0;
|
|
7263
7277
|
const expiresAt = new Date(Date.parse(this.now()) + 15 * 6e4).toISOString();
|
|
7264
7278
|
const previewToken = definitionId && settings.enabled && settings.ready && source.assessments.length ? this.sign({ projectId, selection: frozenSelection, definitionId, fingerprint: sentimentHash(source.assessments), expiresAt }) : null;
|
|
7265
|
-
const
|
|
7279
|
+
const skipped6 = source.runIds.flatMap((runId) => Object.entries(source.skippedByRun?.[runId] ?? {}).map(([reason, count10]) => ({ runId, reason, count: count10 })));
|
|
7266
7280
|
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:
|
|
7281
|
+
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
7282
|
}
|
|
7269
7283
|
/** The receipt carries the first page of attempts; a replay of a long-running job never returns all of them. */
|
|
7270
7284
|
submit(projectId, previewToken, idempotencyKey, actor2) {
|
|
@@ -8419,6 +8433,7 @@ var HEALTH_CHECK_LABELS = {
|
|
|
8419
8433
|
"ga.auth.connection": "GA4 sign-in",
|
|
8420
8434
|
"ga.data.recent-data": "GA4 data stopped",
|
|
8421
8435
|
"gsc.data.recent-data": "Search Console data stopped",
|
|
8436
|
+
"seo.data.recent-refresh": "SEO refresh stalled",
|
|
8422
8437
|
"report.sweeps": "Monthly report sweeps",
|
|
8423
8438
|
"report.models": "Monthly report models",
|
|
8424
8439
|
"report.daily-data": "Monthly report data gaps",
|
|
@@ -8471,7 +8486,11 @@ var HEALTH_FIXED_LINES = {
|
|
|
8471
8486
|
"traffic.source.all-errored-recently": "Traffic syncs are working again.",
|
|
8472
8487
|
"traffic.queue-backlog.remaining": "The traffic backlog is cleared.",
|
|
8473
8488
|
"traffic.worker-version.stale": "The Cloudflare worker is up to date.",
|
|
8474
|
-
"gbp.auth.no-connection": "Business Profile is connected."
|
|
8489
|
+
"gbp.auth.no-connection": "Business Profile is connected.",
|
|
8490
|
+
"seo.data.refresh-failed": "SEO refreshes complete again.",
|
|
8491
|
+
"seo.data.refresh-partial": "SEO refreshes complete again.",
|
|
8492
|
+
"seo.data.overdue": "SEO data is current again.",
|
|
8493
|
+
"seo.data.schedule-invalid": "The SEO refresh schedule can run again."
|
|
8475
8494
|
};
|
|
8476
8495
|
function firstSentence(text, max2 = 180) {
|
|
8477
8496
|
if (!text) return null;
|
|
@@ -15220,7 +15239,37 @@ function runDto(job, counts2) {
|
|
|
15220
15239
|
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
15240
|
}
|
|
15222
15241
|
function resultRows(tasks) {
|
|
15223
|
-
|
|
15242
|
+
const kept = tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed);
|
|
15243
|
+
return {
|
|
15244
|
+
keywordMetrics: kept.flatMap((task) => task.results.keywordMetrics),
|
|
15245
|
+
domainIntelligence: kept.flatMap((task) => task.results.domainIntelligence),
|
|
15246
|
+
rankedKeywords: kept.flatMap((task) => task.results.rankedKeywords),
|
|
15247
|
+
marketRankings: tasks.flatMap(marketRankingRows)
|
|
15248
|
+
};
|
|
15249
|
+
}
|
|
15250
|
+
function marketRankingRows(task) {
|
|
15251
|
+
const stored = task.reconciliation?.outcome === SeoAdapterOutcomes.failed ? [] : task.results.marketRankings ?? [];
|
|
15252
|
+
if (stored.length > 0) return stored.map((row) => ({ ...row, searchStatus: row.searchStatus ?? SeoMarketRankingSearchStatuses.completed }));
|
|
15253
|
+
const { search, target } = task.request;
|
|
15254
|
+
if (task.request.capability !== SeoCapabilities["market-rankings"] || !search || !target) return [];
|
|
15255
|
+
const searchStatus = task.status === SeoRunStatuses.cancelled ? SeoMarketRankingSearchStatuses.cancelled : task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"] ? SeoMarketRankingSearchStatuses.failed : null;
|
|
15256
|
+
if (!searchStatus) return [];
|
|
15257
|
+
return search.markets.map((market) => ({
|
|
15258
|
+
...market.context,
|
|
15259
|
+
searchText: task.request.keywords[0] ?? "",
|
|
15260
|
+
locationLabel: search.locationLabel,
|
|
15261
|
+
target,
|
|
15262
|
+
searchStatus,
|
|
15263
|
+
rankOrganic: null,
|
|
15264
|
+
rankAbsolute: null,
|
|
15265
|
+
url: null,
|
|
15266
|
+
depth: 0,
|
|
15267
|
+
competitors: [],
|
|
15268
|
+
provenance: null
|
|
15269
|
+
}));
|
|
15270
|
+
}
|
|
15271
|
+
function taskSearch(task) {
|
|
15272
|
+
return task.request.capability === SeoCapabilities["market-rankings"] ? { searchText: task.request.keywords[0] ?? null, locationLabel: task.request.search?.locationLabel ?? null } : { searchText: null, locationLabel: null };
|
|
15224
15273
|
}
|
|
15225
15274
|
function compareMarketRankings(a, b) {
|
|
15226
15275
|
return compareText(a.regionLabel ?? "", b.regionLabel ?? "") || compareText(a.marketLabel, b.marketLabel) || compareText(a.searchText, b.searchText) || compareText(a.queryText, b.queryText);
|
|
@@ -15434,14 +15483,20 @@ function budgetBuckets(account, projectId, policy, now) {
|
|
|
15434
15483
|
{ scope: `project-month:${projectId}`, period: now.slice(0, 7), limit: policy.projectMonthlyLimitMicros }
|
|
15435
15484
|
];
|
|
15436
15485
|
}
|
|
15486
|
+
function skippedRefreshReason(profileId, reason) {
|
|
15487
|
+
return `Scheduled SEO refresh for ${profileId ? `profile "${profileId}"` : "this project"} did not start: ${reason}`;
|
|
15488
|
+
}
|
|
15489
|
+
var SKIPPED_REFRESH_PROFILE = /^Scheduled SEO refresh for profile "([^"]+)" did not start: /;
|
|
15490
|
+
function skippedRefreshProfile(error) {
|
|
15491
|
+
return error ? SKIPPED_REFRESH_PROFILE.exec(error)?.[1] ?? null : null;
|
|
15492
|
+
}
|
|
15437
15493
|
function startScheduledSeoRefreshes(db, opts, input) {
|
|
15438
15494
|
const repository = new SeoRepository(db);
|
|
15439
15495
|
const principalId = `schedule:${input.scheduleId}`;
|
|
15440
15496
|
const recordSkipped = (profileId, reason, detail) => {
|
|
15441
15497
|
const runId = crypto17.randomUUID();
|
|
15442
15498
|
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();
|
|
15499
|
+
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
15500
|
return { profileId, status: "skipped", runId, reason, ...detail ? { detail } : {} };
|
|
15446
15501
|
};
|
|
15447
15502
|
const saved = db.select().from(seoProfiles).where(eq32(seoProfiles.projectId, input.projectId)).orderBy(asc6(seoProfiles.id)).all().map(profileDto);
|
|
@@ -15493,8 +15548,8 @@ async function seoRoutes(app, opts) {
|
|
|
15493
15548
|
if (!row) throw notFound("SEO profile", id);
|
|
15494
15549
|
return profileDto(row);
|
|
15495
15550
|
};
|
|
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() : [];
|
|
15551
|
+
const tasks = (id) => currentSeoTasks(app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all());
|
|
15552
|
+
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
15553
|
const runSummary = (row) => runDto(row, taskCounts([row.id]));
|
|
15499
15554
|
const job = (projectId, id) => {
|
|
15500
15555
|
const row = app.db.select().from(seoJobs).where(and24(eq32(seoJobs.projectId, projectId), isNotNull3(seoJobs.runId), eq32(seoJobs.id, id))).get();
|
|
@@ -15645,7 +15700,7 @@ async function seoRoutes(app, opts) {
|
|
|
15645
15700
|
const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
|
|
15646
15701
|
const rows = tasks(row.id);
|
|
15647
15702
|
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) };
|
|
15703
|
+
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
15704
|
});
|
|
15650
15705
|
app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
|
|
15651
15706
|
requireScope(request, SEO_RUN_SCOPE);
|
|
@@ -15680,11 +15735,29 @@ async function seoRoutes(app, opts) {
|
|
|
15680
15735
|
if (!parsed.success) throw validationError("Invalid SEO evidence selection.");
|
|
15681
15736
|
const query = parsed.data;
|
|
15682
15737
|
const row = selected(resolveProject(app.db, request.params.name).id, query, true);
|
|
15683
|
-
const items = (row ? resultRows(tasks(row.id)).marketRankings : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey)).sort(
|
|
15738
|
+
const items = (row ? resultRows(tasks(row.id)).marketRankings.map(marketRankingRow) : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey) && (query.outcome === void 0 || matchesMarketRankingFilter(item2, query.outcome)) && (query.q === void 0 || matchesMarketRankingText(item2, query.q))).sort(compareMarketRankingsBy(query.sort ?? SeoMarketRankingSorts.market));
|
|
15684
15739
|
const limit = query.limit ?? 50;
|
|
15685
15740
|
const offset = query.offset ?? 0;
|
|
15686
15741
|
return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
|
|
15687
15742
|
});
|
|
15743
|
+
app.get("/projects/:name/seo/market-rankings/summary", async (request) => {
|
|
15744
|
+
const parsed = seoMarketRankingSummaryQuerySchema.safeParse(request.query ?? {});
|
|
15745
|
+
if (!parsed.success) throw validationError("Invalid SEO evidence selection.");
|
|
15746
|
+
const query = parsed.data;
|
|
15747
|
+
const row = selected(resolveProject(app.db, request.params.name).id, query, true);
|
|
15748
|
+
const rows = (row ? resultRows(tasks(row.id)).marketRankings.map(marketRankingRow) : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey));
|
|
15749
|
+
return {
|
|
15750
|
+
// The depth each search asked for; a partial search's rows say how far they reached.
|
|
15751
|
+
runId: row?.id ?? null,
|
|
15752
|
+
depth: row?.profile.rankings?.depth ?? null,
|
|
15753
|
+
regionKey: query.regionKey ?? null,
|
|
15754
|
+
marketKey: query.marketKey ?? null,
|
|
15755
|
+
regionLabel: query.regionKey === void 0 ? null : rows[0]?.regionLabel ?? null,
|
|
15756
|
+
marketLabel: query.marketKey === void 0 ? null : rows[0]?.marketLabel ?? null,
|
|
15757
|
+
totals: countMarketRankings(rows),
|
|
15758
|
+
groups: groupMarketRankings(rows, { regionKey: query.regionKey, marketKey: query.marketKey })
|
|
15759
|
+
};
|
|
15760
|
+
});
|
|
15688
15761
|
}
|
|
15689
15762
|
|
|
15690
15763
|
// ../api-routes/src/runs.ts
|
|
@@ -27130,7 +27203,7 @@ async function applyRoutes(app, opts) {
|
|
|
27130
27203
|
timezone: resolvedSchedule.timezone,
|
|
27131
27204
|
nextRunAt,
|
|
27132
27205
|
providers: config.spec.schedule?.providers ?? [],
|
|
27133
|
-
...specEnabled === void 0 ? {} : { enabled: specEnabled },
|
|
27206
|
+
...specEnabled === void 0 ? {} : { enabled: specEnabled, enabledAt: scheduleEnabledAt(existingSched, specEnabled, now) },
|
|
27134
27207
|
updatedAt: nextScheduleUpdatedAt(existingSched.updatedAt, Date.parse(now))
|
|
27135
27208
|
}).where(eq44(schedules.id, existingSched.id)).run();
|
|
27136
27209
|
} else {
|
|
@@ -27144,6 +27217,7 @@ async function applyRoutes(app, opts) {
|
|
|
27144
27217
|
timezone: resolvedSchedule.timezone,
|
|
27145
27218
|
nextRunAt,
|
|
27146
27219
|
enabled: specEnabled ?? true,
|
|
27220
|
+
enabledAt: scheduleEnabledAt(void 0, specEnabled ?? true, now),
|
|
27147
27221
|
providers: config.spec.schedule?.providers ?? [],
|
|
27148
27222
|
createdAt: now,
|
|
27149
27223
|
updatedAt: now
|
|
@@ -33892,6 +33966,7 @@ var SCHEMA_TABLE = {
|
|
|
33892
33966
|
SeoDomainIntelligenceList: seoDomainIntelligenceListSchema,
|
|
33893
33967
|
SeoRankedKeywordList: seoRankedKeywordListSchema,
|
|
33894
33968
|
SeoMarketRankingList: seoMarketRankingListSchema,
|
|
33969
|
+
SeoMarketRankingSummary: seoMarketRankingSummarySchema,
|
|
33895
33970
|
SentimentSummary: sentimentSummarySchema,
|
|
33896
33971
|
SentimentSettings: sentimentSettingsSchema,
|
|
33897
33972
|
SentimentSettingsUpdate: sentimentSettingsUpdateSchema,
|
|
@@ -35124,12 +35199,15 @@ var routeCatalog = [
|
|
|
35124
35199
|
path: "/api/v1/projects/{name}/seo/market-rankings",
|
|
35125
35200
|
summary: "Read stored market rankings",
|
|
35126
35201
|
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
|
|
35202
|
+
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. 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. outcome says where each row landed (a partial search without the site is partly-checked, never not-ranked) and rivalAhead whether a tracked competitor ranks above the site.",
|
|
35128
35203
|
parameters: [
|
|
35129
35204
|
nameParameter,
|
|
35130
35205
|
...seoEvidenceParameters,
|
|
35131
35206
|
{ name: "marketKey", in: "query", description: "Only rows for this market (an Advanced market key or a Simple location label).", schema: stringSchema },
|
|
35132
|
-
{ name: "regionKey", in: "query", description: "Only rows whose market sits under this top-level group.", schema: stringSchema }
|
|
35207
|
+
{ name: "regionKey", in: "query", description: "Only rows whose market sits under this top-level group.", schema: stringSchema },
|
|
35208
|
+
{ name: "outcome", in: "query", description: "Only rows with this outcome. `partly-checked` is a partial search that did not reach the site, so ranks past its depth are unknown; `needs-work` is ranks 11 and deeper, not ranked or partly checked; `rival-ahead` is rows where a tracked competitor ranks above the site.", schema: { type: "string", enum: [...seoMarketRankingFilterSchema.options] } },
|
|
35209
|
+
{ name: "q", in: "query", description: "Only rows whose query, searched text or market label contains this text, ignoring case.", schema: { type: "string", minLength: 1, maxLength: 200 } },
|
|
35210
|
+
{ name: "sort", in: "query", description: "`market` (default): region, market, search text. `rank`: best rank first, then partly checked rows, then rows not in the results with the strongest rival first, then searches with no result. `-rank`: rows not in the results first, then partly checked rows, then the worst rank. `query` / `-query`: by query text.", schema: { type: "string", enum: [...seoMarketRankingSortSchema.options], default: "market" } }
|
|
35133
35211
|
],
|
|
35134
35212
|
responses: {
|
|
35135
35213
|
200: jsonResponse("SEO response.", "SeoMarketRankingList"),
|
|
@@ -35139,6 +35217,27 @@ var routeCatalog = [
|
|
|
35139
35217
|
409: errorResponse("Selection, intent or execution identity changed.")
|
|
35140
35218
|
}
|
|
35141
35219
|
},
|
|
35220
|
+
{
|
|
35221
|
+
method: "get",
|
|
35222
|
+
path: "/api/v1/projects/{name}/seo/market-rankings/summary",
|
|
35223
|
+
summary: "Summarize stored market rankings by outcome",
|
|
35224
|
+
tags: ["seo"],
|
|
35225
|
+
description: "Stored evidence only; never calls a provider. Outcome counts for the selected scope (the whole profile, one region or one market) and for the groups under it: regions for the whole profile, markets within one region, none within one market. Groups come in needs-work order: most ranks 11-20, then most ranks 21 and deeper.",
|
|
35226
|
+
parameters: [
|
|
35227
|
+
nameParameter,
|
|
35228
|
+
{ name: "profileId", in: "query", description: "Exact saved profile. Required when multiple profiles exist.", schema: stringSchema },
|
|
35229
|
+
{ name: "runId", in: "query", description: "Exact historical refresh. Omit for latest saved evidence for the selected profile.", schema: stringSchema },
|
|
35230
|
+
{ name: "marketKey", in: "query", description: "Summarize one market (an Advanced market key or a Simple location label).", schema: stringSchema },
|
|
35231
|
+
{ name: "regionKey", in: "query", description: "Summarize one top-level group and the markets under it.", schema: stringSchema }
|
|
35232
|
+
],
|
|
35233
|
+
responses: {
|
|
35234
|
+
200: jsonResponse("SEO response.", "SeoMarketRankingSummary"),
|
|
35235
|
+
400: errorResponse("Invalid scope or input."),
|
|
35236
|
+
403: errorResponse("Insufficient authority."),
|
|
35237
|
+
404: errorResponse("Project or selected evidence not found."),
|
|
35238
|
+
409: errorResponse("Selection, intent or execution identity changed.")
|
|
35239
|
+
}
|
|
35240
|
+
},
|
|
35142
35241
|
{
|
|
35143
35242
|
method: "put",
|
|
35144
35243
|
path: "/api/v1/projects/{name}/seo/profiles/{id}",
|
|
@@ -43091,6 +43190,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
43091
43190
|
providers: providers ?? [],
|
|
43092
43191
|
sourceId: sourceId ?? null,
|
|
43093
43192
|
enabled: enabledBool,
|
|
43193
|
+
enabledAt: scheduleEnabledAt(existing, enabledBool, now),
|
|
43094
43194
|
updatedAt: now
|
|
43095
43195
|
};
|
|
43096
43196
|
if (existing) {
|
|
@@ -59835,11 +59935,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
59835
59935
|
reportMetaStep(metaWriteOutcome(result.results));
|
|
59836
59936
|
const applied = result.results.filter((r) => r.status === "applied").length;
|
|
59837
59937
|
const manual = result.results.filter((r) => r.status === "manual").length;
|
|
59838
|
-
const
|
|
59938
|
+
const skipped6 = result.results.filter((r) => r.status === "skipped").length;
|
|
59839
59939
|
steps.push({
|
|
59840
59940
|
name: "set-meta",
|
|
59841
59941
|
status: "completed",
|
|
59842
|
-
summary: `${applied} applied, ${manual} manual-assist, ${
|
|
59942
|
+
summary: `${applied} applied, ${manual} manual-assist, ${skipped6} skipped`
|
|
59843
59943
|
});
|
|
59844
59944
|
}
|
|
59845
59945
|
} catch (err) {
|
|
@@ -59869,11 +59969,11 @@ async function wordpressRoutes(app, opts) {
|
|
|
59869
59969
|
reportSchemaStep(schemaDeployOutcome(result.results));
|
|
59870
59970
|
const deployed = result.results.filter((r) => r.status === "deployed").length;
|
|
59871
59971
|
const stripped = result.results.filter((r) => r.status === "stripped").length;
|
|
59872
|
-
const
|
|
59972
|
+
const skipped6 = result.results.filter((r) => r.status === "skipped").length;
|
|
59873
59973
|
steps.push({
|
|
59874
59974
|
name: "schema-deploy",
|
|
59875
59975
|
status: "completed",
|
|
59876
|
-
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${
|
|
59976
|
+
summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped6} skipped`
|
|
59877
59977
|
});
|
|
59878
59978
|
}
|
|
59879
59979
|
} catch (err) {
|
|
@@ -60261,7 +60361,7 @@ async function queryBacklinks(opts) {
|
|
|
60261
60361
|
const reversed = opts.targets.map(reverseDomain);
|
|
60262
60362
|
const targetList = reversed.map(quote).join(", ");
|
|
60263
60363
|
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
|
|
60364
|
+
const sql38 = `
|
|
60265
60365
|
WITH vertices AS (
|
|
60266
60366
|
SELECT * FROM read_csv(
|
|
60267
60367
|
${quote(opts.vertexPath)},
|
|
@@ -60297,7 +60397,7 @@ async function queryBacklinks(opts) {
|
|
|
60297
60397
|
const conn = await instance.connect();
|
|
60298
60398
|
let rows;
|
|
60299
60399
|
try {
|
|
60300
|
-
const reader = await conn.runAndReadAll(
|
|
60400
|
+
const reader = await conn.runAndReadAll(sql38);
|
|
60301
60401
|
rows = reader.getRowObjects();
|
|
60302
60402
|
} finally {
|
|
60303
60403
|
conn.disconnectSync?.();
|
|
@@ -62904,6 +63004,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
|
|
|
62904
63004
|
preset: null,
|
|
62905
63005
|
timezone: "UTC",
|
|
62906
63006
|
enabled: true,
|
|
63007
|
+
enabledAt: now,
|
|
62907
63008
|
providers: [],
|
|
62908
63009
|
sourceId,
|
|
62909
63010
|
createdAt: now,
|
|
@@ -67413,6 +67514,195 @@ var gscRecentDataCheck = {
|
|
|
67413
67514
|
};
|
|
67414
67515
|
var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
|
|
67415
67516
|
|
|
67517
|
+
// ../api-routes/src/doctor/checks/seo-refresh.ts
|
|
67518
|
+
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";
|
|
67519
|
+
var GRACE_DAYS = 2;
|
|
67520
|
+
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
67521
|
+
var FIRING_SPAN_MS = 6e4;
|
|
67522
|
+
var REASON_LIMIT = 300;
|
|
67523
|
+
function skipped2(code, summary3) {
|
|
67524
|
+
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67525
|
+
}
|
|
67526
|
+
function seoSyncRuns(projectId) {
|
|
67527
|
+
return and75(eq89(runs.projectId, projectId), eq89(runs.kind, RunKinds["seo-sync"]));
|
|
67528
|
+
}
|
|
67529
|
+
function scheduleTiming(schedule) {
|
|
67530
|
+
if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
|
|
67531
|
+
const recurrence = calendarRecurrenceSchema.safeParse(schedule.recurrence).data;
|
|
67532
|
+
return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
|
|
67533
|
+
}
|
|
67534
|
+
function latestFiring(ctx, projectId, enabledSince) {
|
|
67535
|
+
const scheduled = and75(seoSyncRuns(projectId), eq89(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
|
|
67536
|
+
const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc42(runs.createdAt)).limit(1).get();
|
|
67537
|
+
if (!newest) return [];
|
|
67538
|
+
const newestMs = Date.parse(newest.createdAt);
|
|
67539
|
+
const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
|
|
67540
|
+
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();
|
|
67541
|
+
}
|
|
67542
|
+
function lastCompleted(ctx, projectId) {
|
|
67543
|
+
const at = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
|
|
67544
|
+
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;
|
|
67545
|
+
}
|
|
67546
|
+
function isIncomplete(run) {
|
|
67547
|
+
return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
|
|
67548
|
+
}
|
|
67549
|
+
function runProfiles(ctx, firing) {
|
|
67550
|
+
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();
|
|
67551
|
+
const byRun = new Map(jobs.flatMap((job) => job.runId ? [[job.runId, job.profileId]] : []));
|
|
67552
|
+
return new Map(firing.map((run) => [run.id, byRun.get(run.id) ?? skippedRefreshProfile(run.error)]));
|
|
67553
|
+
}
|
|
67554
|
+
function unsettledRuns(ctx, projectId, firing, profiles) {
|
|
67555
|
+
const incomplete = firing.filter(isIncomplete);
|
|
67556
|
+
const firedAt = firing[firing.length - 1]?.createdAt;
|
|
67557
|
+
if (incomplete.length === 0 || !firedAt) return incomplete;
|
|
67558
|
+
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));
|
|
67559
|
+
return incomplete.filter((run) => {
|
|
67560
|
+
const profile = profiles.get(run.id) ?? null;
|
|
67561
|
+
return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
|
|
67562
|
+
});
|
|
67563
|
+
}
|
|
67564
|
+
var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
|
|
67565
|
+
function refreshReason(ctx, run) {
|
|
67566
|
+
const stored = parseRunError(run.error);
|
|
67567
|
+
if (stored) return formatRunErrorOneLine(stored);
|
|
67568
|
+
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());
|
|
67569
|
+
const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
|
|
67570
|
+
const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
|
|
67571
|
+
const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
|
|
67572
|
+
const errors = [...new Set(failed.flatMap((task) => task.error ? [task.error] : []))].join(" ");
|
|
67573
|
+
if (tasks.length <= 1) {
|
|
67574
|
+
if (errors) return errors;
|
|
67575
|
+
if (partial > 0) return "Some SEO results could not be collected.";
|
|
67576
|
+
return cancelled > 0 ? "The refresh was cancelled." : "No reason was recorded.";
|
|
67577
|
+
}
|
|
67578
|
+
const unit = tasks.every((task) => task.request.capability === SeoCapabilities["market-rankings"]) ? "searches" : "requests";
|
|
67579
|
+
const counts2 = [
|
|
67580
|
+
...failed.length > 0 ? [`${failed.length} failed`] : [],
|
|
67581
|
+
...partial > 0 ? [`${partial} returned only part of their results`] : [],
|
|
67582
|
+
...cancelled > 0 ? [`${cancelled} did not run`] : []
|
|
67583
|
+
];
|
|
67584
|
+
if (counts2.length === 0) return "No reason was recorded.";
|
|
67585
|
+
return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
|
|
67586
|
+
}
|
|
67587
|
+
function gradeSeoRefresh(ctx, project) {
|
|
67588
|
+
const schedule = ctx.db.select().from(schedules).where(and75(eq89(schedules.projectId, project.id), eq89(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
|
|
67589
|
+
if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
|
|
67590
|
+
if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
|
|
67591
|
+
const name = project.name;
|
|
67592
|
+
const day = (iso) => formatIsoDateInTimeZone(iso, schedule.timezone);
|
|
67593
|
+
const now = Date.now();
|
|
67594
|
+
const timing = scheduleTiming(schedule);
|
|
67595
|
+
const last = lastCompleted(ctx, project.id);
|
|
67596
|
+
const enabledSince = schedule.enabledAt ?? schedule.createdAt;
|
|
67597
|
+
const reference = last && Date.parse(last.at) > Date.parse(enabledSince) ? last.at : enabledSince;
|
|
67598
|
+
const dueAt = timing ? nextRunFromSchedule(timing, new Date(reference)) : null;
|
|
67599
|
+
const deadlineAt = dueAt ? new Date(Date.parse(dueAt) + GRACE_DAYS * DAY_MS5).toISOString() : null;
|
|
67600
|
+
const firing = latestFiring(ctx, project.id, enabledSince);
|
|
67601
|
+
const profiles = runProfiles(ctx, firing);
|
|
67602
|
+
const details = {
|
|
67603
|
+
scheduleId: schedule.id,
|
|
67604
|
+
cronExpr: schedule.cronExpr || null,
|
|
67605
|
+
recurrence: schedule.recurrence ?? null,
|
|
67606
|
+
timezone: schedule.timezone,
|
|
67607
|
+
enabledSince,
|
|
67608
|
+
graceDays: GRACE_DAYS,
|
|
67609
|
+
lastCompletedAt: last?.at ?? null,
|
|
67610
|
+
lastCompletedRunId: last?.id ?? null,
|
|
67611
|
+
dueAt,
|
|
67612
|
+
deadlineAt,
|
|
67613
|
+
latestScheduledRuns: firing.map((run) => ({
|
|
67614
|
+
id: run.id,
|
|
67615
|
+
profileId: profiles.get(run.id) ?? null,
|
|
67616
|
+
status: run.status,
|
|
67617
|
+
createdAt: run.createdAt,
|
|
67618
|
+
reason: isIncomplete(run) ? refreshReason(ctx, run) : null
|
|
67619
|
+
}))
|
|
67620
|
+
};
|
|
67621
|
+
if (!timing || !dueAt || !deadlineAt) {
|
|
67622
|
+
return {
|
|
67623
|
+
status: CheckStatuses.warn,
|
|
67624
|
+
code: "seo.data.schedule-invalid",
|
|
67625
|
+
summary: "The SEO refresh schedule has no time it can run, so no scheduled SEO refresh will start.",
|
|
67626
|
+
remediation: `Set a schedule that can run, for example \`canonry schedule set ${name} --kind seo-sync --cron '0 7 2 * *' --timezone UTC\`.`,
|
|
67627
|
+
details,
|
|
67628
|
+
alert: {
|
|
67629
|
+
label: "SEO schedule invalid",
|
|
67630
|
+
line: "The SEO refresh schedule has no time it can run, so SEO data never refreshes.",
|
|
67631
|
+
next: `Set it again with canonry schedule set ${name} --kind seo-sync.`
|
|
67632
|
+
}
|
|
67633
|
+
};
|
|
67634
|
+
}
|
|
67635
|
+
const nextAt = nextRunFromSchedule(timing, new Date(now));
|
|
67636
|
+
const unsettled = unsettledRuns(ctx, project.id, firing, profiles);
|
|
67637
|
+
const newestFired = firing[firing.length - 1];
|
|
67638
|
+
if (newestFired && unsettled.length > 0) {
|
|
67639
|
+
const failed = unsettled.some((run) => run.status === RunStatuses.failed);
|
|
67640
|
+
const firedOn = day(newestFired.createdAt);
|
|
67641
|
+
const reasons = [...new Set(unsettled.map((run) => refreshReason(ctx, run)))].join(" ");
|
|
67642
|
+
const reason = reasons.length > REASON_LIMIT ? `${truncateUtf16(reasons, REASON_LIMIT)}...` : reasons;
|
|
67643
|
+
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`;
|
|
67644
|
+
const line = firing.length > 1 ? `${outcome2}.` : unsettled[0].error ? reason : `${outcome2}: ${reason}`;
|
|
67645
|
+
const next = nextAt ? ` on ${day(nextAt)}` : "";
|
|
67646
|
+
return {
|
|
67647
|
+
status: CheckStatuses.warn,
|
|
67648
|
+
code: failed ? "seo.data.refresh-failed" : "seo.data.refresh-partial",
|
|
67649
|
+
summary: `${outcome2}. Reason: ${reason} ${last ? `The last completed SEO refresh was on ${day(last.at)}.` : "No SEO refresh has completed yet."}`,
|
|
67650
|
+
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.`,
|
|
67651
|
+
details,
|
|
67652
|
+
alert: {
|
|
67653
|
+
label: failed ? "SEO refresh failed" : "SEO refresh incomplete",
|
|
67654
|
+
line: firstSentence(line) ?? line,
|
|
67655
|
+
next: `Fix the cause, then refresh by hand or wait for the next scheduled refresh${next}.`
|
|
67656
|
+
}
|
|
67657
|
+
};
|
|
67658
|
+
}
|
|
67659
|
+
if (now >= Date.parse(deadlineAt)) {
|
|
67660
|
+
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();
|
|
67661
|
+
const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
|
|
67662
|
+
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.`;
|
|
67663
|
+
return {
|
|
67664
|
+
status: CheckStatuses.warn,
|
|
67665
|
+
code: "seo.data.overdue",
|
|
67666
|
+
summary: `${since} ${missed}${active ? ` A refresh started ${day(active.createdAt)} has not finished, and scheduled refreshes wait for it.` : ""}`,
|
|
67667
|
+
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\`.`,
|
|
67668
|
+
details: { ...details, activeRunId: active?.id ?? null },
|
|
67669
|
+
alert: {
|
|
67670
|
+
label: "SEO refresh overdue",
|
|
67671
|
+
line: since,
|
|
67672
|
+
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.`
|
|
67673
|
+
}
|
|
67674
|
+
};
|
|
67675
|
+
}
|
|
67676
|
+
const pending = Date.parse(dueAt) <= now;
|
|
67677
|
+
if (!last) {
|
|
67678
|
+
return {
|
|
67679
|
+
status: CheckStatuses.ok,
|
|
67680
|
+
code: "seo.data.awaiting-first-refresh",
|
|
67681
|
+
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)}.`,
|
|
67682
|
+
remediation: null,
|
|
67683
|
+
details
|
|
67684
|
+
};
|
|
67685
|
+
}
|
|
67686
|
+
return {
|
|
67687
|
+
status: CheckStatuses.ok,
|
|
67688
|
+
code: "seo.data.fresh",
|
|
67689
|
+
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)}.`}`,
|
|
67690
|
+
remediation: null,
|
|
67691
|
+
details
|
|
67692
|
+
};
|
|
67693
|
+
}
|
|
67694
|
+
var seoRecentRefreshCheck = {
|
|
67695
|
+
id: "seo.data.recent-refresh",
|
|
67696
|
+
category: CheckCategories.integrations,
|
|
67697
|
+
scope: CheckScopes.project,
|
|
67698
|
+
title: "SEO data still refreshing",
|
|
67699
|
+
run: (ctx) => {
|
|
67700
|
+
if (!ctx.project) return skipped2("seo.data.no-project", "Project context required.");
|
|
67701
|
+
return gradeSeoRefresh(ctx, ctx.project);
|
|
67702
|
+
}
|
|
67703
|
+
};
|
|
67704
|
+
var SEO_REFRESH_CHECKS = [seoRecentRefreshCheck];
|
|
67705
|
+
|
|
67416
67706
|
// ../api-routes/src/site-reachability.ts
|
|
67417
67707
|
import dns3 from "dns/promises";
|
|
67418
67708
|
import http3 from "http";
|
|
@@ -67616,7 +67906,7 @@ function probeHostFromDomain(value) {
|
|
|
67616
67906
|
if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
|
|
67617
67907
|
return hostname;
|
|
67618
67908
|
}
|
|
67619
|
-
var
|
|
67909
|
+
var skipped3 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
|
|
67620
67910
|
var siteReachabilityCheck = {
|
|
67621
67911
|
id: SITE_REACHABILITY_CHECK_ID,
|
|
67622
67912
|
category: CheckCategories.integrations,
|
|
@@ -67627,16 +67917,16 @@ var siteReachabilityCheck = {
|
|
|
67627
67917
|
// `canonry doctor --project` must not exit 1 on a single dropped packet.
|
|
67628
67918
|
optIn: true,
|
|
67629
67919
|
run: async (ctx) => {
|
|
67630
|
-
if (!ctx.project) return
|
|
67920
|
+
if (!ctx.project) return skipped3("site.reachability.no-project", "Project context required.");
|
|
67631
67921
|
const host = probeHostFromDomain(ctx.project.canonicalDomain);
|
|
67632
67922
|
if (!host) {
|
|
67633
|
-
return
|
|
67923
|
+
return skipped3("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
|
|
67634
67924
|
}
|
|
67635
67925
|
const url = `https://${host}/`;
|
|
67636
67926
|
const probe = ctx.probeSiteReachability ?? probeSiteReachability;
|
|
67637
67927
|
const result = await probe(url);
|
|
67638
67928
|
if (result.state === "unavailable") {
|
|
67639
|
-
return
|
|
67929
|
+
return skipped3("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
|
|
67640
67930
|
}
|
|
67641
67931
|
if (result.state === "up") {
|
|
67642
67932
|
return {
|
|
@@ -67677,7 +67967,7 @@ var siteReachabilityCheck = {
|
|
|
67677
67967
|
var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
|
|
67678
67968
|
|
|
67679
67969
|
// ../api-routes/src/doctor/checks/api-hosts.ts
|
|
67680
|
-
import { eq as
|
|
67970
|
+
import { eq as eq91 } from "drizzle-orm";
|
|
67681
67971
|
|
|
67682
67972
|
// ../api-routes/src/api-host-probe.ts
|
|
67683
67973
|
import dns4 from "dns/promises";
|
|
@@ -67783,7 +68073,7 @@ async function probeApiHost(host, options = {}) {
|
|
|
67783
68073
|
}
|
|
67784
68074
|
|
|
67785
68075
|
// ../api-routes/src/doctor/checks/integration-sync.ts
|
|
67786
|
-
import { and as
|
|
68076
|
+
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
68077
|
var SYNC_FAILURE_WINDOW = 3;
|
|
67788
68078
|
var SUMMARY_ERROR_LIMIT = 300;
|
|
67789
68079
|
var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
|
|
@@ -67804,7 +68094,7 @@ function gscConnection(ctx, project) {
|
|
|
67804
68094
|
function gbpConnection(ctx, project) {
|
|
67805
68095
|
if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
|
|
67806
68096
|
if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
|
|
67807
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(
|
|
68097
|
+
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
68098
|
return selected ? CONNECTED : { state: "no-selected-locations" };
|
|
67809
68099
|
}
|
|
67810
68100
|
function gaConnection(ctx, project) {
|
|
@@ -67861,7 +68151,7 @@ var SPECS = [
|
|
|
67861
68151
|
causeCheckIds: ["google.auth.connection", "google.auth.property-access", "google.auth.scopes", "gsc.api.reachable"]
|
|
67862
68152
|
}
|
|
67863
68153
|
];
|
|
67864
|
-
function
|
|
68154
|
+
function skipped4(code, summary3) {
|
|
67865
68155
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67866
68156
|
}
|
|
67867
68157
|
function latestErrorText(raw) {
|
|
@@ -67869,16 +68159,16 @@ function latestErrorText(raw) {
|
|
|
67869
68159
|
return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
|
|
67870
68160
|
}
|
|
67871
68161
|
function readSyncRunState(db, projectId, kind, options = {}) {
|
|
67872
|
-
const scope =
|
|
67873
|
-
|
|
67874
|
-
|
|
67875
|
-
...options.sourceId ? [
|
|
68162
|
+
const scope = and76(
|
|
68163
|
+
eq90(runs.projectId, projectId),
|
|
68164
|
+
eq90(runs.kind, kind),
|
|
68165
|
+
...options.sourceId ? [eq90(runs.sourceId, options.sourceId)] : [],
|
|
67876
68166
|
...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
|
|
67877
68167
|
);
|
|
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(
|
|
68168
|
+
const graded = options.since ? and76(scope, gte23(runs.createdAt, options.since)) : scope;
|
|
68169
|
+
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();
|
|
68170
|
+
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();
|
|
68171
|
+
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
68172
|
const failuresSinceSuccess = Number(failureCount?.count ?? 0);
|
|
67883
68173
|
const latest = recent[0];
|
|
67884
68174
|
return {
|
|
@@ -67895,21 +68185,21 @@ function readSyncRunState(db, projectId, kind, options = {}) {
|
|
|
67895
68185
|
function gradeSyncFailures(ctx, spec) {
|
|
67896
68186
|
const code = (suffix) => `${spec.prefix}.sync.${suffix}`;
|
|
67897
68187
|
const project = ctx.project;
|
|
67898
|
-
if (!project) return
|
|
68188
|
+
if (!project) return skipped4(code("no-project"), "Project context required.");
|
|
67899
68189
|
const connection = spec.connection(ctx, project);
|
|
67900
68190
|
if (connection.state === "store-unavailable") {
|
|
67901
|
-
return
|
|
68191
|
+
return skipped4(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
|
|
67902
68192
|
}
|
|
67903
68193
|
if (connection.state === "not-connected") {
|
|
67904
|
-
return
|
|
68194
|
+
return skipped4(code("not-connected"), `${spec.label} is not connected for this project.`);
|
|
67905
68195
|
}
|
|
67906
68196
|
if (connection.state === "no-selected-locations") {
|
|
67907
|
-
return
|
|
68197
|
+
return skipped4(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
|
|
67908
68198
|
}
|
|
67909
68199
|
const nothingToSync = spec.nothingToSyncErrors ?? [];
|
|
67910
68200
|
const state = readSyncRunState(ctx.db, project.id, spec.kind, { since: connection.since, nothingToSyncErrors: nothingToSync });
|
|
67911
68201
|
if (state.latestStatus === null) {
|
|
67912
|
-
return
|
|
68202
|
+
return skipped4(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
|
|
67913
68203
|
}
|
|
67914
68204
|
const latestFailed = state.latestStatus === RunStatuses.failed;
|
|
67915
68205
|
const details = {
|
|
@@ -67965,7 +68255,7 @@ var API_HOST_TABLE = {
|
|
|
67965
68255
|
ads: ["api.ads.openai.com"]
|
|
67966
68256
|
};
|
|
67967
68257
|
var API_HOST_EXCLUDED = { gsc: [new URL(INDEXING_API_BASE).hostname] };
|
|
67968
|
-
function
|
|
68258
|
+
function skipped5(code, summary3) {
|
|
67969
68259
|
return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
|
|
67970
68260
|
}
|
|
67971
68261
|
function connectionFor(prefix, ctx, project) {
|
|
@@ -67975,7 +68265,7 @@ function connectionFor(prefix, ctx, project) {
|
|
|
67975
68265
|
if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
|
|
67976
68266
|
if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
|
|
67977
68267
|
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(
|
|
68268
|
+
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
68269
|
}
|
|
67980
68270
|
function syncCommand(prefix) {
|
|
67981
68271
|
if (prefix === "gsc") return "google sync";
|
|
@@ -67989,9 +68279,9 @@ function syncCommand(prefix) {
|
|
|
67989
68279
|
async function grade2(prefix, ctx) {
|
|
67990
68280
|
const id = `${prefix}.api`;
|
|
67991
68281
|
const checkId = `${id}.reachable`;
|
|
67992
|
-
if (!ctx.project) return
|
|
68282
|
+
if (!ctx.project) return skipped5(`${id}.no-project`, "Project context required.");
|
|
67993
68283
|
const connection = connectionFor(prefix, ctx, ctx.project);
|
|
67994
|
-
if (connection.state !== "connected") return
|
|
68284
|
+
if (connection.state !== "connected") return skipped5(`${id}.not-connected`, `${prefix} is not connected for this project.`);
|
|
67995
68285
|
const probe = ctx.probeApiHost ?? probeApiHost;
|
|
67996
68286
|
const results = await Promise.all(API_HOST_TABLE[prefix].map((host) => probe(host)));
|
|
67997
68287
|
const details = { hosts: results };
|
|
@@ -68020,7 +68310,7 @@ function definition(prefix) {
|
|
|
68020
68310
|
var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
|
|
68021
68311
|
|
|
68022
68312
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
68023
|
-
import { and as
|
|
68313
|
+
import { and as and77, eq as eq92 } from "drizzle-orm";
|
|
68024
68314
|
var RECENT_SYNC_WARN_DAYS = 4;
|
|
68025
68315
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
68026
68316
|
function skippedNoProject3() {
|
|
@@ -68040,7 +68330,7 @@ function storeUnavailable() {
|
|
|
68040
68330
|
};
|
|
68041
68331
|
}
|
|
68042
68332
|
function hasSelectedLocation(ctx, projectId) {
|
|
68043
|
-
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(
|
|
68333
|
+
return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, projectId), eq92(gbpLocations.selected, true))).limit(1).get();
|
|
68044
68334
|
}
|
|
68045
68335
|
async function resolveGbpToken(ctx) {
|
|
68046
68336
|
if (!ctx.project) return { ok: false, output: skippedNoProject3() };
|
|
@@ -68300,7 +68590,7 @@ var recentSyncCheck = {
|
|
|
68300
68590
|
title: "GBP recent sync",
|
|
68301
68591
|
run: (ctx) => {
|
|
68302
68592
|
if (!ctx.project) return skippedNoProject3();
|
|
68303
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
68593
|
+
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
68594
|
if (selected.length === 0) {
|
|
68305
68595
|
return {
|
|
68306
68596
|
status: CheckStatuses.skipped,
|
|
@@ -68360,7 +68650,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
68360
68650
|
);
|
|
68361
68651
|
|
|
68362
68652
|
// ../api-routes/src/doctor/checks/places.ts
|
|
68363
|
-
import { eq as
|
|
68653
|
+
import { eq as eq93 } from "drizzle-orm";
|
|
68364
68654
|
var apiKeyCheck = {
|
|
68365
68655
|
id: "gbp.places.api-key",
|
|
68366
68656
|
category: CheckCategories.auth,
|
|
@@ -68405,7 +68695,7 @@ var apiKeyCheck = {
|
|
|
68405
68695
|
details: { tier: cfg.tier }
|
|
68406
68696
|
};
|
|
68407
68697
|
}
|
|
68408
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
68698
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq93(gbpLocations.projectId, ctx.project.id)).all();
|
|
68409
68699
|
const selected = rows.filter((r) => r.selected);
|
|
68410
68700
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
68411
68701
|
const details = {
|
|
@@ -68438,7 +68728,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
|
|
|
68438
68728
|
);
|
|
68439
68729
|
|
|
68440
68730
|
// ../api-routes/src/doctor/checks/gbp-reviews.ts
|
|
68441
|
-
import { and as
|
|
68731
|
+
import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray41, sql as sql33 } from "drizzle-orm";
|
|
68442
68732
|
var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
|
|
68443
68733
|
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
68734
|
var reviewsAccessCheck = {
|
|
@@ -68466,7 +68756,7 @@ var reviewsAccessCheck = {
|
|
|
68466
68756
|
reviewsAccess: gbpLocations.reviewsAccess,
|
|
68467
68757
|
reviewsAccessReason: gbpLocations.reviewsAccessReason,
|
|
68468
68758
|
reviewsCheckedAt: gbpLocations.reviewsCheckedAt
|
|
68469
|
-
}).from(gbpLocations).where(
|
|
68759
|
+
}).from(gbpLocations).where(and78(eq94(gbpLocations.projectId, project.id), eq94(gbpLocations.selected, true))).all();
|
|
68470
68760
|
if (locations.length === 0) {
|
|
68471
68761
|
return {
|
|
68472
68762
|
status: CheckStatuses.skipped,
|
|
@@ -68486,7 +68776,7 @@ var reviewsAccessCheck = {
|
|
|
68486
68776
|
const places = ctx.getPlacesConfig?.();
|
|
68487
68777
|
const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
|
|
68488
68778
|
const v4Worked = new Set(
|
|
68489
|
-
ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(
|
|
68779
|
+
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
68780
|
);
|
|
68491
68781
|
const coverageOf = (loc) => {
|
|
68492
68782
|
if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
|
|
@@ -68520,7 +68810,7 @@ var reviewsAccessCheck = {
|
|
|
68520
68810
|
none > 0 ? `${none} have no review source` : null
|
|
68521
68811
|
].filter((part) => part !== null);
|
|
68522
68812
|
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(
|
|
68813
|
+
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
68814
|
const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
|
|
68525
68815
|
if (none > 0 && subscribed) {
|
|
68526
68816
|
return {
|
|
@@ -68551,7 +68841,7 @@ var reviewDeliveryCheck = {
|
|
|
68551
68841
|
return { status: CheckStatuses.skipped, code: "gbp.reviews.delivery.no-project", summary: "Project context required.", remediation: null };
|
|
68552
68842
|
}
|
|
68553
68843
|
const since = new Date(Date.now() - UNDELIVERED_ATTENTION_DAYS * 864e5).toISOString();
|
|
68554
|
-
const lastTouched =
|
|
68844
|
+
const lastTouched = sql33`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
|
|
68555
68845
|
const rows = ctx.db.select({
|
|
68556
68846
|
id: reviewWebhookDeliveries.id,
|
|
68557
68847
|
notificationId: reviewWebhookDeliveries.notificationId,
|
|
@@ -68559,11 +68849,11 @@ var reviewDeliveryCheck = {
|
|
|
68559
68849
|
attempts: reviewWebhookDeliveries.attempts,
|
|
68560
68850
|
lastTouched,
|
|
68561
68851
|
payload: reviewWebhookDeliveries.payload
|
|
68562
|
-
}).from(reviewWebhookDeliveries).where(
|
|
68563
|
-
|
|
68564
|
-
|
|
68852
|
+
}).from(reviewWebhookDeliveries).where(and78(
|
|
68853
|
+
eq94(reviewWebhookDeliveries.projectId, ctx.project.id),
|
|
68854
|
+
inArray41(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
|
|
68565
68855
|
gte24(lastTouched, since)
|
|
68566
|
-
)).orderBy(
|
|
68856
|
+
)).orderBy(desc44(lastTouched)).all();
|
|
68567
68857
|
if (rows.length === 0) {
|
|
68568
68858
|
return {
|
|
68569
68859
|
status: CheckStatuses.ok,
|
|
@@ -68572,7 +68862,7 @@ var reviewDeliveryCheck = {
|
|
|
68572
68862
|
remediation: null
|
|
68573
68863
|
};
|
|
68574
68864
|
}
|
|
68575
|
-
const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(
|
|
68865
|
+
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
68866
|
const deliveries = rows.map((row) => {
|
|
68577
68867
|
const review = row.payload.reviews[0];
|
|
68578
68868
|
return {
|
|
@@ -69146,7 +69436,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
69146
69436
|
];
|
|
69147
69437
|
|
|
69148
69438
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
69149
|
-
import { and as
|
|
69439
|
+
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
69440
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
69151
69441
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
69152
69442
|
function isCloudflareDirectPush(source) {
|
|
@@ -69185,8 +69475,8 @@ function skippedNoProject5() {
|
|
|
69185
69475
|
function loadProbes(ctx) {
|
|
69186
69476
|
if (!ctx.project) return [];
|
|
69187
69477
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
69188
|
-
|
|
69189
|
-
|
|
69478
|
+
and79(
|
|
69479
|
+
eq95(trafficSources.projectId, ctx.project.id),
|
|
69190
69480
|
ne16(trafficSources.status, TrafficSourceStatuses.archived)
|
|
69191
69481
|
)
|
|
69192
69482
|
).all();
|
|
@@ -69323,28 +69613,28 @@ var recentDataCheck = {
|
|
|
69323
69613
|
const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
|
|
69324
69614
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
69325
69615
|
const recentCrawlers = Number(
|
|
69326
|
-
ctx.db.select({ total:
|
|
69327
|
-
|
|
69328
|
-
|
|
69329
|
-
|
|
69616
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
69617
|
+
and79(
|
|
69618
|
+
eq95(crawlerEventsHourly.projectId, ctx.project.id),
|
|
69619
|
+
inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
69330
69620
|
gte25(crawlerEventsHourly.tsHour, warnCutoff)
|
|
69331
69621
|
)
|
|
69332
69622
|
).get()?.total ?? 0
|
|
69333
69623
|
);
|
|
69334
69624
|
const recentReferrals = Number(
|
|
69335
|
-
ctx.db.select({ total:
|
|
69336
|
-
|
|
69337
|
-
|
|
69338
|
-
|
|
69625
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
69626
|
+
and79(
|
|
69627
|
+
eq95(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
69628
|
+
inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
69339
69629
|
gte25(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
69340
69630
|
)
|
|
69341
69631
|
).get()?.total ?? 0
|
|
69342
69632
|
);
|
|
69343
69633
|
const recentUserFetches = Number(
|
|
69344
|
-
ctx.db.select({ total:
|
|
69345
|
-
|
|
69346
|
-
|
|
69347
|
-
|
|
69634
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
69635
|
+
and79(
|
|
69636
|
+
eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
69637
|
+
inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
69348
69638
|
gte25(aiUserFetchEventsHourly.tsHour, warnCutoff)
|
|
69349
69639
|
)
|
|
69350
69640
|
).get()?.total ?? 0
|
|
@@ -69367,28 +69657,28 @@ var recentDataCheck = {
|
|
|
69367
69657
|
};
|
|
69368
69658
|
}
|
|
69369
69659
|
const olderCrawlers = Number(
|
|
69370
|
-
ctx.db.select({ total:
|
|
69371
|
-
|
|
69372
|
-
|
|
69373
|
-
|
|
69660
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
69661
|
+
and79(
|
|
69662
|
+
eq95(crawlerEventsHourly.projectId, ctx.project.id),
|
|
69663
|
+
inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
|
|
69374
69664
|
gte25(crawlerEventsHourly.tsHour, failCutoff)
|
|
69375
69665
|
)
|
|
69376
69666
|
).get()?.total ?? 0
|
|
69377
69667
|
);
|
|
69378
69668
|
const olderReferrals = Number(
|
|
69379
|
-
ctx.db.select({ total:
|
|
69380
|
-
|
|
69381
|
-
|
|
69382
|
-
|
|
69669
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
69670
|
+
and79(
|
|
69671
|
+
eq95(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
69672
|
+
inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
|
|
69383
69673
|
gte25(aiReferralEventsHourly.tsHour, failCutoff)
|
|
69384
69674
|
)
|
|
69385
69675
|
).get()?.total ?? 0
|
|
69386
69676
|
);
|
|
69387
69677
|
const olderUserFetches = Number(
|
|
69388
|
-
ctx.db.select({ total:
|
|
69389
|
-
|
|
69390
|
-
|
|
69391
|
-
|
|
69678
|
+
ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
69679
|
+
and79(
|
|
69680
|
+
eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
|
|
69681
|
+
inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
|
|
69392
69682
|
gte25(aiUserFetchEventsHourly.tsHour, failCutoff)
|
|
69393
69683
|
)
|
|
69394
69684
|
).get()?.total ?? 0
|
|
@@ -69453,7 +69743,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
|
|
|
69453
69743
|
function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
69454
69744
|
const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
|
|
69455
69745
|
const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
|
|
69456
|
-
const
|
|
69746
|
+
const skipped6 = results.filter((r) => r.output.status === CheckStatuses.skipped);
|
|
69457
69747
|
const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
|
|
69458
69748
|
const detail = {
|
|
69459
69749
|
sources: results.map((r) => ({
|
|
@@ -69487,7 +69777,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
69487
69777
|
return {
|
|
69488
69778
|
status: CheckStatuses.ok,
|
|
69489
69779
|
code: `traffic.${fallbackId}.ok`,
|
|
69490
|
-
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${
|
|
69780
|
+
summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped6.length > 0 ? ` (${skipped6.length} skipped)` : ""}.`,
|
|
69491
69781
|
details: detail
|
|
69492
69782
|
};
|
|
69493
69783
|
}
|
|
@@ -69495,7 +69785,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
|
|
|
69495
69785
|
status: CheckStatuses.skipped,
|
|
69496
69786
|
code: `traffic.${fallbackId}.all-skipped`,
|
|
69497
69787
|
summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
|
|
69498
|
-
remediation:
|
|
69788
|
+
remediation: skipped6.find((result) => result.output.remediation)?.output.remediation,
|
|
69499
69789
|
details: detail
|
|
69500
69790
|
};
|
|
69501
69791
|
}
|
|
@@ -69714,12 +70004,12 @@ var syncLagCheck = {
|
|
|
69714
70004
|
}
|
|
69715
70005
|
};
|
|
69716
70006
|
function lastQueueStopReason(ctx, sourceId) {
|
|
69717
|
-
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(
|
|
69718
|
-
|
|
69719
|
-
|
|
69720
|
-
|
|
70007
|
+
const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and79(
|
|
70008
|
+
eq95(runs.projectId, ctx.project.id),
|
|
70009
|
+
eq95(runs.kind, RunKinds["traffic-sync"]),
|
|
70010
|
+
eq95(runs.sourceId, sourceId),
|
|
69721
70011
|
isNotNull9(runs.syncSummary)
|
|
69722
|
-
)).orderBy(
|
|
70012
|
+
)).orderBy(desc45(runs.createdAt)).limit(1).get();
|
|
69723
70013
|
return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
|
|
69724
70014
|
}
|
|
69725
70015
|
var QUEUE_LIMIT_STOPS = [
|
|
@@ -70009,7 +70299,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
|
|
|
70009
70299
|
];
|
|
70010
70300
|
var FRESH_SNAPSHOT_WARN_DAYS = 7;
|
|
70011
70301
|
var FRESH_SNAPSHOT_FAIL_DAYS = 30;
|
|
70012
|
-
var
|
|
70302
|
+
var DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
70013
70303
|
function unavailableStatus() {
|
|
70014
70304
|
return {
|
|
70015
70305
|
status: CheckStatuses.skipped,
|
|
@@ -70096,7 +70386,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
|
|
|
70096
70386
|
details: { latestSnapshotAt }
|
|
70097
70387
|
};
|
|
70098
70388
|
}
|
|
70099
|
-
const ageDays2 = (now.getTime() - snapshotMs) /
|
|
70389
|
+
const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS6;
|
|
70100
70390
|
const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays2)) };
|
|
70101
70391
|
if (ageDays2 < 0) {
|
|
70102
70392
|
return {
|
|
@@ -70319,6 +70609,7 @@ var ALL_CHECKS = [
|
|
|
70319
70609
|
...WORDPRESS_PUBLISH_CHECKS,
|
|
70320
70610
|
...GA_AUTH_CHECKS,
|
|
70321
70611
|
...DATA_FRESHNESS_CHECKS,
|
|
70612
|
+
...SEO_REFRESH_CHECKS,
|
|
70322
70613
|
reportSweepsCheck,
|
|
70323
70614
|
reportModelsCheck,
|
|
70324
70615
|
reportDailyDataCheck,
|
|
@@ -70516,7 +70807,7 @@ async function doctorRoutes(app, opts) {
|
|
|
70516
70807
|
}
|
|
70517
70808
|
|
|
70518
70809
|
// ../api-routes/src/status.ts
|
|
70519
|
-
import { and as
|
|
70810
|
+
import { and as and80, desc as desc46, eq as eq96, isNull as isNull15 } from "drizzle-orm";
|
|
70520
70811
|
function ageDays(iso) {
|
|
70521
70812
|
if (!iso) return null;
|
|
70522
70813
|
const ms = Date.now() - Date.parse(iso);
|
|
@@ -70566,7 +70857,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
|
|
|
70566
70857
|
};
|
|
70567
70858
|
}
|
|
70568
70859
|
function latestDate2(db, table, projectId) {
|
|
70569
|
-
const row = db.select({ date: table.date }).from(table).where(
|
|
70860
|
+
const row = db.select({ date: table.date }).from(table).where(eq96(table.projectId, projectId)).orderBy(desc46(table.date)).limit(1).get();
|
|
70570
70861
|
return row?.date ?? null;
|
|
70571
70862
|
}
|
|
70572
70863
|
function projectRows(opts, project) {
|
|
@@ -70581,7 +70872,7 @@ function projectRows(opts, project) {
|
|
|
70581
70872
|
const rows = [];
|
|
70582
70873
|
const gsc = gscConnection(ctx, info);
|
|
70583
70874
|
if (connected(gsc)) {
|
|
70584
|
-
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(
|
|
70875
|
+
const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq96(gscDataWatermarks.projectId, project.id)).limit(1).get();
|
|
70585
70876
|
const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
|
|
70586
70877
|
const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
|
|
70587
70878
|
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 +70885,12 @@ function projectRows(opts, project) {
|
|
|
70594
70885
|
const gbp = gbpConnection(ctx, info);
|
|
70595
70886
|
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
70887
|
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(
|
|
70888
|
+
const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq96(adsConnections.projectId, project.id)).limit(1).get();
|
|
70598
70889
|
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
70890
|
const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
|
|
70600
70891
|
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
70892
|
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(
|
|
70893
|
+
const sources = opts.db.select().from(trafficSources).where(and80(eq96(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
|
|
70603
70894
|
for (const source of sources) {
|
|
70604
70895
|
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
70896
|
const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
|
|
@@ -70611,7 +70902,7 @@ function projectRows(opts, project) {
|
|
|
70611
70902
|
}
|
|
70612
70903
|
function projectStatus(opts, project) {
|
|
70613
70904
|
const integrations = projectRows(opts, project);
|
|
70614
|
-
const healthRow = opts.db.select().from(doctorHealthState).where(
|
|
70905
|
+
const healthRow = opts.db.select().from(doctorHealthState).where(eq96(doctorHealthState.projectId, project.id)).limit(1).get();
|
|
70615
70906
|
const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
|
|
70616
70907
|
const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
|
|
70617
70908
|
const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
|
|
@@ -70631,7 +70922,7 @@ async function statusRoutes(app, opts) {
|
|
|
70631
70922
|
if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
|
|
70632
70923
|
return instanceStatusDtoSchema.parse(result);
|
|
70633
70924
|
}
|
|
70634
|
-
const projectsRows = (scopedId ? opts.db.select().from(projects).where(
|
|
70925
|
+
const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq96(projects.id, scopedId)) : opts.db.select().from(projects)).all();
|
|
70635
70926
|
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
70927
|
for (const project of output.projects) {
|
|
70637
70928
|
const key = project.state === "never-synced" ? "neverSynced" : project.state;
|
|
@@ -70644,7 +70935,7 @@ async function statusRoutes(app, opts) {
|
|
|
70644
70935
|
|
|
70645
70936
|
// ../api-routes/src/discovery/routes.ts
|
|
70646
70937
|
import crypto54 from "crypto";
|
|
70647
|
-
import { and as
|
|
70938
|
+
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
70939
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
70649
70940
|
async function discoveryRoutes(app, opts) {
|
|
70650
70941
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -70678,13 +70969,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
70678
70969
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
70679
70970
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
70680
70971
|
const decision = app.db.transaction((tx) => {
|
|
70681
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
70682
|
-
|
|
70683
|
-
|
|
70972
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and81(
|
|
70973
|
+
eq97(discoverySessions.projectId, project.id),
|
|
70974
|
+
eq97(discoverySessions.icpDescription, icpDescription),
|
|
70684
70975
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
70685
70976
|
// semantics, so a request with a different (or no) buyer must start
|
|
70686
70977
|
// its own session, never adopt another buyer's probes.
|
|
70687
|
-
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) :
|
|
70978
|
+
parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq97(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
70688
70979
|
// Locations are identity too: a different service-area subset seeds
|
|
70689
70980
|
// and probes a different geo, so it must never reuse another geo's
|
|
70690
70981
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -70694,18 +70985,18 @@ async function discoveryRoutes(app, opts) {
|
|
|
70694
70985
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
70695
70986
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
70696
70987
|
// upgrade, never a wrong reuse.
|
|
70697
|
-
locations.length === 0 ? or20(isNull16(discoverySessions.locations),
|
|
70988
|
+
locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq97(discoverySessions.locations, locations)) : eq97(discoverySessions.locations, locations),
|
|
70698
70989
|
// Seed provider set is identity: a different phrasing distribution
|
|
70699
70990
|
// must never reuse another set's session. Null (= Gemini-only
|
|
70700
70991
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
70701
|
-
seedProviders == null ? isNull16(discoverySessions.seedProviders) :
|
|
70702
|
-
|
|
70992
|
+
seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq97(discoverySessions.seedProviders, seedProviders),
|
|
70993
|
+
inArray43(discoverySessions.status, [
|
|
70703
70994
|
DiscoverySessionStatuses.queued,
|
|
70704
70995
|
DiscoverySessionStatuses.seeding,
|
|
70705
70996
|
DiscoverySessionStatuses.probing
|
|
70706
70997
|
]),
|
|
70707
70998
|
gte26(discoverySessions.createdAt, ageFloorIso)
|
|
70708
|
-
)).orderBy(
|
|
70999
|
+
)).orderBy(desc47(discoverySessions.createdAt)).get();
|
|
70709
71000
|
if (existing && existing.runId) {
|
|
70710
71001
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
70711
71002
|
}
|
|
@@ -70774,7 +71065,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70774
71065
|
const project = resolveProject(app.db, request.params.name);
|
|
70775
71066
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
70776
71067
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
70777
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
71068
|
+
const rows = app.db.select().from(discoverySessions).where(eq97(discoverySessions.projectId, project.id)).orderBy(desc47(discoverySessions.createdAt)).limit(limit).all();
|
|
70778
71069
|
return reply.send(rows.map(serializeSession));
|
|
70779
71070
|
}
|
|
70780
71071
|
);
|
|
@@ -70782,11 +71073,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
70782
71073
|
"/projects/:name/discover/sessions/:id",
|
|
70783
71074
|
async (request, reply) => {
|
|
70784
71075
|
const project = resolveProject(app.db, request.params.name);
|
|
70785
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71076
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70786
71077
|
if (!session || session.projectId !== project.id) {
|
|
70787
71078
|
throw notFound("Discovery session", request.params.id);
|
|
70788
71079
|
}
|
|
70789
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71080
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70790
71081
|
const detail = {
|
|
70791
71082
|
...serializeSession(session),
|
|
70792
71083
|
probes: probeRows.map(serializeProbe)
|
|
@@ -70798,7 +71089,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70798
71089
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
70799
71090
|
async (request, reply) => {
|
|
70800
71091
|
const project = resolveProject(app.db, request.params.name);
|
|
70801
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71092
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70802
71093
|
if (!session || session.projectId !== project.id) {
|
|
70803
71094
|
throw notFound("Discovery session", request.params.id);
|
|
70804
71095
|
}
|
|
@@ -70806,7 +71097,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70806
71097
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
70807
71098
|
const applyAnchor = request.query.anchor !== "false";
|
|
70808
71099
|
const provider = session.seedProvider ?? "gemini";
|
|
70809
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71100
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70810
71101
|
const extract = opts.harvestSearchQueries;
|
|
70811
71102
|
const probesWithQueries = probeRows.map((row) => {
|
|
70812
71103
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -70817,7 +71108,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70817
71108
|
return { searchQueries: [] };
|
|
70818
71109
|
}
|
|
70819
71110
|
});
|
|
70820
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
71111
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query);
|
|
70821
71112
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
70822
71113
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
70823
71114
|
effectiveDomains(project)
|
|
@@ -70864,11 +71155,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
70864
71155
|
"/projects/:name/discover/sessions/:id/promote",
|
|
70865
71156
|
async (request, reply) => {
|
|
70866
71157
|
const project = resolveProject(app.db, request.params.name);
|
|
70867
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71158
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70868
71159
|
if (!session || session.projectId !== project.id) {
|
|
70869
71160
|
throw notFound("Discovery session", request.params.id);
|
|
70870
71161
|
}
|
|
70871
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71162
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70872
71163
|
const cited = /* @__PURE__ */ new Set();
|
|
70873
71164
|
const aspirational = /* @__PURE__ */ new Set();
|
|
70874
71165
|
const wasted = /* @__PURE__ */ new Set();
|
|
@@ -70903,7 +71194,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70903
71194
|
);
|
|
70904
71195
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
70905
71196
|
const project = resolveProject(app.db, request.params.name);
|
|
70906
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
71197
|
+
const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
|
|
70907
71198
|
if (!session || session.projectId !== project.id) {
|
|
70908
71199
|
throw notFound("Discovery session", request.params.id);
|
|
70909
71200
|
}
|
|
@@ -70926,7 +71217,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70926
71217
|
const bucketSet = new Set(buckets);
|
|
70927
71218
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
70928
71219
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
70929
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
71220
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
|
|
70930
71221
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
70931
71222
|
for (const probe of probeRows) {
|
|
70932
71223
|
if (!probe.bucket) continue;
|
|
@@ -70934,7 +71225,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
70934
71225
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
70935
71226
|
}
|
|
70936
71227
|
const existingQueries = new Set(
|
|
70937
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
71228
|
+
app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
70938
71229
|
);
|
|
70939
71230
|
const promotedQueries = [];
|
|
70940
71231
|
const skippedQueries = [];
|
|
@@ -71106,21 +71397,21 @@ function planCompetitorPromotion(input) {
|
|
|
71106
71397
|
const preferred = new Set(input.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES);
|
|
71107
71398
|
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
71399
|
const tracked = new Set(input.tracked.map((row) => normalizeCompetitorDomain(row.domain)));
|
|
71109
|
-
const
|
|
71400
|
+
const skipped6 = [];
|
|
71110
71401
|
const candidates = [];
|
|
71111
71402
|
for (const group of eligible) {
|
|
71112
71403
|
const projectDomain = competitorDomainProjectClaim(group.domain, input.project.domains);
|
|
71113
71404
|
const subdomains = [...new Set(group.sources.map((source) => hostOf(source.domain) ?? source.domain))].filter((host) => host !== group.domain);
|
|
71114
71405
|
if (projectDomain) {
|
|
71115
|
-
|
|
71406
|
+
skipped6.push({
|
|
71116
71407
|
...group,
|
|
71117
71408
|
reason: DiscoveryPromoteCompetitorSkipReasons["project-domain"],
|
|
71118
71409
|
message: `${group.domain} overlaps the project domain ${projectDomain}, so every citation of the project would count for it`
|
|
71119
71410
|
});
|
|
71120
71411
|
} else if (tracked.has(group.domain)) {
|
|
71121
|
-
|
|
71412
|
+
skipped6.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
|
|
71122
71413
|
} else if (subdomains.length > 1) {
|
|
71123
|
-
|
|
71414
|
+
skipped6.push({
|
|
71124
71415
|
...group,
|
|
71125
71416
|
reason: DiscoveryPromoteCompetitorSkipReasons["shared-host"],
|
|
71126
71417
|
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 +71425,7 @@ function planCompetitorPromotion(input) {
|
|
|
71134
71425
|
for (const group of candidates) {
|
|
71135
71426
|
const rejections = claimed.get(group.domain);
|
|
71136
71427
|
if (rejections) {
|
|
71137
|
-
|
|
71428
|
+
skipped6.push({
|
|
71138
71429
|
...group,
|
|
71139
71430
|
reason: DiscoveryPromoteCompetitorSkipReasons["claimed-by-alias"],
|
|
71140
71431
|
message: rejections.map((rejection) => `${group.domain} ${describeCompetitorAliasRejection(rejection)}`).join("; ")
|
|
@@ -71143,12 +71434,12 @@ function planCompetitorPromotion(input) {
|
|
|
71143
71434
|
promote.push(group);
|
|
71144
71435
|
}
|
|
71145
71436
|
}
|
|
71146
|
-
return { promote, skipped:
|
|
71437
|
+
return { promote, skipped: skipped6 };
|
|
71147
71438
|
}
|
|
71148
71439
|
|
|
71149
71440
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
71150
71441
|
import crypto55 from "crypto";
|
|
71151
|
-
import { eq as
|
|
71442
|
+
import { eq as eq98 } from "drizzle-orm";
|
|
71152
71443
|
var DEFAULT_MAX_PROBES = 100;
|
|
71153
71444
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
71154
71445
|
function classifyProbeBucket(input) {
|
|
@@ -71234,7 +71525,7 @@ async function executeDiscovery(opts) {
|
|
|
71234
71525
|
status: DiscoverySessionStatuses.seeding,
|
|
71235
71526
|
dedupThreshold,
|
|
71236
71527
|
startedAt
|
|
71237
|
-
}).where(
|
|
71528
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71238
71529
|
const seedResult = await opts.deps.seed({
|
|
71239
71530
|
project: opts.project,
|
|
71240
71531
|
icpDescription: opts.icpDescription,
|
|
@@ -71295,7 +71586,7 @@ async function executeDiscovery(opts) {
|
|
|
71295
71586
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
71296
71587
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
71297
71588
|
warning
|
|
71298
|
-
}).where(
|
|
71589
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71299
71590
|
const probeLocation = opts.locations?.[0];
|
|
71300
71591
|
const probeResults = await mapWithConcurrency(
|
|
71301
71592
|
probedCanonicals,
|
|
@@ -71349,7 +71640,7 @@ async function executeDiscovery(opts) {
|
|
|
71349
71640
|
wastedCount: buckets["wasted-surface"],
|
|
71350
71641
|
competitorMap,
|
|
71351
71642
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71352
|
-
}).where(
|
|
71643
|
+
}).where(eq98(discoverySessions.id, opts.sessionId)).run();
|
|
71353
71644
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
71354
71645
|
return {
|
|
71355
71646
|
buckets,
|
|
@@ -71389,7 +71680,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
71389
71680
|
status: DiscoverySessionStatuses.failed,
|
|
71390
71681
|
error,
|
|
71391
71682
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
71392
|
-
}).where(
|
|
71683
|
+
}).where(eq98(discoverySessions.id, sessionId)).run();
|
|
71393
71684
|
}
|
|
71394
71685
|
function dedupeStrings(input) {
|
|
71395
71686
|
const seen2 = /* @__PURE__ */ new Set();
|
|
@@ -71407,12 +71698,12 @@ function dedupeStrings(input) {
|
|
|
71407
71698
|
|
|
71408
71699
|
// ../api-routes/src/technical-aeo.ts
|
|
71409
71700
|
import crypto56 from "crypto";
|
|
71410
|
-
import { and as
|
|
71701
|
+
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
71702
|
import { alias } from "drizzle-orm/sqlite-core";
|
|
71412
71703
|
|
|
71413
71704
|
// ../api-routes/src/technical-aeo-issues.ts
|
|
71414
71705
|
import { createHash as createHash10 } from "crypto";
|
|
71415
|
-
import { and as
|
|
71706
|
+
import { and as and82, asc as asc21, eq as eq99, gt as gt10, sql as sql35 } from "drizzle-orm";
|
|
71416
71707
|
var ADVISORY_CODES = /* @__PURE__ */ new Set([
|
|
71417
71708
|
"entity-consistency.title.short",
|
|
71418
71709
|
"site.duplicate-title",
|
|
@@ -71557,12 +71848,12 @@ function readSiteAuditIssues(input) {
|
|
|
71557
71848
|
}
|
|
71558
71849
|
}
|
|
71559
71850
|
if (snapshot.detailsAvailable && snapshot.attemptId) {
|
|
71560
|
-
const scope =
|
|
71561
|
-
|
|
71562
|
-
|
|
71563
|
-
|
|
71851
|
+
const scope = and82(
|
|
71852
|
+
eq99(siteCrawlPages.projectId, project.id),
|
|
71853
|
+
eq99(siteCrawlPages.runId, snapshot.runId),
|
|
71854
|
+
eq99(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
71564
71855
|
);
|
|
71565
|
-
const size = db.select({ bytes:
|
|
71856
|
+
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
71857
|
if (size + crossBytes > MAX_EVIDENCE_BYTES) {
|
|
71567
71858
|
throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
|
|
71568
71859
|
}
|
|
@@ -71573,7 +71864,7 @@ function readSiteAuditIssues(input) {
|
|
|
71573
71864
|
url: siteCrawlPages.url,
|
|
71574
71865
|
auditScore: siteCrawlPages.auditScore,
|
|
71575
71866
|
auditFields: siteCrawlPages.auditFields
|
|
71576
|
-
}).from(siteCrawlPages).where(
|
|
71867
|
+
}).from(siteCrawlPages).where(and82(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc21(siteCrawlPages.nodeKey)).limit(200).all();
|
|
71577
71868
|
for (const page3 of pages) {
|
|
71578
71869
|
evidenceHash.update(JSON.stringify(page3));
|
|
71579
71870
|
const fields = record(page3.auditFields);
|
|
@@ -71935,13 +72226,13 @@ function parseLinkKind(value) {
|
|
|
71935
72226
|
return allowed.data;
|
|
71936
72227
|
}
|
|
71937
72228
|
function linkKindFilter(linkKind) {
|
|
71938
|
-
if (linkKind === SiteHealthLinkKinds.content) return
|
|
71939
|
-
if (linkKind === SiteHealthLinkKinds.template) return
|
|
72229
|
+
if (linkKind === SiteHealthLinkKinds.content) return eq100(siteCrawlEdges.isTemplate, false);
|
|
72230
|
+
if (linkKind === SiteHealthLinkKinds.template) return eq100(siteCrawlEdges.isTemplate, true);
|
|
71940
72231
|
return void 0;
|
|
71941
72232
|
}
|
|
71942
72233
|
var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
|
|
71943
72234
|
var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
|
|
71944
|
-
var PAGE_CHANGE_PREDICATE =
|
|
72235
|
+
var PAGE_CHANGE_PREDICATE = sql36`(
|
|
71945
72236
|
current.url IS NOT previous.url
|
|
71946
72237
|
OR current.final_url IS NOT previous.final_url
|
|
71947
72238
|
OR current.path IS NOT previous.path
|
|
@@ -71963,7 +72254,7 @@ var PAGE_CHANGE_PREDICATE = sql35`(
|
|
|
71963
72254
|
OR current.link_score_raw IS NOT previous.link_score_raw
|
|
71964
72255
|
OR current.link_score_normalized IS NOT previous.link_score_normalized
|
|
71965
72256
|
)`;
|
|
71966
|
-
var LINK_CHANGE_PREDICATE =
|
|
72257
|
+
var LINK_CHANGE_PREDICATE = sql36`(
|
|
71967
72258
|
current.source_node_key IS NOT previous.source_node_key
|
|
71968
72259
|
OR current.source_url IS NOT previous.source_url
|
|
71969
72260
|
OR current.target_node_key IS NOT previous.target_node_key
|
|
@@ -72040,20 +72331,20 @@ function changedFields(before, after, fields) {
|
|
|
72040
72331
|
}
|
|
72041
72332
|
async function technicalAeoRoutes(app, opts) {
|
|
72042
72333
|
const visibleCrawlFilters = (projectId) => [
|
|
72043
|
-
|
|
72044
|
-
|
|
72045
|
-
|
|
72046
|
-
|
|
72334
|
+
eq100(siteCrawlSnapshots.projectId, projectId),
|
|
72335
|
+
eq100(runs.projectId, projectId),
|
|
72336
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72337
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72047
72338
|
notProbeRun()
|
|
72048
72339
|
];
|
|
72049
72340
|
const availableScanDatesFor = (projectId, requestedDate) => {
|
|
72050
72341
|
if (!requestedDate) return void 0;
|
|
72051
|
-
const date =
|
|
72342
|
+
const date = sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
|
|
72052
72343
|
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:
|
|
72344
|
+
const allDates = and83(...filters);
|
|
72345
|
+
const matchingDates = and83(...filters, eq100(sql36`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
|
|
72346
|
+
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);
|
|
72347
|
+
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
72348
|
return {
|
|
72058
72349
|
recentDates: readDates(allDates),
|
|
72059
72350
|
totalDates: countDates(allDates),
|
|
@@ -72069,34 +72360,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72069
72360
|
nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
|
|
72070
72361
|
filters.push(gte27(siteCrawlSnapshots.createdAt, requestedDate), lt14(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
|
|
72071
72362
|
}
|
|
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
|
-
|
|
72363
|
+
const selectionFilters = scope.scoredOnly ? [...filters, sql36`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
|
|
72364
|
+
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(
|
|
72365
|
+
desc48(sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
|
|
72366
|
+
desc48(siteCrawlSnapshots.complete),
|
|
72367
|
+
desc48(siteCrawlSnapshots.pagesFetched),
|
|
72368
|
+
desc48(siteCrawlSnapshots.createdAt),
|
|
72369
|
+
desc48(siteCrawlSnapshots.runId)
|
|
72079
72370
|
).limit(1).get();
|
|
72080
72371
|
if (!selected) return void 0;
|
|
72081
72372
|
const date = selected.snapshot.createdAt.slice(0, 10);
|
|
72082
72373
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
72083
72374
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
72084
|
-
const sameDate =
|
|
72375
|
+
const sameDate = and83(
|
|
72085
72376
|
...filters,
|
|
72086
72377
|
gte27(siteCrawlSnapshots.createdAt, date),
|
|
72087
72378
|
lt14(siteCrawlSnapshots.createdAt, nextDate.toISOString())
|
|
72088
72379
|
);
|
|
72089
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72380
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
72090
72381
|
const candidates = app.db.select({
|
|
72091
72382
|
runId: siteCrawlSnapshots.runId,
|
|
72092
72383
|
createdAt: siteCrawlSnapshots.createdAt,
|
|
72093
72384
|
complete: siteCrawlSnapshots.complete,
|
|
72094
72385
|
pages: siteCrawlSnapshots.pagesFetched
|
|
72095
|
-
}).from(siteCrawlSnapshots).innerJoin(runs,
|
|
72096
|
-
|
|
72097
|
-
|
|
72098
|
-
|
|
72099
|
-
|
|
72386
|
+
}).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
72387
|
+
desc48(siteCrawlSnapshots.complete),
|
|
72388
|
+
desc48(siteCrawlSnapshots.pagesFetched),
|
|
72389
|
+
desc48(siteCrawlSnapshots.createdAt),
|
|
72390
|
+
desc48(siteCrawlSnapshots.runId)
|
|
72100
72391
|
).limit(10).all();
|
|
72101
72392
|
const runSelection = {
|
|
72102
72393
|
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 +72401,20 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72110
72401
|
};
|
|
72111
72402
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
72112
72403
|
const filters = [
|
|
72113
|
-
|
|
72114
|
-
|
|
72115
|
-
|
|
72116
|
-
|
|
72404
|
+
eq100(siteAuditSnapshots.projectId, projectId),
|
|
72405
|
+
eq100(runs.projectId, projectId),
|
|
72406
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72407
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72117
72408
|
notProbeRun(),
|
|
72118
72409
|
...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
72119
72410
|
];
|
|
72120
|
-
const complete =
|
|
72121
|
-
const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72122
|
-
|
|
72123
|
-
|
|
72124
|
-
|
|
72125
|
-
|
|
72126
|
-
|
|
72411
|
+
const complete = sql36`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
72412
|
+
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(
|
|
72413
|
+
desc48(sql36`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
|
|
72414
|
+
desc48(complete),
|
|
72415
|
+
desc48(siteAuditSnapshots.pagesAudited),
|
|
72416
|
+
desc48(siteAuditSnapshots.createdAt),
|
|
72417
|
+
desc48(siteAuditSnapshots.runId)
|
|
72127
72418
|
).limit(1).get();
|
|
72128
72419
|
const auditOfCrawl = (target) => {
|
|
72129
72420
|
const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
|
|
@@ -72139,22 +72430,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72139
72430
|
const date = selected.snap.createdAt.slice(0, 10);
|
|
72140
72431
|
const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
|
|
72141
72432
|
nextDate.setUTCDate(nextDate.getUTCDate() + 1);
|
|
72142
|
-
const sameDate =
|
|
72433
|
+
const sameDate = and83(
|
|
72143
72434
|
...filters,
|
|
72144
72435
|
gte27(siteAuditSnapshots.createdAt, date),
|
|
72145
72436
|
lt14(siteAuditSnapshots.createdAt, nextDate.toISOString())
|
|
72146
72437
|
);
|
|
72147
|
-
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72438
|
+
const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
|
|
72148
72439
|
const candidates = app.db.select({
|
|
72149
72440
|
runId: siteAuditSnapshots.runId,
|
|
72150
72441
|
createdAt: siteAuditSnapshots.createdAt,
|
|
72151
72442
|
complete,
|
|
72152
72443
|
pages: siteAuditSnapshots.pagesAudited
|
|
72153
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
72154
|
-
|
|
72155
|
-
|
|
72156
|
-
|
|
72157
|
-
|
|
72444
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
|
|
72445
|
+
desc48(complete),
|
|
72446
|
+
desc48(siteAuditSnapshots.pagesAudited),
|
|
72447
|
+
desc48(siteAuditSnapshots.createdAt),
|
|
72448
|
+
desc48(siteAuditSnapshots.runId)
|
|
72158
72449
|
).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
|
|
72159
72450
|
const runSelection = {
|
|
72160
72451
|
// A crawl exists here only when none stored a scorecard: this score is a fallback.
|
|
@@ -72169,9 +72460,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72169
72460
|
};
|
|
72170
72461
|
const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
|
|
72171
72462
|
const crawlReasonGroups = (where) => {
|
|
72172
|
-
const canonicalOther =
|
|
72463
|
+
const canonicalOther = sql36`case when ${siteCrawlPages.canonicalNodeKey} is not null
|
|
72173
72464
|
and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
|
|
72174
|
-
const canonicalReason =
|
|
72465
|
+
const canonicalReason = sql36`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
|
|
72175
72466
|
where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
|
|
72176
72467
|
const groups = app.db.select({
|
|
72177
72468
|
fetchState: siteCrawlPages.fetchState,
|
|
@@ -72180,7 +72471,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72180
72471
|
canonicalOther,
|
|
72181
72472
|
canonicalReason,
|
|
72182
72473
|
pages: count8(),
|
|
72183
|
-
exampleUrl:
|
|
72474
|
+
exampleUrl: sql36`min(${siteCrawlPages.url})`
|
|
72184
72475
|
}).from(siteCrawlPages).where(where).groupBy(
|
|
72185
72476
|
siteCrawlPages.fetchState,
|
|
72186
72477
|
siteCrawlPages.indexabilityState,
|
|
@@ -72216,10 +72507,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72216
72507
|
};
|
|
72217
72508
|
const inventorySummaryFor = (scope) => {
|
|
72218
72509
|
if (!scope) return null;
|
|
72219
|
-
const groups = crawlReasonGroups(
|
|
72220
|
-
|
|
72221
|
-
|
|
72222
|
-
|
|
72510
|
+
const groups = crawlReasonGroups(and83(
|
|
72511
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72512
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72513
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId)
|
|
72223
72514
|
));
|
|
72224
72515
|
const total = groups.reduce((sum, group) => sum + group.pages, 0);
|
|
72225
72516
|
const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
|
|
@@ -72239,34 +72530,34 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72239
72530
|
const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
|
|
72240
72531
|
const pageInScope = (scope, selector) => {
|
|
72241
72532
|
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
|
-
|
|
72533
|
+
return app.db.select().from(siteCrawlPages).where(and83(
|
|
72534
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72535
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72536
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
72537
|
+
selector.nodeKey ? eq100(siteCrawlPages.nodeKey, selector.nodeKey) : eq100(siteCrawlPages.url, selector.url)
|
|
72538
|
+
)).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
72539
|
+
};
|
|
72540
|
+
const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and83(
|
|
72541
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
72542
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
72543
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
72544
|
+
eq100(siteCrawlPages.depth, 0)
|
|
72545
|
+
)).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
|
|
72546
|
+
const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and83(
|
|
72547
|
+
eq100(runs.id, runId),
|
|
72548
|
+
eq100(runs.projectId, projectId),
|
|
72549
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72550
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72260
72551
|
notProbeRun()
|
|
72261
72552
|
)).limit(1).get());
|
|
72262
72553
|
const assertKnownAuditRun = (projectId, runId) => {
|
|
72263
72554
|
if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
|
|
72264
72555
|
};
|
|
72265
|
-
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs,
|
|
72266
|
-
|
|
72267
|
-
|
|
72268
|
-
|
|
72269
|
-
|
|
72556
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
|
|
72557
|
+
eq100(siteAuditSnapshots.projectId, projectId),
|
|
72558
|
+
eq100(runs.projectId, projectId),
|
|
72559
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72560
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72270
72561
|
notProbeRun()
|
|
72271
72562
|
)).limit(1).get());
|
|
72272
72563
|
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
@@ -72367,14 +72658,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72367
72658
|
};
|
|
72368
72659
|
}
|
|
72369
72660
|
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 =
|
|
72661
|
+
const conds = [eq100(siteAuditPages.projectId, project.id), eq100(siteAuditPages.runId, latest.snap.runId)];
|
|
72662
|
+
if (statusFilter) conds.push(eq100(siteAuditPages.status, statusFilter));
|
|
72663
|
+
const where = and83(...conds);
|
|
72373
72664
|
const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
|
|
72374
72665
|
const total = totalRow?.value ?? 0;
|
|
72375
72666
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
72376
72667
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
72377
|
-
const orderBy = request.query.sort === "score-desc" ?
|
|
72668
|
+
const orderBy = request.query.sort === "score-desc" ? desc48(siteAuditPages.overallScore) : request.query.sort === "url" ? asc22(siteAuditPages.url) : asc22(siteAuditPages.overallScore);
|
|
72378
72669
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
72379
72670
|
const pages = rows.map((row) => ({
|
|
72380
72671
|
url: row.url,
|
|
@@ -72393,13 +72684,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72393
72684
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
72394
72685
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
72395
72686
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
72396
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
72397
|
-
|
|
72398
|
-
|
|
72399
|
-
|
|
72400
|
-
|
|
72687
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
|
|
72688
|
+
eq100(siteAuditSnapshots.projectId, project.id),
|
|
72689
|
+
eq100(runs.projectId, project.id),
|
|
72690
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
72691
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
72401
72692
|
notProbeRun()
|
|
72402
|
-
)).orderBy(
|
|
72693
|
+
)).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
72403
72694
|
return { project: project.name, points: rows.reverse() };
|
|
72404
72695
|
});
|
|
72405
72696
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -72499,10 +72790,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72499
72790
|
{ projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
|
|
72500
72791
|
snapshot.rootUrl
|
|
72501
72792
|
)?.nodeKey ?? null;
|
|
72502
|
-
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(
|
|
72503
|
-
|
|
72504
|
-
|
|
72505
|
-
|
|
72793
|
+
const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and83(
|
|
72794
|
+
eq100(siteCrawlGraphLayouts.projectId, project.id),
|
|
72795
|
+
eq100(siteCrawlGraphLayouts.runId, snapshot.runId),
|
|
72796
|
+
eq100(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
|
|
72506
72797
|
)).limit(1).get();
|
|
72507
72798
|
if (!persistedLayout) {
|
|
72508
72799
|
return {
|
|
@@ -72555,9 +72846,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72555
72846
|
const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
|
|
72556
72847
|
const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
|
|
72557
72848
|
const graphScope = [
|
|
72558
|
-
|
|
72559
|
-
|
|
72560
|
-
|
|
72849
|
+
eq100(siteCrawlGraphNodes.projectId, project.id),
|
|
72850
|
+
eq100(siteCrawlGraphNodes.runId, snapshot.runId),
|
|
72851
|
+
eq100(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
|
|
72561
72852
|
];
|
|
72562
72853
|
const nodeRows = app.db.select({
|
|
72563
72854
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -72576,18 +72867,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72576
72867
|
linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
|
|
72577
72868
|
x: siteCrawlGraphNodes.x,
|
|
72578
72869
|
y: siteCrawlGraphNodes.y
|
|
72579
|
-
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages,
|
|
72580
|
-
|
|
72581
|
-
|
|
72582
|
-
|
|
72583
|
-
|
|
72584
|
-
)).where(
|
|
72870
|
+
}).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and83(
|
|
72871
|
+
eq100(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
|
|
72872
|
+
eq100(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
|
|
72873
|
+
eq100(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
|
|
72874
|
+
eq100(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
|
|
72875
|
+
)).where(and83(...graphScope, lt14(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc22(siteCrawlGraphNodes.sampleRank)).all();
|
|
72585
72876
|
const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
|
|
72586
72877
|
...row,
|
|
72587
72878
|
healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
|
|
72588
72879
|
healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
|
|
72589
72880
|
}));
|
|
72590
|
-
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 :
|
|
72881
|
+
const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq100(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
|
|
72591
72882
|
const edges = app.db.select({
|
|
72592
72883
|
edgeKey: siteCrawlGraphEdges.edgeKey,
|
|
72593
72884
|
sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
|
|
@@ -72595,25 +72886,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72595
72886
|
followable: siteCrawlGraphEdges.followable,
|
|
72596
72887
|
occurrences: siteCrawlGraphEdges.occurrences,
|
|
72597
72888
|
isTemplate: siteCrawlGraphEdges.isTemplate
|
|
72598
|
-
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode,
|
|
72599
|
-
|
|
72600
|
-
|
|
72601
|
-
|
|
72602
|
-
|
|
72889
|
+
}).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and83(
|
|
72890
|
+
eq100(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
|
|
72891
|
+
eq100(graphSourceNode.runId, siteCrawlGraphEdges.runId),
|
|
72892
|
+
eq100(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
72893
|
+
eq100(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
|
|
72603
72894
|
lt14(graphSourceNode.sampleRank, maxNodes)
|
|
72604
|
-
)).innerJoin(graphTargetNode,
|
|
72605
|
-
|
|
72606
|
-
|
|
72607
|
-
|
|
72608
|
-
|
|
72895
|
+
)).innerJoin(graphTargetNode, and83(
|
|
72896
|
+
eq100(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
|
|
72897
|
+
eq100(graphTargetNode.runId, siteCrawlGraphEdges.runId),
|
|
72898
|
+
eq100(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
|
|
72899
|
+
eq100(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
|
|
72609
72900
|
lt14(graphTargetNode.sampleRank, maxNodes)
|
|
72610
|
-
)).where(
|
|
72611
|
-
|
|
72612
|
-
|
|
72613
|
-
|
|
72901
|
+
)).where(and83(
|
|
72902
|
+
eq100(siteCrawlGraphEdges.projectId, project.id),
|
|
72903
|
+
eq100(siteCrawlGraphEdges.runId, snapshot.runId),
|
|
72904
|
+
eq100(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
|
|
72614
72905
|
lt14(siteCrawlGraphEdges.sampleRank, maxEdges),
|
|
72615
72906
|
graphLinkKindFilter
|
|
72616
|
-
)).orderBy(
|
|
72907
|
+
)).orderBy(asc22(siteCrawlGraphEdges.sampleRank)).all();
|
|
72617
72908
|
const totalNodes = persistedLayout.totalNodes;
|
|
72618
72909
|
const totalEdges = persistedLayout.totalEdges;
|
|
72619
72910
|
const totalTemplateEdges = persistedLayout.totalTemplateEdges;
|
|
@@ -72705,17 +72996,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72705
72996
|
let frontier = [focus.nodeKey];
|
|
72706
72997
|
let queryTruncated = false;
|
|
72707
72998
|
for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
|
|
72708
|
-
const candidates = app.db.select().from(siteCrawlEdges).where(
|
|
72709
|
-
|
|
72710
|
-
|
|
72711
|
-
|
|
72712
|
-
|
|
72999
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and83(
|
|
73000
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73001
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73002
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73003
|
+
eq100(siteCrawlEdges.internal, true),
|
|
72713
73004
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72714
73005
|
or21(
|
|
72715
|
-
|
|
72716
|
-
|
|
73006
|
+
inArray44(siteCrawlEdges.sourceNodeKey, frontier),
|
|
73007
|
+
inArray44(siteCrawlEdges.targetNodeKey, frontier)
|
|
72717
73008
|
)
|
|
72718
|
-
)).orderBy(
|
|
73009
|
+
)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
|
|
72719
73010
|
if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
|
|
72720
73011
|
const next = /* @__PURE__ */ new Set();
|
|
72721
73012
|
for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
|
|
@@ -72746,22 +73037,22 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72746
73037
|
frontier = [...next].sort();
|
|
72747
73038
|
}
|
|
72748
73039
|
const nodeKeys = [...distances.keys()];
|
|
72749
|
-
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(
|
|
72750
|
-
|
|
72751
|
-
|
|
72752
|
-
|
|
72753
|
-
|
|
73040
|
+
const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
|
|
73041
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73042
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73043
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73044
|
+
inArray44(siteCrawlPages.nodeKey, nodeKeys)
|
|
72754
73045
|
)).all();
|
|
72755
73046
|
const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
|
|
72756
73047
|
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
|
-
|
|
73048
|
+
const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and83(
|
|
73049
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73050
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73051
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73052
|
+
eq100(siteCrawlEdges.internal, true),
|
|
72762
73053
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72763
|
-
|
|
72764
|
-
|
|
73054
|
+
inArray44(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
|
|
73055
|
+
inArray44(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
|
|
72765
73056
|
)).limit(1).get();
|
|
72766
73057
|
if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
|
|
72767
73058
|
omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
|
|
@@ -72854,23 +73145,23 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72854
73145
|
let found = fromPage.nodeKey === toPage.nodeKey;
|
|
72855
73146
|
let truncated = false;
|
|
72856
73147
|
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
|
-
|
|
73148
|
+
const candidates = app.db.select().from(siteCrawlEdges).where(and83(
|
|
73149
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73150
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73151
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73152
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73153
|
+
eq100(siteCrawlEdges.followable, true),
|
|
73154
|
+
eq100(siteCrawlEdges.relation, "anchor"),
|
|
72864
73155
|
isNotNull10(siteCrawlEdges.targetNodeKey),
|
|
72865
|
-
|
|
72866
|
-
)).orderBy(
|
|
73156
|
+
inArray44(siteCrawlEdges.sourceNodeKey, frontier)
|
|
73157
|
+
)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
|
|
72867
73158
|
if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
|
|
72868
73159
|
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
|
-
|
|
73160
|
+
const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and83(
|
|
73161
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73162
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73163
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73164
|
+
inArray44(siteCrawlPages.nodeKey, candidateTargetKeys)
|
|
72874
73165
|
)).all().map((row) => row.nodeKey));
|
|
72875
73166
|
const next = /* @__PURE__ */ new Set();
|
|
72876
73167
|
for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
|
|
@@ -72911,11 +73202,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72911
73202
|
pathEdges.unshift(step.edge);
|
|
72912
73203
|
pathKeys.unshift(step.nodeKey);
|
|
72913
73204
|
}
|
|
72914
|
-
const pathRows = app.db.select().from(siteCrawlPages).where(
|
|
72915
|
-
|
|
72916
|
-
|
|
72917
|
-
|
|
72918
|
-
|
|
73205
|
+
const pathRows = app.db.select().from(siteCrawlPages).where(and83(
|
|
73206
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73207
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73208
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73209
|
+
inArray44(siteCrawlPages.nodeKey, pathKeys)
|
|
72919
73210
|
)).all();
|
|
72920
73211
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
72921
73212
|
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
@@ -72943,25 +73234,25 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
72943
73234
|
throw validationError("change must be all, added, removed, or changed");
|
|
72944
73235
|
}
|
|
72945
73236
|
const completeSnapshotFilters = [
|
|
72946
|
-
|
|
72947
|
-
|
|
72948
|
-
|
|
72949
|
-
|
|
72950
|
-
|
|
73237
|
+
eq100(siteCrawlSnapshots.projectId, project.id),
|
|
73238
|
+
eq100(runs.projectId, project.id),
|
|
73239
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73240
|
+
eq100(runs.status, RunStatuses.completed),
|
|
73241
|
+
eq100(siteCrawlSnapshots.complete, true),
|
|
72951
73242
|
notProbeRun()
|
|
72952
73243
|
];
|
|
72953
|
-
const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs,
|
|
73244
|
+
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
73245
|
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,
|
|
73246
|
+
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
73247
|
...completeSnapshotFilters,
|
|
72957
73248
|
or21(
|
|
72958
73249
|
lt14(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
72959
|
-
|
|
72960
|
-
|
|
73250
|
+
and83(
|
|
73251
|
+
eq100(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
|
|
72961
73252
|
lt14(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
|
|
72962
73253
|
)
|
|
72963
73254
|
)
|
|
72964
|
-
)).orderBy(
|
|
73255
|
+
)).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
|
|
72965
73256
|
if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
|
|
72966
73257
|
if (!afterTarget) {
|
|
72967
73258
|
const latestCrawl = resolveCrawl(project.id);
|
|
@@ -73045,9 +73336,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73045
73336
|
const summary4 = { pages: zero2(), links: zero2() };
|
|
73046
73337
|
if (scopeFilter !== "links") {
|
|
73047
73338
|
if (includeAdded || includeChanged) {
|
|
73048
|
-
const addedExpression = includeAdded ?
|
|
73049
|
-
const changedExpression = includeChanged ?
|
|
73050
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
73339
|
+
const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73340
|
+
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`;
|
|
73341
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
|
|
73051
73342
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
73052
73343
|
FROM site_crawl_pages AS current
|
|
73053
73344
|
LEFT JOIN site_crawl_pages AS previous
|
|
@@ -73063,7 +73354,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73063
73354
|
summary4.pages.changed = Number(counts2.changed);
|
|
73064
73355
|
}
|
|
73065
73356
|
if (includeRemoved) {
|
|
73066
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
73357
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql36`
|
|
73067
73358
|
SELECT COUNT(*) AS removed
|
|
73068
73359
|
FROM site_crawl_pages AS previous
|
|
73069
73360
|
LEFT JOIN site_crawl_pages AS current
|
|
@@ -73081,9 +73372,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73081
73372
|
}
|
|
73082
73373
|
if (scopeFilter !== "pages") {
|
|
73083
73374
|
if (includeAdded || includeChanged) {
|
|
73084
|
-
const addedExpression = includeAdded ?
|
|
73085
|
-
const changedExpression = includeChanged ?
|
|
73086
|
-
const [counts2 = { added: 0, changed: 0 }] = app.db.all(
|
|
73375
|
+
const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
|
|
73376
|
+
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`;
|
|
73377
|
+
const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
|
|
73087
73378
|
SELECT ${addedExpression} AS added, ${changedExpression} AS changed
|
|
73088
73379
|
FROM site_crawl_edges AS current
|
|
73089
73380
|
LEFT JOIN site_crawl_edges AS previous
|
|
@@ -73101,7 +73392,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73101
73392
|
summary4.links.changed = Number(counts2.changed);
|
|
73102
73393
|
}
|
|
73103
73394
|
if (includeRemoved) {
|
|
73104
|
-
const [counts2 = { removed: 0 }] = app.db.all(
|
|
73395
|
+
const [counts2 = { removed: 0 }] = app.db.all(sql36`
|
|
73105
73396
|
SELECT COUNT(*) AS removed
|
|
73106
73397
|
FROM site_crawl_edges AS previous
|
|
73107
73398
|
LEFT JOIN site_crawl_edges AS current
|
|
@@ -73131,48 +73422,48 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73131
73422
|
const includeRemoved = changeFilter === "all" || changeFilter === "removed";
|
|
73132
73423
|
const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
|
|
73133
73424
|
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" ?
|
|
73425
|
+
const internalCurrent = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
|
|
73426
|
+
const internalPreviousJoin = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
|
|
73427
|
+
const internalPrevious = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
|
|
73428
|
+
const internalCurrentJoin = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
|
|
73138
73429
|
const difference2 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
|
|
73139
|
-
const afterKind = changeFilter === "added" ?
|
|
73140
|
-
const afterRows = includeAfter ? app.db.all(
|
|
73430
|
+
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})`;
|
|
73431
|
+
const afterRows = includeAfter ? app.db.all(sql36`
|
|
73141
73432
|
SELECT ${entity} AS entity,
|
|
73142
73433
|
CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
|
|
73143
|
-
current.${
|
|
73144
|
-
FROM ${
|
|
73145
|
-
LEFT JOIN ${
|
|
73434
|
+
current.${sql36.raw(keyColumn)} AS key
|
|
73435
|
+
FROM ${sql36.raw(table)} AS current
|
|
73436
|
+
LEFT JOIN ${sql36.raw(table)} AS previous
|
|
73146
73437
|
ON previous.project_id = ${beforeScope.projectId}
|
|
73147
73438
|
AND previous.run_id = ${beforeScope.runId}
|
|
73148
73439
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
73149
73440
|
${internalPreviousJoin}
|
|
73150
|
-
AND previous.${
|
|
73441
|
+
AND previous.${sql36.raw(keyColumn)} = current.${sql36.raw(keyColumn)}
|
|
73151
73442
|
WHERE current.project_id = ${afterScope.projectId}
|
|
73152
73443
|
AND current.run_id = ${afterScope.runId}
|
|
73153
73444
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
73154
73445
|
${internalCurrent}
|
|
73155
|
-
AND current.${
|
|
73446
|
+
AND current.${sql36.raw(keyColumn)} > ${afterKey}
|
|
73156
73447
|
AND (${afterKind})
|
|
73157
|
-
ORDER BY current.${
|
|
73448
|
+
ORDER BY current.${sql36.raw(keyColumn)} ASC
|
|
73158
73449
|
LIMIT ${wanted2}
|
|
73159
73450
|
`) : [];
|
|
73160
|
-
const removedRows = includeRemoved ? app.db.all(
|
|
73161
|
-
SELECT ${entity} AS entity, 'removed' AS change, previous.${
|
|
73162
|
-
FROM ${
|
|
73163
|
-
LEFT JOIN ${
|
|
73451
|
+
const removedRows = includeRemoved ? app.db.all(sql36`
|
|
73452
|
+
SELECT ${entity} AS entity, 'removed' AS change, previous.${sql36.raw(keyColumn)} AS key
|
|
73453
|
+
FROM ${sql36.raw(table)} AS previous
|
|
73454
|
+
LEFT JOIN ${sql36.raw(table)} AS current
|
|
73164
73455
|
ON current.project_id = ${afterScope.projectId}
|
|
73165
73456
|
AND current.run_id = ${afterScope.runId}
|
|
73166
73457
|
AND current.attempt_id = ${afterScope.attemptId}
|
|
73167
73458
|
${internalCurrentJoin}
|
|
73168
|
-
AND current.${
|
|
73459
|
+
AND current.${sql36.raw(keyColumn)} = previous.${sql36.raw(keyColumn)}
|
|
73169
73460
|
WHERE previous.project_id = ${beforeScope.projectId}
|
|
73170
73461
|
AND previous.run_id = ${beforeScope.runId}
|
|
73171
73462
|
AND previous.attempt_id = ${beforeScope.attemptId}
|
|
73172
73463
|
${internalPrevious}
|
|
73173
|
-
AND previous.${
|
|
73464
|
+
AND previous.${sql36.raw(keyColumn)} > ${afterKey}
|
|
73174
73465
|
AND current.id IS NULL
|
|
73175
|
-
ORDER BY previous.${
|
|
73466
|
+
ORDER BY previous.${sql36.raw(keyColumn)} ASC
|
|
73176
73467
|
LIMIT ${wanted2}
|
|
73177
73468
|
`) : [];
|
|
73178
73469
|
const merged = [];
|
|
@@ -73202,18 +73493,18 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73202
73493
|
const visibleKeys = keyRows.slice(0, limit);
|
|
73203
73494
|
const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
|
|
73204
73495
|
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
|
-
|
|
73496
|
+
const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
|
|
73497
|
+
eq100(siteCrawlPages.projectId, scope.projectId),
|
|
73498
|
+
eq100(siteCrawlPages.runId, scope.runId),
|
|
73499
|
+
eq100(siteCrawlPages.attemptId, scope.attemptId),
|
|
73500
|
+
inArray44(siteCrawlPages.nodeKey, pageKeys)
|
|
73210
73501
|
)).all();
|
|
73211
|
-
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(
|
|
73212
|
-
|
|
73213
|
-
|
|
73214
|
-
|
|
73215
|
-
|
|
73216
|
-
|
|
73502
|
+
const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and83(
|
|
73503
|
+
eq100(siteCrawlEdges.projectId, scope.projectId),
|
|
73504
|
+
eq100(siteCrawlEdges.runId, scope.runId),
|
|
73505
|
+
eq100(siteCrawlEdges.attemptId, scope.attemptId),
|
|
73506
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73507
|
+
inArray44(siteCrawlEdges.edgeKey, linkKeys)
|
|
73217
73508
|
)).all();
|
|
73218
73509
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
73219
73510
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
@@ -73386,16 +73677,16 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73386
73677
|
}
|
|
73387
73678
|
const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
|
|
73388
73679
|
const filters = [
|
|
73389
|
-
|
|
73390
|
-
|
|
73391
|
-
|
|
73680
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73681
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73682
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
73392
73683
|
];
|
|
73393
|
-
if (request.query.nodeKey) filters.push(
|
|
73684
|
+
if (request.query.nodeKey) filters.push(eq100(siteCrawlPages.nodeKey, request.query.nodeKey));
|
|
73394
73685
|
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(
|
|
73686
|
+
if (inventoryEligible != null) filters.push(eq100(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
73687
|
+
if (request.query.fetchState) filters.push(eq100(siteCrawlPages.fetchState, request.query.fetchState));
|
|
73688
|
+
if (request.query.indexabilityState) filters.push(eq100(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
73689
|
+
if (request.query.auditState) filters.push(eq100(siteCrawlPages.auditState, request.query.auditState));
|
|
73399
73690
|
const healthState = parseSiteHealthState(request.query.healthState);
|
|
73400
73691
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73401
73692
|
const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
|
|
@@ -73413,17 +73704,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73413
73704
|
sort
|
|
73414
73705
|
})).digest("base64url");
|
|
73415
73706
|
const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
|
|
73416
|
-
const orderBy = sort === "score-desc" ? [
|
|
73707
|
+
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
73708
|
let healthStateFilter = null;
|
|
73418
73709
|
if (healthState) {
|
|
73419
|
-
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(
|
|
73420
|
-
|
|
73421
|
-
|
|
73422
|
-
|
|
73710
|
+
const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and83(
|
|
73711
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73712
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73713
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId),
|
|
73423
73714
|
isNull17(siteCrawlPages.healthState)
|
|
73424
73715
|
)).limit(1).get();
|
|
73425
73716
|
healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
|
|
73426
|
-
if (healthStateFilter === "applied") filters.push(
|
|
73717
|
+
if (healthStateFilter === "applied") filters.push(eq100(siteCrawlPages.healthState, healthState));
|
|
73427
73718
|
}
|
|
73428
73719
|
if (healthStateFilter === "unavailable-legacy-scan") {
|
|
73429
73720
|
return {
|
|
@@ -73440,7 +73731,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73440
73731
|
pages: []
|
|
73441
73732
|
};
|
|
73442
73733
|
}
|
|
73443
|
-
const where =
|
|
73734
|
+
const where = and83(...filters);
|
|
73444
73735
|
const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
73445
73736
|
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
73446
73737
|
const nextOffset = offset + rows.length;
|
|
@@ -73475,10 +73766,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73475
73766
|
url: siteCrawlPages.url,
|
|
73476
73767
|
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
73477
73768
|
fetchState: siteCrawlPages.fetchState
|
|
73478
|
-
}).from(siteCrawlPages).where(
|
|
73479
|
-
|
|
73480
|
-
|
|
73481
|
-
|
|
73769
|
+
}).from(siteCrawlPages).where(and83(
|
|
73770
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
73771
|
+
eq100(siteCrawlPages.runId, snapshot.runId),
|
|
73772
|
+
eq100(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
73482
73773
|
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
73483
73774
|
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
73484
73775
|
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
@@ -73560,21 +73851,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73560
73851
|
};
|
|
73561
73852
|
}
|
|
73562
73853
|
const filters = [
|
|
73563
|
-
|
|
73564
|
-
|
|
73565
|
-
|
|
73566
|
-
|
|
73854
|
+
eq100(siteCrawlEdges.projectId, project.id),
|
|
73855
|
+
eq100(siteCrawlEdges.runId, snapshot.runId),
|
|
73856
|
+
eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
73857
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73567
73858
|
linkKindFilter(linkKind)
|
|
73568
73859
|
];
|
|
73569
|
-
if (request.query.sourceUrl) filters.push(
|
|
73570
|
-
if (request.query.targetUrl) filters.push(
|
|
73860
|
+
if (request.query.sourceUrl) filters.push(eq100(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
73861
|
+
if (request.query.targetUrl) filters.push(eq100(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
73571
73862
|
const followable = parseBoolean2(request.query.followable);
|
|
73572
|
-
if (followable != null) filters.push(
|
|
73573
|
-
const where =
|
|
73863
|
+
if (followable != null) filters.push(eq100(siteCrawlEdges.followable, followable));
|
|
73864
|
+
const where = and83(...filters);
|
|
73574
73865
|
const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
73575
73866
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73576
73867
|
const offset = decodeCursor(request.query.cursor);
|
|
73577
|
-
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(
|
|
73868
|
+
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
|
|
73578
73869
|
const nextOffset = offset + rows.length;
|
|
73579
73870
|
return {
|
|
73580
73871
|
project: project.name,
|
|
@@ -73631,17 +73922,17 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73631
73922
|
};
|
|
73632
73923
|
}
|
|
73633
73924
|
const scope = [
|
|
73634
|
-
|
|
73635
|
-
|
|
73636
|
-
|
|
73637
|
-
|
|
73925
|
+
eq100(siteCrawlEdges.projectId, project.id),
|
|
73926
|
+
eq100(siteCrawlEdges.runId, snapshot.runId),
|
|
73927
|
+
eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
73928
|
+
eq100(siteCrawlEdges.internal, true),
|
|
73638
73929
|
linkKindFilter(linkKind)
|
|
73639
73930
|
];
|
|
73640
|
-
const inboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
73641
|
-
const outboundMatch = request.query.nodeKey && request.query.url ? or21(
|
|
73931
|
+
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);
|
|
73932
|
+
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
73933
|
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(
|
|
73934
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, inboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
73935
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, outboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
73645
73936
|
return {
|
|
73646
73937
|
project: project.name,
|
|
73647
73938
|
hasCrawlData: true,
|
|
@@ -73675,11 +73966,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73675
73966
|
if (!snapshot.attemptId) {
|
|
73676
73967
|
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
73968
|
}
|
|
73678
|
-
const where =
|
|
73679
|
-
|
|
73680
|
-
|
|
73681
|
-
|
|
73682
|
-
|
|
73969
|
+
const where = and83(
|
|
73970
|
+
eq100(siteCrawlFindings.projectId, project.id),
|
|
73971
|
+
eq100(siteCrawlFindings.runId, snapshot.runId),
|
|
73972
|
+
eq100(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
73973
|
+
eq100(siteCrawlFindings.findingType, "dead-link"),
|
|
73683
73974
|
// Belt and braces on the client-facing surface: a row without a status
|
|
73684
73975
|
// code is not evidence of a broken link, and this response is where such
|
|
73685
73976
|
// a row would be read as one. Migration 140 removed every stored row that
|
|
@@ -73689,12 +73980,12 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73689
73980
|
// a fabricated broken link shown to a client is not worth the saved
|
|
73690
73981
|
// predicate. Applied to `total` as well as the rows, or the count would
|
|
73691
73982
|
// promise a page the list cannot fill.
|
|
73692
|
-
isNotNull10(
|
|
73983
|
+
isNotNull10(sql36`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
|
|
73693
73984
|
);
|
|
73694
73985
|
const total = app.db.select({ value: count8() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
73695
73986
|
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
73696
73987
|
const offset = decodeCursor(request.query.cursor);
|
|
73697
|
-
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(
|
|
73988
|
+
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc22(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
|
|
73698
73989
|
const nextOffset = offset + rows.length;
|
|
73699
73990
|
const deadLinks = rows.map((row) => ({
|
|
73700
73991
|
findingKey: row.findingKey,
|
|
@@ -73729,19 +74020,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73729
74020
|
createdAt: runs.createdAt,
|
|
73730
74021
|
startedAt: runs.startedAt,
|
|
73731
74022
|
finishedAt: runs.finishedAt
|
|
73732
|
-
}).from(runs).where(
|
|
73733
|
-
|
|
73734
|
-
|
|
74023
|
+
}).from(runs).where(and83(
|
|
74024
|
+
eq100(runs.projectId, project.id),
|
|
74025
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73735
74026
|
notProbeRun()
|
|
73736
|
-
)).orderBy(
|
|
74027
|
+
)).orderBy(desc48(runs.createdAt), desc48(runs.id)).limit(limit).all();
|
|
73737
74028
|
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
|
-
|
|
74029
|
+
const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
|
|
74030
|
+
eq100(siteCrawlSnapshots.projectId, project.id),
|
|
74031
|
+
eq100(runs.projectId, project.id),
|
|
74032
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
74033
|
+
inArray44(runs.status, SURFACEABLE_STATUSES),
|
|
73743
74034
|
notProbeRun(),
|
|
73744
|
-
|
|
74035
|
+
inArray44(siteCrawlSnapshots.runId, runIds)
|
|
73745
74036
|
)).all().map((row) => row.runId));
|
|
73746
74037
|
return {
|
|
73747
74038
|
project: project.name,
|
|
@@ -73755,21 +74046,21 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73755
74046
|
});
|
|
73756
74047
|
app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
|
|
73757
74048
|
const project = resolveProject(app.db, request.params.name);
|
|
73758
|
-
const run = app.db.select().from(runs).where(
|
|
73759
|
-
|
|
73760
|
-
|
|
73761
|
-
|
|
74049
|
+
const run = app.db.select().from(runs).where(and83(
|
|
74050
|
+
eq100(runs.id, request.params.runId),
|
|
74051
|
+
eq100(runs.projectId, project.id),
|
|
74052
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73762
74053
|
notProbeRun()
|
|
73763
74054
|
)).get();
|
|
73764
74055
|
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
|
-
|
|
74056
|
+
const attempt = app.db.select().from(siteCrawlAttempts).where(and83(
|
|
74057
|
+
eq100(siteCrawlAttempts.projectId, project.id),
|
|
74058
|
+
eq100(siteCrawlAttempts.runId, run.id)
|
|
74059
|
+
)).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
74060
|
+
const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and83(
|
|
74061
|
+
eq100(siteCrawlGraphLayouts.projectId, project.id),
|
|
74062
|
+
eq100(siteCrawlGraphLayouts.runId, run.id),
|
|
74063
|
+
eq100(siteCrawlGraphLayouts.attemptId, attempt.id)
|
|
73773
74064
|
)).get() : void 0;
|
|
73774
74065
|
const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
|
|
73775
74066
|
return {
|
|
@@ -73800,19 +74091,19 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73800
74091
|
};
|
|
73801
74092
|
});
|
|
73802
74093
|
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(
|
|
74094
|
+
const project = tx.select().from(projects).where(eq100(projects.name, request.params.name)).get();
|
|
73804
74095
|
if (!project) throw notFound("Project", request.params.name);
|
|
73805
|
-
const run = tx.select().from(runs).where(
|
|
73806
|
-
|
|
73807
|
-
|
|
73808
|
-
|
|
74096
|
+
const run = tx.select().from(runs).where(and83(
|
|
74097
|
+
eq100(runs.id, request.params.runId),
|
|
74098
|
+
eq100(runs.projectId, project.id),
|
|
74099
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
73809
74100
|
notProbeRun()
|
|
73810
74101
|
)).get();
|
|
73811
74102
|
if (!run) throw notFound("Site audit run", request.params.runId);
|
|
73812
|
-
const attempt = tx.select().from(siteCrawlAttempts).where(
|
|
73813
|
-
|
|
73814
|
-
|
|
73815
|
-
)).orderBy(
|
|
74103
|
+
const attempt = tx.select().from(siteCrawlAttempts).where(and83(
|
|
74104
|
+
eq100(siteCrawlAttempts.projectId, project.id),
|
|
74105
|
+
eq100(siteCrawlAttempts.runId, run.id)
|
|
74106
|
+
)).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
|
|
73816
74107
|
const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
|
|
73817
74108
|
const base = {
|
|
73818
74109
|
project: project.name,
|
|
@@ -73825,11 +74116,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73825
74116
|
if (!attempt) {
|
|
73826
74117
|
return { ...base, pagesAudited: 0, examples: [] };
|
|
73827
74118
|
}
|
|
73828
|
-
const auditedWhere =
|
|
73829
|
-
|
|
73830
|
-
|
|
73831
|
-
|
|
73832
|
-
|
|
74119
|
+
const auditedWhere = and83(
|
|
74120
|
+
eq100(siteCrawlPages.projectId, project.id),
|
|
74121
|
+
eq100(siteCrawlPages.runId, run.id),
|
|
74122
|
+
eq100(siteCrawlPages.attemptId, attempt.id),
|
|
74123
|
+
eq100(siteCrawlPages.auditState, "success")
|
|
73833
74124
|
);
|
|
73834
74125
|
const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
|
|
73835
74126
|
if (state !== "collecting") {
|
|
@@ -73840,11 +74131,11 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73840
74131
|
url: siteCrawlPages.url,
|
|
73841
74132
|
auditScore: siteCrawlPages.auditScore,
|
|
73842
74133
|
auditFields: siteCrawlPages.auditFields
|
|
73843
|
-
}).from(siteCrawlPages).where(
|
|
74134
|
+
}).from(siteCrawlPages).where(and83(
|
|
73844
74135
|
auditedWhere,
|
|
73845
74136
|
isNotNull10(siteCrawlPages.auditScore),
|
|
73846
74137
|
lt14(siteCrawlPages.auditScore, 70)
|
|
73847
|
-
)).orderBy(
|
|
74138
|
+
)).orderBy(asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
|
|
73848
74139
|
const examples = candidates.flatMap((candidate) => {
|
|
73849
74140
|
if (candidate.auditScore == null) return [];
|
|
73850
74141
|
const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
|
|
@@ -73876,13 +74167,13 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73876
74167
|
status: runs.status,
|
|
73877
74168
|
identityKey: siteCrawlRunRequests.identityKey,
|
|
73878
74169
|
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
73879
|
-
}).from(runs).leftJoin(siteCrawlRunRequests,
|
|
73880
|
-
|
|
73881
|
-
|
|
73882
|
-
)).where(
|
|
73883
|
-
|
|
73884
|
-
|
|
73885
|
-
|
|
74170
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and83(
|
|
74171
|
+
eq100(siteCrawlRunRequests.projectId, runs.projectId),
|
|
74172
|
+
eq100(siteCrawlRunRequests.runId, runs.id)
|
|
74173
|
+
)).where(and83(
|
|
74174
|
+
eq100(runs.projectId, project.id),
|
|
74175
|
+
eq100(runs.kind, RunKinds["site-audit"]),
|
|
74176
|
+
inArray44(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
73886
74177
|
)).get();
|
|
73887
74178
|
if (existing) {
|
|
73888
74179
|
if (existing.identityKey === identityKey) {
|
|
@@ -73938,7 +74229,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73938
74229
|
// ../api-routes/src/research.ts
|
|
73939
74230
|
import crypto57 from "crypto";
|
|
73940
74231
|
import { z as z7 } from "zod";
|
|
73941
|
-
import { and as
|
|
74232
|
+
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
74233
|
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
74234
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
73944
74235
|
async function researchRoutes(app, opts) {
|
|
@@ -73957,7 +74248,7 @@ async function researchRoutes(app, opts) {
|
|
|
73957
74248
|
const input = parsed.data;
|
|
73958
74249
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
73959
74250
|
const requestHash2 = directResearchRequestHash(input);
|
|
73960
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
74251
|
+
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
74252
|
if (existingReceipt) {
|
|
73962
74253
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
73963
74254
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -73992,7 +74283,7 @@ async function researchRoutes(app, opts) {
|
|
|
73992
74283
|
const initiatedBy = researchPrincipal(request);
|
|
73993
74284
|
const decision = app.db.transaction((tx) => {
|
|
73994
74285
|
if (input.idempotencyKey) {
|
|
73995
|
-
const existing = tx.select().from(researchRuns).where(
|
|
74286
|
+
const existing = tx.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
73996
74287
|
if (existing) {
|
|
73997
74288
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
73998
74289
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -74000,11 +74291,11 @@ async function researchRoutes(app, opts) {
|
|
|
74000
74291
|
}
|
|
74001
74292
|
if (initiatedBy?.limited) {
|
|
74002
74293
|
const { start, end, date } = utcDayBounds(now);
|
|
74003
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
74004
|
-
|
|
74294
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
|
|
74295
|
+
eq101(researchRuns.projectId, project.id),
|
|
74005
74296
|
gte28(researchRuns.createdAt, start),
|
|
74006
74297
|
lt15(researchRuns.createdAt, end),
|
|
74007
|
-
|
|
74298
|
+
sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
74008
74299
|
)).get()?.value ?? 0;
|
|
74009
74300
|
if (used >= viewerDailyRunLimit) {
|
|
74010
74301
|
throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
@@ -74032,15 +74323,15 @@ async function researchRoutes(app, opts) {
|
|
|
74032
74323
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
74033
74324
|
const requestHash2 = researchBatchRequestHash(input);
|
|
74034
74325
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
74035
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
74036
|
-
|
|
74037
|
-
|
|
74326
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and84(
|
|
74327
|
+
eq101(researchRuns.projectId, project.id),
|
|
74328
|
+
eq101(researchRuns.idempotencyKey, receiptKeys[0])
|
|
74038
74329
|
)).get();
|
|
74039
74330
|
if (firstReceipt) {
|
|
74040
74331
|
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
|
-
|
|
74332
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and84(
|
|
74333
|
+
eq101(researchRuns.projectId, project.id),
|
|
74334
|
+
eq101(researchRuns.idempotencyKey, key)
|
|
74044
74335
|
)).get());
|
|
74045
74336
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
74046
74337
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -74056,11 +74347,11 @@ async function researchRoutes(app, opts) {
|
|
|
74056
74347
|
const decision = app.db.transaction((tx) => {
|
|
74057
74348
|
if (initiatedBy?.limited) {
|
|
74058
74349
|
const { start, end, date } = utcDayBounds(now);
|
|
74059
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
74060
|
-
|
|
74350
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
|
|
74351
|
+
eq101(researchRuns.projectId, project.id),
|
|
74061
74352
|
gte28(researchRuns.createdAt, start),
|
|
74062
74353
|
lt15(researchRuns.createdAt, end),
|
|
74063
|
-
|
|
74354
|
+
sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
74064
74355
|
)).get()?.value ?? 0;
|
|
74065
74356
|
if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
|
|
74066
74357
|
}
|
|
@@ -74111,10 +74402,10 @@ async function researchRoutes(app, opts) {
|
|
|
74111
74402
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
74112
74403
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
74113
74404
|
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(
|
|
74405
|
+
const rows = app.db.select().from(researchRuns).where(and84(
|
|
74406
|
+
eq101(researchRuns.projectId, project.id),
|
|
74407
|
+
cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and84(eq101(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
|
|
74408
|
+
)).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
|
|
74118
74409
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
74119
74410
|
const last = runs2.at(-1);
|
|
74120
74411
|
const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
|
|
@@ -74263,9 +74554,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
74263
74554
|
return stored;
|
|
74264
74555
|
}
|
|
74265
74556
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
74266
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
74267
|
-
|
|
74268
|
-
|
|
74557
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and84(
|
|
74558
|
+
eq101(measurementQueryTemplates.projectId, projectId),
|
|
74559
|
+
eq101(measurementQueryTemplates.id, selection.templateId)
|
|
74269
74560
|
)).get();
|
|
74270
74561
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
74271
74562
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -74274,7 +74565,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
74274
74565
|
}
|
|
74275
74566
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
74276
74567
|
if (bindingLocation) {
|
|
74277
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
74568
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq101(projects.id, projectId)).get();
|
|
74278
74569
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
74279
74570
|
}
|
|
74280
74571
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -74287,9 +74578,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
74287
74578
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
74288
74579
|
}
|
|
74289
74580
|
function getDetail(app, projectId, id) {
|
|
74290
|
-
const row = app.db.select().from(researchRuns).where(
|
|
74581
|
+
const row = app.db.select().from(researchRuns).where(and84(eq101(researchRuns.id, id), eq101(researchRuns.projectId, projectId))).get();
|
|
74291
74582
|
if (!row) throw notFound("Research run", id);
|
|
74292
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
74583
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq101(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
74293
74584
|
return { ...serializeRun(row), queries: queries2 };
|
|
74294
74585
|
}
|
|
74295
74586
|
function serializeRun(row) {
|
|
@@ -74344,7 +74635,7 @@ function utcDayBounds(now) {
|
|
|
74344
74635
|
|
|
74345
74636
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
74346
74637
|
import crypto58 from "crypto";
|
|
74347
|
-
import { and as
|
|
74638
|
+
import { and as and85, eq as eq102 } from "drizzle-orm";
|
|
74348
74639
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
74349
74640
|
return db.transaction((tx) => {
|
|
74350
74641
|
const run = tx.select({
|
|
@@ -74352,7 +74643,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74352
74643
|
trigger: runs.trigger,
|
|
74353
74644
|
status: runs.status,
|
|
74354
74645
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
74355
|
-
}).from(runs).where(
|
|
74646
|
+
}).from(runs).where(and85(eq102(runs.projectId, input.projectId), eq102(runs.id, input.runId))).get();
|
|
74356
74647
|
if (!run) throw notFound("Run", input.runId);
|
|
74357
74648
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
74358
74649
|
return null;
|
|
@@ -74373,7 +74664,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74373
74664
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
74374
74665
|
}
|
|
74375
74666
|
}
|
|
74376
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
74667
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and85(eq102(simpleMeasurementDefinitions.projectId, input.projectId), eq102(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
74377
74668
|
if (existing) {
|
|
74378
74669
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
74379
74670
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -74399,15 +74690,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
74399
74690
|
if (run.status !== RunStatuses.running) {
|
|
74400
74691
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
74401
74692
|
}
|
|
74402
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
74693
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq102(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
74403
74694
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
74404
74695
|
}
|
|
74405
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
74696
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq102(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
74406
74697
|
if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
74407
74698
|
throw validationError("Every captured query must belong to the run project.");
|
|
74408
74699
|
}
|
|
74409
74700
|
if (definition2.competitors !== void 0) {
|
|
74410
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
74701
|
+
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
74702
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
74412
74703
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
74413
74704
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|