@canonry/canonry 4.154.1 → 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/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 +2 -2
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +14 -6
- package/assets/assets/{AuditHistoryPanel-WKf-vvmg.js → AuditHistoryPanel-BlbMBxOY.js} +1 -1
- package/assets/assets/{BacklinksPage-By06DbHN.js → BacklinksPage-DA9EaV9q.js} +1 -1
- package/assets/assets/{HistoryPage-C_PcPiyX.js → HistoryPage-BTrIHjMQ.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BHnNegEa.js → MeasurementPropertyPage-CN6o5xFg.js} +1 -1
- package/assets/assets/{ProjectPage-VTbW21dz.js → ProjectPage-CsDbgp_0.js} +1 -1
- package/assets/assets/{RunRow-9qn5Eer_.js → RunRow-ch_OlCPs.js} +1 -1
- package/assets/assets/{RunsPage-FxMTprK1.js → RunsPage-T89Y_yS8.js} +1 -1
- package/assets/assets/{SettingsPage-Cr7_PrNW.js → SettingsPage-DnmEq3F9.js} +1 -1
- package/assets/assets/{TrafficPage-D0qk9uMN.js → TrafficPage-CW1UhaJt.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CXa_3_Gy.js → TrafficSourceDetailPage-r4Ji3wJm.js} +1 -1
- package/assets/assets/{extract-error-message-RkTEH1Vb.js → extract-error-message-a8EErBoZ.js} +1 -1
- package/assets/assets/{index-nSWJ9yka.js → index-BKq6k5Of.js} +26 -26
- package/assets/index.html +1 -1
- package/dist/{chunk-ANSQ6JNO.js → chunk-OSHRDV36.js} +317 -4
- package/dist/{chunk-NNW7TJUZ.js → chunk-VIKPFI6F.js} +200 -5
- package/dist/{chunk-KRMLGFY3.js → chunk-VOL2BSM5.js} +1113 -92
- package/dist/{chunk-JOP2DGXL.js → chunk-YSESZNVT.js} +609 -165
- package/dist/cli.js +319 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-ZY5KOSZV.js → intelligence-service-G4NV3X36.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
|
@@ -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,
|
|
@@ -23110,7 +23546,7 @@ function buildInsightList(db, projectId, locationFilter) {
|
|
|
23110
23546
|
)
|
|
23111
23547
|
).orderBy(desc12(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
|
|
23112
23548
|
if (recentRunIds.length === 0) return [];
|
|
23113
|
-
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();
|
|
23114
23550
|
const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
23115
23551
|
const flat = rows.filter((r) => !r.dismissed).map((r) => {
|
|
23116
23552
|
const recommendation = r.recommendation;
|
|
@@ -24859,7 +25295,7 @@ async function organicEvidenceRoutes(app) {
|
|
|
24859
25295
|
}
|
|
24860
25296
|
|
|
24861
25297
|
// ../api-routes/src/citations.ts
|
|
24862
|
-
import { and as and25, eq as eq31, inArray as
|
|
25298
|
+
import { and as and25, eq as eq31, inArray as inArray13 } from "drizzle-orm";
|
|
24863
25299
|
async function citationRoutes(app) {
|
|
24864
25300
|
app.get("/projects/:name/citations/visibility", async (request, reply) => {
|
|
24865
25301
|
const project = resolveProject(app.db, request.params.name);
|
|
@@ -24883,7 +25319,7 @@ async function citationRoutes(app) {
|
|
|
24883
25319
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
24884
25320
|
answerMentioned: querySnapshots.answerMentioned,
|
|
24885
25321
|
createdAt: querySnapshots.createdAt
|
|
24886
|
-
}).from(querySnapshots).where(
|
|
25322
|
+
}).from(querySnapshots).where(inArray13(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
|
|
24887
25323
|
if (rawSnapshots.length === 0) {
|
|
24888
25324
|
return reply.send(emptyCitationVisibility("no-runs-yet"));
|
|
24889
25325
|
}
|
|
@@ -25022,7 +25458,7 @@ function computeCitationVisibility(input) {
|
|
|
25022
25458
|
}
|
|
25023
25459
|
|
|
25024
25460
|
// ../api-routes/src/visibility-stats.ts
|
|
25025
|
-
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";
|
|
25026
25462
|
|
|
25027
25463
|
// ../api-routes/src/visibility-compare.ts
|
|
25028
25464
|
var VISIBILITY_COMPARE_MIN_RUNS = 5;
|
|
@@ -25433,7 +25869,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25433
25869
|
citationState: querySnapshots.citationState,
|
|
25434
25870
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25435
25871
|
createdAt: querySnapshots.createdAt
|
|
25436
|
-
}).from(querySnapshots).where(
|
|
25872
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25437
25873
|
const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
|
|
25438
25874
|
const queryAttribution = buildQueryAttribution(projectQueries);
|
|
25439
25875
|
let shareOfVoice;
|
|
@@ -25448,7 +25884,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25448
25884
|
queryText: querySnapshots.queryText,
|
|
25449
25885
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25450
25886
|
answerText: querySnapshots.answerText
|
|
25451
|
-
}).from(querySnapshots).where(
|
|
25887
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25452
25888
|
const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
|
|
25453
25889
|
const result = buildMentionShare(
|
|
25454
25890
|
attributedSovSnapshots.map((s) => ({ projectMentioned: s.answerMentioned === true, answerText: s.answerText })),
|
|
@@ -25529,7 +25965,7 @@ async function visibilityStatsRoutes(app) {
|
|
|
25529
25965
|
answerMentioned: querySnapshots.answerMentioned,
|
|
25530
25966
|
answerText: querySnapshots.answerText,
|
|
25531
25967
|
citedDomains: querySnapshots.citedDomains
|
|
25532
|
-
}).from(querySnapshots).where(
|
|
25968
|
+
}).from(querySnapshots).where(inArray14(querySnapshots.runId, runIds)).all() : [];
|
|
25533
25969
|
return { runCount: monthRuns.length, snapshots };
|
|
25534
25970
|
};
|
|
25535
25971
|
const fromMonth = loadMonth(fromBounds);
|
|
@@ -25784,7 +26220,7 @@ async function resultsExportRoutes(app) {
|
|
|
25784
26220
|
}
|
|
25785
26221
|
|
|
25786
26222
|
// ../api-routes/src/composites.ts
|
|
25787
|
-
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";
|
|
25788
26224
|
var TOP_INSIGHT_LIMIT = 5;
|
|
25789
26225
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
25790
26226
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -26051,7 +26487,7 @@ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
|
|
|
26051
26487
|
answerText: querySnapshots.answerText,
|
|
26052
26488
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
26053
26489
|
citedDomains: querySnapshots.citedDomains
|
|
26054
|
-
}).from(querySnapshots).where(
|
|
26490
|
+
}).from(querySnapshots).where(inArray15(querySnapshots.runId, [...runIds])).all();
|
|
26055
26491
|
for (const row of rows) {
|
|
26056
26492
|
const queryText = row.queryText?.trim() || null;
|
|
26057
26493
|
let queryId;
|
|
@@ -26686,6 +27122,12 @@ var SCHEMA_TABLE = {
|
|
|
26686
27122
|
SiteAuditRunResponseDto: siteAuditRunResponseSchema,
|
|
26687
27123
|
SiteAuditScoreDto: siteAuditScoreSchema,
|
|
26688
27124
|
SiteAuditTrendResponseDto: siteAuditTrendResponseSchema,
|
|
27125
|
+
SiteCrawlDeadLinksResponseDto: siteCrawlDeadLinksResponseSchema,
|
|
27126
|
+
SiteCrawlInternalLinksResponseDto: siteCrawlInternalLinksResponseSchema,
|
|
27127
|
+
SiteCrawlNeighborsResponseDto: siteCrawlNeighborsResponseSchema,
|
|
27128
|
+
SiteCrawlPagesResponseDto: siteCrawlPagesResponseSchema,
|
|
27129
|
+
SiteCrawlStructureResponseDto: siteCrawlStructureResponseSchema,
|
|
27130
|
+
SiteCrawlSummaryDto: siteCrawlSummarySchema,
|
|
26689
27131
|
SnapshotDiffResponse: snapshotDiffResponseSchema,
|
|
26690
27132
|
SnapshotListResponse: snapshotListResponseSchema,
|
|
26691
27133
|
SnapshotReportDto: snapshotReportSchema,
|
|
@@ -27142,6 +27584,18 @@ var measurementCursorParameter = {
|
|
|
27142
27584
|
description: "Opaque cursor from the previous page.",
|
|
27143
27585
|
schema: stringSchema
|
|
27144
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
|
+
};
|
|
27145
27599
|
var measurementOverviewSortParameter = {
|
|
27146
27600
|
name: "sort",
|
|
27147
27601
|
in: "query",
|
|
@@ -32593,11 +33047,122 @@ var routeCatalog = [
|
|
|
32593
33047
|
404: errorResponse("Project not found.")
|
|
32594
33048
|
}
|
|
32595
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
|
+
},
|
|
32596
33161
|
{
|
|
32597
33162
|
method: "post",
|
|
32598
33163
|
path: "/api/v1/projects/{name}/technical-aeo/runs",
|
|
32599
33164
|
summary: "Trigger a Technical AEO site-audit run",
|
|
32600
|
-
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.",
|
|
32601
33166
|
tags: ["technical-aeo"],
|
|
32602
33167
|
parameters: [nameParameter],
|
|
32603
33168
|
requestBody: {
|
|
@@ -32607,8 +33172,12 @@ var routeCatalog = [
|
|
|
32607
33172
|
schema: {
|
|
32608
33173
|
type: "object",
|
|
32609
33174
|
properties: {
|
|
32610
|
-
sitemapUrl: { ...stringSchema, description: "
|
|
32611
|
-
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." }
|
|
32612
33181
|
}
|
|
32613
33182
|
}
|
|
32614
33183
|
}
|
|
@@ -32617,6 +33186,7 @@ var routeCatalog = [
|
|
|
32617
33186
|
responses: {
|
|
32618
33187
|
200: jsonResponse("Site-audit run queued (or the in-flight run returned).", "SiteAuditRunResponseDto"),
|
|
32619
33188
|
400: errorResponse("Invalid site-audit request."),
|
|
33189
|
+
409: errorResponse("A crawl with different effective options is already queued or running."),
|
|
32620
33190
|
404: errorResponse("Project not found.")
|
|
32621
33191
|
}
|
|
32622
33192
|
}
|
|
@@ -33654,7 +34224,7 @@ function formatNotification(row) {
|
|
|
33654
34224
|
|
|
33655
34225
|
// ../api-routes/src/google.ts
|
|
33656
34226
|
import crypto29 from "crypto";
|
|
33657
|
-
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";
|
|
33658
34228
|
|
|
33659
34229
|
// ../api-routes/src/gbp-summary.ts
|
|
33660
34230
|
function computeMetricTotals(rows) {
|
|
@@ -36914,10 +37484,10 @@ async function googleRoutes(app, opts) {
|
|
|
36914
37484
|
locationProfiles: []
|
|
36915
37485
|
});
|
|
36916
37486
|
}
|
|
36917
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(and30(eq39(gbpDailyMetrics.projectId, project.id),
|
|
36918
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and30(eq39(gbpKeywordImpressions.projectId, project.id),
|
|
36919
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and30(eq39(gbpPlaceActions.projectId, project.id),
|
|
36920
|
-
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();
|
|
36921
37491
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
36922
37492
|
for (const row of lodgingRows) {
|
|
36923
37493
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -36931,7 +37501,7 @@ async function googleRoutes(app, opts) {
|
|
|
36931
37501
|
regularHours: gbpLocations.regularHours,
|
|
36932
37502
|
primaryPhone: gbpLocations.primaryPhone,
|
|
36933
37503
|
openStatus: gbpLocations.openStatus
|
|
36934
|
-
}).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, project.id),
|
|
37504
|
+
}).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, project.id), inArray16(gbpLocations.locationName, locationNames))).all();
|
|
36935
37505
|
return buildGbpSummary({
|
|
36936
37506
|
locationName,
|
|
36937
37507
|
locationCount: locationNames.length,
|
|
@@ -36947,7 +37517,7 @@ async function googleRoutes(app, opts) {
|
|
|
36947
37517
|
|
|
36948
37518
|
// ../api-routes/src/ads.ts
|
|
36949
37519
|
import crypto32 from "crypto";
|
|
36950
|
-
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";
|
|
36951
37521
|
|
|
36952
37522
|
// ../api-routes/src/ads-activation-routes.ts
|
|
36953
37523
|
import crypto31 from "crypto";
|
|
@@ -39894,7 +40464,7 @@ function semanticallyMatchesMaterializedEntity(app, input) {
|
|
|
39894
40464
|
eq41(adsOperations.projectId, input.projectId),
|
|
39895
40465
|
eq41(adsOperations.entityType, input.entityType),
|
|
39896
40466
|
eq41(adsOperations.entityId, input.entityId),
|
|
39897
|
-
|
|
40467
|
+
inArray17(adsOperations.kind, [createKind, updateKind])
|
|
39898
40468
|
)).orderBy(asc8(adsOperations.createdAt), asc8(adsOperations.id)).all();
|
|
39899
40469
|
if (rows.some((row) => row.state === AdsOperationStates.pending || row.state === AdsOperationStates.reconciling || row.state === AdsOperationStates.unknown || row.kind === updateKind && row.state === AdsOperationStates.succeeded)) {
|
|
39900
40470
|
return false;
|
|
@@ -40331,7 +40901,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
|
|
|
40331
40901
|
eq41(adsOperations.id, row.id),
|
|
40332
40902
|
eq41(adsOperations.projectId, row.projectId),
|
|
40333
40903
|
gte9(adsOperations.reconcileAttempts, policy.maxAttempts),
|
|
40334
|
-
|
|
40904
|
+
inArray17(adsOperations.state, [
|
|
40335
40905
|
AdsOperationStates.pending,
|
|
40336
40906
|
AdsOperationStates.unknown,
|
|
40337
40907
|
AdsOperationStates.reconciling
|
|
@@ -40555,7 +41125,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
|
|
|
40555
41125
|
eq41(adsOperations.reconcileStrategy, AdsReconcileStrategies.create_fingerprint),
|
|
40556
41126
|
or9(
|
|
40557
41127
|
isNotNull2(adsOperations.entityId),
|
|
40558
|
-
|
|
41128
|
+
inArray17(adsOperations.state, [
|
|
40559
41129
|
AdsOperationStates.pending,
|
|
40560
41130
|
AdsOperationStates.reconciling
|
|
40561
41131
|
])
|
|
@@ -40603,7 +41173,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
|
|
|
40603
41173
|
const eligibleProjectIds = [...credentialsByProjectId.keys()];
|
|
40604
41174
|
if (eligibleProjectIds.length === 0) return;
|
|
40605
41175
|
const rows = app.db.select().from(adsOperations).where(and32(
|
|
40606
|
-
|
|
41176
|
+
inArray17(adsOperations.projectId, eligibleProjectIds),
|
|
40607
41177
|
staleReconcileWhere
|
|
40608
41178
|
)).orderBy(asc8(adsOperations.updatedAt)).limit(config.batchSize).all();
|
|
40609
41179
|
for (const row of rows) {
|
|
@@ -40931,7 +41501,7 @@ async function adsRoutes(app, opts) {
|
|
|
40931
41501
|
const cursor = parsed.data.cursor ? decodeAdsOperationCursor(parsed.data.cursor, project.id, parsed.data.state) : void 0;
|
|
40932
41502
|
const rows = app.db.select().from(adsOperations).where(and32(
|
|
40933
41503
|
eq41(adsOperations.projectId, project.id),
|
|
40934
|
-
|
|
41504
|
+
inArray17(adsOperations.state, parsed.data.state),
|
|
40935
41505
|
cursor ? or9(
|
|
40936
41506
|
gt2(adsOperations.createdAt, cursor.createdAt),
|
|
40937
41507
|
and32(
|
|
@@ -41278,7 +41848,7 @@ async function adsRoutes(app, opts) {
|
|
|
41278
41848
|
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and32(
|
|
41279
41849
|
eq41(runs.projectId, project.id),
|
|
41280
41850
|
eq41(runs.kind, RunKinds["ads-sync"]),
|
|
41281
|
-
|
|
41851
|
+
inArray17(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
41282
41852
|
)).get();
|
|
41283
41853
|
if (inFlight) {
|
|
41284
41854
|
const existing = { runId: inFlight.id, status: inFlight.status };
|
|
@@ -54135,7 +54705,7 @@ async function doctorRoutes(app, opts) {
|
|
|
54135
54705
|
|
|
54136
54706
|
// ../api-routes/src/discovery/routes.ts
|
|
54137
54707
|
import crypto41 from "crypto";
|
|
54138
|
-
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";
|
|
54139
54709
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
54140
54710
|
async function discoveryRoutes(app, opts) {
|
|
54141
54711
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -54190,7 +54760,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
54190
54760
|
// must never reuse another set's session. Null (= Gemini-only
|
|
54191
54761
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
54192
54762
|
seedProviders == null ? isNull4(discoverySessions.seedProviders) : eq53(discoverySessions.seedProviders, seedProviders),
|
|
54193
|
-
|
|
54763
|
+
inArray18(discoverySessions.status, [
|
|
54194
54764
|
DiscoverySessionStatuses.queued,
|
|
54195
54765
|
DiscoverySessionStatuses.seeding,
|
|
54196
54766
|
DiscoverySessionStatuses.probing
|
|
@@ -54815,7 +55385,7 @@ function dedupeStrings(input) {
|
|
|
54815
55385
|
|
|
54816
55386
|
// ../api-routes/src/technical-aeo.ts
|
|
54817
55387
|
import crypto43 from "crypto";
|
|
54818
|
-
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";
|
|
54819
55389
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
54820
55390
|
function emptyScore(projectName) {
|
|
54821
55391
|
return {
|
|
@@ -54844,13 +55414,133 @@ function parsePositiveInt(value, fallback, max) {
|
|
|
54844
55414
|
if (!Number.isFinite(n) || n < 0) return fallback;
|
|
54845
55415
|
return Math.min(max, Math.floor(n));
|
|
54846
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
|
+
}
|
|
54847
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
|
+
});
|
|
54848
55537
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
54849
55538
|
const project = resolveProject(app.db, request.params.name);
|
|
54850
55539
|
const baseFilters = [
|
|
54851
55540
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55541
|
+
eq55(runs.projectId, project.id),
|
|
54852
55542
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54853
|
-
|
|
55543
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54854
55544
|
notProbeRun()
|
|
54855
55545
|
];
|
|
54856
55546
|
const targetFilters = request.query.runId ? [...baseFilters, eq55(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
|
|
@@ -54888,8 +55578,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54888
55578
|
const project = resolveProject(app.db, request.params.name);
|
|
54889
55579
|
const targetFilters = [
|
|
54890
55580
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55581
|
+
eq55(runs.projectId, project.id),
|
|
54891
55582
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54892
|
-
|
|
55583
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54893
55584
|
notProbeRun()
|
|
54894
55585
|
];
|
|
54895
55586
|
if (request.query.runId) targetFilters.push(eq55(siteAuditSnapshots.runId, request.query.runId));
|
|
@@ -54901,7 +55592,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54901
55592
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
54902
55593
|
}
|
|
54903
55594
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
54904
|
-
const conds = [eq55(siteAuditPages.runId, latest.runId)];
|
|
55595
|
+
const conds = [eq55(siteAuditPages.projectId, project.id), eq55(siteAuditPages.runId, latest.runId)];
|
|
54905
55596
|
if (statusFilter) conds.push(eq55(siteAuditPages.status, statusFilter));
|
|
54906
55597
|
const where = and43(...conds);
|
|
54907
55598
|
const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
|
|
@@ -54929,41 +55620,363 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
54929
55620
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
54930
55621
|
}).from(siteAuditSnapshots).innerJoin(runs, eq55(siteAuditSnapshots.runId, runs.id)).where(and43(
|
|
54931
55622
|
eq55(siteAuditSnapshots.projectId, project.id),
|
|
55623
|
+
eq55(runs.projectId, project.id),
|
|
54932
55624
|
eq55(runs.kind, RunKinds["site-audit"]),
|
|
54933
|
-
|
|
55625
|
+
inArray19(runs.status, SURFACEABLE_STATUSES),
|
|
54934
55626
|
notProbeRun()
|
|
54935
55627
|
)).orderBy(desc24(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
54936
55628
|
return { project: project.name, points: rows.reverse() };
|
|
54937
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
|
+
});
|
|
54938
55914
|
app.post("/projects/:name/technical-aeo/runs", async (request) => {
|
|
54939
55915
|
const project = resolveProject(app.db, request.params.name);
|
|
54940
55916
|
const parsed = siteAuditRunRequestSchema.safeParse(request.body ?? {});
|
|
54941
55917
|
if (!parsed.success) {
|
|
54942
55918
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
|
|
54943
55919
|
}
|
|
54944
|
-
const
|
|
54945
|
-
|
|
54946
|
-
|
|
54947
|
-
|
|
54948
|
-
|
|
54949
|
-
|
|
54950
|
-
|
|
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
|
+
});
|
|
54951
55978
|
}
|
|
54952
|
-
|
|
54953
|
-
const runId = crypto43.randomUUID();
|
|
54954
|
-
app.db.insert(runs).values({
|
|
54955
|
-
id: runId,
|
|
54956
|
-
projectId: project.id,
|
|
54957
|
-
kind: RunKinds["site-audit"],
|
|
54958
|
-
status: RunStatuses.queued,
|
|
54959
|
-
trigger: RunTriggers.manual,
|
|
54960
|
-
createdAt: now
|
|
54961
|
-
}).run();
|
|
54962
|
-
opts.onSiteAuditRequested?.(runId, project.id, {
|
|
54963
|
-
sitemapUrl: parsed.data.sitemapUrl,
|
|
54964
|
-
limit: parsed.data.limit
|
|
54965
|
-
});
|
|
54966
|
-
return { runId, status: RunStatuses.queued };
|
|
55979
|
+
return { runId: result.runId, status: result.status };
|
|
54967
55980
|
});
|
|
54968
55981
|
}
|
|
54969
55982
|
|
|
@@ -55113,6 +56126,7 @@ async function apiRoutes(app, opts) {
|
|
|
55113
56126
|
await api.register(competitorRoutes);
|
|
55114
56127
|
await api.register(runRoutes, {
|
|
55115
56128
|
onRunCreated: opts.onRunCreated,
|
|
56129
|
+
onRunCancelled: opts.onRunCancelled,
|
|
55116
56130
|
validProviderNames: opts.providerAdapters?.map((a) => a.name),
|
|
55117
56131
|
getRunnableProviderNames: opts.getRunnableProviderNames,
|
|
55118
56132
|
getEffectiveProviderModels: opts.getEffectiveProviderModels
|
|
@@ -55634,7 +56648,7 @@ var IntelligenceService = class {
|
|
|
55634
56648
|
const recentRuns = this.db.select().from(runs).where(
|
|
55635
56649
|
and45(
|
|
55636
56650
|
eq57(runs.projectId, projectId),
|
|
55637
|
-
|
|
56651
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
55638
56652
|
// Defensive: RunCoordinator already skips probes before this is
|
|
55639
56653
|
// called, but if a future call site invokes analyzeAndPersist
|
|
55640
56654
|
// directly for a probe, probes still must not pollute the
|
|
@@ -55937,7 +56951,7 @@ var IntelligenceService = class {
|
|
|
55937
56951
|
const allRuns = this.db.select().from(runs).where(
|
|
55938
56952
|
and45(
|
|
55939
56953
|
eq57(runs.projectId, project.id),
|
|
55940
|
-
|
|
56954
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
55941
56955
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
55942
56956
|
ne7(runs.trigger, RunTriggers.probe)
|
|
55943
56957
|
)
|
|
@@ -55970,7 +56984,7 @@ var IntelligenceService = class {
|
|
|
55970
56984
|
let wouldDeleteTotal = 0;
|
|
55971
56985
|
const existingByRunId = /* @__PURE__ */ new Map();
|
|
55972
56986
|
if (isDryRun && targetRuns.length > 0) {
|
|
55973
|
-
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();
|
|
55974
56988
|
for (const r of rows) {
|
|
55975
56989
|
if (r.runId == null) continue;
|
|
55976
56990
|
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
@@ -56115,7 +57129,7 @@ var IntelligenceService = class {
|
|
|
56115
57129
|
and45(
|
|
56116
57130
|
eq57(runs.projectId, projectId),
|
|
56117
57131
|
eq57(runs.kind, RunKinds["answer-visibility"]),
|
|
56118
|
-
|
|
57132
|
+
or12(eq57(runs.status, "completed"), eq57(runs.status, "partial")),
|
|
56119
57133
|
// Defensive — see top of file.
|
|
56120
57134
|
ne7(runs.trigger, RunTriggers.probe)
|
|
56121
57135
|
)
|
|
@@ -56135,7 +57149,7 @@ var IntelligenceService = class {
|
|
|
56135
57149
|
const haveHistory = recentRunIds.length > 0;
|
|
56136
57150
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
56137
57151
|
if (haveHistory) {
|
|
56138
|
-
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();
|
|
56139
57153
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
56140
57154
|
for (const row of priorRows) {
|
|
56141
57155
|
if (!row.runId) continue;
|
|
@@ -56241,6 +57255,13 @@ export {
|
|
|
56241
57255
|
gscCoverageSnapshots,
|
|
56242
57256
|
siteAuditSnapshots,
|
|
56243
57257
|
siteAuditPages,
|
|
57258
|
+
siteCrawlRunRequests,
|
|
57259
|
+
siteCrawlAttempts,
|
|
57260
|
+
siteCrawlSnapshots,
|
|
57261
|
+
siteCrawlPages,
|
|
57262
|
+
siteCrawlEdges,
|
|
57263
|
+
siteCrawlFindings,
|
|
57264
|
+
siteCrawlEventReceipts,
|
|
56244
57265
|
bingCoverageSnapshots,
|
|
56245
57266
|
bingUrlInspections,
|
|
56246
57267
|
gaTrafficSnapshots,
|