@canonry/canonry 4.154.0 → 4.155.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/README.md +2 -2
- package/assets/agent-workspace/AGENTS.md +2 -2
- package/assets/agent-workspace/skills/aero/references/orchestration.md +3 -3
- package/assets/agent-workspace/skills/canonry/SKILL.md +6 -21
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +14 -6
- package/assets/assets/{AuditHistoryPanel-WWT1n8jp.js → AuditHistoryPanel-BlbMBxOY.js} +1 -1
- package/assets/assets/BacklinksPage-DA9EaV9q.js +1 -0
- package/assets/assets/HistoryPage-BTrIHjMQ.js +1 -0
- package/assets/assets/MeasurementPropertyPage-CN6o5xFg.js +1 -0
- package/assets/assets/{ProjectPage-BR5ECwUP.js → ProjectPage-CsDbgp_0.js} +9 -9
- package/assets/assets/{RunRow-OE5tiuih.js → RunRow-ch_OlCPs.js} +1 -1
- package/assets/assets/RunsPage-T89Y_yS8.js +1 -0
- package/assets/assets/SettingsPage-DnmEq3F9.js +1 -0
- package/assets/assets/TrafficPage-CW1UhaJt.js +1 -0
- package/assets/assets/TrafficSourceDetailPage-r4Ji3wJm.js +1 -0
- package/assets/assets/extract-error-message-a8EErBoZ.js +1 -0
- package/assets/assets/index-BKq6k5Of.js +83 -0
- package/assets/assets/vendor-lucide-ByU7vBHt.js +1 -0
- package/assets/assets/vendor-yaml-GWJz5H2G.js +131 -0
- package/assets/index.html +3 -1
- package/dist/{chunk-ANSQ6JNO.js → chunk-OSHRDV36.js} +317 -4
- package/dist/{chunk-NNW7TJUZ.js → chunk-VIKPFI6F.js} +200 -5
- package/dist/{chunk-IFZS2TXV.js → chunk-VOL2BSM5.js} +1133 -104
- package/dist/{chunk-X5I7CAXH.js → chunk-YSESZNVT.js} +609 -165
- package/dist/cli.js +342 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-B5AETQ5N.js → intelligence-service-G4NV3X36.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/BacklinksPage-DIjUnsVL.js +0 -1
- package/assets/assets/HistoryPage-CrIBhGuW.js +0 -1
- package/assets/assets/MeasurementPropertyPage-uN4BWigS.js +0 -1
- package/assets/assets/RunsPage-C6xBmjps.js +0 -1
- package/assets/assets/SettingsPage-CUjjr8U0.js +0 -1
- package/assets/assets/TrafficPage-DrEfsHiE.js +0 -1
- package/assets/assets/TrafficSourceDetailPage-B3DX5BEk.js +0 -1
- package/assets/assets/arrow-left-CLmqvCqU.js +0 -1
- package/assets/assets/extract-error-message-BEBd1a5v.js +0 -1
- package/assets/assets/index-CsM-Xxia.js +0 -213
- package/assets/assets/refresh-cw-DYSS2Wd-.js +0 -1
- package/assets/assets/trash-2-VoiF_ckU.js +0 -1
|
@@ -403,6 +403,7 @@ import {
|
|
|
403
403
|
normalizeProjectAliases,
|
|
404
404
|
normalizeProjectDomain,
|
|
405
405
|
normalizeQueryText,
|
|
406
|
+
normalizeSiteAuditRunRequest,
|
|
406
407
|
normalizeUrlPath,
|
|
407
408
|
normalizeUserName,
|
|
408
409
|
notFound,
|
|
@@ -469,10 +470,17 @@ import {
|
|
|
469
470
|
serializeRunError,
|
|
470
471
|
settingsDtoSchema,
|
|
471
472
|
siteAuditPagesResponseSchema,
|
|
473
|
+
siteAuditRequestIdentity,
|
|
472
474
|
siteAuditRunRequestSchema,
|
|
473
475
|
siteAuditRunResponseSchema,
|
|
474
476
|
siteAuditScoreSchema,
|
|
475
477
|
siteAuditTrendResponseSchema,
|
|
478
|
+
siteCrawlDeadLinksResponseSchema,
|
|
479
|
+
siteCrawlInternalLinksResponseSchema,
|
|
480
|
+
siteCrawlNeighborsResponseSchema,
|
|
481
|
+
siteCrawlPagesResponseSchema,
|
|
482
|
+
siteCrawlStructureResponseSchema,
|
|
483
|
+
siteCrawlSummarySchema,
|
|
476
484
|
snapshotDiffResponseSchema,
|
|
477
485
|
snapshotListResponseSchema,
|
|
478
486
|
snapshotReportSchema,
|
|
@@ -521,10 +529,10 @@ import {
|
|
|
521
529
|
wordpressSchemaDeployResultDtoSchema,
|
|
522
530
|
wordpressSchemaStatusResultDtoSchema,
|
|
523
531
|
wordpressStatusDtoSchema
|
|
524
|
-
} from "./chunk-
|
|
532
|
+
} from "./chunk-VIKPFI6F.js";
|
|
525
533
|
|
|
526
534
|
// src/intelligence-service.ts
|
|
527
|
-
import { eq as eq57, desc as desc26, asc as asc11, and as and45, ne as ne7, or as
|
|
535
|
+
import { eq as eq57, desc as desc26, asc as asc11, and as and45, ne as ne7, or as or12, inArray as inArray20, gte as gte13, lte as lte10 } from "drizzle-orm";
|
|
528
536
|
|
|
529
537
|
// ../db/src/client.ts
|
|
530
538
|
import { mkdirSync } from "fs";
|
|
@@ -615,6 +623,13 @@ __export(schema_exports, {
|
|
|
615
623
|
schedules: () => schedules,
|
|
616
624
|
siteAuditPages: () => siteAuditPages,
|
|
617
625
|
siteAuditSnapshots: () => siteAuditSnapshots,
|
|
626
|
+
siteCrawlAttempts: () => siteCrawlAttempts,
|
|
627
|
+
siteCrawlEdges: () => siteCrawlEdges,
|
|
628
|
+
siteCrawlEventReceipts: () => siteCrawlEventReceipts,
|
|
629
|
+
siteCrawlFindings: () => siteCrawlFindings,
|
|
630
|
+
siteCrawlPages: () => siteCrawlPages,
|
|
631
|
+
siteCrawlRunRequests: () => siteCrawlRunRequests,
|
|
632
|
+
siteCrawlSnapshots: () => siteCrawlSnapshots,
|
|
618
633
|
trafficSources: () => trafficSources,
|
|
619
634
|
usageCounters: () => usageCounters,
|
|
620
635
|
userSessions: () => userSessions,
|
|
@@ -839,6 +854,9 @@ var runs = sqliteTable("runs", {
|
|
|
839
854
|
createdAt: text("created_at").notNull()
|
|
840
855
|
}, (table) => [
|
|
841
856
|
index("idx_runs_project").on(table.projectId),
|
|
857
|
+
// Enables child tables that carry project + run to enforce that the two IDs
|
|
858
|
+
// belong together, rather than trusting a caller-provided project ID.
|
|
859
|
+
uniqueIndex("idx_runs_project_id").on(table.projectId, table.id),
|
|
842
860
|
index("idx_runs_status").on(table.status),
|
|
843
861
|
index("idx_runs_source").on(table.sourceId),
|
|
844
862
|
index("idx_runs_measurement_plan").on(table.projectId, table.measurementPlanVersionId, table.createdAt),
|
|
@@ -1185,6 +1203,213 @@ var siteAuditPages = sqliteTable("site_audit_pages", {
|
|
|
1185
1203
|
index("idx_site_audit_pages_run").on(table.runId),
|
|
1186
1204
|
index("idx_site_audit_pages_project_score").on(table.projectId, table.overallScore)
|
|
1187
1205
|
]);
|
|
1206
|
+
var siteCrawlRunRequests = sqliteTable("site_crawl_run_requests", {
|
|
1207
|
+
runId: text("run_id").primaryKey(),
|
|
1208
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1209
|
+
identityKey: text("identity_key").notNull(),
|
|
1210
|
+
effectiveOptions: text("effective_options", { mode: "json" }).$type().notNull(),
|
|
1211
|
+
createdAt: text("created_at").notNull()
|
|
1212
|
+
}, (table) => [
|
|
1213
|
+
index("idx_site_crawl_run_requests_project").on(table.projectId, table.runId),
|
|
1214
|
+
foreignKey({
|
|
1215
|
+
name: "site_crawl_run_requests_project_run_fk",
|
|
1216
|
+
columns: [table.projectId, table.runId],
|
|
1217
|
+
foreignColumns: [runs.projectId, runs.id]
|
|
1218
|
+
}).onDelete("cascade")
|
|
1219
|
+
]);
|
|
1220
|
+
var siteCrawlAttempts = sqliteTable("site_crawl_attempts", {
|
|
1221
|
+
id: text("id").primaryKey(),
|
|
1222
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1223
|
+
runId: text("run_id").notNull(),
|
|
1224
|
+
attemptNumber: integer("attempt_number").notNull(),
|
|
1225
|
+
state: text("state").notNull().default("queued"),
|
|
1226
|
+
lastEventSequence: integer("last_event_sequence").notNull().default(0),
|
|
1227
|
+
lastEventChecksum: text("last_event_checksum"),
|
|
1228
|
+
pagesDiscovered: integer("pages_discovered").notNull().default(0),
|
|
1229
|
+
pagesFetched: integer("pages_fetched").notNull().default(0),
|
|
1230
|
+
pagesEligible: integer("pages_eligible").notNull().default(0),
|
|
1231
|
+
pagesErrored: integer("pages_errored").notNull().default(0),
|
|
1232
|
+
edgesDiscovered: integer("edges_discovered").notNull().default(0),
|
|
1233
|
+
startedAt: text("started_at"),
|
|
1234
|
+
finishedAt: text("finished_at"),
|
|
1235
|
+
error: text("error"),
|
|
1236
|
+
createdAt: text("created_at").notNull(),
|
|
1237
|
+
updatedAt: text("updated_at").notNull()
|
|
1238
|
+
}, (table) => [
|
|
1239
|
+
uniqueIndex("idx_site_crawl_attempts_run_number").on(table.runId, table.attemptNumber),
|
|
1240
|
+
uniqueIndex("idx_site_crawl_attempts_project_run_id").on(table.projectId, table.runId, table.id),
|
|
1241
|
+
index("idx_site_crawl_attempts_project_run").on(table.projectId, table.runId),
|
|
1242
|
+
foreignKey({
|
|
1243
|
+
name: "site_crawl_attempts_project_run_fk",
|
|
1244
|
+
columns: [table.projectId, table.runId],
|
|
1245
|
+
foreignColumns: [runs.projectId, runs.id]
|
|
1246
|
+
}).onDelete("cascade")
|
|
1247
|
+
]);
|
|
1248
|
+
var siteCrawlSnapshots = sqliteTable("site_crawl_snapshots", {
|
|
1249
|
+
id: text("id").primaryKey(),
|
|
1250
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1251
|
+
runId: text("run_id").notNull(),
|
|
1252
|
+
attemptId: text("attempt_id"),
|
|
1253
|
+
rootUrl: text("root_url").notNull(),
|
|
1254
|
+
crawlSchemaVersion: text("crawl_schema_version").notNull().default("1.0"),
|
|
1255
|
+
engineVersion: text("engine_version").notNull().default(""),
|
|
1256
|
+
normalizationVersion: text("normalization_version").notNull().default(""),
|
|
1257
|
+
indexabilityVersion: text("indexability_version").notNull().default(""),
|
|
1258
|
+
linkScoreVersion: text("link_score_version").notNull().default(""),
|
|
1259
|
+
effectiveOptions: text("effective_options", { mode: "json" }).$type().notNull().default({}),
|
|
1260
|
+
pageBudget: integer("page_budget"),
|
|
1261
|
+
edgeBudget: integer("edge_budget"),
|
|
1262
|
+
maxDepth: integer("max_depth"),
|
|
1263
|
+
checkDeadLinks: integer("check_dead_links", { mode: "boolean" }).notNull().default(false),
|
|
1264
|
+
complete: integer("complete", { mode: "boolean" }).notNull().default(false),
|
|
1265
|
+
termination: text("termination").notNull().default("unknown"),
|
|
1266
|
+
detailsAvailable: integer("details_available", { mode: "boolean" }).notNull().default(false),
|
|
1267
|
+
pagesDiscovered: integer("pages_discovered").notNull().default(0),
|
|
1268
|
+
pagesFetched: integer("pages_fetched").notNull().default(0),
|
|
1269
|
+
pagesEligible: integer("pages_eligible").notNull().default(0),
|
|
1270
|
+
pagesErrored: integer("pages_errored").notNull().default(0),
|
|
1271
|
+
edgesDiscovered: integer("edges_discovered").notNull().default(0),
|
|
1272
|
+
findingsCount: integer("findings_count").notNull().default(0),
|
|
1273
|
+
deadLinkState: text("dead_link_state").notNull().default("disabled"),
|
|
1274
|
+
deadLinksChecked: integer("dead_links_checked").notNull().default(0),
|
|
1275
|
+
deadLinksFound: integer("dead_links_found").notNull().default(0),
|
|
1276
|
+
createdAt: text("created_at").notNull(),
|
|
1277
|
+
updatedAt: text("updated_at").notNull()
|
|
1278
|
+
}, (table) => [
|
|
1279
|
+
uniqueIndex("idx_site_crawl_snapshots_run").on(table.runId),
|
|
1280
|
+
index("idx_site_crawl_snapshots_project_created").on(table.projectId, table.createdAt),
|
|
1281
|
+
foreignKey({
|
|
1282
|
+
name: "site_crawl_snapshots_project_run_fk",
|
|
1283
|
+
columns: [table.projectId, table.runId],
|
|
1284
|
+
foreignColumns: [runs.projectId, runs.id]
|
|
1285
|
+
}).onDelete("cascade"),
|
|
1286
|
+
foreignKey({
|
|
1287
|
+
name: "site_crawl_snapshots_attempt_fk",
|
|
1288
|
+
columns: [table.projectId, table.runId, table.attemptId],
|
|
1289
|
+
foreignColumns: [siteCrawlAttempts.projectId, siteCrawlAttempts.runId, siteCrawlAttempts.id]
|
|
1290
|
+
})
|
|
1291
|
+
]);
|
|
1292
|
+
var siteCrawlPages = sqliteTable("site_crawl_pages", {
|
|
1293
|
+
id: text("id").primaryKey(),
|
|
1294
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1295
|
+
runId: text("run_id").notNull(),
|
|
1296
|
+
attemptId: text("attempt_id").notNull(),
|
|
1297
|
+
nodeKey: text("node_key").notNull(),
|
|
1298
|
+
url: text("url").notNull(),
|
|
1299
|
+
path: text("path").notNull(),
|
|
1300
|
+
parentPath: text("parent_path").notNull(),
|
|
1301
|
+
discoverySource: text("discovery_source").notNull().default("crawl"),
|
|
1302
|
+
discoveryProvenance: text("discovery_provenance", { mode: "json" }).$type().notNull().default([]),
|
|
1303
|
+
sitemapMetadata: text("sitemap_metadata", { mode: "json" }).$type().notNull().default({}),
|
|
1304
|
+
fetchState: text("fetch_state").notNull().default("queued"),
|
|
1305
|
+
fetchedAt: text("fetched_at"),
|
|
1306
|
+
httpStatus: integer("http_status"),
|
|
1307
|
+
contentType: text("content_type"),
|
|
1308
|
+
finalUrl: text("final_url"),
|
|
1309
|
+
redirectChain: text("redirect_chain", { mode: "json" }).$type().notNull().default([]),
|
|
1310
|
+
directives: text("directives", { mode: "json" }).$type().notNull().default({}),
|
|
1311
|
+
canonicalUrl: text("canonical_url"),
|
|
1312
|
+
canonicalNodeKey: text("canonical_node_key"),
|
|
1313
|
+
indexabilityState: text("indexability_state").notNull().default("unknown"),
|
|
1314
|
+
indexabilityReasons: text("indexability_reasons", { mode: "json" }).$type().notNull().default([]),
|
|
1315
|
+
auditState: text("audit_state").notNull().default("pending"),
|
|
1316
|
+
auditScore: real("audit_score"),
|
|
1317
|
+
auditFields: text("audit_fields", { mode: "json" }).$type().notNull().default({}),
|
|
1318
|
+
inventoryEligible: integer("inventory_eligible", { mode: "boolean" }).notNull().default(false),
|
|
1319
|
+
depth: integer("depth"),
|
|
1320
|
+
inboundUniqueEdges: integer("inbound_unique_edges").notNull().default(0),
|
|
1321
|
+
outboundUniqueEdges: integer("outbound_unique_edges").notNull().default(0),
|
|
1322
|
+
inboundOccurrences: integer("inbound_occurrences").notNull().default(0),
|
|
1323
|
+
outboundOccurrences: integer("outbound_occurrences").notNull().default(0),
|
|
1324
|
+
linkScoreRaw: real("link_score_raw"),
|
|
1325
|
+
linkScoreNormalized: real("link_score_normalized"),
|
|
1326
|
+
createdAt: text("created_at").notNull(),
|
|
1327
|
+
updatedAt: text("updated_at").notNull()
|
|
1328
|
+
}, (table) => [
|
|
1329
|
+
uniqueIndex("idx_site_crawl_pages_attempt_node").on(table.projectId, table.runId, table.attemptId, table.nodeKey),
|
|
1330
|
+
index("idx_site_crawl_pages_read").on(table.projectId, table.runId, table.attemptId, table.inventoryEligible, table.auditScore, table.url),
|
|
1331
|
+
index("idx_site_crawl_pages_parent").on(table.projectId, table.runId, table.attemptId, table.parentPath, table.path),
|
|
1332
|
+
index("idx_site_crawl_pages_url").on(table.projectId, table.runId, table.attemptId, table.url),
|
|
1333
|
+
foreignKey({
|
|
1334
|
+
name: "site_crawl_pages_attempt_fk",
|
|
1335
|
+
columns: [table.projectId, table.runId, table.attemptId],
|
|
1336
|
+
foreignColumns: [siteCrawlAttempts.projectId, siteCrawlAttempts.runId, siteCrawlAttempts.id]
|
|
1337
|
+
}).onDelete("cascade")
|
|
1338
|
+
]);
|
|
1339
|
+
var siteCrawlEdges = sqliteTable("site_crawl_edges", {
|
|
1340
|
+
id: text("id").primaryKey(),
|
|
1341
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1342
|
+
runId: text("run_id").notNull(),
|
|
1343
|
+
attemptId: text("attempt_id").notNull(),
|
|
1344
|
+
edgeKey: text("edge_key").notNull(),
|
|
1345
|
+
sourceNodeKey: text("source_node_key").notNull(),
|
|
1346
|
+
sourceUrl: text("source_url").notNull(),
|
|
1347
|
+
targetNodeKey: text("target_node_key"),
|
|
1348
|
+
targetUrl: text("target_url").notNull(),
|
|
1349
|
+
relation: text("relation").notNull().default("link"),
|
|
1350
|
+
internal: integer("internal", { mode: "boolean" }).notNull().default(true),
|
|
1351
|
+
followable: integer("followable", { mode: "boolean" }).notNull().default(true),
|
|
1352
|
+
occurrences: integer("occurrences").notNull().default(1),
|
|
1353
|
+
followableOccurrences: integer("followable_occurrences").notNull().default(1),
|
|
1354
|
+
nofollowOccurrences: integer("nofollow_occurrences").notNull().default(0),
|
|
1355
|
+
anchors: text("anchors", { mode: "json" }).$type().notNull().default([]),
|
|
1356
|
+
createdAt: text("created_at").notNull(),
|
|
1357
|
+
updatedAt: text("updated_at").notNull()
|
|
1358
|
+
}, (table) => [
|
|
1359
|
+
uniqueIndex("idx_site_crawl_edges_attempt_key").on(table.projectId, table.runId, table.attemptId, table.edgeKey),
|
|
1360
|
+
index("idx_site_crawl_edges_outbound").on(table.projectId, table.runId, table.attemptId, table.sourceNodeKey, table.edgeKey),
|
|
1361
|
+
index("idx_site_crawl_edges_inbound").on(table.projectId, table.runId, table.attemptId, table.targetNodeKey, table.edgeKey),
|
|
1362
|
+
index("idx_site_crawl_edges_source_url").on(table.projectId, table.runId, table.attemptId, table.sourceUrl),
|
|
1363
|
+
index("idx_site_crawl_edges_target_url").on(table.projectId, table.runId, table.attemptId, table.targetUrl),
|
|
1364
|
+
foreignKey({
|
|
1365
|
+
name: "site_crawl_edges_attempt_fk",
|
|
1366
|
+
columns: [table.projectId, table.runId, table.attemptId],
|
|
1367
|
+
foreignColumns: [siteCrawlAttempts.projectId, siteCrawlAttempts.runId, siteCrawlAttempts.id]
|
|
1368
|
+
}).onDelete("cascade")
|
|
1369
|
+
]);
|
|
1370
|
+
var siteCrawlFindings = sqliteTable("site_crawl_findings", {
|
|
1371
|
+
id: text("id").primaryKey(),
|
|
1372
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1373
|
+
runId: text("run_id").notNull(),
|
|
1374
|
+
attemptId: text("attempt_id").notNull(),
|
|
1375
|
+
findingKey: text("finding_key").notNull(),
|
|
1376
|
+
findingType: text("finding_type").notNull(),
|
|
1377
|
+
severity: text("severity").notNull().default("info"),
|
|
1378
|
+
sourceNodeKey: text("source_node_key"),
|
|
1379
|
+
sourceUrl: text("source_url"),
|
|
1380
|
+
targetNodeKey: text("target_node_key"),
|
|
1381
|
+
targetUrl: text("target_url"),
|
|
1382
|
+
evidence: text("evidence", { mode: "json" }).$type().notNull().default({}),
|
|
1383
|
+
createdAt: text("created_at").notNull(),
|
|
1384
|
+
updatedAt: text("updated_at").notNull()
|
|
1385
|
+
}, (table) => [
|
|
1386
|
+
uniqueIndex("idx_site_crawl_findings_attempt_key").on(table.projectId, table.runId, table.attemptId, table.findingKey),
|
|
1387
|
+
index("idx_site_crawl_findings_type").on(table.projectId, table.runId, table.attemptId, table.findingType, table.findingKey),
|
|
1388
|
+
foreignKey({
|
|
1389
|
+
name: "site_crawl_findings_attempt_fk",
|
|
1390
|
+
columns: [table.projectId, table.runId, table.attemptId],
|
|
1391
|
+
foreignColumns: [siteCrawlAttempts.projectId, siteCrawlAttempts.runId, siteCrawlAttempts.id]
|
|
1392
|
+
}).onDelete("cascade")
|
|
1393
|
+
]);
|
|
1394
|
+
var siteCrawlEventReceipts = sqliteTable("site_crawl_event_receipts", {
|
|
1395
|
+
id: text("id").primaryKey(),
|
|
1396
|
+
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
1397
|
+
runId: text("run_id").notNull(),
|
|
1398
|
+
attemptId: text("attempt_id").notNull(),
|
|
1399
|
+
sequence: integer("sequence").notNull(),
|
|
1400
|
+
batchId: text("batch_id").notNull(),
|
|
1401
|
+
checksum: text("checksum").notNull(),
|
|
1402
|
+
receipt: text("receipt", { mode: "json" }).$type().notNull().default({}),
|
|
1403
|
+
createdAt: text("created_at").notNull()
|
|
1404
|
+
}, (table) => [
|
|
1405
|
+
uniqueIndex("idx_site_crawl_receipts_attempt_event").on(table.attemptId, table.sequence, table.batchId),
|
|
1406
|
+
index("idx_site_crawl_receipts_project_run").on(table.projectId, table.runId, table.attemptId),
|
|
1407
|
+
foreignKey({
|
|
1408
|
+
name: "site_crawl_receipts_attempt_fk",
|
|
1409
|
+
columns: [table.projectId, table.runId, table.attemptId],
|
|
1410
|
+
foreignColumns: [siteCrawlAttempts.projectId, siteCrawlAttempts.runId, siteCrawlAttempts.id]
|
|
1411
|
+
}).onDelete("cascade")
|
|
1412
|
+
]);
|
|
1188
1413
|
var bingCoverageSnapshots = sqliteTable("bing_coverage_snapshots", {
|
|
1189
1414
|
id: text("id").primaryKey(),
|
|
1190
1415
|
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
@@ -5096,6 +5321,211 @@ var MIGRATION_VERSIONS = [
|
|
|
5096
5321
|
`ALTER TABLE gsc_coverage_snapshots ADD COLUMN verified_by_inspection INTEGER NOT NULL DEFAULT 0`,
|
|
5097
5322
|
`ALTER TABLE gsc_coverage_snapshots ADD COLUMN derived_from_impressions INTEGER NOT NULL DEFAULT 0`
|
|
5098
5323
|
]
|
|
5324
|
+
},
|
|
5325
|
+
{
|
|
5326
|
+
// Crawl persistence is intentionally separate from the legacy
|
|
5327
|
+
// site_audit_snapshots/site_audit_pages scorecard model. Older engines can
|
|
5328
|
+
// keep reading and writing that legacy surface after a rollback; new tables
|
|
5329
|
+
// only enrich an audit run when the crawler writes them.
|
|
5330
|
+
version: 126,
|
|
5331
|
+
name: "site-crawl-persistence",
|
|
5332
|
+
statements: [
|
|
5333
|
+
// Composite child FKs below enforce that project_id and run_id are a
|
|
5334
|
+
// matched pair. `runs.id` is globally unique already; this named unique
|
|
5335
|
+
// index provides SQLite's required parent key for the composite check.
|
|
5336
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_project_id ON runs(project_id, id)`,
|
|
5337
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_run_requests (
|
|
5338
|
+
run_id TEXT PRIMARY KEY,
|
|
5339
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5340
|
+
identity_key TEXT NOT NULL,
|
|
5341
|
+
effective_options TEXT NOT NULL,
|
|
5342
|
+
created_at TEXT NOT NULL,
|
|
5343
|
+
FOREIGN KEY (project_id, run_id) REFERENCES runs(project_id, id) ON DELETE CASCADE
|
|
5344
|
+
)`,
|
|
5345
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_run_requests_project
|
|
5346
|
+
ON site_crawl_run_requests(project_id, run_id)`,
|
|
5347
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_attempts (
|
|
5348
|
+
id TEXT PRIMARY KEY,
|
|
5349
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5350
|
+
run_id TEXT NOT NULL,
|
|
5351
|
+
attempt_number INTEGER NOT NULL,
|
|
5352
|
+
state TEXT NOT NULL DEFAULT 'queued',
|
|
5353
|
+
last_event_sequence INTEGER NOT NULL DEFAULT 0,
|
|
5354
|
+
last_event_checksum TEXT,
|
|
5355
|
+
pages_discovered INTEGER NOT NULL DEFAULT 0,
|
|
5356
|
+
pages_fetched INTEGER NOT NULL DEFAULT 0,
|
|
5357
|
+
pages_eligible INTEGER NOT NULL DEFAULT 0,
|
|
5358
|
+
pages_errored INTEGER NOT NULL DEFAULT 0,
|
|
5359
|
+
edges_discovered INTEGER NOT NULL DEFAULT 0,
|
|
5360
|
+
started_at TEXT,
|
|
5361
|
+
finished_at TEXT,
|
|
5362
|
+
error TEXT,
|
|
5363
|
+
created_at TEXT NOT NULL,
|
|
5364
|
+
updated_at TEXT NOT NULL,
|
|
5365
|
+
FOREIGN KEY (project_id, run_id) REFERENCES runs(project_id, id) ON DELETE CASCADE
|
|
5366
|
+
)`,
|
|
5367
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_attempts_run_number
|
|
5368
|
+
ON site_crawl_attempts(run_id, attempt_number)`,
|
|
5369
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_attempts_project_run_id
|
|
5370
|
+
ON site_crawl_attempts(project_id, run_id, id)`,
|
|
5371
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_attempts_project_run
|
|
5372
|
+
ON site_crawl_attempts(project_id, run_id)`,
|
|
5373
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_snapshots (
|
|
5374
|
+
id TEXT PRIMARY KEY,
|
|
5375
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5376
|
+
run_id TEXT NOT NULL,
|
|
5377
|
+
attempt_id TEXT,
|
|
5378
|
+
root_url TEXT NOT NULL,
|
|
5379
|
+
crawl_schema_version TEXT NOT NULL DEFAULT '1.0',
|
|
5380
|
+
engine_version TEXT NOT NULL DEFAULT '',
|
|
5381
|
+
normalization_version TEXT NOT NULL DEFAULT '',
|
|
5382
|
+
indexability_version TEXT NOT NULL DEFAULT '',
|
|
5383
|
+
link_score_version TEXT NOT NULL DEFAULT '',
|
|
5384
|
+
effective_options TEXT NOT NULL DEFAULT '{}',
|
|
5385
|
+
page_budget INTEGER,
|
|
5386
|
+
edge_budget INTEGER,
|
|
5387
|
+
max_depth INTEGER,
|
|
5388
|
+
check_dead_links INTEGER NOT NULL DEFAULT 0,
|
|
5389
|
+
complete INTEGER NOT NULL DEFAULT 0,
|
|
5390
|
+
termination TEXT NOT NULL DEFAULT 'unknown',
|
|
5391
|
+
details_available INTEGER NOT NULL DEFAULT 0,
|
|
5392
|
+
pages_discovered INTEGER NOT NULL DEFAULT 0,
|
|
5393
|
+
pages_fetched INTEGER NOT NULL DEFAULT 0,
|
|
5394
|
+
pages_eligible INTEGER NOT NULL DEFAULT 0,
|
|
5395
|
+
pages_errored INTEGER NOT NULL DEFAULT 0,
|
|
5396
|
+
edges_discovered INTEGER NOT NULL DEFAULT 0,
|
|
5397
|
+
findings_count INTEGER NOT NULL DEFAULT 0,
|
|
5398
|
+
dead_link_state TEXT NOT NULL DEFAULT 'disabled',
|
|
5399
|
+
dead_links_checked INTEGER NOT NULL DEFAULT 0,
|
|
5400
|
+
dead_links_found INTEGER NOT NULL DEFAULT 0,
|
|
5401
|
+
created_at TEXT NOT NULL,
|
|
5402
|
+
updated_at TEXT NOT NULL,
|
|
5403
|
+
FOREIGN KEY (project_id, run_id) REFERENCES runs(project_id, id) ON DELETE CASCADE,
|
|
5404
|
+
FOREIGN KEY (project_id, run_id, attempt_id)
|
|
5405
|
+
REFERENCES site_crawl_attempts(project_id, run_id, id)
|
|
5406
|
+
)`,
|
|
5407
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_snapshots_run ON site_crawl_snapshots(run_id)`,
|
|
5408
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_snapshots_project_created
|
|
5409
|
+
ON site_crawl_snapshots(project_id, created_at)`,
|
|
5410
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_pages (
|
|
5411
|
+
id TEXT PRIMARY KEY,
|
|
5412
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5413
|
+
run_id TEXT NOT NULL,
|
|
5414
|
+
attempt_id TEXT NOT NULL,
|
|
5415
|
+
node_key TEXT NOT NULL,
|
|
5416
|
+
url TEXT NOT NULL,
|
|
5417
|
+
path TEXT NOT NULL,
|
|
5418
|
+
parent_path TEXT NOT NULL,
|
|
5419
|
+
discovery_source TEXT NOT NULL DEFAULT 'crawl',
|
|
5420
|
+
discovery_provenance TEXT NOT NULL DEFAULT '[]',
|
|
5421
|
+
sitemap_metadata TEXT NOT NULL DEFAULT '{}',
|
|
5422
|
+
fetch_state TEXT NOT NULL DEFAULT 'queued',
|
|
5423
|
+
fetched_at TEXT,
|
|
5424
|
+
http_status INTEGER,
|
|
5425
|
+
content_type TEXT,
|
|
5426
|
+
final_url TEXT,
|
|
5427
|
+
redirect_chain TEXT NOT NULL DEFAULT '[]',
|
|
5428
|
+
directives TEXT NOT NULL DEFAULT '{}',
|
|
5429
|
+
canonical_url TEXT,
|
|
5430
|
+
canonical_node_key TEXT,
|
|
5431
|
+
indexability_state TEXT NOT NULL DEFAULT 'unknown',
|
|
5432
|
+
indexability_reasons TEXT NOT NULL DEFAULT '[]',
|
|
5433
|
+
audit_state TEXT NOT NULL DEFAULT 'pending',
|
|
5434
|
+
audit_score REAL,
|
|
5435
|
+
audit_fields TEXT NOT NULL DEFAULT '{}',
|
|
5436
|
+
inventory_eligible INTEGER NOT NULL DEFAULT 0,
|
|
5437
|
+
depth INTEGER,
|
|
5438
|
+
inbound_unique_edges INTEGER NOT NULL DEFAULT 0,
|
|
5439
|
+
outbound_unique_edges INTEGER NOT NULL DEFAULT 0,
|
|
5440
|
+
inbound_occurrences INTEGER NOT NULL DEFAULT 0,
|
|
5441
|
+
outbound_occurrences INTEGER NOT NULL DEFAULT 0,
|
|
5442
|
+
link_score_raw REAL,
|
|
5443
|
+
link_score_normalized REAL,
|
|
5444
|
+
created_at TEXT NOT NULL,
|
|
5445
|
+
updated_at TEXT NOT NULL,
|
|
5446
|
+
FOREIGN KEY (project_id, run_id, attempt_id)
|
|
5447
|
+
REFERENCES site_crawl_attempts(project_id, run_id, id) ON DELETE CASCADE
|
|
5448
|
+
)`,
|
|
5449
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_pages_attempt_node
|
|
5450
|
+
ON site_crawl_pages(project_id, run_id, attempt_id, node_key)`,
|
|
5451
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_pages_read
|
|
5452
|
+
ON site_crawl_pages(project_id, run_id, attempt_id, inventory_eligible, audit_score, url)`,
|
|
5453
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_pages_parent
|
|
5454
|
+
ON site_crawl_pages(project_id, run_id, attempt_id, parent_path, path)`,
|
|
5455
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_pages_url
|
|
5456
|
+
ON site_crawl_pages(project_id, run_id, attempt_id, url)`,
|
|
5457
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_edges (
|
|
5458
|
+
id TEXT PRIMARY KEY,
|
|
5459
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5460
|
+
run_id TEXT NOT NULL,
|
|
5461
|
+
attempt_id TEXT NOT NULL,
|
|
5462
|
+
edge_key TEXT NOT NULL,
|
|
5463
|
+
source_node_key TEXT NOT NULL,
|
|
5464
|
+
source_url TEXT NOT NULL,
|
|
5465
|
+
target_node_key TEXT,
|
|
5466
|
+
target_url TEXT NOT NULL,
|
|
5467
|
+
relation TEXT NOT NULL DEFAULT 'link',
|
|
5468
|
+
internal INTEGER NOT NULL DEFAULT 1,
|
|
5469
|
+
followable INTEGER NOT NULL DEFAULT 1,
|
|
5470
|
+
occurrences INTEGER NOT NULL DEFAULT 1,
|
|
5471
|
+
followable_occurrences INTEGER NOT NULL DEFAULT 1,
|
|
5472
|
+
nofollow_occurrences INTEGER NOT NULL DEFAULT 0,
|
|
5473
|
+
anchors TEXT NOT NULL DEFAULT '[]',
|
|
5474
|
+
created_at TEXT NOT NULL,
|
|
5475
|
+
updated_at TEXT NOT NULL,
|
|
5476
|
+
FOREIGN KEY (project_id, run_id, attempt_id)
|
|
5477
|
+
REFERENCES site_crawl_attempts(project_id, run_id, id) ON DELETE CASCADE
|
|
5478
|
+
)`,
|
|
5479
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_edges_attempt_key
|
|
5480
|
+
ON site_crawl_edges(project_id, run_id, attempt_id, edge_key)`,
|
|
5481
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_edges_outbound
|
|
5482
|
+
ON site_crawl_edges(project_id, run_id, attempt_id, source_node_key, edge_key)`,
|
|
5483
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_edges_inbound
|
|
5484
|
+
ON site_crawl_edges(project_id, run_id, attempt_id, target_node_key, edge_key)`,
|
|
5485
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_edges_source_url
|
|
5486
|
+
ON site_crawl_edges(project_id, run_id, attempt_id, source_url)`,
|
|
5487
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_edges_target_url
|
|
5488
|
+
ON site_crawl_edges(project_id, run_id, attempt_id, target_url)`,
|
|
5489
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_findings (
|
|
5490
|
+
id TEXT PRIMARY KEY,
|
|
5491
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5492
|
+
run_id TEXT NOT NULL,
|
|
5493
|
+
attempt_id TEXT NOT NULL,
|
|
5494
|
+
finding_key TEXT NOT NULL,
|
|
5495
|
+
finding_type TEXT NOT NULL,
|
|
5496
|
+
severity TEXT NOT NULL DEFAULT 'info',
|
|
5497
|
+
source_node_key TEXT,
|
|
5498
|
+
source_url TEXT,
|
|
5499
|
+
target_node_key TEXT,
|
|
5500
|
+
target_url TEXT,
|
|
5501
|
+
evidence TEXT NOT NULL DEFAULT '{}',
|
|
5502
|
+
created_at TEXT NOT NULL,
|
|
5503
|
+
updated_at TEXT NOT NULL,
|
|
5504
|
+
FOREIGN KEY (project_id, run_id, attempt_id)
|
|
5505
|
+
REFERENCES site_crawl_attempts(project_id, run_id, id) ON DELETE CASCADE
|
|
5506
|
+
)`,
|
|
5507
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_findings_attempt_key
|
|
5508
|
+
ON site_crawl_findings(project_id, run_id, attempt_id, finding_key)`,
|
|
5509
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_findings_type
|
|
5510
|
+
ON site_crawl_findings(project_id, run_id, attempt_id, finding_type, finding_key)`,
|
|
5511
|
+
`CREATE TABLE IF NOT EXISTS site_crawl_event_receipts (
|
|
5512
|
+
id TEXT PRIMARY KEY,
|
|
5513
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
5514
|
+
run_id TEXT NOT NULL,
|
|
5515
|
+
attempt_id TEXT NOT NULL,
|
|
5516
|
+
sequence INTEGER NOT NULL,
|
|
5517
|
+
batch_id TEXT NOT NULL,
|
|
5518
|
+
checksum TEXT NOT NULL,
|
|
5519
|
+
receipt TEXT NOT NULL DEFAULT '{}',
|
|
5520
|
+
created_at TEXT NOT NULL,
|
|
5521
|
+
FOREIGN KEY (project_id, run_id, attempt_id)
|
|
5522
|
+
REFERENCES site_crawl_attempts(project_id, run_id, id) ON DELETE CASCADE
|
|
5523
|
+
)`,
|
|
5524
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_site_crawl_receipts_attempt_event
|
|
5525
|
+
ON site_crawl_event_receipts(attempt_id, sequence, batch_id)`,
|
|
5526
|
+
`CREATE INDEX IF NOT EXISTS idx_site_crawl_receipts_project_run
|
|
5527
|
+
ON site_crawl_event_receipts(project_id, run_id, attempt_id)`
|
|
5528
|
+
]
|
|
5099
5529
|
}
|
|
5100
5530
|
];
|
|
5101
5531
|
function addRunsMeasurementPlanVersionForeignKey(tx) {
|
|
@@ -7968,7 +8398,7 @@ function isOverviewRead(url) {
|
|
|
7968
8398
|
}
|
|
7969
8399
|
function isTechnicalAeoRead(url) {
|
|
7970
8400
|
const rest = projectRouteRest(url);
|
|
7971
|
-
return rest === "technical-aeo" || rest === "technical-aeo/pages" || rest === "technical-aeo/trend";
|
|
8401
|
+
return rest === "technical-aeo" || rest === "technical-aeo/pages" || rest === "technical-aeo/trend" || rest === "technical-aeo/crawl" || rest === "technical-aeo/crawl/pages" || rest === "technical-aeo/structure" || rest === "technical-aeo/internal-links" || rest === "technical-aeo/internal-links/neighbors" || rest === "technical-aeo/dead-links";
|
|
7972
8402
|
}
|
|
7973
8403
|
function isReportRead(url) {
|
|
7974
8404
|
const rest = projectRouteRest(url);
|
|
@@ -9206,7 +9636,7 @@ function parseCompetitorBatch(value) {
|
|
|
9206
9636
|
|
|
9207
9637
|
// ../api-routes/src/runs.ts
|
|
9208
9638
|
import crypto12 from "crypto";
|
|
9209
|
-
import { and as and6, eq as eq11, asc, desc as desc3, or as or2, sql as sql6 } from "drizzle-orm";
|
|
9639
|
+
import { and as and6, eq as eq11, asc, desc as desc3, inArray as inArray4, or as or2, sql as sql6 } from "drizzle-orm";
|
|
9210
9640
|
import { gte as gte2 } from "drizzle-orm";
|
|
9211
9641
|
|
|
9212
9642
|
// ../api-routes/src/run-queue.ts
|
|
@@ -10954,7 +11384,12 @@ async function runRoutes(app, opts) {
|
|
|
10954
11384
|
const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
|
|
10955
11385
|
if (terminalStatuses.has(run.status)) throw runNotCancellable(run.id, run.status);
|
|
10956
11386
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10957
|
-
app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(eq11(runs.id, run.id)).run();
|
|
11387
|
+
const cancelled = app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(and6(eq11(runs.id, run.id), inArray4(runs.status, ["queued", "running"]))).run();
|
|
11388
|
+
if (cancelled.changes === 0) {
|
|
11389
|
+
const current = app.db.select().from(runs).where(eq11(runs.id, run.id)).get();
|
|
11390
|
+
if (!current) throw notFound("Run", run.id);
|
|
11391
|
+
throw runNotCancellable(run.id, current.status);
|
|
11392
|
+
}
|
|
10958
11393
|
writeAuditLog(app.db, {
|
|
10959
11394
|
projectId: run.projectId,
|
|
10960
11395
|
actor: "api",
|
|
@@ -10962,6 +11397,7 @@ async function runRoutes(app, opts) {
|
|
|
10962
11397
|
entityType: "run",
|
|
10963
11398
|
entityId: run.id
|
|
10964
11399
|
});
|
|
11400
|
+
opts.onRunCancelled?.(run.id, run.projectId);
|
|
10965
11401
|
const updated = app.db.select().from(runs).where(eq11(runs.id, run.id)).get();
|
|
10966
11402
|
return reply.send(formatRun(updated));
|
|
10967
11403
|
});
|
|
@@ -12231,7 +12667,7 @@ async function measurementServiceRoutes(app, opts) {
|
|
|
12231
12667
|
|
|
12232
12668
|
// ../api-routes/src/measurement-draft.ts
|
|
12233
12669
|
import crypto17 from "crypto";
|
|
12234
|
-
import { and as and9, asc as asc2, desc as desc5, eq as eq14, inArray as
|
|
12670
|
+
import { and as and9, asc as asc2, desc as desc5, eq as eq14, inArray as inArray5 } from "drizzle-orm";
|
|
12235
12671
|
|
|
12236
12672
|
// ../api-routes/src/measurement-draft-repo.ts
|
|
12237
12673
|
import crypto15 from "crypto";
|
|
@@ -14753,7 +15189,7 @@ async function measurementDraftRoutes(app, opts = {}) {
|
|
|
14753
15189
|
const sets = app.db.select().from(measurementQuerySets).where(eq14(measurementQuerySets.projectId, project.id)).orderBy(asc2(measurementQuerySets.name)).all();
|
|
14754
15190
|
const counts2 = /* @__PURE__ */ new Map();
|
|
14755
15191
|
if (sets.length) {
|
|
14756
|
-
for (const item2 of app.db.select({ querySetId: measurementQuerySetItems.querySetId }).from(measurementQuerySetItems).where(
|
|
15192
|
+
for (const item2 of app.db.select({ querySetId: measurementQuerySetItems.querySetId }).from(measurementQuerySetItems).where(inArray5(measurementQuerySetItems.querySetId, sets.map((set) => set.id))).all()) {
|
|
14757
15193
|
counts2.set(item2.querySetId, (counts2.get(item2.querySetId) ?? 0) + 1);
|
|
14758
15194
|
}
|
|
14759
15195
|
}
|
|
@@ -16276,10 +16712,10 @@ async function measurementPropertyEvidenceRoutes(app) {
|
|
|
16276
16712
|
}
|
|
16277
16713
|
|
|
16278
16714
|
// ../api-routes/src/measurement-portfolio-reads.ts
|
|
16279
|
-
import { and as and14, desc as desc6, eq as eq20, inArray as
|
|
16715
|
+
import { and as and14, desc as desc6, eq as eq20, inArray as inArray7, lt as lt3, or as or3 } from "drizzle-orm";
|
|
16280
16716
|
|
|
16281
16717
|
// ../api-routes/src/measurement-question-reads.ts
|
|
16282
|
-
import { and as and13, eq as eq19, getTableColumns, inArray as
|
|
16718
|
+
import { and as and13, eq as eq19, getTableColumns, inArray as inArray6, sql as sql7 } from "drizzle-orm";
|
|
16283
16719
|
var DEFAULT_LIMIT2 = 50;
|
|
16284
16720
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
16285
16721
|
RunStatuses.completed,
|
|
@@ -16395,7 +16831,7 @@ function selectedQuestionSnapshots(db, runId, manifest, scope) {
|
|
|
16395
16831
|
if (scope !== void 0) {
|
|
16396
16832
|
const executionIds = [...new Set(manifest.expectedSlots.map((slot) => slot.executionId))];
|
|
16397
16833
|
if (executionIds.length === 0) return [];
|
|
16398
|
-
conditions.push(
|
|
16834
|
+
conditions.push(inArray6(querySnapshots.measurementExecutionId, executionIds));
|
|
16399
16835
|
}
|
|
16400
16836
|
if (scope?.provider !== void 0) {
|
|
16401
16837
|
conditions.push(sql7`lower(trim(${querySnapshots.provider})) = ${normalizeText(scope.provider)}`);
|
|
@@ -16410,7 +16846,7 @@ function rawResponsesForLegacyQuestionSnapshots(db, runId, snapshots) {
|
|
|
16410
16846
|
rawResponse: querySnapshots.rawResponse
|
|
16411
16847
|
}).from(querySnapshots).where(and13(
|
|
16412
16848
|
eq19(querySnapshots.runId, runId),
|
|
16413
|
-
|
|
16849
|
+
inArray6(querySnapshots.id, legacyIds)
|
|
16414
16850
|
)).all().map((snapshot) => [snapshot.id, snapshot.rawResponse]));
|
|
16415
16851
|
}
|
|
16416
16852
|
function withLegacyRawEvidence(db, runId, snapshots) {
|
|
@@ -17056,7 +17492,7 @@ function previousComparableRun(db, active, current) {
|
|
|
17056
17492
|
eq20(runs.projectId, current.projectId),
|
|
17057
17493
|
eq20(runs.measurementPlanVersionId, active.version.id),
|
|
17058
17494
|
eq20(runs.kind, RunKinds["answer-visibility"]),
|
|
17059
|
-
|
|
17495
|
+
inArray7(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
17060
17496
|
or3(
|
|
17061
17497
|
lt3(runs.createdAt, current.createdAt),
|
|
17062
17498
|
and14(eq20(runs.createdAt, current.createdAt), lt3(runs.id, current.id))
|
|
@@ -17882,7 +18318,7 @@ function normalizeCompetitorList2(domains) {
|
|
|
17882
18318
|
}
|
|
17883
18319
|
|
|
17884
18320
|
// ../api-routes/src/history.ts
|
|
17885
|
-
import { and as and16, asc as asc3, desc as desc7, eq as eq22, gte as gte3, inArray as
|
|
18321
|
+
import { and as and16, asc as asc3, desc as desc7, eq as eq22, gte as gte3, inArray as inArray8 } from "drizzle-orm";
|
|
17886
18322
|
|
|
17887
18323
|
// ../api-routes/src/notification-redaction.ts
|
|
17888
18324
|
var REDACTED_URL = {
|
|
@@ -17968,7 +18404,7 @@ async function historyRoutes(app) {
|
|
|
17968
18404
|
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
17969
18405
|
location: querySnapshots.location,
|
|
17970
18406
|
createdAt: querySnapshots.createdAt
|
|
17971
|
-
}).from(querySnapshots).leftJoin(queries, eq22(querySnapshots.queryId, queries.id)).where(
|
|
18407
|
+
}).from(querySnapshots).leftJoin(queries, eq22(querySnapshots.queryId, queries.id)).where(inArray8(querySnapshots.runId, projectRuns.map((r) => r.id))).orderBy(desc7(querySnapshots.createdAt)).all();
|
|
17972
18408
|
const locationFilter = request.query.location;
|
|
17973
18409
|
const filtered = locationFilter !== void 0 ? allSnapshots.filter((s) => s.location === (locationFilter || null)) : allSnapshots;
|
|
17974
18410
|
const total = filtered.length;
|
|
@@ -18014,7 +18450,7 @@ async function historyRoutes(app) {
|
|
|
18014
18450
|
return reply.send([]);
|
|
18015
18451
|
}
|
|
18016
18452
|
const runIds = new Set(projectRuns.map((r) => r.id));
|
|
18017
|
-
const rawSnapshots = app.db.select().from(querySnapshots).where(
|
|
18453
|
+
const rawSnapshots = app.db.select().from(querySnapshots).where(inArray8(querySnapshots.runId, [...runIds])).all();
|
|
18018
18454
|
const timelineLocationFilter = request.query.location;
|
|
18019
18455
|
const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
|
|
18020
18456
|
const allSnapshots = filteredSnapshots.map((snapshot) => ({
|
|
@@ -18135,7 +18571,7 @@ async function historyRoutes(app) {
|
|
|
18135
18571
|
throw validationError("Both run1 and run2 query params are required");
|
|
18136
18572
|
}
|
|
18137
18573
|
const requestedRunIds = [.../* @__PURE__ */ new Set([run1, run2])];
|
|
18138
|
-
const runRows = app.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
18574
|
+
const runRows = app.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(inArray8(runs.id, requestedRunIds)).all();
|
|
18139
18575
|
const runsById = new Map(runRows.map((row) => [row.id, row]));
|
|
18140
18576
|
for (const runId of requestedRunIds) {
|
|
18141
18577
|
const run = runsById.get(runId);
|
|
@@ -18233,7 +18669,7 @@ function parseOptionalPositiveInt(value, max) {
|
|
|
18233
18669
|
}
|
|
18234
18670
|
|
|
18235
18671
|
// ../api-routes/src/analytics.ts
|
|
18236
|
-
import { and as and17, desc as desc8, eq as eq23, gte as gte4, inArray as
|
|
18672
|
+
import { and as and17, desc as desc8, eq as eq23, gte as gte4, inArray as inArray9, lt as lt4 } from "drizzle-orm";
|
|
18237
18673
|
|
|
18238
18674
|
// ../api-routes/src/model-evidence.ts
|
|
18239
18675
|
function classifyModelEvidence(values) {
|
|
@@ -18368,7 +18804,7 @@ async function analyticsRoutes(app) {
|
|
|
18368
18804
|
const projectRuns = app.db.select().from(runs).where(and17(
|
|
18369
18805
|
eq23(runs.projectId, project.id),
|
|
18370
18806
|
eq23(runs.kind, RunKinds["answer-visibility"]),
|
|
18371
|
-
|
|
18807
|
+
inArray9(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
18372
18808
|
notProbeRun(),
|
|
18373
18809
|
cutoff ? gte4(runs.createdAt, cutoff) : void 0
|
|
18374
18810
|
)).orderBy(desc8(runs.createdAt)).all().filter((r) => measuredWhole(app.db, r)).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -18411,7 +18847,7 @@ async function analyticsRoutes(app) {
|
|
|
18411
18847
|
citationState: querySnapshots.citationState,
|
|
18412
18848
|
answerMentioned: querySnapshots.answerMentioned,
|
|
18413
18849
|
answerText: querySnapshots.answerText
|
|
18414
|
-
}).from(querySnapshots).where(
|
|
18850
|
+
}).from(querySnapshots).where(inArray9(querySnapshots.runId, runIds)).all();
|
|
18415
18851
|
const rawSnapshots = referenceBasket ? loadedSnapshots.filter(
|
|
18416
18852
|
(s) => s.queryId !== null || s.queryText !== null && referenceBasket.has(normalizeQueryText(s.queryText))
|
|
18417
18853
|
) : filterTrackedSnapshots(loadedSnapshots);
|
|
@@ -18453,7 +18889,7 @@ async function analyticsRoutes(app) {
|
|
|
18453
18889
|
const anchorRunPredicate = and17(
|
|
18454
18890
|
eq23(runs.projectId, project.id),
|
|
18455
18891
|
eq23(runs.kind, RunKinds["answer-visibility"]),
|
|
18456
|
-
|
|
18892
|
+
inArray9(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
18457
18893
|
notProbeRun(),
|
|
18458
18894
|
lt4(runs.createdAt, cutoff)
|
|
18459
18895
|
);
|
|
@@ -18483,7 +18919,7 @@ async function analyticsRoutes(app) {
|
|
|
18483
18919
|
provider: querySnapshots.provider,
|
|
18484
18920
|
model: querySnapshots.model,
|
|
18485
18921
|
servedModel: querySnapshots.servedModel
|
|
18486
|
-
}).from(querySnapshots).where(
|
|
18922
|
+
}).from(querySnapshots).where(inArray9(querySnapshots.runId, runIds2)).all())) {
|
|
18487
18923
|
if (pending.has(snapshot.provider)) {
|
|
18488
18924
|
const models = modelsByProvider.get(snapshot.provider) ?? [];
|
|
18489
18925
|
models.push(snapshot.model);
|
|
@@ -18629,7 +19065,7 @@ async function analyticsRoutes(app) {
|
|
|
18629
19065
|
citationState: querySnapshots.citationState,
|
|
18630
19066
|
answerMentioned: querySnapshots.answerMentioned,
|
|
18631
19067
|
answerText: querySnapshots.answerText
|
|
18632
|
-
}).from(querySnapshots).where(
|
|
19068
|
+
}).from(querySnapshots).where(inArray9(querySnapshots.runId, windowRunIds)).all());
|
|
18633
19069
|
for (const s of allWindowSnaps) {
|
|
18634
19070
|
const timePoint = runIdToCreatedAt.get(s.runId) ?? s.runId;
|
|
18635
19071
|
let entry = consistencyMap.get(s.queryId);
|
|
@@ -18650,7 +19086,7 @@ async function analyticsRoutes(app) {
|
|
|
18650
19086
|
answerMentioned: querySnapshots.answerMentioned,
|
|
18651
19087
|
answerText: querySnapshots.answerText,
|
|
18652
19088
|
competitorOverlap: querySnapshots.competitorOverlap
|
|
18653
|
-
}).from(querySnapshots).leftJoin(queries, eq23(querySnapshots.queryId, queries.id)).where(
|
|
19089
|
+
}).from(querySnapshots).leftJoin(queries, eq23(querySnapshots.queryId, queries.id)).where(inArray9(querySnapshots.runId, latestGroupRunIds)).all());
|
|
18654
19090
|
const snapshots = rawSnapshots.map((s) => ({
|
|
18655
19091
|
...s,
|
|
18656
19092
|
resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
|
|
@@ -18769,7 +19205,7 @@ async function analyticsRoutes(app) {
|
|
|
18769
19205
|
query: queries.query,
|
|
18770
19206
|
provider: querySnapshots.provider,
|
|
18771
19207
|
rawResponse: querySnapshots.rawResponse
|
|
18772
|
-
}).from(querySnapshots).leftJoin(queries, eq23(querySnapshots.queryId, queries.id)).where(
|
|
19208
|
+
}).from(querySnapshots).leftJoin(queries, eq23(querySnapshots.queryId, queries.id)).where(inArray9(querySnapshots.runId, windowRunIds)).all();
|
|
18773
19209
|
const overallCounts = /* @__PURE__ */ new Map();
|
|
18774
19210
|
const byQuery = {};
|
|
18775
19211
|
const overallDomains = /* @__PURE__ */ new Map();
|
|
@@ -19041,7 +19477,7 @@ function buildCategoryCounts(counts2) {
|
|
|
19041
19477
|
}
|
|
19042
19478
|
|
|
19043
19479
|
// ../api-routes/src/intelligence.ts
|
|
19044
|
-
import { eq as eq24, desc as desc9, and as and18, inArray as
|
|
19480
|
+
import { eq as eq24, desc as desc9, and as and18, inArray as inArray10, like } from "drizzle-orm";
|
|
19045
19481
|
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
19046
19482
|
function severitiesAtOrAbove(min) {
|
|
19047
19483
|
const floor = SEVERITY_RANK[min];
|
|
@@ -19171,7 +19607,7 @@ async function intelligenceRoutes(app) {
|
|
|
19171
19607
|
}
|
|
19172
19608
|
const severityFilter = request.query.severity?.trim();
|
|
19173
19609
|
if (severityFilter) {
|
|
19174
|
-
conditions.push(
|
|
19610
|
+
conditions.push(inArray10(insights.severity, severitiesAtOrAbove(severityFilter)));
|
|
19175
19611
|
}
|
|
19176
19612
|
let limit;
|
|
19177
19613
|
if (request.query.limit !== void 0) {
|
|
@@ -19209,7 +19645,7 @@ async function intelligenceRoutes(app) {
|
|
|
19209
19645
|
const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and18(
|
|
19210
19646
|
eq24(runs.projectId, project.id),
|
|
19211
19647
|
eq24(runs.kind, RunKinds["answer-visibility"]),
|
|
19212
|
-
|
|
19648
|
+
inArray10(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
19213
19649
|
// Health-latest is the dashboard headline; probe runs must not
|
|
19214
19650
|
// displace the most recent real visibility sweep.
|
|
19215
19651
|
notProbeRun()
|
|
@@ -19219,7 +19655,7 @@ async function intelligenceRoutes(app) {
|
|
|
19219
19655
|
if (latestGroupRunIds.length > 0) {
|
|
19220
19656
|
const groupRows = app.db.select().from(healthSnapshots).where(and18(
|
|
19221
19657
|
eq24(healthSnapshots.projectId, project.id),
|
|
19222
|
-
|
|
19658
|
+
inArray10(healthSnapshots.runId, latestGroupRunIds)
|
|
19223
19659
|
)).all();
|
|
19224
19660
|
if (groupRows.length > 0) {
|
|
19225
19661
|
return reply.send(aggregateHealthSnapshots(project.id, groupRows));
|
|
@@ -19241,14 +19677,14 @@ async function intelligenceRoutes(app) {
|
|
|
19241
19677
|
}
|
|
19242
19678
|
|
|
19243
19679
|
// ../api-routes/src/report.ts
|
|
19244
|
-
import { and as and22, desc as desc12, eq as eq28, gte as gte5, inArray as
|
|
19680
|
+
import { and as and22, desc as desc12, eq as eq28, gte as gte5, inArray as inArray12, lt as lt5, lte as lte3, ne as ne3, or as or4, sql as sql9 } from "drizzle-orm";
|
|
19245
19681
|
|
|
19246
19682
|
// ../api-routes/src/content.ts
|
|
19247
19683
|
import crypto21 from "crypto";
|
|
19248
19684
|
import { and as and20, desc as desc11, eq as eq26 } from "drizzle-orm";
|
|
19249
19685
|
|
|
19250
19686
|
// ../api-routes/src/content-data.ts
|
|
19251
|
-
import { and as and19, eq as eq25, desc as desc10, inArray as
|
|
19687
|
+
import { and as and19, eq as eq25, desc as desc10, inArray as inArray11 } from "drizzle-orm";
|
|
19252
19688
|
var RECENT_RUNS_WINDOW = 5;
|
|
19253
19689
|
function loadOrchestratorInput(db, project, locationFilter = void 0) {
|
|
19254
19690
|
const projectId = project.id;
|
|
@@ -19384,7 +19820,7 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
|
|
|
19384
19820
|
// Queued/running/failed/cancelled runs may have partial or no
|
|
19385
19821
|
// snapshots; including them risks pointing latestRunId at a run with
|
|
19386
19822
|
// no usable evidence.
|
|
19387
|
-
|
|
19823
|
+
inArray11(runs.status, [RunStatuses.completed, RunStatuses.partial]),
|
|
19388
19824
|
// Probe runs are operator/agent test runs; they must not poison the
|
|
19389
19825
|
// recent-runs window the content engine uses to recommend actions.
|
|
19390
19826
|
notProbeRun()
|
|
@@ -19434,7 +19870,7 @@ function buildCandidateQueries(opts) {
|
|
|
19434
19870
|
const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq25(queries.projectId, opts.projectId)).all();
|
|
19435
19871
|
const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
|
|
19436
19872
|
const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
|
|
19437
|
-
const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(
|
|
19873
|
+
const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray11(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
|
|
19438
19874
|
const snapshotsByQuery = /* @__PURE__ */ new Map();
|
|
19439
19875
|
for (const row of snapshotRows) {
|
|
19440
19876
|
const list = snapshotsByQuery.get(row.queryId) ?? [];
|
|
@@ -22361,7 +22797,7 @@ function loadSnapshotsForRun(db, runId) {
|
|
|
22361
22797
|
}
|
|
22362
22798
|
function loadSnapshotsForRunIds(db, runIds) {
|
|
22363
22799
|
if (runIds.length === 0) return [];
|
|
22364
|
-
const rows = db.select().from(querySnapshots).where(
|
|
22800
|
+
const rows = db.select().from(querySnapshots).where(inArray12(querySnapshots.runId, [...runIds])).all();
|
|
22365
22801
|
return rows.filter((r) => r.queryId !== null).map((r) => ({
|
|
22366
22802
|
id: r.id,
|
|
22367
22803
|
runId: r.runId,
|
|
@@ -22384,15 +22820,19 @@ function loadQueryLookup(db, projectId) {
|
|
|
22384
22820
|
return { byId };
|
|
22385
22821
|
}
|
|
22386
22822
|
function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays2) {
|
|
22387
|
-
const
|
|
22388
|
-
const
|
|
22389
|
-
|
|
22390
|
-
for (const r of allRows) if (r.date > maxDate) maxDate = r.date;
|
|
22391
|
-
for (const r of allDailyTotals) if (r.date > maxDate) maxDate = r.date;
|
|
22823
|
+
const maxSearch = db.select({ m: sql9`MAX(${gscSearchData.date})` }).from(gscSearchData).where(eq28(gscSearchData.projectId, projectId)).get()?.m ?? "";
|
|
22824
|
+
const maxDaily = db.select({ m: sql9`MAX(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq28(gscDailyTotals.projectId, projectId)).get()?.m ?? "";
|
|
22825
|
+
const maxDate = maxSearch > maxDaily ? maxSearch : maxDaily;
|
|
22392
22826
|
if (!maxDate) return null;
|
|
22393
22827
|
const startDate = windowStartDate(maxDate, windowDays2);
|
|
22394
|
-
const rows =
|
|
22395
|
-
|
|
22828
|
+
const rows = db.select().from(gscSearchData).where(
|
|
22829
|
+
and22(
|
|
22830
|
+
eq28(gscSearchData.projectId, projectId),
|
|
22831
|
+
gte5(gscSearchData.date, startDate),
|
|
22832
|
+
lte3(gscSearchData.date, maxDate)
|
|
22833
|
+
)
|
|
22834
|
+
).all();
|
|
22835
|
+
const dailyTotals = readGscDailyTotals(db, projectId, startDate, maxDate);
|
|
22396
22836
|
if (rows.length === 0 && dailyTotals.length === 0) return null;
|
|
22397
22837
|
let dimensionedClicks = 0;
|
|
22398
22838
|
const queryDayAgg = /* @__PURE__ */ new Map();
|
|
@@ -22492,12 +22932,16 @@ function buildGaSection(db, projectId, windowDays2) {
|
|
|
22492
22932
|
)
|
|
22493
22933
|
).limit(1).get() : void 0;
|
|
22494
22934
|
const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, projectId)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
22495
|
-
const
|
|
22496
|
-
if (!windowSummary && !fallbackSummary &&
|
|
22497
|
-
let snapshotMaxDate = "";
|
|
22498
|
-
for (const r of allSnapshotRows) if (r.date > snapshotMaxDate) snapshotMaxDate = r.date;
|
|
22935
|
+
const snapshotMaxDate = db.select({ m: sql9`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, projectId)).get()?.m ?? "";
|
|
22936
|
+
if (!windowSummary && !fallbackSummary && !snapshotMaxDate) return null;
|
|
22499
22937
|
const snapshotStartDate = snapshotMaxDate ? windowStartDate(snapshotMaxDate, windowDays2) : "";
|
|
22500
|
-
const snapshotRows = snapshotStartDate ?
|
|
22938
|
+
const snapshotRows = snapshotStartDate ? db.select().from(gaTrafficSnapshots).where(
|
|
22939
|
+
and22(
|
|
22940
|
+
eq28(gaTrafficSnapshots.projectId, projectId),
|
|
22941
|
+
gte5(gaTrafficSnapshots.date, snapshotStartDate),
|
|
22942
|
+
lte3(gaTrafficSnapshots.date, snapshotMaxDate)
|
|
22943
|
+
)
|
|
22944
|
+
).all() : db.select().from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, projectId)).all();
|
|
22501
22945
|
const totalSessions = windowSummary?.totalSessions ?? fallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
|
|
22502
22946
|
const totalUsers = windowSummary?.totalUsers ?? fallbackSummary?.totalUsers ?? snapshotRows.reduce((s, r) => s + r.users, 0);
|
|
22503
22947
|
const totalOrganicSessions = windowSummary?.totalOrganicSessions ?? fallbackSummary?.totalOrganicSessions ?? snapshotRows.reduce((s, r) => s + r.organicSessions, 0);
|
|
@@ -23102,7 +23546,7 @@ function buildInsightList(db, projectId, locationFilter) {
|
|
|
23102
23546
|
)
|
|
23103
23547
|
).orderBy(desc12(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
|
|
23104
23548
|
if (recentRunIds.length === 0) return [];
|
|
23105
|
-
const rows = db.select().from(insights).where(and22(eq28(insights.projectId, projectId),
|
|
23549
|
+
const rows = db.select().from(insights).where(and22(eq28(insights.projectId, projectId), inArray12(insights.runId, recentRunIds))).orderBy(desc12(insights.createdAt)).all();
|
|
23106
23550
|
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
23107
23551
|
const flat = rows.filter((r) => !r.dismissed).map((r) => {
|
|
23108
23552
|
const recommendation = r.recommendation;
|
|
@@ -24851,7 +25295,7 @@ async function organicEvidenceRoutes(app) {
|
|
|
24851
25295
|
}
|
|
24852
25296
|
|
|
24853
25297
|
// ../api-routes/src/citations.ts
|
|
24854
|
-
import { and as and25, eq as eq31, inArray as
|
|
25298
|
+
import { and as and25, eq as eq31, inArray as inArray13 } from "drizzle-orm";
|
|
24855
25299
|
async function citationRoutes(app) {
|
|
24856
25300
|
app.get("/projects/:name/citations/visibility", async (request, reply) => {
|
|
24857
25301
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -24875,7 +25319,7 @@ async function citationRoutes(app) {
|
|
|
24875
25319
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
24876
25320
|
answerMentioned: querySnapshots.answerMentioned,
|
|
24877
25321
|
createdAt: querySnapshots.createdAt
|
|
24878
|
-
}).from(querySnapshots).where(
|
|
25322
|
+
}).from(querySnapshots).where(inArray13(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
|
|
24879
25323
|
if (rawSnapshots.length === 0) {
|
|
24880
25324
|
return reply.send(emptyCitationVisibility("no-runs-yet"));
|
|
24881
25325
|
}
|
|
@@ -25014,7 +25458,7 @@ function computeCitationVisibility(input) {
|
|
|
25014
25458
|
}
|
|
25015
25459
|
|
|
25016
25460
|
// ../api-routes/src/visibility-stats.ts
|
|
25017
|
-
import { and as and26, desc as desc14, eq as eq32, inArray as
|
|
25461
|
+
import { and as and26, desc as desc14, eq as eq32, inArray as inArray14 } from "drizzle-orm";
|
|
25018
25462
|
|
|
25019
25463
|
// ../api-routes/src/visibility-compare.ts
|
|
25020
25464
|
var VISIBILITY_COMPARE_MIN_RUNS = 5;
|
|
@@ -25425,7 +25869,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25425
25869
|
citationState: querySnapshots.citationState,
|
|
25426
25870
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25427
25871
|
createdAt: querySnapshots.createdAt
|
|
25428
|
-
}).from(querySnapshots).where(
|
|
25872
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25429
25873
|
const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
|
|
25430
25874
|
const queryAttribution = buildQueryAttribution(projectQueries);
|
|
25431
25875
|
let shareOfVoice;
|
|
@@ -25440,7 +25884,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25440
25884
|
queryText: querySnapshots.queryText,
|
|
25441
25885
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25442
25886
|
answerText: querySnapshots.answerText
|
|
25443
|
-
}).from(querySnapshots).where(
|
|
25887
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25444
25888
|
const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
|
|
25445
25889
|
const result = buildMentionShare(
|
|
25446
25890
|
attributedSovSnapshots.map((s) => ({ projectMentioned: s.answerMentioned === true, answerText: s.answerText })),
|
|
@@ -25521,7 +25965,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25521
25965
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25522
25966
|
answerText: querySnapshots.answerText,
|
|
25523
25967
|
citedDomains: querySnapshots.citedDomains
|
|
25524
|
-
}).from(querySnapshots).where(
|
|
25968
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25525
25969
|
return { runCount: monthRuns.length, snapshots };
|
|
25526
25970
|
};
|
|
25527
25971
|
const fromMonth = loadMonth(fromBounds);
|
|
@@ -25776,7 +26220,7 @@ async function resultsExportRoutes(app) {
|
|
|
25776
26220
|
}
|
|
25777
26221
|
|
|
25778
26222
|
// ../api-routes/src/composites.ts
|
|
25779
|
-
import { eq as eq34, and as and28, desc as desc15, sql as sql12, like as like2, or as or7, inArray as
|
|
26223
|
+
import { eq as eq34, and as and28, desc as desc15, sql as sql12, like as like2, or as or7, inArray as inArray15 } from "drizzle-orm";
|
|
25780
26224
|
var TOP_INSIGHT_LIMIT = 5;
|
|
25781
26225
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
25782
26226
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -26043,7 +26487,7 @@ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
|
|
|
26043
26487
|
answerText: querySnapshots.answerText,
|
|
26044
26488
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
26045
26489
|
citedDomains: querySnapshots.citedDomains
|
|
26046
|
-
}).from(querySnapshots).where(
|
|
26490
|
+
}).from(querySnapshots).where(inArray15(querySnapshots.runId, [...runIds])).all();
|
|
26047
26491
|
for (const row of rows) {
|
|
26048
26492
|
const queryText = row.queryText?.trim() || null;
|
|
26049
26493
|
let queryId;
|
|
@@ -26678,6 +27122,12 @@ var SCHEMA_TABLE = {
|
|
|
26678
27122
|
SiteAuditRunResponseDto: siteAuditRunResponseSchema,
|
|
26679
27123
|
SiteAuditScoreDto: siteAuditScoreSchema,
|
|
26680
27124
|
SiteAuditTrendResponseDto: siteAuditTrendResponseSchema,
|
|
27125
|
+
SiteCrawlDeadLinksResponseDto: siteCrawlDeadLinksResponseSchema,
|
|
27126
|
+
SiteCrawlInternalLinksResponseDto: siteCrawlInternalLinksResponseSchema,
|
|
27127
|
+
SiteCrawlNeighborsResponseDto: siteCrawlNeighborsResponseSchema,
|
|
27128
|
+
SiteCrawlPagesResponseDto: siteCrawlPagesResponseSchema,
|
|
27129
|
+
SiteCrawlStructureResponseDto: siteCrawlStructureResponseSchema,
|
|
27130
|
+
SiteCrawlSummaryDto: siteCrawlSummarySchema,
|
|
26681
27131
|
SnapshotDiffResponse: snapshotDiffResponseSchema,
|
|
26682
27132
|
SnapshotListResponse: snapshotListResponseSchema,
|
|
26683
27133
|
SnapshotReportDto: snapshotReportSchema,
|
|
@@ -27134,6 +27584,18 @@ var measurementCursorParameter = {
|
|
|
27134
27584
|
description: "Opaque cursor from the previous page.",
|
|
27135
27585
|
schema: stringSchema
|
|
27136
27586
|
};
|
|
27587
|
+
var crawlCursorParameter = {
|
|
27588
|
+
name: "cursor",
|
|
27589
|
+
in: "query",
|
|
27590
|
+
description: "Opaque cursor from the previous bounded crawl response.",
|
|
27591
|
+
schema: stringSchema
|
|
27592
|
+
};
|
|
27593
|
+
var crawlLimitParameter = {
|
|
27594
|
+
name: "limit",
|
|
27595
|
+
in: "query",
|
|
27596
|
+
description: "Page size. Defaults to 100 (50 for structure/neighbors); maximum 200 (100 for structure/neighbors).",
|
|
27597
|
+
schema: { type: "integer", minimum: 1, maximum: 200 }
|
|
27598
|
+
};
|
|
27137
27599
|
var measurementOverviewSortParameter = {
|
|
27138
27600
|
name: "sort",
|
|
27139
27601
|
in: "query",
|
|
@@ -32585,11 +33047,122 @@ var routeCatalog = [
|
|
|
32585
33047
|
404: errorResponse("Project not found.")
|
|
32586
33048
|
}
|
|
32587
33049
|
},
|
|
33050
|
+
{
|
|
33051
|
+
method: "get",
|
|
33052
|
+
path: "/api/v1/projects/{name}/technical-aeo/crawl",
|
|
33053
|
+
summary: "Get persisted Technical AEO crawl metadata",
|
|
33054
|
+
description: "Returns the latest complete non-probe site-audit crawl, or the selected historical run (which may be partial). `hasCrawlData=false` never synthesizes a graph from legacy scorecard rows; `legacyAuditAvailable` says that the old score/pages/trend data can still be read separately.",
|
|
33055
|
+
tags: ["technical-aeo"],
|
|
33056
|
+
parameters: [
|
|
33057
|
+
nameParameter,
|
|
33058
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema }
|
|
33059
|
+
],
|
|
33060
|
+
responses: {
|
|
33061
|
+
200: jsonResponse("Persisted crawl summary returned.", "SiteCrawlSummaryDto"),
|
|
33062
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33063
|
+
}
|
|
33064
|
+
},
|
|
33065
|
+
{
|
|
33066
|
+
method: "get",
|
|
33067
|
+
path: "/api/v1/projects/{name}/technical-aeo/crawl/pages",
|
|
33068
|
+
summary: "List persisted Technical AEO crawl pages",
|
|
33069
|
+
description: "Cursor-paged canonical crawl nodes for the latest or selected crawl. `inventoryEligible` is Canonry technical-inventory eligibility, not a statement about actual Google index state.",
|
|
33070
|
+
tags: ["technical-aeo"],
|
|
33071
|
+
parameters: [
|
|
33072
|
+
nameParameter,
|
|
33073
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema },
|
|
33074
|
+
{ name: "inventoryEligible", in: "query", description: "Filter Canonry technical inventory eligibility (`true` or `false`).", schema: booleanSchema },
|
|
33075
|
+
{ name: "fetchState", in: "query", description: "Filter by persisted fetch state.", schema: stringSchema },
|
|
33076
|
+
{ name: "indexabilityState", in: "query", description: "Filter by crawler-derived indexability state; this is not Google index coverage.", schema: stringSchema },
|
|
33077
|
+
{ name: "auditState", in: "query", description: "Filter by audit state.", schema: stringSchema },
|
|
33078
|
+
{ name: "sort", in: "query", description: "Sort order. Defaults to `url`.", schema: { type: "string", enum: ["url", "path", "score-asc", "score-desc"] } },
|
|
33079
|
+
crawlCursorParameter,
|
|
33080
|
+
crawlLimitParameter
|
|
33081
|
+
],
|
|
33082
|
+
responses: {
|
|
33083
|
+
200: jsonResponse("Crawl pages returned.", "SiteCrawlPagesResponseDto"),
|
|
33084
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33085
|
+
}
|
|
33086
|
+
},
|
|
33087
|
+
{
|
|
33088
|
+
method: "get",
|
|
33089
|
+
path: "/api/v1/projects/{name}/technical-aeo/structure",
|
|
33090
|
+
summary: "List one level of Technical AEO crawl structure",
|
|
33091
|
+
description: "Returns immediate children under `parentPath` only. It is deliberately bounded and never emits a full site tree.",
|
|
33092
|
+
tags: ["technical-aeo"],
|
|
33093
|
+
parameters: [
|
|
33094
|
+
nameParameter,
|
|
33095
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema },
|
|
33096
|
+
{ name: "parentPath", in: "query", description: "Parent URL path. Defaults to `/`.", schema: stringSchema },
|
|
33097
|
+
crawlCursorParameter,
|
|
33098
|
+
{ name: "limit", in: "query", description: "Maximum immediate children. Defaults to 50; maximum 100.", schema: { type: "integer", minimum: 1, maximum: 100 } }
|
|
33099
|
+
],
|
|
33100
|
+
responses: {
|
|
33101
|
+
200: jsonResponse("Immediate crawl children returned.", "SiteCrawlStructureResponseDto"),
|
|
33102
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33103
|
+
}
|
|
33104
|
+
},
|
|
33105
|
+
{
|
|
33106
|
+
method: "get",
|
|
33107
|
+
path: "/api/v1/projects/{name}/technical-aeo/internal-links",
|
|
33108
|
+
summary: "List persisted internal crawl links",
|
|
33109
|
+
description: "Cursor-paged internal edges for the latest or selected crawl. Optional source/target/followability filters remain project-, run-, and attempt-scoped.",
|
|
33110
|
+
tags: ["technical-aeo"],
|
|
33111
|
+
parameters: [
|
|
33112
|
+
nameParameter,
|
|
33113
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema },
|
|
33114
|
+
{ name: "sourceUrl", in: "query", description: "Restrict to a source URL.", schema: stringSchema },
|
|
33115
|
+
{ name: "targetUrl", in: "query", description: "Restrict to a target URL.", schema: stringSchema },
|
|
33116
|
+
{ name: "followable", in: "query", description: "Restrict to followable or nofollow link observations.", schema: booleanSchema },
|
|
33117
|
+
crawlCursorParameter,
|
|
33118
|
+
crawlLimitParameter
|
|
33119
|
+
],
|
|
33120
|
+
responses: {
|
|
33121
|
+
200: jsonResponse("Internal crawl links returned.", "SiteCrawlInternalLinksResponseDto"),
|
|
33122
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33123
|
+
}
|
|
33124
|
+
},
|
|
33125
|
+
{
|
|
33126
|
+
method: "get",
|
|
33127
|
+
path: "/api/v1/projects/{name}/technical-aeo/internal-links/neighbors",
|
|
33128
|
+
summary: "Get a bounded internal-link neighborhood",
|
|
33129
|
+
description: "Returns bounded inbound and outbound internal edges for one `nodeKey` or URL; this is not a full graph traversal.",
|
|
33130
|
+
tags: ["technical-aeo"],
|
|
33131
|
+
parameters: [
|
|
33132
|
+
nameParameter,
|
|
33133
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema },
|
|
33134
|
+
{ name: "nodeKey", in: "query", description: "Canonical crawl node key.", schema: stringSchema },
|
|
33135
|
+
{ name: "url", in: "query", description: "Canonical crawl URL.", schema: stringSchema },
|
|
33136
|
+
{ name: "limit", in: "query", description: "Maximum inbound and outbound edges independently. Defaults to 50; maximum 100.", schema: { type: "integer", minimum: 1, maximum: 100 } }
|
|
33137
|
+
],
|
|
33138
|
+
responses: {
|
|
33139
|
+
200: jsonResponse("Bounded internal-link neighborhood returned.", "SiteCrawlNeighborsResponseDto"),
|
|
33140
|
+
400: errorResponse("A nodeKey or URL is required."),
|
|
33141
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33142
|
+
}
|
|
33143
|
+
},
|
|
33144
|
+
{
|
|
33145
|
+
method: "get",
|
|
33146
|
+
path: "/api/v1/projects/{name}/technical-aeo/dead-links",
|
|
33147
|
+
summary: "List dead-link findings when checks were enabled",
|
|
33148
|
+
description: "Returns a discriminated `disabled`, `complete`, `partial`, or `unavailable` state. A disabled check is never returned as an empty zero-result list.",
|
|
33149
|
+
tags: ["technical-aeo"],
|
|
33150
|
+
parameters: [
|
|
33151
|
+
nameParameter,
|
|
33152
|
+
{ name: "runId", in: "query", description: "Historical site-audit run ID. Omit for the latest crawl.", schema: stringSchema },
|
|
33153
|
+
crawlCursorParameter,
|
|
33154
|
+
crawlLimitParameter
|
|
33155
|
+
],
|
|
33156
|
+
responses: {
|
|
33157
|
+
200: jsonResponse("Dead-link status and findings returned.", "SiteCrawlDeadLinksResponseDto"),
|
|
33158
|
+
404: errorResponse("Project or crawl-bearing site-audit run not found.")
|
|
33159
|
+
}
|
|
33160
|
+
},
|
|
32588
33161
|
{
|
|
32589
33162
|
method: "post",
|
|
32590
33163
|
path: "/api/v1/projects/{name}/technical-aeo/runs",
|
|
32591
33164
|
summary: "Trigger a Technical AEO site-audit run",
|
|
32592
|
-
description: "Queues a `site-audit` run
|
|
33165
|
+
description: "Queues a `site-audit` run. The run discovers pages from the root, recursive sitemaps, and internal links. An active run is reused only when its persisted effective sitemap, budgets, depth, and dead-link option match exactly; a semantic mismatch returns 409.",
|
|
32593
33166
|
tags: ["technical-aeo"],
|
|
32594
33167
|
parameters: [nameParameter],
|
|
32595
33168
|
requestBody: {
|
|
@@ -32599,8 +33172,12 @@ var routeCatalog = [
|
|
|
32599
33172
|
schema: {
|
|
32600
33173
|
type: "object",
|
|
32601
33174
|
properties: {
|
|
32602
|
-
sitemapUrl: { ...stringSchema, description: "
|
|
32603
|
-
limit: { ...integerSchema, description: "
|
|
33175
|
+
sitemapUrl: { ...stringSchema, description: "Deprecated compatibility alias for the sitemap override." },
|
|
33176
|
+
limit: { ...integerSchema, minimum: 1, maximum: 2e3, description: "Deprecated compatibility alias for maxPages. Max 2000." },
|
|
33177
|
+
maxPages: { ...integerSchema, minimum: 1, maximum: 5e4, description: "Crawl page budget. Defaults to 1000; max 50000." },
|
|
33178
|
+
maxEdges: { ...integerSchema, minimum: 1, maximum: 1e6, description: "Internal-link observation budget. Defaults to 100000; max 1000000." },
|
|
33179
|
+
maxDepth: { ...integerSchema, minimum: 0, maximum: 100, description: "Maximum crawl depth from the root. Max 100." },
|
|
33180
|
+
checkDeadLinks: { ...booleanSchema, default: false, description: "Enable dead-link checking. Defaults to false." }
|
|
32604
33181
|
}
|
|
32605
33182
|
}
|
|
32606
33183
|
}
|
|
@@ -32609,6 +33186,7 @@ var routeCatalog = [
|
|
|
32609
33186
|
responses: {
|
|
32610
33187
|
200: jsonResponse("Site-audit run queued (or the in-flight run returned).", "SiteAuditRunResponseDto"),
|
|
32611
33188
|
400: errorResponse("Invalid site-audit request."),
|
|
33189
|
+
409: errorResponse("A crawl with different effective options is already queued or running."),
|
|
32612
33190
|
404: errorResponse("Project not found.")
|
|
32613
33191
|
}
|
|
32614
33192
|
}
|
|
@@ -33646,7 +34224,7 @@ function formatNotification(row) {
|
|
|
33646
34224
|
|
|
33647
34225
|
// ../api-routes/src/google.ts
|
|
33648
34226
|
import crypto29 from "crypto";
|
|
33649
|
-
import { eq as eq39, and as and30, desc as desc17, sql as sql13, inArray as
|
|
34227
|
+
import { eq as eq39, and as and30, desc as desc17, sql as sql13, inArray as inArray16 } from "drizzle-orm";
|
|
33650
34228
|
|
|
33651
34229
|
// ../api-routes/src/gbp-summary.ts
|
|
33652
34230
|
function computeMetricTotals(rows) {
|
|
@@ -36906,10 +37484,10 @@ async function googleRoutes(app, opts) {
|
|
|
36906
37484
|
locationProfiles: []
|
|
36907
37485
|
});
|
|
36908
37486
|
}
|
|
36909
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(and30(eq39(gbpDailyMetrics.projectId, project.id),
|
|
36910
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and30(eq39(gbpKeywordImpressions.projectId, project.id),
|
|
36911
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and30(eq39(gbpPlaceActions.projectId, project.id),
|
|
36912
|
-
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and30(eq39(gbpLodgingSnapshots.projectId, project.id),
|
|
37487
|
+
const metricRows = app.db.select().from(gbpDailyMetrics).where(and30(eq39(gbpDailyMetrics.projectId, project.id), inArray16(gbpDailyMetrics.locationName, locationNames))).all();
|
|
37488
|
+
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and30(eq39(gbpKeywordImpressions.projectId, project.id), inArray16(gbpKeywordImpressions.locationName, locationNames))).all();
|
|
37489
|
+
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and30(eq39(gbpPlaceActions.projectId, project.id), inArray16(gbpPlaceActions.locationName, locationNames))).all();
|
|
37490
|
+
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and30(eq39(gbpLodgingSnapshots.projectId, project.id), inArray16(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc17(gbpLodgingSnapshots.syncedAt)).all();
|
|
36913
37491
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
36914
37492
|
for (const row of lodgingRows) {
|
|
36915
37493
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -36923,7 +37501,7 @@ async function googleRoutes(app, opts) {
|
|
|
36923
37501
|
regularHours: gbpLocations.regularHours,
|
|
36924
37502
|
primaryPhone: gbpLocations.primaryPhone,
|
|
36925
37503
|
openStatus: gbpLocations.openStatus
|
|
36926
|
-
}).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, project.id),
|
|
37504
|
+
}).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, project.id), inArray16(gbpLocations.locationName, locationNames))).all();
|
|
36927
37505
|
return buildGbpSummary({
|
|
36928
37506
|
locationName,
|
|
36929
37507
|
locationCount: locationNames.length,
|
|
@@ -36939,7 +37517,7 @@ async function googleRoutes(app, opts) {
|
|
|
36939
37517
|
|
|
36940
37518
|
// ../api-routes/src/ads.ts
|
|
36941
37519
|
import crypto32 from "crypto";
|
|
36942
|
-
import { eq as eq41, and as and32, asc as asc8, desc as desc18, gt as gt2, gte as gte9, lt as lt6, lte as lte8, ne as ne4, inArray as
|
|
37520
|
+
import { eq as eq41, and as and32, asc as asc8, desc as desc18, gt as gt2, gte as gte9, lt as lt6, lte as lte8, ne as ne4, inArray as inArray17, or as or9, isNull as isNull3, isNotNull as isNotNull2, sql as sql15 } from "drizzle-orm";
|
|
36943
37521
|
|
|
36944
37522
|
// ../api-routes/src/ads-activation-routes.ts
|
|
36945
37523
|
import crypto31 from "crypto";
|
|
@@ -39886,7 +40464,7 @@ function semanticallyMatchesMaterializedEntity(app, input) {
|
|
|
39886
40464
|
eq41(adsOperations.projectId, input.projectId),
|
|
39887
40465
|
eq41(adsOperations.entityType, input.entityType),
|
|
39888
40466
|
eq41(adsOperations.entityId, input.entityId),
|
|
39889
|
-
|
|
40467
|
+
inArray17(adsOperations.kind, [createKind, updateKind])
|
|
39890
40468
|
)).orderBy(asc8(adsOperations.createdAt), asc8(adsOperations.id)).all();
|
|
39891
40469
|
if (rows.some((row) => row.state === AdsOperationStates.pending || row.state === AdsOperationStates.reconciling || row.state === AdsOperationStates.unknown || row.kind === updateKind && row.state === AdsOperationStates.succeeded)) {
|
|
39892
40470
|
return false;
|
|
@@ -40323,7 +40901,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
|
|
|
40323
40901
|
eq41(adsOperations.id, row.id),
|
|
40324
40902
|
eq41(adsOperations.projectId, row.projectId),
|
|
40325
40903
|
gte9(adsOperations.reconcileAttempts, policy.maxAttempts),
|
|
40326
|
-
|
|
40904
|
+
inArray17(adsOperations.state, [
|
|
40327
40905
|
AdsOperationStates.pending,
|
|
40328
40906
|
AdsOperationStates.unknown,
|
|
40329
40907
|
AdsOperationStates.reconciling
|
|
@@ -40547,7 +41125,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
|
|
|
40547
41125
|
eq41(adsOperations.reconcileStrategy, AdsReconcileStrategies.create_fingerprint),
|
|
40548
41126
|
or9(
|
|
40549
41127
|
isNotNull2(adsOperations.entityId),
|
|
40550
|
-
|
|
41128
|
+
inArray17(adsOperations.state, [
|
|
40551
41129
|
AdsOperationStates.pending,
|
|
40552
41130
|
AdsOperationStates.reconciling
|
|
40553
41131
|
])
|
|
@@ -40595,7 +41173,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
|
|
|
40595
41173
|
const eligibleProjectIds = [...credentialsByProjectId.keys()];
|
|
40596
41174
|
if (eligibleProjectIds.length === 0) return;
|
|
40597
41175
|
const rows = app.db.select().from(adsOperations).where(and32(
|
|
40598
|
-
|
|
41176
|
+
inArray17(adsOperations.projectId, eligibleProjectIds),
|
|
40599
41177
|
staleReconcileWhere
|
|
40600
41178
|
)).orderBy(asc8(adsOperations.updatedAt)).limit(config.batchSize).all();
|
|
40601
41179
|
for (const row of rows) {
|
|
@@ -40923,7 +41501,7 @@ async function adsRoutes(app, opts) {
|
|
|
40923
41501
|
const cursor = parsed.data.cursor ? decodeAdsOperationCursor(parsed.data.cursor, project.id, parsed.data.state) : void 0;
|
|
40924
41502
|
const rows = app.db.select().from(adsOperations).where(and32(
|
|
40925
41503
|
eq41(adsOperations.projectId, project.id),
|
|
40926
|
-
|
|
41504
|
+
inArray17(adsOperations.state, parsed.data.state),
|
|
40927
41505
|
cursor ? or9(
|
|
40928
41506
|
gt2(adsOperations.createdAt, cursor.createdAt),
|
|
40929
41507
|
and32(
|
|
@@ -41270,7 +41848,7 @@ async function adsRoutes(app, opts) {
|
|
|
41270
41848
|
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and32(
|
|
41271
41849
|
eq41(runs.projectId, project.id),
|
|
41272
41850
|
eq41(runs.kind, RunKinds["ads-sync"]),
|
|
41273
|
-
|
|
41851
|
+
inArray17(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
41274
41852
|
)).get();
|
|
41275
41853
|
if (inFlight) {
|
|
41276
41854
|
const existing = { runId: inFlight.id, status: inFlight.status };
|
|
@@ -54127,7 +54705,7 @@ async function doctorRoutes(app, opts) {
|
|
|
54127
54705
|
|
|
54128
54706
|
// ../api-routes/src/discovery/routes.ts
|
|
54129
54707
|
import crypto41 from "crypto";
|
|
54130
|
-
import { and as and42, desc as desc23, eq as eq53, gte as gte12, inArray as
|
|
54708
|
+
import { and as and42, desc as desc23, eq as eq53, gte as gte12, inArray as inArray18, isNull as isNull4, or as or10 } from "drizzle-orm";
|
|
54131
54709
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
54132
54710
|
async function discoveryRoutes(app, opts) {
|
|
54133
54711
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -54182,7 +54760,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
54182
54760
|
// must never reuse another set's session. Null (= Gemini-only
|
|
54183
54761
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
54184
54762
|
seedProviders == null ? isNull4(discoverySessions.seedProviders) : eq53(discoverySessions.seedProviders, seedProviders),
|
|
54185
|
-
|
|
54763
|
+
inArray18(discoverySessions.status, [
|
|
54186
54764
|
DiscoverySessionStatuses.queued,
|
|
54187
54765
|
DiscoverySessionStatuses.seeding,
|
|
54188
54766
|
DiscoverySessionStatuses.probing
|
|
@@ -54807,7 +55385,7 @@ function dedupeStrings(input) {
|
|
|
54807
55385
|
|
|
54808
55386
|
// ../api-routes/src/technical-aeo.ts
|
|
54809
55387
|
import crypto43 from "crypto";
|
|
54810
|
-
import { and as and43, asc as asc10, count, desc as desc24, eq as eq55, inArray as
|
|
55388
|
+
import { and as and43, asc as asc10, count, desc as desc24, eq as eq55, inArray as inArray19, lt as lt7, or as or11 } from "drizzle-orm";
|
|
54811
55389
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
54812
55390
|
function emptyScore(projectName) {
|
|
54813
55391
|
return {
|
|
@@ -54836,13 +55414,133 @@ function parsePositiveInt(value, fallback, max) {
|
|
|
54836
55414
|
if (!Number.isFinite(n) || n < 0) return fallback;
|
|
54837
55415
|
return Math.min(max, Math.floor(n));
|
|
54838
55416
|
}
|
|
55417
|
+
function parseBoundedLimit(value, fallback, max) {
|
|
55418
|
+
const parsed = parsePositiveInt(value, fallback, max);
|
|
55419
|
+
return Math.max(1, parsed);
|
|
55420
|
+
}
|
|
55421
|
+
function parseBoolean2(value) {
|
|
55422
|
+
if (value === true || value === "true" || value === "1") return true;
|
|
55423
|
+
if (value === false || value === "false" || value === "0") return false;
|
|
55424
|
+
return null;
|
|
55425
|
+
}
|
|
55426
|
+
function encodeCursor2(offset) {
|
|
55427
|
+
return Buffer.from(JSON.stringify({ offset })).toString("base64url");
|
|
55428
|
+
}
|
|
55429
|
+
function decodeCursor(value) {
|
|
55430
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 256) return 0;
|
|
55431
|
+
try {
|
|
55432
|
+
const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
55433
|
+
return typeof parsed.offset === "number" && Number.isSafeInteger(parsed.offset) && parsed.offset >= 0 ? Math.min(parsed.offset, 1e6) : 0;
|
|
55434
|
+
} catch {
|
|
55435
|
+
return 0;
|
|
55436
|
+
}
|
|
55437
|
+
}
|
|
55438
|
+
function normalizeParentPath(value) {
|
|
55439
|
+
if (typeof value !== "string" || value.trim() === "") return "/";
|
|
55440
|
+
const bare = value.trim().split(/[?#]/, 1)[0] ?? "/";
|
|
55441
|
+
const prefixed = bare.startsWith("/") ? bare : `/${bare}`;
|
|
55442
|
+
const collapsed = prefixed.replace(/\/{2,}/g, "/");
|
|
55443
|
+
return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
55444
|
+
}
|
|
55445
|
+
var MAX_STRUCTURE_SOURCE_ROWS = 5e4;
|
|
55446
|
+
function structureHierarchyPath(path8) {
|
|
55447
|
+
if (path8.length <= 1) return path8;
|
|
55448
|
+
return path8.replace(/\/+$/, "") || "/";
|
|
55449
|
+
}
|
|
55450
|
+
function structureChildPath(parentPath, path8) {
|
|
55451
|
+
const hierarchyPath = structureHierarchyPath(path8);
|
|
55452
|
+
if (hierarchyPath === parentPath) return null;
|
|
55453
|
+
const relative = parentPath === "/" ? hierarchyPath.startsWith("/") ? hierarchyPath.slice(1) : "" : hierarchyPath.startsWith(`${parentPath}/`) ? hierarchyPath.slice(parentPath.length + 1) : "";
|
|
55454
|
+
if (relative.length === 0) return null;
|
|
55455
|
+
const separator = relative.indexOf("/");
|
|
55456
|
+
const segment = separator === -1 ? relative : relative.slice(0, separator);
|
|
55457
|
+
return parentPath === "/" ? `/${segment}` : `${parentPath}/${segment}`;
|
|
55458
|
+
}
|
|
55459
|
+
function mapCrawlPage(row) {
|
|
55460
|
+
return {
|
|
55461
|
+
nodeKey: row.nodeKey,
|
|
55462
|
+
url: row.url,
|
|
55463
|
+
finalUrl: row.finalUrl,
|
|
55464
|
+
path: row.path,
|
|
55465
|
+
parentPath: row.parentPath,
|
|
55466
|
+
discoverySource: row.discoverySource,
|
|
55467
|
+
fetchState: row.fetchState,
|
|
55468
|
+
httpStatus: row.httpStatus,
|
|
55469
|
+
canonicalUrl: row.canonicalUrl,
|
|
55470
|
+
indexabilityState: row.indexabilityState,
|
|
55471
|
+
indexabilityReasons: row.indexabilityReasons,
|
|
55472
|
+
auditState: row.auditState,
|
|
55473
|
+
auditScore: row.auditScore,
|
|
55474
|
+
inventoryEligible: row.inventoryEligible,
|
|
55475
|
+
depth: row.depth,
|
|
55476
|
+
inboundUniqueEdges: row.inboundUniqueEdges,
|
|
55477
|
+
outboundUniqueEdges: row.outboundUniqueEdges,
|
|
55478
|
+
inboundOccurrences: row.inboundOccurrences,
|
|
55479
|
+
outboundOccurrences: row.outboundOccurrences,
|
|
55480
|
+
linkScoreRaw: row.linkScoreRaw,
|
|
55481
|
+
linkScoreNormalized: row.linkScoreNormalized
|
|
55482
|
+
};
|
|
55483
|
+
}
|
|
55484
|
+
function mapCrawlEdge(row) {
|
|
55485
|
+
return {
|
|
55486
|
+
edgeKey: row.edgeKey,
|
|
55487
|
+
sourceNodeKey: row.sourceNodeKey,
|
|
55488
|
+
sourceUrl: row.sourceUrl,
|
|
55489
|
+
targetNodeKey: row.targetNodeKey,
|
|
55490
|
+
targetUrl: row.targetUrl,
|
|
55491
|
+
relation: row.relation,
|
|
55492
|
+
internal: row.internal,
|
|
55493
|
+
followable: row.followable,
|
|
55494
|
+
occurrences: row.occurrences,
|
|
55495
|
+
followableOccurrences: row.followableOccurrences,
|
|
55496
|
+
nofollowOccurrences: row.nofollowOccurrences,
|
|
55497
|
+
anchors: row.anchors
|
|
55498
|
+
};
|
|
55499
|
+
}
|
|
54839
55500
|
async function technicalAeoRoutes(app, opts) {
|
|
55501
|
+
const resolveCrawl = (projectId, runId) => {
|
|
55502
|
+
const filters = [
|
|
55503
|
+
eq55(siteCrawlSnapshots.projectId, projectId),
|
|
55504
|
+
eq55(runs.projectId, projectId),
|
|
55505
|
+
eq55(runs.kind, RunKinds["site-audit"]),
|
|
55506
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
55507
|
+
notProbeRun()
|
|
55508
|
+
];
|
|
55509
|
+
if (runId) {
|
|
55510
|
+
filters.push(eq55(siteCrawlSnapshots.runId, runId));
|
|
55511
|
+
} else {
|
|
55512
|
+
filters.push(eq55(siteCrawlSnapshots.complete, true), eq55(runs.status, RunStatuses.completed));
|
|
55513
|
+
}
|
|
55514
|
+
return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq55(siteCrawlSnapshots.runId, runs.id)).where(and43(...filters)).orderBy(desc24(siteCrawlSnapshots.createdAt)).limit(1).get();
|
|
55515
|
+
};
|
|
55516
|
+
const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq55(siteAuditSnapshots.runId, runs.id)).where(and43(
|
|
55517
|
+
eq55(siteAuditSnapshots.projectId, projectId),
|
|
55518
|
+
eq55(runs.projectId, projectId),
|
|
55519
|
+
eq55(runs.kind, RunKinds["site-audit"]),
|
|
55520
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
55521
|
+
notProbeRun()
|
|
55522
|
+
)).limit(1).get());
|
|
55523
|
+
const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
|
|
55524
|
+
project: projectName,
|
|
55525
|
+
hasCrawlData: false,
|
|
55526
|
+
legacyAuditAvailable,
|
|
55527
|
+
runId: null,
|
|
55528
|
+
runStatus: null,
|
|
55529
|
+
rootUrl: null,
|
|
55530
|
+
effectiveOptions: {},
|
|
55531
|
+
complete: false,
|
|
55532
|
+
termination: null,
|
|
55533
|
+
detailsAvailable: false,
|
|
55534
|
+
counts: { pagesDiscovered: 0, pagesFetched: 0, pagesEligible: 0, edges: 0, findings: 0 },
|
|
55535
|
+
deadLinks: { state: "unavailable" }
|
|
55536
|
+
});
|
|
54840
55537
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
54841
55538
|
const project = resolveProject(app.db, request.params.name);
|
|
54842
55539
|
const baseFilters = [
|
|
54843
55540
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55541
|
+
eq55(runs.projectId, project.id),
|
|
54844
55542
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54845
|
-
|
|
55543
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54846
55544
|
notProbeRun()
|
|
54847
55545
|
];
|
|
54848
55546
|
const targetFilters = request.query.runId ? [...baseFilters, eq55(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
|
|
@@ -54880,8 +55578,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54880
55578
|
const project = resolveProject(app.db, request.params.name);
|
|
54881
55579
|
const targetFilters = [
|
|
54882
55580
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55581
|
+
eq55(runs.projectId, project.id),
|
|
54883
55582
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54884
|
-
|
|
55583
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54885
55584
|
notProbeRun()
|
|
54886
55585
|
];
|
|
54887
55586
|
if (request.query.runId) targetFilters.push(eq55(siteAuditSnapshots.runId, request.query.runId));
|
|
@@ -54893,7 +55592,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54893
55592
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
54894
55593
|
}
|
|
54895
55594
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
54896
|
-
const conds = [eq55(siteAuditPages.runId, latest.runId)];
|
|
55595
|
+
const conds = [eq55(siteAuditPages.projectId, project.id), eq55(siteAuditPages.runId, latest.runId)];
|
|
54897
55596
|
if (statusFilter) conds.push(eq55(siteAuditPages.status, statusFilter));
|
|
54898
55597
|
const where = and43(...conds);
|
|
54899
55598
|
const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
|
|
@@ -54921,41 +55620,363 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54921
55620
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
54922
55621
|
}).from(siteAuditSnapshots).innerJoin(runs, eq55(siteAuditSnapshots.runId, runs.id)).where(and43(
|
|
54923
55622
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55623
|
+
eq55(runs.projectId, project.id),
|
|
54924
55624
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54925
|
-
|
|
55625
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54926
55626
|
notProbeRun()
|
|
54927
55627
|
)).orderBy(desc24(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
54928
55628
|
return { project: project.name, points: rows.reverse() };
|
|
54929
55629
|
});
|
|
55630
|
+
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
55631
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55632
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55633
|
+
const legacyAuditAvailable = hasLegacyAudit(project.id);
|
|
55634
|
+
if (!target) {
|
|
55635
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55636
|
+
return emptyCrawlSummary(project.name, legacyAuditAvailable);
|
|
55637
|
+
}
|
|
55638
|
+
const snapshot = target.snapshot;
|
|
55639
|
+
const deadLinks = !snapshot.checkDeadLinks || snapshot.deadLinkState === "disabled" ? { state: "disabled" } : snapshot.deadLinkState === "complete" ? { state: "complete", checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound } : snapshot.deadLinkState === "partial" ? { state: "partial", checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound } : { state: "unavailable" };
|
|
55640
|
+
return {
|
|
55641
|
+
project: project.name,
|
|
55642
|
+
hasCrawlData: true,
|
|
55643
|
+
legacyAuditAvailable,
|
|
55644
|
+
runId: snapshot.runId,
|
|
55645
|
+
runStatus: target.runStatus,
|
|
55646
|
+
rootUrl: snapshot.rootUrl,
|
|
55647
|
+
crawlSchemaVersion: snapshot.crawlSchemaVersion,
|
|
55648
|
+
engineVersion: snapshot.engineVersion,
|
|
55649
|
+
normalizationVersion: snapshot.normalizationVersion,
|
|
55650
|
+
indexabilityVersion: snapshot.indexabilityVersion,
|
|
55651
|
+
linkScoreVersion: snapshot.linkScoreVersion,
|
|
55652
|
+
effectiveOptions: snapshot.effectiveOptions,
|
|
55653
|
+
complete: snapshot.complete,
|
|
55654
|
+
termination: snapshot.termination,
|
|
55655
|
+
detailsAvailable: snapshot.detailsAvailable,
|
|
55656
|
+
counts: {
|
|
55657
|
+
pagesDiscovered: snapshot.pagesDiscovered,
|
|
55658
|
+
pagesFetched: snapshot.pagesFetched,
|
|
55659
|
+
pagesEligible: snapshot.pagesEligible,
|
|
55660
|
+
edges: snapshot.edgesDiscovered,
|
|
55661
|
+
findings: snapshot.findingsCount
|
|
55662
|
+
},
|
|
55663
|
+
deadLinks
|
|
55664
|
+
};
|
|
55665
|
+
});
|
|
55666
|
+
app.get("/projects/:name/technical-aeo/crawl/pages", async (request) => {
|
|
55667
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55668
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55669
|
+
if (!target) {
|
|
55670
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55671
|
+
return { project: project.name, hasCrawlData: false, runId: null, total: 0, nextCursor: null, pages: [] };
|
|
55672
|
+
}
|
|
55673
|
+
const snapshot = target.snapshot;
|
|
55674
|
+
if (!snapshot.detailsAvailable || !snapshot.attemptId) {
|
|
55675
|
+
return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, pages: [] };
|
|
55676
|
+
}
|
|
55677
|
+
const filters = [
|
|
55678
|
+
eq55(siteCrawlPages.projectId, project.id),
|
|
55679
|
+
eq55(siteCrawlPages.runId, snapshot.runId),
|
|
55680
|
+
eq55(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
55681
|
+
];
|
|
55682
|
+
const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
|
|
55683
|
+
if (inventoryEligible != null) filters.push(eq55(siteCrawlPages.inventoryEligible, inventoryEligible));
|
|
55684
|
+
if (request.query.fetchState) filters.push(eq55(siteCrawlPages.fetchState, request.query.fetchState));
|
|
55685
|
+
if (request.query.indexabilityState) filters.push(eq55(siteCrawlPages.indexabilityState, request.query.indexabilityState));
|
|
55686
|
+
if (request.query.auditState) filters.push(eq55(siteCrawlPages.auditState, request.query.auditState));
|
|
55687
|
+
const where = and43(...filters);
|
|
55688
|
+
const total = app.db.select({ value: count() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
|
|
55689
|
+
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
55690
|
+
const offset = decodeCursor(request.query.cursor);
|
|
55691
|
+
const orderBy = request.query.sort === "score-desc" ? [desc24(siteCrawlPages.auditScore), asc10(siteCrawlPages.nodeKey)] : request.query.sort === "score-asc" ? [asc10(siteCrawlPages.auditScore), asc10(siteCrawlPages.nodeKey)] : request.query.sort === "path" ? [asc10(siteCrawlPages.path), asc10(siteCrawlPages.nodeKey)] : [asc10(siteCrawlPages.url), asc10(siteCrawlPages.nodeKey)];
|
|
55692
|
+
const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
|
|
55693
|
+
const nextOffset = offset + rows.length;
|
|
55694
|
+
return {
|
|
55695
|
+
project: project.name,
|
|
55696
|
+
hasCrawlData: true,
|
|
55697
|
+
runId: snapshot.runId,
|
|
55698
|
+
total,
|
|
55699
|
+
nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
|
|
55700
|
+
pages: rows.map(mapCrawlPage)
|
|
55701
|
+
};
|
|
55702
|
+
});
|
|
55703
|
+
app.get("/projects/:name/technical-aeo/structure", async (request) => {
|
|
55704
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55705
|
+
const parentPath = normalizeParentPath(request.query.parentPath);
|
|
55706
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55707
|
+
if (!target) {
|
|
55708
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55709
|
+
return { project: project.name, hasCrawlData: false, runId: null, parentPath, nextCursor: null, children: [] };
|
|
55710
|
+
}
|
|
55711
|
+
const snapshot = target.snapshot;
|
|
55712
|
+
if (!snapshot.detailsAvailable || !snapshot.attemptId) {
|
|
55713
|
+
return { project: project.name, hasCrawlData: true, runId: snapshot.runId, parentPath, nextCursor: null, children: [] };
|
|
55714
|
+
}
|
|
55715
|
+
const sourceRows = app.db.select({
|
|
55716
|
+
path: siteCrawlPages.path,
|
|
55717
|
+
url: siteCrawlPages.url,
|
|
55718
|
+
inventoryEligible: siteCrawlPages.inventoryEligible,
|
|
55719
|
+
fetchState: siteCrawlPages.fetchState
|
|
55720
|
+
}).from(siteCrawlPages).where(and43(
|
|
55721
|
+
eq55(siteCrawlPages.projectId, project.id),
|
|
55722
|
+
eq55(siteCrawlPages.runId, snapshot.runId),
|
|
55723
|
+
eq55(siteCrawlPages.attemptId, snapshot.attemptId)
|
|
55724
|
+
)).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
|
|
55725
|
+
if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
|
|
55726
|
+
throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
|
|
55727
|
+
}
|
|
55728
|
+
const children = /* @__PURE__ */ new Map();
|
|
55729
|
+
for (const row of sourceRows) {
|
|
55730
|
+
const childPath = structureChildPath(parentPath, row.path);
|
|
55731
|
+
if (!childPath) continue;
|
|
55732
|
+
const child = children.get(childPath) ?? {
|
|
55733
|
+
url: null,
|
|
55734
|
+
hasPage: false,
|
|
55735
|
+
pageCount: 0,
|
|
55736
|
+
inventoryEligibleCount: 0,
|
|
55737
|
+
fetchedCount: 0
|
|
55738
|
+
};
|
|
55739
|
+
child.pageCount++;
|
|
55740
|
+
if (row.inventoryEligible) child.inventoryEligibleCount++;
|
|
55741
|
+
if (["html", "redirect", "non-html", "fetch-error", "fetched"].includes(row.fetchState)) child.fetchedCount++;
|
|
55742
|
+
if (structureHierarchyPath(row.path) === childPath) {
|
|
55743
|
+
child.hasPage = true;
|
|
55744
|
+
if (child.url == null || row.url > child.url) child.url = row.url;
|
|
55745
|
+
}
|
|
55746
|
+
children.set(childPath, child);
|
|
55747
|
+
}
|
|
55748
|
+
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
55749
|
+
const offset = decodeCursor(request.query.cursor);
|
|
55750
|
+
const sortedChildren = [...children.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
55751
|
+
const page = sortedChildren.slice(offset, offset + limit + 1);
|
|
55752
|
+
const hasMore = page.length > limit;
|
|
55753
|
+
const visible = hasMore ? page.slice(0, limit) : page;
|
|
55754
|
+
return {
|
|
55755
|
+
project: project.name,
|
|
55756
|
+
hasCrawlData: true,
|
|
55757
|
+
runId: snapshot.runId,
|
|
55758
|
+
parentPath,
|
|
55759
|
+
nextCursor: hasMore ? encodeCursor2(offset + visible.length) : null,
|
|
55760
|
+
children: visible.map(([path8, child]) => ({
|
|
55761
|
+
path: path8,
|
|
55762
|
+
url: child.url,
|
|
55763
|
+
hasPage: child.hasPage,
|
|
55764
|
+
pageCount: child.pageCount,
|
|
55765
|
+
inventoryEligibleCount: child.inventoryEligibleCount,
|
|
55766
|
+
fetchedCount: child.fetchedCount
|
|
55767
|
+
}))
|
|
55768
|
+
};
|
|
55769
|
+
});
|
|
55770
|
+
app.get("/projects/:name/technical-aeo/internal-links", async (request) => {
|
|
55771
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55772
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55773
|
+
if (!target) {
|
|
55774
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55775
|
+
return { project: project.name, hasCrawlData: false, runId: null, total: 0, nextCursor: null, edges: [] };
|
|
55776
|
+
}
|
|
55777
|
+
const snapshot = target.snapshot;
|
|
55778
|
+
if (!snapshot.detailsAvailable || !snapshot.attemptId) {
|
|
55779
|
+
return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, edges: [] };
|
|
55780
|
+
}
|
|
55781
|
+
const filters = [
|
|
55782
|
+
eq55(siteCrawlEdges.projectId, project.id),
|
|
55783
|
+
eq55(siteCrawlEdges.runId, snapshot.runId),
|
|
55784
|
+
eq55(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
55785
|
+
eq55(siteCrawlEdges.internal, true)
|
|
55786
|
+
];
|
|
55787
|
+
if (request.query.sourceUrl) filters.push(eq55(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
|
|
55788
|
+
if (request.query.targetUrl) filters.push(eq55(siteCrawlEdges.targetUrl, request.query.targetUrl));
|
|
55789
|
+
const followable = parseBoolean2(request.query.followable);
|
|
55790
|
+
if (followable != null) filters.push(eq55(siteCrawlEdges.followable, followable));
|
|
55791
|
+
const where = and43(...filters);
|
|
55792
|
+
const total = app.db.select({ value: count() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
|
|
55793
|
+
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
55794
|
+
const offset = decodeCursor(request.query.cursor);
|
|
55795
|
+
const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc10(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
|
|
55796
|
+
const nextOffset = offset + rows.length;
|
|
55797
|
+
return {
|
|
55798
|
+
project: project.name,
|
|
55799
|
+
hasCrawlData: true,
|
|
55800
|
+
runId: snapshot.runId,
|
|
55801
|
+
total,
|
|
55802
|
+
nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
|
|
55803
|
+
edges: rows.map(mapCrawlEdge)
|
|
55804
|
+
};
|
|
55805
|
+
});
|
|
55806
|
+
app.get("/projects/:name/technical-aeo/internal-links/neighbors", async (request) => {
|
|
55807
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55808
|
+
if (!request.query.nodeKey && !request.query.url) {
|
|
55809
|
+
throw validationError("Provide nodeKey or url");
|
|
55810
|
+
}
|
|
55811
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55812
|
+
if (!target) {
|
|
55813
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55814
|
+
return {
|
|
55815
|
+
project: project.name,
|
|
55816
|
+
hasCrawlData: false,
|
|
55817
|
+
runId: null,
|
|
55818
|
+
nodeKey: request.query.nodeKey ?? null,
|
|
55819
|
+
url: request.query.url ?? null,
|
|
55820
|
+
inbound: [],
|
|
55821
|
+
outbound: [],
|
|
55822
|
+
inboundTruncated: false,
|
|
55823
|
+
outboundTruncated: false
|
|
55824
|
+
};
|
|
55825
|
+
}
|
|
55826
|
+
const snapshot = target.snapshot;
|
|
55827
|
+
if (!snapshot.detailsAvailable || !snapshot.attemptId) {
|
|
55828
|
+
return {
|
|
55829
|
+
project: project.name,
|
|
55830
|
+
hasCrawlData: true,
|
|
55831
|
+
runId: snapshot.runId,
|
|
55832
|
+
nodeKey: request.query.nodeKey ?? null,
|
|
55833
|
+
url: request.query.url ?? null,
|
|
55834
|
+
inbound: [],
|
|
55835
|
+
outbound: [],
|
|
55836
|
+
inboundTruncated: false,
|
|
55837
|
+
outboundTruncated: false
|
|
55838
|
+
};
|
|
55839
|
+
}
|
|
55840
|
+
const scope = [
|
|
55841
|
+
eq55(siteCrawlEdges.projectId, project.id),
|
|
55842
|
+
eq55(siteCrawlEdges.runId, snapshot.runId),
|
|
55843
|
+
eq55(siteCrawlEdges.attemptId, snapshot.attemptId),
|
|
55844
|
+
eq55(siteCrawlEdges.internal, true)
|
|
55845
|
+
];
|
|
55846
|
+
const inboundMatch = request.query.nodeKey && request.query.url ? or11(eq55(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq55(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq55(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq55(siteCrawlEdges.targetUrl, request.query.url);
|
|
55847
|
+
const outboundMatch = request.query.nodeKey && request.query.url ? or11(eq55(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq55(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq55(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq55(siteCrawlEdges.sourceUrl, request.query.url);
|
|
55848
|
+
const limit = parseBoundedLimit(request.query.limit, 50, 100);
|
|
55849
|
+
const inboundRows = app.db.select().from(siteCrawlEdges).where(and43(...scope, inboundMatch)).orderBy(asc10(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
55850
|
+
const outboundRows = app.db.select().from(siteCrawlEdges).where(and43(...scope, outboundMatch)).orderBy(asc10(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
|
|
55851
|
+
return {
|
|
55852
|
+
project: project.name,
|
|
55853
|
+
hasCrawlData: true,
|
|
55854
|
+
runId: snapshot.runId,
|
|
55855
|
+
nodeKey: request.query.nodeKey ?? null,
|
|
55856
|
+
url: request.query.url ?? null,
|
|
55857
|
+
inbound: inboundRows.slice(0, limit).map(mapCrawlEdge),
|
|
55858
|
+
outbound: outboundRows.slice(0, limit).map(mapCrawlEdge),
|
|
55859
|
+
inboundTruncated: inboundRows.length > limit,
|
|
55860
|
+
outboundTruncated: outboundRows.length > limit
|
|
55861
|
+
};
|
|
55862
|
+
});
|
|
55863
|
+
app.get("/projects/:name/technical-aeo/dead-links", async (request) => {
|
|
55864
|
+
const project = resolveProject(app.db, request.params.name);
|
|
55865
|
+
const target = resolveCrawl(project.id, request.query.runId);
|
|
55866
|
+
const legacyAuditAvailable = hasLegacyAudit(project.id);
|
|
55867
|
+
if (!target) {
|
|
55868
|
+
if (request.query.runId) throw notFound("Site crawl run", request.query.runId);
|
|
55869
|
+
return { project: project.name, runId: null, state: "unavailable", legacyAuditAvailable };
|
|
55870
|
+
}
|
|
55871
|
+
const snapshot = target.snapshot;
|
|
55872
|
+
if (!snapshot.checkDeadLinks || snapshot.deadLinkState === "disabled") {
|
|
55873
|
+
return { project: project.name, runId: snapshot.runId, state: "disabled", checkDeadLinks: false };
|
|
55874
|
+
}
|
|
55875
|
+
if (snapshot.deadLinkState === "unavailable") {
|
|
55876
|
+
return { project: project.name, runId: snapshot.runId, state: "unavailable", legacyAuditAvailable };
|
|
55877
|
+
}
|
|
55878
|
+
if (!snapshot.attemptId) {
|
|
55879
|
+
return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, total: 0, nextCursor: null, deadLinks: [] };
|
|
55880
|
+
}
|
|
55881
|
+
const where = and43(
|
|
55882
|
+
eq55(siteCrawlFindings.projectId, project.id),
|
|
55883
|
+
eq55(siteCrawlFindings.runId, snapshot.runId),
|
|
55884
|
+
eq55(siteCrawlFindings.attemptId, snapshot.attemptId),
|
|
55885
|
+
eq55(siteCrawlFindings.findingType, "dead-link")
|
|
55886
|
+
);
|
|
55887
|
+
const total = app.db.select({ value: count() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
|
|
55888
|
+
const limit = parseBoundedLimit(request.query.limit, 100, 200);
|
|
55889
|
+
const offset = decodeCursor(request.query.cursor);
|
|
55890
|
+
const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc10(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
|
|
55891
|
+
const nextOffset = offset + rows.length;
|
|
55892
|
+
const deadLinks = rows.map((row) => ({
|
|
55893
|
+
findingKey: row.findingKey,
|
|
55894
|
+
severity: row.severity,
|
|
55895
|
+
sourceNodeKey: row.sourceNodeKey,
|
|
55896
|
+
sourceUrl: row.sourceUrl,
|
|
55897
|
+
targetNodeKey: row.targetNodeKey,
|
|
55898
|
+
targetUrl: row.targetUrl,
|
|
55899
|
+
evidence: row.evidence
|
|
55900
|
+
}));
|
|
55901
|
+
const state = snapshot.deadLinkState === "complete" ? "complete" : "partial";
|
|
55902
|
+
return {
|
|
55903
|
+
project: project.name,
|
|
55904
|
+
runId: snapshot.runId,
|
|
55905
|
+
state,
|
|
55906
|
+
checkDeadLinks: true,
|
|
55907
|
+
checked: snapshot.deadLinksChecked,
|
|
55908
|
+
found: snapshot.deadLinksFound,
|
|
55909
|
+
total,
|
|
55910
|
+
nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
|
|
55911
|
+
deadLinks
|
|
55912
|
+
};
|
|
55913
|
+
});
|
|
54930
55914
|
app.post("/projects/:name/technical-aeo/runs", async (request) => {
|
|
54931
55915
|
const project = resolveProject(app.db, request.params.name);
|
|
54932
55916
|
const parsed = siteAuditRunRequestSchema.safeParse(request.body ?? {});
|
|
54933
55917
|
if (!parsed.success) {
|
|
54934
55918
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
|
|
54935
55919
|
}
|
|
54936
|
-
const
|
|
54937
|
-
|
|
54938
|
-
|
|
54939
|
-
|
|
54940
|
-
|
|
54941
|
-
|
|
54942
|
-
|
|
55920
|
+
const effectiveRequest = normalizeSiteAuditRunRequest(parsed.data);
|
|
55921
|
+
const identityKey = siteAuditRequestIdentity(effectiveRequest);
|
|
55922
|
+
const result = app.db.transaction((tx) => {
|
|
55923
|
+
const existing = tx.select({
|
|
55924
|
+
id: runs.id,
|
|
55925
|
+
status: runs.status,
|
|
55926
|
+
identityKey: siteCrawlRunRequests.identityKey,
|
|
55927
|
+
effectiveOptions: siteCrawlRunRequests.effectiveOptions
|
|
55928
|
+
}).from(runs).leftJoin(siteCrawlRunRequests, and43(
|
|
55929
|
+
eq55(siteCrawlRunRequests.projectId, runs.projectId),
|
|
55930
|
+
eq55(siteCrawlRunRequests.runId, runs.id)
|
|
55931
|
+
)).where(and43(
|
|
55932
|
+
eq55(runs.projectId, project.id),
|
|
55933
|
+
eq55(runs.kind, RunKinds["site-audit"]),
|
|
55934
|
+
inArray19(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
55935
|
+
)).get();
|
|
55936
|
+
if (existing) {
|
|
55937
|
+
if (existing.identityKey === identityKey) {
|
|
55938
|
+
return { runId: existing.id, status: existing.status, created: false };
|
|
55939
|
+
}
|
|
55940
|
+
throw operationInProgress(
|
|
55941
|
+
`A Technical AEO crawl is already ${existing.status} with different request options.`,
|
|
55942
|
+
{
|
|
55943
|
+
activeRunId: existing.id,
|
|
55944
|
+
activeStatus: existing.status,
|
|
55945
|
+
activeOptions: existing.effectiveOptions,
|
|
55946
|
+
requestedOptions: effectiveRequest
|
|
55947
|
+
}
|
|
55948
|
+
);
|
|
55949
|
+
}
|
|
55950
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
55951
|
+
const runId = crypto43.randomUUID();
|
|
55952
|
+
tx.insert(runs).values({
|
|
55953
|
+
id: runId,
|
|
55954
|
+
projectId: project.id,
|
|
55955
|
+
kind: RunKinds["site-audit"],
|
|
55956
|
+
status: RunStatuses.queued,
|
|
55957
|
+
trigger: RunTriggers.manual,
|
|
55958
|
+
createdAt: now
|
|
55959
|
+
}).run();
|
|
55960
|
+
tx.insert(siteCrawlRunRequests).values({
|
|
55961
|
+
runId,
|
|
55962
|
+
projectId: project.id,
|
|
55963
|
+
identityKey,
|
|
55964
|
+
effectiveOptions: effectiveRequest,
|
|
55965
|
+
createdAt: now
|
|
55966
|
+
}).run();
|
|
55967
|
+
return { runId, status: RunStatuses.queued, created: true };
|
|
55968
|
+
});
|
|
55969
|
+
if (result.created) {
|
|
55970
|
+
opts.onSiteAuditRequested?.(result.runId, project.id, {
|
|
55971
|
+
sitemapUrl: effectiveRequest.sitemapUrl ?? void 0,
|
|
55972
|
+
limit: parsed.data.limit,
|
|
55973
|
+
maxPages: effectiveRequest.maxPages,
|
|
55974
|
+
maxEdges: effectiveRequest.maxEdges,
|
|
55975
|
+
maxDepth: effectiveRequest.maxDepth ?? void 0,
|
|
55976
|
+
checkDeadLinks: effectiveRequest.checkDeadLinks
|
|
55977
|
+
});
|
|
54943
55978
|
}
|
|
54944
|
-
|
|
54945
|
-
const runId = crypto43.randomUUID();
|
|
54946
|
-
app.db.insert(runs).values({
|
|
54947
|
-
id: runId,
|
|
54948
|
-
projectId: project.id,
|
|
54949
|
-
kind: RunKinds["site-audit"],
|
|
54950
|
-
status: RunStatuses.queued,
|
|
54951
|
-
trigger: RunTriggers.manual,
|
|
54952
|
-
createdAt: now
|
|
54953
|
-
}).run();
|
|
54954
|
-
opts.onSiteAuditRequested?.(runId, project.id, {
|
|
54955
|
-
sitemapUrl: parsed.data.sitemapUrl,
|
|
54956
|
-
limit: parsed.data.limit
|
|
54957
|
-
});
|
|
54958
|
-
return { runId, status: RunStatuses.queued };
|
|
55979
|
+
return { runId: result.runId, status: result.status };
|
|
54959
55980
|
});
|
|
54960
55981
|
}
|
|
54961
55982
|
|
|
@@ -55105,6 +56126,7 @@ async function apiRoutes(app, opts) {
|
|
|
55105
56126
|
await api.register(competitorRoutes);
|
|
55106
56127
|
await api.register(runRoutes, {
|
|
55107
56128
|
onRunCreated: opts.onRunCreated,
|
|
56129
|
+
onRunCancelled: opts.onRunCancelled,
|
|
55108
56130
|
validProviderNames: opts.providerAdapters?.map((a) => a.name),
|
|
55109
56131
|
getRunnableProviderNames: opts.getRunnableProviderNames,
|
|
55110
56132
|
getEffectiveProviderModels: opts.getEffectiveProviderModels
|
|
@@ -55626,7 +56648,7 @@ var IntelligenceService = class {
|
|
|
55626
56648
|
const recentRuns = this.db.select().from(runs).where(
|
|
55627
56649
|
and45(
|
|
55628
56650
|
eq57(runs.projectId, projectId),
|
|
55629
|
-
|
|
56651
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
55630
56652
|
// Defensive: RunCoordinator already skips probes before this is
|
|
55631
56653
|
// called, but if a future call site invokes analyzeAndPersist
|
|
55632
56654
|
// directly for a probe, probes still must not pollute the
|
|
@@ -55929,7 +56951,7 @@ var IntelligenceService = class {
|
|
|
55929
56951
|
const allRuns = this.db.select().from(runs).where(
|
|
55930
56952
|
and45(
|
|
55931
56953
|
eq57(runs.projectId, project.id),
|
|
55932
|
-
|
|
56954
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
55933
56955
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
55934
56956
|
ne7(runs.trigger, RunTriggers.probe)
|
|
55935
56957
|
)
|
|
@@ -55962,7 +56984,7 @@ var IntelligenceService = class {
|
|
|
55962
56984
|
let wouldDeleteTotal = 0;
|
|
55963
56985
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
55964
56986
|
if (isDryRun && targetRuns.length > 0) {
|
|
55965
|
-
const rows = this.db.select({ runId: insights.runId }).from(insights).where(
|
|
56987
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray20(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
55966
56988
|
for (const r of rows) {
|
|
55967
56989
|
if (r.runId == null) continue;
|
|
55968
56990
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -56107,7 +57129,7 @@ var IntelligenceService = class {
|
|
|
56107
57129
|
and45(
|
|
56108
57130
|
eq57(runs.projectId, projectId),
|
|
56109
57131
|
eq57(runs.kind, RunKinds["answer-visibility"]),
|
|
56110
|
-
|
|
57132
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
56111
57133
|
// Defensive — see top of file.
|
|
56112
57134
|
ne7(runs.trigger, RunTriggers.probe)
|
|
56113
57135
|
)
|
|
@@ -56127,7 +57149,7 @@ var IntelligenceService = class {
|
|
|
56127
57149
|
const haveHistory = recentRunIds.length > 0;
|
|
56128
57150
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
56129
57151
|
if (haveHistory) {
|
|
56130
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and45(eq57(insights.type, "regression"),
|
|
57152
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and45(eq57(insights.type, "regression"), inArray20(insights.runId, recentRunIds))).all();
|
|
56131
57153
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
56132
57154
|
for (const row of priorRows) {
|
|
56133
57155
|
if (!row.runId) continue;
|
|
@@ -56233,6 +57255,13 @@ export {
|
|
|
56233
57255
|
gscCoverageSnapshots,
|
|
56234
57256
|
siteAuditSnapshots,
|
|
56235
57257
|
siteAuditPages,
|
|
57258
|
+
siteCrawlRunRequests,
|
|
57259
|
+
siteCrawlAttempts,
|
|
57260
|
+
siteCrawlSnapshots,
|
|
57261
|
+
siteCrawlPages,
|
|
57262
|
+
siteCrawlEdges,
|
|
57263
|
+
siteCrawlFindings,
|
|
57264
|
+
siteCrawlEventReceipts,
|
|
56236
57265
|
bingCoverageSnapshots,
|
|
56237
57266
|
bingUrlInspections,
|
|
56238
57267
|
gaTrafficSnapshots,
|